CSAPP实验二——bomb lab实验

实验前准备

在linux操作系统上安装GDB调试器。由于我是在Ubuntu虚拟机上进行实验,所以只需要在终端输入如下安装指令即可快速安装。
sudo apt install gdb

在可执行程序所在的目录下进入终端,输入gdb bomb即可启动调试过程(bomb是可执行程序名称),可以输入quit结束GDB调试。

第一部分(phase_1)

使用disas phase_1反汇编phase_1函数

   0x0000000000400ee0 <+0>:		sub    $0x8,%rsp
   0x0000000000400ee4 <+4>:		mov    $0x402400,%esi 			//修改%esi的值作为参数传入后面函数
   0x0000000000400ee9 <+9>:		callq  0x401338 <strings_not_equal>
   0x0000000000400eee <+14>:	test   %eax,%eax     #修改条件码
   0x0000000000400ef0 <+16>:	je     0x400ef7 <phase_1+23>  	//根据条件码进行跳转,如果%eax寄存器里存放的是0则跳过爆炸的程序
   0x0000000000400ef2 <+18>:	callq  0x40143a <explode_bomb>
   0x0000000000400ef7 <+23>:	add    $0x8,%rsp
   0x0000000000400efb <+27>:	retq   

发现第一部分还是很简单的,基本操作就是调用strings_not_equal函数,根据返回值来决定是否引爆炸弹。至于strings_not_equal函数,从名字就可以看出来是实现比较两字符串是否相等的功能,通过disas strings_not_equal也可以验证一下猜想。

比较的两个字符串一个是用户输入的字符串,一个是地址存放在%esi寄存器里的内存中的值,也就是地址为0x402400的内存值。

可以使用x/s 0x402400来查看对应的字符串,得到以下内容:

0x402400: “Border relations with Canada have never been better.”

所以只要输入的字符串与之完全一样,则可以通过,进入下一关。

第二部分(phase_2)

使用disas phase_2反汇编phase_2函数

   0x0000000000400efc <+0>:		push   %rbp
   0x0000000000400efd <+1>:		push   %rbx
   0x0000000000400efe <+2>:		sub    $0x28,%rsp
   0x0000000000400f02 <+6>:		mov    %rsp,%rsi
   0x0000000000400f05 <+9>:		callq  0x40145c <read_six_numbers>
   0x0000000000400f0a <+14>:	cmpl   $0x1,(%rsp)  			//将第一个输入与1比较
   0x0000000000400f0e <+18>:	je     0x400f30 <phase_2+52>    //确定第一个数字必须为1
   0x0000000000400f10 <+20>:	callq  0x40143a <explode_bomb>
   0x0000000000400f15 <+25>:	jmp    0x400f30 <phase_2+52>
   0x0000000000400f17 <+27>:	mov    -0x4(%rbx),%eax		  //eax寄存器存放上一个输入数字的值
   0x0000000000400f1a <+30>:	add    %eax,%eax			  //上一个输入数字*2
   0x0000000000400f1c <+32>:	cmp    %eax,(%rbx)			  //当前数字与上一个数字*2比较
   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        //0x18=24,控制循环的终点
   0x0000000000400f3a <+62>:	jmp    0x400f17 <phase_2+27>
   0x0000000000400f3c <+64>:	add    $0x28,%rsp
   0x0000000000400f40 <+68>:	pop    %rbx
   0x0000000000400f41 <+69>:	pop    %rbp
   0x0000000000400f42 <+70>:	retq   

通过进一步查看read_six_numbers函数的汇编代码,可以知道其完成的是读取用户输入的六个整数,每个数字占用四个字节大小空间,所以最终循环的终点为栈指针偏移4*6=0x18=24的位置。

这部分代码实现的是一个循环,将输入的六个数字依次与其前一位的两倍进行比较。当且仅当第一个数字为1,后一位依次是前一位的两倍的情况下,才不会引起爆炸。所以得到第二问的答案为

1 2 4 8 16 32

