iOS逆向--LLDB调试

2,449 阅读21分钟

一、断点:breakPoint

项目开发者最常用的就是断点调试,但是我们一般都是在代码的某一行点一下下个断点,我们来看看其他设置断点的方式。

设置断点:breakpoint set -n

给函数设置断点

首先我们定义一个函数

void test1(){
    NSLog(@"3");
}

然后在控制器中写上下面代码

运行,然后我们可以通过breakpoint set -n test1来对函数test1进行下断点,-n是name的意思

(lldb) breakpoint set -n test1
Breakpoint 2: where = 001--LLDB调试`test1 + 16 at ViewController.m:30:5, address = 0x0000000104a31cc8
(lldb) 

上面输出的语句意思是:

第二个断点是在名为LLDB的MachO文件的的viewController文件的第三十行的test1函数处

上面Breakpoint 2代表设置的第二个断点,假如在项目运行之前设置五个断点,那么,当你通过breakpoint设置断点时候,就会显示是第六个

给方法设置断点

上面是给函数设置断点,对于OC方法该怎么设置断点呢?

首先我们在页面上创建三个按钮,并对应实现触发方法

- (IBAction)save:(id)sender {
    NSLog(@"保存!");
}
- (IBAction)pauseGame:(id)sender {
    NSLog(@"暂停");
}
- (IBAction)continueGame:(id)sender {
    NSLog(@"继续");
}

运行项目,然后点击下面按钮,这个按钮是暂停项目的意思,暂停后我们就可以通过命令设置断点

给OC方法设置断点的命令就是

(lldb) breakpoint set -n "-[ViewController save:]" -n "-[ViewController pauseGame:]" -n "-[ViewController continueGame:]"
Breakpoint 1: 3 locations.

查看断点列表:breakpoint list

上面显示设置一个断点,但是一个断点有是三个位置,我们通过breakpoint list看一下断点列表

(lldb) breakpoint list
Current breakpoints:
1: names = {'-[ViewController save:]', '-[ViewController pauseGame:]', '-[ViewController continueGame:]'}, locations = 3, resolved = 3, hit count = 0
  1.1: where = 001--LLDB调试`-[ViewController save:] + 43 at ViewController.m:33:5, address = 0x000000010b4e0ceb, resolved, hit count = 0 
  1.2: where = 001--LLDB调试`-[ViewController pauseGame:] + 43 at ViewController.m:36:5, address = 0x000000010b4e0d3b, resolved, hit count = 0 
  1.3: where = 001--LLDB调试`-[ViewController continueGame:] + 43 at ViewController.m:39:5, address = 0x000000010b4e0d8b, resolved, hit count = 0 

(lldb) 

我们发现三个地方断点列的很清楚,点击按钮测试一下确实都有断点

断点的禁用、启用、删除

当断点断住时候,命令c是continue的意思也就是跳过断点的意思

(lldb) c
Process 21081 resuming
2020-11-11 20:45:41.211892+0800 001--LLDB调试[21081:2419390] 保存!

禁用: breakpoint disable

breakpoint disable + 断点编号,意思就是禁用那个断点的意思

例如我们禁用一下第一个断点,然后再看一下断点列表:

(lldb) breakpoint disable 1
1 breakpoints disabled.
(lldb) breakpoint list
Current breakpoints:
1: names = {'-[ViewController save:]', '-[ViewController pauseGame:]', '-[ViewController continueGame:]'}, locations = 3 Options: disabled 
  1.1: where = 001--LLDB调试`-[ViewController save:] + 43 at ViewController.m:33:5, address = 0x000000010b4e0ceb, unresolved, hit count = 2 
  1.2: where = 001--LLDB调试`-[ViewController pauseGame:] + 43 at ViewController.m:36:5, address = 0x000000010b4e0d3b, unresolved, hit count = 1 
  1.3: where = 001--LLDB调试`-[ViewController continueGame:] + 43 at ViewController.m:39:5, address = 0x000000010b4e0d8b, unresolved, hit count = 0 

(lldb) 

发现第一个断点后面有disable字样,也就是禁用了,我们再点击按钮,就不会用断点了

启用:breakpoint enable

breakpoint enable + 断点编号就是启用断点的意思

(lldb) breakpoint enable 1
1 breakpoints enabled.
(lldb) breakpoint list
Current breakpoints:
1: names = {'-[ViewController save:]', '-[ViewController pauseGame:]', '-[ViewController continueGame:]'}, locations = 3, resolved = 3, hit count = 3
  1.1: where = 001--LLDB调试`-[ViewController save:] + 43 at ViewController.m:33:5, address = 0x000000010b4e0ceb, resolved, hit count = 2 
  1.2: where = 001--LLDB调试`-[ViewController pauseGame:] + 43 at ViewController.m:36:5, address = 0x000000010b4e0d3b, resolved, hit count = 1 
  1.3: where = 001--LLDB调试`-[ViewController continueGame:] + 43 at ViewController.m:39:5, address = 0x000000010b4e0d8b, resolved, hit count = 0 

