前言

又是一个刚上手差点放弃的lab,一边看视频一边看书一边找资料,初窥门径知道gdb怎么用和汇编怎么看后就开始自己拆弹了

参考资料:

gdb命令:Enscript Output (cmu.edu)

windows和linux读取换行符的转换问题:windows和linux下读取文件换行符的一个坑——\r\n和\n_linux unknown \r\n-CSDN博客

bomblab全攻略:【深入理解计算机系统 实验2 CSAPP】bomb lab 炸弹实验 CMU bomblab

实验过程

0. 实验热身

0.1 gdb命令

先圈一下一些重要的命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# gdb,启动!
root@Andrew:/mnt/d/.c/csapp/bomb# gdb bomb

# 打印栈信息
(gdb) info stack

# 打印寄存器的值
(gdb) print $rax
(gdb) p $rax
(gdb) p /x $rip # 输出当前指令地址

# 反汇编某个函数
(gdb) disas funcName

# 将输出信息保存到文件中(很好用,方便在文件看汇编
(gdb) set logging file fileName
(gdb) set logging on
# 反汇编某个函数
(gdb) set logging off

# 调试
(gdb) break funcName #设置断点
(gdb) run
(gdb) layout asm # 显示汇编代码 ctrl+x+a退出

0.2 常用寄存器

register1.png
register2.png

还有一个$rip保存指令地址

0.3 保存答案

bomb.c允许我们传入文件,文件包含已经破解的炸弹的密码,在调试后面的炸弹时就不需要反复输入了:

1
(gdb) run ans.txt

注意!!!如果是在wsl中访问windows的文件,会出现换行符的转换问题,导致程序读到的字符串比我们写的多出一个\r,解决方案如下:

用vim打开ans.txt

1
root@Andrew:/mnt/d/.c/csapp/bomb# vim ans.txt

输入以下命令
1
2
:set fileformat=unix
:wq

之后即可正常使用

1. phase_1

1.1 对主函数反汇编

先将主函数反汇编后保存在main.asm

1
2
3
4
(gdb) set logging file main.asm
(gdb) set logging on
(gdb) disas main
(gdb) set logging off

找到跟第一个炸弹有关的代码如下:

1
2
3
4
5
; main.asm
0x0000000000400e2d <+141>: call 0x400b10 <puts@plt>
0x0000000000400e32 <+146>: call 0x40149e <read_line>
0x0000000000400e37 <+151>: mov %rax,%rdi
0x0000000000400e3a <+154>: call 0x400ee0 <phase_1>

我们知道%rax是存返回值的,%rdi是存第一个参数的,那么上述代码的意思,就是在read_line()中读到用户的输入,然后将这个输入返回,作为phase_1()的参数

1.2 对phase_1()反汇编

同理,对phase_1()反汇编,我们对其逐行解析一下:

1
2
3
4
5
6
7
8
9
; phase_1.asm
0x0000000000400ee0 <+0>: sub $0x8,%rsp
0x0000000000400ee4 <+4>: mov $0x402400,%esi ; 将常量赋给%esi
0x0000000000400ee9 <+9>: call 0x401338 <strings_not_equal> ; 调用函数比较两个字符串, 第一个参数是%edi, 第二个参数是%esi
0x0000000000400eee <+14>: test %eax,%eax
0x0000000000400ef0 <+16>: je 0x400ef7 <phase_1+23> ; 当两个字符串相等时,跳过炸弹爆炸(explode_bomb)
0x0000000000400ef2 <+18>: call 0x40143a <explode_bomb>
0x0000000000400ef7 <+23>: add $0x8,%rsp
0x0000000000400efb <+27>: ret

从上述分析可知,我们要输入一个字符串跟内存$0x402400处的字符串一样

那么先看一下这个字符串是什么:

1
2
3
# x/s 0xbffff890 Examine a string stored at 0xbffff890
(gdb) x/s 0x402400
0x402400: "Border relations with Canada have never been better."

