CSAPP BombLab

欢迎前往我的github的CSAPP仓库,含各章家庭作业解答,各实验原始数据及题解和CSAPP电子书等,如果对您的学习有所帮助,能点个star就更好不过了

phase_1

反汇编main函数,找到phase_1()函数入口
(gdb) disassemble main
	...
   0x0000000000400e32 <+146>:	callq  0x40149e <read_line>
   0x0000000000400e37 <+151>:	mov    %rax,%rdi
   0x0000000000400e3a <+154>:	callq  0x400ee0 <phase_1>
	...
	
phase_1()函数首地址为0x400ee0,接着反汇编phase_1()函数
(gdb) disassemble 0x400ee0
Dump of assembler code for function phase_1:
   0x0000000000400ee0 <+0>:		sub    $0x8,%rsp
   0x0000000000400ee4 <+4>:		mov    $0x402400,%esi
   0x0000000000400ee9 <+9>:		callq  0x401338 <strings_not_equal>
   0x0000000000400eee <+14>:	test   %eax,%eax
   0x0000000000400ef0 <+16>:	je     0x400ef7 <phase_1+23>
   0x0000000000400ef2 <+18>:	callq  0x40143a <explode_bomb>
   0x0000000000400ef7 <+23>:	add    $0x8,%rsp
   0x0000000000400efb <+27>:	retq   
End of assembler dump.

简要分析,phase_1()函数调用首地址为0x401338名为strings_not_equal()的函数,后者通过%rax寄存器返回一个值。若该值为0,炸弹拆除成功;否则炸弹爆炸,于是接着反汇编string_not_euqal()函数
(gdb) disassemble 0x401338
	...
   0x000000000040133c <+4>:		mov    %rdi,%rbx
   0x000000000040133f <+7>:		mov    %rsi,%rbp
   0x0000000000401342 <+10>:	callq  0x40131b <string_length>
   0x0000000000401347 <+15>:	mov    %eax,%r12d
   0x000000000040134a <+18>:	mov    %rbp,%rdi
   0x000000000040134d <+21>:	callq  0x40131b <string_length>
   0x0000000000401352 <+26>:	mov    $0x1,%edx
   0x0000000000401357 <+31>:	cmp    %eax,%r12d
   0x000000000040135a <+34>:	jne    0x40139b <strings_not_equal+99>
	...

该函数又会调用首地址为0x40131b名为string_length()的函数,并比较二者返回值是否相同,若不同则strings_not_equal()通过%rax返回值1,最终炸弹爆炸,继续反汇编string_length()函数
(gdb) disassemble 0x40131b
Dump of assembler code for function string_length:
   0x000000000040131b <+0>:		cmpb   $0x0,(%rdi)
   0x000000000040131e <+3>:		je     0x401332 <string_length+23>
   0x0000000000401320 <+5>:		mov    %rdi,%rdx
   0x0000000000401323 <+8>:		add    $0x1,%rdx
   0x0000000000401327 <+12>:	mov    %edx,%eax
   0x0000000000401329 <+14>:	sub    %edi,%eax
   0x000000000040132b <+16>:	cmpb   $0x0,(%rdx)
   0x000000000040132e <+19>:	jne    0x401323 <string_length+8>
   0x0000000000401330 <+21>:	repz retq 
   0x0000000000401332 <+23>:	mov    $0x0,%eax
   0x0000000000401337 <+28>:	retq   
End of assembler dump.