我们发现启用断点后,断点后面的disable消失了,再点击按钮,就会有断点断住了

我们再禁用一下断点中的某一个位置

(lldb) breakpoint disable 1.1
1 breakpoints disabled.
(lldb) breakpoint list
Current breakpoints:
1: names = {'-[ViewController save:]', '-[ViewController pauseGame:]', '-[ViewController continueGame:]'}, locations = 3, resolved = 2, hit count = 3
  1.1: where = 001--LLDB调试`-[ViewController save:] + 43 at ViewController.m:33:5, address = 0x000000010b4e0ceb, unresolved, hit count = 2  Options: disabled 
  1.2: where = 001--LLDB调试`-[ViewController pauseGame:] + 43 at ViewController.m:36:5, address = 0x000000010b4e0d3b, resolved, hit count = 1 
  1.3: where = 001--LLDB调试`-[ViewController continueGame:] + 43 at ViewController.m:39:5, address = 0x000000010b4e0d8b, resolved, hit count = 0 

我们发现只有1.1位置后面有disable字样,并且点击按钮只有第一个按钮不会断住,其他都会断住

删除:breakpoint delete

我们给项目多加几个断点,

(lldb) breakpoint list
Current breakpoints:
1: names = {'-[ViewController save:]', '-[ViewController pauseGame:]', '-[ViewController continueGame:]'}, locations = 3, resolved = 2, hit count = 3
  1.1: where = 001--LLDB调试`-[ViewController save:] + 43 at ViewController.m:33:5, address = 0x000000010b4e0ceb, resolved, hit count = 2 
  1.2: where = 001--LLDB调试`-[ViewController pauseGame:] + 43 at ViewController.m:36:5, address = 0x000000010b4e0d3b, resolved, hit count = 1 
  1.3: where = 001--LLDB调试`-[ViewController continueGame:] + 43 at ViewController.m:39:5, address = 0x000000010b4e0d8b, resolved, hit count = 0 

2: file = '/Volumes/赵静静的硬盘/iOS视频/iOS攻防班2/20191023-应用安全-第九讲-LLDB/009--LLDB/代码/001--LLDB调试/001--LLDB调试/ViewController.m', line = 43, exact_match = 0, locations = 1, resolved = 1, hit count = 0

  2.1: where = 001--LLDB调试`-[ViewController eatWithObject:] + 46 at ViewController.m:43:26, address = 0x000000010b4e0dde, resolved, hit count = 0 

然后用命令删除第二个断点:

(lldb) breakpoint delete 2
1 breakpoints deleted; 0 breakpoint locations disabled.
(lldb) breakpoint list
Current breakpoints:
1: names = {'-[ViewController save:]', '-[ViewController pauseGame:]', '-[ViewController continueGame:]'}, locations = 3, resolved = 2, hit count = 3
  1.1: where = 001--LLDB调试`-[ViewController save:] + 43 at ViewController.m:33:5, address = 0x000000010b4e0ceb, resolved, hit count = 2  
  1.2: where = 001--LLDB调试`-[ViewController pauseGame:] + 43 at ViewController.m:36:5, address = 0x000000010b4e0d3b, resolved, hit count = 1 
  1.3: where = 001--LLDB调试`-[ViewController continueGame:] + 43 at ViewController.m:39:5, address = 0x000000010b4e0d8b, resolved, hit count = 0 

(lldb) 

我们发现第二个断点删除了,假如我们要删除第1.1个断点呢?

