逆向核心环节,但是只能通过实践学习,后面有专门的实践文章。至于本文,浏览一遍即可,并安装几个分析工具。

静态分析

静态分析就是在不运行程序的情况下分析程序。

借助工具对二进制反汇编进行静态分析,可以得到被分析文件的反汇编代码、流程图及伪代码,也可以直接修改汇编指令,生成新的可执行文件。工具中提供的反编译伪代码功能让汇编基础薄弱的人也能做静态分析,大大降低了逆向工程的门槛。下面介绍两个反汇编工具:Hopper Disassembler 和 IDA Pro。

Hopper Disassembler

Hopper 提供 Mac、Linux 版本。使用 Hopper 打开目标可执行文件,可以自动识别出文件包含的架构,选择某一架构开始分析。

界面十分简洁易用,第一次使用的用户也能看出界面左侧是符号和字符串列表,中间是反汇编得到的代码。逆向工程中,我们一般是想了解或修改程序中的某个功能,而功能肯定是在某个函数内实现的,所以肯定要先找到函数名,然后才能找到对应的汇编代码进行静态分析。下面列举几个寻找目标函数的例子。

  • 按钮或其他视图的事件响应函数:通过界面调试工具,找到视图组件对象,打印出它的事件响应对象,进而得到响应函数。
  • 一些后台默默进行的任务,比如获取地理位置,向服务器发送操作日志:这类操作一般程序员都会封装为专门的工具类,通过提取头文件,分析头文件大概率可以找到。
  • 通过判断某个值来影响某个视图的内容:一切与视图有关的逆向分析都可以使用界面调试工具找到视图对象、控制器对象,然后结合对象所属类的头文件,分析行为可能发生在哪个函数内,然后去看汇编代码确保是目标函数。

找到目标函数后,如果是要学习功能实现,只看代码逻辑即可;如果要修改逻辑,可以 hook 目标函数或者判断条件的函数,或者在 hook 函数内直接将条件满足。后面文章会介绍 hook。

在用工具分析汇编代码时,有一些功能经常用到:

  • 查看某个函数的伪代码,工具条上有转换按钮。
  • 选中函数或变量,用快捷键 X 或在界面右边栏查看交叉引用,便于分析调用关系。
  • 用快捷键 G 跳转到指定地址。
  • 用快捷键 空格 在流程图和汇编代码之间切换查看。
  • Mac 逆向中经常会修改汇编指令,在菜单栏中可以打开修改窗口。

IDA Pro

IDA Pro 提供 Windows、Mac、Linux 版本,功能比 Hopper 多一些。目前网络上能免费下载的最高为 7.0 版本,但是在最新的 macOS 10.15 上无法安装,解决办法是先在低版本的 mac 上安装,把安装成功后的 app 直接复制到高版本 mac,如果没有低版本 mac,可以直接在这里下载。安装后有两个 app,分别提供 32 位和 64 位二进制文件的解析,如果打不开其中的 ida64,执行此命令即可:sudo xattr -rd com.apple.quarantine ida64.app。如果仍然打不开,则切换输入法为英文,再尝试。使用时如果崩溃,先检查输入法,只能用英文。

如果只做 iOS 逆向,那么这两个反汇编工具的比较:
Hopper 最新版($100) = IDA Pro 7.0(网上下载) > Hopper 旧版(网上下载)
IDA Pro 最新版($1000+)禁止大陆 IP 购买,不在考虑范围内。

使用 IDA 打开文件,选择架构。主界面整体结构和 Hopper 相同,拥有 Hopper 的所有功能,额外的常用功能有:

  • 快捷键 F5 用于查看伪代码,免费的前提下,可读性最高。
  • 快捷键 Tab 在汇编代码和伪代码间切换查看。
  • 菜单栏中提供多种搜索方式,包括字符串、立即数、地址等。
  • 在伪代码中按 / 输入注释,在汇编代码中按 / 把伪代码插入到汇编代码中。
  • 快捷键 N 可以给地址、标签、寄存器、变量重命名。
  • 选中数字,用快捷键 H 在十进制和十六进制互相转换。

动态调试

静态分析只能分析函数内部执行逻辑,但是如果想获取程序在运行过程中的参数传递、实际执行过程、寄存器和内存等信息,就需要使用动态调试。下面介绍 LLDB 命令行调试和直接使用 Xcode 调试。

LLDB

准备

