前言
在进入逆向正式实战之前 , 动态调试和静态分析都是我们必不可少的能力 .
LLDB 是不管在正向开发还是逆向开发中 , 都是帮助我们调试必不可少的手段 . 而在逆向开发中不能像正向开发一样页面断点 , 可视化数据展示 , 源代码调试等方式的情况下 , LLDB 的作用就会尤其重要 .
-
考虑到并不是所有同学都会亲自建一个工程来实战演练一下
LLDB, 本篇文章我会结合实战来介绍LLDB常用指令的实际效果 , 以此来更好地理解和记忆LLDB( 毕竟面试还是经常会问到的 ) , 熟悉的同学可以自行跳过 . -
另外笔者写指令时会尽量写全 , 全称指令有助于理解 , 并且以后再换为简写也很简单 , 反过来就不行了 . 因此大佬勿喷 .
LLDB
概述
默认内置于
Xcode中的动态调试工具。标准的LLDB提供了一组广泛的命令,旨在与老版本的GDB命令兼容。 除了使用标准配置外,还可以很容易地自定义LLDB以满足实际需要。
查看指令和指令帮助
- 任意工程进入断点模式 或者暂停后 , 直接输入
help - 再输入具体指令加
help可以查看某一条指令的说明 :breakpoint help.
代码准备
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"touch screen");
testFunc();
}
void testFunc(){
printf("testFunc");
}
- (IBAction)btnAction1:(id)sender {
}
- (IBAction)btnAction2:(id)sender {
}
- (IBAction)btnAction3:(id)sender {
}
@end
常用断点指令
一、过掉这个断点
- 指令 :
continue, 简写为c. - 单步走为
step, 简写为s, 遇到嵌套子函数会进去. - 单步运行
next, 简写为n, 遇到嵌套子函数会当做整体一步执行 .
二、查看当前断点列表
-
指令 :
breakpoint list, 简写为b l( 我们在
Xcode可视化加的断点也可以被查看到 , 因为本身都在寄存器中 ) .
三、方法名加断点
-
指令 :
breakpoint set --name testFunc -
结果 :
-
说明 :
该指令直接通过方法名称下断点 .
--name可简写为-n -
查看断点 :
-
结果验证 : continue 过掉断点 , 点击屏幕 , 来到我们下的断点 .
四、一次通过多个方法名加断点
-
指令 :
breakpoint set --name "-[ViewController btnAction1:]" --name "-[ViewController btnAction2:]" --name "-[ViewController btnAction3:]" -
结果 :
-
说明 :
该指令直接通过一次添加多个方法名称下断点 .
--name可简写为-n, 添加结果是一组断点 , 但是为多个位置 . -
查看断点 :
五、禁用 / 启用断点
-
指令 :
breakpoint disable 5/breakpoint enable 5 -
结果 :
-
说明 :
通过
breakpoint list先查看断点ID, 然后禁用或者启用某一个或者某一组断点. -
查看断点 :
六、通过 sel 加断点
-
指令 :
breakpoint set --selector touchesBegan:withEvent: -
结果 :
-
说明 :
-
通过
selector添加断点 , 不局限于某个类 , 当再添加--file指令时 , 会具体到该类中寻找这个sel下断点 . -
例:
breakpoint set --file ViewController.m --selector touchesBegan:withEvent:
-
-
查看断点 :
七、不完整 sel 加断点
-
指令 :
breakpoint set --func-regex btnActi -
结果 :
-
说明 :
- 通过 并没有写完整的
selector添加断点 , 但是并非是模糊搜索的模式 , 用的正则来处理的 , 因此输入的名称不能错 , 只能是未写完 . - 也可结合
--file指令来针对某个类处理
- 通过 并没有写完整的
-
查看断点 :
八、删除断点
-
指令 :
breakpoint delete 10 -
结果 :
-
说明 :
不传断点
ID则为删除所有断点 , 会提示确认操作. -
查看断点 :
提示
LLDB指令对断点做的操作与Xcode可视化页面中对断点所做的添加 / 删除 / 禁用 / 启用 操作都是同步的.当删除断点时 , 不能删除某一组中的一个 , 删除会变成
disable, 只能删除组
breakpoint set可直接简写为b
breakpoint list可简写为break l,breakpoint disable可简写为break dis等等以此类推 , 可以简写到只要系统可以区分你到底敲的是哪一条命令都可以
其他常用命令
执行代码 expression / p
-
指令 :
expression self.view.subviews, 简写p -
结果 :
-
说明 :
p为expression的简写 , 并非很多同学理解的print哦 ,po是expression -O(--object-descriptionNSObject的description方法 ) 的简写. -
使用举例 :
- 执行代码 :
p self.view.backgroundColor = [UIColor orangeColor]; - 过掉断点 :
c - 页面效果 :
- 执行代码 :
同样的 , p 可以写多句代码 , 使用 ; 即可 . 该方法常用与逆向中 Cycript 检查是否是我们要找的 view .
函数调用栈
- 指令 :
thread backtrace, 简写bt, - 结果 :
- 说明 :
- bt查看函数调用流程 , 可加上要查看的数量 , 例如
bt 3 - 通过
up / down方法可以跳转到前一个/后一个方法中 - 通过
frame select 5也可以直接通过编号跳转对应方法 - 通过
frame variable可以查看方法参数 - 通过
thread return可以线程回滚 , 而且可以修改参数 , 但是执行完该函数会直接return.
- bt查看函数调用流程 , 可加上要查看的数量 , 例如
还有一些其他的指令这里就不一一列举了 , 通过 help 指令可以查看自行练习.
image 相关
image list
- 指令 :
image list - 说明 : 查看当前进程加载了哪些库 .
- 结果 :
image lookup
- 指令 :
image lookup -t LBPerson - 说明 : 查看某个类信息 .
- 结果 :
逆向常用命令
刚刚我们看了很多断点调试指令 , 那么逆向过程中呢 ? 我们并没有源码 , 结果就是 : 很多通过方法名称下断点等方式都不能用 😶 .
-
为什么 ?
- 我们就一个 Mach-O , 没有符号 ,
release模式的情况下 , 符号是会被编译器自动去掉的 .
- 我们就一个 Mach-O , 没有符号 ,
-
怎么办 ?
- 内存断点
代码准备
#import "ViewController.h"
@interface LBPerson : NSObject
@property(nonatomic , assign) int age;
@property(nonatomic , copy) NSString * name;
@end
@implementation LBPerson
@end
@interface ViewController ()
@property(nonatomic, strong) LBPerson * person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[LBPerson alloc] init];
self.person.age = 20;
self.person.name = @"lb";
NSLog(@"haha");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person.name = @"test";
}
在 NSLog 处加个断点 , 运行 .
添加内存断点
来到断点后输入指令 : watchpoint set variable self->_person->_name
过掉断点 . 点击屏幕 , 修改了 person 的 name , 监控到内存断点 , 并打印如下 :
Watchpoint 1 hit:
old value: 0x0000000107f72078
new value: 0x0000000107f720b8
查看内容 .
set 方法时调用的 .
通过内存地址下断点
逆向中我们往往只有内存地址 , 甚至不一定拿得到变量或者属性 . 那么就需要通过内存地址下断点 .
同样刚刚的代码 , 重新运行来到 NSLog 断点.
p查看name内存地址 ,p &self->_person->_namewatchpoint set expression 0x0000600000db2f70- 过掉断点 , 点击屏幕
- 检测到改变
内存断点的删除 禁用 查看列表
同上述 breakpoint , 不多赘述.
LLDB高级用法
特定断点添加指令
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"touch screen");
[self test];
}
- (void)test{
NSLog(@"Add Command test");
}
1. 给 testFunc 添加断点
指令 : b testFunc
2. 给这个断点添加指令
指令 : breakpoint command add 1
3. 输入要添加的指令
输入 :
>po self
>p self.view.backgroundColor = [UIColor orangeColor];
>DONE
过掉断点 , 点击屏幕.
结果 :
再过掉断点 , 查看页面 .
这种方法适用场景较多 , 例如某个断点一断住就查看形参 等等 , 自由发挥 .
同样的 查看断点指令列表
breakpoint command list 1
删除断点指令
breakpoint command list 1
stop-hook
-
指令 :
target stop-hook add --one "frame variable" -
说明 :
--one代表添加一条指令 , 可简写为-o- 可视化页面中
Pause paogram execution以及Debug view不属于stop范畴.
-
结果: 在
TouchBegan添加一个断点 , 点击屏幕 , 打印如下 : -
相较于上一个 特定断点添加指令 , 显然是更通用 .
那么同样 , 查看断点指令列表 :
target stop-hook list
target stop-hook delete , 使用 undisplay 1 , 也是一样的 .
禁用 / 启用 同理 .
自动启用加载指令
以上我们说的这些 , 建立在工程运行起来之后 , 进入 lldb 模式 , 添加指令等操作的前提下 . 那么我们思考一个问题 , 每次运行工程都要重新添加 , 可否自动处理呢 ?
答案是肯定的 .
打开 终端 / iTerm2 . 来到家目录下 , ls -a 查看文件.
- 如果你跟我一样有这个
.lldbinit文件 , 直接vim添加target stop-hook add -o "frame variable". - 如果你没有这个文件 . 没关系 直接 vim 创建 然后写上
target stop-hook add -o "frame variable". - 保存并退出 .
来到工程中 , 重新 run 一下 , 点击屏幕方法添加一个断点 , 点击屏幕 :
其原因是
lldb在启动时会加载这个文件 , 对每个工程都有效 .
最后
最后简单说一下逆向过程中如何下一个内存断点
- 使用
MachOView/Hopper, 找到方法地址 , 减去PageZero的虚拟内存 (也就是最前面的那个1, / 64 位下是 4 个 G, ) 得到方法基于Mach-O首地址的真实偏移地址. lldb查看Mach-O地址 .- 得到的地址和方法偏移地址相加 , 得到方法的真实地址 .
- 根据地址添加断点 , 即可添加成功.
这个是由于 ASLR 的原因 . 说白了就是地址空间配置随机加载 , 因此我们需要在加载完后再获取 Mach-O 的地址即可.
物理地址 = ALSR + 虚拟地址