iOS逆向--应用签名

1,302 阅读17分钟

我们知道iOSAPP开发和按照都需要苹果颁布的一个证书,这是为什么呢,我们先了解一个概念,就是代码签名

代码签名是对可执行文件或脚本进行数字签名,用来确认软件在签名后未被修改或损坏的措施,和数字签名原理一样,只不过签名的数据是代码而已

在iOS出来之前,以前的主流操作系统(mac/windows)软件随便从哪里下载都能运行,系统安全存在隐患,盗版软件,病毒入侵,静默安装等等,那么苹果希望解决这一的问题,要保证每一个安装到iOS上的APP都是经过苹果官方允许的,怎样保证呢, 就是通过代码签名。

如果要实现验证,其实最简单的方式就是通过苹果官方生成非对称加密的一对公私钥,在iOS的系统中内置一个公钥,私钥由苹果后台保存,我们传APP到App Store时,苹果后台用私钥对APP数据进行签名,iOS系统下载这个APP后,用公钥验证这个签名,若签名正确,这个APP肯定是由苹果后台认证的,并且没有被修改过,也就达到了苹果的需求:保证按照的每一个APP都是经过苹果官运行的

但是如果我们iOS设备安装只从App Store这一个入口这件事就简单解决了,没有任何复杂的东西,一个数字签名搞定

但是实际上iOS安装APP还有其他渠道,比如对于我们开发者来说,我们需要在开发APP时候直接真机调试的,而且苹果还开放了企业内部分发的渠道,企业证书签名的APP也是需要顺利安装的

苹果需要开放这些方式安装APP,这些需求就无法通过简单的代码签名来办到了

为了实现苹果验证应用的一些需求,iOS签名的复杂度也就开始增加了,苹果给出的方案就是双层签名

iOS 双层代码签名流程有两个角色,一个是iOS系统,还有一个就是我们的mac系统,因为iOS 的APP开发环境实在mac系统下,所以这个依赖关系成为了苹果双层签名的基础,我们先画个图

  • 1、在mac系统中生产非对称加密算法的一堆公钥/私钥(XCode帮我们生成了),这里我们称为公钥M、私钥M,M=mac
  • 2、苹果自己有固定的一对公私钥,跟之前App Store原理一样,私钥在苹果后台,公钥在每个iOS系统中,这里称为公钥A、私钥A,A=apple
  • 3、把公钥M以及一些你开发者的信息传到苹果后台(这个就是csr文件),用苹果后台里的私钥A去签名公钥M,得到一份数据包含了公钥M以及其签名,这份数据就是证书
  • 4、在开发时,编译完一个APP后,用本地私钥M(也就是P12)对这个APP进行签名,同时把第三步得到证书一起打包进APP里,安装到手机上
  • 5、在安装时,iOS系统取得证书,通过系统内置的公钥A,去验证证书的数字签名是否正确
  • 6、验证证书后确保 私钥M是苹果认证过的,在用公钥M去验证APP的签名,这里就简介验证了这个APP安装行为是否经过苹果官方许可。(这里只验证安装行为,不验证APP是否被改动,因为开发阶段APP内容总是不断变化的,苹果不需要管)。

有了上面过程,已经可以保证开发者的认证和程序的安全性了,但是要知道iOS程序主要渠道是通过App Store才能分发到用户设备的,如果只有上述的过程,那岂不是只要申请了一个证书,就可以安装到所有iOS设备了吗?这时候苹果又引入了描述文件(Provisioning profile)。

描述文件一般包括三样东西:证书、APP ID、设备。当我们在真机运行或者打包一个项目的时候,证书用来证明我们程序额安全性和合法性

描述文件作用:

  • 限制了只有在苹果后台注册过的设备才可以安装
  • 限制了签名只能针对某一个具体APP

并且苹果还想控制APP里面的icloud/push/后台运行/调试器等这些全系所以苹果把这些权限开关统一称为entitlements(授权文件),并将这个文件和上面我们说的证书放在一个叫provisioning profile文件中。

描述文件是在appledevelop网站创建的(在XCode中填上appleID它会自动帮你创建),XCode运行时会打包进APP,所以我们私用CSR申请证书时,我们还要申请一个东西就是描述文件!

在开发时候,编译完一个APP后,用本地私钥M对这个APP进行签名,同时把从苹果服务器得到的provisioning profile文件打包进APP里,文件名为embedded.mobileprovision,把APP安装到手机上,最后进行系统验证。

我们可以查看一下provisioning profile文件,首先找到存放profile文件的地方

