动态调试
一、什么是动态调试
- 动态调试就是将程序运行起来,通过打断点、打印等方式,查看参数、返回值、函数调用流程等信息
- 之前我们说的静态分析,就是程序不运行的时候,对程序的可执行文件进行分析,分析头文件、伪代码之类的信息
- 学会动态调试之后,我们就可以分析某个程序的整体调用流程了,例如:分析微信抢红包的时候,就可以知道微信调用了哪些方法去抢红包,以便我们hook
二、Xcode动态调试的原理
- 其实Xcode自带的LLDB工具,就是动态调试的工具,我们平常开发的时候也一直在用,在Xcode打断点,程序就会暂停,输入lldb指令,就会出现结果,那么Xcode动态调试的原理是怎样的呢?
- Xcode动态调试的原理是这样的:我们用Xcode启动程序的时候,就会在自动在手机上安装一个
debugserver
程序,Xcode的LLDB工具会跟debugserver
程序进行交互,将lldb指令传输给debugserver
程序,debugserver
程序又会将lldb指令传输给App,App执行完之后,会将返回值传输给debugserver
程序,debugserver
程序又发给LLDB,在控制台显示出结果,如下图所示:
Xcode动态调试的原理.png
- Xcode动态调试的原理是这样的:我们用Xcode启动程序的时候,就会在自动在手机上安装一个
-
debugserver
程序一开始是存放在Mac中的Xcode里面的,手机里是没有的,只有当Xcode识别到手机设备时,才会自动把debugserver
程序安装到手机上。debugserver
的具体路径,如下所示
-
debugserver在Mac上的路径是:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/13.2/DeveloperDiskImage.dmg/usr/bin/debugserver
debugserver安装到手机的路径是:
/Developer/usr/bin/debugserver
- 需要注意的是,Xcode动态调试,只能调试通过Xcode安装的App,也就是说我们逆向别人App的时候,是无法用Xcode动态调试来调试的,那么如何才能调试任意App呢?
三、动态调试任意App的原理
- 想要调试任意App,我们就需要用终端取代Xcode,终端里的LLDB工具与手机的
debugserver
程序交互,来传输lldb命令,如下所示:
动态调试任意App原理.png
- 想要调试任意App,我们就需要用终端取代Xcode,终端里的LLDB工具与手机的
- 我们知道
Xcode安装的debugserver
只能与Xcode安装的App
进行交互,因为沙盒机制的原因,导致debugserver
没有权限与其他App产生交互,所以我们还需要给debugserver
赋予相应的权限,才能与任意App进行交互
- 我们知道
- 动态调试任意App只需要三步:将有权限的debugser安装到手机、让debugserver与App建立交互、让debugserver与LLDB产生交互
四、动态调试任意App的第一步:将有权限的debugserver安装到手机
这一步是一劳永逸的,安装完后,以后只需要第二步和第三步,就可以启动动态调试了
- 在Mac的中找到与手机系统版本对应的
debugserver程序
,Mac中的路径如下:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/13.2/DeveloperDiskImage.dmg/usr/bin/debugserver
- 在Mac的中找到与手机系统版本对应的
-
- 使用
ldid签名工具
给debugserver
新增两个权限:get-task-allow、 task_for_pid-allow
,这两个都是Bool值,设置为YES,新增权限流程如下:
-
(1). 通过ldid命令,导出
debugserver
现有的权限,命令是:
$ ldid -e debugserver > debugserver.entitlements
-
(2). 编辑
编辑debugserver.entitlements文件.pngdebugserver.entitlements
文件,新增get-task-allow、 task_for_pid-allow
,值设置为YES,如下图所示:
-
(3). 通过ldid命令,对debugserver进行重新签名,命令是:
$ ldid -Sdebugserver.entitlements debugserver
,注意S是大写,并且与debugserver紧挨着。
- 使用
- 将已经签好权限的
debugserver
放到手机的/usr/bin
里面,便于手机找到debugserver
命令
- 将已经签好权限的
五、动态调试任意App的第二步:让debugserver与App建立交互
- 远程登录到手机后 ,让debugserver附加到某个App进程,命令如下:
打开命令行:sh usb.sh
新建命令行:sh login.sh,登录到手机后
让debugserver附加到某个App进程:$ debugserver 127.0.0.1:端口号 -a 进程名
例如让debugserver附加到微信的进程:$ debugserver 127.0.0.1:10011 -a WeChat
- 重点命令就这一句话:
$ debugserver 127.0.0.1:端口号 -a 进程名
,其中127.0.0.1:端口号
代表使用iPhone的某个端口启动debugserver服务,只要不是保留端口号就行
- 重点命令就这一句话:
六、动态调试任意App的第三步:让debugserver与LLDB建立交互
- 在Mac上启动
LLDB
服务,并且远程连接到iPhone的debugserver
服务,命令如下:
- 在Mac上启动
启动LLDB
` $ lldb `
连接到iPhone的debugserver服务
` (lldb) process connect connect://手机IP地址:debugserver服务的端口号 `
以微信为例,连接到iPhone的debugserver服务:
` (lldb) process connect connect://10.88.211.170:10011 `
- 重点命令就这一句话
process connect connect://手机IP地址:debugserver服务的端口号
,如果输入完这句命令后,出现以下提示,就说明已经动态调试已经就绪,可以执行LLDB指令来调试了(注意此时会处在线程卡死状态,需要执行(lldb) c
命令,让程序先运行起来)
- 重点命令就这一句话
(lldb) process connect connect://localhost:10011
Process 1194 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x000000018da64634 libsystem_kernel.dylib`mach_msg_trap + 8
libsystem_kernel.dylib`mach_msg_trap:
-> 0x18da64634 <+8>: ret
libsystem_kernel.dylib`mach_msg_overwrite_trap:
0x18da64638 <+0>: mov x16, #-0x20
0x18da6463c <+4>: svc #0x80
0x18da64640 <+8>: ret
Target 0: (WeChat) stopped.
七、使用USB方式动态调试任意App
- 上面说的流程是通过用Wifi的方式传输LLDB命令的,传输速度较慢,所以推荐使用USB的方式建立交互,也就是:将
Mac的10011端口
与iPhone的10011端口
建立映射,然后debugserver服务
使用iPhone的10011端口
,此时Mac的LLDB工具
与Mac的10011端口
传输数据就相当于与iPhone的10011端口
传输数据,如下所示:
- 上面说的流程是通过用Wifi的方式传输LLDB命令的,传输速度较慢,所以推荐使用USB的方式建立交互,也就是:将
在Mac上输入命令,让10011端口与10011端口映射、10010与22端口映射
`python /Users/songpeng/Documents/python-client/tcprelay.py -t 22:10010 10011:10011`
在iPhone上执行下面的命令,启动debugserver服务,端口号为10011
`$ debugserver 127.0.0.1:10011 -a WeChat`
在Mac上执行以下命令,让LLDB与本地的10011传输数据,由于映射已经建立,就相当于给iPhone的debugserver服务发送数据
`$ lldb `
`(lldb) process connect connect://localhost:10011 `
- 可以将以前写的
usb.sh
文件的内容改成python /Users/songpeng/Documents/python-client/tcprelay.py -t 22:10010 10011:10011
,这样即可以用USB的方式SSH登录到手机,也可以以USB的方式让LLDB与debugserver建立交互。以后使用起来就很方便了
- 可以将以前写的
- 3. 我们总结一下,使用USB方式动态调试任意App的全部流程,如下所示,以后忘记原理没关系,只需要按顺序输入以下命令,就可以顺利开启动态调试
在Mac上打开命令行窗口,让10011端口与10011端口映射、10010与22端口映射
`$ sh usb.sh`
在Mac上新建命令行窗口,然后SSH登陆到手机
`$ sh login.sh`
登录到手机后,启动手机的debugserver服务,让其与App建立交互
`iPhone7ceshiji:~ root# debugserver 127.0.0.1:10011 -a WeChat`
在Mac上新建命令行窗口,进入lldb工具,并且让LLDB与debugserver建立交互
`$ lldb`
`(lldb) process connect connect://localhost:10011`
使用LLDB命令c,先让程序继续运行
`(lldb) c`
- 动态调试建立好之后,我们就可以使用LLDB指令,来正式开始调试了
八、LLDB指令
- LLDB指令的格式是:
<command> [<subcommand> [<subcommand>...]] <action> [-options [option- value]] [argument [argument...]]
,其中,command
代表命令,subcommand
代表子命令,action
代表命令的动作,-options
代表命令的选项,argument
代表命令的参数,[]
中的可以省略。
- LLDB指令的格式是:
-
help
命令,用于查看指令的用法,例如:help breakpoint、help breakpoint set
-
-
expression
命令,执行一个表达式,例如:expression self.view.backgroundColor = [UIColor redColor]
,expression与print、p、call
的效果一样
-
-
thread backtrace
命令,打印线程的堆栈信息,与bt命令
效果一样
-
-
thread return []
命令,让函数直接返回某个值,不会执行断点后面的代码了,例如:thread return 3
,返回了3
-
-
frame variable []
命令,打印当前栈帧的变量
-
-
thread continue、continue、c
命令,让程序继续运行
-
-
thread step-over、next、n
命令,单步执行,把子函数当做一个整体,不会进入子函数
-
-
thread step-in、step、s
命令,单步执行,遇到子函数会进入子函数内部
-
-
-
breakpoint set
命令,设置断点,参数主要有以下几种:
breakpoint set -a 函数地址
breakpoint set -n 函数名
breakpoint set -r 正则表达式
breakpoint set -s 动态库 -n 函数名
-
-
breakpoint list
命令,列出所有的断点,每个断点都有自己的编号
-
-
breakpoint delete 断点编号
命令,删除某个断点
-
-
breakpoint command add 断点编号
命令,给断点预先设置需要执行的命令,到触发断点时,就会按顺序执行
-
-
breakpoint command list 断点编号
命令,查看某个断点的预设命令
-
-
-
watchpoint内存断点
,就是当内存数据改变时,触发此断点,以便确认是谁修改了内存,子命令主要有以下几种:
-
watchpoint set variable 变量
,例如:watchpoint set variable self->_age
,当age变量改变时,断点就会触发,以便找到修改age内存的代码 -
watchpoint set expression 地址
,例如:watchpoint set expression &self->_age
-
watchpoint list
,列出所有的内存断点 -
watchpoint delete 断点编号
,删除此内存断点 -
watchpoint command add 断点编号
,给此内存断点,增加预设命令
-
-
-
image lookup
,寻找模块信息,如果你想找某个类型、某个方法、某个地址在模块中的什么位置,就可以用这个命令,主要参数如下:
-
image lookup -t 类型
,查找某个类型的信息,例如image lookup -t NSInterger
-
image lookup -a 地址
,看看某个内存地址在模块中的位置 -
image lookup -n 符号或者函数名
,查找某个符号或者函数的位置
-
-
image list
,列出所加载的模块信息
-
- 一些小技巧:敲Enter会自动执行上次的命令、绝大部分命令可以使用缩写
网友评论