lldb有一个内存调试工具malloc stack,开启以后就可以查看某个内存地址的malloc和free记录,追踪对象是在哪里创建的。
这个工具可以打印出对象创建的堆栈,而在逆向时,也经常需要追踪某些方法的调用栈,如果可以随时打印出某个对象的创建记录,也就能直接找到其所在的类和方法,不用再花费大量的时间去打log和动态调试追踪了。
malloc stack
在自己的项目中,要开启malloc stack,需要在Product->Scheme->Edit Scheme->Diagnistic
里勾选Malloc Stack
选项。
效果如下:
测试代码:
|
|
断点后在lldb中使用lldb.macosx.heap
里的malloc_info
命令,虽然官网上说是Mac app才能用的命令,但是经测试现在在iOS上也能用了:
|
|
这个工具是继承自gdb的malloc_history
,不过malloc_history
只能用在模拟器上,而malloc_info
在模拟器和真机上都可以使用。另外,新版Xcode又增加了一个新的lldb工具memory history
,在Product->Scheme->Edit Scheme->Diagnistic
里勾选Address Sanitizer
即可,效果类似。
使用非官方版的heap.py
注意,在Xcode8.3以后使用malloc_info
会crash,似乎是出bug了,一直没修复。在Xcode8.2上可以正常使用。
所以我们需要替换一下lldb自带的lldb.macosx.heap模块。使用这个非官方的版本:heap.py。
lldb可以加载自定义的pthon脚本。只需要在lldb中输入:
|
|
因此把上面的heap.py
下载到本地后,输入:
|
|
即可。
在任意app上开启malloc stack
Address Sanitizer
的memory history
需要重新编译app,但是malloc stack
只需要在app启动前设置环境变量MallocStackLogging
和MallocStackLoggingNoCompact
即可。开启后会在系统的/tmp
目录下生成一个.index
文件,这个文件里的内容是依赖于app的运行时环境的,进程退出以后这个文件也就没用处了。
那么,现在的问题就变成了如何给app设置启动环境变量。
方法一:execve
这是我一开始使用的方法。使用execve
函数来运行app的二进制文件。
由于沙盒的限制,需要让app拥有root权限才能使用execve
。步骤如下。
1.重签名ipa
重签名需要逆向的app。因为需要对app内容作出修改。重签名后安装到越狱设备上。
2.移动app到系统app目录下,修改权限
只有系统目录下的app才有root权限。
假设需要逆向的app是YOUR_APP.app
。把app移动到系统app目录下:mv -f /var/containers/Bundle/Application/xxxxxxxxxxxxx/YOUR_APP.app /Applications/YOUR_APP.app
。
然后修改文件权限:
cd /Applications
chown -R root:wheel YOUR_APP.app
chmod 4755 YOUR_APP.app/YOUR_APP
移动后,用uicache
刷新app图标,用killall SpringBoard
重启SpringBoard
。
3.使用引导程序启动app
最终的目的就是使用引导程序用execve
启动app,在启动前设置环境变量。
首先重命名原来的二进制文件:mv YOUR_APP.app/YOUR_APP YOUR_APP.app/YOUR_APP_Orig
。
然后制作引导程序,随便创建一个iOS工程,替换main.m里的内容为:
|
|
编译后,取出二进制文件,重命名为YOUR_APP
,复制到越狱设备的/Application/YOUR_APP.app/
目录下。
给引导程序设置执行权限:chmod +x /Application/YOUR_APP.app/YOUR_APP
。
最后重启SpringBoard:killall SpringBoard
。
这样,每次启动app就都会使用引导程序间接启动app。
缺点
- 步骤繁琐。
- 有些app重签名很麻烦。
- 越狱后的系统分区容量很小,很容易就被占满了,想要测试大一点的app就麻烦了。
- 无法使用
debugserver
唤醒app,调试启动过程。因为YOUR_APP
和YOUR_APP_Orig
是两个进程,第一个在execve
执行完就退出了。 - 把app放到系统目录下有时候会引起crash。
方法2:debugserver参数
方法1实在是太麻烦了,有时候遇上重签名失败的app就更麻烦了。但其实还有另一个更直接的方法。就是使用debugserver的命令。
debugserver是动态调试工具,参考:IOS平台lldb动态调试介绍。
安装好后,在越狱设备上输入debugserver *:1234 /var/containers/Bundle/Application/589822B6-BFDA-4A3D-A71C-AD0D30BA6077/WeChat.app/WeChat
就能唤醒app进行调试。
但是网上的教程都没有提到,其实debugserver还有一个隐藏的参数--env
(-env
,-e
都可以),就是用来设置进程的环境变量的:
debugserver *:1234 /var/containers/Bundle/Application/589822B6-BFDA-4A3D-A71C-AD0D30BA6077/WeChat.app/WeChat -env MallocStackLogging=1 -env MallocStackLoggingNoCompact=1
当时我想debugserver会不会有设置环境变量的功能,没想到随便试了个-env
就成功了。后来在debugserver的源码里也发现了它的存在:debugserver.cpp(搜索g_long_options
可以找到env
)。
这样,即使app没有重签名,也可以直接调试了。
缺点
debugserver
无法启动调试extension app,因为extension app是依赖于宿主app而存在的,不能单独运行。这种情况就只能使用方法1了。
测试
这里使用一个重签名,并且恢复了符号表的微信进行测试。
比如找到微信查看表情的界面,打印出内存地址为0x108795c20
:
|
|
第一次使用malloc_info
需要在lldb里导入lldb.macosx.heap
,这里需要导入非官方版本的heap.py
:
|
|
使用malloc_info
打印创建堆栈:
|
|
这样就直接找到表情界面所在的类,以及在哪里初始化了。
这样的话,只要能找到一个对象,就能快速定位到其所在模块。比原来打log,打断点一步步回溯高效多了。
恢复符号表
建议在对app重签名时恢复符号表。恢复符号表后,就能直接在堆栈中看到方法名,免去了计算偏移量然后在hopper里查找的麻烦。
参考:iOS符号表恢复&逆向支付宝, restore-symbol。
其他几个调试命令
ptr_refs
可以在内存中找出哪些地址引用了某个指针,也就相当于查看某个变量在哪里被引用。
cstr_refs
在内存中寻找某个C String在哪里被引用。
find_variable
在当前栈帧上寻找某个局部变量在哪里被引用。
objc_refs
在内存中寻找某个类的实例。
转到Xcode中调试
如果想要在Xcode中调试并开启malloc stack
,则需要先用debugserver
启动app,在终端的lldb里连接上以后,再用process detach
断开连接。接下来用Xcode的Attach to Process
就可以了,参考:iOS逆向:用Xcode直接调试第三方app。