csapp-Bomblab

  这是csapp系列的第二篇文章。具体的题目请见官网。另,本文均为自己手打,可能会有不少错误。如若发现有错误或者哪里写得不清楚,欢迎联系我修改(右下角小图标点开即可对话)。
  这个lab从头到尾都是自己慢慢看过来的,花了很长的时间,也不知道算不算值得吧。个人见解:看反汇编代码是真的很花时间,对着几十行的代码有的时候看了几个小时还是懵的。这个时候还是换一下心情,做点别的事,可能突然就看懂了。但也不要看到代码多就直接放弃了吧,沉下心来看,还是可以看得懂的。

bomb lab实验要求

  如同字面上的意思,这个实验要求我们拆一个“炸弹”。总共有六个关卡,需要保证每一个题目的输出结果都能满足某个特定的要求(答案不一定唯一),否则炸弹爆炸,游戏失败。

实验文件:
bomb: 炸弹,打开后需要正确输入对应的字符串才能通关
bomb.c: bomb的main函数所在的文件,提供给我们进行查看

实验目的:
考察gdb的使用,以及reverse engneering的能力。
需要学会使用gdb参考网站

  さあ、私たちの実験を始めましょう。
  首先,我们需要先使用objdump -d bomb > bomb.txt这个命令,将bomb反编译,并保存在txt文件当中,方便我们查看。然后,不妨来看一下bomb.c的代码,方便我们对整个实验有一个整体的了解。
  注意看一下注释,上面告诉了我们,可以将已解决的答案放入另一个txt文件当中,运行时用run < answer.txt将其导入,可以不用重复打。然后我们看一下整体,总共有六个字符串,每一个字符串输入后,会判断是否正确,错误则发生爆炸,正确则继续输入。看完之后,就准备进入反编译得到的文件了。   

bomb.txt

  我们先整体看一下这个文件里面有什么。文件很长,总共有一千多行,不可能每一行都去解读。我们可以利用一些小技巧帮助我们理解。如main函数,我们可以看到它分成如下的几个部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 读取第一个字符串
400e32: e8 67 06 00 00 callq 40149e <read_line>
400e37: 48 89 c7 mov %rax,%rdi
400e3a: e8 a1 00 00 00 callq 400ee0 <phase_1>
400e3f: e8 80 07 00 00 callq 4015c4 <phase_defused>
400e44: bf a8 23 40 00 mov $0x4023a8,%edi
400e49: e8 c2 fc ff ff callq 400b10 <puts@plt>
// 第二个
400e4e: e8 4b 06 00 00 callq 40149e <read_line>
400e53: 48 89 c7 mov %rax,%rdi
400e56: e8 a1 00 00 00 callq 400efc <phase_2>
400e5b: e8 64 07 00 00 callq 4015c4 <phase_defused>
400e60: bf ed 22 40 00 mov $0x4022ed,%edi
400e65: e8 a6 fc ff ff callq 400b10 <puts@plt>
// 第三个
400e6a: e8 2f 06 00 00 callq 40149e <read_line>
400e6f: 48 89 c7 mov %rax,%rdi
400e72: e8 cc 00 00 00 callq 400f43 <phase_3>
400e77: e8 48 07 00 00 callq 4015c4 <phase_defused>
400e7c: bf 0b 23 40 00 mov $0x40230b,%edi
400e81: e8 8a fc ff ff callq 400b10 <puts@plt>
...

  结合bomb.c,我们应该可以很容易理解main.c的反汇编代码了。接下来,我们再看下其他函数。
  1.strings_not_equal函数。名字很明显告诉了我们,这个函数是判断两个字符串是否相等的。相等则返回0
  2.explode_bomb函数,注意到,其中使用了exit函数。也就是说,这个函数一旦运行,就意味着游戏失败,直接exit退出。