(lldb) breakpoint delete 1.1
0 breakpoints deleted; 1 breakpoint locations disabled.
(lldb) breakpoint list
Current breakpoints:
1: names = {'-[ViewController save:]', '-[ViewController pauseGame:]', '-[ViewController continueGame:]'}, locations = 3, resolved = 2, hit count = 3
  1.1: where = 001--LLDB调试`-[ViewController save:] + 43 at ViewController.m:33:5, address = 0x000000010b4e0ceb, unresolved, hit count = 2  Options: disabled 
  1.2: where = 001--LLDB调试`-[ViewController pauseGame:] + 43 at ViewController.m:36:5, address = 0x000000010b4e0d3b, resolved, hit count = 1 
  1.3: where = 001--LLDB调试`-[ViewController continueGame:] + 43 at ViewController.m:39:5, address = 0x000000010b4e0d8b, resolved, hit count = 0 

(lldb) 

我们发现删除的时候,提示0个断点删除了,一个断点被禁用了,说明我们无法删除一个断点的其中一个位置,只能禁用,哪怕一个断点只有一个位置也不行

并且,当我们在项目运行期间添加的断点都会有编号,哪怕被删除了编号依然存在,后续新加的断点,编号只会依次增加,而不会覆盖老的断点

断点的其他指令:help breakpoint

(lldb) help breakpoint
     Commands for operating on breakpoints (see 'help b' for shorthand.)

Syntax: breakpoint <subcommand> [<command-options>]

The following subcommands are supported:

      clear   -- Delete or disable breakpoints matching the specified source
                 file and line.
      command -- Commands for adding, removing and listing LLDB commands
                 executed when a breakpoint is hit.
      delete  -- Delete the specified breakpoint(s).  If no breakpoints are
                 specified, delete them all.
      disable -- Disable the specified breakpoint(s) without deleting them.  If
                 none are specified, disable all breakpoints.
      enable  -- Enable the specified disabled breakpoint(s). If no breakpoints
                 are specified, enable all of them.
      list    -- List some or all breakpoints at configurable levels of detail.
      modify  -- Modify the options on a breakpoint or set of breakpoints in
                 the executable.  If no breakpoint is specified, acts on the
                 last created breakpoint.  With the exception of -e, -d and -i,
                 passing an empty argument clears the modification.
      name    -- Commands to manage name tags for breakpoints
      read    -- Read and set the breakpoints previously saved to a file with
                 "breakpoint write".  
      set     -- Sets a breakpoint or set of breakpoints in the executable.
      write   -- Write the breakpoints listed to a file that can be read in
                 with "breakpoint read".  If given no arguments, writes all
                 breakpoints.

For more help on any particular subcommand, type 'help <command> <subcommand>'.
(lldb) 

通过方法名下断点:breakpoint set --selector

(lldb) breakpoint set --selector touchesBegan:withEvent:
Breakpoint 7: 97 locations.
(lldb) 

我们发现总共有97个位置,这是因为很多UIKit系统类里面都有这个方法

我们也可以指定某个文件通过方法名进行设置断点

(lldb) breakpoint set --file ViewController.m --selector touchesBegan:withEvent:

Breakpoint 8: where = 001--LLDB调试`-[ViewController touchesBegan:withEvent:] + 77 at ViewController.m:90:5, address = 0x000000010b4e123d
(lldb) 

模糊设置断点:breakpoint set -r + 方法名

通过这个命令,我们可以模糊设置断点,也就是只要方法名里面包含字符的方法,都会设置断点

(lldb) breakpoint set -r Game:
Breakpoint 11: 2 locations.
(lldb) breakpoint list
Current breakpoints:
11: regex = 'Game:', locations = 2, resolved = 2, hit count = 0
  11.1: where = 001--LLDB调试`-[ViewController continueGame:] + 43 at ViewController.m:39:5, address = 0x000000010b4e0d8b, resolved, hit count = 0 
  11.2: where = 001--LLDB调试`-[ViewController pauseGame:] + 43 at ViewController.m:36:5, address = 0x000000010b4e0d3b, resolved, hit count = 0 

(lldb) 

我们发现断点的方法名中都有Game:

同样,我们可以指定文件进行模糊断点设置

(lldb) breakpoint set --file ViewController.m -r Game:
Breakpoint 12: 2 locations.
(lldb) 

注意的是:方法名要区分大小写