分析得string_length()函数接收一个参数(存于%rdi中),%rdi为一字符串首地址,string_length()函数从该首地址起将数据类型为byte的字符值逐个与值0x('\0')比较,最终利用%rax寄存器返回字符串长度,接着回到string_not_equal()函数中
   0x000000000040135a <+34>:	jne    0x40139b <strings_not_equal+99>
   0x000000000040135c <+36>:	movzbl (%rbx),%eax
   0x000000000040135f <+39>:	test   %al,%al
   0x0000000000401361 <+41>:	je     0x401388 <strings_not_equal+80>
   0x0000000000401363 <+43>:	cmp    0x0(%rbp),%al
   0x0000000000401366 <+46>:	je     0x401372 <strings_not_equal+58>
   0x0000000000401368 <+48>:	jmp    0x40138f <strings_not_equal+87>
   0x000000000040136a <+50>:	cmp    0x0(%rbp),%al
   0x000000000040136d <+53>:	nopl   (%rax)
   0x0000000000401370 <+56>:	jne    0x401396 <strings_not_equal+94>
   0x0000000000401372 <+58>:	add    $0x1,%rbx
   0x0000000000401376 <+62>:	add    $0x1,%rbp
   0x000000000040137a <+66>:	movzbl (%rbx),%eax
   0x000000000040137d <+69>:	test   %al,%al
   0x000000000040137f <+71>:	jne    0x40136a <strings_not_equal+50>
   0x0000000000401381 <+73>:	mov    $0x0,%edx
   0x0000000000401386 <+78>:	jmp    0x40139b <strings_not_equal+99>
   0x0000000000401388 <+80>:	mov    $0x0,%edx
   0x000000000040138d <+85>:	jmp    0x40139b <strings_not_equal+99>
   0x000000000040138f <+87>:	mov    $0x1,%edx
   0x0000000000401394 <+92>:	jmp    0x40139b <strings_not_equal+99>
   0x0000000000401396 <+94>:	mov    $0x1,%edx
   0x000000000040139b <+99>:	mov    %edx,%eax

简要分析得首地址为0x40135a的指令判断两字符串长度是否相同。若长度相同,则逐一比较两串是否逐字符相同。若两串完全相同,则函数返回0;若任意字符不同,函数返回1
分析至此知本题关键在于如何获知模式串内容。观察知当通过string_length()函数计算字符串长度时,待计算字符串的首地址需通过%rdi寄存器传入函数中,而由string_not_equal()中
   0x000000000040133c <+4>:		mov    %rdi,%rbx
   0x000000000040133f <+7>:		mov    %rsi,%rbp
   0x0000000000401342 <+10>:	callq  0x40131b <string_length>
   0x0000000000401347 <+15>:	mov    %eax,%r12d
   0x000000000040134a <+18>:	mov    %rbp,%rdi
   0x000000000040134d <+21>:	callq  0x40131b <string_length>
   
知,两次调用string_length()函数时分别传入了%rdi与%rsi中的值,而又由phase_1()
   0x0000000000400ee4 <+4>:	mov    $0x402400,%esi
   0x0000000000400ee9 <+9>:	callq  0x401338 <strings_not_equal>
   
知,%rsi为定值$0x402400,不难得出该定值为模式串首地址

(gdb) x /s 0x402400
0x402400:	"Border relations with Canada have never been better."
得答案

phase_2

反汇编main函数,找到phase_2()函数入口
   0x0000000000400e56 <+182>:	callq  0x400efc <phase_2>

phase_2()函数首地址为0x400efc,接着反汇编phase_2()函数
   0x0000000000400efe <+2>:		sub    $0x28,%rsp
   0x0000000000400f02 <+6>:		mov    %rsp,%rsi
   0x0000000000400f05 <+9>:		callq  0x40145c <read_six_numbers>
   0x0000000000400f0a <+14>:	cmpl   $0x1,(%rsp)
   0x0000000000400f0e <+18>:	je     0x400f30 <phase_2+52>
   0x0000000000400f10 <+20>:	callq  0x40143a <explode_bomb>

该函数在栈上开辟了40个字节大小的空间,设开辟后%rsp值为s0,接着调用首地址为0x40145c名为read_six_numbers()的函数,随后比较确认栈顶四字节大小的数据值是否为1,若不为1则炸弹爆炸,由此确定DWORD PTR 0(%rsp) = 1,接着反汇编read_six_numbers()函数
(gdb) disassemble 0x40145c
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>:	callq  0x400bf0 <__isoc99_sscanf@plt>
   0x000000000040148f <+51>:	cmp    $0x5,%eax
   0x0000000000401492 <+54>:	jg     0x401499 <read_six_numbers+61>
   0x0000000000401494 <+56>:	callq  0x40143a <explode_bomb>
   0x0000000000401499 <+61>:	add    $0x18,%rsp
   0x000000000040149d <+65>:	retq   