1
2
3
4
5
6
7
8
000000000040143a <explode_bomb>:
40143a: 48 83 ec 08 sub $0x8,%rsp
40143e: bf a3 25 40 00 mov $0x4025a3,%edi
401443: e8 c8 f6 ff ff callq 400b10 <puts@plt>
401448: bf ac 25 40 00 mov $0x4025ac,%edi
40144d: e8 be f6 ff ff callq 400b10 <puts@plt>
401452: bf 08 00 00 00 mov $0x8,%edi
401457: e8 c4 f7 ff ff callq 400c20 <exit@plt>

  3.read_line函数。由名字我们也可以知道,这个函数就是为了读取一行字符串。
  4.read_six_numbers函数。由名字,我们知道,这个函数就是用来读六个数字。注意代码中用到了sscanf函数,用于从某一个字符串中格式化读取。其中%rdi存放的是待读取字符串,%rsi存储的是用于格式化的串,后面跟着的都是变量的地址。如sscanf(“7 0”, “%d %d”, &a, &b),返回值为成功读取的变量个数。
  5.phase_1 phase_2 … 用于判断输入的字符串是否满足要求

phase_1

1
2
3
4
5
6
7
8
9
0000000000400ee0 <phase_1>:
400ee0: 48 83 ec 08 sub $0x8,%rsp
400ee4: be 00 24 40 00 mov $0x402400,%esi
400ee9: e8 4a 04 00 00 callq 401338 <strings_not_equal> // 直接判断与0x402400位置的字符串是否相同
400eee: 85 c0 test %eax,%eax
400ef0: 74 05 je 400ef7 <phase_1+0x17>
400ef2: e8 43 05 00 00 callq 40143a <explode_bomb>
400ef7: 48 83 c4 08 add $0x8,%rsp
400efb: c3 retq

  题目很短,难度也很小,就是将读取的字符串直接与首地址为0x402400的字符串作比较,若相同则返回,否则引爆炸弹。因此,我们需要知道0x402400中究竟放着什么字符串。直接从反汇编代码中没办法看出来,这个时候就要用到强大的gdb了。需要用到x命令。语法为: x/<n/f/u>
  其中,n是一个正整数,表示需要显示的内存单元的个数。每个内存单元的大小与u相关。u表示每个单元的大小。f表示输出的格式。较常用的如下。
  x 按十六进制格式输出
  d 按十进制格式输出
  t 按二进制格式输出
  c 按字符格式输出
  如:x/10c 0x402400 将以字符形式输出从0x402400开始的十个字节
  x/10xw 0x402400 将以字符形式输出从0x402400开始的十个单元,每个单元为4个字节
  具体的使用自己试一试就知道了。我们直接查看内存后就发现,对应的字符串为: Border relations with Canada have never been better. 第一题结束。

phase_2

  第二题,长度明显加长了一些。我们可以试着将其分段解读。至于从哪里开始分的话,尽量是选择jmp类的命令所在的行或者jmp命令跳转到的行,这种地方有可能是for循环,或者条件分支语句的结尾。就第二题来说,我们可以将其分成两个部分。第一个部分如下:

1
2
3
4
5
6
400efe:	48 83 ec 28          	sub    $0x28,%rsp                 // 分配40kb的内存空间
400f02: 48 89 e6 mov %rsp,%rsi
400f05: e8 52 05 00 00 callq 40145c <read_six_numbers> // 将空间传给函数,读取六个数字
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) // 判断第一个数字是否为1
400f0e: 74 20 je 400f30 <phase_2+0x34> // 为1,跳开;否则,引爆炸弹
400f10: e8 25 05 00 00 callq 40143a <explode_bomb>

  第二个部分如下:

1
2
3
4
5
6
7
8
400f17:	8b 43 fc             	mov    -0x4(%rbx),%eax
400f1a: 01 c0 add %eax,%eax // 取出下一个数字,并*2
400f1c: 39 03 cmp %eax,(%rbx) // 判断每一个数字是否为上一个数字的两倍
400f1e: 74 05 je 400f25 <phase_2+0x29> // 是,跳开;不是,爆炸
400f20: e8 15 05 00 00 callq 40143a <explode_bomb> // 循环体内部,依次判断输入是否正确
400f25: 48 83 c3 04 add $0x4,%rbx
400f29: 48 39 eb cmp %rbp,%rbx
400f2c: 75 e9 jne 400f17 <phase_2+0x1b>

  于是我们发现,整个程序对我们的要求有三个:
  1.输入六个数字
  2.第一个数字是1
  3.第二个数字开始,每一个数字是前一个的两倍
  由此,我们可以得到结果为:1, 2, 4, 8, 16, 32

phase_3

  第三题,题目又变长了一些。在400f65之前,就是用sscanf读入两个数字。也就是说这次我们需要输入两个满足特定关系的数字。
  再接下来的三行,程序判断输入的第一个数字是否大于小于等于7,否则爆炸。
  接下来一行是重点了。

