Bomb Lab

Bomb Lab

exdoubled Lv3

前置工作

打开源码 bomb.c

注意到很长的

1
2
3
4
5
6
7
8
9
10
11
12
initialize_bomb();

printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
printf("which to blow yourself up. Have a nice day!\n");

/* Hmm... Six phases must be more secure than one phase! */
input = read_line(); /* Get input */
phase_1(input); /* Run the phase */
phase_defused(); /* Drat! They figured it out!
* Let me know how they did it. */
printf("Phase 1 defused. How about the next one?\n");
...

由此可以得知函数 phase_x 就是需要反汇编的函数,但是 phase_defused 是什么?

运行

1
2
gdb -q ./bomb
(gdb) disassemble 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
34
35
Dump of assembler code for function phase_defused:
0x00000000004015c4 <+0>: sub $0x78,%rsp
0x00000000004015c8 <+4>: mov %fs:0x28,%rax
0x00000000004015d1 <+13>: mov %rax,0x68(%rsp)
0x00000000004015d6 <+18>: xor %eax,%eax
0x00000000004015d8 <+20>: cmpl $0x6,0x202181(%rip) # 0x603760 <num_input_strings>
0x00000000004015df <+27>: jne 0x40163f <phase_defused+123>
0x00000000004015e1 <+29>: lea 0x10(%rsp),%r8
0x00000000004015e6 <+34>: lea 0xc(%rsp),%rcx
0x00000000004015eb <+39>: lea 0x8(%rsp),%rdx
0x00000000004015f0 <+44>: mov $0x402619,%esi
0x00000000004015f5 <+49>: mov $0x603870,%edi
0x00000000004015fa <+54>: call 0x400bf0 <__isoc99_sscanf@plt>
0x00000000004015ff <+59>: cmp $0x3,%eax
0x0000000000401602 <+62>: jne 0x401635 <phase_defused+113>
0x0000000000401604 <+64>: mov $0x402622,%esi
0x0000000000401609 <+69>: lea 0x10(%rsp),%rdi
0x000000000040160e <+74>: call 0x401338 <strings_not_equal>
0x0000000000401613 <+79>: test %eax,%eax
0x0000000000401615 <+81>: jne 0x401635 <phase_defused+113>
0x0000000000401617 <+83>: mov $0x4024f8,%edi
0x000000000040161c <+88>: call 0x400b10 <puts@plt>
0x0000000000401621 <+93>: mov $0x402520,%edi
0x0000000000401626 <+98>: call 0x400b10 <puts@plt>
0x000000000040162b <+103>: mov $0x0,%eax
0x0000000000401630 <+108>: call 0x401242 <secret_phase>
0x0000000000401635 <+113>: mov $0x402558,%edi
0x000000000040163a <+118>: call 0x400b10 <puts@plt>
0x000000000040163f <+123>: mov 0x68(%rsp),%rax
0x0000000000401644 <+128>: xor %fs:0x28,%rax
0x000000000040164d <+137>: je 0x401654 <phase_defused+144>
0x000000000040164f <+139>: call 0x400b30 <__stack_chk_fail@plt>
0x0000000000401654 <+144>: add $0x78,%rsp
0x0000000000401658 <+148>: ret
End of assembler dump.

给了注释,如果 num_input_strings==6 所以先决条件是输入的字符串条数为 6 也就是完成所有关卡,调用 sscanf ,如果返回值等于 3 ,判断输入偏移 0x10 (也就是输入的第三个)和 0x402622 处的字符串是否相等,然后 put 打印 0x4024f8 的字符串,查看此处比较的字符串

1
2
3
4
5
6
(gdb)  x/s 0x402622
0x402622: "DrEvil"
(gdb) x/s 0x4024f8
0x4024f8: "Curses, you've found the secret phase!"
(gdb) x/s 0x402520
0x402520: "But finding it and solving it are quite different..."

后面调用函数,发现了隐藏关卡叫做 secret_phase,后期再处理

既然每次输入后都要运行函数 phase_defused ,不妨把断点设在这里

1
2
(gdb) break phase_defused
Breakpoint 1 at 0x4015c4

以下均省略函数开辟栈空间和恢复栈空间和寄存器的分析过程

phase_1

1
(gdb) disassemble phase_1
1
2
3
4
5
6
7
8
9
10
Dump of assembler code for function phase_1:
0x0000000000400ee0 <+0>: sub $0x8,%rsp
0x0000000000400ee4 <+4>: mov $0x402400,%esi
0x0000000000400ee9 <+9>: call 0x401338 <strings_not_equal>
0x0000000000400eee <+14>: test %eax,%eax
0x0000000000400ef0 <+16>: je 0x400ef7 <phase_1+23>
0x0000000000400ef2 <+18>: call 0x40143a <explode_bomb>
0x0000000000400ef7 <+23>: add $0x8,%rsp
0x0000000000400efb <+27>: ret
End of assembler dump.

发现这就是一个简单地和 0x402400 处的字符串比较,查看储存的字符串

1
2
(gdb) x/s 0x402400
0x402400: "Border relations with Canada have never been better."

运行试试

1
2
3
4
5
(gdb) run
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Border relations with Canada have never been better.
Breakpoint 1, 0x00000000004015c4 in phase_defused ()

成功进入断点,说明第一关通过

这里没显示

1
Phase 1 defused. How about the next one?

是因为 phase_defused 运行在 printf

