lldb有一个内存调试工具malloc stack,开启之后就能够查看某个内存地址的malloc和free记录,追踪对象是在哪里建立的。html
这个工具能够打印出对象建立的堆栈,而在逆向时,也常常须要追踪某些方法的调用栈,若是能够随时打印出某个对象的建立记录,也就能直接找到其所在的类和方法,不用再花费大量的时间去打log和动态调试追踪了。python
在本身的项目中,要开启malloc stack,须要在Product->Scheme->Edit Scheme->Diagnistic
里勾选Malloc Stack
选项。git
效果以下。github
测试代码:macos
- (IBAction)create:(id)sender {
NSString *testString = [NSString stringWithFormat:@"string created by %@",self];
}
复制代码
断点后在lldb中使用lldb.macosx.heap
里的malloc_info
命令,虽然官网上说是Mac app才能用的命令,可是经测试如今在iOS上也能用了:bash
(lldb) p/x testString
(__NSCFString *) $3 = 0x16eac000 @"string created by <ViewController: 0x16e9d7c0>"
(lldb) command script import lldb.macosx.heap //加载lldb.macosx.heap
"malloc_info", "ptr_refs", "cstr_refs", "find_variable", and "objc_refs" commands have been installed, use the "--help" options on these commands for detailed help.
(lldb) malloc_info -s 0x16eac000
0x0000000016eac000: malloc( 64) -> 0x16eac000 __NSCFString.NSMutableString.NSString.NSObject.isa
stack[0]: addr = 0x16eac000, type=malloc, frames:
[0] 0x00000000242948ab libsystem_malloc.dylib`malloc_zone_malloc + 123
[1] 0x00000000244e3bc1 CoreFoundation`_CFRuntimeCreateInstance + 237
[2] 0x00000000245a6ffd CoreFoundation`__CFStringCreateImmutableFunnel3 + 1657
[3] 0x00000000244ee0f7 CoreFoundation`CFStringCreateCopy + 359
[4] 0x00000000245a725d CoreFoundation`_CFStringCreateWithFormatAndArgumentsAux2 + 89
[5] 0x0000000024d17dd3 Foundation`-[NSPlaceholderString initWithFormat:locale:arguments:] + 139
[6] 0x0000000024d17cd1 Foundation`+[NSString stringWithFormat:] + 61
[7] 0x00000000000d7343 testMallocStack`-[ViewController create:] + 97 at ViewController.m:23:28
[8] 0x00000000287a5771 UIKit`-[UIApplication sendAction:to:from:forEvent:] + 81
[9] 0x00000000287a5701 UIKit`-[UIControl sendAction:to:forEvent:] + 65
[10] 0x000000002878d61f UIKit`-[UIControl _sendActionsForEvents:withEvent:] + 447
[11] 0x00000000287a5051 UIKit`-[UIControl touchesEnded:withEvent:] + 617
[12] 0x00000000287a4cbf UIKit`-[UIWindow _sendTouchesForEvent:] + 647
[13] 0x000000002879d5d7 UIKit`-[UIWindow sendEvent:] + 643
[14] 0x000000002876e119 UIKit`-[UIApplication sendEvent:] + 205
[15] 0x000000002876c757 UIKit`_UIApplicationHandleEventQueue + 5135
[16] 0x0000000024599257 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 15
[17] 0x0000000024598e47 CoreFoundation`__CFRunLoopDoSources0 + 455
[18] 0x00000000245971af CoreFoundation`__CFRunLoopRun + 807
[19] 0x00000000244e9bb9 CoreFoundation`CFRunLoopRunSpecific + 517
[20] 0x00000000244e99ad CoreFoundation`CFRunLoopRunInMode + 109
[21] 0x0000000025763af9 GraphicsServices`GSEventRunModal + 161
[22] 0x00000000287d5fb5 UIKit`UIApplicationMain + 145
[23] 0x00000000000d7587 testMallocStack`main + 107 at main.m:14:9
[24] 0x000000002419c873 libdyld.dylib`start + 3
[25] 0x000000003a9c0001 libsystem_pthread.dylib`_thread + 1
复制代码
这个工具是继承自gdb的malloc_history
,不过malloc_history
只能用在模拟器上,而malloc_info
在模拟器和真机上均可以使用。另外,新版Xcode又增长了一个新的lldb工具memory history
,在Product->Scheme->Edit Scheme->Diagnistic
里勾选Address Sanitizer
便可,效果相似。微信
注意,在Xcode8.3之后使用malloc_info
会致使lldb调试器crash,彷佛是出bug了,一直没修复。在Xcode8.2上能够正常使用。app
因此咱们须要替换一下lldb自带的lldb.macosx.heap模块。使用这个非官方的版本:heap.py。svn
lldb能够加载自定义的pthon脚本。只须要在lldb中输入:函数
command script import python脚本的地址
复制代码
所以把上面的heap.py
下载到本地后,输入:
command script import /你的路径/lldb/examples/darwin/heap_find/heap.py
复制代码
便可。
Address Sanitizer
的memory history
须要从新编译app,可是malloc stack
只须要在app启动前设置环境变量MallocStackLogging
和MallocStackLoggingNoCompact
便可。开启后会在系统的/tmp
目录下生成一个.index
文件,这个文件里的内容是依赖于app的运行时环境的,进程退出之后这个文件也就没用处了。
那么,如今的问题就变成了如何给app设置启动环境变量。
这是我一开始使用的方法。使用execve
函数来运行app的二进制文件。
因为沙盒的限制,须要让app拥有root权限才能使用execve
。步骤以下。
重签名须要逆向的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
。
最终的目的就是使用引导程序用execve
启动app,在启动前设置环境变量。
首先重命名原来的二进制文件:mv YOUR_APP.app/YOUR_APP YOUR_APP.app/YOUR_APP_Orig
。
而后制做引导程序,随便建立一个iOS工程,替换main.m里的内容为:
int main(int argc, char * argv[]) {
@autoreleasepool {
NSString* string = [[NSBundle mainBundle] pathForResource:@"YOUR_APP_Orig" ofType:nil];//YOUR_APP_Orig是所要启动的二进制文件名
argv[0] = (char*)[string UTF8String];
char *envp[] =
{
"HOME=/var/root",
"LOGNAME=root",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/bin/X11:/usr/games",
"USER=root",
"MallocStackLogging=1",
"MallocStackLoggingNoCompact=1"
0
};
execve([string UTF8String], argv, envp);
return 0;
}
}
复制代码
编译后,取出二进制文件,重命名为YOUR_APP
,复制到越狱设备的/Application/YOUR_APP.app/
目录下。
给引导程序设置执行权限:chmod +x /Application/YOUR_APP.app/YOUR_APP
。
最后重启SpringBoard:killall SpringBoard
。
这样,每次启动app就都会使用引导程序间接启动app。
debugserver
唤醒app,调试启动过程。由于YOUR_APP
和YOUR_APP_Orig
是两个进程,第一个在execve
执行完就退出了。方法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
:
<MMEmoticonView: 0x108795c20; frame = (276.25 404.25; 215.5 215.5); autoresize = LM+RM+TM+BM; layer = <CALayer: 0x170828700>>
复制代码
第一次使用malloc_info
须要在lldb里导入lldb.macosx.heap
,这里须要导入非官方版本的heap.py
:
(lldb) command script import heap.py的路径
"malloc_info", "ptr_refs", "cstr_refs", "find_variable", and "objc_refs" commands have been installed, use the "--help" options on these commands for detailed help.
复制代码
使用malloc_info
打印建立堆栈:
(lldb) malloc_info -s 0x108795c20
0x0000000108795c20: malloc( 480) -> 0x108795c20 MMEmoticonView.UIView.UIResponder.NSObject.isa
stack[0]: addr = 0x108795c20, type=malloc, frames:
[0] 0x000000018374e0ac libsystem_malloc.dylib`calloc + 40
[1] 0x000000018318b624 libobjc.A.dylib`class_createInstance + 76
[2] 0x0000000183199ae4 libobjc.A.dylib`_objc_rootAlloc + 52
[3] 0x00000001026d8fd4 WeChat`-[MMImageBrowseView InitEmoticonView:] + 432
[4] 0x000000010245e950 WeChat`-[MMEmotionMsgBrowseViewController initImageViewWithFrame:] + 404
[5] 0x000000010245ea74 WeChat`-[MMEmotionMsgBrowseViewController setupImageView] + 156
[6] 0x000000010245e024 WeChat`-[MMEmotionMsgBrowseViewController initView] + 224
[7] 0x000000010245d76c WeChat`-[MMEmotionMsgBrowseViewController viewDidLoad] + 112
[8] 0x000000018a5f7924 UIKit`-[UIViewController loadViewIfRequired] + 1056
[9] 0x000000018a60f4b4 UIKit`-[UIViewController __viewWillAppear:] + 132
[10] 0x00000001026e05f8 WeChat`-[MMUIViewController beginAppearanceTransition:animated:] + 92
[11] 0x000000018a7975b4 UIKit`-[UINavigationController _startCustomTransition:] + 1136
[12] 0x000000018a6afe74 UIKit`-[UINavigationController _startDeferredTransitionIfNeeded:] + 676
[13] 0x000000018a6afadc UIKit`-[UINavigationController __viewWillLayoutSubviews] + 64
[14] 0x000000018a6afa40 UIKit`-[UILayoutContainerView layoutSubviews] + 188
[15] 0x000000018a5f4a80 UIKit`-[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1196
[16] 0x0000000187aa29d8 QuartzCore`-[CALayer layoutSublayers] + 148
[17] 0x0000000187a974cc QuartzCore`CA::Layer::layout_if_needed(CA::Transaction*) + 292
[18] 0x0000000187a9738c QuartzCore`CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 32
[19] 0x0000000187a143e0 QuartzCore`CA::Context::commit_transaction(CA::Transaction*) + 252
[20] 0x0000000187a3ba68 QuartzCore`CA::Transaction::commit() + 512
[21] 0x0000000187a3c488 QuartzCore`CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 120
[22] 0x00000001846f60c0 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
[23] 0x00000001846f3cf0 CoreFoundation`__CFRunLoopDoObservers + 372
[24] 0x00000001846f4180 CoreFoundation`__CFRunLoopRun + 1024
[25] 0x00000001846222b8 CoreFoundation`CFRunLoopRunSpecific + 444
[26] 0x00000001860d6198 GraphicsServices`GSEventRunModal + 180
[27] 0x000000018a6627fc UIKit`-[UIApplication _run] + 684
[28] 0x000000018a65d534 UIKit`UIApplicationMain + 208
[29] 0x00000001000ebea4 WeChat`-[WATemplateMsgMngSwitchCell .cxx_destruct] + 372
[30] 0x00000001836055b8 libdyld.dylib`start + 4
复制代码
这样就直接找到表情界面所在的类,以及在哪里初始化了。
这样的话,只要能找到一个对象,就能快速定位到其所在模块。比原来打log,打断点一步步回溯高效多了。
建议在对app重签名时恢复符号表。恢复符号表后,就能直接在堆栈中看到方法名,免去了计算偏移量而后在hopper里查找的麻烦。
参考:iOS符号表恢复&逆向支付宝, restore-symbol。
能够在内存中找出哪些地址引用了某个指针,也就至关于查看某个变量在哪里被引用。
在内存中寻找某个C String在哪里被引用。
在当前栈帧上寻找某个局部变量在哪里被引用。
在内存中寻找某个类的实例。
若是想要在Xcode中调试并开启malloc stack
,则须要先用debugserver
启动app,在终端的lldb里链接上之后,再用process detach
断开链接。接下来用Xcode的Attach to Process
就能够了,参考:iOS逆向:用Xcode直接调试第三方app。