End of assembler dump.

分析得read_six_numbers()接收一个由%rsi传递来的一个参数,其值为s0,即phase_2()函数栈空间开辟后栈顶地址。而read_six_numbers()函数的作用为将用户输入的六个数字按顺序放于栈中(先输入者于栈顶),再次返回phase_2()函数中
   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>:	callq  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>
自read_six_numbers()函数返回后,phase_2()函数剩余部分为一循环,由
   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>:	callq  0x40143a <explode_bomb>
知,相邻输入的两数字,若后输入者的值不为前者两倍,则炸弹爆炸。而又由DWORD PTR 0(%rsp) = 1知,六个数字分别为1,2,4,8,16,32

phase_3

反汇编phase_3()函数
(gdb) disassemble phase_3
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>:	callq  0x400bf0 <__isoc99_sscanf@plt>

知phase_3()函数先将值$0x4025cf存于%esi中,接着调用sscanf()函数读取输入数据,尝试反汇编首地址为0x4025cf的内容
(gdb) x /s 0x4025cf
0x4025cf:	"%d %d"

得该谜题应输入两个数字,继续查看phase_3()代码
   0x0000000000400f6a <+39>:	cmpl   $0x7,0x8(%rsp)
   0x0000000000400f6f <+44>:	ja     0x400fad <phase_3+106>
   0x0000000000400f71 <+46>:	mov    0x8(%rsp),%eax
   0x0000000000400f75 <+50>:	jmpq   *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>:	callq  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>:	callq  0x40143a <explode_bomb>

由前两行知DWORD PTR 0x8(%rsp)中的值小于等于7,而
   0x0000000000400f71 <+46>:	mov    0x8(%rsp),%eax
   0x0000000000400f75 <+50>:	jmpq   *0x402470(,%rax,8)
   
结合后续一系列跳转指令知,这里实现了swtich语句的跳转表功能,以DWORD PTR 0x8(%rsp)中的值为偏移量实现跳转,基地址为0x402470,于是反汇编查看跳转表内容
(gdb) x /8xg 0x402470
0x402470:	0x0000000000400f7c	0x0000000000400fb9
0x402480:	0x0000000000400f83	0x0000000000400f8a
0x402490:	0x0000000000400f91	0x0000000000400f98
0x4024a0:	0x0000000000400f9f	0x0000000000400fa6

当DWORD PTR 0x8(%rsp)值为0时,跳转至0x400f7c,再由后续几条指令知DWORD PTR 0xc(%rsp)的值应为0xcf,于是得本题一组解: [0, 207],同理得其他七组解分别为[1, 311],[2, 707],[3, 256],[4, 389],[5, 206],[6, 682],[7, 147]

phase_4

反汇编phase_4()
   0x0000000000401010 <+4>:		lea    0xc(%rsp),%rcx
   0x0000000000401015 <+9>:		lea    0x8(%rsp),%rdx
   0x000000000040101a <+14>:	mov    $0x4025cf,%esi
   0x000000000040101f <+19>:	mov    $0x0,%eax
   0x0000000000401024 <+24>:	callq  0x400bf0 <__isoc99_sscanf@plt>
   0x0000000000401029 <+29>:	cmp    $0x2,%eax
   0x000000000040102c <+32>:	jne    0x401035 <phase_4+41>

同phase_3(),易得该谜题需要两个输入
   0x000000000040102e <+34>:	cmpl   $0xe,0x8(%rsp)
   0x0000000000401033 <+39>:	jbe    0x40103a <phase_4+46>
   0x0000000000401035 <+41>:	callq  0x40143a <explode_bomb>

由上述三条指令知DOWRD PTR 0x8(%rsp)中的值小于等于14,即第一个数字应小于15
   0x0000000000401051 <+69>:	cmpl   $0x0,0xc(%rsp)
   0x0000000000401056 <+74>:	je     0x40105d <phase_4+81>
   0x0000000000401058 <+76>:	callq  0x40143a <explode_bomb>

