iOS符号表恢复&逆向支付宝-阿里云开发者社区

开发者社区> 玄学酱> 正文

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。


  1. //  位于系统库 <macho-o/nlist.h> 头文件中 
  2. struct nlist {   
  3.   union {   
  4.     // 符号名在字符串表中的偏移量 
  5.     uint32_t n_strx;   
  6.   } n_un; 
  7.   uint8_t n_type; 
  8.   uint8_t n_sect; 
  9.   int16_t n_desc;   
  10.   // 符号在内存中的地址,类似于函数指针 
  11.   uint32_t n_value; 
  12. };  

这里重点关注第一项和最后一项,第一项是符号名在字符串表中的偏移量,用于表示函数名,最后一项是符号在内存中的地址,类似于函数指针(这里只说明大概的结构,详细的信息请参考官方 Mach O 文件格式的文档)。

也就是说如果我们知道了符号名和内存地址的对应关系,我们是可以根据这个结构来逆向构造出符号表数据的。

知道了如何构造符号表,下一步就是收集符号名和内存地址的对应关系了。

获取 OC 方法的符号表

因为 OC 语言的特性,编译器会将类名、函数名等编译进最后的可执行文件中,所以我们可以根据 Mach-O 文件的结构逆向还原出工程里的所有类,这也就是大名鼎鼎的逆向工具 class-dump 了。class-dump 出来的头文件里是有函数地址的:

所以我们只要对 class-dump 的源码稍作修改,即可获取我们要的信息。

符号表恢复工具

整理完数据格式,又理清了数据来源,我们就可以写工具了。

实现过程就不详细说明了,工具开源在我的 Github 上了,链接:

https://github.com/tobefuturer/restore-symbol

我们来看看怎么用这个工具:

1. 下载源码编译


  1. git clone --recursive https://github.com/tobefuturer/restore-symbol.git 
  2. cd restore-symbol && make 
  3. ./restore-symbol  

2. 恢复 OC 的符号表,非常简单


  1. ./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 在内存中是以一个结构体的形式存在的,大致的结构如下:


  1. struct __block_impl {   
  2.   /** 
  3.   block 在内存中也是类 NSObject 的结构体, 
  4.   结构体开始位置是一个 isa 指针 
  5.   */ 
  6.   Class isa;   
  7.   /** 这两个变量暂时不关心 */ 
  8.   int flags;  int reserved;   
  9.   /** 
  10.   真正的函数指针!! 
  11.   */ 
  12.   void (*invoke)(...); 
  13.   ... 
  14.  

说明下 block 中的 isa 指针,根据实际情况会有三种不同的取值,来表示不同类型的 block:

  1. _NSConcreteStackBlock

栈上的 block,一般 block 创建时是在栈上分配了一个 block 结构体的空间,然后对其中的 isa 等变量赋值。

   2._NSConcreteMallocBlock

堆上的 block,当 block 被加入到 GCD 或者被对象持有时,将栈上的 block 复制到堆上,此时复制得到的 block 类型变为了 _NSConcreteMallocBlock。

   3._NSConcreteGlobalBlock

全局静态的 block,当 block 不依赖于上下文环境,比如不持有 block 外的变量、只使用 block 内部的变量的时候,block 的内存分配可以在编译期就完成,分配在全局的静态常量区。

第 2 种 block 在运行时才会出现,我们只关注 1、3 两种,下面就分析这两种 isa 指针和 block 符号地址之间的关联。

block isa 指针和符号地址之间的关联

分析这部分需要用到 IDA 这个反汇编软件 , 这里结合两个实际的小例子来说明:

1._NSConcreteStackBlock

假设我们的源代码是这样很简单的一个 block:


  1. @implementation ViewController 
  2.  
  3. - (void)viewDidLoad { 
  4.     int t = 2; 
  5.     void (^ foo)() = ^(){ 
  6.         NSLog(@"%d", t); //block 引用了外部的变量 t 
  7.     }; 
  8.     foo(); 
  9.  
  10. @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 的复制等等操作,他存在于可执行文件的常量区里。

不太理解的话,看个例子:

我们把源代码改成这样:


  1. @implementation ViewController 
  2.  
  3. - (void)viewDidLoad { 
  4.  
  5.     void (^ foo)() = ^(){ 
  6.         //block 不引用外部的变量 
  7.         NSLog(@"%d", 123); 
  8.     }; 
  9.     foo(); 
  10.  
  11. @end  

那么在编译后会变成这样:

那么借鉴上面的思路,在逆向分析的时候,我们可以这么推断

  • 在静态常量区发现一个 _NSConcreteGlobalBlock 的引用
  • 这个地方必然存在一个 block 的结构体数据
  • 在这个结构体第 16 个字节的地方会出现一个值,这个值是一个 block 的函数地址

3. block 的嵌套结构

实际在使用中,可能会出现 block 内嵌 block 的情况:


  1. - (void)viewDidLoad { 
  2.   dispatch_async(background_queue ,^{ 
  3.     ... 
  4.     dispatch_async(main_queue, ^{ 
  5.       ...      
  6.     }); 
  7.   }); 
  8.  

所以这里 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 符号表

这里简单介绍下怎么使用上面这个脚本:

  1. 用 IDA 打开支付宝的 Mach-O 文件
  2. 等待分析完成! 可能要一个小时
  3. Alt + F7 或者 菜单栏 File -> Script file... 

  4.等待脚本运行完成,预计 30s 至 60s,运行过程中会有这样的弹窗

 5.弹窗消失即 block 符号表提取完成

 6.在 IDA 打开文件的目录下 , 会输出一份名为block_symbol.json的 json 格式 block 符号表

 

恢复符号表 & 实际分析

用之前的符号表恢复工具,将 block 的符号表导入 Mach-O 文件


  1. ./restore-symbol ./origin_AlipayWallet -o ./AlipayWallet_with_symbol -j block_symbol.json 

-j 后面跟上之前得到的 json 符号表

最后得到一份同时具有 OC 函数符号表和 block 符号表的可执行文件

这里简单介绍一个分析案例 , 你就能体会到这个工具的强大之处了。

1.在 Xcode 里对 -[UIAlertView show] 设置断点

2.运行程序,并在支付宝的登录页面输入手机号和 错误的密码 ,点击登录

3.Xcode 会在 ‘密码错误’ 的警告框弹出时停下,左侧会显示出这样的调用栈

一张图看完支付宝的登录过程

项目开源地址:

https://github.com/tobefuturer/restore-symbol

欢迎大家在上面提各种 Issues,或者有问题也可以直接 Email(tobefuturer@gmail.com)。




作者:杨君
来源:51CTO

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
10081 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,大概有三种登录方式:
2962 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
10883 0
使用OpenApi弹性释放和设置云服务器ECS释放
云服务器ECS的一个重要特性就是按需创建资源。您可以在业务高峰期按需弹性的自定义规则进行资源创建,在完成业务计算的时候释放资源。本篇将提供几个Tips帮助您更加容易和自动化的完成云服务器的释放和弹性设置。
12074 0
分享到支付宝demo测试流程(iOS版)
产品首页:[url]https://openclub.alipay.com/read.php?tid=13656&fid=72[/url] 1、创建应用,并添加功能 如何创建应用:[url]https://openclub.
376 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
7365 0
+关注
玄学酱
这个时候,玄酱是不是应该说点什么...
20710
文章
438
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载