~/资源库/MobileDevice/Provisioning Profiles/

然后通过命令查看一下其中一个profile文件内容

security cms -D -i 1dcdead8-7191-49ee-abab-794ca5e0d67b.mobileprovision

我看到

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>AppIDName</key>
	<string>Credit</string>
	<key>ApplicationIdentifierPrefix</key>
	<array>
	<string>MN3L9MS2DL</string>
	</array>
	<key>CreationDate</key>
	<date>2018-10-08T06:27:56Z</date>
	<key>Platform</key>
	<array>
		<string>iOS</string>
	</array>
    <key>IsXcodeManaged</key>
    <false/>
	<key>DeveloperCertificates</key>
	<array>
		<data>MIIF0zCCBLugAwIBAgIIT39KgdKnyE0wDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTgwMTA5MDEzNDU3WhcNMTkwMTA5MDEzNDU3WjCBxjEaMBgGCgmSJomT8ixkAQEMCk1OM0w5TVMyREwxUzBRBgNVBAMMSmlQaG9uZSBEaXN0cmlidXRpb246IEJhb3RvdSBCQU9ZSU4gQ29uc3VtZXIgRmluYW5jZSBDby4sIEx0ZC4gKE1OM0w5TVMyREwpMRMwEQYDVQQLDApNTjNMOU1TMkRMMTEwLwYDVQQKDChCYW90b3UgQkFPWUlOIENvbnN1bWVyIEZpbmFuY2UgQ28uLCBMdGQuMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK6SNDvxvm3YAYMjMrckliRqIFfOw6AMPyWrlrR7N3SmH6+15j1JjARCvKHuSlq4F+9Of8TyG3cP96q8zZ2BAzzUJVDW8i5voax/PuRs7/Y1Og6MGJi2opdxXUwydtvSD6NqR0o0XXWxDCRHM8FIMo0YRH6DLBsWdNhmYl9P34RYO+2EU3PF/kM/AvNU/VW9vBFtXRQpxiD6AqRBSxqknjDvPwxGwD/XcvppSfeqr4R9ZZ7746Nm9eqUPKOVcTrONS3cdAiAFEYZy/AFzn9Umnt3xwb5ZEjB5HzzcyWG3EV9kH1T9UHRhv+v3hfCUDuEEhr3+49DZ9Wv8VnqdpNeficCAwEAAaOCAfEwggHtMD8GCCsGAQUFBwEBBDMwMTAvBggrBgEFBQcwAYYjaHR0cDovL29jc3AuYXBwbGUuY29tL29jc3AwMy13d2RyMTEwHQYDVR0OBBYEFOZQqhfB5g9HrJcrwvfzV8o7MAJhMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUiCcXCam2GGCL7Ou69kdZxVJUo7cwggEdBgNVHSAEggEUMIIBEDCCAQwGCSqGSIb3Y2QFATCB/jCBwwYIKwYBBQUHAgIwgbYMgbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjA2BggrBgEFBQcCARYqaHR0cDovL3d3dy5hcHBsZS5jb20vY2VydGlmaWNhdGVhdXRob3JpdHkvMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzATBgoqhkiG92NkBgEEAQH/BAIFADANBgkqhkiG9w0BAQsFAAOCAQEAS8SKmFpSkBdI/vpEMVb1QswxsJab9SmfE+BQbzVgbTXsbNbzrIhm0YefOicHmfLh9NcUiriar644SflNzbLbZ2gxkirH1KhzRgxzWTqQ9gqK7g+mj4NHXpNPTWAg13H8c0tqc5f22dsdlHVSC5oYqJKxqcjkY8+5dWC32OKS69r3a2ohTqzLlFLTSMoKLgds4IZb+Gcs78F6fDR5oKVorlnVMC+84cFWR/1rC/TgULeQ8jqnLyFY0B5wgVpahofoK1RpYzN3Sd82Yf9YyN3tJCFE8VJKqI/JwF/AGKgE7dH9/9NAophPI2LbxoQRtC7b4LoHYFvG5YI64pAGzhuYWQ==</data>
	</array>


	<key>Entitlements</key>
	<dict>
		<key>com.apple.developer.pass-type-identifiers</key>
		<array>
			<string>MN3L9MS2DL.*</string>
		</array>
		<key>keychain-access-groups</key>
		<array>
			<string>MN3L9MS2DL.*</string>
		</array>
		<key>get-task-allow</key>
		<false/>
		<key>application-identifier</key>
		<string>MN3L9MS2DL.com.baoyin.credit</string>
		<key>com.apple.developer.associated-domains</key>
		<string>*</string>
		<key>com.apple.developer.team-identifier</key>
		<string>MN3L9MS2DL</string>
		<key>aps-environment</key>
		<string>production</string>

	</dict>
	<key>ExpirationDate</key>
	<date>2019-01-09T01:34:57Z</date>
	<key>Name</key>
	<string>disProFile</string>
	<key>ProvisionedDevices</key>
	<array>
		<string>c9525fe01f1ac7f035d0445bdbead4f65cb7c6a5</string>
		<string>dee35e080b7c4ee1194076889578cb0b26a96fda</string>
		<string>b2c739f4aa7cc405c49478be17753be896f0fc06</string>
		<string>d9dc28a745aedfdf58530231452c65d5acd30435</string>
		<string>569a8993e80878881db57a6b224072704ef61703</string>
		<string>98a22a7f48a99f5485363ba52b69980971cba0cb</string>
		<string>596398bb2890076f95304456f3c7be2dea24e3ea</string>
		<string>86c55c132516638419998e5fac9ae3e37d7d96ac</string>
		<string>ccee448089ac72e7ad9529817726c64dcc233a70</string>
		<string>d53986882f309abf2e0b12935649dc66674c94cc</string>
		<string>618fd89fcd08d0ae522bf7e71c1bb926ac96e557</string>
		<string>7d74d8874344b353cd7b1e4bc3cb970681b890e3</string>
		<string>4ffc26b365d49b6292ba62a0fb53dfc8e6ea3f97</string>
		<string>2d04843592dd0a04a95496bc895d13fbe4a36a21</string>
		<string>ba6808502c82cbc640817913468efb607282db18</string>
		<string>94143f99a4c5b16f1422bff700a6ba5fccf33156</string>
		<string>227b7d4133771ca30ab6b2ca854bda1fa9eec2c6</string>
		<string>99248d09cbb78e11f93b96df03b7684860d71abf</string>
	</array>
	<key>TeamIdentifier</key>
	<array>
		<string>MN3L9MS2DL</string>
	</array>
	<key>TeamName</key>
	<string>Baotou BAOYIN Consumer Finance Co., Ltd.</string>
	<key>TimeToLive</key>
	<integer>92</integer>
	<key>UUID</key>
	<string>1dcdead8-7191-49ee-abab-794ca5e0d67b</string>
	<key>Version</key>
	<integer>1</integer>