而且这些断点是有简写的:

例如简写设置断点:

(lldb) b -f ViewController.m -r Game
Breakpoint 17: 2 locations.
(lldb) breakpoint list
Current breakpoints:
17: regex = 'Game', locations = 2, resolved = 2, hit count = 0
  17.1: where = 001--LLDB调试`-[ViewController continueGame:] + 43 at ViewController.m:39:5, address = 0x000000010b4e0d8b, resolved, hit count = 0 
  17.2: where = 001--LLDB调试`-[ViewController pauseGame:] + 43 at ViewController.m:36:5, address = 0x000000010b4e0d3b, resolved, hit count = 0 

(lldb) 

简写查看断点:

(lldb) break li
Current breakpoints:
17: regex = 'Game', locations = 2, resolved = 2, hit count = 0
  17.1: where = 001--LLDB调试`-[ViewController continueGame:] + 43 at ViewController.m:39:5, address = 0x000000010b4e0d8b, resolved, hit count = 0 
  17.2: where = 001--LLDB调试`-[ViewController pauseGame:] + 43 at ViewController.m:36:5, address = 0x000000010b4e0d3b, resolved, hit count = 0 

(lldb) 

简写禁用:

(lldb) break dis 17.1
1 breakpoints disabled.
(lldb) 

简写启用:

(lldb) break en 17.1
1 breakpoints enabled.
(lldb) 

二、执行语句:P

执行代码指令:expression + 执行语句

(lldb) expression self.view.subviews
(__NSArrayM *) $0 = 0x0000600000d03630 @"3 elements"
(lldb) 

但是我们实际中一般用p指令执行:

(lldb) p self.view.subviews
(__NSArrayM *) $1 = 0x0000600000d03630 @"3 elements"
(lldb) 

我们看一下p指令到底是啥:

(lldb) help p
     Evaluate an expression on the current thread.  Displays any returned value
     with LLDB's default formatting.  Expects 'raw' input (see 'help
     raw-input'.)

Syntax: p <expr>

Command Options Usage:
  p <expr>


'p' is an abbreviation for 'expression --'
(lldb) 

我们发现p其实是expression的简写

而我们开发中一般想看某个变量值一般用po指令,我们再看一下po是啥:

(lldb) help po
     Evaluate an expression on the current thread.  Displays any returned value
     with formatting controlled by the type's author.  Expects 'raw' input (see
     'help raw-input'.)

Syntax: po <expr>

Command Options Usage:
  po <expr>


'po' is an abbreviation for 'expression -O  --'
(lldb) 

是expression -O 的简写,我们知道expression是执行指令,再具体看一下是啥:

我们发现-O是类似对象的description方法

p作为执行语句指令,我们来试验一下,断住程序,然后我们执行下面指令:

(lldb) p sender.backgroundColor=[UIColor redColor];
error: <user expression 0>:1:8: property 'backgroundColor' not found on object of type 'UIButton *'
sender.backgroundColor=[UIColor redColor];
       ^
(lldb) 

放开断点,我们发现按钮颜色变了

并且我们也可以来修改变量,例如我们断住下面方法:

然后通过p指令去修改name

(lldb) p name = @"234"
(NSTaggedPointerString *) $0 = 0xde7f7b1ce0722058 @"234"
2020-11-11 22:06:31.599465+0800 001--LLDB调试[23841:2488906] 保存!234

放开后,我们打印发现name变了,name如果是一个对象,那么我们也可以修改对象的属性,这个叫内存调试

例如我们在方法里面创建一个可变数组,然后断点断住:

我们通过指令给array添加一个元素,然后打印,发现array里多了一个元素

(lldb) p [array addObject:@"4"]
(lldb) po array
<__NSArrayM 0x600000f17c90>(
1,
2,
3,
4
)

2020-11-11 22:14:15.499845+0800 001--LLDB调试[24073:2494681] (
    1,
    2,
    3,
    4
)

假如数组里面是对象

(lldb) p self.models.lastObject
(Person *) $0 = 0x00006000029c4c20
(lldb) p $0.name=@"123"
error: <user expression 1>:1:4: property 'name' not found on object of type 'id _Nullable'
$0.name=@"123"
   ^
(lldb) 

我们发现设置不成功,这是因为我们需要对$0进行强转一下才行,例如

