一、前言
动态调试就是在我们的程序运行之时,通过下断点、打印等一系列方式查看参数、返回值、函数调用流程等等。不仅是在iOS开放中需要动态调试,在任何语言的开发过程中都需要用到动态调试。
学会动态调试之后,我们就可以分析某个程序的整体调用流程了,例如:分析微信抢红包的时候,就可以知道微信调用了哪些方法去抢红包,以便我们hook。
一、Xcode动态调试的原理
1、GCC、LLVM、GDB、LLDB
我们在开发iOS程序的时候常常会用到调试跟踪,如何正确的使用调试器来debug十分重要。xcode里有内置的Debugger,老版使用的是GDB,xcode自4.3之后默认使用的就是LLDB了。
GDB
:UNIX及UNIX-like下的调试工具。
LLDB
:LLDB是个开源的内置于XCode的具有REPL(read-eval-print-loop)特征的Debugger,其可以安装C++或者Python插件。
GCC
:(GNU Compiler Collection,GNU编译器套装),是一套由 GNU 开发的编程语言编译器。
LLVM
:(low level virtual machine)是一个开源编译器框架,这个库提供了与编译器相关的支持,能够进行程序语言的编译期优化、链接优化、在线编译优化、代码生成。简而言之,可以作为多种语言编译器的后台来使用。
Xcode的编译器
发展历程:GCC → LLVM
Xcode的调试器
发展历程:GDB → LLDB
2、debugserver
debugserver、lldb是协同工作的,debugserver依附在APP上,时刻监听APP的运行状态,并有控制APP执行的能力;lldb是在APP外部的,可以和debugserver建立连接,通过debugserver获取APP运行状态,并且能通知debugserver对APP做一些事情。在真机调试的时候,Xcode将debugserver加入到APP中,通过lldb来调试APP,那么同样也可以在iterm上对越狱手机上的任意APP进行调试。
debugserver一开始存放在Mac的Xcode里面,当Xcode识别到手机设备时,Xcode会自动将debugserver安装到iPhone上。Xcode调试有个局限性,就是一般情况下,只能调试通过Xcode安装的APP。
debugserver的Mac存放路径:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/De viceSupport/9.1/DeveloperDiskImage.dmg/usr/bin/debugserver
debugserver的iPhone存放路径:/Developer/usr/bin/debugserver
二、动态调试任意App
1、debugserver的权限问题
默认情况下,/Developer/usr/bin/debugserver
缺少一定的权限,只能调试通过Xcode安装的APP,无法调试调试APP,比如来自AppStore的APP。
如果希望调试其他APP,需要对debugserver重新签名,签上2个调试相关的权限。
- get-task-allow
- task_for_pid-allow
2、给debugserver签上权限
iPhone上的
/Developer
目录是只读的,无法直接对/Developer/usr/bin/debugserver
文件签名,需要先把debugserver复制到Mac.通过ldid命令导出文件以前的签名权限
$ ldid -e debugserver > debugserver.entitlements
- 给
debugserver.entitlements
文件加上get-task-allow
和task_for_pid-allow
权限。
ps:如果Xcode12 打不开debugserver.entitlements文件的话,可以使用Xcode11试试
- 通过ldid命令重新签名
ldid -Sdebugserver.entitlements debugserver
- 由于
/Developer/usr/bin/
目录是只读的,所以我们将重新签名过的debugserver
放在/usr/bin/
下,然后对debugserver
增加运行权限:chmod +x /usr/bin/debugserver
,就可以在终端使用debugserver
了
- 关于权限的签名,也可以使用codesign
# 查看权限信息
$ codesign -d --entitlements - debugserver
# 签名权限
$ codesign -f -s - --entitlements debugserver.entitlements debugserver
# 或着简写为
$ codesign -fs- --entitlements debugserver.entitlements debugserver
3、让debugserver附加到某个APP进程
远程登录到手机后 ,让debugserver附加到某个App进程,
命令如下:debugserver *:端口号 -a 进程
-
*:端口号
:表示使用iPhone上的某个端口启动debugserver服务(注意:不能使用保留端口号) -
-a 进程
:输入APP的进程信息(指定进程id或者进程名称)
例如让debugserver附加到微信的进程:debugserver 127.0.0.1:10011 -a WeChat
4、在Mac上启动LLDB,远程连接iPhone上的debugserver
- 启动LLDB:
$ lldb
(lldb)
- 连接debugserver服务
(lldb) process connect connect://手机IP地址:debugserver服务端口号
- 使用LLDB的
c
命令让程序先继续运行
(lldb) c
- 接下来就可以使用LLDB命令调试APP了
5、使用debugserver启动App
$ debugserver -x auto *:端口号 APP的可执行文件路径
6、使用USB方式动态调试任意App
上面的步骤不好理解,没有关系,让我们再重新详细来一遍吧。
1)使用python建立本地映射
编辑脚本文件uConnect.sh
, 让10010端口
与22端口
映射、10011端口
与10011端口
映射。本地映射逻辑可参考: Mac远程登录iPhone
python ~/Documents/usbmuxd/python-client/tcprelay.py -t 22:10010 10011:10011
编辑脚本文件uLogin.sh
,让Mac连接上手机
ssh root@localhost -p 10010
然后mac各自执行脚本
2)iPhone启动debugserver服务
以抖音为例,手机上开启app,然后通过指令搜索抖音进程。之后通过进程id
或者进程名
启动debugserver服务
ZhangCong-iPhone5s:~ root# ps -A | grep Aweme
10602 ?? 0:42.86 /var/mobile/Containers/Bundle/Application/BFA3B9CA-42BA-41B2-9C29-259EBF3EF97C/Aweme.app/Aweme
10610 ttys000 0:00.01 grep Aweme
ZhangCong-iPhone5s:~ root# debugserver 127.0.0.1:10011 -a 10602
debugserver-@(#)PROGRAM:debugserver PROJECT:debugserver-340.3.51.1
for arm64.
Attaching to process 10602...
Listening to port 10011 for a connection from 127.0.0.1...
3)LLDB连接debugserver服务
上步启动debugserver服务后,可以看到debugserver处于Listening
监听状态,等待被连接。这时,我们需要在Mac上开启LLDB
,去连接debugserver。
在Mac上执行以下命令,让LLDB与本地的10011传输数据,由于映射已经建立,就相当于给iPhone的debugserver服务发送数据。连接成功后,需要执行命令c
,好让程序继续运行。
$ LLDB
(lldb) process connect connect://localhost:10011
三、LLDB常用命令
LLDB常用命令比较多,在这篇文章有详细介绍,可以转阅这里: iOS开发之LLDB常用命令
四、ASLR介绍
以前我们用Xcode的LLDB指令打断点时,可以用方法名打断点,例如breakpoint set -n "[UIViewController touchBegin:]"
,但是我们想动态调试别人的App,就不能用方法名称了,需要用到方法的内存地址才能打,例如breakpoint set --address 0x123123123
。而想知道方法的内存地址就需要学习ASLR。
1、什么是ASLR
ASLR(Address Space Layout Randomization),地址空间布局随机化,是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。iOS4.3开始引入了ASLR技术
2、Mach-O文件的内部结构
1)Mach-O基本结构
2)Data基本结构
3)字段介绍
3、未使用ASLR时,Mach-O文件是如何载入内存的?
- _PAGEZERO在Mach-O文件中的大小为0x0,加载进内存后大小为0x100000000(arm64架构下)
- Header和Load commands都是描述信息,存储在_TEXT中,所以内存空间从_PAGEZERO开始分配
- 码编译完成后,_TEXT中方法的位置和_DATA中变量的位置就是固定的,这样很容易进行动态调试,为了增加安全性,从iOS4.3开始就引入了ASLR技术
4、使用ASLR时,Mach-O文件是如何载入内存的?
- ASLR是Address Space Layout Randomization(地址空间布局随机化)的缩写
- 引入ASLR技术后,当Mach-O文件加载进内存时,会随机生成一个内存地址,然后从该地址开始进行内存空间的分配
- 虽然方法和变量的位置是固定的,但是内存地址就变得随机了,这样就会增加动态调试的难度,从而提高了安全性
五、如何给别人的App打断点,进行调试
1、无ASLR虚拟内存地址
用Hopper等工具静态分析出某个函数在虚拟内存中的函数地址,这里的函数地址是静待分析出来的,也就是未经过ASLR偏移的地址,并不能直接用于打断点,如下图所示,-[AWEAwemePlayVideoViewController play]
函数的未ASLR偏移的函数地址是:0x1006de960
。如下图所示:
2、ASLR随机内存地址(Mach-O文件加载进内存的起始地址)
使用LLDB指令image list -o -f | grep Mach-O文件名称
,获取ASLR偏移量,如下图所示,本次载入内存的偏移量是0x7c000
。
(lldb) image list -o -f | grep Aweme
[ 0] 0x000000000007c000 /var/mobile/Containers/Bundle/Application/BFA3B9CA-42BA-41B2-9C29-259EBF3EF97C/Aweme.app/Aweme(0x000000010007c000)
3、真实内存地址(调试其他APP时,需要用真实内存地址来设置断点,用方法名是无效的)
使用LLDB指令breakpoint set -a 函数地址
,给某个函数打断点,这里的函数地址指的是虚拟内存中的真实函数地址,也就是说这里的函数地址,是ASLR偏移量+静待分析的函数地址
,也就是0x1006de960+0x7c000
,完整的LLDB指令就是 breakpoint set -a 0x1006de960+0x7c000
,如下图所示:
4、调试程序
打完断点之后,当App触发此断点时,就会卡住,以便我们输入LLDB调试,例如,我这里对微信的发送消息的方法[AWEAwemePlayVideoViewController play]
打了断点,之后每次发消息时都会卡主,以便我们继续输入LLDB指令调试。如果不想要断点了,可以用breakpoint delete
断点编号删除断点;可以通过breakpoint list
命令,列出所有的断点编号。
- po $x0:打印方法调用者
- x/s $x1:打印方法名
- po $x2:打印参数(以此类推,x3、x4也可能是参数)
- 如果是非arm64,寄存器就是r0、r1、r2
更多指令请转阅: iOS开发之LLDB常用命令
参考链接:
https://www.jianshu.com/p/fa2f080fb4bb
https://www.jianshu.com/p/94f67bb84c93
https://www.jianshu.com/p/220b9be14be7