《深入理解计算机系统》(CSAPP)实验二 —— Bomb Lab

这是CSAPP的第二个实验,主要让我们理解代码的机器级表示,最重要的是理解每个寄存器的作用以及如何使用这些寄存器。本次的实验内容有点晦涩难懂,对于这些内容多看下习惯就好了。

  本次实验中的bomb文件*有7个炸弹问题(6个显式的和1个隐藏的),每条问题只有输入正确的答案才能进入下一题,否则则会触发爆炸。通过阅读bomb文件的汇编代码理解各个问题的运作方式,推出正确的输入答案。隐藏的问题需要通过gdb直接调用解决。

  我的编译环境:Ubuntu 16.04,gcc 5.4.0。

准备工作

  从官网下载到实验,解压后一共三个文件,具体如下图所示。

《深入理解计算机系统》(CSAPP)实验二 —— Bomb Lab

  readme中没写什么有用的内容,bomb文件是编译完成的文件,bomb.c是本次实验的源码,打开看下,大概浏览了一遍,一共有phase_1 ~ phase_6 6个炸弹,从命令行输入的内容必须要和phase函数中的一致,否则就会爆炸退出程序。phase函数并没有给出源码,所以无法得知其期望的字符串是什么。给了bomb可执行文件,我们就把这个文件反汇编下,从反汇编推算下其内容是什么。

  首先使用objdump -d bomb > bomb.asm命令生成反汇编文件。

《深入理解计算机系统》(CSAPP)实验二 —— Bomb Lab

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_16933601/article/details/109744569

  先运行bomb文件,提示没有权限,我的文件是从windwos拷贝到Linux虚拟机中的,所以会报这个错误。执行chmod +777 bomb 赋予权限。如下图所示。

《深入理解计算机系统》(CSAPP)实验二 —— Bomb Lab

  然后随便输入一些内容看下会有什么后果,如下图所示,提示已经爆炸。

《深入理解计算机系统》(CSAPP)实验二 —— Bomb Lab

phase_1

  下面从main函数开始分析下反汇编。