因此,密码就是:Border relations with Canada have never been better.

1
2
3
4
5
root@Andrew:/mnt/d/.c/csapp/bomb# ./bomb
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.
Phase 1 defused. How about the next one?

2. phase_2

2.1 分析read_six_numbers()

一回生二回熟,对 phase_2() 反汇编,然后掐头去尾(忽略callee-saved register的保存复原操作),先看最前面的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
; phase_2.asm
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

看起来比phase_1()复杂得多,先看最前面吧:

1
2
3
4
; phase_2.asm
0x0000000000400efe <+2>: sub $0x28,%rsp
0x0000000000400f02 <+6>: mov %rsp,%rsi
0x0000000000400f05 <+9>: call 0x40145c <read_six_numbers>

这里为phase_2()申请了一段栈帧,然后调用read_six_numbers,第一个参数$rdi还是我们输入的字符串,第二个参数%rsi居然是phase_2()栈顶的位置

那read_six_numbers干了什么?有些人能猜出来,我是没猜出来,可以对 read_six_numbers() 反汇编继续分析:

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
; read_six_numbers.asm
0x000000000040145c <+0>: sub $0x18,%rsp
0x0000000000401460 <+4>: mov %rsi,%rdx ; %rsi是phase_2()的栈帧的栈顶,我们记作p, 那么%rdx = p
0x0000000000401463 <+7>: lea 0x4(%rsi),%rcx ; %rcx = p + 4
0x0000000000401467 <+11>: lea 0x14(%rsi),%rax
0x000000000040146b <+15>: mov %rax,0x8(%rsp) ; 0x8(%rsp) = p + 0x14 = p + 20
0x0000000000401470 <+20>: lea 0x10(%rsi),%rax
0x0000000000401474 <+24>: mov %rax,(%rsp) ; (%rsp) = p + 0x10 = p + 16
0x0000000000401478 <+28>: lea 0xc(%rsi),%r9 ; %r9 = p + 0xc = p + 12
0x000000000040147c <+32>: lea 0x8(%rsi),%r8 ; %r8 = p + 8
0x0000000000401480 <+36>: mov $0x4025c3,%esi ; %esi为地址0x4025c3上的值, x/s 4025c3得"%d %d %d %d %d %d"
0x0000000000401485 <+41>: mov $0x0,%eax ; %eax = 0

; 重点来了,下面调用了sscanf(),在c语言中,这个函数读取一个字符串,将它格式化到其他参数中
; 那么这个函数的参数是哪些?
; 显然, 第一个参数 %edi 没改变过, 仍然是我们输入的字符串,
; 第二个参数是%esi = "%d %d %d %d %d %d", 也就是将我们的字符串格式化成六个十进制整型数字
; 第三到六个参数依次为 %edx=p, %ecx=p+4, %r8=p+8, %r9=p+12
; 从第七个参数开始不使用寄存器, 而是read_six_numbers()的栈帧, 如下:
; 第七个参数 (%rsp)=p+16, 第八个参数0x8(%rsp)=p+20
;
; 总结: 也就是说, sscanf()从我们输入的字符串中读取六个十进制数, 然后存到phase_2()的栈帧中,
; 栈顶存的第一个, 每+4位存一个
0x000000000040148a <+46>: call 0x400bf0 <__isoc99_sscanf@plt>

0x000000000040148f <+51>: cmp $0x5,%eax ; sscanf()的返回值应该是匹配数
0x0000000000401492 <+54>: jg 0x401499 <read_six_numbers+61>
0x0000000000401494 <+56>: call 0x40143a <explode_bomb> ; 如果没有六个数, 那么炸弹爆炸
0x0000000000401499 <+61>: add $0x18,%rsp
0x000000000040149d <+65>: ret

省流:read_six_numbers()从我们输入的字符串中读取六个十进制数, 然后存到phase_2()的栈帧中, (%rsp)存第一个, 0x4(rsp)存第二个, …, 0x14(rsp)存第六个。

