在逆向过程中,我们需要通过各种工具和方法分析程序,找到合适的突破点破解程序,其中最重要的分析手段是静态分析和动态调试。
静态分析是指在不运行程序的前提下进行程序分析的一种方法,一般会配合已经得到的线索和分析工具来进行分析。静态分析相关的文章比较多,工具也比较丰富,类似 Hopper Disassembler、IDA、class-dump 等,这里不再赘述。
这篇文章重点来讲下动态调试部分,如何在越狱手机上动态调试 App Store 下载的应用(以微博为例)。先列举下自己的设备情况
- iPhone 5s, iOS 9.2
- 已越狱
- 通过 USB 线 SSH 2222 端口连接到手机
准备调试工具
LLDB 原理
正向开发过程中,相信没有哪个开发者会没用到过 LLDB 进行调试。
通过 Xcode 调试,实际上是把调试指令发送到手机上的 debugserver 服务端,debugserver 是专门用来连接 Mac 上的 LLDB 客户端,并接收 LLDB 所提供的命令,从而达到调试的目的。
当手机设备连接到 Mac 进行真机调试的时候,debugserver 才会被安装到 Developer/usr/bin 目录下。但是,debugserver 默认只能对自己开发的 App 进行调试,要想调试其他 App 的话,需要进行如下的工作:
获取 debugserver
首先将手机里的 debugserver 拷贝到 Mac 中,这里通过 iFunBox 进行拷贝。用 USB 线将手机连接到 Mac,打开 iFunBox,左侧边栏选中文件系统,进入目录 /Developer/usr/bin
,将 debugserver 文件拖拽到 Mac 端桌面上,如下图所示
给 debugserver 赋予权限
在桌面新建 entitlements.plist
文件,写入以下内容:
com.apple.springboard.debugapplications
get-task-allow
task_for_pid-allow
run-unsigned-code
使用 codesign 进行签名,命令如下:
codesign -s - --entitlements entitlements.plist -f debugserver
将 debugserver 复制回手机
通过 iFunBox 将修改好的 debugserver 拷贝到手机的 /usr/bin/
目录下,然后就可以对从 App Store 下载的 App 进行调试了
调试实战
准备好上述的 debugserver 之后就可以开始对从 App Store 下载的 App 进行调试了,这里以微博为例。
这里说明一下,动态调试通常是建立在静态调试的基础之上的。比如已经通过 class-dump 生成了头文件并找到了疑似的调用点,然后通过 Hopper Disassembler 反编译基本确认了这个调用点就是我们逆向的入口,那么此时就可以通过动态调试获取寄存器的值证实我们的猜测,还可以修改寄存器的值寻找可能的方法,最终达到破解的目的。
比如我们想要破解微博的登录功能(下图)
我们已经通过 class-dump 和 Hopper Disassembler 找到了疑似的调用点
- [WBLoginViewController loadloginEngineWithUserName:password:]
,查看一下这个函数的实现:
先记录下函数对应的偏移地址
0x00000001014affc8
(以下简称 D1),待会会用到。
LLDB 连接手机
现在就可以通过 LLDB 动态调试来验证一下我们的猜测,新建一个终端窗口(以下简称终端 A),通过 USB 线 SSH 2222 端口连接到手机,点击微博让进程运行起来,然后输入
ps aux
命令找到微博对应的进程 ID 为 798:
找到目标进程之后就可以通过进程 ID 进行调试了,在终端 A 输入以下命令:
debugserver *:1234 -a 798
此时微博界面会卡住,进入等待连接的状态。然后新建一个终端窗口(以下简称终端 B),输入 iproxy 1234 1234
命令进行端口转发。
新建一个终端窗口(以下简称终端 C),输入 lldb
并回车进入调试状态,然后输入以下命令连接上手机的 debugserver 服务端:
process connect connect://localhost:1234
可以看到终端 C 有下图所示的输出,代表连接成功,可以开始调试了:
终端 C 输入命令
c
然后回车让程序继续运行并响应外部事件。
打印寄存器的值
我们现在需要在函数 - [WBLoginViewController loadloginEngineWithUserName:password:]
上打一个断点好让程序执行到这个函数的时候可以断下来,那么就需要将模块的基地址加上之前说到的函数偏移地址 D1 得到函数在内存中的真实地址。在终端 C 中输入以下命令可以获取模块的基地址(以下简称 D0):
image list -o -f "Weibo"
结果如下图:
函数在内存中的真实地址 = D0 + D1,执行以下命令对该函数下断点:
br s -a 0x00000000000b0000+0x00000001014affc8
下完断点后在终端 C 中输入
c
然后回车让程序可以继续运行。
在微博上输入用户名
10010
和密码 123
,点击登录按钮
可以看到微博再次卡住,终端 C 输出了断点对应的信息:
在终端 C 中用
po
命令打印出各个寄存器的值:
参考文章:深入iOS系统底层之函数调用可知,x0、x1 分别为函数隐藏的参数 self 和 cmd,而 x2、x3、x4...分别对应函数的参数,可以看到我们输入的用户名
10010
和密码 123
都被正确打印出来了。至此,我们成功地在越狱手机上对 App Store 下载的应用进行动态调试
接下来,我们可以用
bt
打印当前堆栈信息、通过 expression
命令修改寄存器的值等等,这些命令我们平时正向开发都有用到,可以参考文章与调试器共舞 - LLDB 的华尔兹,这里不再赘述。
结语
这是逆向之旅系列的第一篇文章,实现了用命令行动态调试第三方应用的功能。
但是我们可以发现过程比较繁杂,用到的命令也比较多,而且因为没有符号表,所以断点输出的信息很多都是没被符号化的,不够完美。接下来会撰文总结如何用熟悉的 Xcode 调试第三方应用,以及如何恢复符号表信息,从而可以在调试过程中获取更多的信息,最终达成破解的目的。