1
400f75:	ff 24 c5 70 24 40 00 	jmpq   *0x402470(,%rax,8)

  这个代码是什么意思呢?注意号的作用,该指令跳转的目标点是地址为0x402470+8%rax的内存单元。因此,我们需要打印出来0x402470+8i的值,对照代码后会发现,其实源代码应该就是一个switch函数。再看下接下来的几行代码,基本都是这样的格式:

1
2
400f7c:	b8 cf 00 00 00       	mov    $0xcf,%eax
400f81: eb 3b jmp 400fbe <phase_3+0x7b>

  也就是说,根据你所输入的第一个数,你的第二个数需要对应这跳转目标点mov赋的值。为了简单起见,笔者直接打印0x402470,得到答案。

1
2
3
4
5
6
7
8
// b为我们输入的第一个数字,v为我们输入的第二个数字。
int a;
switch(b) {
case 0: a = ...
case 1: a = ...
case 2: a = ...
}
if (a != v) explode_bomb();

phase_4

  这道题看起来难度并不大。前面我们已经做了三道题了,基本上开始能够看懂一些复杂一点点的代码。phase_3函数应该也就不成问题了。首先输入两个数字,第二个数字必须为0,第一个数字将放入func4函数当中。也就是说,我们目的就是看懂func4这个函数在干什么。然后根据函数推断出我们需要输入的第一个数字即可。

1
2
3
4
400fd6:	89 c1                	mov    %eax,%ecx
400fd8: c1 e9 1f shr $0x1f,%ecx
400fdb: 01 c8 add %ecx,%eax
400fdd: d1 f8 sar %eax

  首先,上面这个部分一开始让我迷惑了很久,这是要干嘛??其中shr向右移动31位只有两个结果,当%ecx的值大于等于0时,得到结果为0,否则得到-1。因此这几行其实是当%eax的值小于0的时候就减去1。最后再向又移一位(缺省则位移一位)。
  再接下来这段笔者看了特别久,最后是采用尝试着打出源代码才理解的。如果有跟我一样的看不太懂的,也不妨试试这个办法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
400fdf:	8d 0c 30             	lea    (%rax,%rsi,1),%ecx
400fe2: 39 f9 cmp %edi,%ecx
400fe4: 7e 0c jle 400ff2 <func4+0x24>
400fe6: 8d 51 ff lea -0x1(%rcx),%edx
400fe9: e8 e0 ff ff ff callq 400fce <func4>

400fee: 01 c0 add %eax,%eax
400ff0: eb 15 jmp 401007 <func4+0x39>

400ff2: b8 00 00 00 00 mov $0x0,%eax
400ff7: 39 f9 cmp %edi,%ecx
400ff9: 7d 0c jge 401007 <func4+0x39>

400ffb: 8d 71 01 lea 0x1(%rcx),%esi
400ffe: e8 cb ff ff ff callq 400fce <func4>
401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax

  如上,将其通过跳转指令划分为4个部分。然后我们发现它调用了自己,也就是说这是一个递归函数。注意到phase_4调用它之前放入了3个参数。我们可以尝试着打出源代码。

1
2
3
4
5
6
7
8
9
10
11
// a为我们输入的数,b初始为0,c初始值为15。我们需要返回的值是0。因此,只需要让一开始tt就等于a即可
void func4(int a, int b, int c)
{
int t = c-b;
if(t<0) t--;
t>>=1;
int tt=t+b;
if(tt>a) c=tt-1; return 2*func4(a,b,c);
else if (tt==a) return 0;
else return 2*func4(a,tt+1,c)+1;
}

phase_5

  这道题长度又一次增加了,不过好在难度还不算太大。其实能做完第四题的话做第五题问题应该是不大的。和之前一样。我们先尝试着将整个代码拆分成几个部分。
  首先,要求我们输入的应该是一个字符串,且长度必须为6。接下来的几行是一个循环,我们先跳过。先看最后面的几行代码。

