CSAPP Lab2:Bomb Lab

简介

实验提供了一个可执行文件bomb,并且埋下了6个"炸弹"。
需要通过反汇编查看bomb,找出6把钥匙字符串,破解6个炸弹。

  • 反汇编命令:objdump -d xxx

一、阶段一

第一阶段,炸弹所在函数为phase1,其反汇编代码为:

  400ee0:	48 83 ec 08          	sub    $0x8,%rsp   //创建栈帧,大小为8个字节
  400ee4:	be 00 24 40 00       	mov    $0x402400,%esi  //将立即数402400存储到%esi中作为参数
  400ee9:	e8 4a 04 00 00       	callq  401338 <strings_not_equal> //调用strings_not_equal函数
  400eee:	85 c0                	test   %eax,%eax //判断结果是否等于0
  400ef0:	74 05                	je     400ef7 <phase_1+0x17>//如果等于0,则跳转到400ef7处执行
  400ef2:	e8 43 05 00 00       	callq  40143a <explode_bomb>//不等于0,即字符串不相等,爆炸
  400ef7:	48 83 c4 08          	add    $0x8,%rsp
  400efb:	c3                   	retq   

看了上面的汇编结合main函数的汇编,我们可以得知,传入strings_not_equal函数进行比较的字符串为我们输入的字符串首地址和0x402400这个地址处的字符串,如果得到的结果是等于0的,那么就跳过触发炸弹的语句,而如果为false,则触发炸弹。
因此,我们再看strings_not_equal的汇编代码,查看具体的字符串比较逻辑。

//  strings_not_equal函数
  401338:	41 54                	push   %r12      
  40133a:	55                   	push   %rbp
  40133b:	53                   	push   %rbx     //保存被调用者保存寄存器,phase1函数并未存储值到这三个寄存器,不管
  40133c:	48 89 fb             	mov    %rdi,%rbx  //将%rdi中保存的参数1放入%rbx中,该参数由main函数指定,其实就是我们输入的字符串的首地址。
  40133f:	48 89 f5             	mov    %rsi,%rbp //将%rsi中的参数2(0x402400)放入基址寄存器%rbp,即破解炸弹字符串的首地址
  401342:	e8 d4 ff ff ff       	callq  40131b <string_length>//调用string_length,计算参数1的长度,即我们输入的字符串的长度
  401347:	41 89 c4             	mov    %eax,%r12d //将返回的结果放入%r12d
  40134a:	48 89 ef             	mov    %rbp,%rdi //计算参数2的长度
  40134d:	e8 c9 ff ff ff       	callq  40131b <string_length>
  401352:	ba 01 00 00 00       	mov    $0x1,%edx //将参数2的长度存入%edx寄存器
  401357:	41 39 c4             	cmp    %eax,%r12d //比较长度,如果不等,跳至40139b执行
  40135a:	75 3f                	jne    40139b <strings_not_equal+0x63>
  //如果长度相同,则比较每一个字符,%rbx保存的是我们输入的字符串的首地址
  //%rbp保存的是破解字符串的首地址(0x402400)
  40135c:	0f b6 03             	movzbl (%rbx),%eax //取出输入的第一个字符
  40135f:	84 c0                	test   %al,%al //判断是否等于0,即空串,等于跳401388 
  401361:	74 25                	je     401388 <strings_not_equal+0x50>
  401363:	3a 45 00             	cmp    0x0(%rbp),%al //比较第一个字符,等于跳401372,不等跳转到40138f
  401366:	74 0a                	je     401372 <strings_not_equal+0x3a>
  401368:	eb 25                	jmp    40138f <strings_not_equal+0x57>
  40136a:	3a 45 00             	cmp    0x0(%rbp),%al
  40136d:	0f 1f 00             	nopl   (%rax)
  401370:	75 24                	jne    401396 <strings_not_equal+0x5e>
  401372:	48 83 c3 01          	add    $0x1,%rbx //读取我们输入的字符串的下一个字符
  401376:	48 83 c5 01          	add    $0x1,%rbp //读取下一个字符
  40137a:	0f b6 03             	movzbl (%rbx),%eax //判断是否为结束字符
  40137d:	84 c0                	test   %al,%al //不为0.即不为结束字符,跳转到40136a进行比较,形成循环
  40137f:	75 e9                	jne    40136a <strings_not_equal+0x32>
  401381:	ba 00 00 00 00       	mov    $0x0,%edx //%edx置0,如果循环中一直没有跳转到40138f,然后我们输入的字符串到达末尾,跳转到40139b
  401386:	eb 13                	jmp    40139b <strings_not_equal+0x63>
  401388:	ba 00 00 00 00       	mov    $0x0,%edx  //输出0
  40138d:	eb 0c                	jmp    40139b <strings_not_equal+0x63>
  40138f:	ba 01 00 00 00       	mov    $0x1,%edx  //输出1
  401394:	eb 05                	jmp    40139b <strings_not_equal+0x63>
  401396:	ba 01 00 00 00       	mov    $0x1,%edx   //输出1
  40139b:	89 d0                	mov    %edx,%eax  //将edx中的内容当作返回值返回
  40139d:	5b                   	pop    %rbx
  40139e:	5d                   	pop    %rbp
  40139f:	41 5c                	pop    %r12
  4013a1:	c3                   	retq   