2.2 分析phase_2()主体

接下来继续分析read_six_numbers()之后的代码:

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
0x0000000000400f0a <+14>:	cmpl   $0x1,(%rsp)
0x0000000000400f0e <+18>: je 0x400f30 <phase_2+52>
0x0000000000400f10 <+20>: call 0x40143a <explode_bomb> ; (%rsp)不为1则引爆炸弹, 说明第一个数是1

; 以下四段构成了一个循环:
; for(%rbx=0x4(%rsp), %rbp=0x18(%rsp); %rbx != %rbp; %rbx += 4){
; %eax = -0x4(rbx);
; %eax += %eax
; if (*%rbx != %eax)
; explode_bomb();
; }
0x0000000000400f15 <+25>: jmp 0x400f30 <phase_2+52> ; 通过之后跳到phase_2+52

0x0000000000400f17 <+27>: mov -0x4(%rbx),%eax ; %eax = -0x4(%rbx), 是第一个数, 也就是1
0x0000000000400f1a <+30>: add %eax,%eax ; %eax += %eax, 也就是翻倍
0x0000000000400f1c <+32>: cmp %eax,(%rbx)
0x0000000000400f1e <+34>: je 0x400f25 <phase_2+41>
0x0000000000400f20 <+36>: call 0x40143a <explode_bomb> ; 如果%rbx不等于两倍的-0x4(%rbx), bomb!
0x0000000000400f25 <+41>: add $0x4,%rbx ; %rbx += 0x4

0x0000000000400f29 <+45>: cmp %rbp,%rbx
0x0000000000400f2c <+48>: jne 0x400f17 <phase_2+27>
0x0000000000400f2e <+50>: jmp 0x400f3c <phase_2+64> ; if(%rbx != %rbp) 跳转到phase_27, 也就是循环, 否则跳转到phase_2+64

0x0000000000400f30 <+52>: lea 0x4(%rsp),%rbx ; %rbx = 0x4(%rsp), 是第二个数的地址
0x0000000000400f35 <+57>: lea 0x18(%rsp),%rbp ; %rbp = 0x18(%rsp), 是第六个数的地址0x14(%rsp)再加4
0x0000000000400f3a <+62>: jmp 0x400f17 <phase_2+27> ; 初始化%rbx和%rbp后跳转到phase_2+27

; 总结: 上述循环要求从第二个数开始到第六个数, 每个数都得是前一个数的两倍,
; 也就是要求我们输入: 1 2 4 8 16 32

0x0000000000400f3c <+64>: add $0x28,%rsp
0x0000000000400f40 <+68>: pop %rbx
0x0000000000400f41 <+69>: pop %rbp
0x0000000000400f42 <+70>: ret

省流:read_six_numbers()读取了六个数字, 而phase_2()中要求我们第一个数是1,且从第二个数开始,每个数需要是前一个数的两倍,故密码为:1 2 4 8 16 32

1
2
3
4
5
6
7
root@Andrew:/mnt/d/.c/csapp/bomb# ./bomb
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.
Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2. Keep going!

3. phase_3

对phase_3()反汇编,先看最前面:

1
2
3
4
5
6
7
8
9
10
0x0000000000400f43 <+0>:	sub    $0x18,%rsp
0x0000000000400f47 <+4>: lea 0xc(%rsp),%rcx ; 第四个参数是0xc(%rsp)
0x0000000000400f4c <+9>: lea 0x8(%rsp),%rdx ; 第三个参数是0x8(%rsp)
0x0000000000400f51 <+14>: mov $0x4025cf,%esi ; 第二个参数是地址0x4025cf的值, x/s 0x4025cf得"%d %d"
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> ; 没有读到两个数- > bomb!

省流:从我们输入的字符串中读取两个十进制数,第一个存储在0x8(%rsp)第二个存储在0xc(%rsp)