1
2
3
4
4010ae:	c6 44 24 16 00       	movb   $0x0,0x16(%rsp)
4010b3: be 5e 24 40 00 mov $0x40245e,%esi
4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
4010bd: e8 76 02 00 00 callq 401338 <strings_not_equal>

  上面几行将栈上面位于%rsp+10~%rsp+15的字符串与首地址为0x40245e的字符串相比较,打印地址后我们看到,字符串为flyers,也就是说我们的输入经过变换之后要变成flyers这个字符串。这个时候我们再回去看一下循环体内部是怎样做变换的。

1
2
3
4
5
6
7
8
9
40108b:	0f b6 0c 03          	movzbl (%rbx,%rax,1),%ecx
40108f: 88 0c 24 mov %cl,(%rsp)
401092: 48 8b 14 24 mov (%rsp),%rdx
401096: 83 e2 0f and $0xf,%edx
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1)
4010a4: 48 83 c0 01 add $0x1,%rax
4010a8: 48 83 f8 06 cmp $0x6,%rax
4010ac: 75 dd jne 40108b <phase_5+0x29>

  注意到这一段代码,一次取出每一个字符。并将其与0xf相与,也就是说我们取出后四位的值c,再加上0x4024b0得到一个值v,再取出内存地址为v的值,放入栈当中。这样说可能有点难理解。我们来举一个例子。比如第一个字符串处理之后要变成’f’,查询ASCII得,相当与102,也就是0x66, 再查看一下内存,看到‘f’位于0x4024b9。因此我们需要的c的值为9,为了方便,我第一个字符输入的是i(0x69),和0xf相与之后恰好为9,满足要求。后面的同理,不再赘述。由此,本题成功解决了。

phase_6

  这道题可以说是有点丧心病狂了。难度和之前感觉完全不在一个档次上。不过毕竟是压轴题,也可以理解。同样的,我们先将代码拆分成几个小部分。
  首先,题目读取了六个数字。然后需要对这六个数字做出相当长的处理。下面的这一段应该是目前为止最难理解的一个点。如果无法理解,还请多看几遍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 下面为一个嵌套循环,终止条件为r12 == 6 目的为检测是否所有字符均不相等以及小于等于6
// %eax = a[i] (%r13)
// %rbp = &a[i]
401114: 4c 89 ed mov %r13,%rbp
401117: 41 8b 45 00 mov (%r13),%eax
40111b: 83 e8 01 sub $0x1,%eax
40111e: 83 f8 05 cmp $0x5,%eax
401121: 76 05 jbe 401128 <phase_6+0x34> // a[i] > 6 爆炸
401123: e8 12 03 00 00 callq 40143a <explode_bomb>
401128: 41 83 c4 01 add $0x1,%r12d
40112c: 41 83 fc 06 cmp $0x6,%r12d
401130: 74 21 je 401153 <phase_6+0x5f> // %r12 == 6 退出循环
// 第二层循环
401132: 44 89 e3 mov %r12d,%ebx
401135: 48 63 c3 movslq %ebx,%rax
401138: 8b 04 84 mov (%rsp,%rax,4),%eax
40113b: 39 45 00 cmp %eax,0x0(%rbp)
40113e: 75 05 jne 401145 <phase_6+0x51> // a[i] != a[i+j] 否则爆炸
401140: e8 f5 02 00 00 callq 40143a <explode_bomb>
401145: 83 c3 01 add $0x1,%ebx
401148: 83 fb 05 cmp $0x5,%ebx
40114b: 7e e8 jle 401135 <phase_6+0x41>
// 第二层循环外
40114d: 49 83 c5 04 add $0x4,%r13
401151: eb c1 jmp 401114 <phase_6+0x20>

  接下来的部分就比较好理解了。最终对整个程序的影响是将每一个值a[i]转化为7-a[i]。

1
2
3
4
5
6
7
8
9
401153:	48 8d 74 24 18       	lea    0x18(%rsp),%rsi
401158: 4c 89 f0 mov %r14,%rax
40115b: b9 07 00 00 00 mov $0x7,%ecx
401160: 89 ca mov %ecx,%edx
401162: 2b 10 sub (%rax),%edx
401164: 89 10 mov %edx,(%rax) // a[i] = 7 - a[i]
401166: 48 83 c0 04 add $0x4,%rax
40116a: 48 39 f0 cmp %rsi,%rax
40116d: 75 f1 jne 401160 <phase_6+0x6c>

  再接下来这一段就比较麻烦了。和之前一样,我采用了尝试这打出源代码的方法进行理解。由于接下来的两段代码经常要访问内存,为了方便理解,我将内存代码打出来,欢迎查阅。

