Post

CSAPP - Bomb Lab

CSAPP - Bomb Lab

本答案仅供参考,每份bomblab的数据是独一无二的,但思路大致相同

Tips

  1. !!!每次gdb中运行run命令之前一定要在explode_bomb函数前打上断点 !!!

  2. 在通过几个关卡之后可以把答案存在ans.txt中并用gdb的设置参数功能省去每次输入的麻烦:

1
(gdb) set args ans.txt
  1. gdb不需要重新运行退出重进,需要退出时用kill中止运行,此时断点信息依然保留,重新run即可
  2. gdb支持重复运行上一条指令,例如多次运行stepinexti,只需要在输入一遍后连续按enter即可

Phase1

首先运行objdump -d bomb > bd.txt把反汇编代码储存在bd.txt中。

打开bd.txt并观察,发现有六个phase函数,这便是六个阶段的函数入口

1
2
3
4
5
6
7
8
9
10
11
12
  400e4d: e8 9e 00 00 00        callq  400ef0 <phase_1>
  400e52: e8 b0 09 00 00        callq  401807 <phase_defused>
  400e69: e8 9e 00 00 00        callq  400f0c <phase_2>
  400e6e: e8 94 09 00 00        callq  401807 <phase_defused>
  400e85: e8 cd 00 00 00        callq  400f57 <phase_3>
  400e8a: e8 78 09 00 00        callq  401807 <phase_defused>
  400ea1: e8 36 02 00 00        callq  4010dc <phase_4>
  400ea6: e8 5c 09 00 00        callq  401807 <phase_defused>
  400ebd: e8 6b 02 00 00        callq  40112d <phase_5>
  400ec2: e8 40 09 00 00        callq  401807 <phase_defused>
  400ed9: e8 b1 02 00 00        callq  40118f <phase_6>
  400ede: e8 24 09 00 00        callq  401807 <phase_defused>

首先在phase_1处设置断点:

1
(gdb) break phase_1

运行至phase_1处,用disas命令查看反汇编码:

1
2
3
4
5
6
7
8
9
Dump of assembler code for function phase_1:
   0x0000000000400ef0 <+0>:   sub    $0x8,%rsp
   0x0000000000400ef4 <+4>:   mov    $0x4025c0,%esi
   0x0000000000400ef9 <+9>:   callq  0x4013be <strings_not_equal>
   0x0000000000400efe <+14>:  test   %eax,%eax
   0x0000000000400f00 <+16>:  je     0x400f07 <phase_1+23>
   0x0000000000400f02 <+18>:  callq  0x401669 <explode_bomb>
   0x0000000000400f07 <+23>:  add    $0x8,%rsp
   0x0000000000400f0b <+27>:  retq

其中有一行比较特殊:

1
0x0000000000400ef4 <+4>:    mov    $0x4025c0,%esi

0x40开头的地址储存的应是代码,于是可以想到这应该是有用信息,于是打印一下0x4025c0的内容:

1
(gdb) x/s 0x4025c0

结果是:

1
The moon unit will be divided into two divisions.

看着非常像答案,重新run一下测试发现答案正确

Phase 2

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
26
27
Dump of assembler code for function phase_2:
   0x0000000000400f0c <+0>:   push   %rbp
   0x0000000000400f0d <+1>:   push   %rbx
   0x0000000000400f0e <+2>:   sub    $0x28,%rsp
   0x0000000000400f12 <+6>:   mov    %rsp,%rsi
   0x0000000000400f15 <+9>:   callq  0x40169f <read_six_numbers>
   0x0000000000400f1a <+14>:  cmpl   $0x0,(%rsp)
   0x0000000000400f1e <+18>:  jns    0x400f44 <phase_2+56>
   0x0000000000400f20 <+20>:  callq  0x401669 <explode_bomb>
   0x0000000000400f25 <+25>:  jmp    0x400f44 <phase_2+56>
   0x0000000000400f27 <+27>:  mov    %ebx,%eax
   0x0000000000400f29 <+29>:  add    -0x4(%rbp),%eax
   0x0000000000400f2c <+32>:  cmp    %eax,0x0(%rbp)
   0x0000000000400f2f <+35>:  je     0x400f36 <phase_2+42>
   0x0000000000400f31 <+37>:  callq  0x401669 <explode_bomb>
   0x0000000000400f36 <+42>:  add    $0x1,%ebx
   0x0000000000400f39 <+45>:  add    $0x4,%rbp
   0x0000000000400f3d <+49>:  cmp    $0x6,%ebx
   0x0000000000400f40 <+52>:  jne    0x400f27 <phase_2+27>
   0x0000000000400f42 <+54>:  jmp    0x400f50 <phase_2+68>
   0x0000000000400f44 <+56>:  lea    0x4(%rsp),%rbp
   0x0000000000400f49 <+61>:  mov    $0x1,%ebx
   0x0000000000400f4e <+66>:  jmp    0x400f27 <phase_2+27>
   0x0000000000400f50 <+68>:  add    $0x28,%rsp
   0x0000000000400f54 <+72>:  pop    %rbx
   0x0000000000400f55 <+73>:  pop    %rbp
   0x0000000000400f56 <+74>:  retq

1
0x0000000000400f15 <+9>:  callq  0x40169f <read_six_numbers>

从函数名可以看出,这一关卡需要我们输入6个数字,于是重新运行,输入六个数字,继续运行


1
2
3
4
5
6
7
8
9
10
11
12
13
14
0x0000000000400f25 <+25>: jmp    0x400f44 <phase_2+56>
0x0000000000400f27 <+27>: mov    %ebx,%eax
0x0000000000400f29 <+29>: add    -0x4(%rbp),%eax
0x0000000000400f2c <+32>: cmp    %eax,0x0(%rbp)
0x0000000000400f2f <+35>: je     0x400f36 <phase_2+42>
0x0000000000400f31 <+37>: callq  0x401669 <explode_bomb>
0x0000000000400f36 <+42>: add    $0x1,%ebx
0x0000000000400f39 <+45>: add    $0x4,%rbp
0x0000000000400f3d <+49>: cmp    $0x6,%ebx
0x0000000000400f40 <+52>: jne    0x400f27 <phase_2+27>
0x0000000000400f42 <+54>: jmp    0x400f50 <phase_2+68>
0x0000000000400f44 <+56>: lea    0x4(%rsp),%rbp
0x0000000000400f49 <+61>: mov    $0x1,%ebx
0x0000000000400f4e <+66>: jmp    0x400f27 <phase_2+27>

接下来进入循环,首先会到达phase_2+56,这里显然是对循环进行初始化,把0x4(%rsp)这一个地址赋给%rbp,即我们输入的第二个数的 地址,同时把%ebx初始化为1

然后开始循环,每次循环,把%ebx的数加上%rbp的前一个数,再与%rbp对应的数做比较,如果不相等,那么炸弹爆炸。接着把%rbp向下一个数移动,并把%ebx加1,继续循环,直到%ebx等于6,即把每个数都比较了一遍,这段程序可以用c语言大致写为:

1
2
3
4
int a[6]; // 我们输入的六个数
for (int i = 1; i < 6; i++) {
  if (a[i] != a[i - 1] + i) explode_bomb();
}

所以答案可以为

1
2
1 2 4 7 11 16
(满足x, x+1, x+3, x+6, x+10, x+15的形式即可)

