深入理解计算机系统CSAPP-bomblab

CSAPP-bomblab

实验目的

​ 理解汇编语言,学习使用gdb调试器,推导出正确的字符串用于跳过explode_bomb,破解bomb.c生成的炸弹。

实验环境和工具

​ ubuntu 12.04.5 (32位) ;

​ gdb 7.4 ;

实验内容及操作步骤

阅读bomb.c

  • C文件开头的注释如下:
/***************************************************************************
 * Dr. Evil's Insidious Bomb, Version 1.1
 * Copyright 2011, Dr. Evil Incorporated. All rights reserved.
 *
 * LICENSE:
 *
 * Dr. Evil Incorporated (the PERPETRATOR) hereby grants you (the
 * VICTIM) explicit permission to use this bomb (the BOMB).  This is a
 * time limited license, which expires on the death of the VICTIM.
 * The PERPETRATOR takes no responsibility for damage, frustration,
 * insanity, bug-eyes, carpal-tunnel syndrome, loss of sleep, or other
 * harm to the VICTIM.  Unless the PERPETRATOR wants to take credit,
 * that is.  The VICTIM may not distribute this bomb source code to
 * any enemies of the PERPETRATOR.  No VICTIM may debug,
 * reverse-engineer, run "strings" on, decompile, decrypt, or use any
 * other technique to gain knowledge of and defuse the BOMB.  BOMB
 * proof clothing may not be worn when handling this program.  The
 * PERPETRATOR will not apologize for the PERPETRATOR's poor sense of
 * humor.  This license is null and void where the BOMB is prohibited
 * by law.
 ***************************************************************************/
  • 翻译如下:

​ Evil Incorporated博士(PERPETRATOR)特此授予您(VICTIM)使用该炸弹(BOMB)的明确许可。 这是一个有时间限制的许可证,在VICTIM死亡时到期。 PERPETRATOR对损坏,沮丧,精神错乱,虫眼,腕管综合症,睡眠不足或对VICTIM造成的其他伤害不承担任何责任。 除非PERPETRATOR想要获得信誉,否则就是这样。 VICTIM不得将此炸弹源代码分发给PERPETRATOR的任何敌人。 VICTIM不得调试,反向工程,在其上运行“字符串”,反编译,解密或使用任何其他技术来了解和缓解BOMB。 处理此程序时,可能不能穿防弹衣。 PERPETRATOR不会因PERPETRATOR的幽默感而道歉。 在法律禁止BOMB的情况下,此许可无效。

  • bomb.c代码如下:(英文注释做了适当翻译)
#include <stdio.h>
#include <stdlib.h>
#include "support.h"
#include "phases.h"

/* 
 * Note to self: Remember to erase this file so my victims will have no
 * idea what is going on, and so they will all blow up in a
 * spectaculary fiendish explosion. -- Dr. Evil 
 */
//自我提醒:请记住要删除此文件,以使我的受害者不知道发生了什么事,因此他们都会在一场壮观的恶魔般的爆炸中炸毁。 -邪恶博士

FILE *infile;

int main(int argc, char *argv[])
{
    char *input;

    /* Note to self: remember to port this bomb to Windows and put a 
     * fantastic GUI on it. */	//自我提醒:记得将炸弹移植到Windows并在上面放上精美的GUI。
    
    /* When run with no arguments, the bomb reads its input lines 
     * from standard input. */	//当不带参数运行时,炸弹会从标准输入中读取其输入行。
    if (argc == 1) {  
	infile = stdin;
    } 

    /* When run with one argument <file>, the bomb reads from <file> 
     * until EOF, and then switches to standard input. Thus, as you 
     * defuse each phase, you can add its defusing string to <file> and
     * avoid having to retype it. */
    //当使用一个参数<file>运行时,炸弹会从<file>读取直到EOF,然后切换到标准输入。 因此,在对每个阶段进行解压缩时,可以将其解压缩字符串添加到<file>中,而不必重新输入。
    else if (argc == 2) {
	if (!(infile = fopen(argv[1], "r"))) {
	    printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]);
	    exit(8);
	}
    }

    /* You can't call the bomb with more than 1 command line argument. */	    //您不能使用超过1个命令行参数来调用炸弹。
    else {
	printf("Usage: %s [<input_file>]\n", argv[0]);
	exit(8);
    }

    /* Do all sorts of secret stuff that makes the bomb harder to defuse. */	  //做各种使炸弹难以化解的秘密工作。
    initialize_bomb();

    printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
    printf("which to blow yourself up. Have a nice day!\n");

    /* Hmm...  Six phases must be more secure than one phase! */
    //嗯...六个阶段必须比一个阶段更安全!
    input = read_line();             /* Get input                   */ //获取输入
    phase_1(input);                  /* Run the phase               */ //运行阶段
    phase_defused();                 /* Drat!  They figured it out!		//他们想通了!
				      * Let me know how they did it. */					//让我知道他们是如何做到的。
    printf("Phase 1 defused. How about the next one?\n");

    /* The second phase is harder.  No one will ever figure out
     * how to defuse this... */		//第二阶段比较困难。 没有人会想出如何化解这个问题的。
    input = read_line();
    phase_2(input);
    phase_defused();
    printf("That's number 2.  Keep going!\n");

    /* I guess this is too easy so far.  Some more complex code will
     * confuse people. */		//我想到目前为止这太容易了。 一些更复杂的代码会使人们感到困惑。
    input = read_line();
    phase_3(input);
    phase_defused();
    printf("Halfway there!\n");

    /* Oh yeah?  Well, how good is your math?  Try on this saucy problem! */ //哦耶? 好吧,你的数学有多好? 尝试这个棘手的问题!
    input = read_line();
    phase_4(input);
    phase_defused();
    printf("So you got that one.  Try this one.\n");
    
    /* Round and 'round in memory we go, where we stop, the bomb blows! */	//我们在记忆中走来走去,停下来,炸弹炸毁!
    input = read_line();
    phase_5(input);
    phase_defused();
    printf("Good work!  On to the next...\n");

    /* This phase will never be used, since no one will get past the
     * earlier ones.  But just in case, make this one extra hard. */
    //此阶段将永远不会使用,因为没有人会越过早期的阶段。 但以防万一,使这一操作更加困难。
    input = read_line();
    phase_6(input);
    phase_defused();

    /* Wow, they got it!  But isn't something... missing?  Perhaps
     * something they overlooked?  Mua ha ha ha ha! */	//哇,他们明白了! 但是,是不是……缺少了什么? 也许他们忽略了什么? 哇哈哈哈哈哈!
    
    return 0;
}
  • 总之,有两种方法闯关:
  1. ./bomb,然后在终端输入
  2. ./bomb xxx.txtxxx.txt中按行保存用于过关的字符串

分析汇编代码,找到通关密码

