iOS逆向学习之十二(iOS签名机制)

2,817 阅读15分钟

iOS签名机制的作用

在学习了上一章什么是加密解密、数字签名以及证书之后,现在我们再来学习iOS的签名机制就事半功倍了。其实iOS签名机制的作用就是保证安装到用户手机上的App都是经过Apple官方允许的。当然,越狱设备除外。

在平时开发时,不管是真机调试,还是发布App到App Store,我们都需要经过以下几个步骤

  • 首先,在Mac上生成CertificateSigningRequest.certSigningRequest文件
  • 然后在Apple开发者网站上获取到ios_development.cer或者ios_distribution.cer证书文件,前者是开发证书,后者是发布证书
  • 在Apple开发者网站上注册device,添加App ID。
  • 选择device、App ID以及开发证书或者生产证书,生成mobileprovision文件

经过以上的一系列操作之后,我们最终就可以拿到ios_development.cer或者ios_distribution.cer证书文件,以及mobileprovision描述文件。通过安装这些文件到Mac上就可以进行真机调试。当然,如果你在Xcode中勾选了Automatically manage signing选项,那么Xcode会自动帮我们执行以上的所有操作。

那么,以上操作每一步执行的作用是什么?最后获得的ios_development.cer或者ios_distribution.cer证书文件,以及mobileprovision描述文件的作用又是什么?这些文件中又包含了什么内容?

iOS签名流程

要想知道上述所有文件的具体作用,就需要了解iOS签名的完整流程。在使用Xcode编译、运行完项目之后,Xcode其实帮我们做了签名操作。

其实就是利用codeSign指令对.app文件进行签名操作。

准备

想要执行iOS签名流程,就需要有以下公钥、私钥信息

  • Mac设备的公钥和私钥,一般是Mac设备自己生成
  • Apple官方的私钥,保存在Apple的后台
  • Apple官方的公钥,每一台iPhone出厂之后都会保存Apple官方的公钥信息

签名流程分析

iOS项目在编译完成之后会生成.app文件,App的签名操作从拿到.app文件开始

  • 第一步,拿到.app文件之后,使用Mac私钥对.app文件进行签名操作,生成的签名文件存放在.app目录下的_CodeSignature/CodeResources

  • 第二步,使用Apple私钥对Mac公钥进行签名,生成证书文件

  • 第三步,将第二步获取到的证书文件和devices、app id以及entitlement一起使用Apple私钥再次进行签名,生成mobileprovision文件,也就是描述文件。

  • 第四步,将第一步签名过后的.app文件和第三步生成的mobileprovision文件一起压缩生成ipa安装包。

  • 最后,完整的签名打包过程如下

验证流程

对.app安装包进行签名之后,压缩生成ipa安装包,然后将ipa安装包安装到iPhone上时,会进行签名验证操作。

  • 第一步,使用iPhone上存放的Apple公钥验证mobileprovision文件中的签名。
  • 第二步,签名验证成功之后,拿到mobileprovision中存放的devices、app id以及entitlement信息。同时拿到证书文件。然后使用Apple公钥对证书中的签名进行验证。验证成功之后拿到Mac公钥。
  • 第三步,拿到Mac公钥之后,使用Mac公钥对App的签名文件进行验证,如果验证成功,则表明当前的App的源码没有被篡改过。然后此App就能安装到iPhone上
  • 最后得到完整的签名加验证流程,如下

在文件mobileprovision中,有devices、app id以及entitlement信息,它们的作用分别是:

  • devices标识着哪些设备可以安装此App,如果设备不在devices中,安装会失败。
  • app id,指定此标识的App才能安装,如果App的唯一标识和此app id不对应,则安装失败。
  • entitlement中存放在App所具备的权限信息,如果App所使用的权限和entitlement中存放的权限不一致,也会产生问题。

签名实际操作流程

