LLDB与debugserver
LLDB是Xcode内置的动态调试工具。debugserver运行在iOS上,他作为服务端实际执行LLDB传过来的命令,显示给用户,即所谓的”远程调试”。注意
,设备只有连电脑真机测试过APP了,debugserver才会安装进设备的/Developer/usr/bin
目录下。但是由于缺少 task_for_pid
权限,通过Xcode安装的debugserver只能调试自己的app。想要调试别人的App要做下面的这些步骤配置:
配置debugserver
1、帮debugserver瘦身
找出你的设备支持arm,比如我的是ipod5,那么支持的是 armv7 。将未处理的debugserver从iOS拷贝出来,放到 OSX上的 /Users/用户名/
目录下。1
scp -P 2222 root@localhost:/Developer/usr/bin/debugserver ~/debugserver
然后帮他减肥:1
lipo -thin armv7 ~/debugserver -output ~/debugserver
注意把 armv7换成你的设备对应的ARM
2、给debugserver添加task_for_pid权限
下载 ent.xml 到 OSX的 ~
目录,然后运行一下命令:1
/opt/theos/bin/ldid -Sent.xml debugserver
正常情况下5秒内就能执行完毕。如果不行,就下载 ent.plist。然后执行1
codesign -s - --entitlements ent.plist -f debugserver
3、将经过处理的debugserver拷回iOS
1 | scp -P 2222 ~/debugserver root@localhost:/usr/bin/debugserver |
用debugserver启动或附加进程
启动进程 (一般用于从程序启动开始调试)
1 | 格式: |
上面的例子启动MobileSMS,并且开启1234端口,等待任意IP地址的LLDB接入。
附加到进程
1 | debugserver *:1234 -a "SpringBoard" |
同样,附加到了进程SpringBoard
,等待LLDB的接入
LLDB的使用说明
这里使用USBmuxd链接调试
1 | 先按照正常的USB链接流程 22:2222端口的那个链接着 |
image list
用于列举当前进程中的所有模块。因为ALSR的关系,每次进程启动时,所有的模块在虚拟内存中的起始地址都会产生随机偏移。这个起始位置是接下来会频繁用到的一个关键数据。需要用到image List
来需找。待LLDB链接上debugserver,输入下面命令1
2(lldb) image list -o -f | grep Foundation
[ 0] 0x045d7000 /System/Library/Frameworks/Foundation.framework/Foundation(0x0000000026bca000)
第一列是:模块序号。 第二列是:模块在虚拟内存中的起始地址因ASLR而产生的随机偏移。第三列是:全路径。括号里面的是偏移后的地址。。 以Foundation这个模块举个例子:模块的偏移值是0x045d7000
,偏移后的地址是0x0000000026bca000
。那么它的偏移前的地址是 0x0000000026bca000 - 0x045d7000 = 0x00000000225F3000
;
符号基地址
用image List
分析出了模块的基地址,接下来分析一下符号基地址
。我们以NSLog
为例(在foundation模块)。到IDA中去foundation中搜搜NSLog,然后双击跳转到他的实现。找出NSLog相对于Foundation的相对位置。方法如下:1
2
3NSLog函数的第一条指令 "SUB SP,SP,#0xC"左边的那个数#0x226038A4,代表NSLog再Foundation中的位置
减去Foundation第一行 "HEADER:225F3000"中提取出来的225F3000,就是NSLog函数在Foundation中的相对位置,即 0x226038A4 - 0x225F3000 = 0x108A4。
因此NSLog的实际地址为 模块的基地址(偏移后的) + 符号相对于模块的地址。 为0x0000000026bca000 + 0x108A4 = 0x26BDA8A4
所以无论是符号地址,还是指令地址,都是相对于模块的地址的。只要知道了相对于模块的地址,然后知道了模块的地址,就能计算出你要的指令地址。
breakpoint
用于设置断点
//在函数的起
#b function
1 | (lldb) b NSLog |
//在地址处设置断点
#br s -a address
#br s -a ‘ASLROffset+address’
1 | (lldb) br s -a 0xCCCCC |
breakpoint 后面那个是断点的序号。逆向工程中大多数是给汇编指令打断点。
实战例子
这里掩饰一个 [SpringBoard _menuButtonDown:] 函数的第一条指令设置断点为例子,演示一下操作流程。
1、用IDA查看偏移前基地址。
查找 [SpringBoard _menuButtonDown:],的第一条指令的偏移前基地址为0x00017720。
lldb到debugserver, image list -o -f。列出模块偏移地址如下:
看到springBoard的偏移是 0x0009c000。 所以 _menuButtonDown:第一条指令的地址就是
1 | 0x0009c000 + 0x00017720 = 0xB3720 |
在llDB中输入命令给这个地址下断点:
1 | (lldb) br s -a 0xB3720 |
按下Home键触发断点,如图
注:
r1 寄存器放的是函数第二个参数的名称。第二个参数的名称是函数名。当程序停下来后,可以用
1 | c |
命令让程序继续运行.
2、断点命令
- 设置断点
1 | //给某个地址下断点 |
- 禁用断点:
1 | //禁用所有断点 |
- 启动断点:
1 | //启动所有断点 |
- 删除断点:
1 | //删除所有断点 |
某个断点触发的时候,执行预先设置的指令,它的用法如下(假设1号断点位于某个objc_msgSend函数上):
1 | (lldb) br com add 1 |
执行这条命令后会要求我们设置一系列指令,以”Done”结束,如下:
1 | Enter your debugger command(s). Type 'DONE' to end. |
c
命令是继续执行的意思
3、 print
打印某处的值。 仍然以[SpringBoard _menuButtonDown:]里的指令为例。1
2(lldb) po $r0
(lldb) p (char *)$r1
4、命令 ni
/ si
执行下一条指令。是 nexti
/stepi
的简写。
如果下一步是个函数调用:
stepi 在执行下一步的时候会进入函数体。
nexti 在执行下一步的时候不会进入函数体。
5、当进程停留在某一条”BL”指令上时,LLDB会自动解析这条指令,把指令对应的符号注释出来。但是,LLDB的解析有时会出错,注释出的符号不对。这种情况下,请以IDA静态分析出的符号为准。
6、命令 X
打印一个地址处存放的值,如下:
1 | (lldb) p/x $sp |
7、给指定的寄存器赋值,从而 “对程序进行改动,观察程序的执行过程有什么变化”
1 | //写1 给寄存器r0 |
lldb常用命令
lldb p命令
p打印变量的值, 打印出来默认是十进制的值。 p/t输出二进制,p/x输出16进制。
lldb po命令
是打印对象的,就是调用对象的 description的函数
lldb re命令
格式 register read –format 寄存器
register read 寄存器
简写 re r/x 寄存器
读取寄存器的值, r/x 16进制来表示。 r/t是二进制 。re r -a
读取全部寄存器的内容
内存读取 memory
内存读取,从0xbffff3c0开始读,并且4字节 16进制 连续8个
1 | (lldb) memory read -size 4 -format x -count 8 0x00116080 |
LLDB调试MAC APP
运行
直接上命令, 敲入下面的命令。
1 | lldb /Applications/xxx.app |
然后输入命令 r
。就会启动开始run 对应的app。
打断点
直接看ida分析出来的地址,貌似没有随机偏移地址。ida上分析的地址是多少就直接打多少地址