【iOS】iOS逆向系列十一 - 签名机制(二)

通过上一章节了解了对称密码、公钥密码、单向散列函数、数字签名、证书等相关知识,本章节就开始介绍iOS的签名机制和重签名。

一、iOS签名机制

iOS签名机制的作用:保证安装到用户手机上的APP都是经过Apple官方允许的。

不管是真机调试,还是发布APP,开发者都需要经过一系列复杂的步骤:

  1. 生成CertificateSigningRequest.certSigningRequest文件;
  2. 获得ios_development.cer\ios_distribution.cer证书文件;
  3. 注册device、添加App ID;
  4. 获得*.mobileprovision文件。

对于真机调试,现在的Xcode已经自动帮开发者做了以上操作。

思考:每一步的作用是什么?.certSigningRequest.cer.mobileprovision文件究竟里面包含了什么?有何用处?

1.1. 流程图

大致流程分为以下几步:

  1. 对应的是keychain里的 “从证书颁发机构请求证书”,本地生成了一对密钥,保存的.certSigningRequest就是公钥,私钥保存在本地电脑里。
  2. 苹果自身实现。
  3. .certSigningRequest传到苹果后台,Apple后台的私钥对Mac设备的公钥进行签名,生成证书ios_development.cer、ios_distribution.cer,并下载到本地。
  4. 配置AppID/权限/设备等,最后下载.mobileprovision文件。
  5. Xcode会通过第3步下载回来的证书(存着公钥),在本地找到对应的私钥(第一步生成的),用本地私钥去签名 App(在Xcode中选择对应的证书时,实际上会找到keychain里对应的私钥去签名。这里私钥只有生成它的这台Mac有,如果别的Mac也要编译签名这个App怎么办?答案是在keychain里导出私钥(.p12文件),其他Mac打开并输入密码后会自动把这个私钥导入到钥匙串。),并把.mobileprovision文件命名为embedded.mobileprovision后一起打包到APP中。这里对App的签名数据保存分两部分,Mach-O可执行文件会把签名直接写入这个文件里,其他资源文件则会保存在_CodeSignature目录下。
  6. 在安装时,iOS系统取得证书,通过系统内置的公钥A,去验证embedded.mobileprovision的数字签名是否正确,里面的证书签名也会再验一遍。
  7. 确保embedded.mobileprovision里的数据都是苹果授权以后,就可以取出里面的数据,做各种验证,包括用公钥L验证APP签名,验证设备ID是否在ID列表上,AppID是否对应得上,权限开关是否跟APP里的Entitlements对应等。

证书:Mac公钥 + Apple签名信息。

Code Signing Identity:证书 + Mac私钥。

.p12文件:导出的Mac私钥就是p12文件格式。

Entitlements:授权机制决定了哪些系统资源在什么情况下允许被一个应用使用。简单的说它就是一个沙盒的配置列表,上面列出了哪些行为被允许,哪些会被拒绝。在Xcode的Capabilities中列举的功能都是需要配置授权的。

Provisioning Profile:证书 + 设备IDs + AppID + Entitlements + 签名信息。

上面的流程是双层签名认证。如果APP是从AppStore下载安装的,你会发现里面是没有mobileprovision文件的。它的验证流程会简单很多,大概如下所示。

提交App Store的应用是采用单层签名验证,只需要苹果自身的密钥就能完成。而企业签名、真机调试等情形,不仅需要苹果参与签名,而且需要开发者自身参与签名,因此称之为双层签名验证。

二、重签名

如果希望将破坏了签名的安装包安装到非越狱的手机上,需要对安装包进行重签名的操作。

注意:

  • 安装包中的可执行文件必须是经过脱壳的,重签名才会有效(通过Xcode或者其他脱壳软件市场安装的软件没有经过AppStore,所以不需要脱壳)。
  • app包内部的所有动态库(.framework.dylib)、AppExtension(PlugIns文件夹,拓展名是appex)、WatchApp(Watch文件夹)都需要重新签名。