上文学习了iOS签名的具体流程,现在,我们就来一步步的执行实际的操作来对我们所学的签名流程进行验证。同时了解ios_development.cer或者ios_distribution.cer证书文件,以及mobileprovision等文件的实际作用。

  • 第一步,在Mac上生成CertificateSigningRequest.certSigningRequest文件,其实这个文件就是Mac设备的公钥。

  • 第二步,登录Apple开发者网站,获取证书,安装到Mac设备。
    • 创建certificates

- 选择证书类型,有开发证书和发布证书。

- 上传CertificateSigningRequest.certSigningRequest文件,也就是Mac公钥信息。

- 下载证书,安装到Mac设备上

此步骤就是利用Apple的私钥,对Mac公钥进行签名,生成证书文件ios_development.cer和ios_distribution.cer

  • 第三步,生成mobileprovision
    • 创建mobileprovision,可以选择开发环境或者生产环境

- 选择App ID

- 选择设备devices

- 选择证书certificates

- 生成mobileprovision文件,下载到Mac上进行安装

此步骤就是将devices、app id、entitlement和证书文件,通过Apple私钥进行签名,生成最后的mobileprovision文件。而且生成的mobileprovision文件就决定了当前App可以安装的设备有哪些,可以安装的App的BundleId,以及App所拥有的权限。

Apple官方验证流程

Apple官方验证流程如下:

重签名

当我们逆向了一款App,为App编写了相应的插件,并且安装到了我们自己的越狱手机上。但是,逆向过的App只能在自己越狱手机上使用,如果我们想要将App和我们自己编写的插件重新打包,安装到未越狱的iPhone上,那就需要学习如何对App进行重签名。

学习重签名之前,需要注意几点

  • 第一,安装包中的可执行文件必须是进行过脱壳操作的,重签名才会生效,不然会安装失败
  • 第二,重签名所需要的mobileprovision文件必须是付费开发者账号申请的才可以,免费开发者账号无法进行重签名。
  • 第三,.app包中的所有动态库(.framework,.dylib)、AppExtension(PlugIns文件夹,拓展名是appex)、WatchApp(Watch文件夹)等都需要进行重签名操作

CodeSign指令重签名

具体步骤

  • 首先,需要准备一个embedded.mobileprovision文件(必须是付费开发者账号生成的,里面的appid、device等需要匹配),然后将此文件放入.app包中

生成mobileprovision文件有两种方式,第一种是通过Xcode自动生成,在编译后的App包中可以找到,第二种是到Apple官网生成,上文有详细步骤。

  • 从embedded.mobileprovision文件中提取出entitlements.plist权限文件,具体有两步:
// 首先从embedded.mobileprovision文件中导出权限信息,存放到temp.plist中
security cms -D -i embedded.mobileprovision > temp.plist

# 然后使用PlistBuddy工具将temp.plist转换成Entitlements格式的文件entitlements.plist
/usr/libexec/PlistBuddy -x -c 'Print :Entitlements' temp.plist > entitlements.plist
  • 查看Mac上可以使用的证书,获取到证书的Identity,后续签名需要使用,具体指令如下
security find-identity -v -p codesigning

得到的结果如下

➜  ~ security find-identity -v -p codesigning
  1) D9E2802126C89BF6BF6621064FC5547F895FC25E "iPhone Developer: xxxxx@xxxx.com (KT9PJDKFVG)"
  • 对.app包中的所有动态库、AppExtension等进行重签名,前提是修改过这些动态库或者AppExtension,如果没有修改的动态库或者AppExtension,可以跳过此步骤。指令如下:
# -fs 是 -f -s 的缩写
codesign -fs 证书ID xxx.dylib
  • 对.app包进行签名,需要使用到之前生成的entitlements.plist文件,指令如下:
codesign -fs 证书ID --entitlements entitlements.plist xxx.app

Theos插件重签名

使用Xcode编译自己的项目,我们知道源码,所以知道怎么修改Mach-O文件,但是如果我们在逆向别人的App时,是不知道别人的源码的,所以无法直接修改Mach-O文件。