phase_2

1
(gdb) disassemble 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:
0x0000000000400efc <+0>: push %rbp
0x0000000000400efd <+1>: push %rbx
0x0000000000400efe <+2>: sub $0x28,%rsp
0x0000000000400f02 <+6>: mov %rsp,%rsi
0x0000000000400f05 <+9>: call 0x40145c <read_six_numbers>
0x0000000000400f0a <+14>: cmpl $0x1,(%rsp)
0x0000000000400f0e <+18>: je 0x400f30 <phase_2+52>
0x0000000000400f10 <+20>: call 0x40143a <explode_bomb>
0x0000000000400f15 <+25>: jmp 0x400f30 <phase_2+52>
0x0000000000400f17 <+27>: mov -0x4(%rbx),%eax
0x0000000000400f1a <+30>: add %eax,%eax
0x0000000000400f1c <+32>: cmp %eax,(%rbx)
0x0000000000400f1e <+34>: je 0x400f25 <phase_2+41>
0x0000000000400f20 <+36>: call 0x40143a <explode_bomb>
0x0000000000400f25 <+41>: add $0x4,%rbx
0x0000000000400f29 <+45>: cmp %rbp,%rbx
0x0000000000400f2c <+48>: jne 0x400f17 <phase_2+27>
0x0000000000400f2e <+50>: jmp 0x400f3c <phase_2+64>
0x0000000000400f30 <+52>: lea 0x4(%rsp),%rbx
0x0000000000400f35 <+57>: lea 0x18(%rsp),%rbp
0x0000000000400f3a <+62>: jmp 0x400f17 <phase_2+27>
0x0000000000400f3c <+64>: add $0x28,%rsp
0x0000000000400f40 <+68>: pop %rbx
0x0000000000400f41 <+69>: pop %rbp
0x0000000000400f42 <+70>: ret
End of assembler dump.

一眼看到读取 6 个数字,格式应该是什么?

1
(gdb) disassemble read_six_numbers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Dump of assembler code for function read_six_numbers:
0x000000000040145c <+0>: sub $0x18,%rsp
0x0000000000401460 <+4>: mov %rsi,%rdx
0x0000000000401463 <+7>: lea 0x4(%rsi),%rcx
0x0000000000401467 <+11>: lea 0x14(%rsi),%rax
0x000000000040146b <+15>: mov %rax,0x8(%rsp)
0x0000000000401470 <+20>: lea 0x10(%rsi),%rax
0x0000000000401474 <+24>: mov %rax,(%rsp)
0x0000000000401478 <+28>: lea 0xc(%rsi),%r9
0x000000000040147c <+32>: lea 0x8(%rsi),%r8
0x0000000000401480 <+36>: mov $0x4025c3,%esi
0x0000000000401485 <+41>: mov $0x0,%eax
0x000000000040148a <+46>: call 0x400bf0 <__isoc99_sscanf@plt>
0x000000000040148f <+51>: cmp $0x5,%eax
0x0000000000401492 <+54>: jg 0x401499 <read_six_numbers+61>
0x0000000000401494 <+56>: call 0x40143a <explode_bomb>
0x0000000000401499 <+61>: add $0x18,%rsp
0x000000000040149d <+65>: ret
End of assembler dump.

这里让 sscanf 的返回值和 5 比较,读入数据个数小于等于就跳到 0x40143a <explode_bomb>,所以读取的格式是输入的六个数字中间用空格隔开

回到原来题目,读取的结果其实是把六个整数读取到栈上从 (%rsp) 开始,其实也就是栈上数组的表现形式,第 0 个值不等于 1 就 bomb,然后就进循环(+27 到 +48 部分)了,%rbx 是用来依次遍历数组的,%eax%rbx 对应的上一个数据,有一个两倍的比较,翻译一下

1
2
3
4
5
6
7
8
9
void phase_2() {
int rsp[6];
read_six_numbers(rsp);
if (arr[0] != 1) explode_bomb();
for (int i = 1; i < 6; i++) {
if (rsp[i] != 2 * rsp[i - 1])
explode_bomb();
}
}

所以可以设计

1
1 2 4 8 16 32
1
2
3
4
5
(gdb) c
Continuing.
Phase 1 defused. How about the next one?
1 2 4 8 16 32
Breakpoint 1, 0x00000000004015c4 in phase_defused ()

又进断点了,说明正确

phase_3