0000000000400da0 <main>:
  400da0:	53                   	push   %rbx
  400da1:	83 ff 01             	cmp    $0x1,%edi                  #if (argc == 1)
  400da4:	75 10                	jne    400db6 <main+0x16>         # 不相等就跳转到400db6
  400da6:	48 8b 05 9b 29 20 00 	mov    0x20299b(%rip),%rax        # 603748 <stdin@@GLIBC_2.2.5>
  400dad:	48 89 05 b4 29 20 00 	mov    %rax,0x2029b4(%rip)        # 603768 <infile> 相等就读取输入
  400db4:	eb 63                	jmp    400e19 <main+0x79>         #跳转到initialize_bomb
  400db6:	48 89 f3             	mov    %rsi,%rbx
  400db9:	83 ff 02             	cmp    $0x2,%edi                  #else if (argc == 2)
  400dbc:	75 3a                	jne    400df8 <main+0x58>         #不相等跳转到400df8
  400dbe:	48 8b 7e 08          	mov    0x8(%rsi),%rdi             
  400dc2:	be b4 22 40 00       	mov    $0x4022b4,%esi
  400dc7:	e8 44 fe ff ff       	callq  400c10 <fopen@plt>         
  400dcc:	48 89 05 95 29 20 00 	mov    %rax,0x202995(%rip)        # 603768 <infile>
  400dd3:	48 85 c0             	test   %rax,%rax
  400dd6:	75 41                	jne    400e19 <main+0x79>         #跳转到initialize_bomb
  400dd8:	48 8b 4b 08          	mov    0x8(%rbx),%rcx
  400ddc:	48 8b 13             	mov    (%rbx),%rdx
  400ddf:	be b6 22 40 00       	mov    $0x4022b6,%esi  
  400de4:	bf 01 00 00 00       	mov    $0x1,%edi                  #传参
  400de9:	e8 12 fe ff ff       	callq  400c00 <__printf_chk@plt>  #printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]);
  400dee:	bf 08 00 00 00       	mov    $0x8,%edi
  400df3:	e8 28 fe ff ff       	callq  400c20 <exit@plt>          #exit(8);
   400df8:	48 8b 16             	mov    (%rsi),%rdx
  400dfb:	be d3 22 40 00       	mov    $0x4022d3,%esi
  400e00:	bf 01 00 00 00       	mov    $0x1,%edi
  400e05:	b8 00 00 00 00       	mov    $0x0,%eax                  #传参
  400e0a:	e8 f1 fd ff ff       	callq  400c00 <__printf_chk@plt>  #printf("Usage: %s [<input_file>]\n", argv[0]);
  400e0f:	bf 08 00 00 00       	mov    $0x8,%edi
  400e14:	e8 07 fe ff ff       	callq  400c20 <exit@plt>          #exit(8);
  400e19:	e8 84 05 00 00       	callq  4013a2 <initialize_bomb>   #调用initialize_bomb();
  400e1e:	bf 38 23 40 00       	mov    $0x402338,%edi
  400e23:	e8 e8 fc ff ff       	callq  400b10 <puts@plt>          #printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
  400e28:	bf 78 23 40 00       	mov    $0x402378,%edi 
  400e2d:	e8 de fc ff ff       	callq  400b10 <puts@plt>          #printf("which to blow yourself up. Have a nice day!\n");
  400e32:	e8 67 06 00 00       	callq  40149e <read_line>         #调用read_line();
  400e37:	48 89 c7             	mov    %rax,%rdi                  #传参
  400e3a:	e8 a1 00 00 00       	callq  400ee0 <phase_1>           #调用phase_1();
  400e3f:	e8 80 07 00 00       	callq  4015c4 <phase_defused>     #调用phase_defused();
  400e44:	bf a8 23 40 00       	mov    $0x4023a8,%edi     
  400e49:	e8 c2 fc ff ff       	callq  400b10 <puts@plt>          #printf("Phase 1 defused. How about the next one?\n");
  400e4e:	e8 4b 06 00 00       	callq  40149e <read_line>         #调用read_line();
  400e53:	48 89 c7             	mov    %rax,%rdi                  #传参
  400e56:	e8 a1 00 00 00       	callq  400efc <phase_2>           #调用phase_2();
  400e5b:	e8 64 07 00 00       	callq  4015c4 <phase_defused>
  400e60:	bf ed 22 40 00       	mov    $0x4022ed,%edi
  400e65:	e8 a6 fc ff ff       	callq  400b10 <puts@plt>
  400e6a:	e8 2f 06 00 00       	callq  40149e <read_line>
  400e6f:	48 89 c7             	mov    %rax,%rdi
  400e72:	e8 cc 00 00 00       	callq  400f43 <phase_3>           #调用phase_3();
  400e77:	e8 48 07 00 00       	callq  4015c4 <phase_defused>
  400e7c:	bf 0b 23 40 00       	mov    $0x40230b,%edi
  400e81:	e8 8a fc ff ff       	callq  400b10 <puts@plt>
  400e86:	e8 13 06 00 00       	callq  40149e <read_line>
  400e8b:	48 89 c7             	mov    %rax,%rdi
  400e8e:	e8 79 01 00 00       	callq  40100c <phase_4>           #调用phase_4();
  400e93:	e8 2c 07 00 00       	callq  4015c4 <phase_defused>
  400e98:	bf d8 23 40 00       	mov    $0x4023d8,%edi
  400e9d:	e8 6e fc ff ff       	callq  400b10 <puts@plt>
  400ea2:	e8 f7 05 00 00       	callq  40149e <read_line>
  400ea7:	48 89 c7             	mov    %rax,%rdi
  400eaa:	e8 b3 01 00 00       	callq  401062 <phase_5>          #调用phase_4();
  400eaf:	e8 10 07 00 00       	callq  4015c4 <phase_defused>
  400eb4:	bf 1a 23 40 00       	mov    $0x40231a,%edi
  400eb9:	e8 52 fc ff ff       	callq  400b10 <puts@plt>
  400ebe:	e8 db 05 00 00       	callq  40149e <read_line>
  400ec3:	48 89 c7             	mov    %rax,%rdi
  400ec6:	e8 29 02 00 00       	callq  4010f4 <phase_6>          #调用phase_4();
  400ecb:	e8 f4 06 00 00       	callq  4015c4 <phase_defused>
  400ed0:	b8 00 00 00 00       	mov    $0x0,%eax
  400ed5:	5b                   	pop    %rbx
  400ed6:	c3                   	retq

  大概分析了下主函数,主要还是传参和函数的调用,想要得出结果还是要看phase_1 ~ phase_6这些函数的反汇编。

0000000000400ee0 <phase_1>:
  400ee0:	48 83 ec 08          	sub    $0x8,%rsp                        #开辟内存空间     
  400ee4:	be 00 24 40 00       	mov    $0x402400,%esi                   #传参。这个参数很可能就是期望输入的字符串
  400ee9:	e8 4a 04 00 00       	callq  401338 <strings_not_equal>       #strings_not_equal 比较两个字符串结果保存在 %eax
  400eee:	85 c0                	test   %eax,%eax                        # 测试 %eax 为0还是1
  400ef0:	74 05                	je     400ef7 <phase_1+0x17>            #相等就跳转到400ef7
  400ef2:	e8 43 05 00 00       	callq  40143a <explode_bomb>            #否则就explode_bomb,失败
  400ef7:	48 83 c4 08          	add    $0x8,%rsp                        #正常结束
  400efb:	c3                   	retq

  第二行表示为函数开辟内存空间。第三行给%esi传进了一个参数,然后就调用了strings_not_equal。由strings_not_equal的反汇编可以看出,这个函数是接受两个参数的。那么,除了%esi传进的一个,另一个就是我们输入的字符串了。strings_not_equal比较的结果放在%eax中。接下来测试%eax为0还是1 ,如果%eax为1就表明两个字符串相等,跳转到相等400ef7,正常结束程序。否则就跳转到explode_bomb,失败。