Phase 3

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
Dump of assembler code for function phase_3:
   0x0000000000400f57 <+0>:   sub    $0x18,%rsp
   0x0000000000400f5b <+4>:   lea    0x8(%rsp),%r8
   0x0000000000400f60 <+9>:   lea    0x7(%rsp),%rcx
   0x0000000000400f65 <+14>:  lea    0xc(%rsp),%rdx
   0x0000000000400f6a <+19>:  mov    $0x40261e,%esi
   0x0000000000400f6f <+24>:  mov    $0x0,%eax
   0x0000000000400f74 <+29>:  callq  0x400c30 <__isoc99_sscanf@plt>
   0x0000000000400f79 <+34>:  cmp    $0x2,%eax
   0x0000000000400f7c <+37>:  jg     0x400f83 <phase_3+44>
   0x0000000000400f7e <+39>:  callq  0x401669 <explode_bomb>
   0x0000000000400f83 <+44>:  cmpl   $0x7,0xc(%rsp)
   0x0000000000400f88 <+49>:  ja     0x40108a <phase_3+307>
   0x0000000000400f8e <+55>:  mov    0xc(%rsp),%eax
   0x0000000000400f92 <+59>:  jmpq   *0x402630(,%rax,8)
*  0x0000000000400f99 <+66>:  mov    $0x48,%eax
   0x0000000000400f9e <+71>:  cmpl   $0x99,0x8(%rsp)
   0x0000000000400fa6 <+79>:  je     0x401094 <phase_3+317>
   0x0000000000400fac <+85>:  callq  0x401669 <explode_bomb>
   0x0000000000400fb1 <+90>:  mov    $0x48,%eax
   0x0000000000400fb6 <+95>:  jmpq   0x401094 <phase_3+317>
*  0x0000000000400fbb <+100>: mov    $0x74,%eax
   0x0000000000400fc0 <+105>: cmpl   $0xa4,0x8(%rsp)
   0x0000000000400fc8 <+113>: je     0x401094 <phase_3+317>
   0x0000000000400fce <+119>: callq  0x401669 <explode_bomb>
   0x0000000000400fd3 <+124>: mov    $0x74,%eax
   0x0000000000400fd8 <+129>: jmpq   0x401094 <phase_3+317>
*  0x0000000000400fdd <+134>: mov    $0x43,%eax
   0x0000000000400fe2 <+139>: cmpl   $0x34c,0x8(%rsp)
   0x0000000000400fea <+147>: je     0x401094 <phase_3+317>
   0x0000000000400ff0 <+153>: callq  0x401669 <explode_bomb>
   0x0000000000400ff5 <+158>: mov    $0x43,%eax
   0x0000000000400ffa <+163>: jmpq   0x401094 <phase_3+317>
*  0x0000000000400fff <+168>: mov    $0x43,%eax
   0x0000000000401004 <+173>: cmpl   $0x3aa,0x8(%rsp)
   0x000000000040100c <+181>: je     0x401094 <phase_3+317>
   0x0000000000401012 <+187>: callq  0x401669 <explode_bomb>
   0x0000000000401017 <+192>: mov    $0x43,%eax
   0x000000000040101c <+197>: jmp    0x401094 <phase_3+317>
*  0x000000000040101e <+199>: mov    $0x74,%eax
   0x0000000000401023 <+204>: cmpl   $0x38c,0x8(%rsp)
   0x000000000040102b <+212>: je     0x401094 <phase_3+317>
   0x000000000040102d <+214>: callq  0x401669 <explode_bomb>
   0x0000000000401032 <+219>: mov    $0x74,%eax
   0x0000000000401037 <+224>: jmp    0x401094 <phase_3+317>
*  0x0000000000401039 <+226>: mov    $0x67,%eax
   0x000000000040103e <+231>: cmpl   $0x344,0x8(%rsp)
   0x0000000000401046 <+239>: je     0x401094 <phase_3+317>
   0x0000000000401048 <+241>: callq  0x401669 <explode_bomb>
   0x000000000040104d <+246>: mov    $0x67,%eax
   0x0000000000401052 <+251>: jmp    0x401094 <phase_3+317>
*  0x0000000000401054 <+253>: mov    $0x57,%eax
   0x0000000000401059 <+258>: cmpl   $0x1e3,0x8(%rsp)
   0x0000000000401061 <+266>: je     0x401094 <phase_3+317>
   0x0000000000401063 <+268>: callq  0x401669 <explode_bomb>
   0x0000000000401068 <+273>: mov    $0x57,%eax
   0x000000000040106d <+278>: jmp    0x401094 <phase_3+317>
*  0x000000000040106f <+280>: mov    $0x71,%eax
   0x0000000000401074 <+285>: cmpl   $0x2cb,0x8(%rsp)
   0x000000000040107c <+293>: je     0x401094 <phase_3+317>
   0x000000000040107e <+295>: callq  0x401669 <explode_bomb>
   0x0000000000401083 <+300>: mov    $0x71,%eax
   0x0000000000401088 <+305>: jmp    0x401094 <phase_3+317>
   0x000000000040108a <+307>: callq  0x401669 <explode_bomb>
   0x000000000040108f <+312>: mov    $0x63,%eax
   0x0000000000401094 <+317>: cmp    0x7(%rsp),%al
   0x0000000000401098 <+321>: je     0x40109f <phase_3+328>
   0x000000000040109a <+323>: callq  0x401669 <explode_bomb>
   0x000000000040109f <+328>: add    $0x18,%rsp
   0x00000000004010a3 <+332>: retq

由第二问的过程可知,%esi里储存的是sscanf的参数的地址,于是打印%esi中地址上的内容:

1
(gdb) x/s 0x40261e

得到结果:

1
"%d %c %d"

由此可知第三阶段的输入应为整型-字符-整型,于是随便尝试一个输入以便进到主体


用stepi继续运行,运行到这两行:

1
2
0x0000000000400f83 <+44>:   cmpl   $0x7,0xc(%rsp)
0x0000000000400f88 <+49>:   ja     0x40108a <phase_3+307>

由这两行可以知道,若0xc(%rsp)这个数大于7的话,那么程序会跳转到0x40108a,即

1
0x000000000040108a <+307>:  callq  0x401669 <explode_bomb>

这条指令,会触发炸弹,因此该数必须小于7,并且由于这是无符号数比较,负数会被cast成很大的无符号数,因此该数范围为0~7。打 印一下这个数,可以发现对应的是我们输入的第一个数


继续运行,马上就遇到这两行:

1
2
0x0000000000400f8e <+55>:   mov    0xc(%rsp),%eax
0x0000000000400f92 <+59>:   jmpq   *0x402630(,%rax,8)

其中*0x402630是一个跳转表的地址,后面的(,%rax,8)代表了偏移量,根据%rax的值来确定跳转目标地址,而%rax的值便是我们输入的第一个数

打印跳转表,显然跳转表的地址数对应0~7即八种可能:

1
2
3
4
5
(gdb) x/8gx 0x402630
0x402630: 0x0000000000400f99  0x0000000000400fbb
0x402640: 0x0000000000400fdd  0x0000000000400fff
0x402650: 0x000000000040101e  0x0000000000401039
0x402660: 0x0000000000401054  0x000000000040106f

跳转表的目标地址可以在反汇编码中找到对应,已在上方用*号标出


