平常我们都是通过Xcode
跑项目,加断点,动态调试,但是别人app
,我们是没有源码的。
这一篇就来实操动态调试别人的app,通过lldb+debugserver 来完成动态调试。
debugserver
-
Xcode
识别设备后,会将debugserver
安装到手机里,所以我们从手机里把debugserver
拿出来。
截屏2020-12-22 下午9.18.36.png - 原来的
debugserver
权限是不够的,我们需要增加2个权限 -
ldid -e debugserver > debugserver.entitlements
导出默认的权限 - 打开权限文件,增加2个权限
task_for_pid-allow yes
get-task-allow yes
截屏2020-12-22 下午9.23.13.png
-
ldid -Sdebugserver.entitlements debugserver
重新签名一下,或者codesign
签名也行 -
签名好的权限的debugserver放到手机的/usr/bin 路径
截屏2020-12-22 下午9.26.57.png -
debugserver *:端口号 -a 进程
debugserver附加到某个app进程 - 我这边通过
usb
将手机10086端口
和mac的10086端口
给映射一下,
image.png -
usb.sh
内容如下
image.png -
登录上手机之后,将debugserver附加到某个app
image.png - 然后新开一个窗口,
mac
上进入lldb
。
image.png - 通过
process connect connect://ip地址:端口
连接某个进程
image.png - 上图后面敲一下
c
,是让app继续运行,连上之后,默认进程是断住的,现在即已经对app进行调试了。
动态调试还需要掌握的知识
lldb指令
breakpoint set -n 函数名 // 设置断点
breakpoint set -n "-[xxx xxx]",断某个地方的某个方法
breakpoint set -r xxx 函数名里包含xxx都会打断点
breakpoint set -s 库名称(xxx.dylib) -n xxxx 断动态库里某个函数
breakpoint list 打的断点list
breakpoint disable 断点编号: 禁用断点
breakpoint enable 断点编号: 启用断点
breakpoint delete 断点编号: 删除断点
breakpoint command add 断点编号: 给断点预先设置需要执行的命令,到触发断点时。就会按顺序执行
breakpoint command list 断点编号: 查看某个断点设置的命令
breakpoint command delete 断点编号: 删除某个断点设置的命令
敲DONE 结束command指令设置
help breakpoint //提示帮助
help breakpoint set // 进一步提示帮助
expression 表达式
p 表达式
bt 打印堆栈
thread return 不执行断点后的代码
frame variable 名称可不加 查看变量值
thread continue , continue , c 程序继续运行
thread step-over , next ,n 单步运行,把子函数当作一步
thread step-in ,step , s 单步运行,有子函数进入子函数
thread step-out ,finish 执行完当前函数所有代码,回到函数调用的地方
si , ni和s ,n 类似
s,n是源码级
si,ni是汇编指令级
/// 内存断点
watchpoint set variable 变量(self->_age)
watchpoint set expression 地址
watchpoint list
disable,enable等 同breakpoint 指令
##image lookup (image:模块(动态库等等))
image lookup -t 类型 // 查看某个类型的信息
image lookup -a 地址 //根据内存地址查找在模块中的位置
image lookup -n 符号或函数名 //查找某个符号或函数的位置
image list 列出所加载的模块信息
image list -o -f
正式的app通过方法打断点不行,需要使用地址打断点
detach 退出debugserver
Mach-O相关
size -l -m -x xxxx 查看mach-o 内存分布
ASLR 让mach-o载入内存时内存开始位置 发生变化,ASLR 随机产生偏移
image list -o -f 打印模块 ,查看真实内存开始地址
breakpoint set -a hopper上地址+偏移
_PAGEZERO
arm64 0x100000000
非arm64 0x4000
代码段开始位置 = ASLR offset + _PAGEZERO
函数的内存地址(VM Address) = ASLR Offset + _PAGEZERO Size + File Offset
hopper,ida中的地址都是未使用ASLR的 VM Address
上面只是列出了一些常用的,实操过程会用到,可以自己去网上学习,包括一些简单的汇编知识,寄存器也需要了解一点。
调试实践
比如上一篇我们把pp虾 我的界面头部的banner 去掉了,是把cellHeightWithBanner:
方法hook了,return 0
。那么我们动态调试来看看这个参数是什么东西。
- 将可执行文件拖入hopper中,搜索
cellHeightWithBanner
,找到它的地址
截屏2020-12-22 下午10.01.27.png -
image list -o -f | grep Super
,找到地址偏移。
image.png -
breakpoint set -a hopper里的方法地址+地址偏移
, 即在指定方法设置了断点
image.png -
这时候来到我的界面,发现app进断点了。
image.png -
register read
查看一下寄存器的信息。
image.png - 这里需要了解一些这方面的知识。
- 方法的参数 放在x0-x7, 根据调用方法实质是
objc_msgSend(xx,@selector,x)
,那么x2或其后才是参数,我们po
一下看看
image.png - 可以看出消息的接收者是
BDSUserHomeBannerCell
, 参数是BDSBannerEntity
。 - 这样我们就可以去
class-dump
导出的头文件里具体看看BDSBannerEntity
了
image.png - 那么我们换一种方式,
hook
这个Model
的initWithModel
方法,也可以去除banner
了
image.png
image.png
当然这里只是简单举个例子,来实践动态调试。学会动态调试,也会方便很多了。
具体怎么调试,还是要看自己了,多了解一些lldb指令,也可以通过断点调试,查到的地址,去hopper里看是干了什么,调用了什么方法。
-
debugserver
从0启动一个app(可以一步步看app从0开始做了什么)
首先知道app可执行文件位置,可以通过MJ的工具,也可以`ps -A
`debugserver -x auto *:10086 app可执行文件位置(app包中)
然后mac `lldb` 连接debugserver ,调试
网友评论