</dict>
</plist>%

这个就是profile的内容,例如

  • Entitlements就是权限
  • get-task-allow是APP是否允许调试
  • application-identifier就是appid
  • ExpirationDate是过期时间
  • ProvisionedDevices设备列表

我们创建一个项目,编译下,然后打开product目录下面的.app文件,然后打开包内容

其中:

  • _CodeSignature是资源文件的前面
  • 代码签名在可执行文件mach_o文件里面
  • embedded.mobileprovision就是打包进去的描述文件

我们通过machOview打开这个可执行文件,在最下面有个code signature

这个就是代码签名

签名我们知道怎么来的了,我们来给应用重签名

获取ipa包

首先获取ipa包。获取ipa包方式有下面几种:

  • 助手下载,例如pp助手,都是没加密的
  • 越狱手机,正版是加密过的,倒闭是没加密过的
  • iTunes,12.6.3版本以下的

codesign

codesign是XCode提供的签名工具,我们可以通过几个命令就可以完成重签名,首先我们先看一下应用的签名信息:

首先我们先拿到微信的正常版ipa包和越狱的ipa包,然后分别对两个ipa进行解压:

我们先看一下非越狱的解压内容在Payload里面有一个WeChat.app包,我们先通过命令

codesign -d WeChat.app 得到

这个就是它的可执行文件的名

在看一下更详细的内容

codesign -d -v WeChat.app

这个时候还没有它的签名信息。我们看一下更详细的

codesign -d -vv WeChat.app

这时候就有签名信息了。其中Authority=iPhone Distribution就是他的签名信息,这时候签名是正常的,没有被破坏的,如果我们要重签名的话,我们需要用自己的证书对APP签名,我们先通过命令看一下自己电脑里面的证书:

security find-identity -v -p codesigning

这里就是我们电脑里面的证书

要对应用重签名,我们先看一下.app里面的内容,我们看一下WeChat包内容,

里面有个WeChat,这个是真正的可执行文件,我们通过otool工具看一下这个可执行文件内容

otool -l WeChat

我们发现内容特别多,不过我们可以把内容重定向一个文件里面去,