(lldb) p (Person *)self.models.lastObject
(Person *) $1 = 0x00006000029c4c20
(lldb) p $1.name = @"123";
(NSTaggedPointerString *) $2 = 0x8904a8a4b4d8c163 @"123"
(lldb) 

而且p可以执行多条语句,例如还是到上面断点

(lldb) p Person *p = [Person new];p.name = @"123";[self.models addObject:p];
(lldb) po self.models
<__NSArrayM 0x6000027de580>(
<Person: 0x6000029c4b40>,
<Person: 0x6000029c4c00>,
<Person: 0x6000029c4c20>,
<Person: 0x6000029c4c60>
)

(lldb) 

我们发现我们通过p语句创建了一个对象,然后将对象添加到数组中去

三、查看函数调用栈

在Demo中写上下面代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    NSString *str = @"123";
    [self test1:str];
}

-(void)test1:(NSString *)str{
    NSLog(@"%@",str);
    [self test2:str];
}
-(void)test2:(NSString *)str{
    NSLog(@" %s %@",__func__,str);
    [self test3:str];
}
-(void)test3:(NSString *)str{
    NSLog(@" %s %@",__func__,str);
    [self test4:str];
}
-(void)test4:(NSString *)str{
    NSLog(@" %s %@",__func__,str);
}

查看堆栈指令:bt

我们在test4方法里打上断点,运行,断住之后可以用指令bt来查看调用的堆栈信息:

我们发现调用的方法都打印出来了,这个地方有个注意事项,就是如果test2除了调用test3方法外,如果没有其他代码,打印堆栈信息时候可能不会打印出来

查看某一个堆栈指令:frame select 1

打印了堆栈信息我们可以通过frame select 1来查看某一个堆栈的详细信息:

(lldb) frame select 1
frame #1: 0x0000000100dab178 ZJJAllModule_Example`-[ZJJLLDBViewController test3:](self=<unavailable>, _cmd=<unavailable>, str=<unavailable>) at ZJJLLDBViewController.m:34:5 [opt]
   31  	}
   32  	-(void)test3:(NSString *)str{
   33  	    NSLog(@" %s %@",__func__,str);
-> 34  	    [self test4:str];
    	    ^
   35  	}
   36  	-(void)test4:(NSString *)str{
   37  	    NSLog(@" %s %@",__func__,str);
(lldb) 

这样我们可以看到第一个堆栈的详细信息

查看上一个堆栈指令:up

(lldb) up
frame #2: 0x0000000100dab118 ZJJAllModule_Example`-[ZJJLLDBViewController test2:](self=<unavailable>, _cmd=<unavailable>, str=<unavailable>) at ZJJLLDBViewController.m:30:5 [opt]
   27  	}
   28  	-(void)test2:(NSString *)str{
   29  	    NSLog(@" %s %@",__func__,str);
-> 30  	    [self test3:str];
    	    ^
   31  	}
   32  	-(void)test3:(NSString *)str{
   33  	    NSLog(@" %s %@",__func__,str);
(lldb) 

查看下一个堆栈指令:down

(lldb) down
frame #1: 0x0000000100dab178 ZJJAllModule_Example`-[ZJJLLDBViewController test3:](self=<unavailable>, _cmd=<unavailable>, str=<unavailable>) at ZJJLLDBViewController.m:34:5 [opt]
   31  	}
   32  	-(void)test3:(NSString *)str{
-> 34  	    [self test4:str];
    	    ^
   35  	}
   36  	-(void)test4:(NSString *)str{
   37  	    NSLog(@" %@",str);
(lldb) 

我们通过up指令进入到test1函数里面

查看方法参数:frame variable

例如我们在viewDidLoad里面加上一个断点,然后查看一下viewDidLoad方法里面的变量:

(lldb) frame variable
(ZJJLLDBViewController *) self = 0x00007fb3d7c28ae0
(SEL) _cmd = "test2:"
(__NSCFConstantString *) str = 0x000000010add84f0 @"123"

我们发现出现了三个参数:self、_cmd、str,也就是把参数和站内的变量都打印出来了,注意的是方法里不能出现__func__函数,不然会导致参数值打印不出来

我们把代码改成下面样子,去掉__func__

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    NSString *str = @"123";
    [self test1:str];
}