第三部分(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>
   0x0000000000400f60 <+29>:	cmp    $0x1,%eax
   0x0000000000400f63 <+32>:	jg     0x400f6a <phase_3+39>
   0x0000000000400f65 <+34>:	callq  0x40143a <explode_bomb>
   0x0000000000400f6a <+39>:	cmpl   $0x7,0x8(%rsp)		//第一个输入与7比较
   0x0000000000400f6f <+44>:	ja     0x400fad <phase_3+106>	//用ja而不是jg,暗示这一部分可能和switch语句有关
   0x0000000000400f71 <+46>:	mov    0x8(%rsp),%eax
   0x0000000000400f75 <+50>:	jmpq   *0x402470(,%rax,8)		//根据第一个输入的值来进行jmp的间接跳转
   0x0000000000400f7c <+57>:	mov    $0xcf,%eax				//第一个输入为0时跳转到这里		
   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		//第二个输入与对应的%eax里的值比较大小,只有等于才不会触发爆炸程序
   0x0000000000400fc2 <+127>:	je     0x400fc9 <phase_3+134>
   0x0000000000400fc4 <+129>:	callq  0x40143a <explode_bomb>
   0x0000000000400fc9 <+134>:	add    $0x18,%rsp
   0x0000000000400fcd <+138>:	retq   

这一部分卡我时间最长的是不知道第7行的%eax里函数的返回值为多少。如果对__isoc99_sscanf@plt函数过程进行分析感觉有点困难。这里我用了一个稍微讨巧一点的办法,和第二问的read_six_numbers函数里进行类比。因为在read_six_numbers函数里也调用了__isoc99_sscanf@plt函数,看看那里的%eax里存放的返回值会不会对我们有一定的提示。

   0x000000000040148a <+46>:	callq  0x400bf0 <__isoc99_sscanf@plt>
   0x000000000040148f <+51>:	cmp    $0x5,%eax
   0x0000000000401492 <+54>:	jg     0x401499 <read_six_numbers+61>


上图展示的是read_six_numbers函数反汇编得到的部分代码。第二问是要求输入六个数字,而这里是%eax里存放的值>5则跳转,所以可以猜想%eax存放的是输入的参数个数。

感觉有点牵强的话,也可以动手验证一下,设置好断点后,一条指令一条指令单步调试。用i r指令来查看寄存器里的值。发现猜想应该是没问题的,并且输入的参数应该是整数类型。

jmpq *0x402470(,%rax,8)这条指令往下,都是和对应跳转的位置有关。跳转到哪一行与第一个输入的数值有关,所以这一部分考察的应该是switch语句的相关知识。第一个输入与7进行无符号数比较,则可以断定第一个输入只可以为0,1,2,3,4,5,6。再分别计算0x402470(,%rax,8)的地址,就可以得到对应的跳转位置。

第一个输入值跳转位置
00x400f7c
10x400fb9
20x400f83
30x400f8a
40x400f91
50x400f98
60x400f9f

第二个输入会根据第一个输入与相应的数值比较,只有相等才可以通过。最终可以计算第三部分的答案如下

0 207(或1 311,或2 707,或3 256,或4 389,或5 206,或6 682)

第四部分(phase_4)

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>:	callq  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>   //第一个输入数值要小于等于14
   0x0000000000401035 <+41>:	callq  0x40143a <explode_bomb>
   0x000000000040103a <+46>:	mov    $0xe,%edx
   0x000000000040103f <+51>:	mov    $0x0,%esi
   0x0000000000401044 <+56>:	mov    0x8(%rsp),%edi
   0x0000000000401048 <+60>:	callq  0x400fce <func4>		//调用func4函数,也是这一块的核心函数
   0x000000000040104d <+65>:	test   %eax,%eax
   0x000000000040104f <+67>:	jne    0x401058 <phase_4+76>	//%eax必须为0
   0x0000000000401051 <+69>:	cmpl   $0x0,0xc(%rsp)
   0x0000000000401056 <+74>:	je     0x40105d <phase_4+81>	//第二个输入要等于0
   0x0000000000401058 <+76>:	callq  0x40143a <explode_bomb>
   0x000000000040105d <+81>:	add    $0x18,%rsp
   0x0000000000401061 <+85>:	retq   

通过分析发现,在phase_4函数中还调用了func4函数,为了进一步确定第一个输入的数值,需要我们来查看func4的汇编代码。如下:

   0x0000000000400fce <+0>:		sub    $0x8,%rsp
   0x0000000000400fd2 <+4>:		mov    %edx,%eax
   0x0000000000400fd4 <+6>:		sub    %esi,%eax		//%eax=%edx-%esi
   0x0000000000400fd6 <+8>:		mov    %eax,%ecx
   0x0000000000400fd8 <+10>:	shr    $0x1f,%ecx		//逻辑右移31位,所以相当于保留符号位
   0x0000000000400fdb <+13>:	add    %ecx,%eax		//负数需要加一个偏置1,正数没有变化
   0x0000000000400fdd <+15>:	sar    %eax				//%eax等于(%edx-%esi)/2
   0x0000000000400fdf <+17>:	lea    (%rax,%rsi,1),%ecx		//%ecx为(%edx+%esi)/2
   0x0000000000400fe2 <+20>:	cmp    %edi,%ecx		//与%edi也就是第一个输入比较
   0x0000000000400fe4 <+22>:	jle    0x400ff2 <func4+36>
   0x0000000000400fe6 <+24>:	lea    -0x1(%rcx),%edx	//如果大了则%edx(上界)变成均值-1,进行递归
   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		//与%edi也就是第一个输入比较
   0x0000000000400ff9 <+43>:	jge    0x401007 <func4+57>
   0x0000000000400ffb <+45>:	lea    0x1(%rcx),%esi	//如果小了则%esi(下界)变成均值+1,进行递归
   0x0000000000400ffe <+48>:	callq  0x400fce <func4>
   0x0000000000401003 <+53>:	lea    0x1(%rax,%rax,1),%eax	//注意这里会变化%eax的值
   0x0000000000401007 <+57>:	add    $0x8,%rsp
   0x000000000040100b <+61>:	retq   

第四部分其实想得到答案很容易,根据分析,是比较第一个输入和%edx与%esi两个寄存器的值的均值,类似二分法折半查找。最直接的答案是第一次进入func4就直接出来,也就是第一个输入正好等于均值。由于在调用之前%edx=14%esi=0,所以第一个输入为7时,可以递归一次直接返回到phase_4,这时%eax返回值也为0,不会引爆炸弹。

如果想进一步分析的话,可以看到无论函数内部实现如何,我们只希望返回值%eax必须为0就可以了。而%eax一般都是置为0的,第20行的lea 0x1(%rax,%rax,1),%eax会对其产生影响,所以让每次递归都是上界变小就会保证%eax一定为0。

用数据会更直观一点,这里只考虑非负数的情况,假设每次递归都是上界减小,也就是均值大于第一个输入的值,直到最终结束递归。

递归次数%edx%esi%ecx%eax%edi(第一个输入)
1140777(一次直接返回)
260333(两次后返回)
320111(三次后返回)
400000(四次后返回)

所以答案如下

7 0(或3 0,或1 0,或0 0…)

第五部分(phase_5)

第五部分还是蛮有意思的,里面的构思比较巧妙。首先看一下汇编代码:

   0x0000000000401062 <+0>:		push   %rbx
   0x0000000000401063 <+1>:		sub    $0x20,%rsp
   0x0000000000401067 <+5>:		mov    %rdi,%rbx		//%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>	//输入字符串长度为6
   0x0000000000401084 <+34>:	callq  0x40143a <explode_bomb>
   0x0000000000401089 <+39>:	jmp    0x4010d2 <phase_5+112>
   0x000000000040108b <+41>:	movzbl (%rbx,%rax,1),%ecx	//每一个字符的ascii码数值传递给%ecx
   0x000000000040108f <+45>:	mov    %cl,(%rsp)
   0x0000000000401092 <+48>:	mov    (%rsp),%rdx
   0x0000000000401096 <+52>:	and    $0xf,%edx			//仅保留%edx最后四位
   0x0000000000401099 <+55>:	movzbl 0x4024b0(%rdx),%edx	//将0x4024b0加上%edx对应的内存位置的内容给%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)		//超过6的后续用0填充
   0x00000000004010b3 <+81>:	mov    $0x40245e,%esi
   0x00000000004010b8 <+86>:	lea    0x10(%rsp),%rdi
   0x00000000004010bd <+91>:	callq  0x401338 <strings_not_equal>		//前面循环新得到的字符串和0x40245e存放的字符串进行比对
   0x00000000004010c2 <+96>:	test   %eax,%eax
   0x00000000004010c4 <+98>:	je     0x4010d9 <phase_5+119>
   0x00000000004010c6 <+100>:	callq  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>:	callq  0x400b30 <__stack_chk_fail@plt>
   0x00000000004010ee <+140>:	add    $0x20,%rsp
   0x00000000004010f2 <+144>:	pop    %rbx
   0x00000000004010f3 <+145>:	retq   

一开始拿到这一题直接看到了callq 0x401338 <strings_not_equal>,所以想当然地直接先找到0x40245e对应内存位置存放的字符串“flyers"试试看,结果发现爆炸了。

于是就静下心来看了看,发现比较的两个字符串其中一个是内存地址为0x40245e的字符串,但另一个并不是输入,而是输入映射后得到的新的六个字符。也就是当你输入"flyers",结果并不会是”flyers"。具体来说,在第一次循环时,%rbx存放的是"f"的ascii码0x66,%edx中保存的是该ascii码的最后4位,也就是0x6。再根据movzbl 0x4024b0(%rdx),%edx这句话,此时%edx得到的值为"r",不是一开始的"f"。自然也就爆炸了。所以该问题还是得找到映射关系。

有了这个思路,就可以尝试查看内存0x4024b0存放了什么映射关系,使用x/s 0x4024b0查看得到结果如下:

“maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?”

后面的那句话简直了,顿时觉得实验特别好玩。前面的那些小写字母得顺序就是对应的映射关系。依次找到“f", “l”, “y”, “e”, “r”, "s"对应的位置是“9”,“15”,“14”,“5”,“6”,“7”。所以只需要ascii码最后四位值等于这些的字符就可以解开。我依次选取了“0x69”,“0x6f”,“0x6e”,“0x65”,“0x66”,“0x67”。也就是如下字符,第五问就解决了。

ionefg

第六部分(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>:	callq  0x40145c <read_six_numbers>		//输入为6个数字
   0x000000000040110b <+23>:	mov    %rsp,%r14
   0x000000000040110e <+26>:	mov    $0x0,%r12d		//%r12d用来计数,外部循环初始化起点i
   0x0000000000401114 <+32>:	mov    %r13,%rbp		//%rbp更新
   0x0000000000401117 <+35>:	mov    0x0(%r13),%eax	//当前输入数字
   0x000000000040111b <+39>:	sub    $0x1,%eax		//当前输入数字-1
   0x000000000040111e <+42>:	cmp    $0x5,%eax
   0x0000000000401121 <+45>:	jbe    0x401128 <phase_6+52>	//需要输入的每个数字小于等于6(减1小于等于5)
   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		//内部循环起点j为外部循环的当前循环i
   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		//%r13更新,后面再传递给%rbp
   0x0000000000401151 <+93>:	jmp    0x401114 <phase_6+32>
   /*前面做的任务是判断输入的六个数字都小于等于6,且两两不相同*/
   0x0000000000401153 <+95>:	lea    0x18(%rsp),%rsi  //%rsi对应于循环终点
   0x0000000000401158 <+100>:	mov    %r14,%rax	//这里%r14存放的值是%rsp,作为循环起点
   0x000000000040115b <+103>:	mov    $0x7,%ecx
   0x0000000000401160 <+108>:	mov    %ecx,%edx
   0x0000000000401162 <+110>:	sub    (%rax),%edx
   0x0000000000401164 <+112>:	mov    %edx,(%rax)	//输入的每个数字x[i]都变成了7-x[i]
   0x0000000000401166 <+114>:	add    $0x4,%rax	//指向下一个数字,判断是否结束循环
   0x000000000040116a <+118>:	cmp    %rsi,%rax
   0x000000000040116d <+121>:	jne    0x401160 <phase_6+108>
   /*前面做的任务是将输入的每个数字x[i]都变成了7-x[i]*/
   0x000000000040116f <+123>:	mov    $0x0,%esi
   0x0000000000401174 <+128>:	jmp    0x401197 <phase_6+163>
   0x0000000000401176 <+130>:	mov    0x8(%rdx),%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)	//每个占8个字节
   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>
   /*前面做的任务是修改(%rsp+32)到(%rsp+80)的内存值(8字节为单位),根据输入的六个数字来决定*/
   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>
 /*让之前(%rsp+32)到(%rsp+80)的六块内存区域,前一块内存区域后八字节存放指向下一块内存区域的指针,链表结构*/
   0x00000000004011d2 <+222>:	movq   $0x0,0x8(%rdx) 		//链表尾节点的指针指向0
   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>
   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>:	retq   

第一块是由两个循环嵌套组成的。外层循环依次遍历输入的第一个数字到第六个数字。外层循环首先判断输入的数字是否不超过6,紧接着内存循环遍历当前输入的数字到末尾剩下的数字,实现的功能是判断输入的六个数字两两不相等。
第二块比较简单,用一个寄存器%edx依次保存7与输入的数字的差值,再将差值覆盖原来输入的数字。
第三块是根据前两块实现后得到的新的6个数字进行操作。在内存0x6032d0到0x60332f中存放着6个大小为16字节的结构体变量。每个结构体变量的后8个字节都存放着一个指针,指向下一个结构体变量的内存位置。这部分代码是根据新的6个数字的数值来重新给这几个结构体排序,将这6个结构体变量的初始地址按照排好的顺序依次存放在栈指针偏移0x20~0x50的位置。
第四块是修改6个结构体变量中存放的指针值,按照重新排好的顺序让前一个指针指向后一个指针,所以这里基本可以确定第三第四块实现的是重新排列一个链表节点的功能。
第五块是验证新排列的六个结构体变量是否是按照从大到小的顺序来排列的,如果验证满足,则炸弹就拆除了。

分析后发现:node3.value>node4.value>node5.value>node6.value>node1.value>node2.value
所以第二块得到的新的六个数字为:3,4,5,6,1,2
所以一开始的输入的六个数字为:4 3 2 1 6 5

放一张图来纪念一下

总结

bomb lab实验总共花了将近两天的时间,做完实验感觉对课程的理解加深了。不过看汇编语言还是挺累的,后面看的有点烦(lol)。实验的设计很有意思,不愧为cmu,真希望国内一些大学的课程实验也改成类似的形式。

  • 9
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值