目录

  • 前言
  • 一.实验总说明
  • 二.实验过程记录
    • 准备
    • Phase 1 字符串比较
    • Phase 2 循环
    • Phase 3 条件/分支
    • Phase 4 递归调用和栈
    • Phase 5 指针
    • Phase 6 链表/指针/结构
    • Secret Phase 二叉树
  • 三.小结

前言

该实验是《深入理解计算机系统》(英文缩写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,%rdx401463: 48 8d 4e 04             lea    0x4(%rsi),%rcx401467:    48 8d 46 14             lea    0x14(%rsi),%rax40146b:   48 89 44 24 08          mov    %rax,0x8(%rsp)401470:    48 8d 46 10             lea    0x10(%rsi),%rax401474:   48 89 04 24             mov    %rax,(%rsp)401478:   4c 8d 4e 0c             lea    0xc(%rsi),%r940147c: 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,%eax40148a: e8 61 f7 ff ff          callq  400bf0 <__isoc99_sscanf@plt>  //调用scanf函数40148f:  83 f8 05                cmp    $0x5,%eax401492: 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,%rsp40149d:    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> //若相等则跳转至400f30400f10:  e8 25 05 00 00          callq  40143a <explode_bomb>  //否则爆炸400f15:   eb 19                   jmp    400f30 <phase_2+0x34>  //jmp指令无条件跳转,直接跳转至400f30400f17: 8b 43 fc                mov    -0x4(%rbx),%eax400f1a:   01 c0                   add    %eax,%eax      //%eax的值*2400f1c: 39 03                   cmp    %eax,(%rbx)    //比较%eax的值和此时%rbx对应的内存的值400f1e:   74 05                   je     400f25 <phase_2+0x29>    //若相等则跳转至400f25400f20:   e8 15 05 00 00          callq  40143a <explode_bomb>     //否则爆炸400f25:    48 83 c3 04             add    $0x4,%rbx        //%rbx的值(地址)加4400f29: 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,%rsp400f40:    5b                      pop    %rbx400f41:  5d                      pop    %rbp400f42:  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)设为num2400f4c:    48 8d 54 24 08          lea    0x8(%rsp),%rdx  //加载地址,将0x8(%rsp)设为num1400f51:    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    //初始化%eax400f5b:    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输入数据的个数必须大于1400f65:    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=207400f81:  eb 3b                   jmp    400fbe <phase_3+0x7b>400f83:  b8 c3 02 00 00          mov    $0x2c3,%eax                //case 2  %eax=0x2c3=707400f88: eb 34                   jmp    400fbe <phase_3+0x7b>400f8a:  b8 00 01 00 00          mov    $0x100,%eax                //case 3  %eax=0x100=256400f8f: eb 2d                   jmp    400fbe <phase_3+0x7b>400f91:  b8 85 01 00 00          mov    $0x185,%eax                //case 4  %eax=0x185=389400f96: eb 26                   jmp    400fbe <phase_3+0x7b>400f98:  b8 ce 00 00 00          mov    $0xce,%eax                 //case 5  %eax=0xce=206400f9d:  eb 1f                   jmp    400fbe <phase_3+0x7b>400f9f:  b8 aa 02 00 00          mov    $0x2aa,%eax                //case 6  %eax=0x2aa=682400fa4: eb 18                   jmp    400fbe <phase_3+0x7b>400fa6:  b8 47 01 00 00          mov    $0x147,%eax                //case 7  %eax=0x147=327400fab: 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,%eax400fb7: eb 05                   jmp    400fbe <phase_3+0x7b>400fb9:  b8 37 01 00 00          mov    $0x137,%eax                //case 1  %eax=0x137=311400fbe: 3b 44 24 0c             cmp    0xc(%rsp),%eax   //比较%eax的值和num2400fc2:  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 207if (num2 != 0xcf) {explode_bomb();}break;case 1: // 1 311if (num2 != 0x137) {explode_bomb();}break;case 2: // 2 707if (num2 != 0x2c3) {explode_bomb(); }break;case 3: // 3 256if (num2 != 0x100) {explode_bomb(); }break;case 4: // 4 389if (num2 != 0x185) {explode_bomb();}break;case 5: // 5 206if (num2 != 0xce) {explode_bomb(); }break;case 6: // 6 682if (num2 != 0x2aa) {explode_bomb(); }break;case 7: // 7 327if (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)设为num2401015:  48 8d 54 24 08          lea    0x8(%rsp),%rdx    //传参,加载有效地址,将0x8(%rsp)设为num140101a:  be cf 25 40 00          mov    $0x4025cf,%esi    //scanf函数输入格式, %d %d40101f: b8 00 00 00 00          mov    $0x0,%eax401024: 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=1440103f:   be 00 00 00 00          mov    $0x0,%esi            //%esi=0401044:    8b 7c 24 08             mov    0x8(%rsp),%edi       //%edi=num1401048: e8 81 ff ff ff          callq  400fce <func4>         //调用函数fun4,三个参数%edx,%esi,%edi40104d:   85 c0                   test   %eax,%eax40104f: 75 07                   jne    401058 <phase_4+0x4c>      //如果fun4返回值%eax不等于0,则跳转至爆炸,所以需要知道当num1为何值时,%eax为0401051:  83 7c 24 0c 00          cmpl   $0x0,0xc(%rsp)401056:    74 05                   je     40105d <phase_4+0x51>      //如果num2=0,则跳转至结束,否则爆炸,所以输入的第二个数据只能为0401058: 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=14400fd4:   29 f0                   sub    %esi,%eax          //%eax=%eax-%esi=14-0=14400fd6:    89 c1                   mov    %eax,%ecx          //%ecx=%eax=14400fd8:   c1 e9 1f                shr    $0x1f,%ecx         //%ecx的值逻辑右移31位,即%ecx=%ecx>>31=1110>>31=14/2^31=0400fdb:   01 c8                   add    %ecx,%eax          //因为是逻辑右移,也就是如果%eax为正数则不变,为负数则+1;%eax=%eax+%ecx=14+0=14400fdd: d1 f8                   sar    %eax               //算术右移1位,%eax=%eax/2=14/2=7400fdf:  8d 0c 30                lea    (%rax,%rsi,1),%ecx   //%eax为%rax的第32位表示,%esi为%rsi的低32位表示,初始时%rax=%eax,%rsi=%esi//加载有效地址,%ecx=%rax+%rsi=7+0=7400fe2:   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下限为0400fee:  01 c0                   add    %eax,%eax                //%eax=%eax*2400ff0:   eb 15                   jmp    401007 <func4+0x39>      //递归出口400ff2:    b8 00 00 00 00          mov    $0x0,%eax                //%eax=0400ff7:    39 f9                   cmp    %edi,%ecx                 //若%ecx大于等于num1,递归结束,返回phase_4400ff9:    7d 0c                   jge    401007 <func4+0x39>       //递归出口400ffb:   8d 71 01                lea    0x1(%rcx),%esi            //%esi=%rcx+1400ffe: e8 cb ff ff ff          callq  400fce <func4>            //递归调用401003:    8d 44 00 01             lea    0x1(%rax,%rax,1),%eax     //%eax=%rax+1+%rax401007:   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的初始值为14int 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   %rbx401063:  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,%rax401071: 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,%eax401082: 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),%rdx401096:   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字符传给%edx4010a0:  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 每次循环后+14010a8:    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),%rdi4010bd:   e8 76 02 00 00          callq  401338 <strings_not_equal>4010c2:  85 c0                   test   %eax,%eax4010c4: 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                //清空%eax4010d7: eb b2                   jmp    40108b <phase_5+0x29>    //跳转至40108b4010d9:   48 8b 44 24 18          mov    0x18(%rsp),%rax4010de:   64 48 33 04 25 28 00    xor    %fs:0x28,%rax4010e5: 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    %rbx4010f3:  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   %r144010f6:  41 55                   push   %r134010f8:  41 54                   push   %r124010fa:  55                      push   %rbp4010fb:  53                      push   %rbx4010fc:  48 83 ec 50             sub    $0x50,%rsp     //申请空间401100: 49 89 e5                mov    %rsp,%r13      //%r13=%rsp401103:   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=%rsp40110e:  41 bc 00 00 00 00       mov    $0x0,%r12d      //%r12d=0   %r12d当做数组索引,类似i=0401114:    4c 89 ed                mov    %r13,%rbp      //初始 %rbp=%r13=%rsp401117:  41 8b 45 00             mov    0x0(%r13),%eax    //%eax=num[i]40111b:  83 e8 01                sub    $0x1,%eax40111e: 83 f8 05                cmp    $0x5,%eax401121: 76 05                   jbe    401128 <phase_6+0x34>  //无符号数比较,说明num为无符号数,即大于等于0,40111b~401121 num[i]-1<=5,所以num[i]<=6401123:   e8 12 03 00 00          callq  40143a <explode_bomb>401128:   41 83 c4 01             add    $0x1,%r12d40112c:    41 83 fc 06             cmp    $0x6,%r12d401130:    74 21                   je     401153 <phase_6+0x5f>401132:  44 89 e3                mov    %r12d,%ebx           //401128~401132 退出大循环的条件:6个数字全部遍历到401135:   48 63 c3                movslq %ebx,%rax401138: 8b 04 84                mov    (%rsp,%rax,4),%eax40113b:    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,%ebx401148: 83 fb 05                cmp    $0x5,%ebx40114b: 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=7401160:   89 ca                   mov    %ecx,%edx     //%edx=%ecx=7401162: 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=7;
for(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 置0401174:  eb 21                   jmp    401197 <phase_6+0xa3>    //跳转至401197401176:   48 8b 52 08             mov    0x8(%rdx),%rdx           //将 0x8(%rdx) 指向内存单元的内容(即下一结点的指针值)复制到 %rdx, 指向链表下一个元素40117a:  83 c0 01                add    $0x1,%eax                //将 %eax 加140117d:  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)=%rdx40118d: 48 83 c6 04             add    $0x4,%rsi                //%rsi=%rsi+4401191:  48 83 fe 18             cmp    $0x18,%rsi401195:    74 14                   je     4011ab <phase_6+0xb7>    //当%rsi=24时,跳转至4011ab401197: 8b 0c 34                mov    (%rsp,%rsi,1),%ecx       //将 (%rsp + %rsi) 指向的数据复制到 %ecx,%ecx存放输入数据40119a:   83 f9 01                cmp    $0x1,%ecx                //比较 %ecx 是否小于等于140119d:    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=14011a4:   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)的链表节点地址复制到 %rbx4011b0: 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,%rcx4011bd: 48 8b 10                mov    (%rax),%rdx4011c0:   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,%ebp4011df: 48 8b 43 08             mov    0x8(%rbx),%rax                //将 %rax 指向 %rbx 下一个链表结点4011e3:    8b 00                   mov    (%rax),%eax4011e5:   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,%rsp4011fb:    5b                      pop    %rbx4011fc:  5d                      pop    %rbp4011fd:  41 5c                   pop    %r124011ff:  41 5d                   pop    %r13401201:  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   %rbx401243:  e8 56 02 00 00          callq  40149e <read_line>     401248: ba 0a 00 00 00          mov    $0xa,%edx40124d: be 00 00 00 00          mov    $0x0,%esi401252: 48 89 c7                mov    %rax,%rdi401255: e8 76 f9 ff ff          callq  400bd0 <strtol@plt>   40125a: 48 89 c3                mov    %rax,%rbx        40125d: 8d 40 ff                lea    -0x1(%rax),%eax401260:   3d e8 03 00 00          cmp    $0x3e8,%eax401265:   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,%edi401287:    e8 84 f8 ff ff          callq  400b10 <puts@plt>40128c:  e8 33 03 00 00          callq  4015c4 <phase_defused>401291:  5b                      pop    %rbx401292:  c3                      retq   401293:  90                      nop         401294: 90                      nop401295:  90                      nop401296:  90                      nop401297:  90                      nop401298:  90                      nop401299:  90                      nop40129a:  90                      nop40129b:  90                      nop40129c:  90                      nop40129d:  90                      nop40129e:  90                      nop40129f:  90                      nop

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

00000000004015c4 <phase_defused>:4015c4:   48 83 ec 78             sub    $0x78,%rsp4015c8:    64 48 8b 04 25 28 00    mov    %fs:0x28,%rax4015cf: 00 00 4015d1:   48 89 44 24 68          mov    %rax,0x68(%rsp)4015d6:   31 c0                   xor    %eax,%eax4015d8: 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),%r84015e6:    48 8d 4c 24 0c          lea    0xc(%rsp),%rcx4015eb:    48 8d 54 24 08          lea    0x8(%rsp),%rdx4015f0:    be 19 26 40 00          mov    $0x402619,%esi4015f5:    bf 70 38 60 00          mov    $0x603870,%edi4015fa:    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),%rdi40160e:   e8 25 fd ff ff          callq  401338 <strings_not_equal>   401613:   85 c0                   test   %eax,%eax401615: 75 1e                   jne    401635 <phase_defused+0x71>  401617:  bf f8 24 40 00          mov    $0x4024f8,%edi40161c:    e8 ef f4 ff ff          callq  400b10 <puts@plt>401621:  bf 20 25 40 00          mov    $0x402520,%edi401626:    e8 e5 f4 ff ff          callq  400b10 <puts@plt>40162b:  b8 00 00 00 00          mov    $0x0,%eax401630: e8 0d fc ff ff          callq  401242 <secret_phase>401635:   bf 58 25 40 00          mov    $0x402558,%edi40163a:    e8 d1 f4 ff ff          callq  400b10 <puts@plt>40163f:  48 8b 44 24 68          mov    0x68(%rsp),%rax401644:   64 48 33 04 25 28 00    xor    %fs:0x28,%rax40164b: 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,%rsp401658:    c3                      retq   401659:  90                      nop40165a:  90                      nop40165b:  90                      nop40165c:  90                      nop40165d:  90                      nop40165e:  90                      nop40165f:  90                      nop