-(void)test1:(NSString *)str{
    NSLog(@"%@",str);
    [self test2:str];
}
-(void)test2:(NSString *)str{
    NSLog(@"%@",str);
    [self test3:str];
    NSLog(@"%@",str);
}
-(void)test3:(NSString *)str{
    NSLog(@"test3:前%@",str);
    [self test4:str];
    NSLog(@"test3:后%@",str);
}
-(void)test4:(NSString *)str{

    NSLog(@"%@",str);
}

在test4方法中打一个断点,然后通过up指令回到test3方法:

2020-11-12 14:28:22.000125+0800 ZJJAllModule_Example[12007:139341] 123
2020-11-12 14:28:22.000318+0800 ZJJAllModule_Example[12007:139341] 123
2020-11-12 14:28:22.000465+0800 ZJJAllModule_Example[12007:139341] test3:前123
ZJJAllModule_Example was compiled with optimization - stepping may behave oddly; variables may not be available.
(lldb) up
frame #1: 0x000000010a8135b2 ZJJAllModule_Example`-[ZJJLLDBViewController test3:](self=<unavailable>, _cmd=<unavailable>, str=<unavailable>) at ZJJLLDBViewController.m:35:5 [opt]
   32  	}
   33  	-(void)test3:(NSString *)str{
   34  	    NSLog(@"test3:前%@",str);
-> 35  	    [self test4:str];
    	    ^
   36  	    NSLog(@"test3:后%@",str);
   37  	}
   38  	-(void)test4:(NSString *)str{
(lldb) 

再通过p指令修改一下str值试一下,到后面的方法中str值是否能改变

(lldb) p str = @"nmc"
error: Couldn't materialize: couldn't get the value of variable str: variable not available
error: errored out in DoExecute, couldn't PrepareToExecuteJITExpression
(lldb) 

我们发现是不能修改的,这是因为执行过的代码不可变

在断点处添加returen:thread return

我们在断点地方不想让程序往下执行下去,但是程序不崩溃,通过thread return相当于给程序在断点地方添加一个return

其他指令

  • 第一个是相当于指令c
  • 第二个单步运行,将子函数当做整体一步执行 $n next,汇编之下使用 ni
  • 第三个单步运行,遇到子函数会进去 $s,汇编之下使用 si

四、内存断点

我们把代码改成下面样子:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
//    NSString *str = @"123";
//    [self test1:str];

    self.dataArray = [NSMutableArray array];

    ZJJPerson *p1 = [ZJJPerson new];
    p1.name = @"111";
    p1.age = 1;

    ZJJPerson *p2 = [ZJJPerson new];
    p2.name = @"222";
    p2.age = 2;

    ZJJPerson *p3 = [ZJJPerson new];
    p3.name = @"333";
    p3.age = 3;

    [self.dataArray addObject:p1];
    [self.dataArray addObject:p2];
    [self.dataArray addObject:p3];

}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    ZJJPerson *p1 = [self.dataArray firstObject];
    p1.name = @"ABC";
}

[super viewDidLoad]; 这行添加断点,重新运行,然后使用 n 或者 Xcode 工具让 LLDB 走到 p1 创建完毕。

p1 创建完毕后,p1 的对象已经存在堆区,指向 p1 对象的指针因为 [self.dataArray addObject:p1]; 方法,在 viewDidLoad 方法走完之后依旧被保存。

设置内存断点:watchpoint set variable p1->_name

现在需要给 p1的 name 属性下断点,我们可以使用 watchpoint set variable p1->_name 给 name 下内存断点。

(lldb) watchpoint set variable p1->_name
Watchpoint created: Watchpoint 1: addr = 0x6000010dc008 size = 8 state = enabled type = w
    declare @ '/Users/zhaojing/Desktop/openGL/赵静静的测试代码/Example/ZJJAllModule/LLDB/ZJJLLDBViewController.m:29'
    watchpoint spec = 'p1->_name'
    new value: 0x000000010b2fa510
(lldb) 

回车后 LLDB 清楚的告诉了我们:内存断点的地址、指针占用的大小(OC对象8字节)。

过掉断点,点击屏幕触发 touchesBegan 的 p1.name = @"ABC" 赋值方法,这样一个内存断点就触发了, po 上方打印的两个地址,我们就能看旧值和新值。然后输入 bt 查看调用堆栈,就能清楚的看到调用队栈。