1
(gdb) disassemble 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
Dump of assembler code for function phase_3:
0x0000000000400f43 <+0>: sub $0x18,%rsp
0x0000000000400f47 <+4>: lea 0xc(%rsp),%rcx
0x0000000000400f4c <+9>: lea 0x8(%rsp),%rdx
0x0000000000400f51 <+14>: mov $0x4025cf,%esi
0x0000000000400f56 <+19>: mov $0x0,%eax
0x0000000000400f5b <+24>: call 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000400f60 <+29>: cmp $0x1,%eax
0x0000000000400f63 <+32>: jg 0x400f6a <phase_3+39>
0x0000000000400f65 <+34>: call 0x40143a <explode_bomb>
0x0000000000400f6a <+39>: cmpl $0x7,0x8(%rsp)
0x0000000000400f6f <+44>: ja 0x400fad <phase_3+106>
0x0000000000400f71 <+46>: mov 0x8(%rsp),%eax
0x0000000000400f75 <+50>: jmp *0x402470(,%rax,8)
0x0000000000400f7c <+57>: mov $0xcf,%eax
0x0000000000400f81 <+62>: jmp 0x400fbe <phase_3+123>
0x0000000000400f83 <+64>: mov $0x2c3,%eax
0x0000000000400f88 <+69>: jmp 0x400fbe <phase_3+123>
0x0000000000400f8a <+71>: mov $0x100,%eax
0x0000000000400f8f <+76>: jmp 0x400fbe <phase_3+123>
0x0000000000400f91 <+78>: mov $0x185,%eax
0x0000000000400f96 <+83>: jmp 0x400fbe <phase_3+123>
0x0000000000400f98 <+85>: mov $0xce,%eax
0x0000000000400f9d <+90>: jmp 0x400fbe <phase_3+123>
0x0000000000400f9f <+92>: mov $0x2aa,%eax
0x0000000000400fa4 <+97>: jmp 0x400fbe <phase_3+123>
0x0000000000400fa6 <+99>: mov $0x147,%eax
0x0000000000400fab <+104>: jmp 0x400fbe <phase_3+123>
0x0000000000400fad <+106>: call 0x40143a <explode_bomb>
0x0000000000400fb2 <+111>: mov $0x0,%eax
0x0000000000400fb7 <+116>: jmp 0x400fbe <phase_3+123>
0x0000000000400fb9 <+118>: mov $0x137,%eax
0x0000000000400fbe <+123>: cmp 0xc(%rsp),%eax
0x0000000000400fc2 <+127>: je 0x400fc9 <phase_3+134>
0x0000000000400fc4 <+129>: call 0x40143a <explode_bomb>
0x0000000000400fc9 <+134>: add $0x18,%rsp
0x0000000000400fcd <+138>: ret
End of assembler dump.

突然有点长,找到 sscanf,这里读取两个整数,否则 bomb,第一个在 0x8(%rsp),另一个在 0xc(%rsp),扫一眼后面,一堆 jmpmov ,注意到

1
jmp    *0x402470(,%rax,8)

说明这是一个跳转表实现的switch,由

1
cmpl   $0x7,0x8(%rsp)

得知一共有 case0~7种

switch0x8(%rsp) 跳转,然后比较 0xc(%rsp)和跳转结果,相等就成功,翻译一下这里的 switch

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
// a 表示 0x8(%rsp)
// b 表示 0xc(%rsp)
switch(a){
case 0:
result = 207LL;
break;
case 1:
result = 311LL;
break;
case 2:
result = 707LL;
break;
case 3:
result = 256LL;
break;
case 4:
result = 389LL;
break;
case 5:
result = 206LL;
break;
case 6:
result = 682LL;
break;
case 7:
result = 327LL;
break;
default:
explode_bomb(); // 这里没有分析是否传参
}
if(b != result) explode_bomb(); // 这里没有分析是否传参

那这里其实就有 8 种解,随便尝试一种

1
0 207
1
2
3
4
5
(gdb) c
Continuing.
That's number 2. Keep going!
0 207
Breakpoint 1, 0x00000000004015c4 in phase_defused ()

进断点了,说明成功

phase_4

1
(gdb) disassemble phase_4
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 phase_4:
0x000000000040100c <+0>: sub $0x18,%rsp
0x0000000000401010 <+4>: lea 0xc(%rsp),%rcx
0x0000000000401015 <+9>: lea 0x8(%rsp),%rdx
0x000000000040101a <+14>: mov $0x4025cf,%esi
0x000000000040101f <+19>: mov $0x0,%eax
0x0000000000401024 <+24>: call 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000401029 <+29>: cmp $0x2,%eax
0x000000000040102c <+32>: jne 0x401035 <phase_4+41>
0x000000000040102e <+34>: cmpl $0xe,0x8(%rsp)
0x0000000000401033 <+39>: jbe 0x40103a <phase_4+46>
0x0000000000401035 <+41>: call 0x40143a <explode_bomb>
0x000000000040103a <+46>: mov $0xe,%edx
0x000000000040103f <+51>: mov $0x0,%esi
0x0000000000401044 <+56>: mov 0x8(%rsp),%edi
0x0000000000401048 <+60>: call 0x400fce <func4>
0x000000000040104d <+65>: test %eax,%eax
0x000000000040104f <+67>: jne 0x401058 <phase_4+76>
0x0000000000401051 <+69>: cmpl $0x0,0xc(%rsp)
0x0000000000401056 <+74>: je 0x40105d <phase_4+81>
0x0000000000401058 <+76>: call 0x40143a <explode_bomb>
0x000000000040105d <+81>: add $0x18,%rsp
0x0000000000401061 <+85>: ret
End of assembler dump.

一样,读取两个整数,第一个在 0x8(%rsp),另一个在 0xc(%rsp),要求 0x8(%rsp) 的值大于 14

然后调用了 func4,传参

1
2
3
mov    $0xe,%edx # 14
mov $0x0,%esi # 0
mov 0x8(%rsp),%edi

func4(a,0,14)

后面判断结果和 0xc(%rsp)都是 0

