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

2022-03-17 15:24:29 浏览数 (1)

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

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

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

准备工作

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

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

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

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

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

phase_1

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

代码语言:javascript复制
0000000000400da0 <main>:
  400da0:	53                   	push   %rbx
  400da1:	83 ff 01             	cmp    $0x1,�i                  #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,�i                  #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,�i                  #传参
  400de9:	e8 12 fe ff ff       	callq  400c00 <__printf_chk@plt>  #printf("%s: Error: Couldn't open %sn", argv[0], argv[1]);
  400dee:	bf 08 00 00 00       	mov    $0x8,�i
  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,�i
  400e05:	b8 00 00 00 00       	mov    $0x0,�x                  #传参
  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,�i
  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,�i
  400e23:	e8 e8 fc ff ff       	callq  400b10 <puts@plt>          #printf("Welcome to my fiendish little bomb. You have 6 phases withn");
  400e28:	bf 78 23 40 00       	mov    $0x402378,�i 
  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,�i     
  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,�i
  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,�i
  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,�i
  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,�i
  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,�x
  400ed5:	5b                   	pop    %rbx
  400ed6:	c3                   	retq   

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

代码语言:javascript复制
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 比较两个字符串结果保存在 �x
  400eee:	85 c0                	test   �x,�x                        # 测试 �x 为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比较的结果放在�x中。接下来测试�x为0还是1 ,如果�x为1就表明两个字符串相等,跳转到相等400ef7,正常结束程序。否则就跳转到explode_bomb,失败。

代码语言:javascript复制
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处的值看下。

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

phase_2

  下面继续看phase_2的反汇编。

代码语言:javascript复制
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),�x                   # %rbx-0x4 的值放到�x  也就是上上个元素 1
  400f1a:	01 c0                	add    �x,�x                         # 2*�x 
  400f1c:	39 03                	cmp    �x,(%rbx)                       # 比较 2*�x (%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   

  伪代码:

