非越狱环境下从应用重签名到微信上加载Cycript

400 查看

注:这么长时间以来本文后续加入了新的内容以及一些修改,由于精力有限,所以不会同步更新到简书上。请大家移步到本项目的Github: https://github.com/Urinx/iOSAppHook

从零到一,非越狱环境下iOS应用逆向研究,从dylib注入,应用重签名到App Hook。文中用到的工具和编译好的dylib可在Github上下载。

注意!本文所有操作均在以下环境下成功进行,不同平台或环境可能存在某些问题,欢迎大家在issue中提出问题以及相互讨论。

Mac OS X 10.11.6 (15G12a)
Xcode 7.3.1 (7D1014)
iPhone 5s, iOS 9.3.3 (13G21)
免费开发者账号
示例App:微信 v6.3.19.18

前言

提到非越狱环境下App Hook大家早就已经耳熟能详,已经有很多大神研究过,这方面相关的资料和文章也能搜到很多。我最早是看到乌云知识库上蒸米的文章才对这方面有所了解,当时就想试试,整个过程看似简单(大神总是一笔带过),然而当自己真正开始动手时一路上遇到各种问题(一脸懵逼),在iOSRE论坛上也看到大家遇到的各种问题,其实阻扰大家的主要是一些环境的搭建以及相关配置没设置好,结果导致dylib编译过程各种错误,重签名不成功,各种闪退等。所以本文里的每一步操作都会很详细的交代,确保大家都能操作成功。

应用脱壳

我们知道,App Store里的应用都是加密了的,直接拿上来撸是不正确的,所以在此之前一般会有这么一个砸壳的过程,其中用到的砸壳工具就是dumpdecrypted,其原理是让app预先加载一个解密的dumpdecrypted.dylib,然后在程序运行后,将代码动态解密,最后在内存中dump出来整个程序。然而砸壳实在越狱的环境下进行的,鉴于本文主要关注点在非越狱环境下,再者我手里也没有越狱设备(有就不会这么蛋疼了)。

所以这里我们选择的是直接从PP助手等各种xx助手里面下载,注意的是这里下载的是越狱应用(不是正版应用),也就是所谓的脱过壳的应用。

为了谨慎起见,这里我们还需要确认一下从xx助手里下载的应用是否已解密,毕竟有好多应用是只有部分架构被解密,还有就是Watch App以及一些扩展依然加密了,所以最好还是确认一下,否则的话,就算hook成功,签名成功,安装成功,app还是会闪退。

首先,找到应用对应的二进制文件,查看包含哪些架构:

可以看到微信包含两个架构,armv7和arm64。关于架构与设备之间的对应关系可以从iOS Support Matrix上查看。理论上只要把最老的架构解密就可以了,因为新的cpu会兼容老的架构。

otool可以输出app的load commands,然后通过查看cryptid这个标志位来判断app是否被加密。1代表加密了,0代表被解密了:

可以看到微信已经被解密了,第一个对应的是较老的armv7架构,后者则是arm64架构。

鉴于微信是一个多targets的应用,包含一个Watch App和一个分享扩展。所以同理,我们还需要依次确认以下二进制文件,这里就跳过了。

应用重签名

在第二部分我们将要进行的是应用重签名,注意这里并不是按照整个操作流程的顺序来讲,而是跳过编译dylib,因为我觉得如果现在你没有把重签名拿下的话,写tweak写hook都是白搭。所以从这里在开始,我们不需要对App进行任何修改和处理,仅仅对其进行重签名,然后将其安装到设备上能够正常的运行。再次提醒的是重签名用的是脱壳后的App,加密的App重签名成功安到设备上也会闪退。

关于iOS应用重签名的文章有很多,签名方法都是一样,个别地方会有些小出入,然后按照里面给出的步骤手动一步一步的来操作,不知道是由于免费的开发者证书的原因还是哪一步漏掉了或者是什么其它的原因,总之就是不成功。就在一筹莫展的时候,偶然发现了一个名为iOS App Signer的Mac上的应用,这款重签名工具能够用免费的开发者账号重签名应用。我试了下,用这个工具重签名了一个应用,并且成功安装到手机上,尽管打开时闪退,但至少总算能安装到设备上去了。

看到签名成功心里一阵高兴,并且由于该应用开源,于是就到Github上阅读源码看其具体的实现。该应用是用Swift语音所写,代码量也不多,阅读起来没有问题。由于整个操作都是在终端下进行的,从终端到图形界面来回切换实在是麻烦,所以在其代码基础上稍作修改写了一个Command Line Tool工具在命令行下使用。

下面就来具体交代下这个重签名工具到底做了什么。

1. 获取到本地上的开发者签名证书和所有的Provisioning文件

获取本机上的Provisioning文件主要是调用了populateProvisioningProfiles()方法,该方法调用了ProvisioningProfile.getProfiles()将结果封装到ProvisioningProfile结构体中以数组的形式返回,并对其结果做了一个刷选,去掉了那些过期的证书。