0000000000401338 <strings_not_equal>:
  401338:	41 54                	push   %r12
  40133a:	55                   	push   %rbp
  40133b:	53                   	push   %rbx
  40133c:	48 89 fb             	mov    %rdi,%rbx           # 这里传入了两个参数%rdi,%rsi,这两个参数一个是输入的字符串,另一个是期望的字符串
  40133f:	48 89 f5             	mov    %rsi,%rbp
  401342:	e8 d4 ff ff ff       	callq  40131b <string_length>

  通过以上分析,我们可以得出结论,在内存为0x402400的地方存储的就是程序期望我们输入的字符串,那么我们利用GDB工具调试下代码,打印0x402400处的值看下。

《深入理解计算机系统》(CSAPP)实验二 —— Bomb Lab

  下面输入这个字符串测试下,结果显示完全正确。

《深入理解计算机系统》(CSAPP)实验二 —— Bomb Lab

phase_2

  下面继续看phase_2的反汇编。

0000000000400efc <phase_2>:
  400efc:	55                   	push   %rbp
  400efd:	53                   	push   %rbx
  400efe:	48 83 ec 28          	sub    $0x28,%rsp
  400f02:	48 89 e6             	mov    %rsp,%rsi
  400f05:	e8 52 05 00 00       	callq  40145c <read_six_numbers>         #read_six_numbers 读取6个数字
  400f0a:	83 3c 24 01          	cmpl   $0x1,(%rsp)                       #比较 1 和(%rsp)的值
  400f0e:	74 20                	je     400f30 <phase_2+0x34>             #相等就继续400f30
  400f10:	e8 25 05 00 00       	callq  40143a <explode_bomb>             #否则就调用explode_bomb
  400f15:	eb 19                	jmp    400f30 <phase_2+0x34>
  400f17:	8b 43 fc             	mov    -0x4(%rbx),%eax                   # %rbx-0x4 的值放到%eax  也就是上上个元素 1
  400f1a:	01 c0                	add    %eax,%eax                         # 2*%eax 
  400f1c:	39 03                	cmp    %eax,(%rbx)                       # 比较 2*%eax (%rbx)
  400f1e:	74 05                	je     400f25 <phase_2+0x29>             #相等就跳转到400f25
  400f20:	e8 15 05 00 00       	callq  40143a <explode_bomb>             #否则就调用explode_bomb
  400f25:	48 83 c3 04          	add    $0x4,%rbx                                
  400f29:	48 39 eb             	cmp    %rbp,%rbx
  400f2c:	75 e9                	jne    400f17 <phase_2+0x1b>
  400f2e:	eb 0c                	jmp    400f3c <phase_2+0x40>
  400f30:	48 8d 5c 24 04       	lea    0x4(%rsp),%rbx                     #0x4+%rsp的值放到%rbx
  400f35:	48 8d 6c 24 18       	lea    0x18(%rsp),%rbp                    #0x18+%rsp的值放到%rbp
  400f3a:	eb db                	jmp    400f17 <phase_2+0x1b>
  400f3c:	48 83 c4 28          	add    $0x28,%rsp
  400f40:	5b                   	pop    %rbx
  400f41:	5d                   	pop    %rbp
  400f42:	c3                   	retq

  伪代码:

// %rsp   %rsp+0x4  %rsp+0x18if((%rsp) == 1){goto 400f30;} 400f17: %eax = %rbx-4;             //%eax其实就是 %rsp %eax=2*%eax;               //%rsp = 2*%rsp if((%rbx)==%eax)           //要满足两倍关系  %rbx代表的是%rsp+0x4地址处的数据,%eax代表的是 2*(%rsp),意思就是说下一个数要是上一个数的两倍 {%rbx=%rbx+0x4;          //下一地址 +0x4if(%rbx==%rbp)          //%rbp = %rsp+0x18  %rbp存储的是结束的条件  {   return;} else {goto 400f17 }  } else {explode_bomb     }else{
	explode_bomb}400f30:%rbx=%rsp+0x4;%rbp=%rsp+0x18;goto 400f17;

  直接看汇编代码有点复杂,所以捋了下思路,写一个伪代码出来会方便看一点。从反汇编的read_six_numbers可以看出,答案一定是6个数字。

  程序一开始将%rsp的值和1比较,只有相等时,才会继续进行。说明第一个数一定要是1。接着跳转到400f30将%rsp+0x4,%rsp+0x18。跳转到400f17,到这里后,把%rbx的值减去了4,减完之后,%eax其实就是%rsp的值,回到了最初的状态。接着将%eax乘以2,%rbx代表的是%rsp+0x4地址处的数据,%eax代表的是 2*(%rsp),意思就是说下一个数要是上一个数的两倍才会跳进if语句中,否则就会爆炸。接着将%rbx+0x4,看起来像是将地址+4.指向下一个数字。这个时候再将%rbx和%rbp比较,从这里可以判断出%rbp很可能就是结束的条件,只有%rbx == %rbp,才会正常结束程序,而且只有这一条路可以结束正序,否则就会爆炸。

  分析到这里可以得出三个重要的结论:1.第一个数是1。2. 6个数字的关系为:后一个数是前一个数的两倍。3.结束的条件存放在%rsp+0x18。所以根据这三个结论我们可以推断出6个数字为:1 2 4 8 16 32。运行程序测试结果完全正确。