代码语言:javascript复制
// %rsp   %rsp 0x4  %rsp 0x18
if((%rsp) == 1)
{
	goto 400f30;
}
     400f17:
     �x = %rbx-4;             //�x其实就是 %rsp
     �x=2*�x;               //%rsp = 2*%rsp
     if((%rbx)==�x)           //要满足两倍关系  %rbx代表的是%rsp 0x4地址处的数据,�x代表的是 2*(%rsp),意思就是说下一个数要是上一个数的两倍
     {
        %rbx=%rbx 0x4;          //下一地址  0x4
        if(%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,减完之后,�x其实就是%rsp的值,回到了最初的状态。接着将�x乘以2,%rbx代表的是%rsp 0x4地址处的数据,�x代表的是 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。运行程序测试结果完全正确。

phase_3

代码语言:javascript复制
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,�x                          # �x = 0
  400f5b:	e8 90 fc ff ff       	callq  400bf0 <__isoc99_sscanf@plt>        
  400f60:	83 f8 01             	cmp    $0x1,�x                          # �x 和 1 比较  输入的数的个数要大于1
  400f63:	7f 05                	jg     400f6a <phase_3 0x27>              # �x > 0x1, 大于就跳转到400f6a,否则explode_bomb
  400f65:	e8 d0 04 00 00       	callq  40143a <explode_bomb>              # �x <= 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),�x                     # 0x8(%rsp) < 0x7 则 �x = 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,�x                         # �x = 0xcf = 207  case 0      
  400f81:	eb 3b                	jmp    400fbe <phase_3 0x7b>              # 400fbe
  400f83:	b8 c3 02 00 00       	mov    $0x2c3,�x                        # �x  = 0x2c3 = 707 case 2   
  400f88:	eb 34                	jmp    400fbe <phase_3 0x7b>
  400f8a:	b8 00 01 00 00       	mov    $0x100,�x                        # �x  = 0x100 = 256 case 3
  400f8f:	eb 2d                	jmp    400fbe <phase_3 0x7b>
  400f91:	b8 85 01 00 00       	mov    $0x185,�x                        # �x  = 0x185 = 389  case 4
  400f96:	eb 26                	jmp    400fbe <phase_3 0x7b>
  400f98:	b8 ce 00 00 00       	mov    $0xce,�x                         # �x  = 0xce = 206  case 5
  400f9d:	eb 1f                	jmp    400fbe <phase_3 0x7b>
  400f9f:	b8 aa 02 00 00       	mov    $0x2aa,�x                        # �x  = 0x2aa = 682 case 6
  400fa4:	eb 18                	jmp    400fbe <phase_3 0x7b>
  400fa6:	b8 47 01 00 00       	mov    $0x147,�x                        # �x  = 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,�x                          # �x  = 0x0 = 0 
  400fb7:	eb 05                	jmp    400fbe <phase_3 0x7b>
  400fb9:	b8 37 01 00 00       	mov    $0x137,�x                        # �x  = 0x137 = 311  case 1
  400fbe:	3b 44 24 0c          	cmp    0xc(%rsp),�x                     # 0xc(%rsp)  �x
  400fc2:	74 05                	je     400fc9 <phase_3 0x86>              # 0xc(%rsp)==�x  输入的第二个参数 和 �x相等则解除
  400fc4:	e8 71 04 00 00       	callq  40143a <explode_bomb>              # 0xc(%rsp)!=�x explode_bomb
  400fc9:	48 83 c4 18          	add    $0x18,%rsp
  400fcd:	c3                   	retq   

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

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

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

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

phase_4

代码语言:javascript复制
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,�x
  401024:	e8 c7 fb ff ff       	callq  400bf0 <__isoc99_sscanf@plt>
  401029:	83 f8 02             	cmp    $0x2,�x                         # 输入两个参数
  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,�x                                     
  40103f:	be 00 00 00 00       	mov    $0x0,%esi                           # 三个参数
  401044:	8b 7c 24 08          	mov    0x8(%rsp),�i
  401048:	e8 81 ff ff ff       	callq  400fce <func4>                      # 三个参数:0x8(%rsp),0,14       
  40104d:	85 c0                	test   �x,�x                           # 测试返回值是否为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()的反汇编。

代码语言:javascript复制
0000000000400fce <func4>:
  400fce:	48 83 ec 08          	sub    $0x8,%rsp
  400fd2:	89 d0                	mov    �x,�x                         # �x第三个参数
  400fd4:	29 f0                	sub    %esi,�x                         # %esi = %esi-�x  %esi 第二个参数
  400fd6:	89 c1                	mov    �x,�x                         
  400fd8:	c1 e9 1f             	shr    $0x1f,�x                        # 0x1f >> �x
  400fdb:	01 c8                	add    �x,�x                         # �x =�x,�x
  400fdd:	d1 f8                	sar    �x
  400fdf:	8d 0c 30             	lea    (%rax,%rsi,1),�x                # �x  = %rax   %rsi
  400fe2:	39 f9                	cmp    �i,�x                         # �i第一个参数
  400fe4:	7e 0c                	jle    400ff2 <func4 0x24>               # �i<= �x 跳转400ff2
  400fe6:	8d 51 ff             	lea    -0x1(%rcx),�x                   # �x = %rcx-0x1
  400fe9:	e8 e0 ff ff ff       	callq  400fce <func4>                    # 递归
  400fee:	01 c0                	add    �x,�x                         # �x = 2 * �x
  400ff0:	eb 15                	jmp    401007 <func4 0x39>               # return
  400ff2:	b8 00 00 00 00       	mov    $0x0,�x
  400ff7:	39 f9                	cmp    �i,�x                          
  400ff9:	7d 0c                	jge    401007 <func4 0x39>               # �i >= �x 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),�x             # �x = %rax   %rax
  401007:	48 83 c4 08          	add    $0x8,%rsp
  40100b:	c3                   	retq 

  将汇编翻译为C如下所示

代码语言:javascript复制
//x: �i y:%esi z:�x k: �x t:�x
void func4(int x,int y,int z)
{//x in %rdi,y in %rsi,z in %rdx,t in %rax,k in �x
 //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。

phase_5

代码语言:javascript复制
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    �x,�x
  40107a:	e8 9c 02 00 00       	callq  40131b <string_length>          # 字符串长度
  40107f:	83 f8 06             	cmp    $0x6,�x                       # 字符串长度要为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),�x              # �x = %rbx   %rax ASCII码值 给�x 
  40108f:	88 0c 24             	mov    %cl,(%rsp)                      # ASCII码值取低8位
  401092:	48 8b 14 24          	mov    (%rsp),%rdx         
  401096:	83 e2 0f             	and    $0xf,�x                       # ASCII码值取低4位
  401099:	0f b6 92 b0 24 40 00 	movzbl 0x4024b0(%rdx),�x             # �x = %rdx   0x4024b0(maduiersnfotvbyl)�x:%rdx低4位有效
  4010a0:	88 54 04 10          	mov    %dl,0x10(%rsp,%rax,1)           # �x低八位存在 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   �x,�x                       
  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,�x                       # 循环开始赋初值
  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~ 被调用者保存寄存器
#  %r10 %r11 调用者保存寄存器
%rdi %rsi %rdx %rcx %r8 %r9 依次保存参数1~6
%dl表示%rdx寄存器最低8bit

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

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

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

  第18行表示再取低4位。

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

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

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

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

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

  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。

phase_6

代码语言:javascript复制
(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),�x                  将第 %r13 指向的输入数复制到 �x
   0x000000000040111b < 39>:    sub    $0x1,�x                       将输入数减1
   0x000000000040111e < 42>:    cmp    $0x5,�x                       判断输入数是否小于等于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,�x                      将 %r12 复制到 �x

   0x0000000000401135 < 65>:    movslq �x,%rax                       将 �x 符号位扩展复制到 %rax
   0x0000000000401138 < 68>:    mov    (%rsp,%rax,4),�x              将第 �x 输入数复制到 �x
   0x000000000040113b < 71>:    cmp    �x,0x0(%rbp)                  比较 %r13 指向的输入数和 第 �x 输入数 是否相等
   0x000000000040113e < 74>:    jne    0x401145 <phase_6 81>           如果相等,则调用 explode_bomb
   0x0000000000401140 < 76>:    callq  0x40143a <explode_bomb>
   0x0000000000401145 < 81>:    add    $0x1,�x                       将 �x 加1
   0x0000000000401148 < 84>:    cmp    $0x5,�x                       判断 �x 是否小于等于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,�x
   0x0000000000401160 < 108>:   mov    �x,�x                       将立即数0x7复制到 �x
   0x0000000000401162 < 110>:   sub    (%rax),�x                     立即数7减去 %r14 指向的数据
   0x0000000000401164 < 112>:   mov    �x,(%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,�x                       将 �x 加1
   0x000000000040117d < 137>:   cmp    �x,�x                       比较 �x 和 �x 是否相等
   0x000000000040117f < 139>:   jne    0x401176 <phase_6 130>          不相等,继续遍历链表,最终 %rdx 指向链表的第 �x 个节点
   0x0000000000401181 < 141>:   jmp    0x401188 <phase_6 148>
   0x0000000000401183 < 143>:   mov    $0x6032d0,�x                  重置链表首地址
   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),�x              将 (%rsp   %rsi) 指向的数据复制到 �x
   0x000000000040119a < 166>:   cmp    $0x1,�x                       比较 �x 是否小于等于1
   0x000000000040119d < 169>:   jle    0x401183 <phase_6 143>          若小于等于,跳转,否则继续执行, 等于1, �x 直接指向链表首地址
   0x000000000040119f < 171>:   mov    $0x1,�x                       将 �x 置1
   0x00000000004011a4 < 176>:   mov    $0x6032d0,�x                  将 %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,�p
   0x00000000004011df < 235>:   mov    0x8(%rbx),%rax                  将 %rax 指向 %rbx 下一个链表节点
   0x00000000004011e3 < 239>:   mov    (%rax),�x
   0x00000000004011e5 < 241>:   cmp    �x,(%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,�p                       判断循环是否结束,该循环判断栈中重新调整后的链表节点是否按照降序排列
   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.
代码语言:javascript复制
%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,�x
   0x000000000040148a < 46>:    callq  0x400bf0 <__isoc99_sscanf@plt>
   0x000000000040148f < 51>:    cmp    $0x5,�x
   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~ 被调用者保存寄存器
%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减去对应的输入后的数据

代码语言:javascript复制
(gdb) p /x $rsp
$1 = 0x7fffffffe270
(gdb) x/6dw 0x7fffffffe270
0x7fffffffe270: 3   4   5   6
0x7fffffffe280: 1   2

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

代码语言:javascript复制
(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

  保存在栈中链表节点信息

代码语言:javascript复制
(gdb) x/6xg 0x7fffffffe290
0x7fffffffe290: 0x00000000006032f0  0x0000000000603300
0x7fffffffe2a0: 0x0000000000603310  0x0000000000603320
0x7fffffffe2b0: 0x00000000006032d0  0x00000000006032e0

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

代码语言:javascript复制
(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调试代码的步骤。收获颇丰!

0 人点赞