1
(gdb) disassemble 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:
0x0000000000400fce <+0>: sub $0x8,%rsp
0x0000000000400fd2 <+4>: mov %edx,%eax
0x0000000000400fd4 <+6>: sub %esi,%eax
0x0000000000400fd6 <+8>: mov %eax,%ecx
0x0000000000400fd8 <+10>: shr $0x1f,%ecx
0x0000000000400fdb <+13>: add %ecx,%eax
0x0000000000400fdd <+15>: sar $1,%eax
0x0000000000400fdf <+17>: lea (%rax,%rsi,1),%ecx
0x0000000000400fe2 <+20>: cmp %edi,%ecx
0x0000000000400fe4 <+22>: jle 0x400ff2 <func4+36>
0x0000000000400fe6 <+24>: lea -0x1(%rcx),%edx
0x0000000000400fe9 <+27>: call 0x400fce <func4>
0x0000000000400fee <+32>: add %eax,%eax
0x0000000000400ff0 <+34>: jmp 0x401007 <func4+57>
0x0000000000400ff2 <+36>: mov $0x0,%eax
0x0000000000400ff7 <+41>: cmp %edi,%ecx
0x0000000000400ff9 <+43>: jge 0x401007 <func4+57>
0x0000000000400ffb <+45>: lea 0x1(%rcx),%esi
0x0000000000400ffe <+48>: call 0x400fce <func4>
0x0000000000401003 <+53>: lea 0x1(%rax,%rax,1),%eax
0x0000000000401007 <+57>: add $0x8,%rsp
0x000000000040100b <+61>: ret
End of assembler dump.

这是一个递归

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
int func4(int a, int b, int c){
// a -> %edi
// b -> %esi
// c -> %edx
/*
int res; // %eax
int temp = c - b; // %ecx
int flag = (unsigned)temp >> 31;
temp += flag;
temp = (temp >> 1) + b;
*/
/*
这里的 +8 到 +15 有点奇怪,其实是在防溢出,b+c 可能会溢出,所以用 (c-b) / 2 + b
但是 c-b 可能是负数,直接右移会出现向下取整的偏差,所以会出现那种操作
所以等效于下面这个
*/
int res; // %eax
int temp = (b+c) / 2; // %ecx

if(temp > a){
res = 2 * func4(a, b, temp-1) + 1;
return res;
}
res = 0;
if(temp < a){
res = 2 * func4(a, temp+1, c);
return res;
}
return res;
}

简单分析知道,第一次传入 func4(a,0,14) 时,直接让 a=7 就返回 0

构造答案

1
7 0
1
2
3
4
5
(gdb) c
Continuing.
Halfway there!
7 0
Breakpoint 1, 0x00000000004015c4 in phase_defused ()

进断点了,答案正确

phase_5

1
(gdb) disassemble 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
31
32
33
34
35
36
37
38
39
Dump of assembler code for function phase_5:
0x0000000000401062 <+0>: push %rbx
0x0000000000401063 <+1>: sub $0x20,%rsp
0x0000000000401067 <+5>: mov %rdi,%rbx
0x000000000040106a <+8>: mov %fs:0x28,%rax
0x0000000000401073 <+17>: mov %rax,0x18(%rsp)
0x0000000000401078 <+22>: xor %eax,%eax
0x000000000040107a <+24>: call 0x40131b <string_length>
0x000000000040107f <+29>: cmp $0x6,%eax
0x0000000000401082 <+32>: je 0x4010d2 <phase_5+112>
0x0000000000401084 <+34>: call 0x40143a <explode_bomb>
0x0000000000401089 <+39>: jmp 0x4010d2 <phase_5+112>
0x000000000040108b <+41>: movzbl (%rbx,%rax,1),%ecx
0x000000000040108f <+45>: mov %cl,(%rsp)
0x0000000000401092 <+48>: mov (%rsp),%rdx
0x0000000000401096 <+52>: and $0xf,%edx
0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx
0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1)
0x00000000004010a4 <+66>: add $0x1,%rax
0x00000000004010a8 <+70>: cmp $0x6,%rax
0x00000000004010ac <+74>: jne 0x40108b <phase_5+41>
0x00000000004010ae <+76>: movb $0x0,0x16(%rsp)
0x00000000004010b3 <+81>: mov $0x40245e,%esi
0x00000000004010b8 <+86>: lea 0x10(%rsp),%rdi
0x00000000004010bd <+91>: call 0x401338 <strings_not_equal>
0x00000000004010c2 <+96>: test %eax,%eax
0x00000000004010c4 <+98>: je 0x4010d9 <phase_5+119>
0x00000000004010c6 <+100>: call 0x40143a <explode_bomb>
0x00000000004010cb <+105>: nopl 0x0(%rax,%rax,1)
0x00000000004010d0 <+110>: jmp 0x4010d9 <phase_5+119>
0x00000000004010d2 <+112>: mov $0x0,%eax
0x00000000004010d7 <+117>: jmp 0x40108b <phase_5+41>
0x00000000004010d9 <+119>: mov 0x18(%rsp),%rax
0x00000000004010de <+124>: xor %fs:0x28,%rax
0x00000000004010e7 <+133>: je 0x4010ee <phase_5+140>
0x00000000004010e9 <+135>: call 0x400b30 <__stack_chk_fail@plt>
0x00000000004010ee <+140>: add $0x20,%rsp
0x00000000004010f2 <+144>: pop %rbx
0x00000000004010f3 <+145>: ret

调用了 string_length 那肯定是输入一个长度为 6 的字符串了,紧接着一个循环,做了一系列操作之后,把最后置 \0 (和下面的字符串比较函数有关),和内存 0x40245e 处字符串进行了比较

1
2
(gdb) x/s 0x40245e
0x40245e: "flyers"