重签名打包后,安装到设备的过程中,可能需要经常查看设备的日志信息,下面是查看日志的入口:

  • 程序运行过程中:Xcode -> Window -> Devices and Simulators -> View Device Logs

  • 程序安装过程中: Xcode -> Window -> Devices and Simulators -> Open Console或直接打开控制台.app

2.1. 重签名步骤

  1. 准备一个embedded.mobileprovision文件(必须是付费证书产生的,appId、device一定要匹配),并放入.app包中。

    • 可以通过Xcode自动生成,然后在编译后的APP包中找到

    • 可以去开发者证书网站生成下载

  2. embedded.mobileprovision文件中提取出entitlements.plist权限文件

    1
    2
    security cms -D -i embedded.mobileprovision > temp.plist
    /usr/libexec/PlistBuddy -x -c 'Print :Entitlements' temp.plist > entitlements.plist
  3. 查看可用的证书。

    1
    security find-identity -v -p codesigning
  4. .app内部的动态库、AppExtension等进行签名(注意:不需要entitlements文件)。

    1
    codesign -fs  证书ID  xxx.dylib
  5. .app包进行签名。

1
codesign -fs  证书ID  --entitlements entitlements.plist xxx.app

2.2. 重签名GUI工具

上面2.1步骤的重签名步骤都需要命令行进行操作,在Github上已经有相关的图形化工具,如下:

iOS App Signerhttps://github.com/DanTheMan827/ios-app-signer

  • 可以对.app重签名打包成ipa

  • 需要在.app包中提供对应的embedded.mobileprovision文件

2.3. 动态库注入

2.3.1. 动态库加载

APP运行时,会通过Mach-O文件的Load Commands中加载相关的动态库,但是我们新增的动态库(比如使用theos的tweak生成的动态库)并不在该文件中,所以会导致APP无法执行动态库的代码。

怎样才能让APP加载这个动态库呢?可以使用insert_dylibhttps://github.com/Tyilo/insert_dylib)库将动态库注入到Mach-O文件中。

1
insert_dylib 动态库加载路径 Mach-O文件

有2个常用参数选项:

  • --weak,即使动态库找不到也不会报错。

  • --all-yes,后面所有的提示的选项都为yes。

动态库加载路径是一个绝对路径,而动态库和Mach-O文件在同一个目录下面,所以可以使用下面2个常用环境变量:

  • @executable_path:代表可执行文件所在的目录。
  • @loader_path:代表动态库所在的目录。

默认情况下,上面的动态库注入命令完成后会在当前文件生成一个APP同名并且后缀为_patched的可执行文件(例:app_patched)。如果想要直接覆盖原来的Mach-O文件,可以在命令最后加上新生成文件的名字。

1
insert_dylib @executable_path/tweak_apptest.dylib apptest --all-yes --weak apptest

insert_dylib的本质是往Mach-O文件的Load Commands中添加了一个LC_LOAD_DYLIBLC_LOAD_WEAK_DYLIB

可以通过otool查看Mach-O/动态库的动态库依赖信息:otool -L Mach-O文件/动态库文件

2.3.2. 动态库依赖

动态库依赖其他非Apple官方提供的动态库时,需要把依赖的动态库也加入到ipa中。同时需要修改动态库的加载地址。

可以使用install_name_tool修改Mach-O文件中动态库的加载地址:

1
install_name_tool -change 动态库依赖旧地址 动态库依赖新地址 被依赖的动态库地址

越狱的手机,通过Theos开发的动态库插件(dylib)默认都依赖于/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate,每次加载动态库时都会到该目录下加载这个动态库。但是非越狱设备没有CydiaSubstrate动态库,如果要将开发的动态库插件打包到ipa中,也需要将CydiaSubstrate打包到ipa中,并且需要修改下CydiaSubstrate的加载地址。

步骤:

  1. CydiaSubstrate放到tweak_apptest.dylib同一个目录下(ipa根目录);
  2. 执行下面的命令,让APP加载tweak_apptest.dylib时去指定位置查找并加载CydiaSubstrate
1
install_name_tool -change /Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate @loader_path/CydiaSubstrate tweak_apptest.dylib

注意:动态库注入操作完成后,需要对动态库(包含依赖的动态库)和APP进行重新签名。