再往下面看:

1
2
3
4
0x0000000000400f6a <+39>:	cmpl   $0x7,0x8(%rsp)         
0x0000000000400f6f <+44>: ja 0x400fad <phase_3+106> ; 第一个数比7大就跳到phase_3+106 -> bomb!
0x0000000000400f71 <+46>: mov 0x8(%rsp),%eax
0x0000000000400f75 <+50>: jmp *0x402470(,%rax,8) ; 根据第一个数的值跳转到某个位置 相当于switch

打印每个跳转地址:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(gdb) p /x *(0x402470)
$2 = 0x400f7c
(gdb) p /x *(0x402470+1*8)
$3 = 0x400fb9
(gdb) p /x *(0x402470+2*8)
$4 = 0x400f83
(gdb) p /x *(0x402470+3*8)
$5 = 0x400f8a
(gdb) p /x *(0x402470+4*8)
$6 = 0x400f91
(gdb) p /x *(0x402470+5*8)
$7 = 0x400f98
(gdb) p /x *(0x402470+6*8)
$8 = 0x400f9f
(gdb) p /x *(0x402470+7*8)
$9 = 0x400fa6

将这些地址对应到语句中,如下:

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
0x0000000000400f7c <+57>:	mov    $0xcf,%eax          ; 0x8(%rsp) = 0
0x0000000000400f81 <+62>: jmp 0x400fbe <phase_3+123>
0x0000000000400f83 <+64>: mov $0x2c3,%eax ; 0x8(%rsp) = 2
0x0000000000400f88 <+69>: jmp 0x400fbe <phase_3+123>
0x0000000000400f8a <+71>: mov $0x100,%eax ; 0x8(%rsp) = 3
0x0000000000400f8f <+76>: jmp 0x400fbe <phase_3+123>
0x0000000000400f91 <+78>: mov $0x185,%eax ; 0x8(%rsp) = 4
0x0000000000400f96 <+83>: jmp 0x400fbe <phase_3+123>
0x0000000000400f98 <+85>: mov $0xce,%eax ; 0x8(%rsp) = 5
0x0000000000400f9d <+90>: jmp 0x400fbe <phase_3+123>
0x0000000000400f9f <+92>: mov $0x2aa,%eax ; 0x8(%rsp) = 6
0x0000000000400fa4 <+97>: jmp 0x400fbe <phase_3+123>
0x0000000000400fa6 <+99>: mov $0x147,%eax ; 0x8(%rsp) = 7
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 ; 0x8(%rsp) = 1

0x0000000000400fbe <+123>: cmp 0xc(%rsp),%eax
0x0000000000400fc2 <+127>: je 0x400fc9 <phase_3+134>
0x0000000000400fc4 <+129>: call 0x40143a <explode_bomb> ; 当第二个数和%eax不相等时爆炸

0x0000000000400fc9 <+134>: add $0x18,%rsp
0x0000000000400fcd <+138>: ret

省流:第一个数应该在[0, 7],确定了第一个数是哪个,就会对应一个数作为第二个数,因此可以有八种答案:

第一个数 第二个数
0 207
1 311
2 707
3 256
4 389
5 206
6 682
7 327

4. phase_4

我直接disas 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
25
0x000000000040100c <+0>:	sub    $0x18,%rsp
0x0000000000401010 <+4>: lea 0xc(%rsp),%rcx
0x0000000000401015 <+9>: lea 0x8(%rsp),%rdx
0x000000000040101a <+14>: mov $0x4025cf,%esi ;x/s 0x4025cf得"%d %d"
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> ; 第一个数要小于等于0xe 十进制为14
0x0000000000401035 <+41>: call 0x40143a <explode_bomb>