翻译一下循环(+41 到 +74)

1
2
3
4
5
6
7
// s -> 0x4024b0
for(int rax = 0; rax != 6; ++rax){
char rcx = rbx[rax];
int rdx = rcx & 0xf;
rsp[rax] = s[rdx];
}
rsp[0] = '\0';

也就是取输入([%rbx])的阿斯克码的后 4 位,再取到字符串 0x40245e 的对应位

字符串看一下

1
2
(gdb) x/s 0x4024b0
0x4024b0 <array.3449>: "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"

查一下阿斯克码表

二进制十进制十六进制图形
0110 00009761a
0110 00109862b
0110 00119963c
0110 010010064d
0110 010110165e
0110 011010266f
0110 011110367g
0110 100010468h
0110 100110569i
0110 10101066Aj
0110 10111076Bk
0110 11001086Cl
0110 11011096Dm
0110 11101106En
0110 11111116Fo
0111 000011270p
0111 000111371q
0111 001011472r
0111 001111573s
0111 010011674t
0111 010111775u
0111 011011876v
0111 011111977w
0111 100012078x
0111 100112179y
0111 10101217Az

第一个要 f[%rdx] 要是 9,对应二进制 1001,第一个字符可以是 iy

第二个要 l[%rdx] 要是 15,对应二进制 1111,第二个字符可以是 o

第三个要 y[%rdx] 要是 14,对应二进制 1110,第三个字符可以是 n

第四个要 e[%rdx] 要是 5,对应二进制 0101,第四个字符可以是 eu

第五个要 r[%rdx] 要是 6,对应二进制 0110,第五个字符可以是 fv

第六个要 s[%rdx] 要是 7,对应二进制 0111,第六个字符可以是 gw

那么构造答案自然就是(当然其他也可以)

当然不是小写字母也可以

1
ionefg
1
2
3
4
5
(gdb) c
Continuing.
So you got that one. Try this one.
ionefg
Breakpoint 1, 0x00000000004015c4 in phase_defused ()

进入断点,说明正确

phase_6

1
(gdb) disassemble 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
88
Dump of assembler code for function phase_6:
0x00000000004010f4 <+0>: push %r14
0x00000000004010f6 <+2>: push %r13
0x00000000004010f8 <+4>: push %r12
0x00000000004010fa <+6>: push %rbp
0x00000000004010fb <+7>: push %rbx
0x00000000004010fc <+8>: sub $0x50,%rsp
0x0000000000401100 <+12>: mov %rsp,%r13
0x0000000000401103 <+15>: mov %rsp,%rsi
0x0000000000401106 <+18>: call 0x40145c <read_six_numbers>
0x000000000040110b <+23>: mov %rsp,%r14
0x000000000040110e <+26>: mov $0x0,%r12d
0x0000000000401114 <+32>: mov %r13,%rbp
0x0000000000401117 <+35>: mov 0x0(%r13),%eax
0x000000000040111b <+39>: sub $0x1,%eax
0x000000000040111e <+42>: cmp $0x5,%eax
0x0000000000401121 <+45>: jbe 0x401128 <phase_6+52>
0x0000000000401123 <+47>: call 0x40143a <explode_bomb>
0x0000000000401128 <+52>: add $0x1,%r12d
0x000000000040112c <+56>: cmp $0x6,%r12d
0x0000000000401130 <+60>: je 0x401153 <phase_6+95>
0x0000000000401132 <+62>: mov %r12d,%ebx
0x0000000000401135 <+65>: movslq %ebx,%rax
0x0000000000401138 <+68>: mov (%rsp,%rax,4),%eax
0x000000000040113b <+71>: cmp %eax,0x0(%rbp)
0x000000000040113e <+74>: jne 0x401145 <phase_6+81>
0x0000000000401140 <+76>: call 0x40143a <explode_bomb>
0x0000000000401145 <+81>: add $0x1,%ebx
0x0000000000401148 <+84>: cmp $0x5,%ebx
0x000000000040114b <+87>: jle 0x401135 <phase_6+65>
0x000000000040114d <+89>: add $0x4,%r13
0x0000000000401151 <+93>: jmp 0x401114 <phase_6+32>
0x0000000000401153 <+95>: lea 0x18(%rsp),%rsi
0x0000000000401158 <+100>: mov %r14,%rax
0x000000000040115b <+103>: mov $0x7,%ecx
0x0000000000401160 <+108>: mov %ecx,%edx
0x0000000000401162 <+110>: sub (%rax),%edx
0x0000000000401164 <+112>: mov %edx,(%rax)
0x0000000000401166 <+114>: add $0x4,%rax
0x000000000040116a <+118>: cmp %rsi,%rax
0x000000000040116d <+121>: jne 0x401160 <phase_6+108>
0x000000000040116f <+123>: mov $0x0,%esi
0x0000000000401174 <+128>: jmp 0x401197 <phase_6+163>
0x0000000000401176 <+130>: mov 0x8(%rdx),%rdx
0x000000000040117a <+134>: add $0x1,%eax
0x000000000040117d <+137>: cmp %ecx,%eax
0x000000000040117f <+139>: jne 0x401176 <phase_6+130>
0x0000000000401181 <+141>: jmp 0x401188 <phase_6+148>
0x0000000000401183 <+143>: mov $0x6032d0,%edx
0x0000000000401188 <+148>: mov %rdx,0x20(%rsp,%rsi,2)
0x000000000040118d <+153>: add $0x4,%rsi
0x0000000000401191 <+157>: cmp $0x18,%rsi
0x0000000000401195 <+161>: je 0x4011ab <phase_6+183>
0x0000000000401197 <+163>: mov (%rsp,%rsi,1),%ecx
0x000000000040119a <+166>: cmp $0x1,%ecx
0x000000000040119d <+169>: jle 0x401183 <phase_6+143>
0x000000000040119f <+171>: mov $0x1,%eax
0x00000000004011a4 <+176>: mov $0x6032d0,%edx
0x00000000004011a9 <+181>: jmp 0x401176 <phase_6+130>
0x00000000004011ab <+183>: mov 0x20(%rsp),%rbx
0x00000000004011b0 <+188>: lea 0x28(%rsp),%rax
0x00000000004011b5 <+193>: lea 0x50(%rsp),%rsi
0x00000000004011ba <+198>: mov %rbx,%rcx
0x00000000004011bd <+201>: mov (%rax),%rdx
0x00000000004011c0 <+204>: mov %rdx,0x8(%rcx)
0x00000000004011c4 <+208>: add $0x8,%rax
0x00000000004011c8 <+212>: cmp %rsi,%rax
0x00000000004011cb <+215>: je 0x4011d2 <phase_6+222>
0x00000000004011cd <+217>: mov %rdx,%rcx
0x00000000004011d0 <+220>: jmp 0x4011bd <phase_6+201>
0x00000000004011d2 <+222>: movq $0x0,0x8(%rdx)
0x00000000004011da <+230>: mov $0x5,%ebp
0x00000000004011df <+235>: mov 0x8(%rbx),%rax
0x00000000004011e3 <+239>: mov (%rax),%eax
0x00000000004011e5 <+241>: cmp %eax,(%rbx)
0x00000000004011e7 <+243>: jge 0x4011ee <phase_6+250>
0x00000000004011e9 <+245>: call 0x40143a <explode_bomb>
0x00000000004011ee <+250>: mov 0x8(%rbx),%rbx
0x00000000004011f2 <+254>: sub $0x1,%ebp
0x00000000004011f5 <+257>: jne 0x4011df <phase_6+235>
0x00000000004011f7 <+259>: add $0x50,%rsp
0x00000000004011fb <+263>: pop %rbx
0x00000000004011fc <+264>: pop %rbp
0x00000000004011fd <+265>: pop %r12
0x00000000004011ff <+267>: pop %r13
0x0000000000401201 <+269>: pop %r14
0x0000000000401203 <+271>: ret
End of assembler dump.