观察八个程序段可以发现,这八个程序段结构上一样,仅一些参数不同,于是拿第一个进行分析,

其中0x8(%rsp)储存的是我们输入的第三个数:

1
2
3
4
5
6
0x0000000000400f99 <+66>: mov    $0x48,%eax
0x0000000000400f9e <+71>: cmpl   $0x99,0x8(%rsp)
0x0000000000400fa6 <+79>: je     0x401094 <phase_3+317>
0x0000000000400fac <+85>: callq  0x401669 <explode_bomb>
0x0000000000400fb1 <+90>: mov    $0x48,%eax
0x0000000000400fb6 <+95>: jmpq   0x401094 <phase_3+317>

为避免触发explode_bomb于是需要让第二行的比较相等,因此第三个数跟第一个数的取值有关,但还不能将两数确定,于是假设比较结果相等,接下来跟着第三行跳转到下一段程序:

1
2
3
4
5
0x0000000000401094 <+317>:  cmp    0x7(%rsp),%al
0x0000000000401098 <+321>:  je     0x40109f <phase_3+328>
0x000000000040109a <+323>:  callq  0x401669 <explode_bomb>
0x000000000040109f <+328>:  add    $0x18,%rsp
0x00000000004010a3 <+332>:  retq

这里需要将0x7(%rsp)地址上储存的数据,即我们输入的字符与%al,即%rax寄存器的最低字节进行比较,若相等,则炸弹可被成功拆除


没有什么别的约束条件,于是尝试输入跳转表第一个地址对应的正确答案后成功拆除:

1
0 H 153

Phase 4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Dump of assembler code for function phase_4:
   0x00000000004010dc <+0>:   sub    $0x18,%rsp
   0x00000000004010e0 <+4>:   lea    0xc(%rsp),%rcx
   0x00000000004010e5 <+9>:   lea    0x8(%rsp),%rdx
   0x00000000004010ea <+14>:  mov    $0x4028a1,%esi
   0x00000000004010ef <+19>:  mov    $0x0,%eax
   0x00000000004010f4 <+24>:  callq  0x400c30 <__isoc99_sscanf@plt>
   0x00000000004010f9 <+29>:  cmp    $0x2,%eax
   0x00000000004010fc <+32>:  jne    0x40110a <phase_4+46>
   0x00000000004010fe <+34>:  mov    0xc(%rsp),%eax
   0x0000000000401102 <+38>:  sub    $0x2,%eax
   0x0000000000401105 <+41>:  cmp    $0x2,%eax
   0x0000000000401108 <+44>:  jbe    0x40110f <phase_4+51>
   0x000000000040110a <+46>:  callq  0x401669 <explode_bomb>
   0x000000000040110f <+51>:  mov    0xc(%rsp),%esi
   0x0000000000401113 <+55>:  mov    $0x6,%edi
   0x0000000000401118 <+60>:  callq  0x4010a4 <func4>
   0x000000000040111d <+65>:  cmp    0x8(%rsp),%eax
   0x0000000000401121 <+69>:  je     0x401128 <phase_4+76>
   0x0000000000401123 <+71>:  callq  0x401669 <explode_bomb>
   0x0000000000401128 <+76>:  add    $0x18,%rsp
   0x000000000040112c <+80>:  retq

同样的,读取一下0x4028a1上的数据:

1
"%d %d"

于是知道是要输入两个整型,重启程序,随便输入两个数


在数据读取之后的一行打上断点:

1
(gdb) break *0x4010fe

跳到断点处,接下来几行:

1
2
3
4
5
0x00000000004010fe <+34>: mov    0xc(%rsp),%eax
0x0000000000401102 <+38>: sub    $0x2,%eax
0x0000000000401105 <+41>: cmp    $0x2,%eax
0x0000000000401108 <+44>: jbe    0x40110f <phase_4+51>
0x000000000040110a <+46>: callq  0x401669 <explode_bomb>

0xc(%rsp)储存的值赋给了%eax,通过打印可以知道这是我们输入的第二个数,后面几行将这一个数减2,再和2比较,若大于2,则炸弹爆炸,反之则可以绕开炸弹。同时注意到这里的比较是无符号的,因此第二个数的范围是2~4


程序继续执行:

1
2
3
0x000000000040110f <+51>: mov    0xc(%rsp),%esi
0x0000000000401113 <+55>: mov    $0x6,%edi
0x0000000000401118 <+60>: callq  0x4010a4 <func4>

这里将0xc(%rsp)储存的值赋给%esi,即我们输入的第二个数,并把6赋给%edi,并调用func4函数,那么%rsi%rdi显然就是传入参数。 这里继续stepi,进入func4中查看


func4:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Dump of assembler code for function func4:
   0x00000000004010a4 <+0>:   push   %r12
   0x00000000004010a6 <+2>:   push   %rbp
   0x00000000004010a7 <+3>:   push   %rbx
   0x00000000004010a8 <+4>:   mov    %edi,%ebx
   0x00000000004010aa <+6>:   test   %edi,%edi
   0x00000000004010ac <+8>:   jle    0x4010d2 <func4+46>
   0x00000000004010ae <+10>:  mov    %esi,%ebp
   0x00000000004010b0 <+12>:  mov    %esi,%eax
   0x00000000004010b2 <+14>:  cmp    $0x1,%edi
   0x00000000004010b5 <+17>:  je     0x4010d7 <func4+51>
   0x00000000004010b7 <+19>:  lea    -0x1(%rdi),%edi
   0x00000000004010ba <+22>:  callq  0x4010a4 <func4>
   0x00000000004010bf <+27>:  lea    (%rax,%rbp,1),%r12d
   0x00000000004010c3 <+31>:  lea    -0x2(%rbx),%edi
   0x00000000004010c6 <+34>:  mov    %ebp,%esi
   0x00000000004010c8 <+36>:  callq  0x4010a4 <func4>
   0x00000000004010cd <+41>:  add    %r12d,%eax
   0x00000000004010d0 <+44>:  jmp    0x4010d7 <func4+51>
   0x00000000004010d2 <+46>:  mov    $0x0,%eax
   0x00000000004010d7 <+51>:  pop    %rbx
   0x00000000004010d8 <+52>:  pop    %rbp
   0x00000000004010d9 <+53>:  pop    %r12
   0x00000000004010db <+55>:  retq

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0x00000000004010a8 <+4>:   mov    %edi,%ebx
0x00000000004010aa <+6>:   test   %edi,%edi
0x00000000004010ac <+8>:   jle    0x4010d2 <func4+46>
0x00000000004010ae <+10>:  mov    %esi,%ebp
0x00000000004010b0 <+12>:  mov    %esi,%eax
0x00000000004010b2 <+14>:  cmp    $0x1,%edi
0x00000000004010b5 <+17>:  je     0x4010d7 <func4+51>
0x00000000004010b7 <+19>:  lea    -0x1(%rdi),%edi
0x00000000004010ba <+22>:  callq  0x4010a4 <func4>
0x00000000004010bf <+27>:  lea    (%rax,%rbp,1),%r12d
0x00000000004010c3 <+31>:  lea    -0x2(%rbx),%edi
0x00000000004010c6 <+34>:  mov    %ebp,%esi
0x00000000004010c8 <+36>:  callq  0x4010a4 <func4>
0x00000000004010cd <+41>:  add    %r12d,%eax
0x00000000004010d0 <+44>:  jmp    0x4010d7 <func4+51>
0x00000000004010d2 <+46>:  mov    $0x0,%eax