《深入理解计算机系统》(CSAPP)实验二 —— Bomb Lab

phase_3

0000000000400f43 <phase_3>:
  400f43:	48 83 ec 18          	sub    $0x18,%rsp
  400f47:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx                     # %rcx = 0xc + %rsp 第二个参数
  400f4c:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx                     # %rdx = 0x8 + %rsp 第一个参数
  400f51:	be cf 25 40 00       	mov    $0x4025cf,%esi                     # %esi = 0x4025cf    %d %d
  400f56:	b8 00 00 00 00       	mov    $0x0,%eax                          # %eax = 0
  400f5b:	e8 90 fc ff ff       	callq  400bf0 <__isoc99_sscanf@plt>        
  400f60:	83 f8 01             	cmp    $0x1,%eax                          # %eax 和 1 比较  输入的数的个数要大于1
  400f63:	7f 05                	jg     400f6a <phase_3+0x27>              # %eax > 0x1, 大于就跳转到400f6a,否则explode_bomb
  400f65:	e8 d0 04 00 00       	callq  40143a <explode_bomb>              # %eax <= 0x1 explode_bomb
  400f6a:	83 7c 24 08 07       	cmpl   $0x7,0x8(%rsp)                     # 0x8(%rsp)  0x7 
  400f6f:	77 3c                	ja     400fad <phase_3+0x6a>              # 0x8(%rsp)>=0x7 跳转 explode_bomb 第一个参数大于7 爆炸 <0
  400f71:	8b 44 24 08          	mov    0x8(%rsp),%eax                     # 0x8(%rsp) < 0x7 则 %eax = 0x8(%rsp) [0,6]
  400f75:	ff 24 c5 70 24 40 00 	jmpq   *0x402470(, %rax,8)                # 跳转表 *0x402470 + 8 * %rax  *0x402470 = 0x400f7c 
  400f7c:	b8 cf 00 00 00       	mov    $0xcf,%eax                         # %eax = 0xcf = 207  case 0      
  400f81:	eb 3b                	jmp    400fbe <phase_3+0x7b>              # 400fbe
  400f83:	b8 c3 02 00 00       	mov    $0x2c3,%eax                        # %eax  = 0x2c3 = 707 case 2   
  400f88:	eb 34                	jmp    400fbe <phase_3+0x7b>
  400f8a:	b8 00 01 00 00       	mov    $0x100,%eax                        # %eax  = 0x100 = 256 case 3
  400f8f:	eb 2d                	jmp    400fbe <phase_3+0x7b>
  400f91:	b8 85 01 00 00       	mov    $0x185,%eax                        # %eax  = 0x185 = 389  case 4
  400f96:	eb 26                	jmp    400fbe <phase_3+0x7b>
  400f98:	b8 ce 00 00 00       	mov    $0xce,%eax                         # %eax  = 0xce = 206  case 5
  400f9d:	eb 1f                	jmp    400fbe <phase_3+0x7b>
  400f9f:	b8 aa 02 00 00       	mov    $0x2aa,%eax                        # %eax  = 0x2aa = 682 case 6
  400fa4:	eb 18                	jmp    400fbe <phase_3+0x7b>
  400fa6:	b8 47 01 00 00       	mov    $0x147,%eax                        # %eax  = 0x147 = 327 case 7
  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  = 0x0 = 0 
  400fb7:	eb 05                	jmp    400fbe <phase_3+0x7b>
  400fb9:	b8 37 01 00 00       	mov    $0x137,%eax                        # %eax  = 0x137 = 311  case 1
  400fbe:	3b 44 24 0c          	cmp    0xc(%rsp),%eax                     # 0xc(%rsp)  %eax
  400fc2:	74 05                	je     400fc9 <phase_3+0x86>              # 0xc(%rsp)==%eax  输入的第二个参数 和 %eax相等则解除
  400fc4:	e8 71 04 00 00       	callq  40143a <explode_bomb>              # 0xc(%rsp)!=%eax explode_bomb
  400fc9:	48 83 c4 18          	add    $0x18,%rsp
  400fcd:	c3                   	retq

  从第5行的0x4025cf入手,查看后发现是%d %d ,说明输入的是两个整数。

《深入理解计算机系统》(CSAPP)实验二 —— Bomb Lab

  第7行,第8行说明输入的参数个数要大于1。第11行将第一个参数0x8(%rsp) 和7比较,大于7则爆炸,说明输入的参数要小于等于7,同时ja为无符号跳转,则参数还有大于0,因此得出第一个参数的范围[0,7]。第14行为间接跳转,以 *0x402470 处的值为基地址,再加上8 * %rax 进行跳转,不同的 %rax 跳转到不同的位置。

《深入理解计算机系统》(CSAPP)实验二 —— Bomb Lab

  我们可以看下0x402470的值为0x400f7c。当%rax 为0时,跳转到0x400f7c。此时%eax = 207,最后跳转到33行,将207于输入的值比较。说明0 和 207为一组正确数据。测试下