1. phase_1
  • phase_1的汇编代码:

    Dump of assembler code for function phase_1:
       0x08048b50 <+0>:		sub    $0x1c,%esp						
       ;栈指针esp减去0x1c,给当前帧开辟大小为0x1c的空间
       0x08048b53 <+3>:		movl   $0x804a1a4,0x4(%esp)				
       ;将0x804a1a4(原字符串在内存中的地址)赋给M[0x4+esp]。
       ;(从后面调用函数<strings_not_equal>推测其为字符串,通过查看<strings_not_equal>的汇编代码进行验证)
       0x08048b5b <+11>:	mov    0x20(%esp),%eax					;将M[0x20+%esp](输入的字符串)赋值给eax
       0x08048b5f <+15>:	mov    %eax,(%esp)						
       ;eax的值赋给M[esp],与M[0x4+esp]共同作为<strings_not_equal>的参数
       0x08048b62 <+18>:	call   0x8048fd4 <strings_not_equal>	
       ;比较两字符串是否相等,若相等,则返回值%eax=0
       0x08048b67 <+23>:	test   %eax,%eax						;如果eax=0,则zf=1
       0x08048b69 <+25>:	je     0x8048b70 <phase_1+32>			;zf=1时跳转至*(phase_1+32),即下一关
       0x08048b6b <+27>:	call   0x80490e6 <explode_bomb>			;否则爆炸
       0x08048b70 <+32>:	add    $0x1c,%esp
       0x08048b73 <+35>:	ret		
    End of assembler dump.
    
    • 其中,<strings_not_equal>的汇编代码如下:

      ·#执行call指令:esp=esp-4
      Dump of assembler code for function strings_not_equal:
         0x08048fd4 <+0>:		sub    $0x10,%esp						;esp=esp-10	
         0x08048fd7 <+3>:		mov    %ebx,0x4(%esp)				
         0x08048fdb <+7>:		mov    %esi,0x8(%esp)
         0x08048fdf <+11>:	mov    %edi,0xc(%esp)
         0x08048fe3 <+15>:	mov    0x14(%esp),%ebx					
         ;此时M[0x14+esp]为输入的字符串*s1,存入ebx
         0x08048fe7 <+19>:	mov    0x18(%esp),%esi					
         ;此时M[0x18+esp]为原字符串*s0,存入esi
         0x08048feb <+23>:	mov    %ebx,(%esp)
         0x08048fee <+26>:	call   0x8048fbb <string_length>		
         ;eax返回值为ebx中字符串的长度s1.length
         0x08048ff3 <+31>:	mov    %eax,%edi						;存入edi
         0x08048ff5 <+33>:	mov    %esi,(%esp)	
         0x08048ff8 <+36>:	call   0x8048fbb <string_length>		
         ;eax返回值为esi中字符串的长度s0.length
         0x08048ffd <+41>:	mov    $0x1,%edx
         0x08049002 <+46>:	cmp    %eax,%edi						;比较s1.length和s0.length
         0x08049004 <+48>:	jne    0x8049039 <strings_not_equal+101>;不相等,则跳转->return 1
         0x08049006 <+50>:	movzbl (%ebx),%eax						;s1首地址存入eax
         0x08049009 <+53>:	mov    $0x0,%dl							;edx=0
         0x0804900b <+55>:	test   %al,%al							
         0x0804900d <+57>:	je     0x8049039 <strings_not_equal+101>;if(*s1==0)->return 0 
         																;(输入空字符)
         0x0804900f <+59>:	mov    $0x1,%dl							;edx=1
         0x08049011 <+61>:	cmp    (%esi),%al						
         0x08049013 <+63>:	jne    0x8049039 <strings_not_equal+101>;if(*s1!=*s0)->return 1
         0x08049015 <+65>:	mov    $0x0,%eax						;eax=0(字符串从*s+1开始的偏移量)
         0x0804901a <+70>:	jmp    0x8049024 <strings_not_equal+80>
         0x0804901c <+72>:	add    $0x1,%eax							
         0x0804901f <+75>:	cmp    (%esi,%eax,1),%dl				;if(*s0+(x++)+1!=*s1+x+1)
         0x08049022 <+78>:	jne    0x8049034 <strings_not_equal+96>	;return 1
         0x08049024 <+80>:	movzbl 0x1(%ebx,%eax,1),%edx			
         						;edx存入ebx+(eax存储的偏移量)+1,即*s1+x+1
         0x08049029 <+85>:	test   %dl,%dl							
         0x0804902b <+87>:	jne    0x804901c <strings_not_equal+72> ;if(*s1+x+1!=0) 执行循环
         0x0804902d <+89>:	mov    $0x0,%edx						
         0x08049032 <+94>:	jmp    0x8049039 <strings_not_equal+101>;否则完成比较,字符串相等,return 0
         0x08049034 <+96>:	mov    $0x1,%edx
         0x08049039 <+101>:	mov    %edx,%eax					;if(edx==1) return 1;else return 0
         0x0804903b <+103>:	mov    0x4(%esp),%ebx
         0x0804903f <+107>:	mov    0x8(%esp),%esi
         0x08049043 <+111>:	mov    0xc(%esp),%edi
         0x08049047 <+115>:	add    $0x10,%esp
         0x0804904a <+118>:	ret    
      End of assembler dump.
      
      • <string_length>的汇编代码如下:

        Dump of assembler code for function string_length:
           0x08048fbb <+0>:		mov    0x4(%esp),%edx				;edx=[0x4+esp] --字符串首地址
           0x08048fbf <+4>:		mov    $0x0,%eax					;eax=0	--计数
           0x08048fc4 <+9>:		cmpb   $0x0,(%edx)					
           0x08048fc7 <+12>:	je     0x8048fd2 <string_length+23>	;edx=0则退出
           0x08048fc9 <+14>:	add    $0x1,%eax					;eax++ --偏移量++
           0x08048fcc <+17>:	cmpb   $0x0,(%edx,%eax,1)			;edx+eax!=0,即当前字符不为空
           0x08048fd0 <+21>:	jne    0x8048fc9 <string_length+14>	;则继续循环
           0x08048fd2 <+23>:	repz ret 							;返回eax则为字符串长度
        End of assembler dump.
        
  • 所以我们要使输入的字符串和0x804a1a4存储的字符串相等。

  • 查看0x804a1a4存储的字符串:

image-20210510225736415

  • 运行./bomb,输入该字符串:Crikey! I have lost my mojo!,即可得到通过第一关:

image-20210511204851504

2. phase_2
  • phase_2的汇编代码(调用函数<read_six_numbers>前的部分)
Dump of assembler code for function phase_2:
   0x08048b74 <+0>:		push   %esi								;调用程序的地址入栈
   0x08048b75 <+1>:		push   %ebx								;被调用者保存寄存器的值入栈
   0x08048b76 <+2>:		sub    $0x34,%esp						;栈指针esp减去0x34,
   																;--给当前帧开辟大小为0x34的空间
   0x08048b79 <+5>:		lea    0x18(%esp),%eax					;eax=地址(0x18+esp) 
   0x08048b7d <+9>:		mov    %eax,0x4(%esp)					;M[0x4+esp]=eax=地址(0x18+esp)-数组首地址
   0x08048b81 <+13>:	mov    0x40(%esp),%eax					;eax=M[0x40+esp]
   0x08048b85 <+17>:	mov    %eax,(%esp)						;M[esp]=eax=M[0x40+esp]	-输入
   0x08048b88 <+20>:	call   0x804921b <read_six_numbers>		;调用函数(读入6个数)
  • <read_six_numbers>的汇编代码如下:

    #调用函数 esp=esp-4
    Dump of assembler code for function read_six_numbers:
       0x0804921b <+0>:		sub    $0x2c,%esp						;栈指针esp减去0x2c
       0x0804921e <+3>:		mov    0x34(%esp),%eax					;eax=esp+0x34 [原esp+0x4]
       0x08049222 <+7>:		lea    0x14(%eax),%edx
       0x08049225 <+10>:	mov    %edx,0x1c(%esp)					;读入第六个数
       0x08049229 <+14>:	lea    0x10(%eax),%edx
       0x0804922c <+17>:	mov    %edx,0x18(%esp)					;读入第五个数
       0x08049230 <+21>:	lea    0xc(%eax),%edx						
       0x08049233 <+24>:	mov    %edx,0x14(%esp)					;读入第四个数
       0x08049237 <+28>:	lea    0x8(%eax),%edx
       0x0804923a <+31>:	mov    %edx,0x10(%esp)					;读入第三个数
       0x0804923e <+35>:	lea    0x4(%eax),%edx
       0x08049241 <+38>:	mov    %edx,0xc(%esp)					;读入第二个数
       0x08049245 <+42>:	mov    %eax,0x8(%esp)					;读入第一个数
       0x08049249 <+46>:	movl   $0x804a3bf,0x4(%esp)
       0x08049251 <+54>:	mov    0x30(%esp),%eax					;eax=esp+0x30
       0x08049255 <+58>:	mov    %eax,(%esp)						;M[esp]=eax=输入
       0x08049258 <+61>:	call   0x8048870 <__isoc99_sscanf@plt>
       						;函数__isoc99_sscanf返回输入参数的个数
       0x0804925d <+66>:	cmp    $0x5,%eax						
       0x08049260 <+69>:	jg     0x8049267 <read_six_numbers+76> ;输入参数数量大于5则安全退出该函数
       0x08049262 <+71>:	call   0x80490e6 <explode_bomb>
       0x08049267 <+76>:	add    $0x2c,%esp
       0x0804926a <+79>:	ret    
    End of assembler dump.
    
    • 函数<__isoc99_sscanf@plt>的汇编代码:

      Dump of assembler code for function __isoc99_sscanf@plt:
         0x08048870 <+0>:		jmp    *0x804c040		
         0x08048876 <+6>:		push   $0x80
         0x0804887b <+11>:	jmp    0x8048760
      End of assembler dump.
      

      查看__isoc99_sscanf调用的第二个参数,可知可以成功输入6个参数的形式

      image-20210511202402385

  • phase_2的汇编代码(调用函数<read_six_numbers>后的部分)

   0x08048b8d <+25>:	cmpl   $0x1,0x18(%esp)					;比较M[0x18+esp]和0x1的大小
   0x08048b92 <+30>:	je     0x8048b99 <phase_2+37>			;若相等,跳转至<phase_2+37>	
   0x08048b94 <+32>:	call   0x80490e6 <explode_bomb>			;否则爆炸
   0x08048b99 <+37>:	lea    0x1c(%esp),%ebx					;ebx=地址(0x1c+esp)	--第一次
   0x08048b9d <+41>:	lea    0x30(%esp),%esi					;esi=地址(0x30+esp)
   0x08048ba1 <+45>:	mov    -0x4(%ebx),%eax					;eax=M[ebx-0x4]			
   																;--第一次为M[0x18+esp]=1
   0x08048ba4 <+48>:	add    %eax,%eax						;eax=2M[ebx-0x4]		
   0x08048ba6 <+50>:	cmp    %eax,(%ebx)						;比较M[ebx]和2M[ebx-0x4]的大小	
   																;--第一次为M[0x1c+esp]和2M[0x18+esp]
   0x08048ba8 <+52>:	je     0x8048baf <phase_2+59>			;若相等,跳转至<phase_2+59>
   0x08048baa <+54>:	call   0x80490e6 <explode_bomb>			;否则爆炸
   0x08048baf <+59>:	add    $0x4,%ebx						;ebx=ebx+0x4			
   																;--第一次变为地址(0x20+esp)
   0x08048bb2 <+62>:	cmp    %esi,%ebx						;比较(0x30+esp)和ebx所存地址的大小
   0x08048bb4 <+64>:	jne    0x8048ba1 <phase_2+45>			;若不相等则跳转至<phase_2+45>,执行循环
   0x08048bb6 <+66>:	add    $0x34,%esp						;循环结束后,释放栈帧空间
   0x08048bb9 <+69>:	pop    %ebx								;还原ebx	
   0x08048bba <+70>:	pop    %esi								;弹出入口地址
   0x08048bbb <+71>:	ret										;成功结束phase_2