这是func4的主体部分,可以发现进行了递归。这么多寄存器看似眼花缭乱,无从下手,但我们只需记住一点:被调用函数会维护传入参数,保证在调用过程前后传入参数的值不会改变,在这个函数中,%rdi%rsi是传入参数,%rax是返回值,我们只需要跟踪这三个值即可


1
2
3
0x00000000004010aa <+6>:   test   %edi,%edi
0x00000000004010ac <+8>:   jle    0x4010d2 <func4+46>
0x00000000004010d2 <+46>:  mov    $0x0,%eax

这三行告诉我们,如果%rdi的值是0,那么函数返回0

1
2
3
4
0x00000000004010ae <+10>:  mov    %esi,%ebp
0x00000000004010b0 <+12>:  mov    %esi,%eax
0x00000000004010b2 <+14>:  cmp    $0x1,%edi
0x00000000004010b5 <+17>:  je     0x4010d7 <func4+51>

同理,如果%rdi的值是1,那么返回%rsi

这两种情况便是边界条件


1
2
3
4
5
6
7
8
0x00000000004010b7 <+19>:  lea    -0x1(%rdi),%edi
0x00000000004010ba <+22>:  callq  0x4010a4 <func4>
0x00000000004010bf <+27>:  lea    (%rax,%rbp,1),%r12d
0x00000000004010c3 <+31>:  lea    -0x2(%rbx),%edi
0x00000000004010c6 <+34>:  mov    %ebp,%esi
0x00000000004010c8 <+36>:  callq  0x4010a4 <func4>
0x00000000004010cd <+41>:  add    %r12d,%eax
0x00000000004010d0 <+44>:  jmp    0x4010d7 <func4+51>

这里是递归部分,不难看出分别进行两次调用,每次分别将%rdi的值减1和减2,最后返回的结果是两次调用的返回值再加上%rsi


于是我们可以写出func4的c语言形式:

1
2
3
4
5
6
/* x in %rdi, y in %rsi */
int func4(int x, int y) {
  if (x <= 0) return 0;
  if (x == 1) return y;
  return func4(x - 1, y) + func4(x - 2, y) + y;
}

运行一下,发现当x=6,y=3时结果是60


于是答案可以是:

1
60 3

其余解不再展示


Phase 5

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
26
27
28
29
30
Dump of assembler code for function phase_5:
   0x000000000040112d <+0>:   push   %rbx
   0x000000000040112e <+1>:   sub    $0x10,%rsp
   0x0000000000401132 <+5>:   mov    %rdi,%rbx
   0x0000000000401135 <+8>:   callq  0x4013a1 <string_length>
   0x000000000040113a <+13>:  cmp    $0x6,%eax
   0x000000000040113d <+16>:  je     0x401182 <phase_5+85>
   0x000000000040113f <+18>:  callq  0x401669 <explode_bomb>
   0x0000000000401144 <+23>:  jmp    0x401182 <phase_5+85>
   0x0000000000401146 <+25>:  movzbl (%rbx,%rax,1),%edx
   0x000000000040114a <+29>:  and    $0xf,%edx
   0x000000000040114d <+32>:  movzbl 0x402670(%rdx),%edx
   0x0000000000401154 <+39>:  mov    %dl,(%rsp,%rax,1)
   0x0000000000401157 <+42>:  add    $0x1,%rax
   0x000000000040115b <+46>:  cmp    $0x6,%rax
   0x000000000040115f <+50>:  jne    0x401146 <phase_5+25>
   0x0000000000401161 <+52>:  movb   $0x0,0x6(%rsp)
   0x0000000000401166 <+57>:  mov    $0x402627,%esi
   0x000000000040116b <+62>:  mov    %rsp,%rdi
   0x000000000040116e <+65>:  callq  0x4013be <strings_not_equal>
   0x0000000000401173 <+70>:  test   %eax,%eax
   0x0000000000401175 <+72>:  je     0x401189 <phase_5+92>
   0x0000000000401177 <+74>:  callq  0x401669 <explode_bomb>
   0x000000000040117c <+79>:  nopl   0x0(%rax)
   0x0000000000401180 <+83>:  jmp    0x401189 <phase_5+92>
   0x0000000000401182 <+85>:  mov    $0x0,%eax
   0x0000000000401187 <+90>:  jmp    0x401146 <phase_5+25>
   0x0000000000401189 <+92>:  add    $0x10,%rsp
   0x000000000040118d <+96>:  pop    %rbx
   0x000000000040118e <+97>:  retq

这里与之前有所不同,首先先对输入的字符串计算大小:

1
2
3
4
5
6
7
0x000000000040112d <+0>:   push   %rbx
0x000000000040112e <+1>:   sub    $0x10,%rsp
0x0000000000401132 <+5>:   mov    %rdi,%rbx
0x0000000000401135 <+8>:   callq  0x4013a1 <string_length>
0x000000000040113a <+13>:  cmp    $0x6,%eax
0x000000000040113d <+16>:  je     0x401182 <phase_5+85>
0x000000000040113f <+18>:  callq  0x401669 <explode_bomb>

如果长度不等于6,那么炸弹爆炸,于是重新运行,这次输入一个长度为6的字符串


程序绕过炸弹,运行到这一段:

1
2
3
4
5
6
7
0x0000000000401146 <+25>: movzbl (%rbx,%rax,1),%edx
0x000000000040114a <+29>: and    $0xf,%edx
0x000000000040114d <+32>: movzbl 0x402670(%rdx),%edx
0x0000000000401154 <+39>: mov    %dl,(%rsp,%rax,1)
0x0000000000401157 <+42>: add    $0x1,%rax
0x000000000040115b <+46>: cmp    $0x6,%rax
0x000000000040115f <+50>: jne    0x401146 <phase_5+25>

首先不难看出这是一个循环,%rax初始值为0,当%rax等于6时,退出循环。第一行的意思是将%rbx对应的地址的第%rax个字节赋给%rdx,其实就相当于把我们输入的字符串的第%rax个字符赋给%rdx。例如我这次输入的是“helloo”,则现在%rdx中是'h'。之后与0xf进行与运算,即取%rdx的最低四位,那么每当程序运行到这里%rdx的值只有0~15这十六种可能。

接下来一行,把0x402670加上%rdx的值之后的地址上的一个字符赋给%rdx。由于%rdx只能取0~15,我们不妨打印一下0x402670开始的16 个字符:

1
2
0x402670 <array.3160>:    109 'm' 97 'a'  100 'd' 117 'u' 105 'i' 101 'e' 114 'r' 115 's'
0x402678 <array.3160+8>:  110 'n' 102 'f' 111 'o' 116 't' 118 'v' 98 'b'  121 'y' 108 'l'

下面一行便是把结果储存到栈顶,其中%dl%rdx的最低字节,即刚才获取的字符,循环继续。


循环结束后,栈顶储存了6个字符,那么显然下面是要进行匹配了:

1
2
3
4
5
6
0x0000000000401161 <+52>: movb   $0x0,0x6(%rsp)
0x0000000000401166 <+57>: mov    $0x402627,%esi
0x000000000040116b <+62>: mov    %rsp,%rdi
0x000000000040116e <+65>: callq  0x4013be <strings_not_equal>
0x0000000000401173 <+70>: test   %eax,%eax
0x0000000000401175 <+72>: je     0x401189 <phase_5+92>