otool -l WeChat > ~/Desktop/123.txt

在这里很多信息,例如我们看一下cryptid

这个属性意思是为0,说明这个APP没有加密,非0代表加密了,等于1代表以1的形式加密

我们也可以通过命令看一下这个包有没有加密

otool -l WeChat |grep cryptid

有时候会发现2个cryptid,这是因为在该可执行文件里面有两个Mach-O文件,我们可以通过file WeChat,来查看

我们来对破解版的ipa进行重签名,因为正版的有加密标示,所以正版的无法重签名,就算重签名完成后也无法安装,我们把刚才解压的Payload文件夹copy出来,因为这个才是重签名需要的,我们进入到WeChat.app里面:

首先我们找到PlugIns文件夹,这里放的是插件,我们无法进行重签名,所以要把插件删除,

并且里面还有一个Watch,这个是手表APP,里面也有插件,因为我们不需要对手表APP进行重签名,,所以我们可以直接把watch整个删掉。

我们在对所有的framework进行签名,签名的命令是:

codesign -fs "钥匙串中的证书名" + framework名

这样把所有framework都进行重新签名

我们在找到WeChat可执行文件,我们要对整个app进行重签,我们需要一个描述文件 ,我们随便创建一个工程,然后打开products目录下的.app文件,里面的embedded.mobileprovision文件就是打包进去的描述文件

我们将这个描述文件copy到之前我们Payload -->WeChat.app 包里面去,但是因为这个描述文件是我们通用的bundle Identifier 所以我们需要把这里info.plist文件里面的bundle Identifier改成我们描述文件对应的,我们打开该描述文件:

security cms -D -i embedded.mobileprovision

里面的Entitlements这一块权限内容我们是需要的。其实这一块在我们开发过程中XCode已经帮我做好了,但是现在重签名需要我们自己来,所以现在我们copy过来

我们创建一个新工程,然后在里面创建一个entitilements.plist的plist文件

然后把我们刚才copy过来的内容粘贴进去

然后我们把这个文件copy到Payload目录下

然后我们通过命令进行重签名:

codesign -fs "证书串" --no-strict --entitlements=权限文件.plist :APP包

然后我们再看一下这个包的签名,发现签名证书变成我们自己的了

然后我们可以吧.app打包成.ipa包,

zip -ry WeChat.ipa Payload

然后我们通过XCode安装这个ipa包。

但是这个时候因为描述政府没有被信任,所以要先到设置里面把该证书信任一下,并且再打开时候,APP会闪退,这是因为该描述证书不是XCode装上去的,所以不被iOS系统承认,解决办法就是,我们先在手机上运行我们拿到授权文件的那个空工程,这个时候XCode帮我们安装了描述证书,然后再安装我们重签名的ipa包,这样就可以了。

但是这种重签名方式很麻烦,我们可以通过XCode进行重签名 ,步骤就是:

首先创建一个新工程,证书选择可以真机调试的证书,在真机上运行一下

然后我们拿到破解版的包,一样解压,把里面不能重签名的插件删除,再把info.plist的bundle Identifier修改一下,再将里面的frameword手动用上面新工程的证书一个个重签名一下

然后我们修改过的.app包替换掉新工的.app文件,记住新工程名字要和我们要重签名的包名一样

然后运行工程,这个时候我们就把重签名的包安装到我们APP上了,其实这个时候也就是XCode帮我们把权限文件那一块

  • security find-identity -v -p codesigning :列出钥匙串里可签名的证书
  • Codesign -fs "证书串"文件名 :强制替换签名
  • Chmod + x 可执行文件 :给文件添加权限
  • security cms -D -i ../embedded.mobileproision: 查看描述文件
  • codesign -fs "证书串" --no-strict --entitlements=权限文件.plist :APP包
  • Zip -ry 输出文件 输入文件:将输入文件压缩为输出文件

重签名步骤:

  • 1、删除插件和带有插件的.app包(比如watch)
  • 2、对Frameworks里面的库进行重签名
  • 3、给可执行文件+x(可执行)权限
  • 4、添加描述文件(新建工程,真机编译得到)
  • 5、替换BundleID
  • 6、通过授权文件(Entilements)重签.app包

shell脚本

shell是一种特殊的交互式工具,他为用户提供了启动程序、管理文件系统中文件以及运行在系统上的进程的途径,shell一般是指命令行工具,它允许你输入文本命令,然后解释命令,并且在内核中执行。 shell脚本,也就是用各类命令预先放入到一个文本文件中,方便一次性执行的一个脚本文件

