iOS 符号表恢复 & 逆向支付宝
推荐序
本文介绍了恢复符号表的技巧,并且利用该技巧实现了在 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 段中,涉及其中的符号表(Symbol Table)和字符串表(String Table)。
这里我们用 MachOView 打开支付宝的可执行文件,找到其中的 Symbol Table 项。
符号表的结构是一个连续的列表,其中的每一项都是一个 struct nlist
。
// 位于系统库 <macho-o/nlist.h> 头文件中
struct nlist {
union {
// 符号名在字符串表中的偏移量
uint32_t n_strx;
} n_un;
uint8_t n_type;
uint8_t n_sect;
int16_t n_desc;
// 符号在内存中的地址,类似于函数指针
uint32_t n_value;
};
这里重点关注第一项和最后一项,第一项是符号名在字符串表中的偏移量,用于表示函数名,最后一项是符号在内存中的地址,类似于函数指针(这里只说明大概的结构,详细的信息请参考官方 Mach O 文件格式的文档)。
也就是说如果我们知道了符号名和内存地址的对应关系,我们是可以根据这个结构来逆向构造出符号表数据的。
知道了如何构造符号表,下一步就是收集符号名和内存地址的对应关系了。
获取 OC 方法的符号表
因为 OC 语言的特性,编译器会将类名、函数名等编译进最后的可执行文件中,所以我们可以根据 Mach-O 文件的结构逆向还原出工程里的所有类,这也就是大名鼎鼎的逆向工具 class-dump 了。class-dump 出来的头文件里是有函数地址的:
所以我们只要对 class-dump 的源码稍作修改,即可获取我们要的信息。
符号表恢复工具
整理完数据格式,又理清了数据来源,我们就可以写工具了。
实现过程就不详细说明了,工具开源在我的 Github 上了,链接:
https://github.com/tobefuturer/restore-symbol
我们来看看怎么用这个工具:
1. 下载源码编译
git clone --recursive https://github.com/tobefuturer/restore-symbol.git
cd restore-symbol && make
./restore-symbol
2. 恢复 OC 的符号表,非常简单
./restore-symbol ./origin_AlipayWallet -o ./AlipayWallet_with_symbol
origin_AlipayWallet 为 Clutch 砸壳后,没有符号表的 Mach-O 文件
-o 后面跟输出文件位置
3. 把 Mach-O 文件重签名打包,看效果
文件恢复符号表后,多出了 20M 的符号表信息
Xcode 里查看调用栈
可以看到,OC 函数这部分的符号已经恢复了,函数调用栈里已经能看出大致的调用过程了,但是支付宝里,采用了 block 的回调形式,所以还有很大一部分的符号没能正确显示。
下面我们就来看看怎么样恢复这部分 block 的符号。
获取 block 的符号信息
还是同样的思路,要恢复 block 的符号信息,我们必须知道 block 在文件中的储存形式。
block 在内存中的结构
首先,我们先分析下运行时,block 在内存中的存在形式。block 在内存中是以一个结构体的形式存在的,大致的结构如下:
struct __block_impl {
/**
block 在内存中也是类 NSObject 的结构体,
结构体开始位置是一个 isa 指针
*/
Class isa;
/** 这两个变量暂时不关心 */
int flags; int reserved;
/**
真正的函数指针!!
*/
void (*invoke)(...);
...
}
说明下 block 中的 isa 指针,根据实际情况会有三种不同的取值,来表示不同类型的 block:
_NSConcreteStackBlock
栈上的 block,一般 block 创建时是在栈上分配了一个 block 结构体的空间,然后对其中的 isa 等变量赋值。
_NSConcreteMallocBlock
堆上的 block,当 block 被加入到 GCD 或者被对象持有时,将栈上的 block 复制到堆上,此时复制得到的 block 类型变为了 _NSConcreteMallocBlock。
_NSConcreteGlobalBlock
全局静态的 block,当 block 不依赖于上下文环境,比如不持有 block 外的变量、只使用 block 内部的变量的时候,block 的内存分配可以在编译期就完成,分配在全局的静态常量区。
第 2 种 block 在运行时才会出现,我们只关注 1、3 两种,下面就分析这两种 isa 指针和 block 符号地址之间的关联。
block isa 指针和符号地址之间的关联
分析这部分需要用到 IDA 这个反汇编软件 , 这里结合两个实际的小例子来说明:
1._NSConcreteStackBlock
假设我们的源代码是这样很简单的一个 block:
@implementation ViewController
- (void)viewDidLoad {
int t = 2;
void (^ foo)() = ^(){
NSLog(@"%d", t); //block 引用了外部的变量 t
};
foo();
}
@end
编译完后,实际的汇编长这个样子:
实际运行时,block 的构造过程是这样:
为 block 开辟栈空间
为 block 的 isa 指针赋值(一定会引用全局变量:
_NSConcreteStackBlock
)获取函数地址,赋值给函数指针
所以我们可以整理出这样一个特征:
重点来了 !!!
凡是代码里用到了栈上的 block,一定会获取__NSConcreteStackBlock
作为 isa 指针,同时会紧接着获取一个函数地址,那个函数地址就是 block 的函数地址。
结合下面这个图,仔细理解上面这句话
(这张图和上面那张图是同一个文件,不过裁掉了符号表)
利用这个特征,逆向分析时我们可以做如下推断:
在一个 OC 方法里发现引用了__NSConcreteStackBlock
这个变量,那么在这附近,一定会出现一个函数地址,这个函数地址就是这个 OC 方法里的一个 block。
比如上面图中,我们发现 viewDidLoad 里,引用了__NSConcreteStackBlock
, 同时紧接着加载了 sub_100049D4 的函数地址,那我们就可以认定 sub_100049D4 是 viewDidLoad 里的一个 block, sub_100049D4 函数的符号名应该是 viewDidLoad_block.
2. _NSConcreteGlobalBlock
全局的静态 block,是那种不引用 block 外变量的 block,他因为不引用外部变量,所以他可以在编译期就进行内存分配操作,也不用担心 block 的复制等等操作,他存在于可执行文件的常量区里。
不太理解的话,看个例子:
我们把源代码改成这样:
@implementation ViewController
- (void)viewDidLoad {
void (^ foo)() = ^(){
//block 不引用外部的变量
NSLog(@"%d", 123);
};
foo();
}
@end
那么在编译后会变成这样:
那么借鉴上面的思路,在逆向分析的时候,我们可以这么推断
在静态常量区发现一个 _NSConcreteGlobalBlock 的引用
这个地方必然存在一个 block 的结构体数据
在这个结构体第 16 个字节的地方会出现一个值,这个值是一个 block 的函数地址
3. block 的嵌套结构
实际在使用中,可能会出现 block 内嵌 block 的情况:
- (void)viewDidLoad {
dispatch_async(background_queue ,^{
...
dispatch_async(main_queue, ^{
...
});
});
}
所以这里 block 就出现了父子关系,如果我们将这些父子关系收集起来,就可以发现,这些关系会构成图论里的森林结构,这里可以简单用递归的深度优先搜索来处理,详细过程不再描述。
block 符号表提取脚本(IDA+python)
整理上面的思路,我们发现搜索过程依赖于 IDA 提供各种引用信息,而 IDA 是提供了编程接口的,可以利用这些接口来提取引用信息。
IDA 提供的是 Python 的 SDK,最后完成的脚本也放在仓库里 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 或者 菜单栏
File -> Script file...
等待脚本运行完成,预计 30s 至 60s,运行过程中会有这样的弹窗
弹窗消失即 block 符号表提取完成
在 IDA 打开文件的目录下 , 会输出一份名为
block_symbol.json
的 json 格式 block 符号表
恢复符号表 & 实际分析
用之前的符号表恢复工具,将 block 的符号表导入 Mach-O 文件
./restore-symbol ./origin_AlipayWallet -o ./AlipayWallet_with_symbol -j block_symbol.json
-j 后面跟上之前得到的 json 符号表
最后得到一份同时具有 OC 函数符号表和 block 符号表的可执行文件
这里简单介绍一个分析案例 , 你就能体会到这个工具的强大之处了。
在 Xcode 里对
-[UIAlertView show]
设置断点
运行程序,并在支付宝的登录页面输入手机号和 错误的密码 ,点击登录
Xcode 会在 ‘密码错误’ 的警告框弹出时停下,左侧会显示出这样的调用栈
一张图看完支付宝的登录过程
项目开源地址:
https://github.com/tobefuturer/restore-symbol
iOS 符号表恢复 & 逆向支付宝的更多相关文章
- iOS符号表
https://docs.bugtags.com/zh/symbols/ios/find.html 发红包的限制 1.发送频率规则 ◆ 每分钟发送红包数量不得超过1800个: ◆ 同一个商户号,每分钟 ...
- 转: iOS崩溃堆栈符号表使用与用途
转:http://bugly.qq.com/blog/?p=119 iOS崩溃堆栈符号化,定位问题分分钟搞定! 2015.3.16 腾讯Bugly 微信分享 最近一段时间,在跟开发者沟通过程中,萝 ...
- iOS:bugly符号表上传
https://blog.csdn.net/weixin_38633659/article/details/81667721 这个篇文章已经讲得足够清楚 而且官方的文档也写得很好(注意官方网站上的文档 ...
- Bugly iOS自动导入符号表
前言 最近在处理Bugly问题的时候顺便解决了下符号表上传的问题,使用最新的上传工具包,也是顺便整理了下可以使用的脚本添加到了项目中,把这个过程中遇到的问题总结出来,脚本也会给出来,实 ...
- iOS - swift项目接入bugly - 报错, 配置符号表,下载Java环境,
1.pod 安装,无需配置任何东西 2.终端找到路径: pod install 3.在 appdelegate 导入 import Bugly extension AppDelegate{ /// ...
- 恢复二进制文件中的block符号表
前篇博客中,使用 杨君的小黑屋 提供的工具恢复二进制文件的符号表,只恢复了函数的符号表,本篇讲述如何恢复block符号表,杨君的博客中使用IDA分析二进制文件,本篇则使用MacOS系统上体验也不错的H ...
- bugly cocos 接入和 符号表使用
bugly cocos 接入和 符号表使用 在bugly网站下载 BuglyCocosPlugin 的sdk ios 1. 在 项目的 classes 里面新建 文件夹 BuglyCocosPlug ...
- MATLAB——matlab特殊符号表【转载】
链接来源: matlab特殊符号表 http://blog.sina.com.cn/s/blog_4a09187801014xg9.html Character Sequence Symbol Cha ...
- Mach-O在内存中符号表地址、字符串表地址的计算
KSCrash 是一个用于 iOS 平台的崩溃捕捉框架,最近读了其部分源码,在 KSDynamicLinker 文件中有一个函数,代码如下: /** Get the segment base addr ...
随机推荐
- log4net 将日志写入数据库
asp.net利用log4net写入日志到SqlServer数据库,Log4net是一个开源的错误日志记录项目,易用性强,源自log4j,品质值得信赖. 下面就我的安装部署log4net到MS sql ...
- python .whl文件与.egg文件用法
都是python 的包,可以用来安装的 __.whl__文件是一个python的包,对应的安装方式是: pip install xx.whl __.egg__文件也是一个python的包,对应的安装方 ...
- as3+java+mysql(mybatis) 数据自动工具(五)
现在介绍常量的配置,主要用于客户端(as3)与服务端(java)之间的常量同步,比如错误码.请求标识等 配置格式: <macros name="Macros" groupSt ...
- codeforce 609A - USB Flash Drives
排序水题 #include<iostream> #include<cstdlib> #include<cstdio> #include<algorithm&g ...
- javscript面试题(一)
你如何理解HTML结构的语意化? 1.去掉或样式丢失的时候能让页面呈现清晰的结构:2.屏幕阅读器(如果访客有视障)会完全根据你的标记来“读”你的网页:3.PDA.手机等设备可能无法像普通电脑的浏览器一 ...
- C# 结构体保存自定义文件 实例部分代码(转)
http://www.cnblogs.com/jxsoft/archive/2011/08/26/2154705.html
- 50道经典的JAVA编程题(46-50)
50道经典的JAVA编程题(46-50),最后五道题了,这是一个美妙的过程,编程真的能让我忘掉一切投入其中,感觉很棒.今天下午考完微机原理了,大三上学期就这样度过了,这学期算是解放了,可是感觉我还是没 ...
- 【原】lua的table深拷贝
一般写的时候要注意以下几个问题: 1.自己里面的属性是自己,要防止死循环 2.同一个table地址出现在table属性(k或者v)的不同地方,复制时不能复制成2个table地址,需与原来地址保持一致 ...
- static,interface and final
1.static: a)抽象类(abstract class):使用了 abstract 关键字所修饰的 类叫做抽象类.抽象类无法实例化,不能 new 出来一个抽象类的对象(实例). 抽象方法(abs ...
- android界面优化笔记(TODO)
1.避免使用线性布局的层层嵌套,要使用相对而已.使用线性布局越多,嵌套越多 官方文档: Optimizing Your UI