之前的文章中学习了Theos,知道了怎么通过创建Tweak项目来修改App的行为,具体流程可以查看iOS逆向学习之六(Theos实战演练)这篇文章。

Tweak加载方式

创建了Tweak项目之后,通过Cydia安装到越狱手机上,然后就可以改变App的行为。具体是怎么实现的呢?

  • 首先,Tweak项目经过编译生成的是一个dylib动态库文件,存放在Tweak项目目录.theos/obj/debug/目录下。
  • 执行make package打包之后生成对应的deb文件,存放在packages目录下。
  • 执行make install之后,会通过Cydia安装到手机上,dylib文件存放在~/Library/MobileSubstrate/DynamicLibraries/目录下。
  • 在App启动之后,会同时将dylib加载到内存中,App中如果访问被我们hook的类中的方法,会直接执行dylib中的方法。

动态库的注入

Tweak项目本质上是生成动态库,而且动态库不是存放在.app目录下,所以,想要将我们逆向过的App安装到别人的手机上,首先需要做的就是将Tweak项目生成的动态库注入到App中的可执行文件中,也就是Mach-O文件中。

可以使用insert_dylib库来将动态库注入到Mach-O文件中,可以通过insert_dylib库主页下载insert_dylib工具。在Release环境下编译,得到命令行工具,将命令行工具放在/usr/local/bin目录下。

insert_dylib库用法

insert_dylib的本质其实就是往Mach-O文件的Load Commands中添加了一个LC_LOAD_DYLIB或者LC_LOAD_WEAK_DYLIB。具体注入方法如下:

insert_dylib 动态库加载路径 Mach-O文件 --all-yes --weak
  • --weak选项表示,即使当前注入的动态库找不到,App也不会报错
  • --all-yes选项表示,后面所有的选项都选yes
查看Mach-O的动态库依赖信息

查看动态库依赖信息两种方式

  • 通过otool查看Mach-O的动态库依赖信息
otool -L Mach-O文件

  • 通过MachOView查看Mach-O的动态库依赖信息

更改动态库的加载地址

在向Mach-O文件中注入动态库之后,需要更改Mach-O文件中动态库的加载地址,否则在App运行时,会因为找不到动态库报错。

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

install_name_tool -change 旧地址 新地址 Mach-O文件

需要注意的是,上述指令中的新地址,必须填写全路径地址,但是我们不知道App安装到手机上后dylib存放的具体地址,因此,可以使用以下两个常用的环境变量:

  • @executable_path代表可执行文件所在的目录,也就是Mach-O文件所在目录。我们将dylib和可执行文件放在同一个目录下,然后将地址修改为@executable_path/dylib名称。这就表示在加载动态库时,到可执行文件所在的目录下去寻找动态库。
  • @loader_path代表动态库所在的目录,此环境变量一般在动态库依赖其它动态库的情况下使用,如果我们需要注入的动态库还依赖其它动态库,那么就需要将所依赖的动态库和原动态库存放在同一目录下,然后更改动态库的加载地址为@loader_path/动态库名称。这表示到原动态库所在目录下加载需要依赖的动态库。

Theos开发的动态库插件注意事项

  • 我们使用Theos开发的动态库插件(dylib)因为使用过Cydia安装的,所以它默认是依赖CydiaSubstrate插件的。CydiaSubstrate插件存放目录为iPhone的/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate。
  • 如果想要将我们开发的动态库插件打包到ipa中, 就需要同时将CydiaSubstrate一起打包到ipa中,并且需要修改CydiaSubstrate的加载地址。

重签名GUI工具

iReSign

可以点击下载iReSign源码,运行里面的Mac应用,提供.app包的路径、entitlements.plist路径和embedded.mobileprovision的路径,就可以对.app进行重签名,然后打包生成ipa文件。

iOS App Signer