第一行便是在字符串末尾加上0,之后把参数读到%rsi中,作为匹配目标串,把%rsp的值给到%rdi中,之后调用函数,这一过程相当于双指针匹配字符串。于是打印一下0x402627的内容:

1
98 'b'  114 'r' 117 'u' 105 'i' 110 'n' 115 's'

因此我们的目标串是"bruins",在前面我们打印的字符表中找到对应的字符,再计算对应的偏移量,于是可分别得到6个字符的最低四位


于是得到答案:

1
mfcdhw (大写也可以)

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
Dump of assembler code for function phase_6:
   0x000000000040118f <+0>:   push   %r14
   0x0000000000401191 <+2>:   push   %r13
   0x0000000000401193 <+4>:   push   %r12
   0x0000000000401195 <+6>:   push   %rbp
   0x0000000000401196 <+7>:   push   %rbx
   0x0000000000401197 <+8>:   sub    $0x50,%rsp
   0x000000000040119b <+12>:  lea    0x30(%rsp),%r13
   0x00000000004011a0 <+17>:  mov    %r13,%rsi
   0x00000000004011a3 <+20>:  callq  0x40169f <read_six_numbers>
   0x00000000004011a8 <+25>:  mov    %r13,%r14
   0x00000000004011ab <+28>:  mov    $0x0,%r12d
   0x00000000004011b1 <+34>:  mov    %r13,%rbp
   0x00000000004011b4 <+37>:  mov    0x0(%r13),%eax
   0x00000000004011b8 <+41>:  sub    $0x1,%eax
   0x00000000004011bb <+44>:  cmp    $0x5,%eax
   0x00000000004011be <+47>:  jbe    0x4011c5 <phase_6+54>
   0x00000000004011c0 <+49>:  callq  0x401669 <explode_bomb>
   0x00000000004011c5 <+54>:  add    $0x1,%r12d
   0x00000000004011c9 <+58>:  cmp    $0x6,%r12d
   0x00000000004011cd <+62>:  je     0x4011f1 <phase_6+98>
   0x00000000004011cf <+64>:  mov    %r12d,%ebx
   0x00000000004011d2 <+67>:  movslq %ebx,%rax
   0x00000000004011d5 <+70>:  mov    0x30(%rsp,%rax,4),%eax
   0x00000000004011d9 <+74>:  cmp    %eax,0x0(%rbp)
   0x00000000004011dc <+77>:  jne    0x4011e3 <phase_6+84>
   0x00000000004011de <+79>:  callq  0x401669 <explode_bomb>
   0x00000000004011e3 <+84>:  add    $0x1,%ebx
   0x00000000004011e6 <+87>:  cmp    $0x5,%ebx
   0x00000000004011e9 <+90>:  jle    0x4011d2 <phase_6+67>
   0x00000000004011eb <+92>:  add    $0x4,%r13
   0x00000000004011ef <+96>:  jmp    0x4011b1 <phase_6+34>
   0x00000000004011f1 <+98>:  lea    0x48(%rsp),%rsi
   0x00000000004011f6 <+103>: mov    %r14,%rax
   0x00000000004011f9 <+106>: mov    $0x7,%ecx
   0x00000000004011fe <+111>: mov    %ecx,%edx
   0x0000000000401200 <+113>: sub    (%rax),%edx
   0x0000000000401202 <+115>: mov    %edx,(%rax)
   0x0000000000401204 <+117>: add    $0x4,%rax
   0x0000000000401208 <+121>: cmp    %rsi,%rax
   0x000000000040120b <+124>: jne    0x4011fe <phase_6+111>
   0x000000000040120d <+126>: mov    $0x0,%esi
   0x0000000000401212 <+131>: jmp    0x401234 <phase_6+165>
   0x0000000000401214 <+133>: mov    0x8(%rdx),%rdx
   0x0000000000401218 <+137>: add    $0x1,%eax
   0x000000000040121b <+140>: cmp    %ecx,%eax
   0x000000000040121d <+142>: jne    0x401214 <phase_6+133>
   0x000000000040121f <+144>: jmp    0x401226 <phase_6+151>
   0x0000000000401221 <+146>: mov    $0x6042f0,%edx
   0x0000000000401226 <+151>: mov    %rdx,(%rsp,%rsi,2)
   0x000000000040122a <+155>: add    $0x4,%rsi
   0x000000000040122e <+159>: cmp    $0x18,%rsi
   0x0000000000401232 <+163>: je     0x401249 <phase_6+186>
   0x0000000000401234 <+165>: mov    0x30(%rsp,%rsi,1),%ecx
   0x0000000000401238 <+169>: cmp    $0x1,%ecx
   0x000000000040123b <+172>: jle    0x401221 <phase_6+146>
   0x000000000040123d <+174>: mov    $0x1,%eax
   0x0000000000401242 <+179>: mov    $0x6042f0,%edx
   0x0000000000401247 <+184>: jmp    0x401214 <phase_6+133>
   0x0000000000401249 <+186>: mov    (%rsp),%rbx
   0x000000000040124d <+190>: lea    0x8(%rsp),%rax
   0x0000000000401252 <+195>: lea    0x30(%rsp),%rsi
   0x0000000000401257 <+200>: mov    %rbx,%rcx
   0x000000000040125a <+203>: mov    (%rax),%rdx
   0x000000000040125d <+206>: mov    %rdx,0x8(%rcx)
   0x0000000000401261 <+210>: add    $0x8,%rax
   0x0000000000401265 <+214>: cmp    %rsi,%rax
   0x0000000000401268 <+217>: je     0x40126f <phase_6+224>
   0x000000000040126a <+219>: mov    %rdx,%rcx
   0x000000000040126d <+222>: jmp    0x40125a <phase_6+203>
   0x000000000040126f <+224>: movq   $0x0,0x8(%rdx)
   0x0000000000401277 <+232>: mov    $0x5,%ebp
   0x000000000040127c <+237>: mov    0x8(%rbx),%rax
   0x0000000000401280 <+241>: mov    (%rax),%eax
   0x0000000000401282 <+243>: cmp    %eax,(%rbx)
   0x0000000000401284 <+245>: jge    0x40128b <phase_6+252>
   0x0000000000401286 <+247>: callq  0x401669 <explode_bomb>
   0x000000000040128b <+252>: mov    0x8(%rbx),%rbx
   0x000000000040128f <+256>: sub    $0x1,%ebp
   0x0000000000401292 <+259>: jne    0x40127c <phase_6+237>
   0x0000000000401294 <+261>: add    $0x50,%rsp
   0x0000000000401298 <+265>: pop    %rbx
   0x0000000000401299 <+266>: pop    %rbp
   0x000000000040129a <+267>: pop    %r12
   0x000000000040129c <+269>: pop    %r13
   0x000000000040129e <+271>: pop    %r14
   0x00000000004012a0 <+273>: retq

观察这三行:

1
2
3
0x0000000000401197 <+8>:   sub    $0x50,%rsp
0x000000000040119b <+12>:  lea    0x30(%rsp),%r13
0x00000000004011a0 <+17>:  mov    %r13,%rsi

这里是在开辟栈空间,并将我们输入的数据起始地址交给%rsi%r13


