CSAPP - Bomb Lab
本答案仅供参考,每份bomblab的数据是独一无二的,但思路大致相同
Tips
!!!每次gdb中运行run命令之前一定要在explode_bomb函数前打上断点 !!!
在通过几个关卡之后可以把答案存在
ans.txt
中并用gdb的设置参数功能省去每次输入的麻烦:
1
(gdb) set args ans.txt
- gdb不需要重新运行退出重进,需要退出时用
kill
中止运行,此时断点信息依然保留,重新run
即可 - gdb支持重复运行上一条指令,例如多次运行
stepi
、nexti
,只需要在输入一遍后连续按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,成功拆除炸弹!