可以点击下载iOS App Signer源码,选择Release环境进行编译,拿到编译后的Mac应用,就可以直接使用。只需提供.app包的路径和embedded.mobileprovision的路径

重签名练习

练习一、使用codesign指令对Xcode生成.app文件进行重签名

现在我们就来使用Xcode生成.app包,然后一步一步实现重签名的过程。

  • 首先,创建iOS项目TestSign,在ViewController中增加如下代码
#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *labelA;
@property (weak, nonatomic) IBOutlet UILabel *labelB;
@property (weak, nonatomic) IBOutlet UILabel *labelResult;

@end

@implementation ViewController

int a = 10;
int b = 20;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.labelA.text = [NSString stringWithFormat:@"%d",a];
    self.labelB.text = [NSString stringWithFormat:@"%d",b];
    self.labelResult.text = [NSString stringWithFormat:@"%d",a + b];
}


@end
  • 运行项目在界面上可以看到10 + 20 = 30的效果,此时,拿到编译Product目录下生成的.app包TestSign.app
  • 此时,创建PayLoad文件夹,将TestSign.app放到文件夹中,压缩PayLoad文件夹,修改压缩文件的后缀为.ipa,将PayLoad.ipa直接安装到手机上,是可以直接安装成功的,因为编译的时候就是选择的是真机编译,而且mobileprovision文件也是由Xcode自动生成,包含当前设备。
  • 通过之前的学习我们知道,在Mach-O文件中,全局变量存放的位置是__DATA段,使用MachOView打开estSign.app中的可执行文件,如下

<font color=red>0A</font>转换成10进制就是10,对应着全局变量a,<font color=red>14</font>转换成10进制就是20,对应着全局变量b。
  • 直接修改Mach-O文件,将全局变量a的值修改成14,也就是将a的值改为20。

  • 重新压缩生成.ipa文件,使用iFunBox将ipa安装到同一台手机上,会出现安装失败提示,原因是.app中的Mach-O文件已经被篡改,它的签名也将会失效。所以安装在iPhone上时对App的签名验证也会失败,所以会导致安装失败。此时就需要对.app进行重签名。

  • 拿到TestSign.app包中的embedded.mobileprovision文件,从embedded.mobileprovision文件中提取出entitlements.plist文件,指令如下:

// 首先从embedded.mobileprovision文件中导出权限信息,存放到temp.plist中
security cms -D -i embedded.mobileprovision > temp.plist

# 然后使用PlistBuddy工具将temp.plist转换成Entitlements格式的文件entitlements.plist
/usr/libexec/PlistBuddy -x -c 'Print :Entitlements' temp.plist > entitlements.plist

  • 通过以下指令获取到证书ID,要与embedded.mobileprovision文件中的证书保持一致:
security find-identity -v -p codesigning
  • 使用codesign指令对.app包进行重签名
codesign -fs 1BEA2FE8783A297CF30B7728849EB225231D67E8 --entitlements entitlements.plist TestSign.app 

出现上图效果,表明重签名成功,此时,重新压缩TestSign.app文件,得到ipa,安装到之前的手机上就可以安装成功,同时,因为全局变量a的值被我们改成了20,所以界面上展示的是20 + 20 = 40。至此,重签名操作完成。

练习二、Theos开发插件,注入腾讯视频App,安装到非越狱手机

下面为腾讯视频开发Theos插件,然后注入到腾讯视频App,并且重新生成ipa,安装到非越狱手机上,

  • 首先,下载腾讯视频App,可以通过爱思助手、PP助手下载已经进行过脱壳的App,或者通过App Store下载,然后使用Clutch工具进行脱壳操作。注意,必须要是脱壳后的App才能进行重签名,否则签名无效。
  • 连接手机,使用Reveal查看腾讯视频的页面,找到主界面QLHomeController。具体流程可参考iOS逆向学习之二(Mac远程操控iPhone)和iOS逆向学习之三(Cycript)
  • 创建Tweak项目。使用nic.pl指令创建Tweak项目,按步骤填写所需要的信息。注意MobileSubstrate Bundle filter填写腾讯视频App的bundle ID,通过Cycript获取到bundle ID为com.tencent.live4iphone。
  • 在Tweak.x文件中添加如下代码,效果是在腾讯视频首页弹出一个Alert弹框。

