在平时的日常工作过程中,时常会有道友私聊我,问我能不能帮他对xxAPP砸个壳这样的场景。所以如何简单快速的对APP砸壳自然尤为重要,本文将会讲述如何实现一键砸壳,做到无痛无瘙痒。
当然按照本系列的惯例,同样会讲述砸壳原理,砸壳遇到的问题如何解决等等。
先看最终效果:
在阅读这篇文章之前,笔者默认读者已经了解如何进入远程连接手机,如果实在不了解请阅读iOS逆向(10)-越狱!越狱原理!远程连接登录手机
在了解如何砸壳之前,需要知道到底什么是壳,砸壳的原理又是什么。
开始之前同样的放出福利:
主动加载默认不启动的framework,解决砸壳失败的痛点:DecryptApp
一、什么是壳
绝大多数APP开发者都认为iOS系统很安全,都未对APP进行加固,所以通俗意义的加壳就是在上传App的时候,由Apple对App的加密。
1、验证何时加壳
(1) Build后
首先利用xcode新建一个默认工程EncryptApp
,不用写任何代码,或者随意写点东西。
Build
这个工程,成功之后进入工程路径,查看此时是否加壳。
显示上图目录中EncryptApp
的包内容
利用终端查看MachO文件EncryptApp
是否已经被加壳
otool -l EncryptApp | grep crypt
复制代码
可以看到其中的cryptid
字段为0
,即代表该文件未被加密。
(2) 运行到手机中
先查找EncryptApp
在手机中的位置。
话不多说,先利用SSH,将电脑连接上iPhone设备(也能用)。
先确保EncryptApp
在设备上已经正常运行,在利用ps
命令查找当前进程。
ps -ax | grep EncryptApp
复制代码
从上图就可以查看到当前APP在设备上的物理路径。之后进入该路径,将其导出到电脑桌面(推荐使用iFunBox,如果使用iFunBox查看不了设备的根目录,则代表设备权限不够,需要在Cydia中下载对应版本的AFC2插件)。
或者直接使用命令拷贝到桌面
scp -r -P 12345 root@localhost:/var/containers/Bundle/Application/AD8F7993-260F-465D-B207-900F67195658/Youkui4Phone.app ~/Desktop/
复制代码
同样利用otool
命令查看其是否被加密
同样cryptid
字段为0
,即代表该文件未被加密。
(3) 本地打包后
常规操作,利用证书将工程打包成ipa包,并且导出到桌面目录。
同样利用otool
命令查看其是否被加密
同样cryptid
字段为0
,即代表该文件未被加密。
这个地方有两个cryptid
字段代表这个MachO是个多架构可执行文件,分别是arm7,arm64,而他们都未被加壳。
(4) AppStore下载的包
从AppStore下载一个崭新的优酷,运行后,同样利用ps
查看其目录地址,再利用otool
查看是否加密。
同样将其导出到桌面,查看其是否加密
此时cryptid
字段为1
,即代表该文件被加密方案为1的方案加密了。
从以上过程即可得出结论,App加壳是在咱们的ipa上传到苹果后台之后被加密的。
二、怎么砸壳
要真正了解什么是砸壳,先要知道系统在启动一个app的时候做了些什么事情。
1、内核加载MachO过程
在我们点击App的从之前,APP是以MachO和各种资源的形式躺在磁盘了,在点击icon之后,内核开始读取MachO,先查看该MachO是否被加密。
如果没有被加密,则直接交给dyld
加载并且运行。
如果已经被加密,则需要内核对其进行解密,得到解密后的MachO文件,再将其交给dyld
加载并运行。
dyld
的加载过程可以查看文章iOS逆向(5)-不知MachO怎敢说自己懂DYLD
从这步可以看出,一个被加密的MachO是不可能全完被加密的,否则内核无法知道该MachO是否被加密,是是什么类型的文件,是什么架构的文件等等信息(具体有哪些信息可以通过查看MachO的Header段得知,具体方法同样可以点击iOS逆向(5)-不知MachO怎敢说自己懂DYLD查看)。
我们在使用otool -l EncryptApp | grep crypt
命令时得出的记过出了字段cryptid
,还有cryptoff
和cryptsize
字段,这两个字段就代表着该MachO从位置16384(10进制)开始后的103153664(10进制)字节都被加密了。
内核加载加壳后的MachO大致的流程图如下:
2、砸壳过程
(1) 两种砸壳原理
砸壳就就相当于一个解密过程。我们人(下文姑且成为小明同学)相当于内核和内存,而未加壳的MachO相当于明文,加壳后的MachO相当于密文。
例如:小明同学在看到aGVsbG8=
的时候并不知道其代表了什么含义,但他知道这是用base64
加密而来的,所以只需要通过base64
解密就可以知道这其实是hello
的意思。
由于每次启动App的时候都是需要对App进行解密的,所以如果利用非对称对App进行加密/解密效率是非常低的,这会严重影响用户体验,所以App的加密肯定是用的对称加密的方式来加密的。
所以就能知道砸壳(解密)原理是有两种的。
静态砸壳:破解解密整个过程,还原所有代码。
动态砸壳:由于内核运行的是内存中已经被解密的MachO,所以只要将该内存中的MachO直接dump到磁盘即可,就相当于小明同学在通过解密算法解密后看到的明文后,我们直接叫小明同学告诉我们明文是什么一样。
静态解密的方式成本极大,需要逆向整个内核,而Apple对内核加固的非常严,并且就算那到了整个加密过程,只要Apple对加密方式稍作修改,则有需要从头再来,所以这种方式不可取。
而各大厂商的内存格式都是统一的,所以从内存读取数据的方式也是统一的,所以动态砸壳的方式是目前比较理想的方式。
(2) 手动动态砸壳
启动手机内部的LLDB
服务
上文提到了动态砸壳的原理是从内存dump出对应的二进制,那么我们日常开发的时候是用什么调试内存中的App呢?
答案自然是我们熟知的LLDB
我们的Mac是自带LLDB
的,比如:
使用终端,敲入命令LLDB
(笔者lldb有python2的脚本,但系统已经默认是python3,所有有相关报错)
或者在使用xcode的时候,挂起App,App即进入LLDB
调试状态。
这也证明在我们的手机里面是是有LLDB
的对应服务的。
在购买一台新的手机的时候,手机上本来是没有这个服务的,当我的连上xcode,第一次联调App的时候,xcode将手机系统对应版本的服务Copy到手机中,而这个服务的名称就叫做debugserver
。
在Mac电脑中,iOS 10.3版本的debugserver
的位置位于:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/10.3
复制代码
../DeveloperDiskImage/usr/bin/debugserver
复制代码
而在手机上,debugserver
的位置位于:
usr/bin
复制代码
我们可以利用md5验证手机内的debugserver
和电脑内的debugserver
是否是同一个东西。
当然结果也有可能是不一样的,因为手机内的
debugserver
有可能是从别的电脑,别的版本的系统里面拷贝进入的,只不过所有LLDB
的指令集都是统一的罢了
总结一下,debugserver
的作用就是连接Mac
上的LLDB
,达到Mac利用LLDB
控制debugserver
,从而debugserver
控制iPhone
的作用。
如图:
既然debugserver
是一个服务,那么服务就必然需要开辟一个端口,让远程客户端通过这个端口来连接这个服务。我们可以在Mac连接上手机之后,通过如下命令开启这个端口:
// debugserver *:端接口 -a APP名称/进程号
debugserver *:12346 -a EncryptApp
// 或者
debugserver *:12346 -a 3657
复制代码
12346
端口号可以随便写,但是要注意的是不要跟别的端口号重复既可。
如果不知道APP名称/进程号,可以通过命令ps -ax
查找。
如果知道APP名称或者部分APP名称可以追加grep
命令进行筛选ps -ax | grep EncryptApp
如果有如下报错
error: failed to attach to process named: "" (os/kern) invalid task Exiting. 复制代码
证明,已经该APP已经被
debugserver
给占用了,尝试检查xcode或者终端是否已经连接了手机。
关闭其他占用该App的debugserver
后,重新开启端口,若出现如下信息则代表成功:
之后再开启电脑端的LLDB
模式,并且吸附该端口就可以了。
相关命令如下
// 开启lldb状态
> lldb
// 吸附对应端口
> process connect connect://localhost:12346
// 查看该端口下对应进程的所有image地址,第一个为该App的ASLR
> image list
复制代码
同xcode一样,LLDB
状态下的app是被挂起,无法被操作的。
使用指令c
可以取消挂起App,使用指令exit
可以退出LLDB
状态。更多指令可查看官方指令集
Dump出已加密的二进制
既然开始实操砸壳,那么就肯定需要一个已经加密的App作为对象,我们还是使用老朋友优酷😝。
部分可分为:
1、启动App,开启端口,开启LLDB
状态,吸附该端口
2、查看该MachO加密的部分的位置和加密部分的大小
3、查看该App在内存中的的ASLR
4、利用memory read -force
指令Dump出解密部分的二进制文件
5、利用dd
指令将Dump出的二进制文件重写如原来的MachO文件中
6、更改MachO文件中的cryptid
为0
Step 1、不在复述,同上一步
Step 2、查看该MachO加密的部分的位置和加密部分的大小
> otool -l Youkui4Phone | grep crypt
复制代码
cryptoff 16384 // 文件偏移
cryptsize 103153664 // 加密文件大小
cryptid 1 // 已经加密
复制代码
Step 3、查看该App在内存中的的ASLR
> image list
复制代码
ASLR为:0x00000001000c0000
Step 4、利用memory read -force
指令Dump出解密部分的二进制文件
// memory read --force --outfile 输出文件的目录名称 --binary --count 加密文件的大小 ASLR+文件偏移
> memory read --force --outfile ./Desktop/decrypted.bin --binary --count 103153664 0x00000001000c0000+16384
复制代码
此时在桌面可以发现一个decrypted.bin
文件。
Step 5、利用dd
指令将Dump出的二进制文件重写如原来的MachO文件中
// dd seek=文件偏移地址 bs=单位为1子节 conv=转换方式(保留未被截取部分的内容) if=输入文件地址 of=输出文件地址
> dd seek=16384 bs=1 conv=notrunc if=./decrypted.bin of=./Youkui4Phone.app/Youkui4Phone
复制代码
Step 6、更改MachO文件中的cryptid
为0
此时该MachO其实已经被解密成功了,但是MachO的cryptid
字段还未被更改,所以cryptid
此时还是唯1的。
直接打开MachO,手动更改cryptid字段为0。
验证是否真的成功
利用class-dump的原理,如果砸壳成功,可以导出头文件,如果失败则不能导出头文件。
(3) frida-ios-dump
在真正日常开发中,当然不可能每次砸壳多来这么一遍,既耗时又有耗心力,已经有张总给我们写出了非常简单易用的砸壳工具,配置好就可实现一条命令完成砸壳。具体可查看frida-ios-dump。
原作者发的配置文档: 一条命令完成砸壳
网友写好的详细文档: Frida-ios-dump一键砸壳菜鸡版
已经有非常优秀的配置文档我这就不再复述。
那么是否只要配置好Frida-ios-dump就能无敌天下,在砸壳路上就一帆风顺呢?
答案当然不是的。
三、特殊的壳
记得上面提到的提到的砸壳原理是从内存中dump已经解密的文件,那如果是否有文件在App运行的不再内存中呢?
肯定是有的,比如iOS中常用的动态库framework
,有部分的动态库只有在使用其功能的时候才会内加载到内存中,在使用frida
砸壳的时候这部分的framework
一定是砸壳失败的,砸壳失败在我们对其重签名的时候必然也会失败,在进行分析的时候也就加大了难度(先不讨论非重签名的调试方式),例如:某宝,肥死book等。
使用MonkeyDev对其重签名的时候会报如下错误:
/Users/xxx/Desktop/xxx/TargetApp/xxx.app/Frameworks/xxx.framework/xxx This file is encrypted! please use github.com/AloneMonkey… to decrypt!
看过dyld
源码的同学肯定知道dyld
在接管App启动的时候的步骤:
1、配置环境变量
2、加载共享缓存库
3、实例化主程序
4、加载动态链接库
5、链接主程序
6、加载Load和特定的C++的构造函数方法
7、寻找APP的main函数并调用
自然我们利用dyld
的一些方法就可以获取到当前App所有的framework
。
具体代码可见libAllFramework
使用方法:
1、下载好工程,Bulid工程
2、将Build成功的framework拷贝到手机的Home目录
> scp -r -P 12345 libAllFramework.framework root@localhost:~/
复制代码
这里需要注意Build的环境应该是真机环境,并且注意framework的权限
3、利用DYLD_INSERT_LIBRARIES
执行libAllFramework
// 查看Facebook目录地址
> ps -ax | grep Facebook
// 插入我们的动态库
> DYLD_INSERT_LIBRARIES=libAllFramework.framework/libAllFramework /var/containers/Bundle/Application/B4217EF2-9F16-453F-B1DE-9EC5D0E69B60/Facebook.app/Facebook
复制代码
可以在看到绝大多数的动态库已经被正确的加载了,只有一个名为FBCardIOSDKWrapperFramework
的动态库加载失败,笔者至今没有找到原因为什么会失败,如果有了解的道友请告知一下,万分感谢。
但这并不影响大局,因为我们只需将FBCardIOSDKWrapperFramework
从源文件中删除即可。再次对其砸壳后,重签名即可成功。
不想下载源码的同学也可以直接下载笔者打包好的framework(arm64架构),直接Copy入手机。
四、总结
本文讲述了在iOS系统中什么是传统意义上的壳,也就是对App进行对称加密。Apple是在什么时候对其加密的。砸壳原理,实操手动砸壳,利用工具砸壳,以及最后举例了一个砸壳会遇到的一些坑。
希望在日后的工作中砸壳将是一种轻松加愉快的事情😸