通过上面的汇编代码,我们可以得知,我们输入的字符作为参数1,立即数0x402400作为参数2,传入该函数,首先判断这两个参数所表示的字符串的长度,如果长度不相等,则将参数2的长度作为返回值返回,此时phase再判断,结果不为0,爆炸,因此我们的目标就是输入一个字符串与起始地址为0x402400的字符串进行比较,比较结果为0,因此阶段1的答案为 0x402400地址处的字符串。

通过gdb调试bomb,查看0x402400处的内容,得到字符串:

/*
1. gdb bomb
2. x/s 0x402400
*/
"Border relations with Canada have never been better."

二、阶段二

phase2的汇编代码如下:

  400efc:	55                   	push   %rbp
  400efd:	53                   	push   %rbx
  400efe:	48 83 ec 28          	sub    $0x28,%rsp  //分配28个字节的空间用于栈帧
  400f02:	48 89 e6             	mov    %rsp,%rsi   //栈顶地址作为参数2
  400f05:	e8 52 05 00 00       	callq  40145c <read_six_numbers>//调用read_six_numbers函数
  400f0a:	83 3c 24 01          	cmpl   $0x1,(%rsp) //判断栈顶元素是否为1,此时栈顶存储数组的首地址,即数组的第一个元素
  400f0e:	74 20                	je     400f30 <phase_2+0x34> //如果等于1,跳转400f30
  400f10:	e8 25 05 00 00       	callq  40143a <explode_bomb> //触发炸弹 
  400f15:	eb 19                	jmp    400f30 <phase_2+0x34> //跳转到400f30
  400f17:	8b 43 fc             	mov    -0x4(%rbx),%eax  //循环开始,取当前元素的前一个元素
  400f1a:	01 c0                	add    %eax,%eax //a[i-1]*2
  400f1c:	39 03                	cmp    %eax,(%rbx) //判断当前元素是否为前一个元素的两倍
  400f1e:	74 05                	je     400f25 <phase_2+0x29>//如果是,则跳过炸弹 
  400f20:	e8 15 05 00 00       	callq  40143a <explode_bomb>//否则引发爆炸
  400f25:	48 83 c3 04          	add    $0x4,%rbx //%rbx向前推进
  400f29:	48 39 eb             	cmp    %rbp,%rbx //比较是否到达数组末尾,如果是则推出,否则继续
  400f2c:	75 e9                	jne    400f17 <phase_2+0x1b>
  400f2e:	eb 0c                	jmp    400f3c <phase_2+0x40> //跳转到return语句
  400f30:	48 8d 5c 24 04       	lea    0x4(%rsp),%rbx //将%rsp + 4的地址送入%rbx
  400f35:	48 8d 6c 24 18       	lea    0x18(%rsp),%rbp //将%rsp + 24的地址送入%rbp
  400f3a:	eb db                	jmp    400f17 <phase_2+0x1b> //跳转到400f17
  400f3c:	48 83 c4 28          	add    $0x28,%rsp
  400f40:	5b                   	pop    %rbx
  400f41:	5d                   	pop    %rbp
  400f42:	c3                   	retq 

结合上面的汇编代码,我们可以得知,首先分配28个字节作为栈帧,然后将我们输入的字符串作为第一个参数、栈顶指针%rsp作为第二个参数调用read_six_numbers函数读取6个字符,即将我们的输入取前6个存放到一个字符数组中,而这个字符数组的首地址为phase2函数的栈顶指针。因此,6个元素相对栈顶的偏移为%rsp、%rsp+4一直到%rsp + 24,然后判断第一个元素是否等于1,如果等于1,进行接下来的循环判断,否则触发炸弹。循环内部,判断当前数组元素a[i]是否为其前一个元素a[i-1]的两倍,直到最后一个元素。如果有任意一个元素不是前一个元素的两倍,则触发炸弹。

read_six_numbers调用了8参数的sscanf,其中参数1为我们传入的字符串、参数2为输入模式,即"%d %d %d %d %d %d",然后将数组的6个元素分别放入%rdx,%rcx,%r8,%r9,以及在栈中存放两个。对解决此题的帮助不大,通过函数的名字即可知道大概的逻辑,此处不在列出。