同理知第二个数字必为0,接下来反汇编首地址为0x400fce的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    %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>:	callq  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>:	callq  0x400fce <func4>
   0x0000000000401003 <+53>:	lea    0x1(%rax,%rax,1),%eax
   0x0000000000401007 <+57>:	add    $0x8,%rsp
   0x000000000040100b <+61>:	retq   

分析知该函数接收三个参数,分别存于%rdi, %rsi, %rdx中,尝试构造c函数
int func4(int x, int y, int z) {
//x in %rdi, y in %rsi, z in %rdx
//t <-> %rax  w <-> %rcx
    if (z >= y) {
        t = z - y;
        w = 0;
        t = (z - y) / 2;
        w = (z + y) / 2;
        if (w <= x) {
            t = 0;
            if (w == x) return t; 
            y = (z + y) / 2 + 1;
            t = func4(x, y, z);
            return 2 * t + 1;
        }
        else {
            z = (z + y) / 2 - 1;
            t = func4(x, y, z);
            return 2 * t;
            }
    }
    else if (z < y) {
        t = z - y;
        w = -1;
        t = (z - y - 1) / 2;
        w = (z + y - 1) / 2;
        if (w <= x) {
            t = 0;
            if (w == x) return t; 
            y = (z + y + 1) / 2;
            t = func4(x, y, z);
            return 2 * t + 1;
        }
        else {
            z = (z + y - 1) / 2 - 1;
            t = func4(x, y, z);
            return 2 * t;
            }
    }
}

而phase_4()调用本函数时,传入的x, y, z值分别为DWORD PTR 0x8(%rsp), 0, 14.我没有详细探究本函数实现的功能,而是将DWORD PTR 0x8(%rsp)可能的取值(0 ~ 14)全部带入算出结果,由phase_4()函数里
   0x000000000040104d <+65>:	test   %eax,%eax
   0x000000000040104f <+67>:	jne    0x401058 <phase_4+76>

知,只有在func4()函数返回值为0的情况下炸弹才不会爆炸,因此第一个数可能的取值有0,1,3,7.
即可选的答案有[0, 0], [1, 0], [3, 0], [7, 0]

0x400fdd地址处的 sar %eax指令只含一个操作数,实际上是sar %eax, 1的隐含简写,见 SAR command in X86 assembly with one paramete

phase_5

反汇编phase_5()函数
   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>:	callq  0x40131b <string_length>
   0x000000000040107f <+29>:	cmp    $0x6,%eax
   0x0000000000401082 <+32>:	je     0x4010d2 <phase_5+112>
   0x0000000000401084 <+34>:	callq  0x40143a <explode_bomb>

首先可见该函数在栈上申请了32字节空间,接着将fs:0x28地址处的某值放入0x18(%rsp)中,即设置金丝雀值用于栈破坏检测然后调用string_length()函数检查输入字符串字符数目是否为6,非6则炸弹爆炸,由此知应输入字符数目为6.接下来结合
   0x0000000000401082 <+32>:	je     0x4010d2 <phase_5+112>
   
   0x00000000004010d2 <+112>:	mov    $0x0,%eax
   0x00000000004010d7 <+117>:	jmp    0x40108b <phase_5+41>
   
在确认输入字符串字符数为6后,先将%rax值清零,然后进入一段循环
   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>

分析知该循环代码依次将我们所输入的六个字符的低四位作为索引,取0x4024b0作为基地址,分六次将索引对应的字符取出存入%rsp + 16 + bias处的栈中,于是我们先反汇编看下0x4024b0地址处存储的信息
(gdb) x /s 0x4024b0
0x4024b0 <array.3449>:	"maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"

由于索引为我们输入字符的低四位,即索引范围为0 ~ 15,对应该字符串的
	maduiersnfotvbyl