然后便调用了read_six_numbers这个函数,那么显然是要读入6个数字,%rsi便是参数,于是重新运行,这次随便输入6个数字,继续往下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
0x00000000004011a8 <+25>: mov    %r13,%r14
0x00000000004011ab <+28>: mov    $0x0,%r12d
0x00000000004011b1 <+34>: mov    %r13,%rbp
0x00000000004011b4 <+37>: mov    0x0(%r13),%eax
0x00000000004011b8 <+41>: sub    $0x1,%eax
0x00000000004011bb <+44>: cmp    $0x5,%eax
0x00000000004011be <+47>: jbe    0x4011c5 <phase_6+54>
0x00000000004011c0 <+49>: callq  0x401669 <explode_bomb>
0x00000000004011c5 <+54>: add    $0x1,%r12d
0x00000000004011c9 <+58>: cmp    $0x6,%r12d
0x00000000004011cd <+62>: je     0x4011f1 <phase_6+98>
0x00000000004011cf <+64>: mov    %r12d,%ebx
0x00000000004011d2 <+67>: movslq %ebx,%rax
0x00000000004011d5 <+70>: mov    0x30(%rsp,%rax,4),%eax
0x00000000004011d9 <+74>: cmp    %eax,0x0(%rbp)
0x00000000004011dc <+77>: jne    0x4011e3 <phase_6+84>
0x00000000004011de <+79>: callq  0x401669 <explode_bomb>
0x00000000004011e3 <+84>: add    $0x1,%ebx
0x00000000004011e6 <+87>: cmp    $0x5,%ebx
0x00000000004011e9 <+90>: jle    0x4011d2 <phase_6+67>
0x00000000004011eb <+92>: add    $0x4,%r13
0x00000000004011ef <+96>: jmp    0x4011b1 <phase_6+34>

这里是一大段循环,分析一下可以得到原来的代码应大致如下,即我们输入的六个数应为123456各一个,可以测试一下输入重复数字和不是1~6的情况,发现能验证我们的分析结果

1
2
3
4
5
6
int a[6]; // 我们输入的数组
for (int i = 0; i < 6 i++) {
  if (a[i] < 1 || a[i] > 6) explode_bomb();
  for (int j = i + 1; j < 6; j++)
    if (a[i] == a[j] ) explode_bomb();
}

1
2
3
4
5
6
7
8
9
0x00000000004011f1 <+98>:   lea    0x48(%rsp),%rsi
0x00000000004011f6 <+103>:  mov    %r14,%rax
0x00000000004011f9 <+106>:  mov    $0x7,%ecx
0x00000000004011fe <+111>:  mov    %ecx,%edx
0x0000000000401200 <+113>:  sub    (%rax),%edx
0x0000000000401202 <+115>:  mov    %edx,(%rax)
0x0000000000401204 <+117>:  add    $0x4,%rax
0x0000000000401208 <+121>:  cmp    %rsi,%rax
0x000000000040120b <+124>:  jne    0x4011fe <phase_6+111>

接下来这段是将我们输入的数组中的每一个数x替换为7-x,在此不多加赘述,简单分析便知


接下来的程序就有些复杂了,不过我们仍然可以发现一些有意思的东西,比如:

1
0x0000000000401242 <+179>:  mov    $0x6042f0,%edx

这显然是将一个地址赋给了%rdx,那么可以将其理解为一个指针,还有:

1
0x0000000000401214 <+133>:  mov    0x8(%rdx),%rdx

同时,0x60开头的内存空间储存的应是全局变量,于是基本可以确定,这个指针指向的是一个全局结构体,我们不妨打印一下0x6042f0及其后继的内存空间:

1
2
3
4
5
6
7
8
9
10
11
12
0x6042f0 <node1>:   0x24  0x02  0x00  0x00  0x01  0x00  0x00  0x00
0x6042f8 <node1+8>: 0x00  0x43  0x60  0x00  0x00  0x00  0x00  0x00
0x604300 <node2>:   0x44  0x03  0x00  0x00  0x02  0x00  0x00  0x00
0x604308 <node2+8>: 0x10  0x43  0x60  0x00  0x00  0x00  0x00  0x00
0x604310 <node3>:   0xdc  0x01  0x00  0x00  0x03  0x00  0x00  0x00
0x604318 <node3+8>: 0x20  0x43  0x60  0x00  0x00  0x00  0x00  0x00
0x604320 <node4>:   0x25  0x02  0x00  0x00  0x04  0x00  0x00  0x00
0x604328 <node4+8>: 0x30  0x43  0x60  0x00  0x00  0x00  0x00  0x00
0x604330 <node5>:   0x0c  0x01  0x00  0x00  0x05  0x00  0x00  0x00
0x604338 <node5+8>: 0x40  0x43  0x60  0x00  0x00  0x00  0x00  0x00
0x604340 <node6>:   0x74  0x02  0x00  0x00  0x06  0x00  0x00  0x00
0x604348 <node6+8>: 0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00

可以发现一共有六个结构体,所以这应该是一个结构体数组,由其名字node可以大胆猜测这是一个链式结构,用x /w来打印一下看看:

1
2
3
4
5
6
7
                                           node*
0x6042f0 <node1>: 0x00000224  0x00000001  0x00604300  0x00000000
0x604300 <node2>: 0x00000344  0x00000002  0x00604310  0x00000000
0x604310 <node3>: 0x000001dc  0x00000003  0x00604320  0x00000000
0x604320 <node4>: 0x00000225  0x00000004  0x00604330  0x00000000
0x604330 <node5>: 0x0000010c  0x00000005  0x00604340  0x00000000
0x604340 <node6>: 0x00000274  0x00000006  0x00000000  0x00000000

结果与我们猜想的一样,每个结构体均储存了一个指向下一个结构体的指针


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0x0000000000401214 <+133>:  mov    0x8(%rdx),%rdx
0x0000000000401218 <+137>:  add    $0x1,%eax
0x000000000040121b <+140>:  cmp    %ecx,%eax
0x000000000040121d <+142>:  jne    0x401214 <phase_6+133>
0x000000000040121f <+144>:  jmp    0x401226 <phase_6+151>
0x0000000000401221 <+146>:  mov    $0x6042f0,%edx
0x0000000000401226 <+151>:  mov    %rdx,(%rsp,%rsi,2)
0x000000000040122a <+155>:  add    $0x4,%rsi
0x000000000040122e <+159>:  cmp    $0x18,%rsi
0x0000000000401232 <+163>:  je     0x401249 <phase_6+186>
0x0000000000401234 <+165>:  mov    0x30(%rsp,%rsi,1),%ecx
0x0000000000401238 <+169>:  cmp    $0x1,%ecx
0x000000000040123b <+172>:  jle    0x401221 <phase_6+146>
0x000000000040123d <+174>:  mov    $0x1,%eax
0x0000000000401242 <+179>:  mov    $0x6042f0,%edx
0x0000000000401247 <+184>:  jmp    0x401214 <phase_6+133>

那么这段程序就容易理解了,这段程序会先从phase_6+165这一行开始,先取数组的第一个数x,将其和1比较,如果不是1,那么进行循环,首先把指向第一个node的指针赋给%rdx,然后开始进行沿链表进行单向移动,直至到达第x个node,然后将这个node的指针存到从栈顶开始的一片空间,这一过程可以大致写为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct node {
  int val;
  int label;
  node *next;
} nodes[6];