phase_defused调用了secret_phase,而bomb.c中每个phase后面都用到了phase_defused

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

上半部分:

00000000004015c4 <phase_defused>:4015c4:   48 83 ec 78             sub    $0x78,%rsp4015c8:    64 48 8b 04 25 28 00    mov    %fs:0x28,%rax4015cf: 00 00 4015d1:   48 89 44 24 68          mov    %rax,0x68(%rsp)4015d6:   31 c0                   xor    %eax,%eax4015d8: 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 - 64015df:    75 5e                   jne    40163f <phase_defused+0x7b>4015e1:    4c 8d 44 24 10          lea    0x10(%rsp),%r84015e6:    48 8d 4c 24 0c          lea    0xc(%rsp),%rcx4015eb:    48 8d 54 24 08          lea    0x8(%rsp),%rdx4015f0:    be 19 26 40 00          mov    $0x402619,%esi4015f5:    bf 70 38 60 00          mov    $0x603870,%edi4015fa:    e8 f1 f5 ff ff          callq  400bf0 <__isoc99_sscanf@plt>    //调用sscanf函数4015ff:   83 f8 03                cmp    $0x3,%eax                       //判断返回值%eax是否等于3401602:  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,%esi4015f5:    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=0x402622401609: 48 8d 7c 24 10          lea    0x10(%rsp),%rdi40160e:   e8 25 fd ff ff          callq  401338 <strings_not_equal>   //调用字符串比较函数,判断输入的字符串和%esi中存的字符串是否相等401613: 85 c0                   test   %eax,%eax401615: 75 1e                   jne    401635 <phase_defused+0x71>   //若相等,则跳转至401635//(gdb)  print (char*) 0x402622 得到字符串 DrEvil//以下的代码不影响解码,暂且不做分析401617:    bf f8 24 40 00          mov    $0x4024f8,%edi40161c:    e8 ef f4 ff ff          callq  400b10 <puts@plt>401621:  bf 20 25 40 00          mov    $0x402520,%edi401626:    e8 e5 f4 ff ff          callq  400b10 <puts@plt>40162b:  b8 00 00 00 00          mov    $0x0,%eax401630: e8 0d fc ff ff          callq  401242 <secret_phase>401635:   bf 58 25 40 00          mov    $0x402558,%edi40163a:    e8 d1 f4 ff ff          callq  400b10 <puts@plt>40163f:  48 8b 44 24 68          mov    0x68(%rsp),%rax401644:   64 48 33 04 25 28 00    xor    %fs:0x28,%rax40164b: 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,%rsp401658:    c3                      retq   401659:  90                      nop40165a:  90                      nop40165b:  90                      nop40165c:  90                      nop40165d:  90                      nop40165e:  90                      nop40165f:  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   %rbx401243:  e8 56 02 00 00          callq  40149e <read_line>     //调用read_line函数,读取字符串401248: ba 0a 00 00 00          mov    $0xa,%edx40124d: be 00 00 00 00          mov    $0x0,%esi401252: 48 89 c7                mov    %rax,%rdi401255: e8 76 f9 ff ff          callq  400bd0 <strtol@plt>    //调用strtol函数,将字符串转换为整型数据num,存在%rax中40125a:   48 89 c3                mov    %rax,%rbx        //%rbx=%rax=num40125d:    8d 40 ff                lea    -0x1(%rax),%eax401260:   3d e8 03 00 00          cmp    $0x3e8,%eax401265:   76 05                   jbe    40126c <secret_phase+0x2a>  //num-1>1000(0x3e8),则会爆炸,所以输入的数字必须小于等于1001401267:    e8 ce 01 00 00          callq  40143a <explode_bomb>40126c:   89 de                   mov    %ebx,%esi       //%esi=%ebx=num  %esi存放输入的数据num,作为参数代入fun740126e:   bf f0 30 60 00          mov    $0x6030f0,%edi  //%edi=6030f0 作为参数代入fun7401273: e8 8c ff ff ff          callq  401204 <fun7>       //调用函数fun7401278:  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,%edi401287:    e8 84 f8 ff ff          callq  400b10 <puts@plt>40128c:  e8 33 03 00 00          callq  4015c4 <phase_defused>401291:  5b                      pop    %rbx401292:  c3                      retq   401293:  90                      nop          //nop 方便指令读取,不影响分析401294:   90                      nop401295:  90                      nop401296:  90                      nop401297:  90                      nop401298:  90                      nop401299:  90                      nop40129a:  90                      nop40129b:  90                      nop40129c:  90                      nop40129d:  90                      nop40129e:  90                      nop40129f:  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,%rsp401208: 48 85 ff                test   %rdi,%rdi40120b: 74 2b                   je     401238 <fun7+0x34>       //返回空指针-140120d: 8b 17                   mov    (%rdi),%edx40120f:   39 f2                   cmp    %esi,%edx           //%esi=num,%edx=(%edi)  比较输入数和指针所指的值401211:    7e 0d                   jle    401220 <fun7+0x1c>       //如果小于等于,则跳转至401220401213:    48 8b 7f 08             mov    0x8(%rdi),%rdi           //如果大于401217:   e8 e8 ff ff ff          callq  401204 <fun7>            //递归调用fun740121c: 01 c0                   add    %eax,%eax            //%eax=2*%eax40121e:   eb 1d                   jmp    40123d <fun7+0x39>   //return401220:  b8 00 00 00 00          mov    $0x0,%eax401225: 39 f2                   cmp    %esi,%edx401227: 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>        //递归调用fun7401232: 8d 44 00 01             lea    0x1(%rax,%rax,1),%eax      //%eax=2*%eax+1401236:  eb 05                   jmp    40123d <fun7+0x39>     //return401238:    b8 ff ff ff ff          mov    $0xffffffff,%eax40123d:  48 83 c4 08             add    $0x8,%rsp401241: 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(多解的关卡可以写一个解,也可以同时写多个解,均可运行)

终端验证:

炸弹拆除!

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

  • CSAPP Lab2 实验记录 ---- Bomb Lab(Phase 1 - Phase 6详细解答 + Secret
    Phase彩蛋解析)
  • CSAPP Lab2 bomblab 炸弹实验
    6+1
  • CS:APP二进制炸弹phase3
  • 《深入理解计算机系统》(CSAPP)实验二 —— Bomb
    Lab
  • CSAPP-Lab02 Bomb Lab
    详细解析

三.小结

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

CSAPP Lab2:Bomb Lab相关推荐

  1. 【计算机系统基础bomb lab】CSAPP实验:Bomb Lab

    [计算机系统基础bomb lab]CSAPP实验:Bomb Lab CSAPP 实验:Bomb Lab 实验内容简述 实验环境 实验过程:phase 1 phase 1 调试过程 实验过程:phase ...

  2. CSAPP实验二——bomb lab实验

    CSAPP实验二-- bomb lab实验 实验前准备 第一部分(phase_1) 第二部分(phase_2) 第三部分(phase_3) 第四部分(phase_4) 第五部分(phase_5) 第六 ...

  3. CSAPP实验之Bomb Lab详解

    前言 Bomb Lab来自<深入理解计算机系统>(CSAPP)一书的第三章"程序的机器级表示"的配套实验,该实验的目的是通过反汇编可执行程序,来反推出程序执行内容,进而 ...

  4. CSAPP Lab1:Data Lab (虚拟机安装+Lab环境配置+函数实现)

    目录 前言 一.WIN10虚拟机安装 1.关于Vmware Workstation,Ubuntu和Vmware tools 2.安装步骤 二.Lab环境配置(安装GCC编译套装) 三.README及实 ...

  5. CSAPP Lab2 实验记录 ---- Bomb Lab(Phase 1 - Phase 6详细解答 + Secret Phase彩蛋解析)

    文章目录 Lab 总结博客链接 实验前提引子 实验需要指令及准备 Phase 1 Phase 2 Phase 3 Phase 4 Phase 5 Phase 6 Phase Secret(彩蛋Phas ...

  6. CSAPP实验二:二进制炸弹(Bomb Lab)

    本系列文章为中国科学技术大学计算机专业学科基础课<计算机系统>布置的实验,上课所用教材和内容为黑书CSAPP,当时花费很大精力和弯路,现来总结下各个实验,本文章为第二个实验--二进制炸弹( ...

  7. CSAPP——Lab2——Bomb Lab

    本篇博客是<深入理解计算机系统>实验记录的第二篇,也是非常有名的炸弹实验.首先吐槽一下学校的这个实验代码不更新的问题,教材和上课讲授的均是x86-64位汇编语言,结果实验给的还是第二版配套 ...

  8. [精品]CSAPP Bomb Lab 解题报告(七)——隐藏关卡

    接上篇[精品]CSAPP Bomb Lab 解题报告(六) gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 ...

  9. [精品]CSAPP Bomb Lab 解题报告(六)

    接上篇[精品]CSAPP Bomb Lab 解题报告(五) gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 ...

  10. [精品]CSAPP Bomb Lab 解题报告(五)

    接上篇[精品]CSAPP Bomb Lab 解题报告(四) gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 ...

最新文章

  1. GPT-2大战GPT-3:OpenAI内部的一场终极对决
  2. Xcode下 gdb 调试命令
  3. 计算机编程导论python程序设计答案-学堂在线_计算机科学与Python编程导论_作业课后答案...
  4. 神策数据搬新家,召唤有才新伙伴!
  5. Neo4j【环境部署 01】图形数据库(简介+下载地址+安装+配置+demo源码+学习教程地址)
  6. [C++基础]034_C++模板编程里的主版本模板类、全特化、偏特化(C++ Type Traits)
  7. matlab 传递函数 响应,matlab:知道传递函数G,怎么画出阶跃响应?
  8. python widget_python 图形界面
  9. linux下用c语言写黄金矿工,c语言课程设计黄金矿工(提高篇)
  10. 010 Editor v8.0.1(32 - bit) 算法逆向分析、注册机编写
  11. 详解CAN总线:常用CAN连接器的使用方法
  12. IEC60958/61937协议
  13. LabVIEW心率监测装置
  14. python 解压文件 重名_Python批量重命名压缩文件
  15. spring boot启动报错: The APR based Apache Tomcat Native library which allows optimal performance
  16. 工业互联网:7  项目生命周期管理(1)
  17. mysql复杂查询的书_mysql 复杂查询
  18. 视频人员行为识别(Action Recognition)
  19. Cannot create an instance of class AndroidViewModel (androidx ViewModelProvider AndroidViewModel)
  20. 【软件工程课后习题】

热门文章

  1. 连接计算机和网络传输介质的接口,最常用的网络传输介质和连接设备
  2. Xbrowser远程连接显示灰屏
  3. 视频横竖屏模式切换,如何将多个视频任意转换
  4. 中国第一代程序员潘爱民的程序人生
  5. 1.DingoApi安装和配置
  6. 当软件定义存储(SDS)遇见区块链(BlockChain)
  7. 基于物联网的智能厨房安全监测系统-上位机程序
  8. 【Linux】gvim封装至gvi命令
  9. windows 电脑如何查看电脑显卡内存(显存)、CPU型号、内存、USB集线器等配置信息 win+r dxdiag
  10. 基于Tofino2的64X100GE高性能可编程交换机MX7636-64X