部分,继续查看phase_5()函数代码
   0x00000000004010ae <+76>:	movb   $0x0,0x16(%rsp)
   0x00000000004010b3 <+81>:	mov    $0x40245e,%esi
   0x00000000004010b8 <+86>:	lea    0x10(%rsp),%rdi
   0x00000000004010bd <+91>:	callq  0x401338 <strings_not_equal>
   0x00000000004010c2 <+96>:	test   %eax,%eax
   0x00000000004010c4 <+98>:	je     0x4010d9 <phase_5+119>
   0x00000000004010c6 <+100>:	callq  0x40143a <explode_bomb>

string_not_equal在之前的谜题里已见过,易知此段代码作用为将刚才通过索引选出的6个字符与首地址为0x40245e处的字符串进行比较,若不匹配则炸弹爆炸,故查看地址0x40245e处字符串内容
(gdb) x /s 0x40245e
0x40245e:	"flyers"

这六个字符分别对应谜题字符串中的[9, 15, 14, 5, 6, 7]位(从0起计数),而高四位不做限制,故本题答案不唯一,查阅ascii表易得其中两个答案为"9?>567", "IONEFG",其余答案不一一枚举。phase_5()函数最后几行代码用于检测是否发生栈溢出,在此不做更多介绍

phase_6

反汇编phase_6()函数
   0x00000000004010fc <+8>:		sub    $0x50,%rsp
   0x0000000000401100 <+12>:	mov    %rsp,%r13
   0x0000000000401103 <+15>:	mov    %rsp,%rsi
   0x0000000000401106 <+18>:	callq  0x40145c <read_six_numbers>
   0x000000000040110b <+23>:	mov    %rsp,%r14

在栈上开辟80字节空间后,将%r13, %rsi, %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>:	callq  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>:	callq  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>

分析知本谜题六个输入数字均小于等于6,且六个数字各不相同
   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>

设六个输入按顺序为a[0]至a[5],上述循环令a[i] = 7 - a[i]
   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>

分析知有一链表首地址为0x6032d0,该链表一个结点16字节,后8字节用于存放指向下一个结点的指针,于是顺藤摸瓜找出该链表所有结点,设结点按顺序分别为b[0]至b[5],上述代码作用为将a[i] - 1对应的结点地址放于%rsp + 32为基地址的栈中(e.g. 假设a[0] = 3,则将b[2]放于(%rsp + 32 + 0 * 8)处空间中)
(gdb) x /16xb 0x6032d0
0x6032d0 <node1>:	0x4c	0x01	0x00	0x00	0x01	0x00	0x00	0x00
0x6032d8 <node1+8>:	0xe0	0x32	0x60	0x00	0x00	0x00	0x00	0x00
(gdb) x /16xb 0x6032e0
0x6032e0 <node2>:	0xa8	0x00	0x00	0x00	0x02	0x00	0x00	0x00
0x6032e8 <node2+8>:	0xf0	0x32	0x60	0x00	0x00	0x00	0x00	0x00
(gdb) x /16xb 0x6032f0
0x6032f0 <node3>:	0x9c	0x03	0x00	0x00	0x03	0x00	0x00	0x00
0x6032f8 <node3+8>:	0x00	0x33	0x60	0x00	0x00	0x00	0x00	0x00
(gdb) x /16xb 0x603300
0x603300 <node4>:	0xb3	0x02	0x00	0x00	0x04	0x00	0x00	0x00
0x603308 <node4+8>:	0x10	0x33	0x60	0x00	0x00	0x00	0x00	0x00
(gdb) x /16xb 0x603310
0x603310 <node5>:	0xdd	0x01	0x00	0x00	0x05	0x00	0x00	0x00
0x603318 <node5+8>:	0x20	0x33	0x60	0x00	0x00	0x00	0x00	0x00
(gdb) x /16xb 0x603320
0x603320 <node6>:	0xbb	0x01	0x00	0x00	0x06	0x00	0x00	0x00
0x603328 <node6+8>:	0x00	0x00	0x00	0x00	0x00	0x00	0x00

继续阅读
   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>:	callq  0x40143a <explode_bomb>
   0x00000000004011ee <+250>:	mov    0x8(%rbx),%rbx
   0x00000000004011f2 <+254>:	sub    $0x1,%ebp
   0x00000000004011f5 <+257>:	jne    0x4011df <phase_6+235>