address address + 4 address + 8 address + 12
0x6032d0 0x0000014c 0x00000001 0x006032e0 0x00000000
0x6032e0 0x000000a8 0x00000002 0x006032f0 0x00000000
0x6032f0 0x0000039c 0x00000003 0x00603300 0x00000000
0x603300 0x000002b3 0x00000004 0x00603300 0x00000000
0x603310 0x000001dd 0x00000005 0x00603320 0x00000000
0x603320 0x000001bb 0x00000006 0x00000000 0x00000000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
401176:	48 8b 52 08          	mov    0x8(%rdx),%rdx
40117a: 83 c0 01 add $0x1,%eax
40117d: 39 c8 cmp %ecx,%eax
40117f: 75 f5 jne 401176 <phase_6+0x82>
401181: eb 05 jmp 401188 <phase_6+0x94>
401183: ba d0 32 60 00 mov $0x6032d0,%edx
401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2)
40118d: 48 83 c6 04 add $0x4,%rsi
401191: 48 83 fe 18 cmp $0x18,%rsi
401195: 74 14 je 4011ab <phase_6+0xb7>
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx
40119a: 83 f9 01 cmp $0x1,%ecx
40119d: 7e e4 jle 401183 <phase_6+0x8f>
40119f: b8 01 00 00 00 mov $0x1,%eax
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx
4011a9: eb cb jmp 401176 <phase_6+0x82>
1
2
3
4
5
6
7
8
9
10
11
12
 // %rsi : 4 * i
// %eax : 用于和a[i]做比较
// %ecx : a[i]
for (int i = 0; i < 6; i++)
{
if (a[i]<=1) {
M[%rsp + 8 * i + 32] = 0x6032d0;
} else {
// 经查看内存可得
M[%rsp + 8 * i + 32] = 0x6032d0 + 16 * (a[i]-1);
}
}

  关于上面的这段函数,我们可以理解为它在构造一个结构体Node(注意,指针为64位,且为小端),其中Node的成员如下:

1
2
3
4
5
Node {
int value;
int id;
Node* next;
}

  倘若我们这样看,会发现整个代码容易理解了很多,结合具体内存中的值,我们发现,下面的这一段其实就是将各个结构体元素连接起来,形成一个链表。

1
2
3
4
5
6
7
8
9
10
11
12
4011ab:	48 8b 5c 24 20       	mov    0x20(%rsp),%rbx
4011b0: 48 8d 44 24 28 lea 0x28(%rsp),%rax
4011b5: 48 8d 74 24 50 lea 0x50(%rsp),%rsi
4011ba: 48 89 d9 mov %rbx,%rcx
4011bd: 48 8b 10 mov (%rax),%rdx
4011c0: 48 89 51 08 mov %rdx,0x8(%rcx)
4011c4: 48 83 c0 08 add $0x8,%rax
4011c8: 48 39 f0 cmp %rsi,%rax
4011cb: 74 05 je 4011d2 <phase_6+0xde>
4011cd: 48 89 d1 mov %rdx,%rcx
4011d0: eb eb jmp 4011bd <phase_6+0xc9>
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx)

  最后这一段其实就好理解很多了。其实就是从前往后遍历链表,然后检查下一个结构体元素的value是否大于上一个的,否则爆炸。再看一下前面的内存值。我们就可以得到满足每一个元素value均大于上一个的链表的顺序了:2,1,6,5,4,3。当然要是你信心满满直接把这个顺序输入进去的话(像我一样),你会发现,炸弹还是爆炸了。别忘了,前面有一个操作将a[i]变成了7-a[i],因此,我们应该再处理一下,得到最终的正确答案为:5,6,1,2,3,4
  Congratulations! You’ve defused the bomb.

1
2
3
4
5
6
7
8
9
4011da:	bd 05 00 00 00       	mov    $0x5,%ebp
4011df: 48 8b 43 08 mov 0x8(%rbx),%rax
4011e3: 8b 00 mov (%rax),%eax
4011e5: 39 03 cmp %eax,(%rbx)
4011e7: 7d 05 jge 4011ee <phase_6+0xfa>
4011e9: e8 4c 02 00 00 callq 40143a <explode_bomb>
4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx
4011f2: 83 ed 01 sub $0x1,%ebp
4011f5: 75 e8 jne 4011df <phase_6+0xeb>