Xcode 自带 LLDB 调试工具,支持本地/远程调试。当远程调试手机 App 时,Xcode 会将 debugserver 文件复制到手机中,在手机上启动一个服务,等待 Xcode 连接。所以得先真机调试一次,手机上才会有 debugserver 文件,位于 /Developer/usr/bin 目录。

debugserver 默认只能调试自己开发的应用,调试从 App Store 下载的应用会报错,这时需要给 debugserver 赋予调试任意程序的权限。

用 Xcode 创建一份 plist 文件,写入以下键值对:

ios sdk反编译 ios反编译工具_调试


如果 iOS 版本较高,如 iOS 12 以上,可能需要加更多键值对,我用的完整内容如下:

ios sdk反编译 ios反编译工具_iOS逆向_02


执行chmod +x debugserver以确保 debugserver 有执行权限,然后签名:

codesign -fs 'iPhone Developer: 证书 (xxx)' --entitlements xxx.plist debugserver

完成对 debugserver 授权后,将其复制到手机的 /usr/bin 目录下。打开目标程序,然后开启调试服务:

debugserver *:3333 -a TomatoTime,最后一个参数是可执行文件名或 pid。

此时被调试进程会被阻塞,等待 debugserver 的消息。如果正在调试 SpringBoard,则整个桌面都会卡住。在电脑命令行中执行 lldb,使用命令 process connect connect://192.168.1.107:3333 连接。如果连接成功会输出:

ios sdk反编译 ios反编译工具_iOS逆向_03


使用 Wi-Fi 连接比较慢,可以用之前提到的 USB 转发:执行 proxy 3333 3333,然后在 lldb 中执行 process connect connect://localhost:3333

一个超级大坑:按以上方法操作之后死活连不上,可能的原因是 debugserver 不支持 ipv6 地址,要在开启调试服务时指定使用 ipv4 地址:debugserver 127.0.0.1:3333 -a TomatoTime,如果不用 iproxy 就需要将 127.0.0.1 修改为电脑的地址。

调试

常用的调试命令及解释如下。注意是在命令行中调试,不是 Xcode 内置的 lldb。

1.1 continue:继续执行,简写 c
1.2 Control + C:暂停执行
1.3 s:step in
1.4 n:next

打断点时注意,因为地址空间随机化的存在,真实的虚拟地址等于模块被加载的基地址加上目标在模块内的偏移地址。
2.1 image list -o -f [模块名]:查看加载的各个[或指定]模块的基地址和文件位置,简写 im li
2.2 print:打印任何内容,简写 p。对于数字,print/x 或 p/x 可以指定打印格式为十六进制,经常用于:p/x 模块基地址+函数偏移地址 获得虚拟地址;打印对象时会打印所有属性,print object 简写 po,可以简洁地打印对象,一般调试只用 po;打印的内容还可以是寄存器、OC 函数等,例如 po $x0po [$x0 class],打印对象时如果对象实现了 description 方法则默认打印 description 方法的返回值。

3.1 breakpoint 0x12345678 或 breakpoint set -a 0x12345678:在某地址处打断点,简写 b 或 br s -a,a 是 address 缩写,-n 表示参数是函数名 name。
3.2 breakpoint disable [编号]或 enable,关闭或打开断点,简写 br dis 或 en;列举断点 breakpoint list,都是可以按单词举一反三的。删除 br del [编号]

4.1 register read x0 或 write x0 1:读写寄存器,简写 reg r 或 w
4.2 memory read 或 write:读写内存,打印当前栈的内容:memory read -force -f A $sp $fp;
4.3 x/5xg 0x1234:读取参数地址处到内存。第一个 x 是读内存,5g 是读 5*8 个字节,第二个 x 是按十六进制显示。
4.4 bt:打印函数调用栈,找到栈顶也就是调用者地址,image lookup -a 0x1234 得到地址所在模块,此地址减去模块基地址得到模块内偏移,到 IDA 中按 G 跳转到该偏移就得到了调用者函数。

5.1 finish:返回上层调用栈
5.2 thread return:不再执行下面代码,直接从当前调用栈返回一个值
5.3 thread info:当前线程信息

6.1 help xxx:查看 xxx 命令的使用方式
6.2 apropos xx:查看关键字含 xx 的命令的使用方式

此外,可以在 ~/.lldbinit 文件中自定义命令别名。


下面的内容有机会再补充

用 LLDB 解密
用 Xcode 调试第三方应用
LLDB 高级调试技巧