因此本题的解码字符串也可以得到,即 1、2、4、8、16、32

三、阶段三

phase3的汇编代码如下所示:

  400f43:	48 83 ec 18          	sub    $0x18,%rsp //申请24个比特的栈空间
  400f47:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx//栈顶+12位置的地址作为第四个参数
  400f4c:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx//栈顶+8位置的地址作为第三个参数
  400f51:	be cf 25 40 00       	mov    $0x4025cf,%esi//地址0x4025cf作为第二个参数,内容为%d %d
  400f56:	b8 00 00 00 00       	mov    $0x0,%eax  //将%rax置0
  400f5b:	e8 90 fc ff ff       	callq  400bf0 <__isoc99_sscanf@plt>
  //前面汇编的意思为,申请24个比特,通过scanf函数读取两个整数,放入到栈顶+8和栈顶+12的位置
  400f60:	83 f8 01             	cmp    $0x1,%eax //比较结果是否为1,
                                                     //scanf返回值存放的是读取的字符串的个数,phase2调用读取6个整数的scanf,返回值为6
  400f63:	7f 05                	jg     400f6a <phase_3+0x27> //如果大于1,跳转到400f6a
  400f65:	e8 d0 04 00 00       	callq  40143a <explode_bomb> //触发炸弹,因为字符个数小于2
  400f6a:	83 7c 24 08 07       	cmpl   $0x7,0x8(%rsp) //比较读入的第一个int与7的大小
  400f6f:	77 3c                	ja     400fad <phase_3+0x6a>//如果大于等于,跳转到400fad,触发炸弹
  400f71:	8b 44 24 08          	mov    0x8(%rsp),%eax //将%rsp+8处的元素保存到%eax中
  400f75:	ff 24 c5 70 24 40 00 	jmpq   *0x402470(,%rax,8)//跳转到M[8*%rax + 0x402470]的位置
  //上一句为间接跳转,跳转到地址为M[8*%rax + 0x402470]内存单元存储的值的地方
  //当%rax存储0时,M[0x402470]处存储的值为0x00400f7c,即跳转到地址为0x00400f7c的地方
  //此时将0xcf转移到%eax中,即下一句汇编代码
  400f7c:	b8 cf 00 00 00       	mov    $0xcf,%eax //移动0xcf到%eax中
  400f81:	eb 3b                	jmp    400fbe <phase_3+0x7b> //跳转到400fbe
  400f83:	b8 c3 02 00 00       	mov    $0x2c3,%eax //移动0x2c3到%eax中
  400f88:	eb 34                	jmp    400fbe <phase_3+0x7b>
  400f8a:	b8 00 01 00 00       	mov    $0x100,%eax //移动0x100到%eax中
  400f8f:	eb 2d                	jmp    400fbe <phase_3+0x7b>
  400f91:	b8 85 01 00 00       	mov    $0x185,%eax //移动0x185到%eax中
  400f96:	eb 26                	jmp    400fbe <phase_3+0x7b>
  400f98:	b8 ce 00 00 00       	mov    $0xce,%eax //移动0xce到%eax中
  400f9d:	eb 1f                	jmp    400fbe <phase_3+0x7b>
  400f9f:	b8 aa 02 00 00       	mov    $0x2aa,%eax //移动0x2aa到%eax中
  400fa4:	eb 18                	jmp    400fbe <phase_3+0x7b>
  400fa6:	b8 47 01 00 00       	mov    $0x147,%eax //移动0x147到%eax中
  400fab:	eb 11                	jmp    400fbe <phase_3+0x7b>
  400fad:	e8 88 04 00 00       	callq  40143a <explode_bomb> //爆炸
  400fb2:	b8 00 00 00 00       	mov    $0x0,%eax   //%eax置为0
  400fb7:	eb 05                	jmp    400fbe <phase_3+0x7b> //跳转400fbe
  400fb9:	b8 37 01 00 00       	mov    $0x137,%eax  //%exa置为0x137
  400fbe:	3b 44 24 0c          	cmp    0xc(%rsp),%eax //%rsp+12位置的值与%eax中的比较
  400fc2:	74 05                	je     400fc9 <phase_3+0x86>//如果等于,跳转400fc9
  400fc4:	e8 71 04 00 00       	callq  40143a <explode_bomb>//爆炸
  400fc9:	48 83 c4 18          	add    $0x18,%rsp //返回
  400fcd:	c3                   	retq 