《深入理解计算机系统》(CSAPP)实验二 —— Bomb Lab

  其他结果如汇编中的注释所示。

phase_4

000000000040100c <phase_4>:
  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
  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>
  40102e:	83 7c 24 08 0e       	cmpl   $0xe,0x8(%rsp)                    # 第一个参数和14比较,要比14小
  401033:	76 05                	jbe    40103a <phase_4+0x2e>
  401035:	e8 00 04 00 00       	callq  40143a <explode_bomb>
  40103a:	ba 0e 00 00 00       	mov    $0xe,%edx                                     
  40103f:	be 00 00 00 00       	mov    $0x0,%esi                           # 三个参数
  401044:	8b 7c 24 08          	mov    0x8(%rsp),%edi
  401048:	e8 81 ff ff ff       	callq  400fce <func4>                      # 三个参数:0x8(%rsp),0,14       
  40104d:	85 c0                	test   %eax,%eax                           # 测试返回值是否为0
  40104f:	75 07                	jne    401058 <phase_4+0x4c>               # 返回值不为0 爆炸      
  401051:	83 7c 24 0c 00       	cmpl   $0x0,0xc(%rsp)                      # 测试第二个参数是否为0
  401056:	74 05                	je     40105d <phase_4+0x51>               # 为0 不会爆炸,所以第二个数一定要输入0
  401058:	e8 dd 03 00 00       	callq  40143a <explode_bomb>
  40105d:	48 83 c4 18          	add    $0x18,%rsp
  401061:	c3                   	retq

  从第5行看起,0x4025cf指向的地方存储的仍然是 两个int型整数。第8行和2比较,说明输入参数的个数为2。第10行和14比较,说明输入的第一个参数一定要小于14。第13,14,15行向func4()传递三个参数0x8(%rsp),0,14 。第17行测试函数返回值是否为0,要想不爆炸,函数返回值一定要为0。第19行说明输入的第二个参数一定要为0。

  所以,我们要确定的是当输入的第一个参数为多少的时候,fun4()的返回值为0。下面看下fun4()的反汇编。

0000000000400fce <func4>:
  400fce:	48 83 ec 08          	sub    $0x8,%rsp
  400fd2:	89 d0                	mov    %edx,%eax                         # %edx第三个参数
  400fd4:	29 f0                	sub    %esi,%eax                         # %esi = %esi-%eax  %esi 第二个参数
  400fd6:	89 c1                	mov    %eax,%ecx                         
  400fd8:	c1 e9 1f             	shr    $0x1f,%ecx                        # 0x1f >> %ecx
  400fdb:	01 c8                	add    %ecx,%eax                         # %ecx =%ecx,%eax
  400fdd:	d1 f8                	sar    %eax
  400fdf:	8d 0c 30             	lea    (%rax,%rsi,1),%ecx                # %ecx  = %rax + %rsi
  400fe2:	39 f9                	cmp    %edi,%ecx                         # %edi第一个参数
  400fe4:	7e 0c                	jle    400ff2 <func4+0x24>               # %edi<= %ecx 跳转400ff2
  400fe6:	8d 51 ff             	lea    -0x1(%rcx),%edx                   # %edx = %rcx-0x1
  400fe9:	e8 e0 ff ff ff       	callq  400fce <func4>                    # 递归
  400fee:	01 c0                	add    %eax,%eax                         # %eax = 2 * %eax
  400ff0:	eb 15                	jmp    401007 <func4+0x39>               # return
  400ff2:	b8 00 00 00 00       	mov    $0x0,%eax
  400ff7:	39 f9                	cmp    %edi,%ecx                          
  400ff9:	7d 0c                	jge    401007 <func4+0x39>               # %edi >= %ecx return 
  400ffb:	8d 71 01             	lea    0x1(%rcx),%esi                    # %esi = %rcx + 0x1
  400ffe:	e8 cb ff ff ff       	callq  400fce <func4>                    # 递归调用 
  401003:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax             # %eax = %rax + %rax
  401007:	48 83 c4 08          	add    $0x8,%rsp
  40100b:	c3                   	retq

  将汇编翻译为C如下所示

//x: %edi y:%esi z:%edx k: %ecx t:%eax
void func4(int x,int y,int z)
{//x in %rdi,y in %rsi,z in %rdx,t in %rax,k in %ecx
 //y的初始值为0,z的初始值为14
  int t=z-y;
  int k=t>>31;
  t=(t+k)>>1;
  k=t+y;
  if(k>x)
  {
    z=k-1;
    func4(x,y,z);
    t=2t;
    return;
  }
  else
   {
     t=0;
     if(k<x)
     {
        y=k+1;
        func4(x,y,z);
        t=2*t+1;
        return;
     }
     else
     {
         return;
     }
   }
}

  当x == k时,返回值为0。所以第一个参数为7。

《深入理解计算机系统》(CSAPP)实验二 —— Bomb Lab

phase_5