分析知正确的结点值应从高到低排列,又因比较结点值时使用的是%eax寄存器,故比较的是各结点前四字节的值,即根据我们输入的六个不同数值a[0]至a[5],分别取a[i] = 7 - a[i],再用新的a[i]值将原链表结点顺序重排得新链表,保证新链表的结点值为从高至低排列。原各结点值如下表
node1node2node3node4node5node6
0x14c0xa80x39c0x2b30x1dd0x1bb

按从大到小排列为 [node3, node4, node5, node6, node1, node2]
	a[0]新 - 1 = 3
	a[1]新 - 1 = 4
	...
	a[5]新 - 1 = 2

而
	a[i]新 = 7 - a[i]旧

故
	a旧 = [4, 3 ,2 ,1 ,6, 5]

即应输入六数分别为[4, 3 ,2 ,1 ,6, 5]

secret_phase

反汇编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>:	callq  0x400bf0 <__isoc99_sscanf@plt>
   0x00000000004015ff <+59>:	cmp    $0x3,%eax
   0x0000000000401602 <+62>:	jne    0x401635 <phase_defused+113>

发现函数调用首地址为0x400bf0的sscanf()函数读入数据,查看%0x402619处内容,得
(gdb) x /s 0x402619
0x402619:	"%d %d %s"

故知一次读入两个int型数据和一个字符串,分别存入0x8(%rsp), 0xc(%rsp), 0x10(%rsp)中
   0x0000000000401604 <+64>:	mov    $0x402622,%esi
   0x0000000000401609 <+69>:	lea    0x10(%rsp),%rdi
   0x000000000040160e <+74>:	callq  0x401338 <strings_not_equal>
   0x0000000000401613 <+79>:	test   %eax,%eax
   0x0000000000401615 <+81>:	jne    0x401635 <phase_defused+113>

接着phase_defused()调用strings_not_euqal()检测我们输入的字符串与首地址为0x402622处的模式串是否匹配,于是查看该地址处的字符串得正解
(gdb) x /s 0x402622
0x402622:	"DrEvil"

需注意,phase_defused()函数并未调用诸如read_line()等函数读取输入,而是直接默认从0x603870地址处读取数据。而前六个谜题每个都调用了一次read_line()函数,再通过
	mov %rax, %rdi

指令将输入的数据传给相应函数,于是猜测0x603870为main()函数中某次调用read_line()参数获得用户输入后传给phase_x()函数的地址,检查六次调用read_line()后read_line()传送给phase_x()函数的地址,得六个地址分别为
	0x603780
	0x6037d0
	0x603820
	0x603870
	0x6038c0
	0x603910

知0x603870为第四次read_line()后传递给phase_4()的输入数据的首地址,而第四个谜题需输入两个int型变量,因此只需在第四题的答案后面加上字符串"DrEvil"即可在六个谜题结束后触发隐藏谜题,继续分析
   0x0000000000401617 <+83>:	mov    $0x4024f8,%edi
   0x000000000040161c <+88>:	callq  0x400b10 <puts@plt>
   0x0000000000401621 <+93>:	mov    $0x402520,%edi
   0x0000000000401626 <+98>:	callq  0x400b10 <puts@plt>
   0x000000000040162b <+103>:	mov    $0x0,%eax
   0x0000000000401630 <+108>:	callq  0x401242 <secret_phase>
   0x0000000000401635 <+113>:	mov    $0x402558,%edi
   0x000000000040163a <+118>:	callq  0x400b10 <puts@plt>
   0x000000000040163f <+123>:	mov    0x68(%rsp),%rax
   0x0000000000401644 <+128>:	xor    %fs:0x28,%rax
   0x000000000040164d <+137>:	je     0x401654 <phase_defused+144>
   0x000000000040164f <+139>:	callq  0x400b30 <__stack_chk_fail@plt>
   0x0000000000401654 <+144>:	add    $0x78,%rsp
   0x0000000000401658 <+148>:	retq   