我们也可以使用 p1->_name 内存的指针地址给 p1->_name 下内存断点。

  • 获取 p1->_name 的内存地址:&p1->_name
  • 下内存断点:使用watchpoint set expression 加上p1->_name 的内存地址
  • 点击屏幕触发赋值方法验证
(lldb) p &p1->_name
(NSString **) $0 = 0x000060000383a888
(lldb) watchpoint set expression 0x000060000383a888
Watchpoint created: Watchpoint 1: addr = 0x60000383a888 size = 8 state = enabled type = w
    new value: 4551816464
    
Watchpoint 1 hit:
old value: 4551816464
new value: 4551816560
(lldb) po 4551816464
111

(lldb) po 4551816560
ABC

(lldb) 

同符号断点一样,内存断点可以查看和删除

  • 查看内存断点:watchpoint list
  • 删除内存断点:watchpoint delete

(lldb) watchpoint list
Number of supported hardware watchpoints: 4
Current watchpoints:
Watchpoint 1: addr = 0x60000383a888 size = 8 state = enabled type = w
    old value: 4551816464
    new value: 4551816560
(lldb) watchpoint delete
About to delete all watchpoints, do you want to do that?: [Y/n] y
All watchpoints removed. (1 watchpoints)
(lldb) 

给一个断点添加多指令:breakpoint command add 1

我们给touchesBegan中添加一个断点,运行项目

再点击暂停键,给断点添加一些指,LLDB 上输入 breakpoint list ,然后给断点1添加多个指令 breakpoint command add 1, 然后在 > 后输入指令,每输入完成一个指令后回车进行下一个指令输入,想要结束,输入DONE即可。