0000000000401062 <phase_5>:
  401062:	53                   	push   %rbx
  401063:	48 83 ec 20          	sub    $0x20,%rsp                         # 开辟空间
  401067:	48 89 fb             	mov    %rdi,%rbx
  40106a:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
  401071:	00 00 
  401073:	48 89 44 24 18       	mov    %rax,0x18(%rsp)
  401078:	31 c0                	xor    %eax,%eax
  40107a:	e8 9c 02 00 00       	callq  40131b <string_length>          # 字符串长度
  40107f:	83 f8 06             	cmp    $0x6,%eax                       # 字符串长度要为6
  401082:	74 4e                	je     4010d2 <phase_5+0x70>
  401084:	e8 b1 03 00 00       	callq  40143a <explode_bomb>           
  401089:	eb 47                	jmp    4010d2 <phase_5+0x70>
  ##############################################start################################################################################
  40108b:	0f b6 0c 03          	movzbl (%rbx,%rax,1),%ecx              # %ecx = %rbx + %rax ASCII码值 给%ecx 
  40108f:	88 0c 24             	mov    %cl,(%rsp)                      # ASCII码值取低8位
  401092:	48 8b 14 24          	mov    (%rsp),%rdx         
  401096:	83 e2 0f             	and    $0xf,%edx                       # ASCII码值取低4位
  401099:	0f b6 92 b0 24 40 00 	movzbl 0x4024b0(%rdx),%edx             # %edx = %rdx + 0x4024b0(maduiersnfotvbyl)%edx:%rdx低4位有效
  4010a0:	88 54 04 10          	mov    %dl,0x10(%rsp,%rax,1)           # %edx低八位存在 0x10 + %rsp + %rax 中
  4010a4:	48 83 c0 01          	add    $0x1,%rax                       # %rax = %rax + 1
  4010a8:	48 83 f8 06          	cmp    $0x6,%rax                       
  4010ac:	75 dd                	jne    40108b <phase_5+0x29>           # %rax 不等于6 则循环   #################################################end###################################################################################
  4010ae:	c6 44 24 16 00       	movb   $0x0,0x16(%rsp)
  4010b3:	be 5e 24 40 00       	mov    $0x40245e,%esi                  # %esi指向从0x40245e内存单元读入的字符串flyers 
  4010b8:	48 8d 7c 24 10       	lea    0x10(%rsp),%rdi                 # %rdi指向前面循环中构造好的长度为6的字符串
  4010bd:	e8 76 02 00 00       	callq  401338 <strings_not_equal>      # 判断%esi和%rdi指向的字符串是否相等
  4010c2:	85 c0                	test   %eax,%eax                       
  4010c4:	74 13                	je     4010d9 <phase_5+0x77>           #只有两个字符串相等,才会解除
  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                       # 循环开始赋初值
  4010d7:	eb b2                	jmp    40108b <phase_5+0x29>
  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  
  #%rbp %rbx %r12~%15 被调用者保存寄存器#  %r10 %r11 调用者保存寄存器%rdi %rsi %rdx %rcx %r8 %r9 依次保存参数1~6
%dl表示%rdx寄存器最低8bit

  这个题目有点扰啊,兜了很大一个圈子!

  第7行 ~ 13行说明输入的字符串长度要为6。

  第15行 ~ 23行为一个循环。输入的字符串存储在%rbx中,第15行表示把输入字符串的第%eax个字符的ASCII码值给%ecx,%cl为%ecx的低8位,所以第16行为取%ecx的低八位。

  第18行表示再取低4位。

  第19行的0x4024b0查看内容为maduiersnfotvbyl,这句话的意思是以0x4024b0为基地址,以%rdx为偏移,从maduiersnfotvbyl字符串中取字符的低32位,结果放在%edx中。

  第20行,%dl中的值应为0x4024b0+%rdx表示的字符,将其赋值给0x10(%rsp,%rax,1),最后计数器%rax+1。

  第22行,表示是否循环够了6次。

  第25行,0x40245e字符串为flyers,比较两个字符串,如果%eax为0(两个字符串相同),则解除炸弹,否则爆炸。

  所以,0x4024b0 + %rdx = {flyers的ASCII码}。

《深入理解计算机系统》(CSAPP)实验二 —— Bomb Lab

  flyers对应的ascii值 0x66 0x6c 0x79 0x65 0x72 0x73。

  与0x4024b0内存地址开始的查找表比较获得偏移量 0x9 0xF 0xE 0x5 0x6 0x72。

  因此输入长度为6的字符串中每个字符的低4bit的值分别为0x9 0xF 0xE 0x5 0x6 0x72。

  若输入为大写字母,将低4bit的值加上0x40,获得输入字符串IONEFG。

  若输入为小写字母,将低4bit的值加上0x60,获得输入字符串ionefg。

《深入理解计算机系统》(CSAPP)实验二 —— Bomb Lab

phase_6