@interface QLHomeController

- (id)presentViewController:(id)controller animated:(BOOL)animated completion:(id)completion;

@end

%hook QLHomeController

- (void)viewDidAppear:(BOOL)animated{

    %orig;
  
    UIAlertController *alertVc = [UIAlertController alertControllerWithTitle:@"提示框" message:@"测试Theos插件" preferredStyle:UIAlertControllerStyleAlert];
    [alertVc addAction:[UIAlertAction actionWithTitle:@"关闭" style:UIAlertActionStyleCancel handler:nil]];
    [self presentViewController:alertVc animated:YES completion:nil];
}

%end

  • 执行make package && make install,对Tweak项目进行打包,打包完成后通过Cydia安装到iPhone上,等待iPhone重启,然后打开腾讯视频,在首页就可以看到我们写的弹框了。

  • 获取以下签名需要的一些文件

    • 在iPhone上的/var/mobile/Containers/Bundle/Application/目录下找到live4iphone.app安装包。
    • 在/Library/MobileSubstrate/DynamicLibraries/目录下找到我们编写的插件所生成的动态库test_live.dylib。
    • 在/Library/Frameworks/CydiaSubstrate.framework/目录下找到test_live.dylib依赖的动态库CydiaSubstrate。
    • 获取之前练习中使用的embedded.mobileprovision文件。
  • 将test_live.dylib、CydiaSubstrate和embedded.mobileprovision文件复制到live4iphone.app包中。

  • 向live4iphone.app包中的可执行文件中注入动态库

insert_dylib @executable_path/test_live.dylib live4iphone --all-yes --weak live4iphone

出现以下结果表明注入成功:

➜  live4iphone.app insert_dylib @executable_path/test_live.dylib live4iphone --all-yes --weak live4iphone
live4iphone already exists. Overwrite it? [y/n] y
Binary is a fat binary with 2 archs.
LC_CODE_SIGNATURE load command found. Remove it? [y/n] y
LC_CODE_SIGNATURE load command found. Remove it? [y/n] y
Added LC_LOAD_WEAK_DYLIB to all archs in live4iphone

使用otool指令或者MachOView查看腾讯视频的可执行文件,可以发现动态库已经注入到Mach-O文件当中

  • 修改CydiaSubstrate库的路径
    • 通过otool查看test_live.dylib的依赖库信息。

- 此时发现<font color=red>CydiaSubstrate</font>的路径是<font color=red>/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate</font>,但是在非越狱手机上是没有这个路径的,所以需要通过install_name_tool指令来改变动态库的加载路径。
```
install_name_tool -change /Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate @loader_path/CydiaSubstrate test_live.dylib
```
再次查看<font color=red>test_live.dylib</font>的依赖库信息,会发现<font color=red>CydiaSubstrate</font>库的路径变成了<font color=red>@loader_path/CydiaSubstrate</font>
  • 分别对test_live.dylib和CydiaSubstrate进行重签名
➜  live4iphone.app codesign -fs 1BEA2FE8783A297CF30B7728849EB225231D67E8 test_live.dylib
test_live.dylib: replacing existing signature
➜  live4iphone.app codesign -fs 1BEA2FE8783A297CF30B7728849EB225231D67E8 CydiaSubstrate
CydiaSubstrate: replacing existing signature
➜  live4iphone.app
  • 使用iOS App Signer对live4iphone.app进行重签名,生成ipa安装包

  • 将live4iphone.ipa安装到非越狱手机上,可以发现安装成功,并且进入之后出现弹框。