int a[i];
node *pt[6];

for (int i = 0; i < 6; i++) {
  int x = a[i];
  node *p = &nodes[0];
  while (--x) {
    p = p->next;
  }
  pt[i] = p;
}

运行到phase_6+186处,打印%rsp的内容,发现可以验证我们的分析:

1
2
3
0x7fffffffe5b0: 0x00604340  0x00000000  0x00604330  0x00000000
0x7fffffffe5c0: 0x00604320  0x00000000  0x00604310  0x00000000
0x7fffffffe5d0: 0x00604300  0x00000000  0x006042f0  0x00000000

1
2
3
4
5
6
7
8
9
10
11
12
0x0000000000401249 <+186>:  mov    (%rsp),%rbx
0x000000000040124d <+190>:  lea    0x8(%rsp),%rax
0x0000000000401252 <+195>:  lea    0x30(%rsp),%rsi
0x0000000000401257 <+200>:  mov    %rbx,%rcx
0x000000000040125a <+203>:  mov    (%rax),%rdx
0x000000000040125d <+206>:  mov    %rdx,0x8(%rcx)
0x0000000000401261 <+210>:  add    $0x8,%rax
0x0000000000401265 <+214>:  cmp    %rsi,%rax
0x0000000000401268 <+217>:  je     0x40126f <phase_6+224>
0x000000000040126a <+219>:  mov    %rdx,%rcx
0x000000000040126d <+222>:  jmp    0x40125a <phase_6+203>
0x000000000040126f <+224>:  movq   $0x0,0x8(%rdx)

这一段实现的功能便是把链表按照栈顶的新顺序链接起来,并在最后一个节点的next指针赋为空


1
2
3
4
5
6
7
8
9
0x0000000000401277 <+232>:  mov    $0x5,%ebp
0x000000000040127c <+237>:  mov    0x8(%rbx),%rax
0x0000000000401280 <+241>:  mov    (%rax),%eax
0x0000000000401282 <+243>:  cmp    %eax,(%rbx)
0x0000000000401284 <+245>:  jge    0x40128b <phase_6+252>
0x0000000000401286 <+247>:  callq  0x401669 <explode_bomb>
0x000000000040128b <+252>:  mov    0x8(%rbx),%rbx
0x000000000040128f <+256>:  sub    $0x1,%ebp
0x0000000000401292 <+259>:  jne    0x40127c <phase_6+237>

分析一下可知,这里是循环比较新链表中的后一个和前一个的值,只有这个链表的值是单调递减的,才能避免触发炸弹,因此我们需要比较六个节点的值的大小,并反推出我们的输入,于是可以得到:

1
5 1 3 6 4 2

成功拆除!!

Secret_Phase

在phase_6通过之后,在其对应的phase_defused函数上打上断点,并查看反汇编,可以发现一个隐藏关的入口:

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
26
27
28
29
30
31
32
33
Dump of assembler code for function phase_defused:
   0x0000000000401807 <+0>:   sub    $0x68,%rsp
   0x000000000040180b <+4>:   mov    $0x1,%edi
   0x0000000000401810 <+9>:   callq  0x40154a <send_msg>
   0x0000000000401815 <+14>:  cmpl   $0x6,0x202f80(%rip)        # 0x60479c <num_input_strings>
   0x000000000040181c <+21>:  jne    0x40188b <phase_defused+132>
   0x000000000040181e <+23>:  lea    0x10(%rsp),%r8
   0x0000000000401823 <+28>:  lea    0x8(%rsp),%rcx
   0x0000000000401828 <+33>:  lea    0xc(%rsp),%rdx
   0x000000000040182d <+38>:  mov    $0x4028eb,%esi
   0x0000000000401832 <+43>:  mov    $0x6048b0,%edi
   0x0000000000401837 <+48>:  mov    $0x0,%eax
   0x000000000040183c <+53>:  callq  0x400c30 <__isoc99_sscanf@plt>
   0x0000000000401841 <+58>:  cmp    $0x3,%eax
   0x0000000000401844 <+61>:  jne    0x401877 <phase_defused+112>
   0x0000000000401846 <+63>:  mov    $0x4028f4,%esi
   0x000000000040184b <+68>:  lea    0x10(%rsp),%rdi
   0x0000000000401850 <+73>:  callq  0x4013be <strings_not_equal>
   0x0000000000401855 <+78>:  test   %eax,%eax
   0x0000000000401857 <+80>:  jne    0x401877 <phase_defused+112>
   0x0000000000401859 <+82>:  mov    $0x402740,%edi
   0x000000000040185e <+87>:  callq  0x400b40 <puts@plt>
   0x0000000000401863 <+92>:  mov    $0x402768,%edi
   0x0000000000401868 <+97>:  callq  0x400b40 <puts@plt>
   0x000000000040186d <+102>: mov    $0x0,%eax
   0x0000000000401872 <+107>: callq  0x4012df <secret_phase> # !!here!!
   0x0000000000401877 <+112>: mov    $0x4027a0,%edi
   0x000000000040187c <+117>: callq  0x400b40 <puts@plt>
   0x0000000000401881 <+122>: mov    $0x4027d0,%edi
   0x0000000000401886 <+127>: callq  0x400b40 <puts@plt>
   0x000000000040188b <+132>: add    $0x68,%rsp
   0x000000000040188f <+136>: retq


1
2
0x0000000000401815 <+14>: cmpl   $0x6,0x202f80(%rip)        # 0x60479c <num_input_strings>
0x000000000040181c <+21>: jne    0x40188b <phase_defused+132>

由这两行可以知道,只有当输入的字符串是6个的时候才能有机会进入隐藏关,即通过六个关卡。由此也可以猜测这个bomb程序每次是读 取一行,再使用sscanf这一函数读取字符串


打印一下0x4028eb,可以得到我们进入隐藏关的办法:

1
"%d %d %s"

前两个连续的整数输入只在第四关见过,因此大胆猜测进入隐藏关的方式是在第四关的答案后加一串字符串


为了验证这一猜想,我们可以打印一下sscanf函数的第一个参数,因为对照函数定义我们可以知道第一个参数就是源字符串的指针。而0x6048b0显然是全局变量区中的数据,那么他对应着我们之前输入过的字符串的可能性就更大了:

1
2
(gdb) x/s $0x6048b0
0x6048b0 <input_strings+240>: "60 3"

这里的60 3显然对应的就是我们的第四个输入


我们不妨更进一步,看看input_strings这个全局变量的结构,首先用readelf -s bomb读取一下这个symbol的地址:

1
00000000006047c0  1600 OBJECT  GLOBAL DEFAULT   25 input_strings