(gdb) disas phase_6
Dump of assembler code for function phase_6:
   0x00000000004010f4 <+0>: push   %r14                                将被调用者保存寄存器压入栈
   0x00000000004010f6 <+2>: push   %r13
   0x00000000004010f8 <+4>: push   %r12
   0x00000000004010fa <+6>: push   %rbp
   0x00000000004010fb <+7>: push   %rbx                                %rsp = 0x7fffffffe2c0
   0x00000000004010fc <+8>: sub    $0x50,%rsp                          分配栈空间 %rsp = 0x7fffffffe270
   0x0000000000401100 <+12>:    mov    %rsp,%r13

   0x0000000000401103 <+15>:    mov    %rsp,%rsi
   0x0000000000401106 <+18>:    callq  0x40145c <read_six_numbers>     读入6个值,保存至从 %rsi 开始的地址

   0x000000000040110b <+23>:    mov    %rsp,%r14
   0x000000000040110e <+26>:    mov    $0x0,%r12d                      %r12 置0,并且%r13 %r14 %rbp 均和 %rsp 指向相同地址 0x7fffffffe270

   0x0000000000401114 <+32>:    mov    %r13,%rbp
   0x0000000000401117 <+35>:    mov    0x0(%r13),%eax                  将第 %r13 指向的输入数复制到 %eax
   0x000000000040111b <+39>:    sub    $0x1,%eax                       将输入数减1
   0x000000000040111e <+42>:    cmp    $0x5,%eax                       判断输入数是否小于等于6,因为上一步中减1操作
   0x0000000000401121 <+45>:    jbe    0x401128 <phase_6+52>           若大于6,则调用 explode_bomb
   0x0000000000401123 <+47>:    callq  0x40143a <explode_bomb>=========================================================================================================================================================
   0x0000000000401128 <+52>:    add    $0x1,%r12d                      将 %r12 加1
   0x000000000040112c <+56>:    cmp    $0x6,%r12d                      判断 %r12 是否等于6
   0x0000000000401130 <+60>:    je     0x401153 <phase_6+95>           若等于6,跳转,否则继续执行
   0x0000000000401132 <+62>:    mov    %r12d,%ebx                      将 %r12 复制到 %ebx

   0x0000000000401135 <+65>:    movslq %ebx,%rax                       将 %ebx 符号位扩展复制到 %rax
   0x0000000000401138 <+68>:    mov    (%rsp,%rax,4),%eax              将第 %ebx 输入数复制到 %eax
   0x000000000040113b <+71>:    cmp    %eax,0x0(%rbp)                  比较 %r13 指向的输入数和 第 %ebx 输入数 是否相等
   0x000000000040113e <+74>:    jne    0x401145 <phase_6+81>           如果相等,则调用 explode_bomb
   0x0000000000401140 <+76>:    callq  0x40143a <explode_bomb>
   0x0000000000401145 <+81>:    add    $0x1,%ebx                       将 %ebx 加1
   0x0000000000401148 <+84>:    cmp    $0x5,%ebx                       判断 %ebx 是否小于等于5
   0x000000000040114b <+87>:    jle    0x401135 <phase_6+65>           若小于等于,跳转,否则继续执行;该循环判断 %r13 指向的数据和其后输入数不相等

   0x000000000040114d <+89>:    add    $0x4,%r13                       将 %r13 指向下一个输入数,该循环判断所有的输入数全部不相等
   0x0000000000401151 <+93>:    jmp    0x401114 <phase_6+32>=========================================================================================================================================================
   0x0000000000401153 <+95>:    lea    0x18(%rsp),%rsi                 将 %rsi 指向栈中跳过读入数据位置作为结束标记,并且 %r14 仍和 %rsp 指向同一个位置
   0x0000000000401158 <+100>:   mov    %r14,%rax                       将 %r14 复制到 %rax
   0x000000000040115b <+103>:   mov    $0x7,%ecx
   0x0000000000401160 <+108>:   mov    %ecx,%edx                       将立即数0x7复制到 %edx
   0x0000000000401162 <+110>:   sub    (%rax),%edx                     立即数7减去 %r14 指向的数据
   0x0000000000401164 <+112>:   mov    %edx,(%rax)                     将7减的结果存回 %r14 执行的内存单元
   0x0000000000401166 <+114>:   add    $0x4,%rax                       %rax 指向下一个输入数
   0x000000000040116a <+118>:   cmp    %rsi,%rax                       比较是否达到输入数组的末尾,
   0x000000000040116d <+121>:   jne    0x401160 <phase_6+108>          该循环使用立即数7减去每个输入数据==========================================================================================================================================================
   0x000000000040116f <+123>:   mov    $0x0,%esi                       将 %rsi 置0
   0x0000000000401174 <+128>:   jmp    0x401197 <phase_6+163>

   0x0000000000401176 <+130>:   mov    0x8(%rdx),%rdx                  将 0x8(%rdx) 指向内存单元的内容复制到 %rdx, 指向链表下一个元素
   0x000000000040117a <+134>:   add    $0x1,%eax                       将 %eax 加1
   0x000000000040117d <+137>:   cmp    %ecx,%eax                       比较 %ecx 和 %eax 是否相等
   0x000000000040117f <+139>:   jne    0x401176 <phase_6+130>          不相等,继续遍历链表,最终 %rdx 指向链表的第 %ecx 个节点
   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              将 (%rsp + %rsi) 指向的数据复制到 %ecx
   0x000000000040119a <+166>:   cmp    $0x1,%ecx                       比较 %ecx 是否小于等于1
   0x000000000040119d <+169>:   jle    0x401183 <phase_6+143>          若小于等于,跳转,否则继续执行, 等于1, %edx 直接指向链表首地址
   0x000000000040119f <+171>:   mov    $0x1,%eax                       将 %eax 置1
   0x00000000004011a4 <+176>:   mov    $0x6032d0,%edx                  将 %rdx 指向内存单元 0x6032d0
   0x00000000004011a9 <+181>:   jmp    0x401176 <phase_6+130>          跳转; 该循环根据输入数将链表中对应的第输入数个节点的地址复制到 0x20(%rsp) 开始的栈中 ==========================================================================================================================================================
   0x00000000004011ab <+183>:   mov    0x20(%rsp),%rbx                 将0x20(%rsp)的链表节点地址复制到 %rbx
   0x00000000004011b0 <+188>:   lea    0x28(%rsp),%rax                 将 %rax 指向栈中下一个链表节点的地址
   0x00000000004011b5 <+193>:   lea    0x50(%rsp),%rsi                 将 %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                       判断6个节点是否遍历完毕
   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)                  该循环按照7减去输入数据的索引重新调整链表==========================================================================================================================================================
   0x00000000004011da <+230>:   mov    $0x5,%ebp
   0x00000000004011df <+235>:   mov    0x8(%rbx),%rax                  将 %rax 指向 %rbx 下一个链表节点
   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                  将 %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