有点长,类似之前关卡,读了六个数字,仔细阅读发现就是几个循环,一个一个拆解即可

六个数据读取到 %rsp,并保存到 %r14

part1

(+26 ~ +93)

可以看出这是一个嵌套循环,%r12 来控制外层循环的计数,比较 [0(%r13)]-15 的大小,大于就bomb ,也就是限定了输入的数在 1~6 之间,然后又有一个循环,这里将已经自增的 %r12 赋值给 %rbx%rbx 用来做计数,这一循环比较了之后的元素和该元素是否相等,相等就 bomb

1
2
3
4
5
6
7
8
9
10
for (int i = 0; i < 6; ++i) {
if (arr[i] < 1 || arr[i] > 6) {
explode_bomb();
}
for (int j = i + 1; j < 6; ++j) {
if (arr[i] == arr[j]) {
explode_bomb();
}
}
}

part2

(+95 ~ +121)

这里是让每个值都变为 7 减去这个值

1
for(int i = 0; i < 6; ++i) arr[i] = 7 - arr[i];

part3

(+123 ~ +183)

注意到这一段

1
0x8(%rdx),%rdx

这是链表非常明显的特征,这个部分也是一个循环,找一下计数器

1
2
3
4
mov    $0x0,%esi
...
add $0x4,%rsi
cmp $0x18,%rsi

发现是 %rsi,随后发现一个 0x20(%rsp,%rsi,2),几乎百分百确认这段就是把数字映射到对应链表节点了

节点指针保存在了 0x20(%rsp)

同时我们可以推测节点长这样

1
2
3
4
5
struct Node{
int val;
int padding;
Node* next;
}

上面有储存的内存,看一眼

1
2
3
4
5
6
7
(gdb) x/100wx 0x6032d0
0x6032d0 <node1>: 0x0000014c 0x00000001 0x006032e0 0x00000000
0x6032e0 <node2>: 0x000000a8 0x00000002 0x006032f0 0x00000000
0x6032f0 <node3>: 0x0000039c 0x00000003 0x00603300 0x00000000
0x603300 <node4>: 0x000002b3 0x00000004 0x00603310 0x00000000
0x603310 <node5>: 0x000001dd 0x00000005 0x00603320 0x00000000
0x603320 <node6>: 0x000001bb 0x00000006 0x00000000 0x00000000

从内存印证了猜想是对的

并且题目还贴心的在定义静态 Node 的时候帮我们把序号标好了

节点储存的十六进制值对应的十进制值
114c332
20a8168
339c924
42b3691
51dd477
61bb413

(+183 ~ +222)

1
2
3
mov    %rbx,%rcx
mov (%rax),%rdx
mov %rdx,0x8(%rcx)

这三个很明显看出是在做 Node[i].next=Node[i+1]

1
movq   $0x0,0x8(%rdx)