(lldb) breakpoint list
Current breakpoints:
1: file = '/Users/zhaojing/Desktop/openGL/赵静静的测试代码/Example/ZJJAllModule/LLDB/ZJJLLDBViewController.m', line = 49, exact_match = 0, locations = 1, resolved = 1, hit count = 0

  1.1: where = ZJJAllModule_Example`-[ZJJLLDBViewController touchesBegan:withEvent:] + 11 at ZJJLLDBViewController.m:49:27, address = 0x00000001063a8334, resolved, hit count = 0 

(lldb) breakpoint command add 1
Enter your debugger command(s).  Type 'DONE' to end.
> po self
> p self.view.backgroundColor
> p self.view.backgroundColor = [UIColor redColor];
> DONE

过掉断点验证一下,进入断点的同时执行了刚才添加的指令,再过掉断点,看到屏幕变红色了。

跟断点一样,可以查看指令列表和删除列表:

  • 删除断点1的指令集可以使用 breakpoint command delete 1 ,
  • 查指令集列表 : breakpoint command list 发现已经没有了。

给所有断点添加指令

上一条是给某一个断点添加多个指令,那我们逆向的时候经常需要使用一些指令,我们如果给所有断点添加多指令,只要有断点来了,就执行我们的指令,这样就方便太多了。 比如我们常使用的指令: frame variable (查看一个方法下的所有参数)。就可以通过 target stop-hook add -o "frame variable" 给所有断点添加这样一个指令。

  • target stop-hook 目标是断点
  • add 添加
  • -o 是 --one 的意思,添加一条指令
  • "frame variable" 需要添加的指令

回车,过掉断点,给touchesBegan:withEvent: 添加一个断点, 点击屏幕后进入断点后执行了指令 frame variable 。

(lldb) target stop-hook add -o "frame variable"
Stop hook #1 added.
(ZJJLLDBViewController *const) self = <Could not evaluate DW_OP_entry_value.>

(SEL) _cmd = <Could not evaluate DW_OP_entry_value.>

(NSSet *) touches = <Could not evaluate DW_OP_entry_value.>

(UIEvent *) event = <Could not evaluate DW_OP_entry_value.>

(ZJJPerson *) p1 = <variable not available>


(lldb) 

target stop-hook的一些其他指令

  • target stop-hook list 查看列表
  • target stop-hook delete 1 删除第一组断点
  • target stop-hook delete 删除所有断点
  • target stop-hook disable 1 禁用第一组断点
  • undisplay 1 删除第一组断点,和 target stop-hook delete 1 功能一样。

给所有断点使用脚本添加多指令

每次Xcode 运行时, LLDB 启动就会加载一个文件 .lldbinit,在~目录下输入ls -a(列出所有文件,包括隐藏文件,. 开头的就是隐藏文件),就能找到(如果没有自己创建一个即可),vi .lldbinit,然后点击 i 添加我们刚才的指令 target stop-hook add -o "frame variable",键盘按 ESC 输入:wq 保存并退出。

因为是给系统的 LLDB 添加的,所以对每个工程都会有效。

然后回到我们的 Xcode 随意下一个断点。

Stop hook #1 added.
ZJJAllModule_Example was compiled with optimization - stepping may behave oddly; variables may not be available.
(ZJJLLDBViewController *const) self = <Could not evaluate DW_OP_entry_value.>

(SEL) _cmd = <Could not evaluate DW_OP_entry_value.>

(NSSet *) touches = <Could not evaluate DW_OP_entry_value.>

(UIEvent *) event = <Could not evaluate DW_OP_entry_value.>

(ZJJPerson *) p1 = <variable not available>

五、其他指令

  • image list :查看当前应用加载的所有的库

  • image lookup -t 类名 查看一个类的头文件信息

六、逆向方法断点

逆向中没有符号文件,那我们怎么给方法下断点呢?在上面我们知道我们可以通过watchpoint set expression对内存进行设置断点,其实breakpoint也可以对内存进行设置断点

b -a 0x100001980

但是怎么获取到方法的内存地址呢,这个时候就要借用工具Hopper Disassembler了。

首先我们写上下面代码

-(void)eatWithObject:(NSString *)objc{
    NSLog(@"吃到了%@",objc);
}


-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self eatWithObject:@"汉堡!"];
}

然后编译,从.app文件中拿到可执行文件,将可执行文件拖入到Hopper Disassembler中,(关于hopper的使用可以去百度一下)

从左边的方法列表里面我们可以找到eatWithObject的内存地址是0x100002190,我们可以通过指令b -a 0x100002190对这个地方进行加断点,

(lldb) b -a 0x100002190
warning: failed to set breakpoint site at 0x100002190 for breakpoint 1.1: error: 9 sending the breakpoint request
Breakpoint 1: address = 0x0000000100002190
(lldb) br list
Current breakpoints:
1: address = 0x0000000100002190, locations = 1
  1.1: address = 0x0000000100002190, unresolved, hit count = 0 

(lldb) 

我们发现断点下载成功了,并且断点列表中已经有了一个断点,我们点击一下,但是发现并没有断住,这是因为0x100002190只是这个方法在machO中的偏移量,但是并不是真正的偏移量,我们通过MachO打开项目的可执行文件,可以在Load Commands里面的PAGEZERO里面看到VM Size是4294967296

这是个空的地址值开头,在64位系统里面是4个G,实际上并不占用真实的内存地址,所以真正的偏移量是0x100002190减去这个大小,也就是0x100000000,也就是0x2190

所以真正的内存地址是需要加上machO文件的内存地址,我们通过image list查看一下macho文件的内存地址

两者相加就是方法的真正内存地址,我们用计算器算一下得到真正的内存地址是:0x102B86190,我们对这个内存地址进行下断点试试

然后点击屏幕,我们发现断点断住了,这个技术就是ASLR

ALSR

在计算机科学中,地址空间配置随机加载(英语:Address space layout randomization,缩写 ASLR ,又称 地址空间配置随机化 、地址空间布局随机化)是一种防范内存损坏漏洞被利用的计算机安全技术。

也就是 :物理地址 = ASLR + 方法虚拟地址

  • 其中在iOS上ASLR就是上面image的内存地址0x0000000102b84000
  • 因为每次应用被加载,machO所在的内存地址都不一样,这样就导致真正方法、变量的内存地址也不一样,这样黑客就不能拿到准确的内存地址,从而防范内存的漏洞,
  • 而iOS在这基础上还做了一个偏移,也就是上面说的Load Commands的PAGEZERO中的VM SIZE,在64位系统是4个G大小,是虚拟的,并不会占用真正的内存,
  • 所以对于iOS来说,真正的内存地址就是machO文件的内存地址加上方法相对macho文件的偏移地址再减去VM SIZE,这样就得到了真正的内存地址