接下来打印以该地址为始的一段内容以寻找规律:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(gdb) x/500c 0x6047c0
0x6047c0 <input_strings>: 84 'T'  104 'h' 101 'e' 32 ' '  109 'm' 111 'o' 111 'o' 110 'n'
0x6047c8 <input_strings+8>: 32 ' '  117 'u' 110 'n' 105 'i' 116 't' 32 ' '  119 'w' 105 'i'
0x6047d0 <input_strings+16>:  108 'l' 108 'l' 32 ' '  98 'b'  101 'e' 32 ' '  100 'd' 105 'i'
0x6047d8 <input_strings+24>:  118 'v' 105 'i' 100 'd' 101 'e' 100 'd' 32 ' '  105 'i' 110 'n'
0x6047e0 <input_strings+32>:  116 't' 111 'o' 32 ' '  116 't' 119 'w' 111 'o' 32 ' '  100 'd'
0x6047e8 <input_strings+40>:  105 'i' 118 'v' 105 'i' 115 's' 105 'i' 111 'o' 110 'n' 115 's'
0x6047f0 <input_strings+48>:  46 '.'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'
0x6047f8 <input_strings+56>:  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'
0x604800 <input_strings+64>:  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'
0x604808 <input_strings+72>:  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'
0x604810 <input_strings+80>:  49 '1'  32 ' '  50 '2'  32 ' '  52 '4'  32 ' '  55 '7'  32 ' '
0x604818 <input_strings+88>:  49 '1'  49 '1'  32 ' '  49 '1'  54 '6'  0 '\000'  0 '\000'  0 '\000'
0x604820 <input_strings+96>:  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'
0x604828 <input_strings+104>: 0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'
0x604830 <input_strings+112>: 0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'
0x604838 <input_strings+120>: 0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'
0x604840 <input_strings+128>: 0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'
0x604848 <input_strings+136>: 0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'
0x604850 <input_strings+144>: 0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'
0x604858 <input_strings+152>: 0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'  0 '\000'
0x604860 <input_strings+160>: 48 '0'  32 ' '  72 'H'  32 ' '  49 '1'  53 '5'  51 '3'  0 '\000'

比较乱,但是可以发现每80个字节是一个字符串,于是可以想到这应该是一个二维字符数组的形式,就像这样:char[][80]。接着打印一下,可以发现确实如此:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(gdb) x/s 0x6047c0
0x6047c0 <input_strings>: "The moon unit will be divided into two divisions."
(gdb) x/s 0x6047c0 + 80
0x604810 <input_strings+80>:  "1 2 4 7 11 16"
(gdb) x/s 0x6047c0 + 160
0x604860 <input_strings+160>: "0 H 153"
(gdb) x/s 0x6047c0 + 240
0x6048b0 <input_strings+240>: "60 3 DrEvil"
(gdb) x/s 0x6047c0 + 300
0x6048ec <input_strings+300>: ""
(gdb) x/s 0x6047c0 + 320
0x604900 <input_strings+320>: "mfcdhw"
(gdb) x/s 0x6047c0 + 400
0x604950 <input_strings+400>: "5 1 3 6 4 2"

回归正题,打印一下strings_not_equal的传入参数:

1
2
(gdb) x/s 0x4028f4
0x4028f4: "DrEvil"

很明显,答案就是这个


重新运行后成功进入隐藏关:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Dump of assembler code for function secret_phase:
   0x00000000004012df <+0>:   push   %rbx
   0x00000000004012e0 <+1>:   callq  0x4016e1 <read_line>
   0x00000000004012e5 <+6>:   mov    $0xa,%edx
   0x00000000004012ea <+11>:  mov    $0x0,%esi
   0x00000000004012ef <+16>:  mov    %rax,%rdi
   0x00000000004012f2 <+19>:  callq  0x400c00 <strtol@plt>
   0x00000000004012f7 <+24>:  mov    %rax,%rbx
   0x00000000004012fa <+27>:  lea    -0x1(%rax),%eax
   0x00000000004012fd <+30>:  cmp    $0x3e8,%eax
   0x0000000000401302 <+35>:  jbe    0x401309 <secret_phase+42>
   0x0000000000401304 <+37>:  callq  0x401669 <explode_bomb>
   0x0000000000401309 <+42>:  mov    %ebx,%esi
   0x000000000040130b <+44>:  mov    $0x604110,%edi
   0x0000000000401310 <+49>:  callq  0x4012a1 <fun7>
   0x0000000000401315 <+54>:  test   %eax,%eax
   0x0000000000401317 <+56>:  je     0x40131e <secret_phase+63>
   0x0000000000401319 <+58>:  callq  0x401669 <explode_bomb>
   0x000000000040131e <+63>:  mov    $0x4025f8,%edi
   0x0000000000401323 <+68>:  callq  0x400b40 <puts@plt>
   0x0000000000401328 <+73>:  callq  0x401807 <phase_defused>
   0x000000000040132d <+78>:  pop    %rbx
   0x000000000040132e <+79>:  retq

1
2
3
4
5
6
7
8
9
10
11
0x00000000004012df <+0>:   push   %rbx
0x00000000004012e0 <+1>:   callq  0x4016e1 <read_line>
0x00000000004012e5 <+6>:   mov    $0xa,%edx
0x00000000004012ea <+11>:  mov    $0x0,%esi
0x00000000004012ef <+16>:  mov    %rax,%rdi
0x00000000004012f2 <+19>:  callq  0x400c00 <strtol@plt>
0x00000000004012f7 <+24>:  mov    %rax,%rbx
0x00000000004012fa <+27>:  lea    -0x1(%rax),%eax
0x00000000004012fd <+30>:  cmp    $0x3e8,%eax
0x0000000000401302 <+35>:  jbe    0x401309 <secret_phase+42>
0x0000000000401304 <+37>:  callq  0x401669 <explode_bomb>

这几行要求我们输入一个数字,strtol函数是将我们输入的字符串转换为long int,我们输入的数大小需要在1~0x3e9之间,即1~1001


进入fun7:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Dump of assembler code for function fun7:
   0x00000000004012a1 <+0>:   sub    $0x8,%rsp
   0x00000000004012a5 <+4>:   test   %rdi,%rdi
   0x00000000004012a8 <+7>:   je     0x4012d5 <fun7+52>
   0x00000000004012aa <+9>:   mov    (%rdi),%edx
   0x00000000004012ac <+11>:  cmp    %esi,%edx
   0x00000000004012ae <+13>:  jle    0x4012bd <fun7+28>
   0x00000000004012b0 <+15>:  mov    0x8(%rdi),%rdi
   0x00000000004012b4 <+19>:  callq  0x4012a1 <fun7>
   0x00000000004012b9 <+24>:  add    %eax,%eax
   0x00000000004012bb <+26>:  jmp    0x4012da <fun7+57>
   0x00000000004012bd <+28>:  mov    $0x0,%eax
   0x00000000004012c2 <+33>:  cmp    %esi,%edx
   0x00000000004012c4 <+35>:  je     0x4012da <fun7+57>
   0x00000000004012c6 <+37>:  mov    0x10(%rdi),%rdi
   0x00000000004012ca <+41>:  callq  0x4012a1 <fun7>
   0x00000000004012cf <+46>:  lea    0x1(%rax,%rax,1),%eax
   0x00000000004012d3 <+50>:  jmp    0x4012da <fun7+57>
   0x00000000004012d5 <+52>:  mov    $0xffffffff,%eax
   0x00000000004012da <+57>:  add    $0x8,%rsp
   0x00000000004012de <+61>:  retq

secret_phase函数可以看出,我们需要让fun7的返回值为0,那么最简单的方法就是让程序跳转到fun7+28,然后跳转到fun7+57返回即 可,因此我们只需要让输入的值是(%rdi)上的数即可,打印一下发现是36,输入36,成功拆除炸弹!


至此,Bomb Lab满分零失误完成!!!

This post is licensed under CC BY 4.0 by the author.