CSAPP Lab2:Bomb Lab

前言

该实验是《深入理解计算机系统》(英文缩写CSAPP)课程附带实验——Lab2:Bomb Lab,对应书中第三章内容(程序的机器级表示),主函数有六个炸弹函数,先是获得输入input = read_line(); 接着运行 phase_1(input); 判断是否是正确答案,如果是正确答案,调用phase_defused(); 来获取接触炸弹的方式。

一.实验总说明

A “binary bomb” is a program provided to students as an object code file.When run, it prompts the user to type in 6 different strings. If any of these is incorrect, the bomb “explodes,” printing an error message and logging the event on a grading server. Students must “defuse” their own unique bomb by disassembling and reverse engineering the program to determine what the 6 strings should be. The lab teaches students to understand assembly language, and also forces them to learn how to use a debugger. It’s also great fun. A legendary lab among the CMU undergrads.
Here’s a Linux/x86-64 binary bomb that you can try out for yourself. The feature that notifies the grading server has been disabled, so feel free to explode this bomb with impunity. If you’re an instructor with a CS:APP account, then you can download the solution.
“二进制炸弹”是作为目标代码文件提供给学生的程序。当运行时,它提示用户输入6个不同的字符串。 如果其中任何一个是不正确的,炸弹“爆炸”,打印一个错误消息,并在分级服务器上记录事件。学生们必须通过拆卸和反向工程程序来“拆除”他们自己独特的炸弹,以确定6个字符串应该是什么。该实验室教学生理解汇编语言,并迫使他们学习如何使用调试器。这也很有趣。卡内基梅隆大学的传奇实验室。
这里有一个Linux/x86-64二进制炸弹,您可以自己尝试一下。 通知分级服务器的功能已经被禁用,所以可以自由引爆这个炸弹而不受惩罚。如果你是一个拥有CS:APP账户的教师,那么你可以下载解决方案。

二.实验过程记录

准备

  • 本实验需要用到gdb调试器,在Lab1配置实验环境时已经安装过gdb调试器,命令:
> sudo apt-get install gdb
  • gdb调试器的一些指令

r                   运行程序
b <*0x某某某>             在某个地址设置断点,具体哪里,可以看反汇编的代码,可以根据那个直接复制粘贴设断点的
d                  删除所有断点
d <断点号>               删除指定断点
info b                   查看所有断点信息
continue               断点处继续执行
display < < <$寄存器 > > >         跟踪寄存器,碰到断点停下时会显示出所有跟踪的寄存器的当前值,非常好用的一个命令,注意的是gdb中表示寄存器的话前面用的不是百分符号%,而是美元符号$
x/参数 <地址>             访问地址的内存,其实就是间接访问,也是很好用的指令,关于参数,s是输出为字符串,d为输出为十进制,x为输出为十六进制,b、w、l、q控制输出字节,默认是w,四字节,s字符串不受这个控制除外。
info r                   查看所有寄存器的值   
print (可加强制转换符号)<数字>   跟C语言的基本性质一样的,理解即可

  • 提前生成Bomb.c的反汇编文件,是后面需要用到的,只需生成一次 在终端打开bomb文件夹,输入
 > objdump -d bomb > bomb.asm

就能看到 在bomb文件夹多出来了一个这样子的文件
在这里插入图片描述

> objdump -d mstore 

可查看汇编代码

Phase 1 字符串比较

首先通过gdb载入bomb,在终端输入 gdb bomb (如果不进入gdb调试模式,可直接用命令./bomb 运行可执行文件(在最后知道所有拆弹密码的时候可以用))
在这里插入图片描述
此时输入r,运行bomb,然后输入任意字符串,看是否通过,效果如下:
在这里插入图片描述
很明显,炸弹炸了

下面通过反汇编文件bomb.asm进行分析
首先来到phase_1处

对汇编代码进行分析:

0000000000400ee0 <phase_1>:
  400ee0:	48 83 ec 08          	sub    $0x8,%rsp     //把栈指针减少8,给局部变量提供空间

  400ee4:	be 00 24 40 00       	mov    $0x402400,%esi	 //向寄存器%esi存放0x402400(可以强制类型转换为一个字符串)
  400ee9:	e8 4a 04 00 00       	callq  401338 <strings_not_equal>  /*调用函数 strings_not_equal,判断输入的字符串和程序
                                                                    内置的字符串(即%esi中存放的数据)是否相同,相同则返回0 */
  400eee:	85 c0                	test   %eax,%eax	 /*test指令同逻辑与and运算,但只设置条件码寄存器,不改变目的寄存器的值,
                                                    test   %eax,%eax用于测试寄存器%eax是否为空,由于寄存器%rax一般存放函数
                                                    的返回值,此处应该存放的是函数 strings_not_equal的值,而%eax是%rax的低
                                                    32位表示,所以不难分析出,当%eax值为0时,test的两个操作数相同且都为0,条
                                                    件码ZF置位为1,即可满足下一行代码的跳转指令*/
  400ef0:	74 05                	je     400ef7 <phase_1+0x17>  //test和je的组合用法,当ZF置位时,je跳转,跳过400ef2,直接到400ef7处
  400ef2:	e8 43 05 00 00       	callq  40143a <explode_bomb>  //调用函数 explode_bomb,发生爆炸
 
  400ef7:	48 83 c4 08          	add    $0x8,%rsp	//栈指针加8,函数回收栈指针
  400efb:	c3                   	retq           //返回 

因此,0x402400中存放的字符串即拆弹密码

两种解码方式:

  1. 直接通过gdb调试查看(print (char*)0x402400)
    在这里插入图片描述
  2. 因为此时已经经过了指令400ee4: mov $0x402400,%esi,在该指令的下一行设置断点,即可查看寄存器中存放的数据
    先输入b *0x400ee9 设置断点,输入r运行程序,然后随便输入一串字符串abcde,当程序运行到断点处自动停止 ,然后通过指令 x/s $esi 看寄存器中存放数据,s即为所需字符串
    在这里插入图片描述
    输入d,删除断点,验证密码是否正确:
    在这里插入图片描述

Phase 1已拆除

Phase1拆弹密码:Border relations with Canada have never been better.

Phase 2 循环

对前几行代码进行分析

0000000000400efc <phase_2>:
  400efc:	55                   	push   %rbp    //把数据压入栈
  400efd:	53                   	push   %rbx	 //把数据压入栈
  400efe:	48 83 ec 28          	sub    $0x28,%rsp	//把栈指针减少40,提供局部变量空间
  //申请空间
  400f02:	48 89 e6             	mov    %rsp,%rsi	//把栈指针状态存入%rsi中
  400f05:	e8 52 05 00 00       	callq  40145c <read_six_numbers>  // 调用函数read_six_numbers,读取6个数字,此时(%rsp)已被赋值

调用函数read_six_numbers,读取6个数字,先尝试输入6个数字,发现:
在这里插入图片描述
根据函数read_six_numbers的地址40145c找到相应代码块进行分析

000000000040145c <read_six_numbers>:
  40145c:	48 83 ec 18          	sub    $0x18,%rsp	//栈指针减少24(6个int型数据)
  401460:	48 89 f2             	mov    %rsi,%rdx
  401463:	48 8d 4e 04          	lea    0x4(%rsi),%rcx
  401467:	48 8d 46 14          	lea    0x14(%rsi),%rax
  40146b:	48 89 44 24 08       	mov    %rax,0x8(%rsp)
  401470:	48 8d 46 10          	lea    0x10(%rsi),%rax
  401474:	48 89 04 24          	mov    %rax,(%rsp)
  401478:	4c 8d 4e 0c          	lea    0xc(%rsi),%r9
  40147c:	4c 8d 46 08          	lea    0x8(%rsi),%r8 //401460~40147c为把6个int型数据的地址存放在各个寄存器中
  401480:	be c3 25 40 00       	mov    $0x4025c3,%esi	 //类似Phase1向寄存器中转入数据(对应字符串)
  401485:	b8 00 00 00 00       	mov    $0x0,%eax
  40148a:	e8 61 f7 ff ff       	callq  400bf0 <__isoc99_sscanf@plt>  //调用scanf函数
  40148f:	83 f8 05             	cmp    $0x5,%eax
  401492:	7f 05                	jg     401499 <read_six_numbers+0x3d>
  401494:	e8 a1 ff ff ff       	callq  40143a <explode_bomb>
  401499:	48 83 c4 18          	add    $0x18,%rsp
  40149d:	c3                   	retq   

发现命401480: mov $0x4025c3,%esi ,类似Phase 1向寄存器中转入数据(对应字符串),于是输入print (char*)0x4025c3进行测试,发现
在这里插入图片描述
验证了输入6个整型数据的猜想

返回看Phase 2剩下的汇编代码:

0000000000400efc <phase_2>:
  400efc:	55                   	push   %rbp    //把数据压入栈
  400efd:	53                   	push   %rbx	 //把数据压入栈
  400efe:	48 83 ec 28          	sub    $0x28,%rsp	//把栈指针减少40,提供局部变量空间
  //申请空间
  400f02:	48 89 e6             	mov    %rsp,%rsi	//把栈指针状态存入%rsi中
  400f05:	e8 52 05 00 00       	callq  40145c <read_six_numbers>  // 调用函数read_six_numbers,读取6个数字,此时(%rsp)已被赋值
  400f0a:	83 3c 24 01          	cmpl   $0x1,(%rsp)  //(%rsp)的值即输入的第一个参数的值,将(%rsp)的值与1比较
  400f0e:	74 20                	je     400f30 <phase_2+0x34> //若相等则跳转至400f30
  400f10:	e8 25 05 00 00       	callq  40143a <explode_bomb>  //否则爆炸
  400f15:	eb 19                	jmp    400f30 <phase_2+0x34>  //jmp指令无条件跳转,直接跳转至400f30
  400f17:	8b 43 fc             	mov    -0x4(%rbx),%eax
  400f1a:	01 c0                	add    %eax,%eax      //%eax的值*2
  400f1c:	39 03                	cmp    %eax,(%rbx)    //比较%eax的值和此时%rbx对应的内存的值
  400f1e:	74 05                	je     400f25 <phase_2+0x29>    //若相等则跳转至400f25
  400f20:	e8 15 05 00 00       	callq  40143a <explode_bomb>     //否则爆炸
  400f25:	48 83 c3 04          	add    $0x4,%rbx        //%rbx的值(地址)加4
  400f29:	48 39 eb             	cmp    %rbp,%rbx        //比较两个寄存器的值,判断是否比较完6个数
  400f2c:	75 e9                	jne    400f17 <phase_2+0x1b>   //若不相等,则进行跳转至400f17,即循环没结束
  400f2e:	eb 0c                	jmp    400f3c <phase_2+0x40>   //否则直接跳转至400f3c,循环结束标志
  400f30:	48 8d 5c 24 04       	lea    0x4(%rsp),%rbx       //将%rsp+4代表的地址移入%rbx,即第2个输入数的地址
  400f35:	48 8d 6c 24 18       	lea    0x18(%rsp),%rbp      //将%rsp+24代表的地址移入%rbp,即第6个输入数的后一块地址
  400f3a:	eb db                	jmp    400f17 <phase_2+0x1b>    //直接跳转至400f17
  //释放空间
  400f3c:	48 83 c4 28          	add    $0x28,%rsp
  400f40:	5b                   	pop    %rbx
  400f41:	5d                   	pop    %rbp
  400f42:	c3                   	retq 

发现是一个循环操作:
首先,将 (%rsp) 的值与立即数$0x1进行比较,由此可知,第一个输入数为1,后跳转至400f30,用lea指令分别加载%rsp+4和%rsp+24对应的地址到%rbx%rbp,由int型数据所占字节大小,可知%rbx和%rbp分别存放第2个输入数的地址和第6个输入数的后一块的地址
然后跳转至400f17,此时(%rbx-4)对应的值即(%rsp)对应的值,将其存放值%eax中,并将该值*2后与(%rbx)对应的值(即第二个输入值)进行比较,即后一个数是前一个数的2倍,由此可知第二个输入值为2,后跳转至400f25,得到%rbx=%rbx+4,与%rbp进行比较(即当下%rbx对应的值(地址)是否为%rbp对应的值(地址)),若不相等则又跳转至400f17重复操作,若相等,则跳转至400f3c,结束循环,可知这是一个循环操作,看是否比较完6个数。

由上述可知,这6个数分别为1,2,4,8,16,32

上述循环中各寄存器对应的值为:

%rbx%rbp%eax
%rsp+4%rsp+24(%rsp)*2=2
%rsp+8(%rsp+4)*2=4
%rsp+12(%rsp+8)*2=8
%rsp+16(%rsp+12)*2=16
%rsp+20(%rsp+16)*2=32
%rsp+24

循环部分对应C代码:

int i;
int a[6];
a[0] = 1;
for(i=1;i<6;i++)
{
  a[i] = a[i-1]*2;
}

终端验证:
在这里插入图片描述
Phase 2已拆除

Phase 2拆弹密码1 2 4 8 16 32

这一关个人觉得困难的部分在于对寄存器及内存地址的理解,例如寄存器%rsp的值为一个地址(栈指针),而(%rsp)的值则为该地址所储存的值,为间接寻址的过程;又如寄存器%eax的值为一个数据值。
即一个寄存器的值可以是一个地址值(如本关中的%rsp,rbx,%rbp的值),也可以是一个数据值(如本关中的%eax的值),具体要看后续操作该寄存器时是否加( )。
理解了这个问题,后续只剩下循环操作,以C语言的角度入手,很快就迎刃而解了。

Phase 3 条件/分支

对汇编代码进行分析:


0000000000400f43 <phase_3>:
  400f43:	48 83 ec 18          	sub    $0x18,%rsp     //给局部变量腾出空间
  400f47:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx  //加载地址,将0xc(%rsp)设为num2
  400f4c:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx  //加载地址,将0x8(%rsp)设为num1

  400f51:	be cf 25 40 00       	mov    $0x4025cf,%esi  //因为后续调用了函数scanf,此处需对输入格式进行测试
                                                       //经测试,0x4025cf对应字符串"%d %d"
                                                       //因此可以猜测开头%rcx存放的为输入的第一个数据(设为num1)的地址,%rdx存放的为输入的第二个数据(设为num2)的地址
  400f56:	b8 00 00 00 00       	mov    $0x0,%eax    //初始化%eax
  400f5b:	e8 90 fc ff ff       	callq  400bf0 <__isoc99_sscanf@plt>  //调用scanf函数,此时%eax放scanf函数的返回值(输入数据的个数)
  400f60:	83 f8 01             	cmp    $0x1,%eax          
  400f63:	7f 05                	jg     400f6a <phase_3+0x27>  //jg:有符号大于则跳转,说明scanf输入数据的个数必须大于1
  400f65:	e8 d0 04 00 00       	callq  40143a <explode_bomb>  //否则爆炸
  400f6a:	83 7c 24 08 07       	cmpl   $0x7,0x8(%rsp)
  400f6f:	77 3c                	ja     400fad <phase_3+0x6a>  //ja:无符号大于则跳转,至爆炸,说明num1为无符号数,大于0且需要小于等于7,所以num1=[0,7]
  400f71:	8b 44 24 08          	mov    0x8(%rsp),%eax           //将num1储存到%eax中
  400f75:	ff 24 c5 70 24 40 00 	jmpq   *0x402470(,%rax,8)      //间接跳转,(此处%rax不完全等于%eax,由num1=1(%rax=1)时的跳转地址可推论)
                                                               //0x402470+%rax*8的计算结果作为地址,跳转到该地址继续执行
                                                               //根据该指令和后续的指令格式,很容易判断此处是switch语句的跳转表,跳转表的首地址为0x402470
                                                               //以 *0x402470 处的值为基地址,再加上8 * %rax 进行跳转,不同的 %rax 跳转到不同的位置。
  400f7c:	b8 cf 00 00 00       	mov    $0xcf,%eax                 //case 0  %eax=0xcf=207
  400f81:	eb 3b                	jmp    400fbe <phase_3+0x7b>
  400f83:	b8 c3 02 00 00       	mov    $0x2c3,%eax                //case 2  %eax=0x2c3=707
  400f88:	eb 34                	jmp    400fbe <phase_3+0x7b>
  400f8a:	b8 00 01 00 00       	mov    $0x100,%eax                //case 3  %eax=0x100=256
  400f8f:	eb 2d                	jmp    400fbe <phase_3+0x7b>
  400f91:	b8 85 01 00 00       	mov    $0x185,%eax                //case 4  %eax=0x185=389
  400f96:	eb 26                	jmp    400fbe <phase_3+0x7b>
  400f98:	b8 ce 00 00 00       	mov    $0xce,%eax                 //case 5  %eax=0xce=206
  400f9d:	eb 1f                	jmp    400fbe <phase_3+0x7b>
  400f9f:	b8 aa 02 00 00       	mov    $0x2aa,%eax                //case 6  %eax=0x2aa=682
  400fa4:	eb 18                	jmp    400fbe <phase_3+0x7b>
  400fa6:	b8 47 01 00 00       	mov    $0x147,%eax                //case 7  %eax=0x147=327
  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
  400fb7:	eb 05                	jmp    400fbe <phase_3+0x7b>
  400fb9:	b8 37 01 00 00       	mov    $0x137,%eax                //case 1  %eax=0x137=311
  400fbe:	3b 44 24 0c          	cmp    0xc(%rsp),%eax   //比较%eax的值和num2
  400fc2:	74 05                	je     400fc9 <phase_3+0x86>  //je:若相等则跳转至结束
  400fc4:	e8 71 04 00 00       	callq  40143a <explode_bomb>  //否则爆炸

  400fc9:	48 83 c4 18          	add    $0x18,%rsp       //释放空间
  400fcd:	c3                   	retq   

可以通过x命令(因为上面判断num1小于等于7,因此可知跳转表中应该存储有8个地址。x表明以十六进制的形式显示地址,g表示每8个字节的内存,因为这是x64平台,所以地址占8个字节。

查看跳转表中储存的地址:
在这里插入图片描述
仔细观察这些地址,可以发现都是函数phase_3范围内的地址。

当num1等于0时,跳转到0x0000000000400f7c处执行。如果num2不等于0xcf,则触发炸弹。

当num1等于1时,跳转到0x0000000000400fb9处执行。如果num2不等于0x137,则触发炸弹。

当num1等于2时,跳转到0x0000000000400f83处执行。如果num2不等于0x2c3,则触发炸弹。

当num1等于3时,跳转到0x0000000000400f8a处执行。如果num2不等于0x100,则触发炸弹。

当num1等于4时,跳转到0x0000000000400f91处执行。如果num2不等于0x185,则触发炸弹。

当num1等于5时,跳转到0x0000000000400f98处执行。如果num2不等于0xce,则触发炸弹。

当num1等于6时,跳转到0x0000000000400f9f处执行。如果num2不等于0x2aa,则触发炸弹。

当num1等于7时,跳转到0x0000000000400fa6处执行。如果num2不等于0x147,则触发炸弹。

所以拆弹密码有8种:0 207;1 311;2 707;3 256;4 389;5 206;6 682;7 327
输入其中任意几种或者全部输入都可以。

对应C代码:

void phase_3(const char *input)
{
  //  0x8(%rsp)  0xc(%rsp)
  int num1, num2;
  //     %rdi     %rsi   %rdx   %rcx 
  int result = sscnaf(input, "%d %d", &num1, &num2);  //返回输入数据的个数
  if (result <= 1) {
    explode_bomb(); 
  }
 
  switch (num1) {
    case 0: // 0 207
      if (num2 != 0xcf) {
        explode_bomb();
      }
      break;
    case 1: // 1 311
      if (num2 != 0x137) {
        explode_bomb();
      }
      break;
    case 2: // 2 707
      if (num2 != 0x2c3) {
        explode_bomb(); 
      }
      break;
    case 3: // 3 256
      if (num2 != 0x100) {
        explode_bomb(); 
      }
      break;
    case 4: // 4 389
      if (num2 != 0x185) {
        explode_bomb();
      }
      break;
    case 5: // 5 206
      if (num2 != 0xce) {
        explode_bomb(); 
      }
      break;
    case 6: // 6 682
      if (num2 != 0x2aa) {
        explode_bomb(); 
      }
      break;
    case 7: // 7 327
      if (num2 != 0x147) {
        explode_bomb(); 
      }
      break;
    default:
      explode_bomb();
      break;
  }
}

终端验证:
在这里插入图片描述
Phase 3已拆除

Phase 3拆弹密码0 207;1 311;2 707;3 256;4 389;5 206;6 682;7 327 (任选其一)

Phase 4 递归调用和栈

对汇编代码进行分析:

000000000040100c <phase_4>:
  40100c:	48 83 ec 18          	sub    $0x18,%rsp        //申请空间
  401010:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx    //传参,加载有效地址,将0xc(%rsp)设为num2
  401015:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx    //传参,加载有效地址,将0x8(%rsp)设为num1

  40101a:	be cf 25 40 00       	mov    $0x4025cf,%esi    //scanf函数输入格式, %d %d
  40101f:	b8 00 00 00 00       	mov    $0x0,%eax
  401024:	e8 c7 fb ff ff       	callq  400bf0 <__isoc99_sscanf@plt>   //调用scanf函数
  401029:	83 f8 02             	cmp    $0x2,%eax          
  40102c:	75 07                	jne    401035 <phase_4+0x29>      //当scanf输入数据个数不等于2时,跳转至爆炸
  40102e:	83 7c 24 08 0e       	cmpl   $0xe,0x8(%rsp)
  401033:	76 05                	jbe    40103a <phase_4+0x2e>      //jdb:无符号小于等于跳转,当num1小于等于14时,跳转至40103a,否则爆炸,所以num1的限制条件为[0,14]
  401035:	e8 00 04 00 00       	callq  40143a <explode_bomb>
  40103a:	ba 0e 00 00 00       	mov    $0xe,%edx            //%edx=14
  40103f:	be 00 00 00 00       	mov    $0x0,%esi            //%esi=0
  401044:	8b 7c 24 08          	mov    0x8(%rsp),%edi       //%edi=num1
  401048:	e8 81 ff ff ff       	callq  400fce <func4>         //调用函数fun4,三个参数%edx,%esi,%edi
  40104d:	85 c0                	test   %eax,%eax
  40104f:	75 07                	jne    401058 <phase_4+0x4c>      //如果fun4返回值%eax不等于0,则跳转至爆炸,所以需要知道当num1为何值时,%eax为0
  401051:	83 7c 24 0c 00       	cmpl   $0x0,0xc(%rsp)
  401056:	74 05                	je     40105d <phase_4+0x51>      //如果num2=0,则跳转至结束,否则爆炸,所以输入的第二个数据只能为0
  401058:	e8 dd 03 00 00       	callq  40143a <explode_bomb>

  40105d:	48 83 c4 18          	add    $0x18,%rsp     //释放空间
  401061:	c3                   	retq   

401051: cmpl 	$0x0,0xc(%rsp)
401056: je		40105d <phase_4+0x51>
40104d: test 	%eax,%eax
40104f: jne     401058 <phase_4+0x4c>

可知解码的条件为:

  • num1满足函数func4的返回值%eax为0
  • num2唯一值为0
0000000000400fce <func4>:
  400fce:	48 83 ec 08          	sub    $0x8,%rsp          //申请空间

  //"="后为初始计算值
  400fd2:	89 d0                	mov    %edx,%eax          //%eax=%edx=14
  400fd4:	29 f0                	sub    %esi,%eax          //%eax=%eax-%esi=14-0=14
  400fd6:	89 c1                	mov    %eax,%ecx          //%ecx=%eax=14
  400fd8:	c1 e9 1f             	shr    $0x1f,%ecx         //%ecx的值逻辑右移31位,即%ecx=%ecx>>31=1110>>31=14/2^31=0
  400fdb:	01 c8                	add    %ecx,%eax          //因为是逻辑右移,也就是如果%eax为正数则不变,为负数则+1;%eax=%eax+%ecx=14+0=14
  400fdd:	d1 f8                	sar    %eax               //算术右移1位,%eax=%eax/2=14/2=7
  400fdf:	8d 0c 30             	lea    (%rax,%rsi,1),%ecx   //%eax为%rax的第32位表示,%esi为%rsi的低32位表示,初始时%rax=%eax,%rsi=%esi
                                                            //加载有效地址,%ecx=%rax+%rsi=7+0=7
  400fe2:	39 f9                	cmp    %edi,%ecx            
  400fe4:	7e 0c                	jle    400ff2 <func4+0x24>    //有符号小于等于则跳转, 若%ecx(初始为7)小于等于num1,则跳转至400ff2,%eax=0,说明num1=7,为其中一个解
  400fe6:	8d 51 ff             	lea    -0x1(%rcx),%edx        //当num1小于等于%ecx时,加载有效地址,%edx=%rcx-1=7-1=6 (%ecx为%rcx的低32位表示)
  400fe9:	e8 e0 ff ff ff       	callq  400fce <func4>         //递归调用,减小%ecx的值,直至num1>%ecx,因为num1是无符号数,取值范围[0,14],所以%ecx下限为0
  400fee:	01 c0                	add    %eax,%eax                //%eax=%eax*2
  400ff0:	eb 15                	jmp    401007 <func4+0x39>      //递归出口
  400ff2:	b8 00 00 00 00       	mov    $0x0,%eax                //%eax=0
  400ff7:	39 f9                	cmp    %edi,%ecx                 //若%ecx大于等于num1,递归结束,返回phase_4
  400ff9:	7d 0c                	jge    401007 <func4+0x39>       //递归出口
  400ffb:	8d 71 01             	lea    0x1(%rcx),%esi            //%esi=%rcx+1
  400ffe:	e8 cb ff ff ff       	callq  400fce <func4>            //递归调用
  401003:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax     //%eax=%rax+1+%rax

  401007:	48 83 c4 08          	add    $0x8,%rsp          //释放空间
  40100b:	c3                   	retq   

函数func4对应C代码:

//x: %edi y:%esi z:%edx k: %ecx t:%eax
int 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 t;
  }
  else
   {
     t=0;
     if(k<x)
     {
        y=k+1;
        func4(x,y,z);
        t=2*t+1;
        return t;
     }
     else
     {
         return t;   //显然,要使返回值t(%eax)为0,其中一个答案为x=k=7
     }
   }
}

由上述分析可知其中一解为7 0

终端验证:
在这里插入图片描述
其余解因为倒推有难度,故将num1∈[0,14](无符号整数)逐一列举代入func4进行验证,得到可行解num1=0,1,3,7

终端验证:
在这里插入图片描述
Phase 4已拆除

Phase 4拆弹密码0 0;1 0;3 0;7 0(任选其一)

Phase 5 指针

对汇编代码进行分析:

0000000000401062 <phase_5>:
  401062:	53                   	push   %rbx
  401063:	48 83 ec 20          	sub    $0x20,%rsp       
  //申请空间

  401067:	48 89 fb             	mov    %rdi,%rbx         //%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)     //40106a~401073为把fs段偏移0x28的一个数据储存到%rsp+0x18处,这是为了防止缓存区溢出。(与金丝雀值有关)
                                                           //(物理地址=段地址*16+偏移)
  401078:	31 c0                	xor    %eax,%eax     //自己与自己异或,清零
  40107a:	e8 9c 02 00 00       	callq  40131b <string_length>
  40107f:	83 f8 06             	cmp    $0x6,%eax
  401082:	74 4e                	je     4010d2 <phase_5+0x70>
  401084:	e8 b1 03 00 00       	callq  40143a <explode_bomb>      //40107a~401084为比较我们输入的字符串长度是否为6,否则爆炸。
                                                                  //说明此题要求输入一个长度为6的字符串。接下来跳到0x4010d2处的代码
  401089:	eb 47                	jmp    4010d2 <phase_5+0x70>
  40108b:	0f b6 0c 03          	movzbl (%rbx,%rax,1),%ecx     //第一轮循环时,%rbx中存放着输入的字符串的地址,此时%rax=0x0,因此%ecx就存放着字符串的第一个字符,每次循环%rax+1
                                                              //设六个字符分别为ch[0],ch[1],ch[2],ch[3],ch[4],ch[5],则每轮循环到这里,%ecx=ch[%rax]
  40108f:	88 0c 24             	mov    %cl,(%rsp)         //%cl是%ecx的低八位
  401092:	48 8b 14 24          	mov    (%rsp),%rdx
  401096:	83 e2 0f             	and    $0xf,%edx          //%edx是%rdx的低32位,40108b~401096目的为只取(%ecx)的最低四位,存放到%edx中
  401099:	0f b6 92 b0 24 40 00 	movzbl 0x4024b0(%rdx),%edx     //用命令(gdb)x/s 0x4024b0查看发现0x4024b0 对应字符串:
                                                               // maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?
                                                               (%rdx)在这里起到了索引的作用,比如(%rdx)=0x1,就是将a字符传给%edx
  4010a0:	88 54 04 10          	mov    %dl,0x10(%rsp,%rax,1)   //%dl是%edx的低8位,将上一句的得到的字符传入栈中保存,(%rax)同样作为栈的索引,第一个字符就储存在(%rsp+0x10)
                                                               //退出循环时,栈上从(%rsp+0x10)开始按顺序存储着6个索引到的字符
  4010a4:	48 83 c0 01          	add    $0x1,%rax          //%rax 每次循环后+1
  4010a8:	48 83 f8 06          	cmp    $0x6,%rax          //直到=6时退出循环

  4010ac:	75 dd                	jne    40108b <phase_5+0x29>     //40108b~4010ac为一个循环,%eax在跳转前已被清空
  4010ae:	c6 44 24 16 00       	movb   $0x0,0x16(%rsp)
  4010b3:	be 5e 24 40 00       	mov    $0x40245e,%esi       //查看0x40245e位置内容为"flyers"
                                                            //接下来就是调用strings_not_equal函数,判断栈上的六个字符与这6个字符是否相等。操作与phase_1相同。
  4010b8:	48 8d 7c 24 10       	lea    0x10(%rsp),%rdi
  4010bd:	e8 76 02 00 00       	callq  401338 <strings_not_equal>
  4010c2:	85 c0                	test   %eax,%eax
  4010c4:	74 13                	je     4010d9 <phase_5+0x77>
  4010c6:	e8 6f 03 00 00       	callq  40143a <explode_bomb>
  4010cb:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)
  4010d0:	eb 07                	jmp    4010d9 <phase_5+0x77>
  4010d2:	b8 00 00 00 00       	mov    $0x0,%eax                //清空%eax
  4010d7:	eb b2                	jmp    40108b <phase_5+0x29>    //跳转至40108b
  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   

由上述分析,可知这一关通过取我们输入六个字符的ASCII码的低四位作为索引值,查找maduiersnfotvbyl里的字符组成的,最后返回的字符应该是flyers

maduiersnfotvbyl中f为第9位,l为第15位,y第14位,e第5位,r第6位,s第7位

即我们需要输入6个字符,使它们ASCII码低四位分别是:1001, 1111, 1110, 0101, 0110, 0111

查看ASCII表可找到对应字符,a的ASCII码为01100001,因此,其中一种解码可为ionuvw;ionefg;9?>567(答案不唯一)

终端验证:
在这里插入图片描述
Phase 5已拆除

Phase 5拆弹密码ionuvw;ionefg;9?>567 … (不唯一)

Phase 6 链表/指针/结构

分几部分对汇编代码进行分析:

  • 第一部分:
00000000004010f4 <phase_6>:

//第一部分:
  4010f4:	41 56                	push   %r14
  4010f6:	41 55                	push   %r13
  4010f8:	41 54                	push   %r12
  4010fa:	55                   	push   %rbp
  4010fb:	53                   	push   %rbx
  4010fc:	48 83 ec 50          	sub    $0x50,%rsp     //申请空间

  401100:	49 89 e5             	mov    %rsp,%r13      //%r13=%rsp
  401103:	48 89 e6             	mov    %rsp,%rsi      //%rsi=%rsp
                                                      //4010f4~401103为保存参数,分配栈帧 
  401106:	e8 51 03 00 00       	callq   <read_six_numbers>     //输入6个数,调用的结果是调用者的栈上按顺序存储输入的6个数
  40110b:	49 89 e6             	mov    %rsp,%r14       //%r14=%rsp
  40110e:	41 bc 00 00 00 00    	mov    $0x0,%r12d      //%r12d=0   %r12d当做数组索引,类似i=0

  401114:	4c 89 ed             	mov    %r13,%rbp      //初始 %rbp=%r13=%rsp
  401117:	41 8b 45 00          	mov    0x0(%r13),%eax    //%eax=num[i]
  40111b:	83 e8 01             	sub    $0x1,%eax
  40111e:	83 f8 05             	cmp    $0x5,%eax
  401121:	76 05                	jbe    401128 <phase_6+0x34>  //无符号数比较,说明num为无符号数,即大于等于0,40111b~401121 num[i]-1<=5,所以num[i]<=6
  401123:	e8 12 03 00 00       	callq  40143a <explode_bomb>
  401128:	41 83 c4 01          	add    $0x1,%r12d
  40112c:	41 83 fc 06          	cmp    $0x6,%r12d
  401130:	74 21                	je     401153 <phase_6+0x5f>
  401132:	44 89 e3             	mov    %r12d,%ebx           //401128~401132 退出大循环的条件:6个数字全部遍历到
  401135:	48 63 c3             	movslq %ebx,%rax
  401138:	8b 04 84             	mov    (%rsp,%rax,4),%eax
  40113b:	39 45 00             	cmp    %eax,0x0(%rbp)
  40113e:	75 05                	jne    401145 <phase_6+0x51>
  401140:	e8 f5 02 00 00       	callq  40143a <explode_bomb>
  401145:	83 c3 01             	add    $0x1,%ebx
  401148:	83 fb 05             	cmp    $0x5,%ebx
  40114b:	7e e8                	jle    401135 <phase_6+0x41>   //401145~40114b 小循环,判断数组元素是否相等
  40114d:	49 83 c5 04          	add    $0x4,%r13            
  401151:	eb c1                	jmp    401114 <phase_6+0x20>// 40114d~401151 大循环,每次将%r13加4,之后回到401114,%r13赋给了%eax

以上部分对应C代码:

r14 = 0;
r13 = 0;
r12d = 0;
while(1){     
  rbp = r13;
  if(num[r13] - 1 > 5)  
    goto bomb;
  r12d++;     
  if(r12d == 6) 
    break;
  for(ebx = r12d; ebx <= 5; ebx++){ 
    if(num[ebx] == num[rbp])    
      goto bomb;
  }
  r13++;
}

这一部分有两层循环,说明输入的每个数字要求不大于6,且互不相同

  • 第二部分
//第二部分
  401153:	48 8d 74 24 18       	lea    0x18(%rsp),%rsi   //0x18=24,刚好为6个int型数据所占字节,将 %rsi 指向栈中跳过读入数据位置作为结束标记
  401158:	4c 89 f0             	mov    %r14,%rax     //%rax=%r14=%rsp  (%rax)存放输入数
  40115b:	b9 07 00 00 00       	mov    $0x7,%ecx     //%ecx=7
  401160:	89 ca                	mov    %ecx,%edx     //%edx=%ecx=7
  401162:	2b 10                	sub    (%rax),%edx   //7-(%rax)=7-(%r14) 立即数7减去 %r14 指向的数据
  401164:	89 10                	mov    %edx,(%rax)   //将7减的结果存回 %r14 执行的内存单元
  401166:	48 83 c0 04          	add    $0x4,%rax     // %rax 指向下一个输入数
  40116a:	48 39 f0             	cmp    %rsi,%rax     // 比较是否达到输入数组的末尾
  40116d:	75 f1                	jne    401160 <phase_6+0x6c>  

以上部分对应C代码:

rsi=7for(rax = 0; rax != rsi; rax++)
{
  num[rax] = 7 - num[rax];
}

这一部分的作用为使用立即数7减去每个输入数据,覆盖原来的数据

  • 第三部分

401183中有一个数据:0x6032d0,先通过gdb调试查看:
在这里插入图片描述
发现最后8字节数字每次都加了16字节,类似通过指针访问下一结点,并且可以通过前面的node1、node2、node3知道这是一个链表的结点
然后访问6304480,即node1的指针
在这里插入图片描述
发现这个指针指向的是下一个结点 node2,类似地如果访问6304496 得到的会是node3和后续结点
由此可以推断出: 前面的 332、168、924是结点数据, 1 2 3是结点编号,最后8字节是next指针

该链表每个结点的结构为:

struct node{
    int value;
    int number;
    node* next;
}
//第三部分
  40116f:	be 00 00 00 00       	mov    $0x0,%esi                //将 %rsi 置0
  401174:	eb 21                	jmp    401197 <phase_6+0xa3>    //跳转至401197
  401176:	48 8b 52 08          	mov    0x8(%rdx),%rdx           //将 0x8(%rdx) 指向内存单元的内容(即下一结点的指针值)复制到 %rdx, 指向链表下一个元素
  40117a:	83 c0 01             	add    $0x1,%eax                //将 %eax 加1
  40117d:	39 c8                	cmp    %ecx,%eax                //比较 %ecx 和 %eax 是否相等
  40117f:	75 f5                	jne    401176 <phase_6+0x82>    //不相等,继续遍历链表 【【最终 %rdx 指向链表的第 %ecx 个节点】】
  401181:	eb 05                	jmp    401188 <phase_6+0x94>
  401183:	ba d0 32 60 00       	mov    $0x6032d0,%edx           //重置链表首地址,%edx存放链表首结点地址
  401188:	48 89 54 74 20       	mov    %rdx,0x20(%rsp,%rsi,2)   /(%rsp+32+%rsi*2=%rdx
  40118d:	48 83 c6 04          	add    $0x4,%rsi                //%rsi=%rsi+4
  401191:	48 83 fe 18          	cmp    $0x18,%rsi
  401195:	74 14                	je     4011ab <phase_6+0xb7>    //当%rsi=24时,跳转至4011ab
  401197:	8b 0c 34             	mov    (%rsp,%rsi,1),%ecx       //将 (%rsp + %rsi) 指向的数据复制到 %ecx,%ecx存放输入数据
  40119a:	83 f9 01             	cmp    $0x1,%ecx                //比较 %ecx 是否小于等于1
  40119d:	7e e4                	jle    401183 <phase_6+0x8f>    //若%ecx小于等于1,跳转(因为%ecx代表结点,结点标号从1开始,所以输入数据的范围为[1,6])
                                                                //即%ecx=1时,%edx存放链表首结点地址
  40119f:	b8 01 00 00 00       	mov    $0x1,%eax                //若%ecx>1, 则%eax=1
  4011a4:	ba d0 32 60 00       	mov    $0x6032d0,%edx           //%edx存放链表首结点地址
  4011a9:	eb cb                	jmp    401176 <phase_6+0x82>

该循环根据输入数将链表中对应的第输入数个结点的地址复制到 0x20(%rsp) 开始的栈中

  • 第四部分
//第四部分
  4011ab:	48 8b 5c 24 20       	mov    0x20(%rsp),%rbx          //将(%rsp+32)的链表节点地址复制到 %rbx
  4011b0:	48 8d 44 24 28       	lea    0x28(%rsp),%rax          //将 %rax 指向栈中下一个链表结点的地址(%rsp+40)
  4011b5:	48 8d 74 24 50       	lea    0x50(%rsp),%rsi          //将 %rsi 指向保存的链表节点地址的末尾(%rsp+80)
  4011ba:	48 89 d9             	mov    %rbx,%rcx
  4011bd:	48 8b 10             	mov    (%rax),%rdx
  4011c0:	48 89 51 08          	mov    %rdx,0x8(%rcx)           //将栈中指向的后一个节点的地址复制到前一个节点的next指针位置
  4011c4:	48 83 c0 08          	add    $0x8,%rax          //移动到下一个节点
  4011c8:	48 39 f0             	cmp    %rsi,%rax           //判断6个节点是否遍历完毕
  4011cb:	74 05                	je     4011d2 <phase_6+0xde>  
  4011cd:	48 89 d1             	mov    %rdx,%rcx          //继续遍历
  4011d0:	eb eb                	jmp    4011bd <phase_6+0xc9>
  4011d2:	48 c7 42 08 00 00 00 	movq   $0x0,0x8(%rdx)     //末尾链表next 为 NULL 则设置为0x0

  //该循环按照7减去输入数据的索引重新调整链表

  4011d9:	00 
  4011da:	bd 05 00 00 00       	mov    $0x5,%ebp
  4011df:	48 8b 43 08          	mov    0x8(%rbx),%rax                //将 %rax 指向 %rbx 下一个链表结点
  4011e3:	8b 00                	mov    (%rax),%eax
  4011e5:	39 03                	cmp    %eax,(%rbx)                    //比较链表结点中第一个字段值的大小,如果前一个节点值大于后一个节点值,跳转
  4011e7:	7d 05                	jge    4011ee <phase_6+0xfa>
  4011e9:	e8 4c 02 00 00       	callq  40143a <explode_bomb>
  4011ee:	48 8b 5b 08          	mov    0x8(%rbx),%rbx               //将 %rbx 向后移动,指向栈中下一个链表节点的地址
  4011f2:	83 ed 01             	sub    $0x1,%ebp                   
  4011f5:	75 e8                	jne    4011df <phase_6+0xeb>         //判断循环是否结束
  //该循环判断栈中重新调整后的链表结点是否按照降序排列

  4011f7:	48 83 c4 50          	add    $0x50,%rsp
  4011fb:	5b                   	pop    %rbx
  4011fc:	5d                   	pop    %rbp
  4011fd:	41 5c                	pop    %r12
  4011ff:	41 5d                	pop    %r13
  401201:	41 5e                	pop    %r13                 //释放空间
  401203:	c3                   	retq   

因为第四部分要求:链表第一项数据 > 第二项数据 >·····
我们根据gdb调试 看地址0x6032d0
在这里插入图片描述
得node[i].value排序为:node[3]>node[4]>node[5]>node[6]>node[1]>node[2]

又因为这个顺序,是经过了numx = 0x7 - numx 则原输入数据应该是4 3 2 1 6 5

终端验证:
在这里插入图片描述
Phase 6已拆除

Phase 6拆弹密码4 3 2 1 6 5

Secret Phase 二叉树

bomb.c中,最后有一段注释:
在这里插入图片描述
说明这个炸弹之中还有一个隐藏关卡。

  • 寻找进入secret_phase 的入口

在bomb.asm中发现了如下汇编代码:

0000000000401242 <secret_phase>:
  401242:	53                   	push   %rbx
  401243:	e8 56 02 00 00       	callq  40149e <read_line>     
  401248:	ba 0a 00 00 00       	mov    $0xa,%edx
  40124d:	be 00 00 00 00       	mov    $0x0,%esi
  401252:	48 89 c7             	mov    %rax,%rdi
  401255:	e8 76 f9 ff ff       	callq  400bd0 <strtol@plt>   
  40125a:	48 89 c3             	mov    %rax,%rbx        
  40125d:	8d 40 ff             	lea    -0x1(%rax),%eax
  401260:	3d e8 03 00 00       	cmp    $0x3e8,%eax
  401265:	76 05                	jbe    40126c <secret_phase+0x2a>  
  401267:	e8 ce 01 00 00       	callq  40143a <explode_bomb>
  40126c:	89 de                	mov    %ebx,%esi       
  40126e:	bf f0 30 60 00       	mov    $0x6030f0,%edi 
  401273:	e8 8c ff ff ff       	callq  401204 <fun7>      
  401278:	83 f8 02             	cmp    $0x2,%eax   
  40127b:	74 05                	je     401282 <secret_phase+0x40>
  40127d:	e8 b8 01 00 00       	callq  40143a <explode_bomb>
  401282:	bf 38 24 40 00       	mov    $0x402438,%edi
  401287:	e8 84 f8 ff ff       	callq  400b10 <puts@plt>
  40128c:	e8 33 03 00 00       	callq  4015c4 <phase_defused>
  401291:	5b                   	pop    %rbx
  401292:	c3                   	retq   
  401293:	90                   	nop         
  401294:	90                   	nop
  401295:	90                   	nop
  401296:	90                   	nop
  401297:	90                   	nop
  401298:	90                   	nop
  401299:	90                   	nop
  40129a:	90                   	nop
  40129b:	90                   	nop
  40129c:	90                   	nop
  40129d:	90                   	nop
  40129e:	90                   	nop
  40129f:	90                   	nop

于是,需要寻找 secret_phase 的入口,即哪个函数调用了secret_phase,在bomb.asm中搜索发现

00000000004015c4 <phase_defused>:
  4015c4:	48 83 ec 78          	sub    $0x78,%rsp
  4015c8:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
  4015cf:	00 00 
  4015d1:	48 89 44 24 68       	mov    %rax,0x68(%rsp)
  4015d6:	31 c0                	xor    %eax,%eax
  4015d8:	83 3d 81 21 20 00 06 	cmpl   $0x6,0x202181(%rip)        # 603760 <num_input_strings>
  4015df:	75 5e                	jne    40163f <phase_defused+0x7b>
  4015e1:	4c 8d 44 24 10       	lea    0x10(%rsp),%r8
  4015e6:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx
  4015eb:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx
  4015f0:	be 19 26 40 00       	mov    $0x402619,%esi
  4015f5:	bf 70 38 60 00       	mov    $0x603870,%edi
  4015fa:	e8 f1 f5 ff ff       	callq  400bf0 <__isoc99_sscanf@plt>    
  4015ff:	83 f8 03             	cmp    $0x3,%eax                       
  401602:	75 31                	jne    401635 <phase_defused+0x71>  
  401604:	be 22 26 40 00       	mov    $0x402622,%esi       
  401609:	48 8d 7c 24 10       	lea    0x10(%rsp),%rdi
  40160e:	e8 25 fd ff ff       	callq  401338 <strings_not_equal>   
  401613:	85 c0                	test   %eax,%eax
  401615:	75 1e                	jne    401635 <phase_defused+0x71>  
  401617:	bf f8 24 40 00       	mov    $0x4024f8,%edi
  40161c:	e8 ef f4 ff ff       	callq  400b10 <puts@plt>
  401621:	bf 20 25 40 00       	mov    $0x402520,%edi
  401626:	e8 e5 f4 ff ff       	callq  400b10 <puts@plt>
  40162b:	b8 00 00 00 00       	mov    $0x0,%eax
  401630:	e8 0d fc ff ff       	callq  401242 <secret_phase>
  401635:	bf 58 25 40 00       	mov    $0x402558,%edi
  40163a:	e8 d1 f4 ff ff       	callq  400b10 <puts@plt>
  40163f:	48 8b 44 24 68       	mov    0x68(%rsp),%rax
  401644:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax
  40164b:	00 00 
  40164d:	74 05                	je     401654 <phase_defused+0x90>
  40164f:	e8 dc f4 ff ff       	callq  400b30 <__stack_chk_fail@plt>
  401654:	48 83 c4 78          	add    $0x78,%rsp
  401658:	c3                   	retq   
  401659:	90                   	nop
  40165a:	90                   	nop
  40165b:	90                   	nop
  40165c:	90                   	nop
  40165d:	90                   	nop
  40165e:	90                   	nop
  40165f:	90                   	nop

phase_defused调用了secret_phase,而bomb.c中每个phase后面都用到了phase_defused
在这里插入图片描述

  • 对phase_defused的汇编代码进行分析

上半部分:

00000000004015c4 <phase_defused>:
  4015c4:	48 83 ec 78          	sub    $0x78,%rsp
  4015c8:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
  4015cf:	00 00 
  4015d1:	48 89 44 24 68       	mov    %rax,0x68(%rsp)
  4015d6:	31 c0                	xor    %eax,%eax
  4015d8:	83 3d 81 21 20 00 06 	cmpl   $0x6,0x202181(%rip)        # 603760 <num_input_strings>
                                                           //num_input_strings 表示我们已经输入了多少串字符串了,判断是否等于6,
                                                           //如果不等于6,直接跳转到最下方,则secret_phase无法进入
                                                           //所以进入secret_phase的则先决条件是:完成phase 1 - 6
  4015df:	75 5e                	jne    40163f <phase_defused+0x7b>
  4015e1:	4c 8d 44 24 10       	lea    0x10(%rsp),%r8
  4015e6:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx
  4015eb:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx
  4015f0:	be 19 26 40 00       	mov    $0x402619,%esi
  4015f5:	bf 70 38 60 00       	mov    $0x603870,%edi
  4015fa:	e8 f1 f5 ff ff       	callq  400bf0 <__isoc99_sscanf@plt>    //调用sscanf函数
  4015ff:	83 f8 03             	cmp    $0x3,%eax                       //判断返回值%eax是否等于3
  401602:	75 31                	jne    401635 <phase_defused+0x71>     //如果返回值%eax不等于3的话,则跳转到最下方,跳过了401630 callq  401242 <secret_phase>
                                                                       //即secret_phase无法进入,所以必须要让sscanf函数的返回值为3 

sscanf函数原理(sscanf函数用法详解)为每次读取都是对指定字符串,作格式化读取,如:

char buf[512] = ; 
  sscanf("123456 ", "%s", buf); 
  printf("%s\n", buf); 

结果为:123456
  
而在调用前的mov进寄存器的参数,有两条mov的语句

  4015f0:	be 19 26 40 00       	mov    $0x402619,%esi
  4015f5:	bf 70 38 60 00       	mov    $0x603870,%edi

根据原理,能够猜测出,一个是指定的字符串,一个格式化读取字符串

调试查看这两个参数具体对应什么字符串:
先输入6个之前我们完成的字符串,并于0x4015fa断点
在这里插入图片描述
最后得到格式化字符串 %d %d %s ,指定字符串7 0

而7 0就是phase 4的解码,联系sscanf函数的返回值%eax需要等于3,可以猜想需要在7 0 后面再输入一串字符串,即可进入隐藏关卡

对剩余部分的汇编代码分析:

 401604:	be 22 26 40 00       	mov    $0x402622,%esi       //%esi=0x402622
  401609:	48 8d 7c 24 10       	lea    0x10(%rsp),%rdi
  40160e:	e8 25 fd ff ff       	callq  401338 <strings_not_equal>   //调用字符串比较函数,判断输入的字符串和%esi中存的字符串是否相等
  401613:	85 c0                	test   %eax,%eax
  401615:	75 1e                	jne    401635 <phase_defused+0x71>   //若相等,则跳转至401635
                                                                     //(gdb)  print (char*) 0x402622 得到字符串 DrEvil
                                                                     //以下的代码不影响解码,暂且不做分析
  401617:	bf f8 24 40 00       	mov    $0x4024f8,%edi
  40161c:	e8 ef f4 ff ff       	callq  400b10 <puts@plt>
  401621:	bf 20 25 40 00       	mov    $0x402520,%edi
  401626:	e8 e5 f4 ff ff       	callq  400b10 <puts@plt>
  40162b:	b8 00 00 00 00       	mov    $0x0,%eax
  401630:	e8 0d fc ff ff       	callq  401242 <secret_phase>
  401635:	bf 58 25 40 00       	mov    $0x402558,%edi
  40163a:	e8 d1 f4 ff ff       	callq  400b10 <puts@plt>
  40163f:	48 8b 44 24 68       	mov    0x68(%rsp),%rax
  401644:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax
  40164b:	00 00 
  40164d:	74 05                	je     401654 <phase_defused+0x90>
  40164f:	e8 dc f4 ff ff       	callq  400b30 <__stack_chk_fail@plt>
  401654:	48 83 c4 78          	add    $0x78,%rsp
  401658:	c3                   	retq   
  401659:	90                   	nop
  40165a:	90                   	nop
  40165b:	90                   	nop
  40165c:	90                   	nop
  40165d:	90                   	nop
  40165e:	90                   	nop
  40165f:	90                   	nop

可得进入隐藏关卡的条件为 在第四关的解码7 0 后面加上字符串DrEvil

可使用命令 touch answers.txt 新建名为answers的.txt文件,将所有拆弹密码写入该文件,然后用命令 ./bomb answers.txt 运行bomb可执行文件 (之前的每一关调试结束后,也可以将新的拆弹密码写入.txt文件,用这个方法验证是否爆炸,就不用每次重复输入之前关卡的拆弹密码了)
在这里插入图片描述
终端验证:
在这里插入图片描述
然后回到secret_phase的源码进行分析

  • 对secret_phase的汇编代码进行分析
0000000000401242 <secret_phase>:
  401242:	53                   	push   %rbx
  401243:	e8 56 02 00 00       	callq  40149e <read_line>     //调用read_line函数,读取字符串
  401248:	ba 0a 00 00 00       	mov    $0xa,%edx
  40124d:	be 00 00 00 00       	mov    $0x0,%esi
  401252:	48 89 c7             	mov    %rax,%rdi
  401255:	e8 76 f9 ff ff       	callq  400bd0 <strtol@plt>    //调用strtol函数,将字符串转换为整型数据num,存在%rax中
  40125a:	48 89 c3             	mov    %rax,%rbx        //%rbx=%rax=num
  40125d:	8d 40 ff             	lea    -0x1(%rax),%eax
  401260:	3d e8 03 00 00       	cmp    $0x3e8,%eax
  401265:	76 05                	jbe    40126c <secret_phase+0x2a>  //num-1>1000(0x3e8),则会爆炸,所以输入的数字必须小于等于1001
  401267:	e8 ce 01 00 00       	callq  40143a <explode_bomb>
  40126c:	89 de                	mov    %ebx,%esi       //%esi=%ebx=num  %esi存放输入的数据num,作为参数代入fun7
  40126e:	bf f0 30 60 00       	mov    $0x6030f0,%edi  //%edi=6030f0 作为参数代入fun7
  401273:	e8 8c ff ff ff       	callq  401204 <fun7>       //调用函数fun7
  401278:	83 f8 02             	cmp    $0x2,%eax     //将fun7的返回值%eax与2比较  
                                                     //因为fun7为调用phase_defusd之前最后调用的一个函数,【【所以如果%eax=2,则跳过炸弹,拆弹成功!】】
                                                     //所以需要对fun7进行分析
  40127b:	74 05                	je     401282 <secret_phase+0x40>
  40127d:	e8 b8 01 00 00       	callq  40143a <explode_bomb>
  401282:	bf 38 24 40 00       	mov    $0x402438,%edi
  401287:	e8 84 f8 ff ff       	callq  400b10 <puts@plt>
  40128c:	e8 33 03 00 00       	callq  4015c4 <phase_defused>
  401291:	5b                   	pop    %rbx
  401292:	c3                   	retq   
  401293:	90                   	nop          //nop 方便指令读取,不影响分析
  401294:	90                   	nop
  401295:	90                   	nop
  401296:	90                   	nop
  401297:	90                   	nop
  401298:	90                   	nop
  401299:	90                   	nop
  40129a:	90                   	nop
  40129b:	90                   	nop
  40129c:	90                   	nop
  40129d:	90                   	nop
  40129e:	90                   	nop
  40129f:	90                   	nop

因为fun7为调用phase_defusd之前最后调用的一个函数,所以如果fun7的返回值 %eax=2,则跳过炸弹,拆弹成功,所以需要对fun7进行分析

  • 对fun7的汇编代码进行分析

首先查看0x6030f0存放的数据
在这里插入图片描述
发现是一个跟phase 6类似的结构体, 6304xxx应该为指针,而且意外发现phase 6的指针数组就在下方
这里其实是一个带着两个指针的结构体,前面的7个结构体的两个指针都是带有值的,指向其他的结构体,最后的8个结构体指针是不带有值的,仅仅有头部数据
指针所指的数据结构是二叉树

0000000000401204 <fun7>:
  401204:	48 83 ec 08          	sub    $0x8,%rsp
  401208:	48 85 ff             	test   %rdi,%rdi
  40120b:	74 2b                	je     401238 <fun7+0x34>       //返回空指针-1
  40120d:	8b 17                	mov    (%rdi),%edx
  40120f:	39 f2                	cmp    %esi,%edx           //%esi=num,%edx=(%edi)  比较输入数和指针所指的值
  401211:	7e 0d                	jle    401220 <fun7+0x1c>       //如果小于等于,则跳转至401220
  401213:	48 8b 7f 08          	mov    0x8(%rdi),%rdi           //如果大于
  401217:	e8 e8 ff ff ff       	callq  401204 <fun7>            //递归调用fun7
  40121c:	01 c0                	add    %eax,%eax            //%eax=2*%eax
  40121e:	eb 1d                	jmp    40123d <fun7+0x39>   //return

  401220:	b8 00 00 00 00       	mov    $0x0,%eax
  401225:	39 f2                	cmp    %esi,%edx
  401227:	74 14                	je     40123d <fun7+0x39>    //如果输入数和当前指针所指的值相等,则返回0(递归出口)

  401229:	48 8b 7f 10          	mov    0x10(%rdi),%rdi      //%rdi=(%rdi+16)
  40122d:	e8 d2 ff ff ff       	callq  401204 <fun7>        //递归调用fun7
  401232:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax      //%eax=2*%eax+1
  401236:	eb 05                	jmp    40123d <fun7+0x39>     //return
  401238:	b8 ff ff ff ff       	mov    $0xffffffff,%eax
  40123d:	48 83 c4 08          	add    $0x8,%rsp
  401241:	c3                   	retq   

fun7对应C代码:

int func7(Type *p, int input)
{
    if(p == NULL)
        return -1;
    if(&p <= input)
    {
        if(&p == input)
            return 0;
        else
        {
            p = p + 0x10;
            int n = func7(p, input);
            return 2 * n + 1;
        }
    }
    else
    {
        p = p + 0x8;
        int n = func7(p, input);
        return 2 * n;
    }
}

需要得到返回值 %eax=2,说明递归顺序为:

  1. 最底层得到0 return 0
  2. 向上经过一层 %eax = %eax*2 + 1 得到1 return 1
  3. 再向上经过一层% eax = %eax*2 得到2 return 2

p所指向的数据结构是二叉搜索树,该树的结构为p = p + 0x10加载右结点p = p + 0x8加载左结点。返回路径如下图:
在这里插入图片描述
顺推思路:

  1. 首先来到二叉树的首地址0x6030f0对应的数据:36,因为36需要大于x,才能得到 %eax = %eax * 2,那么指针值应该为%rdi + 8(加载左结点),指针值为6304016,查看得到值为8
  2. 来到8对应的位置,我们想要数据%eax = %eax*2 + 1,则8需要小于等于x,那么指针值应该为0x603110 + 16(加载右结点),指针值为 6304080,查看得到的值为22
  3. 最后我们得到了数据22,当我们输入22的时候,因为和指针所处位置对应头部数据的值相等,所以%eax = 0
    在这里插入图片描述

因此22为可行解

而当查看22对应位置时,发现该位置还有两个指针,且不是空指针, 猜想如果 22大于所需解码,返回值为%eax= %eax*2,同样符合要求,那么指针值应该为0x603150 + 8(加载左结点),指针值为6304368,查看得到值为20,该位置指针为空,不继续指向下一结点,所以20也为可行解。
在这里插入图片描述
对应递归顺序为:

  1. 最底层 %eax = 0 return 0
  2. 倒数第二层 %eax = %eax*2 return 0
  3. 倒数第三层% eax = %eax*2 +1 return 1
  4. 倒数第四层 %eax = %eax*2 return 2

Secret Phase拆弹密码22或20

将全部解码写入answers.txt(多解的关卡可以写一个解,也可以同时写多个解,均可运行)
在这里插入图片描述
终端验证:
在这里插入图片描述
炸弹拆除!

 
 

参考:
关于栈帧:栈帧详解
实验过程:

三.小结

该实验比起Data Lab难度大了不少,在做到Phase 4的时候,难度一下就上来了,一直在各种寄存器之间转来转去,到后面几个Phase,涉及指针和结构体的时候,好想摆烂 ,后面跟着参考博客一点一点扒,发现实验的设计有好多巧妙之处。
作为经典实验,确实对从汇编角度的寄存器、栈、指针等的理解以及gdb的调试方法有不小帮助。

  • 44
    点赞
  • 285
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
BUPT CSAPP Lab4是指北京邮电大学计算机科学与技术课程《CSAPP-深入理解计算机系统》的实验四。该实验主要涉及操作系统相关的内容,旨在帮助学生加深对操作系统的理解和认识。 该实验的主要内容是通过编写一个简单的Unix shell来实现一个命令行解释器。这个shell可以读取来自用户输入的命令,并执行这些命令。在实现过程中,需要学生理解和掌握进程控制、文件描述符、信号处理、文件I/O等操作系统的核心概念和技术。 实验4主要由以下几个部分组成: 1. 实现命令行解释器的基本功能,包括读取用户输入的命令、解析命令的参数和选项、执行命令等。此外,还需要处理输入输出重定向、管道、后台运行等特殊操作。 2. 实现信号处理功能,包括捕获和处理常见的信号(如SIGINT、SIGCHLD等),以及重新设置信号处理程序等。 3. 实现文件I/O功能,包括文件打开、读写、关闭等操作。需要学生理解文件描述符的概念和用法,并能正确地管理文件描述符。 4. 实现进程控制功能,包括创建新的进程、加载可执行文件、执行命令、等待子进程退出等。学生需要理解进程的创建、终止、调度等基本概念和原理,以及在实践中正确地使用这些操作。 通过完成该实验,学生可以加深对操作系统内核的理解,掌握操作系统的基本功能和组成,提高对计算机系统的整体把握能力。此外,实验也有助于学生培养编程能力、问题解决能力和团队合作能力等重要的综合素养。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值