从汇编代码可以得知,读入两个整数,且第一个数要不大于7,然后用第一个数*8 + 0x402470得到一个内存地址1,然后将该内存地址1处存储的值作为一个地址2,跳转到地址2。然后执行地址2处的代码。
所以,这一阶段答案不止一种,而是有7种,当第一个数输入0,第二个数要输入0xcf,即207
输入1,第二个数要输入0x137

tips:

  • 此处需要使用GDB进行调试,获取对应内存位置的值
  • 通过x/w读取从 0x402470 + 8i开始4个B的内容,当 i = 0时,输出 0x00400f7c
  • 然后执行0x400f7c处的代码
  • x/s是将对应地址处的内容以字符串的形式输出

四、阶段四

phase4的汇编代码如下:

  40100c:	48 83 ec 18          	sub    $0x18,%rsp 
  401010:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx
  401015:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx
  40101a:	be cf 25 40 00       	mov    $0x4025cf,%esi // "%d  %d"放入到%esi中 
  40101f:	b8 00 00 00 00       	mov    $0x0,%eax   
  401024:	e8 c7 fb ff ff       	callq  400bf0 <__isoc99_sscanf@plt>
  401029:	83 f8 02             	cmp    $0x2,%eax
  40102c:	75 07                	jne    401035 <phase_4+0x29>
  //上段汇编的逻辑为,申请24B栈空间,调用scanf函数读取两个整数,如果返回值不等于2,则跳转
  //0x401035执行爆炸函数,读取的数放到 %rsp+8 和 %rsp+12 的位置
  40102e:	83 7c 24 08 0e       	cmpl   $0xe,0x8(%rsp) //比较输入的数和0xe的大小
  401033:	76 05                	jbe    40103a <phase_4+0x2e> //如果14小于等于,爆炸
  401035:	e8 00 04 00 00       	callq  40143a <explode_bomb> //爆炸
  //上段汇编,确保输入的第一个数要小于14
  40103a:	ba 0e 00 00 00       	mov    $0xe,%edx //将%edx置为14
  40103f:	be 00 00 00 00       	mov    $0x0,%esi //将%esi置为0
  401044:	8b 7c 24 08          	mov    0x8(%rsp),%edi//将%edi置为 第一个输入的值 
  401048:	e8 81 ff ff ff       	callq  400fce <func4> //调用func4
  40104d:	85 c0                	test   %eax,%eax //判断结果是否为0
  40104f:	75 07                	jne    401058 <phase_4+0x4c> //不为0,跳转401058处爆炸
  401051:	83 7c 24 0c 00       	cmpl   $0x0,0xc(%rsp) //比较输入的第二个数是否为0
  401056:	74 05                	je     40105d <phase_4+0x51> //等于0跳转40105d
  401058:	e8 dd 03 00 00       	callq  40143a <explode_bomb> //执行爆炸
  40105d:	48 83 c4 18          	add    $0x18,%rsp //返回
  401061:	c3                   	retq   

从上段代码可知,我们需要输入两个数,第一个数需要小于14,且第二个数需要为0
然后将 输入的第一个数、0、14作为参数,调用func4,如果func4返回值不是0,则爆炸,否则再判断第二个数是否为0,所以接下来需要看func4的代码逻辑

func4的汇编代码如下:

  //参数1 %rdi,设为x,参数2 %rsi,设为y,参数3 %rdx 设为z
  400fce:	48 83 ec 08          	sub    $0x8,%rsp //申请8B的栈空间
  400fd2:	89 d0                	mov    %edx,%eax //将14取出(参数3) z = 14
  400fd4:	29 f0                	sub    %esi,%eax //%rsi存储的是参数2(0),14 - 0
  400fd6:	89 c1                	mov    %eax,%ecx //保存到%ecx,等价将%ecx置为14,即temp = z
  400fd8:	c1 e9 1f             	shr    $0x1f,%ecx //逻辑右移31位 
  400fdb:	01 c8                	add    %ecx,%eax //相加,相当于 temp + temp*2^-31
  400fdd:	d1 f8                	sar    %eax //算数右移--->res
  400fdf:	8d 0c 30             	lea    (%rax,%rsi,1),%ecx //将地址%rsi + %rax保存到%ecx,相当于计算y +  res
  400fe2:	39 f9                	cmp    %edi,%ecx //比较x 与%ecx的大小
  400fe4:	7e 0c                	jle    400ff2 <func4+0x24> //如果小于等于,跳转递归结束条件
  400fe6:	8d 51 ff             	lea    -0x1(%rcx),%edx //不小于,%ecx-1,继续递归
  400fe9:	e8 e0 ff ff ff       	callq  400fce <func4>
  400fee:	01 c0                	add    %eax,%eax //将返回值*2 
  400ff0:	eb 15                	jmp    401007 <func4+0x39> //返回
  400ff2:	b8 00 00 00 00       	mov    $0x0,%eax //将%eax置为0
  400ff7:	39 f9                	cmp    %edi,%ecx //比较x与%ecx的结果
  400ff9:	7d 0c                	jge    401007 <func4+0x39> //如果大于等于
  400ffb:	8d 71 01             	lea    0x1(%rcx),%esi  //将%rcx+1
  400ffe:	e8 cb ff ff ff       	callq  400fce <func4> //再次调用func4
  401003:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax //如果小于,则将结果*2 + 1后返回
  401007:	48 83 c4 08          	add    $0x8,%rsp
  40100b:	c3                   	retq 