0x000000000040103a <+46>: mov $0xe,%edx ; 14是第三参数
0x000000000040103f <+51>: mov $0x0,%esi ; 0是第二个参数
0x0000000000401044 <+56>: mov 0x8(%rsp),%edi ; 第一个数是第一个参数
0x0000000000401048 <+60>: call 0x400fce <func4>
0x000000000040104d <+65>: test %eax,%eax
0x000000000040104f <+67>: jne 0x401058 <phase_4+76> ; 返回值需要等于0
0x0000000000401051 <+69>: cmpl $0x0,0xc(%rsp) ; 第二个数需要等于0
0x0000000000401056 <+74>: je 0x40105d <phase_4+81>
0x0000000000401058 <+76>: call 0x40143a <explode_bomb>

0x000000000040105d <+81>: add $0x18,%rsp
0x0000000000401061 <+85>: ret

省流:要求输入两个十进制数,第二个数必须是0,第一个数丢进func4后返回的结果也必须是0

接下来看func4,我直接disas 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
25
26
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 %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

我根据几个比较和跳转的关系把它们分成了几段,可以看到好几处递归调用,为了方便看,把三个参数%edi,%esi,%edx记作a,b,c,把%eax和%ecx记作x,y,写成c语言的形式如下:

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 fun4(int a, int b=0, int c=14){
// a 是我们输入的第一个数
// 以下注释仅针对第一轮进入函数体
int x = c - b; // x=14
int y = (x >= 0)? 0 : -1; // y=0

x = (x + y) / 2; // x=7
y= x + b; // y=7

if(y <= a){ // a >= 7 的时候进入
x = 0 // x=0, 而我们正需要返回值是0
if(y >= a){ // 所以只要让 a <=7 即 a = 7就能返回0
return x; // 所以令 a = 7就能通过
}else{
b = y + 1 // a > 7时会走到这里
x = func4(a, b, c);
return 2 * x + 1; // 返回一个奇数,不可能为0,因此a > 7必爆
}
}else{ // a < 7的时候
c = y - 1;
x = func4(a, b, c); // func4(a, 0, 6)
// 我们这一轮是func4(a, 0, 14), 输入7能过
// 同理, 下一轮是func4(a, 0, 6), 输入3能过
// 再下一轮的c = 6/2-1 = 2, 输入1能过
// 再下一轮的c = 2/2-1 = 0, 输入0能过
// 再下一轮的c = 0/2-1 = -1, 不会走到这里, 而是走到返回奇数那里, 必爆
// 综上, 第一个数可以取7, 3, 1, 0
return 2 * x;
}
}

经过分析,第一个数可以取7或3或1或0

其实在[0, 14]里面枚举也能暴力通过(小声

综上,答案共有四种,如下:

第一个数 第二个数
7 0
3 0
1 0
0 0

5. phase_5

二话不说对phase_5反汇编。

然后看看头:

1
2
3
4
5
6
0x0000000000401062 <+0>:	push   %rbx
0x0000000000401063 <+1>: sub $0x20,%rsp
0x0000000000401067 <+5>: mov %rdi,%rbx ; %rbx = %rdi为我们输入的字符串
0x000000000040106a <+8>: mov %fs:0x28,%rax
0x0000000000401073 <+17>: mov %rax,0x18(%rsp)
0x0000000000401078 <+22>: xor %eax,%eax ; 设置canary

设置canary是为了防止缓冲区溢出,看看就行,重点注意到%rbx保存了我们输入的字符串

再往下看

1
2
3
4
5
0x000000000040107a <+24>:	call   0x40131b <string_length>
0x000000000040107f <+29>: cmp $0x6,%eax
0x0000000000401082 <+32>: je 0x4010d2 <phase_5+112>
0x0000000000401084 <+34>: call 0x40143a <explode_bomb> ; 字符串长度不为6时爆炸
0x0000000000401089 <+39>: jmp 0x4010d2 <phase_5+112>

这里要求字符串长度等于6,然后跳转到phase_5+112,往下看
1
2
0x00000000004010d2 <+112>:	mov    $0x0,%eax
0x00000000004010d7 <+117>: jmp 0x40108b <phase_5+41>

这里把%eax初始化为0,然后又跳转到phase_5+41,往上看
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
0x000000000040108b <+41>:	movzbl (%rbx,%rax,1),%ecx        ; movzbl每次拷贝一个字节,%rbx是输入字符串   
0x000000000040108f <+45>: mov %cl,(%rsp)
0x0000000000401092 <+48>: mov (%rsp),%rdx ; %rdx = %cl
0x0000000000401096 <+52>: and $0xf,%edx ; 只取最后四位, 比如a的ascii码为0x41, 就取1
0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx
0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1)

