在使用class-dump、hopper/ida等工具静态分析程序之后,很可能要定位出关键函数在哪里(进而反汇编或注入这段代码),而动态调试程序是定位关键代码的有效辅助办法。


(一)目标

在mac机上远程调试手机上的进程。

苹果机之动态调试 | 定位关键代码_java


(二)问题

(1)为什么不用本地调试的方式?

可以在手机上安装gdb,再使用gdb调试目标进程。但因为本地用gdb来调试可能会遇到很多gdb本身的问题(有一些可以解决,而有一些并不好解决;后续可以介绍这方面的内容),而lldb被苹果支持,并用于替代gdb。所以,可考虑使用更好的工具,即lldb+debugserver来远程调试目标进程。


(2)需要什么工具?

mac上需要lldb,有安装xcode便有lldb。

手机上需要安装debugserver。


(3)手机如何安装debugserver?

debugserver在哪里?

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/xx.xx(目标手机是什么系统就选择什么版本的目录),双击下面的文件:DeveloperDiskImage.dmg,在打开的finder中,usr/bin目录中存放debugserver。

怎么定制手机上相应的debugserver?

(a)

双击打开DeveloperDiskImage.dmg后,

cd /Volumes/DeveloperDiskImage/Library/PrivateFrameworks,

scp -r ARMDisassembler.framework root@192.168.2.22:/System/Library/PrivateFrameworks/,

把ARMDisassembler.framework拷贝到手机。之所以这样做,是据别人的经验,可以让lldb看汇编代码的效果更好,不妨照做。

(b)

把usr/bin中的debugserver拷贝到另一个目录,为后续的签名作准备。

(c)

创建一个entitlement.xml文件,内容如下:

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">

<plist version="1.0">

<dict>

    <key>com.apple.springboard.debugapplications</key>

    <true/>

    <key>get-task-allow</key>

    <true/>

    <key>task_for_pid-allow</key>

    <true/>

    <key>run-unsigned-code</key>

    <true/>              

</dict>

</plist>                

(d)

使用ldid来签名:

ldid -Sentitlement.xml debugserver

拷贝到手机:

scp debugserver root@192.168.2.22:/usr/bin/

在手机上测试是否可用:

debugserver


(4)重签名工具ldid怎么安装?

如果有安装iOSOpenDev,那在目录/opt/iOSOpenDev/bin/下面就有ldid。

如果没有,那可以拉代码下来安装:

git clone git://git.saurik.com/ldid.git

cd ldid

git submodule update --init

./make.sh

which ldid #查看是否安装成功


(5)怎么使用debugserver与lldb?

在手机上启动服务器debugserver:

debugserver *:54321 -a "Spotify"

*表示侦听任意ip的连接,54321为端口,-a连接目标进程的名称(可用ps取得)。

然后,在mac上启动lldb并连接服务器:

process connect connect://192.168.2.22:54321。

如果在手机端有看到:Waiting for debugger instructions for process 0,那说明已经连接上。

之后可以在lldb端发送命令,比如查看当前调试进程的模块信息:

image list -o -f

注意,第一次image list -o -f,可能要等上几分钟。当有返回后,再输入c,就可以让程序继续运行。


(6)如果觉得debugserver运行很慢,怎么解决?

使用usbmuxd来启用数据线来调试。

* 如何安装与使用usbmuxd?

    * 下载:http://7xibfi.com1.z0.glb.clouddn.com/uploads/default/original/2X/a/aa9cecf05b47d08a59324edeaaeea3f17e0608ee.zip

    * 具体可以参考:http://www.jianshu.com/p/9333a706641a。

我直接用lldb跟debugserver,就第一次image list等几分钟,整体还可以接受


(7)怎么调试?

* debugserver *:54321 -a "Spotify"  手机上启动debugserver并附加到目标进程

* lldb  mac上启动lldb

* process connect connect://192.168.2.22:54321  lldb连接debugserver

* image list -o -f  查看目标进程的所有模块的信息,比如:

    [  0] 0x000d8000 /var/containers/Bundle/Application/A80AF35B-DAD3-4D7E-B467-6C4230E32556/Spotify.app/Spotify(0x00000000000dc000)

    第二个值为程序在内存中的基地址,记录这个值。

* br s -a (0x000d8000+目标代码的偏移地址)

* c  让程序继续执行

* (操作程序)让程序触发断点。

* 使用lldb命令进行调试。


(三)示例

这里简单演示下怎么调试Spotify这个应用。

从class-dump得到的信息,尝试调试类SPTNowPlayingBarModel的pause函数,看看在播放bar条上暂停播放时会不会断在这个函数上。

class-dump得到的pause的地址是0x00477ff1,在hopper上跳转到这个地址(G),也可以看到-[SPTNowPlayingBarModel pause]的信息。


* 运行目标应用

* debugserver *:54321 -a "Spotify"   手机端启动服务器,之后都在客户端lldb中操作。

* lldb

* process connect connect://192.168.2.22:54321

* image list -o -f 或 image list -o -f | grep Spotify

这一步可能要等一会,在ipod5的6.0系统会很快,而在iphone5s的10.1.1系统则很慢。

* 从上面的命令找到基地址为:0x000e6000 (每次运行不一样)

* 查看一下即将下断点的代码是怎么样的:dis -a '0x000e6000+0x00477ff1'  结果跟用hopper看到一样(0x0047ff1处)。

* br s -a '0x000e6000+0x00477ff1' 

* c

* 让程序触发到断点,使用lldb的命令查看数据或设置值,或单步等。


(四)lldb的常用命令

打印信息:

expr或e、po、p或print

display 表达式     

undisplay 序号


断点:

br l或breakpoint list  #所有断点

breakpoint set -a 函数地址,可简写:br s -a xxx  #下断点

br s --函数关键字(可模糊)

    breakpoint set --shlib foo.dylib --name foo

br del 1    #删除断点

br del 2 3 4    #删除几个断点

内存断点 

    watchpoint set expression 地址 

    watchpoint set variable 变量名称

条件断点

    watchpoint modify -c 表达式


单步:

s/si    #单步进入函数

n/ni    #单步

f       #跳出函数


线程:

thread backtrace,或bt,或bt all  #列出所有线程


模块信息:

image list [-o -f]

image lookup -a expression  

    image lookup -a $pc

image lookup -r -n xxx

    image lookup -r -n playPause #用来查看一个函数(关键字)的地址

image lookup -r -s


显示地址处的代码:

disassemble/dis -a 地址

    dis -a $pc

    dis -s 0x0002c000 -c 9  #后面的参数-c用来限制显示的代码数。


内存操作:

memory read [起始地址 结束地址]/寄存器 -outfile 输出路径


寄存器操作:

register read/格式

register write 寄存器名称 数值

https://mp.weixin.qq.com/s/rZRqZxefbuVipMlz4DsZXA