【CMU 15-213 CSAPP】详解lab2——bomblab

前言

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

支持一下个人博客页呗:【CMU 15-213 CSAPP】详解lab2------bomblab | Andrew的个人博客 (andreww1219.github.io)

参考资料:

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命令

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

shell 复制代码
# 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 常用寄存器

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

0.3 保存答案

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

shell 复制代码
(gdb) run ans.txt

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

用vim打开ans.txt

shell 复制代码
root@Andrew:/mnt/d/.c/csapp/bomb# vim ans.txt

输入以下命令

vim 复制代码
:set fileformat=unix
:wq

之后即可正常使用

1. phase_1

1.1 对主函数反汇编

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

shell 复制代码
(gdb) set logging file main.asm
(gdb) set logging on
(gdb) disas main
(gdb) set logging off

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

asm 复制代码
; 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()反汇编,我们对其逐行解析一下:

asm 复制代码
; 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处的字符串一样

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

shell 复制代码
# 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.

shell 复制代码
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的保存复原操作),先看最前面的

asm 复制代码
; 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()复杂得多,先看最前面吧:

asm 复制代码
; 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() 反汇编继续分析:

asm 复制代码
; 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()之后的代码:

perl 复制代码
   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

shell 复制代码
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()反汇编,先看最前面:

asm 复制代码
   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)

再往下面看:

asm 复制代码
   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

打印每个跳转地址:

shell 复制代码
(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

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

asm 复制代码
   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:

asm 复制代码
   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:

asm 复制代码
   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语言的形式如下:

c 复制代码
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反汇编。

然后看看头:

asm 复制代码
   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保存了我们输入的字符串

再往下看

asm 复制代码
   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,往下看

asm 复制代码
   0x00000000004010d2 <+112>:	mov    $0x0,%eax
   0x00000000004010d7 <+117>:	jmp    0x40108b <phase_5+41>

这里把%eax初始化为0,然后又跳转到phase_5+41,往上看

asm 复制代码
  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语言表述如下:

asm 复制代码
// 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站视频

大概就是:

内存里有一条链表:

shell 复制代码
(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如下能通过

txt 复制代码
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
shell 复制代码
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啊

相关推荐
YY_D_S_5 天前
【机组】概述精炼考点(冯诺依曼、层次结构、翻译语言、执行程序的过程、基本工作原理、运算器、控制器、存储器)
计算机组成原理·机组
superiony8 天前
【计算机组成原理】实验二:通用寄存器单元实验
计算机组成原理·通用寄存器
Crossoads8 天前
【汇编语言】内中断(二) —— 安装自己的中断处理程序:你也能控制0号中断
android·开发语言·数据库·人工智能·深度学习·机器学习·汇编语言
青春pig头少年14 天前
《计算机组成原理》(408大题)
学习笔记·408·计算机组成原理
moonless022215 天前
【GISer精英计划_00】从二进制到协议、到计算机通信、到服务器
网络协议·gis·计算机组成原理
运维小文16 天前
linux的磁盘管理
linux·运维·网络·磁盘·计算机组成原理·硬件
Crossoads20 天前
【汇编语言】标志寄存器(一) —— 标志寄存器中的标志位:ZF、PF、SF、CF、OF 一网打尽
android·开发语言·数据库·人工智能·深度学习·机器学习·汇编语言
同志啊为人民服务!21 天前
深入理解计算机系统,源码到可执行文件翻译过程:预处理、编译,汇编和链接
汇编·预处理·链接·编译·汇编语言·高级语言·可执行文件装载过程
shengjk11 个月前
从零开发操作系统-聊一下GDT 和 IDT
人工智能·后端·计算机组成原理
Crossoads1 个月前
【汇编语言】更灵活的定位内存地址的方法(三)—— 不同的寻址方式的灵活应用
android·开发语言·数据库·人工智能·机器学习·数据挖掘·汇编语言