secret_phase

  然而,真的通关了吗?
  细心的同学可能会发现(我也发现啦!),在bomb.c中有着这样一段注释:

/Wow, they got it! But isn’t something… missing? Perhaps
something they overlooked? Mua ha ha ha ha!

  从这句话,我们可以猜出,作者果然还是有阴谋的。于是我们再重新回去看了一下,发现了一个神奇的函数,叫secret_phase,果然有问题。crtf+f查找,发现这是在phase_defused中调用的,而且再看一下它调用的相关代码:

1
2
3
4
4015d1:	48 89 44 24 68       	mov    %rax,0x68(%rsp)
4015d6: 31 c0 xor %eax,%eax
4015d8: 83 3d 81 21 20 00 06 cmpl $0x6,0x202181(%rip) # 603760 <num_input_strings>
4015df: 75 5e jne 40163f <phase_defused+0x7b>

  我们发现,只有当前6个炸弹全部拆除后才可以调用。这不明摆着是彩蛋了吗。接下来一大坨代码就是看你能不能顺利揭开彩蛋了。
  接下来,再看下这坨代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
4015e1:	4c 8d 44 24 10       	lea    0x10(%rsp),%r8
4015e6: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
4015eb: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
4015f0: be 19 26 40 00 mov $0x402619,%esi
4015f5: bf 70 38 60 00 mov $0x603870,%edi
4015fa: e8 f1 f5 ff ff callq 400bf0 <__isoc99_sscanf@plt> // 读取两个整数和一个字符串
4015ff: 83 f8 03 cmp $0x3,%eax
401602: 75 31 jne 401635 <phase_defused+0x71> // 没有读取到三个,原地爆炸

401604: be 22 26 40 00 mov $0x402622,%esi
401609: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
40160e: e8 25 fd ff ff callq 401338 <strings_not_equal> // 将读取的字符串与0x402622为首地址的字符串作比较
401613: 85 c0 test %eax,%eax
401615: 75 1e jne 401635 <phase_defused+0x71> // 字符串和给定的不相同,还是爆炸

  恩,好吧,从一个给定的串中读取一些东西,打印0x402619后面的几个字符,我们发现结果是“%d %d %s”,读取两个整数和一个字符串。可事情并没有这么简单。我们尝试打印一下0x603870,发现字符串就只有“7 0”,不可能读3个数字。那我们怎么玩??
  看来我们只好作弊了。使用gdb直接用print指令更改内存的值,将“7 0”后面补一点东西,补什么呢?那当然是0x402622上面的东西了。打印一下,发现是“DrEvil”,好吧满满的恶意。补上之后(可以用类似 p {int}0x603873=’D’ 这样的指令为内存单元赋值),就顺利进入了secret_phase函数了。
  再看一下这个函数干了些什么。恩,读取了一个字符串,再用strtol函数将其转化为数字,也就是说,我们目的就是输入一个满足要求的数字。然后,又与2进行比较,也就是说,我们目的是要运行fun7函数,并且得到答案为2的返回值。
  接下来我们看一下fun7函数。主体大概是这样子的。可以看出,这是一个递归。其中的v为我们输入的那个值。

1
2
3
4
5
6
7
if (x <= v) {
return 2 * fun7(...);
}
else if (x == v) return 0;
else {
return 2 * fun7(...)+1;
}

  好了,我们发现代码中有0x8(%rdi),%rdi这样的指令,接下来又要打印内存信息了。注意到一开始传入的值为0x6030f0,故我们打印一下0x6030f8和0x603100的值,发现答案为0x603110和0x603130,又是两个地址。并且,打印0x6030f0,0x603100,0x603110我们发现,结果都是一个比较小的值,不难猜测,这应该是一个二叉树。于是,按照这个规律,我们可以将整棵树打印出来.(其中各个框内表示的是该点的地址)


0x6030f0
0x6031100x603130
0x6031900x6031500x6031700x6031b0
0x6031f00x6032500x6032700x6032300x6031d00x6032900x6032100x6032b0

  接着,再倒推,2 = 2 * 1, 1 = 2 * 0 + 1 . 则应该选择地址为0x603150的点,打印得到,答案为22,结束。

------------- The artical is over Thanks for your reading -------------