当前位置: 首页 > 科技观察

iOS符号表还原&逆向支付宝

时间:2023-03-12 21:28:24 科技观察

推荐前言本文介绍符号表还原技术,并利用该技术在Xcode中实现目标程序的符号断点调试。这种技术可以显着减少反向分析时间。文章开头,笔者以支付宝为例,通过在UIAlertView的show方法处设置断点,展示了获取支付宝调用栈的过程。本文涉及的代码也已开源:https://github.com/tobefuturer/restore-symbol,欢迎Star和issue。感谢作者允许发布。作者简介:杨俊,中山大学计算机系研究生,iOS开发者,擅长iOS安全与逆向工程,个人博客:http://blog.imjun.net。前言符号表一直是逆向工程中的“必备”,iOS应用在上线前都会对符号表进行裁剪,避免逆向分析。本文将介绍一个自己编写的工具,用于恢复iOS应用程序的符号表。直接看效果,恢复符号表后支付宝的样子:文章有点长,请耐心看完***,重点是***。为什么要恢复符号表在逆向工程中,调试器的动态分析是必不可少的,而Xcode+lldb确实是一个非常好的调试工具。比如我们可以很方便的在Xcode中查看调用栈,如上图可以清楚的看到支付宝登录的RPC调用过程。事实上,如果我们不还原符号表,你看到的调试页面应该是这样的:同样的函数调用过程,Xcode的显示完全不一样。原因是当Xcode显示调用堆栈中的符号时,它只显示符号表中的符号。为了我们的调试过程能够顺利进行,我们有必要对可执行文件中的符号表进行恢复。什么是符号表?要恢复符号表,首先要知道符号表是什么,它是如何存在于Mach-O文件中的。符号表存放在Mach-O文件的__LINKEDIT段,涉及符号表(SymbolTable)和字符串表(StringTable)。这里我们使用MachOView打开支付宝可执行文件,找到SymbolTable项。符号表的结构是一个连续的链表,每一个链表都是一个structnlist。//位于系统库的头文件中structnlist{union{//符号名在字符串表中的偏移量uint32_tn_strx;}n_un;uint8_tn_type;uint8_tn_sect;int16_tn_desc;//symbol在内存中的地址类似于函数指针uint32_tn_value;};这里重点关注item***和item***,item***是符号名在字符串表中的偏移量,用来表示函数名,**item是符号在内存中的地址,类似于一个函数指针(这里只描述大概的结构,详细信息请参考官方的MachO文件格式文档)。也就是说,如果我们知道了符号名和内存地址的对应关系,就可以根据这个结构逆向构建符号表数据。知道了如何构造符号表,接下来就是收集符号名和内存地址的对应关系了。获取OC方法的符号表。因为OC语言的特性,编译器会把类名、函数名等编译成最终的可执行文件,所以我们可以逆向逆向还原所有的Class,这就是大名鼎鼎的逆向工具class-dump。class-dump的头文件中有函数地址:所以我们只需要稍微修改class-dump的源代码就可以得到我们想要的信息。符号表恢复工具整理好数据格式和数据来源后,我们就可以编写工具了。具体实现过程不再赘述。这个开源工具在我的Github上。链接:https://github.com/tobefuturer/restore-symbol下面看看这个工具的使用方法:1、下载源码编译gitclone--recursivehttps://github.com/tobefuturer/restore-symbol.gitcdrestore-symbol&&make./restore-symbol2.恢复OC的符号表非常简单。符号表-o的Mach-O文件后面是输出文件位置3.重新对Mach-O文件进行签名并打包,看看效果。恢复符号表后,符号表信息多了20M。在Xcode中查看调用栈可以看到,OC函数这部分的符号已经恢复,在函数调用栈中可以看到大致的调用过程,但是在支付宝中,使用的是块回调形式,所以很大一部分符号无法正确显示。我们来看看如何恢复这部分区块的符号。获取区块的符号信息还是同样的思路。要恢复块的符号信息,我们必须知道块在文件中的存储形式。内存中块的结构首先我们分析一下运行时内存中块的存在。block在内存中以结构体的形式存在,一般结构如下:struct__block_impl{/**block也是NSObject类在内存中的一个结构体,结构体的起始位置是一个isa指针*/Classisa;/**这两个变量暂时不用管*/intflags;intreserved;/**真正的函数指针!!*/void(*invoke)(...);...}说明块中的isa指针会根据实际情况有三个不同的值来表示不同类型的块:_NSConcreteStackBlock栈上的块,一般在创建block的时候,会在栈上分配一块block结构空间,然后给isa等变量赋值。2.当_NSConcreteMallocBlock堆上的块被添加到GCD或被对象持有时,栈上的块被复制到堆中。这时候复制的block的类型就变成了_NSConcreteMallocBlock。3._NSConcreteGlobalBlock是全局静态块。当block不依赖上下文时,比如不持有block外的变量,只使用block内的变量,block的内存分配可以在编译期完成,分配到全局静态常量区。第二种类型的块只会在运行时出现。我们只关注1和3,下面分析这两种isa指针和块符号地址的关系。分析blockisa指针与符号地址的关联需要使用反汇编软件IDA。下面用两个实际的小例子来说明一下:1._NSConcreteStackBlock假设我们的源码就是这样一个非常简单的块:@implementationViewController-(void)viewDidLoad{intt=2;void(^foo)()=^(){NSLog(@"%d",t);//block引用了外部变量t};foo();}@end编译后的实际程序集是这样的:实际运行时,block的构建过程如下:为block创建一个栈空间,并为block的isa指针赋值(必须引用全局变量:_NSConcreteStackBlock)获取函数地址,赋值给Function指针所以我们可以梳理出这么一个特点:来了关键点!!!每当代码中使用栈上的块时,都会获取__NSConcreteStackBlock作为isa指针,同时会立即获取一个函数地址,函数地址就是块函数地址。结合下图,仔细理解上面这句话(这张图和上图是同一个文件,只是符号表被截掉了)。利用这个特性,我们在逆向分析时可以做出如下推断:在一个OC方法中发现引用了变量__NSConcreteStackBlock,那么这附近一定有一个函数地址,这个函数地址就是这个OC方法中的一个block。比如上图中,我们发现在viewDidLoad中,引用了__NSConcreteStackBlock,同时加载了sub_100049D4的函数地址,那么我们就可以确定sub_100049D4是viewDidLoad中的一个block,并且符号名是sub_100049D4sub_100049D4函数应该是viewDidLoad_block.2。_NSConcreteGlobalBlock全局静态块是那种不引用块外变量的块。因为不引用外部变量,所以可以在编译时进行内存分配操作,不用担心块复制等操作。它存在于文件常量区的可执行文件中。不明白的话,看个例子:把源码改成这样:@implementationViewController-(void)viewDidLoad{void(^foo)()=^(){//block不引用外部变量NSLog(@"%d",123);};foo();}@end那么编译后会变成这样:那么借鉴上面的思路,逆向分析,我们可以推断出对_NSConcreteGlobalBlock的引用是在静态常量区发现这个地方一定有块结构数据。一个值将出现在该结构的第16个字节中。该值是块的函数地址。嵌入块的情况:-(void)viewDidLoad{dispatch_async(background_queue,^{...dispatch_async(main_queue,^{...});});}所以这里的块有父子关系,如果我们把这些父子关系收集起来,可以发现这些关系在图论中会形成一个森林结构,这里可以简单的用递归深度优先搜索来处理,详细的过程不再赘述。块符号表提取脚本(IDA+python)整理了以上思路。我们发现搜索过程依赖IDA提供各种参考信息,IDA提供编程接口,可以用来提取参考信息。IDA提供了PythonSDK,完成的脚本也放在仓库search_oc_block/ida_search_block.py中(https://github.com/tobefuturer/restore-symbol/blob/master/search_oc_block/ida_search_block.py。提取Block符号表下面简单介绍一下上面脚本的使用方法:用IDA打开支付宝的Mach-O文件,等待分析完成!可能需要一个小时Alt+F7或者菜单栏文件->脚本文件...4.等待脚本完成,估计30s到60s,运行过程中会有这样的弹窗5.弹窗消失,块符号表提取完成6.在IDA打开文件的目录,会输出一个名为block_symbol.json的json格式的blocksymbol./origin_AlipayWallet-o./AlipayWallet_with_symbol-jblock_symbol.json-j后面跟之前得到的json符号表***得到一个既有OC函数符号表又有块符号表的可执行文件。这里简单介绍一个分析案例,大家可以体会到这个工具的强大之处。1.在Xcode-[UIAlertViewshow]设置断点2.运行程序,在支付宝登录页面输入手机号和密码错误,点击登录3.Xcode弹出'密码错误'警告框时停止up,左边会显示一张这样的调用栈图,看完支付宝的登录流程项目开源地址:https://github.com/tobefuturer/restore-symbol欢迎大家在上面提各种Issue,也可以如果您有任何问题,请直接发送电子邮件(tobefuturer@gmail).com)。