脚本执行相关命令

  • source FileName:在当前shell环境中读取并执行fileName的命令,特点是可以强行让一个脚本去立即影响当前环境(一般用于加载配置文件),还会强制执行脚本中的全部命令,而忽略文件的权限,执行完后会跳到目录中
  • bash FileName 、zsj FileName :重新建立一个子shell,在子shell中执行脚本里面的句子
  • ./FileName :读取并执行文件中的命令,但有一个前提,脚本文件需要有可执行权限

在电脑中其实哟u多个shell,我们可以进到

cd /private/etc

然后看到这个目录下面有个shells,这个文件里面就写了我们电脑里面有多少shell,我们打开看一下

cat shells

一般默认都是bash,其中csh已经被tcsh替代了,sh也被bash替代了

关于操作系统推荐:

初探Linux

Unix和Linux都是多用户、多任务的系统,所以这样的系统里面也就有拥有了用户。组的概念,那么同样文件的权限也就有相应的所属用户和所属组了。

Windows其实是单用户的,虽然XP之后有多用户权限,但是是不健全的,他的结构:

而Linux一出来就是多用户的,系统有4个文件夹,是共有的,在home下有账户分组, 他的结构:

我们查看一个目录下所有文件以及权限:

ls -l

  • 第一列是文件权限

  • 文件连接数
  • 所属用户
  • 所有组
  • 文件大小
  • 修改日期
  • 文件名

我们如何修改文件的权限呢:

知道这些,我们就可以通过shell脚本自动重签名了

# ${SRCROOT} 它是工程文件所在的目录
TEMP_PATH="${SRCROOT}/Temp"
#资源文件夹,我们提前在工程目录下新建一个APP文件夹,里面放ipa包
ASSETS_PATH="${SRCROOT}/APP"
#目标ipa包路径
TARGET_IPA_PATH="${ASSETS_PATH}/*.ipa"
#清空Temp文件夹
rm -rf "${SRCROOT}/Temp"
mkdir -p "${SRCROOT}/Temp"



#----------------------------------------
# 1. 解压IPA到Temp下
unzip -oqq "$TARGET_IPA_PATH" -d "$TEMP_PATH"
# 拿到解压的临时的APP的路径
TEMP_APP_PATH=$(set -- "$TEMP_PATH/Payload/"*.app;echo "$1")
# echo "路径是:$TEMP_APP_PATH"


#----------------------------------------
# 2. 将解压出来的.app拷贝进入工程下
# BUILT_PRODUCTS_DIR 工程生成的APP包的路径
# TARGET_NAME target名称
TARGET_APP_PATH="$BUILT_PRODUCTS_DIR/$TARGET_NAME.app"
echo "app路径:$TARGET_APP_PATH"

rm -rf "$TARGET_APP_PATH"
mkdir -p "$TARGET_APP_PATH"
cp -rf "$TEMP_APP_PATH/" "$TARGET_APP_PATH"



#----------------------------------------
# 3. 删除extension和WatchAPP.个人证书没法签名Extention
rm -rf "$TARGET_APP_PATH/PlugIns"
rm -rf "$TARGET_APP_PATH/Watch"



#----------------------------------------
# 4. 更新info.plist文件 CFBundleIdentifier
#  设置:"Set : KEY Value" "目标文件路径"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $PRODUCT_BUNDLE_IDENTIFIER" "$TARGET_APP_PATH/Info.plist"


#----------------------------------------
# 5. 给MachO文件上执行权限
# 拿到MachO文件的路径
APP_BINARY=`plutil -convert xml1 -o - $TARGET_APP_PATH/Info.plist|grep -A1 Exec|tail -n1|cut -f2 -d\>|cut -f1 -d\<`
#上可执行权限
chmod +x "$TARGET_APP_PATH/$APP_BINARY"



#----------------------------------------
# 6. 重签名第三方 FrameWorks
TARGET_APP_FRAMEWORKS_PATH="$TARGET_APP_PATH/Frameworks"
if [ -d "$TARGET_APP_FRAMEWORKS_PATH" ];
then
for FRAMEWORK in "$TARGET_APP_FRAMEWORKS_PATH/"*
do

#签名
/usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" "$FRAMEWORK"
done
fi

创建一个shell脚本,放在工程的根目录下,然后在Build Phases下面创建一个Run Script ,在里面写上

$SRCROOT/脚本名.sh
  • 需要将脚本路径添加到创建的Run Script中
  • 注意:在工程中可能需要对SignApp.sh加可执行权限chmod +x SignApp.sh