; (gdb) x/s 0x4024b0
; 0x4024b0 <array.3449>: "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
; 只用看前面16位字母 maduiersnfotvbyl
; 以%dl为下标,取一个字符压入栈中

0x00000000004010a4 <+66>: add $0x1,%rax
0x00000000004010a8 <+70>: cmp $0x6,%rax
0x00000000004010ac <+74>: jne 0x40108b <phase_5+41> ; 循环条件, 共走六遍

0x00000000004010ae <+76>: movb $0x0,0x16(%rsp) ; 字符'\0'压入栈中
0x00000000004010b3 <+81>: mov $0x40245e,%esi ; x/s 0x40245e得"flyers"
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> ; 不等于这个字符串 -> bomb!

; 为了使压入栈中的字符串为flyers, 那么我们输入的字符串中的每个字符, 按十六进制的最后一位应该为:
; 9fe567
; 查询ASCII码表格就能组合出多种答案,如:ionefg, yonuvw

省流:从phase_5+41到phase_5+74构成了一个循环,依次取出我们输入的字符串的每一个字符,用这个字符的编码值 mod 0x10作下标去取一个常量字符串”maduiersnfotvbyl”中的一个字符压入栈中,最后要求栈中字符串为”flyers”

更直观地用c语言表述如下:

1
2
3
4
5
6
7
8
9
10
11
// input为我们输入的字符串, stack为栈, str为常量字符串

cosnt char str[17] = "maduiersnfotvbyl" // 其实后面还有字符但是不用管
for(int i = 0; i != 6; ++i){
stack[i] = str[input[i] % 0x10];
}
if(strcmp(stack, "flyers")){
// 如果stack中的字符串不等于"flyers"
explode_bomb();
}
return ;

查询ascii码对照表可以有多个答案,如:
ionefg
yonuvw
9?>%&’

6. phase_6

phase_6有些太复杂了。。。详细过程可以参考前言挂的b站视频

大概就是:

内存里有一条链表:

1
2
3
4
5
6
7
(gdb) x/24 0x6032d0
0x6032d0 <node1>: 332 1 6304480 0
0x6032e0 <node2>: 168 2 6304496 0
0x6032f0 <node3>: 924 3 6304512 0
0x603300 <node4>: 691 4 6304528 0
0x603310 <node5>: 477 5 6304544 0
0x603320 <node6>: 443 6 0 0

然后你需要输入六个数字根据这六个数字重新组合这条链表:
假设第一个数字为x,那么重组后的链表第一个节点为内存中链表的第7 - x个节点

然后遍历链表,当链表的值是降序时,可以通过,因此,节点应该是内存中第
3 4 5 6 1 2 个节点

故我们需要输入的数字为:4 3 2 1 6 5

总结

ans.txt如下能通过

1
2
3
4
5
6
Border relations with Canada have never been better.
1 2 4 8 16 32
7 327
7 0
9?>%&'
4 3 2 1 6 5

1
2
3
4
5
6
7
8
9
root@Andrew:/mnt/d/.c/csapp/bomb# ./bomb ans.txt
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2. Keep going!
Halfway there!
So you got that one. Try this one.
Good work! On to the next...
Congratulations! You've defused the bomb!

磕磕绊绊总算都过了,有点可惜没能赶在龙年到来之前发出这篇题解(是谁除夕夜打lab啊