函数输出两字符串,调用secret_phase()函数,再输出一字符串,检查栈是否溢出,结束。于是接下来分析secret_phase()函数
   0x0000000000401242 <+0>:		push   %rbx
   0x0000000000401243 <+1>:		callq  0x40149e <read_line>
   0x0000000000401248 <+6>:		mov    $0xa,%edx
   0x000000000040124d <+11>:	mov    $0x0,%esi
   0x0000000000401252 <+16>:	mov    %rax,%rdi
   0x0000000000401255 <+19>:	callq  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>:	callq  0x40143a <explode_bomb>

通过调用read_line()函数读入字符串,并调用strtol()函数将其转换为十进制数字,且该数字应小于等于0x3e9,否则炸弹爆炸
   0x000000000040126c <+42>:	mov    %ebx,%esi
   0x000000000040126e <+44>:	mov    $0x6030f0,%edi
   0x0000000000401273 <+49>:	callq  0x401204 <fun7>
   0x0000000000401278 <+54>:	cmp    $0x2,%eax
   0x000000000040127b <+57>:	je     0x401282 <secret_phase+64>
   0x000000000040127d <+59>:	callq  0x40143a <explode_bomb>
   0x0000000000401282 <+64>:	mov    $0x402438,%edi
   0x0000000000401287 <+69>:	callq  0x400b10 <puts@plt>
   0x000000000040128c <+74>:	callq  0x4015c4 <phase_defused>
   0x0000000000401291 <+79>:	pop    %rbx
   0x0000000000401292 <+80>:	retq   

函数将地址0x6030f0送入%rdi后,调用首地址为0x401204的fun7()函数,且知该函数必须返回数值2,反汇编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>:	callq  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>:	callq  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>:	retq   

尝试将该函数反编译为c代码风格
fun7(x, y)
x in %rdi, y in %rsi, p for %rdx
	if (x == 0) return -1;
	p = *x;
	if (p <= y) {
		if (p == y) return 0;
		return 2 * fun7(x, y) + 1;
	}
	x = *(x + 8);
	return 2 * fun7(x, y);
	
初次调用fun7()函数时,x值为0x6030f0, y值为我们输入的一个字符串转换所得数值,结合0x6030f0内存地址的值等,可以推出一个答案y值为22(0x16).在我通过此方法推出一个答案值后,参考他人题解,知0x6030f0为一二叉树根节点地址,该数每个结点含一个值和两个指向左右儿子的指针
(gdb) x /80a 0x6030f0
0x6030f0 <n1>:	0x24	0x603110 <n21>
0x603100 <n1+16>:	0x603130 <n22>	0x0
0x603110 <n21>:	0x8	0x603190 <n31>
0x603120 <n21+16>:	0x603150 <n32>	0x0
0x603130 <n22>:	0x32	0x603170 <n33>
0x603140 <n22+16>:	0x6031b0 <n34>	0x0
0x603150 <n32>:	0x16	0x603270 <n43>
0x603160 <n32+16>:	0x603230 <n44>	0x0
0x603170 <n33>:	0x2d	0x6031d0 <n45>
0x603180 <n33+16>:	0x603290 <n46>	0x0
0x603190 <n31>:	0x6	0x6031f0 <n41>
0x6031a0 <n31+16>:	0x603250 <n42>	0x0
0x6031b0 <n34>:	0x6b	0x603210 <n47>
0x6031c0 <n34+16>:	0x6032b0 <n48>	0x0

由以上信息可构建树为

故可的答案为22(0x16), 20(0x14).

至此,全部谜题破解
fez@LAPTOP-MB5DL415:/mnt/d/repositories/CSAPP/Labs/BombLab$ cat phases.txt 
Border relations with Canada have never been better.
1 2 4 8 16 32
0 207
0 0 DrEvil
IONEFG
4 3 2 1 6 5
20
fez@LAPTOP-MB5DL415:/mnt/d/repositories/CSAPP/Labs/BombLab$ ./bomb phases.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...
Curses, you've found the secret phase!
But finding it and solving it are quite different...
Wow! You've defused the secret stage!
Congratulations! You've defused the bomb!
																2021/7/27
																21:16

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值