而这个是把最后一个指针设为 NULL

part4

(+230 ~ +257)

1
2
3
mov    $0x5,%ebp
...
jne 0x4011df <phase_6+235>

这里准备了计数器 %ebp,赋值 5 是为了遍历 5 次

1
2
cmp    %eax,(%rbx)
jge 0x4011ee <phase_6+250>

这里可以看出这一段是检查结点是不是递减的

从下往上推导,最后储存要是递减的,排序应该是

1
3 4 5 6 1 2

再被 7 减一下

1
4 3 2 1 6 5
1
2
3
4
5
(gdb) c
Continuing.
Good work! On to the next...
4 3 2 1 6 5
Breakpoint 1, 0x00000000004015c4 in phase_defused ()

进入断点,通关

1
2
3
(gdb) c
Continuing.
Congratulations! You've defused the bomb!

secret_phase

还记得 前置工作 进行的分析,有隐藏关卡

怎么进入呢,注意到

1
2
mov    $0x402619,%esi
mov $0x603870,%edi
1
2
3
4
(gdb) print(char*) 0x402619
$1 = 0x402619 "%d %d %s"
(gdb) print(char*) 0x603870
$2 = 0x603870 <input_strings+240> ""

读取的是输入后偏移 240 字节的位置,这个其实是 phase 4 输入的位置!

所以只需要在 phase 4 答案后面加上 DrEvil 就可以在通关六个关卡后进入隐藏关

856X470/bomb_lab_1.png

1
(gdb) disassemble secret_phase
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 secret_phase:
0x0000000000401242 <+0>: push %rbx
0x0000000000401243 <+1>: call 0x40149e <read_line>
0x0000000000401248 <+6>: mov $0xa,%edx
0x000000000040124d <+11>: mov $0x0,%esi
0x0000000000401252 <+16>: mov %rax,%rdi
0x0000000000401255 <+19>: call 0x400bd0 <strtol@plt>
0x000000000040125a <+24>: mov %rax,%rbx
0x000000000040125d <+27>: lea -0x1(%rax),%eax
0x0000000000401260 <+30>: cmp $0x3e8,%eax
0x0000000000401265 <+35>: jbe 0x40126c <secret_phase+42>
0x0000000000401267 <+37>: call 0x40143a <explode_bomb>
0x000000000040126c <+42>: mov %ebx,%esi
0x000000000040126e <+44>: mov $0x6030f0,%edi
0x0000000000401273 <+49>: call 0x401204 <fun7>
0x0000000000401278 <+54>: cmp $0x2,%eax
0x000000000040127b <+57>: je 0x401282 <secret_phase+64>
0x000000000040127d <+59>: call 0x40143a <explode_bomb>
0x0000000000401282 <+64>: mov $0x402438,%edi
0x0000000000401287 <+69>: call 0x400b10 <puts@plt>
0x000000000040128c <+74>: call 0x4015c4 <phase_defused>
0x0000000000401291 <+79>: pop %rbx
0x0000000000401292 <+80>: ret
End of assembler dump.

这里调用了一个函数 strtol 它是把字符串转化为长整型,并返回该值

1
long int strtol(const char *nptr, char **endptr, int base);

这里是把输入转化为 10 进制,并且输入要小于 1000

调用函数 fun7(n, input) ,并且返回值应该是 2

1
(gdb) disassemble fun7
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 fun7:
0x0000000000401204 <+0>: sub $0x8,%rsp
0x0000000000401208 <+4>: test %rdi,%rdi
0x000000000040120b <+7>: je 0x401238 <fun7+52>
0x000000000040120d <+9>: mov (%rdi),%edx
0x000000000040120f <+11>: cmp %esi,%edx
0x0000000000401211 <+13>: jle 0x401220 <fun7+28>
0x0000000000401213 <+15>: mov 0x8(%rdi),%rdi
0x0000000000401217 <+19>: call 0x401204 <fun7>
0x000000000040121c <+24>: add %eax,%eax
0x000000000040121e <+26>: jmp 0x40123d <fun7+57>
0x0000000000401220 <+28>: mov $0x0,%eax
0x0000000000401225 <+33>: cmp %esi,%edx
0x0000000000401227 <+35>: je 0x40123d <fun7+57>
0x0000000000401229 <+37>: mov 0x10(%rdi),%rdi
0x000000000040122d <+41>: call 0x401204 <fun7>
0x0000000000401232 <+46>: lea 0x1(%rax,%rax,1),%eax
0x0000000000401236 <+50>: jmp 0x40123d <fun7+57>
0x0000000000401238 <+52>: mov $0xffffffff,%eax
0x000000000040123d <+57>: add $0x8,%rsp
0x0000000000401241 <+61>: ret
End of assembler dump.

这里 传入了一个 0x6030f0,看一下