End of assembler dump.
  • 通过上方汇编代码的<+62>、<+64>可知,该函数会执行循环,在每次循环中ebx所存地址(初始地址为0x1c+esp)的值会先加0x4,直到等于0x30+esp。故循环的执行次数为5次。0x1c+esp的初始值为M[0x18+esp]=1的两倍为2,如此循环。可以推知我们输入6个数存储地址分别为上方的(0x18+esp)(0x1c+esp)(0x20+esp)(0x24+esp)(0x28+esp)(0x2c+esp),是以1为首项,2为公比的等比数列。故通关密码为:1 2 4 8 16 32,结果如下:

image-20210511204913739

3. phase_3
  • phase_3的汇编代码(调用<__isoc99_sscanf@plt>前的部分)

    Dump of assembler code for function phase_3:
       0x08048bbc <+0>:		sub    $0x2c,%esp						;栈指针esp减去0x2c
       0x08048bbf <+3>:		lea    0x1c(%esp),%eax					;eax=(0x1c+esp)
       0x08048bc3 <+7>:		mov    %eax,0xc(%esp)					;M[0xc+esp]=(0x1c+esp)
       0x08048bc7 <+11>:	lea    0x18(%esp),%eax					;eax=(0x18+esp)
       0x08048bcb <+15>:	mov    %eax,0x8(%esp)					;M[0x8+esp]=(0x18+esp)
       0x08048bcf <+19>:	movl   $0x804a3cb,0x4(%esp)				;M[0x4+esp]=0x804a3cb
       0x08048bd7 <+27>:	mov    0x30(%esp),%eax					;eax=M[0x30+esp]
       0x08048bdb <+31>:	mov    %eax,(%esp)						;M[esp]=eax
       0x08048bde <+34>:	call   0x8048870 <__isoc99_sscanf@plt>
    
    • 这里也调用了函数 <__isoc99_sscanf@plt>。查看其第二个参数所在地址$0x804a3cb的内容:

    image-20210511211431900

    • 可知输入应该为两个整型数据,对应的保存地址分别为0x18+esp,0x1c+esp
  • phase_3的汇编代码(剩余部分):

       0x08048be3 <+39>:	cmp    $0x1,%eax					
       ;`<__isoc99_sscanf@plt>的返回值为输入参数的个数,保存在eax中
       0x08048be6 <+42>:	jg     0x8048bed <phase_3+49>	;参数个数需大于1,否则爆炸
       0x08048be8 <+44>:	call   0x80490e6 <explode_bomb>
       0x08048bed <+49>:	cmpl   $0x7,0x18(%esp)			
       0x08048bf2 <+54>:	ja     0x8048c5a <phase_3+158>	
       ;第一个整数需要无符号小于等于7,即范围为0-7否则跳转->爆炸
       0x08048bf4 <+56>:	mov    0x18(%esp),%eax			
       0x08048bf8 <+60>:	jmp    *0x804a1e0(,%eax,4)		
       ;无条件跳转至地址(M[0x804a1e0+4*eax]),即根据第一个整数为跳转的偏移量
       ;根据跳转表,以eax=1为例子如下:
       0x08048bff <+67>:	mov    $0x0,%eax				;eax=0
       0x08048c04 <+72>:	jmp    0x8048c0b <phase_3+79>	;跳转至<phase_3+79>
       0x08048c06 <+74>:	mov    $0x240,%eax				;(被跳过)
       0x08048c0b <+79>:	sub    $0x28b,%eax				;eax=eax-0x28b=-0x28b
       0x08048c10 <+84>:	jmp    0x8048c17 <phase_3+91>	;跳转至<phase_3+91>
       0x08048c12 <+86>:	mov    $0x0,%eax				;(被跳过)
       0x08048c17 <+91>:	add    $0x35a,%eax				;eax=eax+0x35a=0xcf
       0x08048c1c <+96>:	jmp    0x8048c23 <phase_3+103>	;跳转至<phase_3+103>
       0x08048c1e <+98>:	mov    $0x0,%eax				;(被跳过)	
       0x08048c23 <+103>:	sub    $0xc5,%eax				;eax=eax-0xc5=0xa
       0x08048c28 <+108>:	jmp    0x8048c2f <phase_3+115>	;跳转至<phase_3+115>
       0x08048c2a <+110>:	mov    $0x0,%eax				;(被跳过)
       0x08048c2f <+115>:	add    $0xc5,%eax				;eax=eax+0xc5=0xcf
       0x08048c34 <+120>:	jmp    0x8048c3b <phase_3+127>	;跳转至<phase_3+127>
       0x08048c36 <+122>:	mov    $0x0,%eax				;(被跳过)
       0x08048c3b <+127>:	sub    $0xc5,%eax				;eax=eax-0xc5=0xa
       0x08048c40 <+132>:	jmp    0x8048c47 <phase_3+139>	;跳转至<phase_3+139>
       0x08048c42 <+134>:	mov    $0x0,%eax				;(被跳过)
       0x08048c47 <+139>:	add    $0xc5,%eax				;eax=eax+0xc5=0xcf
       0x08048c4c <+144>:	jmp    0x8048c53 <phase_3+151>	;跳转至<phase_3+151>
       0x08048c4e <+146>:	mov    $0x0,%eax				;(被跳过)
       0x08048c53 <+151>:	sub    $0xc5,%eax				;eax=eax-0xc5=0xa
       0x08048c58 <+156>:	jmp    0x8048c64 <phase_3+168>	;(需要此步)跳转至<phase_3+168>,否则爆炸
       0x08048c5a <+158>:	call   0x80490e6 <explode_bomb>
       0x08048c5f <+163>:	mov    $0x0,%eax
       0x08048c64 <+168>:	cmpl   $0x5,0x18(%esp)			
       0x08048c69 <+173>:	jg     0x8048c71 <phase_3+181>	;M[0x18+esp]>5则爆炸
       0x08048c6b <+175>:	cmp    0x1c(%esp),%eax
       0x08048c6f <+179>:	je     0x8048c76 <phase_3+186>	;eax=M[0X1c+esp]时跳转过关,否则爆炸
       0x08048c71 <+181>:	call   0x80490e6 <explode_bomb>
       0x08048c76 <+186>:	add    $0x2c,%esp
       0x08048c79 <+189>:	ret    
    End of assembler dump.
    
    • 从该汇编代码的<+156>到结束可知,我们需要经过跳转到达0x08048c58 <+156>,且输入的第一个数的值需要 < = 5 <=5 <=5,输入的第二个数需要等于eax。

    • 我们通过x指令,参照jmp *0x804a1e0(,%eax,4) 的跳转表如下:

      image-20210512084515692

    • 上述汇编代码的注释输入的第一个数M[0x18+esp]=1为例,先跳转至<phase_3+67>

      得到最终M[0x1c+esp]需要等于0xa,即十进制的10。故我们可以输入1 10过关:

      image-20210512085729848

      • 同样可推知输入0—7对应的第二个数组合如下:

        0 586 1 10 2 661 3 -197 4 0 5 -197 6 0 7 197

      • 同样输入0~5中的其他数以及经过运算得到的相对应的第二个数,也能过关。

4. phase_4
  • phase_4的汇编代码(调用<__isoc99_sscanf@plt>前的部分)

    Dump of assembler code for function phase_4:
       0x08048cd7 <+0>:		sub    $0x2c,%esp				;栈指针esp减去0x2c
       0x08048cda <+3>:		lea    0x18(%esp),%eax			;eax=(0x18+esp)
       0x08048cde <+7>:		mov    %eax,0xc(%esp)			;M[0xc+esp]=eax=(0x18+esp)
       0x08048ce2 <+11>:	lea    0x1c(%esp),%eax			;eax=(0x1c+esp)
       0x08048ce6 <+15>:	mov    %eax,0x8(%esp)			;M[0x8+esp]=eax=(0x1c+esp)
       0x08048cea <+19>:	movl   $0x804a3cb,0x4(%esp)		;M[0x4+esp]=0x804a3cb
       0x08048cf2 <+27>:	mov    0x30(%esp),%eax			;eax=M[0x30+esp]
       0x08048cf6 <+31>:	mov    %eax,(%esp)				;M[esp]=eax=M[0x30+esp]
       0x08048cf9 <+34>:	call   0x8048870 <__isoc99_sscanf@plt>
    
    • 和phase_3相似,调用函数<__isoc99_sscanf@plt>将输入以地址0x804a3cb存储的形式保存:

      image-20210512133109861

      可见输入为两个整型数据,分别保存在地址0x1c+esp0x18+esp中。(要注意0x1c+esp是第三个参数,0x18+esp是第四个参数,表示输入数据存储的次序,和phase_3正好相反!)。

      函数返回值为输入数据的个数。

  • phase_4的汇编代码(剩余部分)

       0x08048cfe <+39>:	cmp    $0x2,%eax				;比较<__isoc99_sscanf@plt>的返回值和2
       0x08048d01 <+42>:	jne    0x8048d11 <phase_4+58>	;不相等则爆炸
       0x08048d03 <+44>:	mov    0x18(%esp),%eax			;eax=M[0x18+esp] 即输入的第二个整数
       0x08048d07 <+48>:	cmp    $0x1,%eax				;比较eax和1
       0x08048d0a <+51>:	jle    0x8048d11 <phase_4+58>	;eax<=1则爆炸,故第二个整数要大于1
       0x08048d0c <+53>:	cmp    $0x4,%eax				;比较eax和4
       0x08048d0f <+56>:	jle    0x8048d16 <phase_4+63>	;eax<=4则不会爆炸,故第二个整数小于等于4
       0x08048d11 <+58>:	call   0x80490e6 <explode_bomb>	
       0x08048d16 <+63>:	mov    0x18(%esp),%eax			;eax=M[0x18+esp],还是第二个整数
       0x08048d1a <+67>:	mov    %eax,0x4(%esp)			;M[0x4+esp]=eax=M[0x18+esp]
       0x08048d1e <+71>:	movl   $0x9,(%esp)				;M[esp]=9
       0x08048d25 <+78>:	call   0x8048c7a <func4>		;调用函数<func4>
       0x08048d2a <+83>:	cmp    0x1c(%esp),%eax			
       ;比较<func4>的返回值和M[esp+0x1c](即输入的第一个整数)的大小
       0x08048d2e <+87>:	je     0x8048d35 <phase_4+94>	;相等则过关
       0x08048d30 <+89>:	call   0x80490e6 <explode_bomb>
       0x08048d35 <+94>:	add    $0x2c,%esp
       0x08048d38 <+97>:	ret    
    End of assembler dump.
    
    • 故输入的第二个整数(大于1且小于等于4)会进入函数<func4>产生返回值,输入的第一个整数应该和该返回值相等

    • 其中,<func4>的汇编代码如下:

      ;call调用函数,esp=esp-4。观察整个代码,有两个参数,设为x和y(初值为9和输入的第一个整数)
      (gdb) disass func4
      Dump of assembler code for function func4:
         0x08048c7a <+0>:		sub    $0x1c,%esp			;esp=esp-0x1c,开辟0x1c的栈帧空间
         0x08048c7d <+3>:		mov    %ebx,0x10(%esp)		
         0x08048c81 <+7>:		mov    %esi,0x14(%esp)		
         0x08048c85 <+11>:	mov    %edi,0x18(%esp)		;以上三行保存被调用寄存器的原值
         0x08048c89 <+15>:	mov    0x20(%esp),%esi		;esi=M[0x20+esp]=x
         0x08048c8d <+19>:	mov    0x24(%esp),%ebx		;ebx=M[0x24+esp]=y
         0x08048c91 <+23>:	test   %esi,%esi			
         0x08048c93 <+25>:	jle    0x8048cc0 <func4+70>	;ZF=1或SF!=OF时跳转
         						;test %esi,%esi后,若esi=0,则ZF=1;或esi为负数,SF!=OF  ->return 0
         0x08048c95 <+27>:	cmp    $0x1,%esi			
         0x08048c98 <+30>:	je     0x8048cc5 <func4+75>	;x=1时跳转至<func4+75> ->return y
         0x08048c9a <+32>:	mov    %ebx,0x4(%esp)		;M[0x4+esp]=ebx=y
         0x08048c9e <+36>:	lea    -0x1(%esi),%eax		;eax=(esi-1)=(x-1)
         0x08048ca1 <+39>:	mov    %eax,(%esp)			;M[esp]=eax=(esi-1)=(x-1)
         0x08048ca4 <+42>:	call   0x8048c7a <func4>	;递归调用func4(x-1,y)
         0x08048ca9 <+47>:	lea    (%eax,%ebx,1),%edi	;edi=(eax+ebx)=func4(x-1,y)+y;
         0x08048cac <+50>:	mov    %ebx,0x4(%esp)		;M[0x4+esp]=ebx=y
         0x08048cb0 <+54>:	sub    $0x2,%esi			;esi=(esi-2)=(x-2)
         0x08048cb3 <+57>:	mov    %esi,(%esp)			;M[esp]=esi=(x-2)
         0x08048cb6 <+60>:	call   0x8048c7a <func4>	;递归调用func4(x-2,y)
         0x08048cbb <+65>:	lea    (%edi,%eax,1),%ebx	;ebx=(edi+eax)=func4(x-1,y)+y+func4(x-2,y)
         0x08048cbe <+68>:	jmp    0x8048cc5 <func4+75>	
         						;跳转至<func4+75> ->return func4(x-1,y)+y+func4(x-2,y)
         0x08048cc0 <+70>:	mov    $0x0,%ebx			
         0x08048cc5 <+75>:	mov    %ebx,%eax			;eax=ebx,即为返回值
         0x08048cc7 <+77>:	mov    0x10(%esp),%ebx		
         0x08048ccb <+81>:	mov    0x14(%esp),%esi
         0x08048ccf <+85>:	mov    0x18(%esp),%edi		;以上三行恢复被调用寄存器的值
         0x08048cd3 <+89>:	add    $0x1c,%esp
         0x08048cd6 <+92>:	ret    
      End of assembler dump.
      
      • 可以得到func4函数和主函数的C++代码如下:
      int func4(int x,int y)
      {
      	if(x<=0)
      		return 0;
      	else if(x==1)
      		return y;
      	else
      		return func4(x-1,y)+y+func4(x-2,y);
      }
      int main()
      {
          cin>>a>>b;
          if(b<=1||b>4)
              explode_bomb();
          else
          {
              if(func4(9,b)!=a)
                  explode_bomb();
          }
          return 0;
      }
      
      • 另写代码输出func4得到的第二个整数(只能为2、3、4)对应的第一个整数:

      image-20210512154349129

    • 输入这3组整数都能过关。

      image-20210512154529595

      image-20210512155152270

      image-20210512155207546

5. phase_5
  • phase_5的汇编代码如下:

    Dump of assembler code for function phase_5:
       0x08048d39 <+0>:		push   %ebx						;保存ebx的原值
       0x08048d3a <+1>:		sub    $0x18,%esp				;esp=esp-0x18,预留0x18的栈帧空间	
       0x08048d3d <+4>:		mov    0x20(%esp),%ebx			;ebx=M[0x20+esp],为输入的字符串
       0x08048d41 <+8>:		mov    %ebx,(%esp)				;M[esp]=ebx
       0x08048d44 <+11>:	call   0x8048fbb <string_length>
       						;调用函数<string_length>(phase_1中出现过),返回值为输入字符串的长度
       0x08048d49 <+16>:	cmp    $0x6,%eax				
       0x08048d4c <+19>:	je     0x8048d53 <phase_5+26>	;若eax=6则跳转
       0x08048d4e <+21>:	call   0x80490e6 <explode_bomb>	;否则爆炸
       0x08048d53 <+26>:	mov    $0x0,%edx				;edx=0
       0x08048d58 <+31>:	mov    $0x0,%eax				;eax=0
       0x08048d5d <+36>:	movsbl (%ebx,%eax,1),%ecx		;ecx=M(ebx+eax),高位用符号位补齐
       0x08048d61 <+40>:	and    $0xf,%ecx				;保留ecx的低四位(字符的ASCII码对16取余)
       0x08048d64 <+43>:	add    0x804a200(,%ecx,4),%edx	;edx=edx+M[0x804a200+4*ecx]
       0x08048d6b <+50>:	add    $0x1,%eax				;eax=eax+1
       0x08048d6e <+53>:	cmp    $0x6,%eax				
       0x08048d71 <+56>:	jne    0x8048d5d <phase_5+36>	;eax不等于6则跳转至<phase_5+36>,执行循环
       0x08048d73 <+58>:	cmp    $0x2b,%edx				
       0x08048d76 <+61>:	je     0x8048d7d <phase_5+68>	;若edx=0x2b 则过关
       0x08048d78 <+63>:	call   0x80490e6 <explode_bomb>
       0x08048d7d <+68>:	add    $0x18,%esp
       0x08048d80 <+71>:	pop    %ebx
       0x08048d81 <+72>:	ret    	
    End of assembler dump.
    
    • 可见输入的字符串长度为6。在循环中,依次取字符串的1个字符,ASCII码对16取余,保存于ecx中。每次edx加上M[0x804a200+4*ecx],相当于0x804a200为数组,ecx为数组的索引。

    • 循环执行次数为6次。结束循环后,edx的值需为0x2b(即43)。

      • 查看首地址为0x804a200的数组保存的数字,数组长度为16,如下:

        image-20210512202614163

        • 需要在其中取6个数,和为43。可选择:13、10、12、4、3、1。(不唯一)
        • 对应索引分别为:15、1、4、8、7、3,分别对应字符ASCII码的余数。
      • 对应字符可以为:O、A、D、H、G、C。(答案不唯一)

  • 输入OADHGC,过关:

    image-20210512203929992

6. phase_6
  • phase_6的汇编代码(分为多个部分)如下:

  • 第一部分:

 Dump of assembler code for function phase_6:
     0x08048d82 <+0>:	push   %esi							;esi入栈
     0x08048d83 <+1>:	push   %ebx							;ebx入栈
     0x08048d84 <+2>:	sub    $0x44,%esp					;esp=esp-0x44,栈帧预留0x44的空间
     0x08048d87 <+5>:	lea    0x10(%esp),%eax				;eax=(esp+0x10)
     0x08048d8b <+9>:	mov    %eax,0x4(%esp)				;M[0x4+esp]=eax 
     0x08048d8f <+13>:	mov    0x50(%esp),%eax				;eax=M[0x50+esp]=输入
     0x08048d93 <+17>:	mov    %eax,(%esp)					;M[esp]=eax=输入
     0x08048d96 <+20>:	call   0x804921b <read_six_numbers>	
     ;调用函数<read_six_numbers>(phase_2中出现过),读入6个数,相当于读入首地址为(esp+0x10)的长度为6的int型数组	a[]
     0x08048d9b <+25>:	mov    $0x0,%esi					;esi=0
     0x08048da0 <+30>:	mov    0x10(%esp,%esi,4),%eax		;eax=M[0x10+(esp+4*esi)]
     0x08048da4 <+34>:	sub    $0x1,%eax					;eax=eax-1=M[eax]-1
     0x08048da7 <+37>:	cmp    $0x5,%eax					
     0x08048daa <+40>:	jbe    0x8048db1 <phase_6+47>		;若eax<=5(无符号),则跳转至<phase_6+47>
     0x08048dac <+42>:	call   0x80490e6 <explode_bomb>		;否则爆炸
     0x08048db1 <+47>:	add    $0x1,%esi					;esi=esi+1
     0x08048db4 <+50>:	cmp    $0x6,%esi					
     0x08048db7 <+53>:	je     0x8048dd4 <phase_6+82>		;esi=6则跳转至<phase_6+82>
     0x08048db9 <+55>:	mov    %esi,%ebx					;ebx=esi
     0x08048dbb <+57>:	mov    0x10(%esp,%ebx,4),%eax		;eax=M[0x10+(esp+4*ebx)]
     0x08048dbf <+61>:	cmp    %eax,0xc(%esp,%esi,4)		
     0x08048dc3 <+65>:	jne    0x8048dca <phase_6+72>		
     						;若M[0xc+(esp+4*esi)]和eax不相等,则跳转至<phase_6+72>
     0x08048dc5 <+67>:	call   0x80490e6 <explode_bomb>		;否则爆炸
     0x08048dca <+72>:	add    $0x1,%ebx					;ebx=ebx+1
     0x08048dcd <+75>:	cmp    $0x5,%ebx					
     0x08048dd0 <+78>:	jle    0x8048dbb <phase_6+57>		
     						;若ebx<=5,则跳转至<phase_6+57> -> eax=M[0x10+(esp+4*ebx)]=a[ebx]
     0x08048dd2 <+80>:	jmp    0x8048da0 <phase_6+30>		
     						;否则跳转至<phase_6+30> -> eax=M[0x10+(esp+4*esi)]=a[esi]
  • 在读入6个数后,根据汇编代码可以推得对应的C++代码(数组为a[]):

    for(int i=0;i<=5;i++)
    {
    	if(a[i]-1>5||a[i]-1<0)
        	explode_bomb();
        else
            for(int j=i+1;j<=5;j++)
                if(a[j]==a[i])
                   explode_bomb();    
    }
    //结束后跳转至<phase_6+82>
    
    • 这个双重循环确保数组a[]中的6个整数不相等且范围为[1,6]。
  • 第二部分:

       0x08048dd4 <+82>:	lea    0x10(%esp),%eax				;eax=(0x10+esp)
       0x08048dd8 <+86>:	lea    0x28(%esp),%ebx				;ebx=(0x28+esp)
       0x08048ddc <+90>:	mov    $0x7,%ecx					;ecx=7
       0x08048de1 <+95>:	mov    %ecx,%edx					;edx=ecx=7
       0x08048de3 <+97>:	sub    (%eax),%edx					;edx=edx-M[eax]=7-M[eax]
       0x08048de5 <+99>:	mov    %edx,(%eax)					;M[eax]=edx
       0x08048de7 <+101>:	add    $0x4,%eax					;eax=eax+4
       0x08048dea <+104>:	cmp    %ebx,%eax					
       0x08048dec <+106>:	jne    0x8048de1 <phase_6+95>		;若eax!=ebx,则回跳至<phase_6+95>
    
    • 这部分汇编代码写成C++代码如下:

      for(int i=0;a[i]!=a[6];i++)
      {
          a[i]=7-a[i];
      }
      
  • 第三部分:

       0x08048dee <+108>:	mov    $0x0,%ebx					;ebx=0;
       0x08048df3 <+113>:	jmp    0x8048e0b <phase_6+137>		;跳转至<phase_6+137>
       0x08048df5 <+115>:	mov    0x8(%edx),%edx				;edx=M[0x8+edx]			
       0x08048df8 <+118>:	add    $0x1,%eax					;eax=eax+1
       0x08048dfb <+121>:	cmp    %ecx,%eax					
       0x08048dfd <+123>:	jne    0x8048df5 <phase_6+115>		;若eax!=ecx,回跳至<phase_6+115>
       0x08048dff <+125>:	mov    %edx,0x28(%esp,%esi,4)		;M[0x28+esp+4*esi]=edx
       0x08048e03 <+129>:	add    $0x1,%ebx					;ebx=ebx+1
       0x08048e06 <+132>:	cmp    $0x6,%ebx
       0x08048e09 <+135>:	je     0x8048e22 <phase_6+160>		;若ebx=6,跳转至<phase_6+160>(跳出循环)
       0x08048e0b <+137>:	mov    %ebx,%esi					;esi=ebx
       0x08048e0d <+139>:	mov    0x10(%esp,%ebx,4),%ecx		;ecx=M[0x10+esp+4*ebx]=a[ebx]
       0x08048e11 <+143>:	mov    $0x1,%eax					;eax=1;
       0x08048e16 <+148>:	mov    $0x804c13c,%edx				;edx=0x804c13c	
       															;以上4行相当于初始化寄存器
       0x08048e1b <+153>:	cmp    $0x1,%ecx					
       0x08048e1e <+156>:	jg     0x8048df5 <phase_6+115>		;若a[ebx]>1,跳转至<phase_6+115>
       0x08048e20 <+158>:	jmp    0x8048dff <phase_6+125>		;否则跳转至<phase_6+125>
    
    • edx=0x804c13cedx=M[0x8+edx] 为突破口,先查看0x804c13c存放的东西:

      image-20210512225255740

      • 由node联想到结点类(结构体),且地址0x8+edx存储的为指向下一结点的地址。
    • 依次查看结构体的信息如下,共有6个结构体:

      image-20210512233328889

      • 可以观察到结构体有一个值(val)、编号(id)、下一节点的地址(*next)构成,形成链表结构。
    • 可推成C++代码帮助理解,如下:

      struct node
      {
        int val;
        int id;
        node *next;
      };  
      node *n[6];
      ebx=0;
      do
      {
          esi=ebx;
      	ecx=a[ebx];	
      	eax=1;
      	edx=0x804c13c	//头结点的首地址	
          if(a[ebx]>1)	//数组a[]的范围为[1,6],只有a[ebx]等于1时不执行if内的循环。
          {
              do
              {
          		edx=M[0x8+edx];	//下一结点地址
              	eax++;
              }while(eax!=ecx);	//直到eax=a[ebx]时跳出循环,故循环次数为a[ebx]的值
          }
          node[esi]=edx;	//故第i次循环是为了让原先的第a[i-1]个结点变为node[i-1]。 (i=ebx+1,esi=ebx)
          				//其中a[i-1]=(7-输入的第i个值)
          ebx++;
      }while(ebx<6) //共执行循环6次
      
      • 故根据数组a[]的值,给6个node节点重新排列:让原先的第a[i-1]个结点变为node[i-1]。其中a[i-1]=(7-输入的第i个值)。
  • 第四部分

       0x08048e22 <+160>:	mov    0x28(%esp),%ebx			;ebx=M[0x28+esp]	--node[0]的地址
       0x08048e26 <+164>:	mov    0x2c(%esp),%eax			;eax=M[0x2c+esp]	--node[1]的地址
       0x08048e2a <+168>:	mov    %eax,0x8(%ebx)			;M[ebx+0x8]=eax		--node[0].next=node[1]
       														;以下至<+196>都为连接结点、形成链表的操作
       0x08048e2d <+171>:	mov    0x30(%esp),%edx
       0x08048e31 <+175>:	mov    %edx,0x8(%eax)
       0x08048e34 <+178>:	mov    0x34(%esp),%eax
       0x08048e38 <+182>:	mov    %eax,0x8(%edx)
       0x08048e3b <+185>:	mov    0x38(%esp),%edx
       0x08048e3f <+189>:	mov    %edx,0x8(%eax)
       0x08048e42 <+192>:	mov    0x3c(%esp),%eax
       0x08048e46 <+196>:	mov    %eax,0x8(%edx)
       0x08048e49 <+199>:	movl   $0x0,0x8(%eax)			;M[0x8+eax]=0,node[5].next=0(空)
       0x08048e50 <+206>:	mov    $0x5,%esi				;esi=5
       0x08048e55 <+211>:	mov    0x8(%ebx),%eax			;eax=M[0x8+ebx]	--下一节点的地址
       0x08048e58 <+214>:	mov    (%eax),%edx				;edx=M[eax]		--下一结点的值
       0x08048e5a <+216>:	cmp    %edx,(%ebx)			
       0x08048e5c <+218>:	jge    0x8048e63 <phase_6+225>	
       						;若M[ebx]>=edx,即某结点的值不小于其后一个结点的值,则不会爆炸
       0x08048e5e <+220>:	call   0x80490e6 <explode_bomb>	;否则爆炸
       0x08048e63 <+225>:	mov    0x8(%ebx),%ebx			;ebx=M[ebx+0x8]	--下一个结点的地址
       0x08048e66 <+228>:	sub    $0x1,%esi				;esi=esi-1 
       0x08048e69 <+231>:	jne    0x8048e55 <phase_6+211>	;当esi!=0时,回跳至<phase_6+211>,执行循环
       														;故执行<+211>到<+231>的次数为6。
       0x08048e6b <+233>:	add    $0x44,%esp				;恢复栈帧,过关。
       0x08048e6e <+236>:	pop    %ebx
       0x08048e6f <+237>:	pop    %esi
     0x08048e70 <+238>:	ret    
    End of assembler dump.
    
    • 故phase_6要求输入6个数,根据这6个数,对带值(val)的6个结点进行重新排序,使得结点的值降序排列。

    • 根据之前查看的结点信息:

      image-20210512233328889

      可以列表如下:

      原结点编号123456
      原值0xdf0x2520x3760xea0x2fb0xf3
      目标编号631524
    • 让原先的第a[i]个结点变为node[i],故数组a[6]={3,5,2,6,4,1}

    • 故输入(7-a[i])4 2 5 1 3 6,过关截图如下:

      image-20210513114343306

7. phase_defused (含secret_phase)
  • 看似结束了。但回到bomb.c,在生成炸弹后,和输入有关的函数中,我们还有两个函数还没查看汇编代码,分别为read_line()
    phase_defused()。何况邪恶博士Dr. Evil还留下了这样一句话,好似幸灾乐祸:

    /* Wow, they got it!  But isn't something... missing?  Perhaps
     * something they overlooked?  Mua ha ha ha ha! */	
    //哇,他们明白了! 但是,是不是……缺少了什么? 也许他们忽略了什么? 哇哈哈哈哈哈!
    
  • 先看了看read_line(),就是对一行输入进行花里胡哨的处理后保存在指定地址,没有什么特别之处。

  • phase_defused()的汇编代码如下:

    • 第一部分:
    Dump of assembler code for function phase_defused:
       0x0804926b <+0>:		sub    $0x8c,%esp
       0x08049271 <+6>:		mov    %gs:0x14,%eax					;取gs:0x14处的值
       0x08049277 <+12>:	mov    %eax,0x7c(%esp)					;置于M[0x7c+esp]
       0x0804927b <+16>:	xor    %eax,%eax						;ZF=1
       0x0804927d <+18>:	cmpl   $0x6,0x804c3cc					;比较M[0x804c3cc]和6的值
       0x08049284 <+25>:	jne    0x80492f8 <phase_defused+141>	;不相等则跳过secret_phase,故我们要让其相等
    
    • 猜测6和我们的关卡数量有关。故在进入每个关卡的函数的位置设断点,观察0x804c3cc的变化情况:

      • 进入关卡1前,M[0x804c3cc]=0

        image-20210513200430515

      • 进入关卡1后,M[0x804c3cc]=1:

        image-20210513200542728

      • 进入关卡2后,M[0x804c3cc]=2:

        image-20210513200748969

      • 进入关卡3后,M[0x804c3cc]=3:

        image-20210513200806250

      • 进入关卡4后,M[0x804c3cc]=4:

        image-20210513201243594

      • 进入关卡5后,M[0x804c3cc]=5:

        image-20210513201259639

      • 进入关卡6后,M[0x804c3cc]=6:

        image-20210513201315080

    • 显然只有在第6关之后,M[0x804c3cc]=6,才可能进入secret_phase。

    • 第二部分:

         0x08049286 <+27>:	lea    0x2c(%esp),%eax					;eax=(0x2c+esp)
         0x0804928a <+31>:	mov    %eax,0x10(%esp)					;M[0x10+esp]=eax=(0x2c+esp)
         0x0804928e <+35>:	lea    0x28(%esp),%eax					;eax=(0x28+esp)
         0x08049292 <+39>:	mov    %eax,0xc(%esp)					;M[0xc+esp]=eax=(0x28+esp)
         0x08049296 <+43>:	lea    0x24(%esp),%eax					;eax=(0x24+esp)
         0x0804929a <+47>:	mov    %eax,0x8(%esp)					;M[0x8+esp]=eax=(0x24+esp)
         0x0804929e <+51>:	movl   $0x804a3d1,0x4(%esp)				;M[0x4+esp]=eax=0x804a3d1
         0x080492a6 <+59>:	movl   $0x804c4d0,(%esp)				;M[esp]=0x804c4d0--从该位置读入
         0x080492ad <+66>:	call   0x8048870 <__isoc99_sscanf@plt>	
         0x080492b2 <+71>:	cmp    $0x3,%eax						;输入参数个数应等于3
         0x080492b5 <+74>:	jne    0x80492ec <phase_defused+129>	;否则不会到达scret_phase
      
      • 调用<__isoc99_sscanf@plt>从0x804c4d0读入参数。根据函数的参数可知,共读入3个参数分别保存在(0x24+esp)(0x28+esp)(0x2c+esp)。输入参数的格式为地址0x804a3d1所示:

        image-20210513203029685

    • 第三部分:

         0x080492b7 <+76>:	movl   $0x804a3da,0x4(%esp)				;M[0x4+esp]=0x804a3da
         0x080492bf <+84>:	lea    0x2c(%esp),%eax					;eax=(0x2c+esp)
         0x080492c3 <+88>:	mov    %eax,(%esp)						;M[esp]=eax=(0x2c+esp)
         0x080492c6 <+91>:	call   0x8048fd4 <strings_not_equal>	
         						;0x804a3da存储的字符串和(0x2c+esp)中的字符串的字符串进行比较。若相等则eax=0。
         						;0x804a3da存储的字符串为 "DrEvil" 
         						;(0x2c+esp)存储的为<__isoc99_sscanf@plt>读入的第三个参数%s对应的字符串。
         0x080492cb <+96>:	test   %eax,%eax
         0x080492cd <+98>:	jne    0x80492ec <phase_defused+129>	;如果eax!=0,则会跳过secret_phase
         0x080492cf <+100>:	movl   $0x804a2a0,(%esp)				;M[esp]=0x804a2a0
         0x080492d6 <+107>:	call   0x8048800 <puts@plt>				
         ;查看0x804a2a0存储的值,得输出的字符串:"Curses, you've found the secret phase!"”
         0x080492db <+112>:	movl   $0x804a2c8,(%esp)				;;M[esp]=0x804a2c8
         0x080492e2 <+119>:	call   0x8048800 <puts@plt>
         ;查看0x804a2c8存储的值,得输出的字符串:"But finding it and solving it are quite different..."
         0x080492e7 <+124>:	call   0x8048ec2 <secret_phase>			;secret_phase:顾名思义,秘密关卡!
         0x080492ec <+129>:	movl   $0x804a300,(%esp)
         0x080492f3 <+136>:	call   0x8048800 <puts@plt>
         0x080492f8 <+141>:	mov    0x7c(%esp),%eax					;取出原%gs:0x14的值
         0x080492fc <+145>:	xor    %gs:0x14,%eax					;与现值异或
         0x08049303 <+152>:	je     0x804930a <phase_defused+159>	
         						;异或结果为0,栈保护者canary未被非法篡改,安全退出程序
         0x08049305 <+154>:	call   0x80487d0 <__stack_chk_fail@plt> 
         						;否则ZF=0,调用__stack_chk_fail,退出程序
         0x0804930a <+159>:	add    $0x8c,%esp
         0x08049310 <+165>:	ret    
      End of assembler dump.
      
      • 此时需要找到地址0x804c4d0存储的是什么,哪来的%d %d %s

      • 故在进入每个关卡的函数的位置设断点,观察地址0x804c4d0存储值的变化,进行调试:

        • 进入前三关,皆无变化:

          image-20210513204703675

        • 进入第4关,变化来了:

          image-20210513204722687

        • 0x804c4d0存储的是第4关的输入。

      • 我们在第4关的输入后增加字符串"DrEvil" ,即可打开秘密关卡secret_phase:

        image-20210513205050635

        image-20210513205105128

    secret_phase
    • 汇编代码如下:

      Dump of assembler code for function secret_phase:
         0x08048ec2 <+0>:		push   %ebx							;ebx入栈
         0x08048ec3 <+1>:		sub    $0x18,%esp					;esp=esp-0x18,开辟栈帧空间
         0x08048ec6 <+4>:		call   0x804910d <read_line>		;读取一行,保存在eax中
         0x08048ecb <+9>:		movl   $0xa,0x8(%esp)				;M[0x8+esp]=0xa=10	--strtol的第3个参数
         0x08048ed3 <+17>:	movl   $0x0,0x4(%esp)				;M[0x4+esp]=0		--strtol的第2个参数
         0x08048edb <+25>:	mov    %eax,(%esp)					;M[esp]=eax			--strtol的第1个参数
         0x08048ede <+28>:	call   0x80488e0 <strtol@plt>		
         						;strtol将eax存储的字符串转化为10进制整数(使结果没有""),结束符为空字符。
         						;返回值10进制整数保存在eax
         0x08048ee3 <+33>:	mov    %eax,%ebx					;ebx=eax
         0x08048ee5 <+35>:	lea    -0x1(%eax),%eax				;eax=eax-1;
         0x08048ee8 <+38>:	cmp    $0x3e8,%eax					;比较eax和0x3e8
         0x08048eed <+43>:	jbe    0x8048ef4 <secret_phase+50>	;若eax<=0x3e8,则不爆炸
         0x08048eef <+45>:	call   0x80490e6 <explode_bomb>
         0x08048ef4 <+50>:	mov    %ebx,0x4(%esp)				;M[0x4+esp]=ebx
         0x08048ef8 <+54>:	movl   $0x804c088,(%esp)			;M[esp]=0x804c088
         0x08048eff <+61>:	call   0x8048e71 <fun7>				
         						;调用函数fun7(0x804c088,ebx),返回值保存在eax
         0x08048f04 <+66>:	cmp    $0x4,%eax					
         0x08048f07 <+69>:	je     0x8048f0e <secret_phase+76>	;若eax=4,则不爆炸且过关
         0x08048f09 <+71>:	call   0x80490e6 <explode_bomb>
         0x08048f0e <+76>:	movl   $0x804a240,(%esp)			;M[esp]=0x804a240
         0x08048f15 <+83>:	call   0x8048800 <puts@plt>			
         						;输出:"Wow! You've defused the secret stage!"
         0x08048f1a <+88>:	call   0x804926b <phase_defused>	;退出秘密关卡
         0x08048f1f <+93>:	add    $0x18,%esp
         0x08048f22 <+96>:	pop    %ebx
         0x08048f23 <+97>:	ret    
      End of assembler dump.
      
      • 其中,fun7的汇编代码如下:

        Dump of assembler code for function fun7:
           0x08048e71 <+0>:		push   %ebx					;ebx入栈
           0x08048e72 <+1>:		sub    $0x18,%esp			;esp=esp-0x18,开辟栈帧空间
           0x08048e75 <+4>:		mov    0x20(%esp),%edx		;edx=M[0x20+esp]	--第一个参数
           0x08048e79 <+8>:		mov    0x24(%esp),%ecx		;ecx=M[0x24+esp]	--第二个参数
           0x08048e7d <+12>:	test   %edx,%edx			
           0x08048e7f <+14>:	je     0x8048eb8 <fun7+71>	;如果edx=0 跳转->return 0xffffffff
           0x08048e81 <+16>:	mov    (%edx),%ebx			;ebx=M[edx]
           0x08048e83 <+18>:	cmp    %ecx,%ebx			
           0x08048e85 <+20>:	jle    0x8048e9a <fun7+41>	;如果ebx<=ecx(有符号数),跳转至<fun7+41>
           0x08048e87 <+22>:	mov    %ecx,0x4(%esp)		;M[0x4+esp]=ecx
           0x08048e8b <+26>:	mov    0x4(%edx),%eax		;eax=M[0x4+edx]
           0x08048e8e <+29>:	mov    %eax,(%esp)			;M[esp]=eax=M[0x4+edx]
           0x08048e91 <+32>:	call   0x8048e71 <fun7>		;递归调用函数fun7(M[0x4+edx],ecx)
           0x08048e96 <+37>:	add    %eax,%eax			;eax=2*fun7(M[0x4+edx],ecx)
           0x08048e98 <+39>:	jmp    0x8048ebd <fun7+76>	;跳转-> return 2*fun7(M[0x4+edx],ecx);
           0x08048e9a <+41>:	mov    $0x0,%eax			;eax=0
           0x08048e9f <+46>:	cmp    %ecx,%ebx			
           0x08048ea1 <+48>:	je     0x8048ebd <fun7+76>	;如果ebx=ecx,跳转-> return 0;	
           0x08048ea3 <+50>:	mov    %ecx,0x4(%esp)		;M[0x4+esp]=ecx
           0x08048ea7 <+54>:	mov    0x8(%edx),%eax		;eax=M[0x8+edx]
           0x08048eaa <+57>:	mov    %eax,(%esp)			;M[esp]=eax
           0x08048ead <+60>:	call   0x8048e71 <fun7>		;递归调用函数fun7(M[0x8+edx],ecx)
           0x08048eb2 <+65>:	lea    0x1(%eax,%eax,1),%eax;return eax=2*fun7(M[0x8+edx],ecx)+1
           0x08048eb6 <+69>:	jmp    0x8048ebd <fun7+76>
           0x08048eb8 <+71>:	mov    $0xffffffff,%eax
           0x08048ebd <+76>:	add    $0x18,%esp			
           0x08048ec0 <+79>:	pop    %ebx
           0x08048ec1 <+80>:	ret    
        End of assembler dump.
        
        • 显然fun7是个含2个参数的递归函数,C++代码可如下表示:

          #include<iostream>
          using namespace std;
          int fun7(int *p,int m)
          {
          	if(p==0)
          		return -1;
          	else
          	{
          		if(*p<m)
          			return 2*fun7(*(p+2),m)+1;
          		else if(*p==m)
          			return 0
          		else
          			return 2*fun7(*(p+1),m);
          	} 
          }
          
          • 这个递归的返回值为0时结束递归,对于每个返回值,根据与m的大小关系,有两个递归分支,分别是 2 ∗ f u n 7 ( ∗ ( p + 2 ) , m ) + 1 2*fun7(*(p+2),m)+1 2fun7((p+2),m)+1 2 ∗ f u n 7 ( ∗ ( p + 1 ) , m ) 2*fun7(*(p+1),m) 2fun7((p+1),m)

          • fun7最终的返回值为4,故0->1->2->4为递归返回的路径,则递归调用时地址的增量为0x4,0x4,0x8。

            image-20210514111858675

          • m=7,使得在最后一层递归返回0。整个过程如下:

            1. *p=24>m,return 2*fun7(*(p+1),m)
            2. *p=8>m,return 2*fun7(*(p+1),m)
            3. *p=6<m,return 2*fun7(*(p+1),m)+1
            4. *p=7=m,return 0
    • 输入7,fun7得到返回值4,通关:

      image-20210514112742119

实验结果

image-20210514112928319

image-20210514112906727

实验心得

  1. 这次实验的各个关卡对汇编语言实现的功能各有侧重:

    phase_1:字符串在内存地址中的存放、访问,字符串长度的获取与字符串的比较。

    phase_2:利用循环得到存储等比数列的数组。

    phase_3:实现switch语句,需要查看跳转表。

    phase_4:递归函数的实现。

    phase_5:数组及其索引。

    phase_6:链表及其重组。

    secret_phase:通过含指针参数的递归函数实现地址跳转。

    通过学习、理解汇编代码在该实验中实现的各种功能,我对汇编代码的各种指令,如比较、跳转、调用指令等有了更深的理解。当遇到难以直接明白其功能的地方,我会尝试把汇编语言转换成自己编写的C++代码帮助理解,从而对汇编的功能实现有了更全面的认识。

  2. 在实验过程中难免需要对汇编代码进行调试,查看某个寄存器或在某个地址内存的值及其变化。所以gdb工具的使用是不可或缺的。正是通过完成这次实验,我对gdb工具的使用愈发熟练。

  3. 在进行函数调用时,栈帧的变化格外重要。通过这次实验对汇编代码的调试,思考在何处保存函数的参数、何处保存临时变量等,我对栈帧的结构有了更深刻的认识。

  4. 进行实验,细心和耐心也是很重要的品质。有时候会因为不够细心而耽误时间,如phase_4中我看反了两个输入参数的位置,但好在能够及时发现并改正。在遭遇比较复杂的结构,如phase_6中重构链表的双重循环,则需要耐心分析汇编语句。有了细心和耐心的加持,才能更好地闯关一个个关卡,收获知识,提升技能。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 深入理解计算机系统(CSAPP)是由Randal E. Bryant和David R. O'Hallaron编写的经典计算机科学教材。该教材通过涵盖计算机体系结构、机器级别表示和程序执行的概念,帮助学生深入理解计算机系统的底层工作原理和运行机制。 深入理解计算机系统的练习题对于学生巩固并应用所学知识非常有帮助。这些练习题涵盖了计算机硬件、操作系统和编译器等多个领域,旨在培养学生解决实际问题和设计高性能软件的能力。 对于深入理解计算机系统的练习题,关键是通过实践进行学习。在解答练习题时,应根据课本提供的相关知识和工具,仔细阅读问题描述,并根据实际需求设计相应的解决方案。 在解答练习题时,需要多角度思考问题。首先,应准确理解题目要求,并设计合适的算法或代码来解决问题。其次,应考虑代码的正确性和效率,以及对系统性能的影响。此外,还要注意处理一些特殊情况和异常情况,避免出现潜在的错误或安全漏洞。 解答练习题的过程中,应注重查阅相关资料和参考优秀的解答。这可以帮助我们扩展对问题的理解,并学习他人的思路和解决方法。同时,还可以通过与同学和老师的讨论,共同探讨问题和学习经验。 总之,通过解答深入理解计算机系统的练习题,可以帮助学生巩固所学知识,同时培养解决实际问题和设计高性能软件的能力。这是一个学以致用的过程,可以加深对计算机系统运行机制和底层工作原理的理解。 ### 回答2: 理解计算机系统(CSAPP)是一本经典的计算机科学教材,通过深入研究计算机系统的各个方面,包括硬件、操作系统和编程环境,对于提高计算机科学专业知识与能力具有很大帮助。 练习题是CSAPP中的重要部分,通过练习题的完成,可以加深对计算机系统的理解,并将理论知识转化为实践能力。练习题的数量、难度逐渐递增,从简单的概念与基础问题到复杂的系统设计与实现。 在解答练习题时,首先需要对题目进行仔细阅读和理解,明确题目的要求和限制条件。然后,可以利用课堂讲解、教材内容、网络资源等进行查阅和学习相应的知识。同时,还可以参考课后习题解答等资料,了解一些常见的解题方法和思路。 在解答练习题时,可以利用计算机系统的工具和环境进行实际测试和验证。例如,可以使用调试器、编译器和模拟器等工具对程序或系统进行分析和测试。这样可以更加深入地理解问题的本质,并找到恰当的解决方法。 另外,解答练习题时还可以与同学、教师和网上社区进行交流和讨论。这样可以互相学习和交流解题思路,共同解决问题。还可以了解不同的解题方法和技巧,提高解题效率和质量。 练习题的解答过程可能会遇到一些困难和挑战,例如理论知识的不足、复杂问题的分析与解决。但是通过不断地思考和实践,相信可以逐渐提高解题能力,更好地理解计算机系统。 总之,深入理解计算机系统(CSAPP)练习题是提高计算机科学专业知识和能力的重要途径。通过仔细阅读和理解题目,查阅相关知识,利用计算机系统工具和环境进行实践,与他人进行交流和讨论,相信可以更好地理解计算机系统的各个方面,并将知识转化为实际能力。 ### 回答3: 《深入理解计算机系统(CSAPP)》是计算机科学领域的经典教材之一,对于深入理解计算机系统的原理、设计和实现起到了极大的帮助。在阅读这本书的过程中,书中的习题也是非常重要的一部分,通过做习题,我们可以更好地理解书中所讲的概念和思想。 CSAPP的习题涵盖了课本中各个章节的内容,从基础的数据表示和处理、程序的机器级表示、优化技术、程序的并发与并行等方面进行了深入探讨。通过解答习题,我们可以对这些知识进行实践应用,巩固自己的理解,并培养自己的解决问题的思维方式。 在解答习题时,我们需要充分理解题目要求和条件,并从知识的角度进行分析。有些习题可能需要进行一些编程实践,我们可以通过编程实现来验证和测试我们的思路和解决方案。在解答问题时,我们还可以查阅一些参考资料和网上资源,充分利用互联网的学习资源。 在解答习题时,我们需要保持积极的思维和态度。可能会遇到一些困难和挑战,但是通过坚持和努力,我们可以克服这些困难,提高我们的解决问题的能力。同时,我们还可以通过与同学或者其他人进行讨论,相互分享解题经验和思路,从而更好地理解问题。 综上所述,通过深入理解计算机系统(CSAPP)的习题,我们可以进一步巩固和深化对计算机系统的理解。掌握这些知识,不仅可以提高我们在计算机领域的能力,还可以为我们未来的学习和职业发展奠定重要的基础。因此,认真对待CSAPP的习题,是我们在学习计算机系统知识中不可或缺的一部分。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yuhan_2001

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值