End of assembler dump.

%rsi存储调用者phase_2栈帧的局部变量开始地址
%rdx = %rsi + 0
%rcx = %rsi + 4
%r8 =  %rsi + 8
%r9 =  %rsi + 12(%rsp)  = %rsi + 16
8(%rsp) = %rsi + 20

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.

%rbp %rbx %r12~%15 被调用者保存寄存器
%r10 %r11 调用者保存寄存器
%rdi %rsi %rdx %rcx %r8 %r9 依次保存输入数1~6

  假设输入数据为4 3 2 1 6 5

  猜测0x6032d8为链表首地址,链表中每个节点占用12个Byte,前8字节保存两个4字Byte的整型数,剩余的4Byte存放下个节点地址

  GDB查看使用7减去对应的输入后的数据

(gdb) p /x $rsp
$1 = 0x7fffffffe270
(gdb) x/6dw 0x7fffffffe270
0x7fffffffe270: 3   4   5   6
0x7fffffffe280: 1   2

  重新调整链表前的链表的结构

(gdb) x/24xw 0x006032d0
0x6032d0 <node1>:   0x0000014c  0x00000001  0x006032e0  0x00000000
0x6032e0 <node2>:   0x000000a8  0x00000002  0x006032f0  0x00000000
0x6032f0 <node3>:   0x0000039c  0x00000003  0x00603300  0x00000000
0x603300 <node4>:   0x000002b3  0x00000004  0x00603310  0x00000000
0x603310 <node5>:   0x000001dd  0x00000005  0x00603320  0x00000000
0x603320 <node6>:   0x000001bb  0x00000006  0x00000000  0x00000000

  保存在栈中链表节点信息

(gdb) x/6xg 0x7fffffffe290
0x7fffffffe290: 0x00000000006032f0  0x0000000000603300
0x7fffffffe2a0: 0x0000000000603310  0x0000000000603320
0x7fffffffe2b0: 0x00000000006032d0  0x00000000006032e0

  按照7减去对应的输入后重新调整链表后的链表结构,索引顺序为 3 4 5 6 1 2

(gdb) x/24xw 0x006032d0
0x6032d0 <node1>:   0x0000014c  0x00000001  0x006032e0  0x00000000
0x6032e0 <node2>:   0x000000a8  0x00000002  0x00000000  0x00000000
0x6032f0 <node3>:   0x0000039c  0x00000003  0x00603300  0x00000000
0x603300 <node4>:   0x000002b3  0x00000004  0x00603310  0x00000000
0x603310 <node5>:   0x000001dd  0x00000005  0x00603320  0x00000000
0x603320 <node6>:   0x000001bb  0x00000006  0x006032d0  0x00000000

  破解思路:

  将链表中每个节点按照前4字节降序排序:3 4 5 6 1 2

  因为在前面使用7减去对应的值,所以破解密码:4 3 2 1 6 5

phase6太难了,心力憔悴。phase6的解析来自以下链接:
这是链接

总结

  实验太难了。分析汇编像看天书,不过通过本次实验也提高了自己阅读汇编代码的能力,学会了基本的GDB调试代码的步骤。收获颇丰!

  养成习惯,先赞后看!如果觉得写的不错,欢迎关注,点赞,转发,谢谢!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_16933601/article/details/109744569

上一篇:计算机系统概述(CSAPP第一章内容)


下一篇:DIV+CSS布局命名规范