由上面的func4的汇编代码可以大概看出,这个函数是一个递归函数,我们传入了3个参数,分别是我们输入的第一个数,0以及14,转换为C代码,大概是这个样子

//参数为x,y,z,其中y = 0,z = 14,函数名func4
int func4(int x,int y,int z){	
	int res= z - y;
	unsigned int temp = res;
	temp = temp >> 31;//取出z-y的最高位
	res += temp;
	res >>= 1;//算术右移1位
	int var = y + res;
	if(x < var){
	   z = var -1;
	   res = func4(x,y,z);
	   res *= 2;
	   return res;
	}else{
	   //400fe4的跳转逻辑
	   res = 0;
	   if(var >= x){
	      return res;
	   }else{
	      res += 1;
	      return 2*func4(x,res,z)+1;
	   }
	}
}	  

–看不太懂这代码啥意思。。。–

但是phase4的大体逻辑就是输入两个数,第二个要为0,第一个要使得func4(x)返回0

func4的逻辑看不懂但看网上大佬,使用暴力挨个测试,得到答案为第一个参数0 1 3 7任选一个,第二个为0

暴力代码:

for (int i = 0; i <= 14; i++)
        if (!func4(i, 0, 14))
            cout << i << " ";

五、阶段五

phase5的汇编代码如下所示:

  401062:	53                   	push   %rbx //保存被调用者保存寄存器的值
  401063:	48 83 ec 20          	sub    $0x20,%rsp //申请32B的栈空间
  401067:	48 89 fb             	mov    %rdi,%rbx  //将我们输入的字符串首地址放入%rbx
  40106a:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax //%rax保存金丝雀值
  401071:	00 00 
  401073:	48 89 44 24 18       	mov    %rax,0x18(%rsp) //将金丝雀值存放到%rsp+24的位置
  401078:	31 c0                	xor    %eax,%eax //将%eax重新置为0
  40107a:	e8 9c 02 00 00       	callq  40131b <string_length> //调用string_length函数
  40107f:	83 f8 06             	cmp    $0x6,%eax //判断结果是否为6
  401082:	74 4e                	je     4010d2 <phase_5+0x70> //如果等于6,跳转4010d2
  401084:	e8 b1 03 00 00       	callq  40143a <explode_bomb> //否则爆炸
  //上段汇编主要生成金丝雀值,保存到%rsp + 24的位置
  //然后调用string_length函数,求我们输入的字符串的长度
  //如果长度不为6,则爆炸,等于6,跳转到0x4010d2处执行
  //疑问------401089这句话的作用是什么?爆炸执行完毕之后执行?这样长度不是6,继续执行会异常吧?
  401089:	eb 47                	jmp    4010d2 <phase_5+0x70> 
  40108b:	0f b6 0c 03          	movzbl (%rbx,%rax,1),%ecx //%ecx = (%rbx + %rax)
  //此时%ecx中存储的是temp,%rax中存储的是索引,%rbx为输入字符串首地址
  //所以,相当于 temp = a[i];
  40108f:	88 0c 24             	mov    %cl,(%rsp) //将该字符压入栈
  401092:	48 8b 14 24          	mov    (%rsp),%rdx 
  401096:	83 e2 0f             	and    $0xf,%edx //将刚入栈的字符低四位与0xf进行&操作
  //下一局汇编代码取出%rdx + 0x4024b0处的值
  //通过GDB查看得知(x/s 0x4024b0),此处为一个字符数组,值如下所示:
  //"maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
  401099:	0f b6 92 b0 24 40 00 	movzbl 0x4024b0(%rdx),%edx //取出某个字符,放入%edx
  4010a0:	88 54 04 10          	mov    %dl,0x10(%rsp,%rax,1)//将该字符放入rsp+i+16的位置
  4010a4:	48 83 c0 01          	add    $0x1,%rax //索引i++
  4010a8:	48 83 f8 06          	cmp    $0x6,%rax //判断索引是否到达6
  4010ac:	75 dd                	jne    40108b <phase_5+0x29> //不等于,跳至40108b
  4010ae:	c6 44 24 16 00       	movb   $0x0,0x16(%rsp) //如果到达最后,将0存入%rsp+22的位置
  4010b3:	be 5e 24 40 00       	mov    $0x40245e,%esi //"flyers"放入%esi
  4010b8:	48 8d 7c 24 10       	lea    0x10(%rsp),%rdi
  4010bd:	e8 76 02 00 00       	callq  401338 <strings_not_equal>
  4010c2:	85 c0                	test   %eax,%eax
  4010c4:	74 13                	je     4010d9 <phase_5+0x77>
  //4010b3---4010c4的逻辑为:判断%rsp + 16开始的字符串与”flyers“是否相等,如果相等则跳转4010d9
  //否则爆炸
  4010c6:	e8 6f 03 00 00       	callq  40143a <explode_bomb>
  4010cb:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)
  4010d0:	eb 07                	jmp    4010d9 <phase_5+0x77>
  4010d2:	b8 00 00 00 00       	mov    $0x0,%eax //将%eax置0
  4010d7:	eb b2                	jmp    40108b <phase_5+0x29>//跳转到0x40108b执行
  4010d9:	48 8b 44 24 18       	mov    0x18(%rsp),%rax //释放栈空间,准备返回
  4010de:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax //判断金丝雀是否更改过
  4010e5:	00 00 
  4010e7:	74 05                	je     4010ee <phase_5+0x8c> //如果金丝雀值没被改,则返回
  4010e9:	e8 42 fa ff ff       	callq  400b30 <__stack_chk_fail@plt>
  4010ee:	48 83 c4 20          	add    $0x20,%rsp
  4010f2:	5b                   	pop    %rbx
  4010f3:	c3                   	retq   