1
(gdb) x/200wx 0x6030f0
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
0x6030f0 <n1>:  		0x00000024      0x00000000      0x00603110      0x00000000
0x603100 <n1+16>: 0x00603130 0x00000000 0x00000000 0x00000000
0x603110 <n21>: 0x00000008 0x00000000 0x00603190 0x00000000
0x603120 <n21+16>: 0x00603150 0x00000000 0x00000000 0x00000000
0x603130 <n22>: 0x00000032 0x00000000 0x00603170 0x00000000
0x603140 <n22+16>: 0x006031b0 0x00000000 0x00000000 0x00000000
0x603150 <n32>: 0x00000016 0x00000000 0x00603270 0x00000000
0x603160 <n32+16>: 0x00603230 0x00000000 0x00000000 0x00000000
0x603170 <n33>: 0x0000002d 0x00000000 0x006031d0 0x00000000
0x603180 <n33+16>: 0x00603290 0x00000000 0x00000000 0x00000000
0x603190 <n31>: 0x00000006 0x00000000 0x006031f0 0x00000000
0x6031a0 <n31+16>: 0x00603250 0x00000000 0x00000000 0x00000000
0x6031b0 <n34>: 0x0000006b 0x00000000 0x00603210 0x00000000
0x6031c0 <n34+16>: 0x006032b0 0x00000000 0x00000000 0x00000000
0x6031d0 <n45>: 0x00000028 0x00000000 0x00000000 0x00000000
0x6031e0 <n45+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x6031f0 <n41>: 0x00000001 0x00000000 0x00000000 0x00000000
0x603200 <n41+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x603210 <n47>: 0x00000063 0x00000000 0x00000000 0x00000000
0x603220 <n47+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x603230 <n44>: 0x00000023 0x00000000 0x00000000 0x00000000
0x603240 <n44+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x603250 <n42>: 0x00000007 0x00000000 0x00000000 0x00000000
0x603260 <n42+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x603270 <n43>: 0x00000014 0x00000000 0x00000000 0x00000000
0x603280 <n43+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x603290 <n46>: 0x0000002f 0x00000000 0x00000000 0x00000000
0x6032a0 <n46+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x6032b0 <n48>: 0x000003e9 0x00000000 0x00000000 0x00000000
0x6032c0 <n48+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x6032d0 <node1>: 0x0000014c 0x00000001 0x006032e0 0x00000000
0x6032e0 <node2>: 0x000000a8 0x00000002 0x006032f0 0x00000000
0x6032f0 <node3>: 0x0000039c 0x00000003 0x00603300 0x00000000
0x603300 <node4>: 0x000002b3 0x00000004 0x00603310 0x00000000
0x603310 <node5>: 0x000001dd 0x00000005 0x00603320 0x00000000
0x603320 <node6>: 0x000001bb 0x00000006 0x00000000 0x00000000

一眼二叉树,那么fun7 传入了我们输入的值和一个根节点,而且这个函数是类似之前的递归,翻译一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int fun7(node *p,int input){
int res;
if(p == NULL){
res = -1;
}
if(p->val > input){
res = fun7(p->left,input) * 2;
return res;
}
result = 0;
if(p->val < input){
result = fun7(p->right,input) * 2 + 1 ;
return res
}
return result;
}

914X507/bomb_lab_tree.png

这其实是一个二叉搜索树,要使得最后返回值是 2,首先要能找到,然后沿左边 return 1 ,再沿着右边 return 2,根据画出的树,可以知道 2022 都是解

1
2
3
4
5
Curses, you've found the secret phase!
But finding it and solving it are quite different...
20
Wow! You've defused the secret stage!
Congratulations! You've defused the bomb!

隐藏关其实可以在 gdb 调试的时候,运行命令

1
(gdb) jump secret_phase

直接跳转,效果如下:

858X513/bomb_lab_2.png

成功截图

897X310/bomb_lab_ans.png

AI-IDA-MCP

IDA 可以偷懒,很快做完,那么 AI 呢

下面在 VSCode 中的插件 Cline 配置 MCP 调用 IDA 自动解题

提示词

我在ida里面打开了bomblab,请你在不让炸弹爆炸的情况下给出所有正确的输入,一共有6个关卡,此外还有一个隐藏关,把所有输出保存在一个ansAI.txt文件中,换行符采用Linux风格,并且把你的逆向思路保存在wpAI.md文件中

Deepseek-r1

1
2
3
4
5
6
7
8
Border relations with Canada have never been better.
1 2 4 8 16 32
0 207
7 0
8NMDEF
2 4 3 1 5 6
22

878X251/dsr1.png

点击这里下载wp

Gemini 2.5 flash

1
2
3
4
5
6
7
8
Border relations with Canada have never been better.
1 2 4 8 16 32
0 207
7 0
8>=456
4 3 2 1 6 5
22

859X250/gemini2.5flash.png

点击这里下载wp

Cline 默认

1
2
3
4
5
6
7
8
Border relations with Canada have never been better.
1 2 4 8 16 32
0 207
7 0
ionefg
4 3 2 1 6 5
20

874X228/cline.png

点击这里下载wp

接下来用 Cline 调用 Github Copilot 中的模型

Github Copilot ChatGPT5

1
2
3
4
5
6
7
8
Border relations with Canada have never been better.
1 2 4 8 16 32
4 389
7 0
9?>567
4 3 2 1 6 5
22

849X226/ChatGPT5.png

点击这里下载wp

Github Copilot Gemini2.5pro

1
2
3
4
5
6
7
8
Border relations with Canada have never been better.
1 2 4 8 16 32
2 707
7 0
ionadg
4 3 2 1 6 5
22

866X248/gemini2.5pro.png

点击这里下载wp

  • Title: Bomb Lab
  • Author: exdoubled
  • Created at : 2025-10-22 22:00:00
  • Updated at : 2025-10-26 10:26:30
  • Link: https://github.com/exdoubled/exdoubled.github.io.git/CSAPP/Bomb Lab/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments