前言
在进入逆向正式实战之前 , 动态调试和静态分析都是我们必不可少的能力 .
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-description
NSObject
的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->_name
watchpoint 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 + 虚拟地址