阶段5的汇编代码大体逻辑就是,首先设置一个金丝雀,然后将我们输入的字符串,挨个取出,取其低四位,转换为int值之后作为索引,从"maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"中取对应的字符,然后取完之后拼成一个字符串,判断是否和"flyers"相等
因此我们输入的字符串的低四位的所对应的整数值是9、15、14、5、6、7,高四位无所谓,但最好处于ASCII码表中且属于英文字符。
因此,选择每位加64,得到 73=I 79=O 78=N 69=E 70=F 77=G
即 IONEFG
每位加96也可以,即 ionefg

六、阶段六

phase6的汇编代码如下:

  4010f4:	41 56                	push   %r14
  4010f6:	41 55                	push   %r13
  4010f8:	41 54                	push   %r12
  4010fa:	55                   	push   %rbp
  4010fb:	53                   	push   %rbx
  //保存被调用者栈寄存器
  4010fc:	48 83 ec 50          	sub    $0x50,%rsp
  //分配90个字节
  401100:	49 89 e5             	mov    %rsp,%r13
  401103:	48 89 e6             	mov    %rsp,%rsi //以栈顶作为第二个参数调用read_six_numbers
  401106:	e8 51 03 00 00       	callq  40145c <read_six_numbers>
  //read_six_numbers向栈中存放了6个整数元素,偏移从0开始,假设数组为num
  40110b:	49 89 e6             	mov    %rsp,%r14 //栈顶存%r14
  40110e:	41 bc 00 00 00 00    	mov    $0x0,%r12d //将%r12d置为0,相当于i = 0
  401114:	4c 89 ed             	mov    %r13,%rbp  
  //%r13保存%rsp + 偏移量的值,首次执行为%rsp的地址,后面40114d每次将%rsp的偏移量+4,访问数组下一个元素
  401117:	41 8b 45 00          	mov    0x0(%r13),%eax //将当前元素放入%eax,即num[i]
  40111b:	83 e8 01             	sub    $0x1,%eax //num[i]-1
  40111e:	83 f8 05             	cmp    $0x5,%eax //与5进行比较
  401121:	76 05                	jbe    401128 <phase_6+0x34> //小于等于跳转,否则爆炸
  401123:	e8 12 03 00 00       	callq  40143a <explode_bomb>
  401128:	41 83 c4 01          	add    $0x1,%r12d //i++--->i+1
  40112c:	41 83 fc 06          	cmp    $0x6,%r12d //进行循环判断i<6,等于6跳转到401153 
  401130:	74 21                	je     401153 <phase_6+0x5f>
  401132:	44 89 e3             	mov    %r12d,%ebx //不等于6,执行循环,将i放入%ebx,即j=i
  401135:	48 63 c3             	movslq %ebx,%rax //将j放到%rax
  401138:	8b 04 84             	mov    (%rsp,%rax,4),%eax //将%rsp+j*4处的元素放到%eax,即num[j]  
  40113b:	39 45 00             	cmp    %eax,0x0(%rbp)//比较num[i]与num[j]
  40113e:	75 05                	jne    401145 <phase_6+0x51>//如果等于则爆炸
  401140:	e8 f5 02 00 00       	callq  40143a <explode_bomb>
  401145:	83 c3 01             	add    $0x1,%ebx //将%ebx + 1-->j+1
  401148:	83 fb 05             	cmp    $0x5,%ebx//比较i是否等于5 
  40114b:	7e e8                	jle    401135 <phase_6+0x41>//小于等于跳转到401135
  40114d:	49 83 c5 04          	add    $0x4,%r13 //将%r13 + 4,相当于访问栈中数组下一个元素
  401151:	eb c1                	jmp    401114 <phase_6+0x20>//跳回到401114
  //上段代码的逻辑大概为一个循环,其中%r14保存栈顶,%r13记录当前数组元素,%r12d记录索引i
  //然后i从0到5进行6次循环,判断每个数要小于6,否则爆炸,然后将i+1赋给j
  //然后再内层循环5次,判断num[i]与num[j]的大小,如果相等则爆炸
  //外层循环起始地址为401114,内层循环地址为401135
  401153:	48 8d 74 24 18       	lea    0x18(%rsp),%rsi //将rsp+24处的地址赋给%rsi
  401158:	4c 89 f0             	mov    %r14,%rax //将%r14赋给%rax,即栈顶%rsp
  40115b:	b9 07 00 00 00       	mov    $0x7,%ecx //将%ecx的置为7,temp = 7
  401160:	89 ca                	mov    %ecx,%edx //temp2 = temp
  401162:	2b 10                	sub    (%rax),%edx //temp2 -= num[0]
  401164:	89 10                	mov    %edx,(%rax) //num[0] = temp2
  401166:	48 83 c0 04          	add    $0x4,%rax //rax指向下一个数组下一个元素
  40116a:	48 39 f0             	cmp    %rsi,%rax //比较是否到达数组的末尾,即偏移量24处
  40116d:	75 f1                	jne    401160 <phase_6+0x6c> //不等于跳回401160继续
  //上段代码也是一个循环,从nums[0]开始遍历整个数组,将数组中的元素都变为7-nums[i]
  40116f:	be 00 00 00 00       	mov    $0x0,%esi //将%esi置为0,即index = 0
  401174:	eb 21                	jmp    401197 <phase_6+0xa3> //跳转到401197
  401176:	48 8b 52 08          	mov    0x8(%rdx),%rdx //rdx+8
  40117a:	83 c0 01             	add    $0x1,%eax //eax + 1
  40117d:	39 c8                	cmp    %ecx,%eax //比较num[index]与eax的大小
  40117f:	75 f5                	jne    401176 <phase_6+0x82>//不等于跳401176
  401181:	eb 05                	jmp    401188 <phase_6+0x94>//等于跳401188 
  401183:	ba d0 32 60 00       	mov    $0x6032d0,%edx
  //0x6032d0位置存储的是一个链表,可以通过GDB查询得到
  401188:	48 89 54 74 20       	mov    %rdx,0x20(%rsp,%rsi,2)//将%rdx元素放于%rsp+32的位置
  40118d:	48 83 c6 04          	add    $0x4,%rsi //index + 4
  401191:	48 83 fe 18          	cmp    $0x18,%rsi //判断index是否超24,即到达数组末尾
  401195:	74 14                	je     4011ab <phase_6+0xb7> //如果是,跳出循环
  401197:	8b 0c 34             	mov    (%rsp,%rsi,1),%ecx //将num[index]赋给%ecx
  40119a:	83 f9 01             	cmp    $0x1,%ecx //比较是否小于1,跳转到401183
  40119d:	7e e4                	jle    401183 <phase_6+0x8f>
  40119f:	b8 01 00 00 00       	mov    $0x1,%eax //不小于1则将%eax置为1
  4011a4:	ba d0 32 60 00       	mov    $0x6032d0,%edx //将表头放到%edx中 
  4011a9:	eb cb                	jmp    401176 <phase_6+0x82>//跳转到401176
  //上段代码为设置一个偏移量index,此时index为0,即判断num[0]处的元素是否小于1,如果小于1则将链表首地址放置于%rsp+32位置处
  //然后开始循环,结束条件为到达数组尾部
  //每次循环首先index+4更新偏移量,即循环是从num[0]开始
  //每次循环判断%rsp+index,即num[i]是否小于1,如果小于1,则将rsp + 2*index + 32处的值置为表头元素
  //如果大于1,则设置一个temp = 1,然后判断,如果temp != num[i],则链表取下一个元素,直到temp=num[i]
  //综上,这一段循环的逻辑为,从%rsp+32位置开始,设置栈内容为List-->get(num[i])
  //假设上面的逻辑,使得我们在%rsp+32位置处新建了一个数组 num2,容量为6,每个元素8字节,存储的是链表节点的指针
  4011ab:	48 8b 5c 24 20       	mov    0x20(%rsp),%rbx //rbx = &num2[0]
  4011b0:	48 8d 44 24 28       	lea    0x28(%rsp),%rax //rax = &num2[1]
  4011b5:	48 8d 74 24 50       	lea    0x50(%rsp),%rsi //rsi = &num2[5]
  4011ba:	48 89 d9             	mov    %rbx,%rcx //rcx = rbx,即设置一个result = num2[0]
  4011bd:	48 8b 10             	mov    (%rax),%rdx //rdx=nums[i],因为后面会更改%rax
  4011c0:	48 89 51 08          	mov    %rdx,0x8(%rcx)//将rdx得值设置到%rcx + 8的地方 
  4011c4:	48 83 c0 08          	add    $0x8,%rax //将rax+8指向下一个元素,即rax=num2[i++]
  4011c8:	48 39 f0             	cmp    %rsi,%rax //比较num2[i++]是否等于num2[5]
  4011cb:	74 05                	je     4011d2 <phase_6+0xde>//如果等于说明到达数组末尾,跳转到4011d2
  4011cd:	48 89 d1             	mov    %rdx,%rcx //没有到达末尾,result += num2[i]
  4011d0:	eb eb                	jmp    4011bd <phase_6+0xc9>//跳转回4011bd
  //上段汇编的代码逻辑比较简单,即我们通过list创造了一个长度为6的数组,数组每个元素占8个字节
  //设置一个result = 第一个元素,然后开始一个循环,累加数组的元素,直到数组末尾
  4011d2:	48 c7 42 08 00 00 00 	movq   $0x0,0x8(%rdx) //将数组后的8个字节设置为0
  4011d9:	00 
  4011da:	bd 05 00 00 00       	mov    $0x5,%ebp //ebp = 5
  4011df:	48 8b 43 08          	mov    0x8(%rbx),%rax // rax = &num2[1]
  4011e3:	8b 00                	mov    (%rax),%eax //rax = num2[1]的低4字节
  4011e5:	39 03                	cmp    %eax,(%rbx) //比较前一个数组元素的低4字节
  4011e7:	7d 05                	jge    4011ee <phase_6+0xfa> //如果大于等于则爆炸
  4011e9:	e8 4c 02 00 00       	callq  40143a <explode_bomb>
  4011ee:	48 8b 5b 08          	mov    0x8(%rbx),%rbx
  4011f2:	83 ed 01             	sub    $0x1,%ebp
  4011f5:	75 e8                	jne    4011df <phase_6+0xeb>
  4011f7:	48 83 c4 50          	add    $0x50,%rsp
  //上段代码为比较num2数组,判断num2数组中的每个元素的低4字节是否比前一个元素的低4字节小,
  //如果不小于则爆炸
  4011fb:	5b                   	pop    %rbx
  4011fc:	5d                   	pop    %rbp
  4011fd:	41 5c                	pop    %r12
  4011ff:	41 5d                	pop    %r13
  401201:	41 5e                	pop    %r14
  401203:	c3                   	retq   

phase6的汇编代码比较长,首先取6个数,需要每个数都小于7
然后将每个数 x 转换为 7- x
然后按照新得到的数字(将新得到的数字作为链表索引)来重新排列整个链表,使得链表的元素的低4字节是从大到小排列的(汇编中的注释我用的一个新的数组num2,其实num2相当于一个指针数组,存储链表元素的指针)
所以我们需要输入一组数字,a,b,c,d,e,f
然后使得链表元素 按照 7-a,7-b,7-c,7-d,7-e,7-f的顺序排列,刚好低四字节是从大到小排列的。

我们首先需要查看0x6032d0处的链表。通过GDB查看,结果如下:

(gdb) x/12gx 0x6032d0
0x6032d0 <node1>:       0x000000010000014c      0x00000000006032e0
0x6032e0 <node2>:       0x00000002000000a8      0x00000000006032f0
0x6032f0 <node3>:       0x000000030000039c      0x0000000000603300
0x603300 <node4>:       0x00000004000002b3      0x0000000000603310
0x603310 <node5>:       0x00000005000001dd      0x0000000000603320
0x603320 <node6>:       0x00000006000001bb      0x0000000000000000

按照低四字节从大到小排序,应该是 03 04 05 06 01 02
所以我们的输入应该是 a = 4,b = 3, c= 2, d= 1, e = 6, f = 5,即4 3 2 1 6 5

结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值