CSAPP 深入理解计算机 Bomb Lab实验纪录

Bomb Lab实验纪录

GDB常用指令

gcc -g test.c生成可除错文件

gdb a.out进行除错

r

程式开始执行

q

离开gdb

list 行数

显示以该行数为中心的前后总共10行程式码list 函数名list 10,20打印10~20行list +打印前进10行list -打印后退10行

p 变数名

打印变数内容

可以直接p 阵列或结构体变数名

p 函数名::某变数

打印该函数下某变数值

p main::i

p test::res

p struct/union

p struct->elements

p foo(*newdata)
可以直接打印函数调用结果
p test.c::res
透过档名打印全域变数
p /x i
已16进制打印i

p i + 3

x addr

记忆体检查x/nfu addrn, f, u为可选参数n表示欲显示记忆体数量,显示的记忆体单元长度由u决定,若n为负数,则从addr向后计数f为显示格式o - octalx - hexadecimald - decimalu - unsigned decimalt - binaryf - floating pointa - addressc - chars - stringi - instructionu为记忆体单元长度b - Bytesh - Halfwords (2 Bytes)w - Words (4 Bytes)g - Giant words (8 Bytes)

set var 变数=特定值

可以在运行中设置变数值(利用断点)

支持多种语法

set var i=10

set var i = i * 10

set var i = $3 + i

set var array = {100,200,300,400,500}

set

set var struct->element = 10

同样规则也是用全域变数

set var main::i=0
指定档案中的变数

info

用来显示各种信息info source显示档案资讯info b检查断点info args查看调用函数参数info r查看暂存器状态

b

设定断点b 函数名b mainb testb 行数b 10删除节点clear 20clear funcd Num

watch

建立监视点,一旦该变数发生变化,gdb就会停下来并显示数值

c

继续执行到下一个断点

s

单步调试s 步数s 10

n

多步调试

finish

跳出函数

bt

back trace查看stack调用消息

frame

当前所处函数

help

查看帮助手册可以指定查找指令help break

layout

layout srcsource file界面layout asm组合语言界面组合语言界面单步为sictrl + x再按a可开关界面

disas 函数名

将该函数转换为组合语言

shell clear

清除画面

实验过程

前言

该实验需要我们找出每个炸弹的解码密码,由于其他source, header files缺失,我们仅能透过可执行档bomb进行GDB调试,并在组合语言层面找出相对应的字串密码。因此在开始前请先安装GDB以及熟悉基本的GDB指令。

Phase 1

   0x0000000000400ee0 <+0>:sub    $0x8,%rsp   0x0000000000400ee4 <+4>:mov    $0x402400,%esi   0x0000000000400ee9 <+9>:callq  0x401338 <strings_not_equal>   0x0000000000400eee <+14>:test   %eax,%eax   0x0000000000400ef0 <+16>:je     0x400ef7 <phase_1+23>   0x0000000000400ef2 <+18>:callq  0x40143a <explode_bomb>   0x0000000000400ef7 <+23>:add    $0x8,%rsp   0x0000000000400efb <+27>:retq 

主要是strings_not_equal这个函式在判断输入字串与密码字串是否相等,是则返回true,否则返回false

strings_not_equal函式主要包含

string_length函式,用来逐一检查字元,判断字串长度strings_not_equal后续就是逐一对字元进行比对,若不同则返回false,直到\0为止可以发现strings_not_equal具有两个参数,一个是输入参数,一个是解答字串,分别为%rdi %rsi,这个解码字串储存位置位于0x402400因此在GDB中直接打印该地址就可以查看该字串x/s 0x402400,输出结果为Border relations with Canada have never been better.

Phase 2

   0x0000000000400efc <+0>:push   %rbp   0x0000000000400efd <+1>:push   %rbx   0x0000000000400efe <+2>:sub    $0x28,%rsp   0x0000000000400f02 <+6>:mov    %rsp,%rsi   0x0000000000400f05 <+9>:callq  0x40145c <read_six_numbers>   0x0000000000400f0a <+14>:cmpl   $0x1,(%rsp)   0x0000000000400f0e <+18>:je     0x400f30 <phase_2+52>   0x0000000000400f10 <+20>:callq  0x40143a <explode_bomb>   0x0000000000400f15 <+25>:jmp    0x400f30 <phase_2+52>   0x0000000000400f17 <+27>:mov    -0x4(%rbx),%eax   0x0000000000400f1a <+30>:add    %eax,%eax   0x0000000000400f1c <+32>:cmp    %eax,(%rbx)   0x0000000000400f1e <+34>:je     0x400f25 <phase_2+41>   0x0000000000400f20 <+36>:callq  0x40143a <explode_bomb>   0x0000000000400f25 <+41>:add    $0x4,%rbx   0x0000000000400f29 <+45>:cmp    %rbp,%rbx   0x0000000000400f2c <+48>:jne    0x400f17 <phase_2+27>   0x0000000000400f2e <+50>:jmp    0x400f3c <phase_2+64>   0x0000000000400f30 <+52>:lea    0x4(%rsp),%rbx   0x0000000000400f35 <+57>:lea    0x18(%rsp),%rbp   0x0000000000400f3a <+62>:jmp    0x400f17 <phase_2+27>   0x0000000000400f3c <+64>:add    $0x28,%rsp   0x0000000000400f40 <+68>:pop    %rbx   0x0000000000400f41 <+69>:pop    %rbp   0x0000000000400f42 <+70>:retq 

phase 2主要观察%rsp的变化(stack指标),并观察存放在中的参数变化

首先判断输入参数个数,从程式码执中可以判断phase_2函数调用了名为read_six_number的函数,因此可以判断出参数数量为6。实际进入函数体可以发现该函数又调用了sscanf(),并且返回值小于等于5就会引爆炸弹,由此可知我们的假设正确紧接着我们查看位于栈顶的局部变数(由于输入参数不大于6因此栈顶没有储存多余参数)。从cmpl $0x1,(%rsp)这条语句推测出输入值有可能为1开头或1结尾(可能栈顶存放顺序不同),这个问题可以直接打印记忆体存放数据来解决。假如我输入的数据为1 2 3 4 5 6 经过指令x/6uw 0x7fffffffdf80输出后结果为1 2 3 4 5 6,这就可以判断出栈顶存放的是输入的元素1,即顺序是随栈地址增长从后续代码其实就是一个类似for loop的操作,每次判断当前元素是否为前一个元素的两倍,直到指标指向%rsp+0x18为止(相当于遍历完6个整数后的下一个地址),将其转换成C大致就是:
/* 假设6个变数存放在arr中 */if(arr[0] != 1){    // bomb!}for(int i=1; i<6; i++){    if(arr[i] != arr[i-1]*2){        // bomb!    }}// 解码成功!
总结以上逻辑,输入参数其实就是1 2 4 8 16 32

Phase 3

   0x0000000000400f43 <+0>:sub    $0x18,%rsp   0x0000000000400f47 <+4>:lea    0xc(%rsp),%rcx   0x0000000000400f4c <+9>:lea    0x8(%rsp),%rdx   0x0000000000400f51 <+14>:mov    $0x4025cf,%esi   0x0000000000400f56 <+19>:mov    $0x0,%eax   0x0000000000400f5b <+24>:callq  0x400bf0 <__isoc99_sscanf@plt>   0x0000000000400f60 <+29>:cmp    $0x1,%eax   0x0000000000400f63 <+32>:jg     0x400f6a <phase_3+39>   0x0000000000400f65 <+34>:callq  0x40143a <explode_bomb>   0x0000000000400f6a <+39>:cmpl   $0x7,0x8(%rsp)   0x0000000000400f6f <+44>:ja     0x400fad <phase_3+106>   0x0000000000400f71 <+46>:mov    0x8(%rsp),%eax   0x0000000000400f75 <+50>:jmpq   *0x402470(,%rax,8)   0x0000000000400f7c <+57>:mov    $0xcf,%eax   0x0000000000400f81 <+62>:jmp    0x400fbe <phase_3+123>   0x0000000000400f83 <+64>:mov    $0x2c3,%eax   0x0000000000400f88 <+69>:jmp    0x400fbe <phase_3+123>   0x0000000000400f8a <+71>:mov    $0x100,%eax   0x0000000000400f8f <+76>:jmp    0x400fbe <phase_3+123>   0x0000000000400f91 <+78>:mov    $0x185,%eax   0x0000000000400f96 <+83>:jmp    0x400fbe <phase_3+123>   0x0000000000400f98 <+85>:mov    $0xce,%eax   0x0000000000400f9d <+90>:jmp    0x400fbe <phase_3+123>   0x0000000000400f9f <+92>:mov    $0x2aa,%eax   0x0000000000400fa4 <+97>:jmp    0x400fbe <phase_3+123>   0x0000000000400fa6 <+99>:mov    $0x147,%eax   0x0000000000400fab <+104>:jmp    0x400fbe <phase_3+123>   0x0000000000400fad <+106>:callq  0x40143a <explode_bomb>   0x0000000000400fb2 <+111>:mov    $0x0,%eax   0x0000000000400fb7 <+116>:jmp    0x400fbe <phase_3+123>   0x0000000000400fb9 <+118>:mov    $0x137,%eax   0x0000000000400fbe <+123>:cmp    0xc(%rsp),%eax   0x0000000000400fc2 <+127>:je     0x400fc9 <phase_3+134>   0x0000000000400fc4 <+129>:callq  0x40143a <explode_bomb>   0x0000000000400fc9 <+134>:add    $0x18,%rsp   0x0000000000400fcd <+138>:retq   

我们粗看一遍phase 3的程式码,可以看到leasscanf()这两个关键指令,可以推测出它要求我们输入2个参数(%rcx%rdx这两个暂存器存放输入的局部变数)。为了验证假设,我们打印输出x/s $0x4025cf可以得到"%d %d"以此证实假设正确

得知输入参数个数后,透过info r获取当前%rsp位置0x7fffffffdf10,并打印出%rsp加上0x8以及0xc的值

x/u 0x7fffffffdf18→ 输入参数1

x/u 0x7fffffffdf1c→ 输入参数2

cmpl $0x7,0x8(%rsp)jg 0x400f6a <phase_3+39> 这两条语句可以发现,若参数1大于7则会直接引爆炸弹,所以参输1必须小于等于7(输入负数会转换成unsigned)

jmpq *0x402470(,%rax,8)这条语句非常关键,它的作用类似C的switch逻辑语句,它会依照输入参数1的值去作条跳转(搜寻jump table),跳转地址为记忆体位置8 * %rax + 0x402470储存的值,我们可以用x/x来查看。可以依照switch中p1,p2来组合解答,例如1 331就是一组正确的解。

/* 假设p1为参数1;p2为参数2 */unsigned res; // %raxswitch(p1){    case 0:        res = 0xcf; // 207        break;    case 1:        res = 0x137; // 331        break;    case 2:        res = 0x2c3; // 707        break;    case 3:        res = 0x100; // 256        break;    case 4:        res = 0x185; // 389        break;    case 5:        res = 0xce; // 206        break;    case 6:        res = 0x2aa; // 682        break;    case 7:        res = 0x147; // 327        break;}return res == p2; // 参数2的值必须要符合res的对应值

Phase 4

   0x000000000040100c <+0>:sub    $0x18,%rsp   0x0000000000401010 <+4>:lea    0xc(%rsp),%rcx   0x0000000000401015 <+9>:lea    0x8(%rsp),%rdx   0x000000000040101a <+14>:mov    $0x4025cf,%esi   0x000000000040101f <+19>:mov    $0x0,%eax   0x0000000000401024 <+24>:callq  0x400bf0 <__isoc99_sscanf@plt>   0x0000000000401029 <+29>:cmp    $0x2,%eax   0x000000000040102c <+32>:jne    0x401035 <phase_4+41>   0x000000000040102e <+34>:cmpl   $0xe,0x8(%rsp)   0x0000000000401033 <+39>:jbe    0x40103a <phase_4+46>   0x0000000000401035 <+41>:callq  0x40143a <explode_bomb>   0x000000000040103a <+46>:mov    $0xe,%edx   0x000000000040103f <+51>:mov    $0x0,%esi   0x0000000000401044 <+56>:mov    0x8(%rsp),%edi   0x0000000000401048 <+60>:callq  0x400fce <func4>   0x000000000040104d <+65>:test   %eax,%eax   0x000000000040104f <+67>:jne    0x401058 <phase_4+76>   0x0000000000401051 <+69>:cmpl   $0x0,0xc(%rsp)   0x0000000000401056 <+74>:je     0x40105d <phase_4+81>   0x0000000000401058 <+76>:callq  0x40143a <explode_bomb>   0x000000000040105d <+81>:add    $0x18,%rsp   0x0000000000401061 <+85>:retq 

func4

   0x0000000000400fce <+0>:sub    $0x8,%rsp   0x0000000000400fd2 <+4>:mov    %edx,%eax   0x0000000000400fd4 <+6>:sub    %esi,%eax   0x0000000000400fd6 <+8>:mov    %eax,%ecx   0x0000000000400fd8 <+10>:shr    $0x1f,%ecx   0x0000000000400fdb <+13>:add    %ecx,%eax   0x0000000000400fdd <+15>:sar    %eax   0x0000000000400fdf <+17>:lea    (%rax,%rsi,1),%ecx   0x0000000000400fe2 <+20>:cmp    %edi,%ecx   0x0000000000400fe4 <+22>:jle    0x400ff2 <func4+36>   0x0000000000400fe6 <+24>:lea    -0x1(%rcx),%edx   0x0000000000400fe9 <+27>:callq  0x400fce <func4>   0x0000000000400fee <+32>:add    %eax,%eax   0x0000000000400ff0 <+34>:jmp    0x401007 <func4+57>   0x0000000000400ff2 <+36>:mov    $0x0,%eax   0x0000000000400ff7 <+41>:cmp    %edi,%ecx   0x0000000000400ff9 <+43>:jge    0x401007 <func4+57>   0x0000000000400ffb <+45>:lea    0x1(%rcx),%esi   0x0000000000400ffe <+48>:callq  0x400fce <func4>   0x0000000000401003 <+53>:lea    0x1(%rax,%rax,1),%eax   0x0000000000401007 <+57>:add    $0x8,%rsp   0x000000000040100b <+61>:retq  

炸弹2需要使用者输入2个参数,第一个参数的关键在于整个程式流程在于func4函式,这是一个递迴函式。第二个参数相对简单,只要输入0就可以通过,所以我们着重分析参数1的判断

首先先界定範围cmpl $0xe,0x8(%rsp)这条语句表示输入参数须小于等于0xe(14)否则引爆炸弹完成第一步后,将%edx设为0xe,将%esi设为0x0后进入func4func4为一个递迴函式,它会将使用者的输入参数与%rdx%rsi的计算结果进行比较,从而不断自我调用,将其逻辑转换成C:
unsigned func4(unsigned input, unsigned* rdx, unsigned* rsi){    unsigned rax = ((*rdx - *rsi) + ((*rdx - *rsi) >> 31)) / 2;    unsigned rcx = rax + *rsi;        if(rcx < input){        *rsi = rcx + 1;        func4(input, rdx, rsi);        return 2*rax + 1;    }else if(rcx > input){        *rdx = rcx - 1;        func4(input, rdx, rsi);        return 2*rax;    }else{        return 0;    }}
其实我们从phase_4函式中可以发现,当%rax等于0时炸弹就不会引爆,所以我们在解这题时要把注意力放在如何使%rax等于0。由于初始调用的%rdx%rsi分别为14以及0,所以我们只需要使输入input等于rcx就可以了,由这个计算结果可以得到第一个解7 0rcx的值小于或大于input时就会进入递迴环节,从上面的C代码可以看出,无论如何我们都不希望rcx小于input,因为不管rax为何,返回值始终不为0。因此在撇除rcxinput相等的状况下(7),剩余的选项就是0~6所以我们希望递迴到底都是从rcx > imput这个代码区块出发,也就是说每次都调用func4(input, rcx-1, rsi)。而经过每次调用其rcx分别会是3,1,0经果整个逻辑推敲参数1的可能值分别为0, 1, 3, 7

Phase 5

   0x0000000000401062 <+0>:push   %rbx   0x0000000000401063 <+1>:sub    $0x20,%rsp   0x0000000000401067 <+5>:mov    %rdi,%rbx   0x000000000040106a <+8>:mov    %fs:0x28,%rax   0x0000000000401073 <+17>:mov    %rax,0x18(%rsp)   0x0000000000401078 <+22>:xor    %eax,%eax   0x000000000040107a <+24>:callq  0x40131b <string_length>   0x000000000040107f <+29>:cmp    $0x6,%eax   0x0000000000401082 <+32>:je     0x4010d2 <phase_5+112>   0x0000000000401084 <+34>:callq  0x40143a <explode_bomb>   0x0000000000401089 <+39>:jmp    0x4010d2 <phase_5+112>   0x000000000040108b <+41>:movzbl (%rbx,%rax,1),%ecx   0x000000000040108f <+45>:mov    %cl,(%rsp)   0x0000000000401092 <+48>:mov    (%rsp),%rdx   0x0000000000401096 <+52>:and    $0xf,%edx   0x0000000000401099 <+55>:movzbl 0x4024b0(%rdx),%edx   0x00000000004010a0 <+62>:mov    %dl,0x10(%rsp,%rax,1)   0x00000000004010a4 <+66>:add    $0x1,%rax   0x00000000004010a8 <+70>:cmp    $0x6,%rax   0x00000000004010ac <+74>:jne    0x40108b <phase_5+41>   0x00000000004010ae <+76>:movb   $0x0,0x16(%rsp)   0x00000000004010b3 <+81>:mov    $0x40245e,%esi   0x00000000004010b8 <+86>:lea    0x10(%rsp),%rdi   0x00000000004010bd <+91>:callq  0x401338 <strings_not_equal>   0x00000000004010c2 <+96>:test   %eax,%eax   0x00000000004010c4 <+98>:je     0x4010d9 <phase_5+119>   0x00000000004010c6 <+100>:callq  0x40143a <explode_bomb>   0x00000000004010cb <+105>:nopl   0x0(%rax,%rax,1)   0x00000000004010d0 <+110>:jmp    0x4010d9 <phase_5+119>   0x00000000004010d2 <+112>:mov    $0x0,%eax   0x00000000004010d7 <+117>:jmp    0x40108b <phase_5+41>   0x00000000004010d9 <+119>:mov    0x18(%rsp),%rax   0x00000000004010de <+124>:xor    %fs:0x28,%rax   0x00000000004010e7 <+133>:je     0x4010ee <phase_5+140>   0x00000000004010e9 <+135>:callq  0x400b30 <__stack_chk_fail@plt>   0x00000000004010ee <+140>:add    $0x20,%rsp   0x00000000004010f2 <+144>:pop    %rbx   0x00000000004010f3 <+145>:retq 
从调用string_length()cmp $0x6,%eax的比较指令可以判定输入的为字串类型,且长度应当为6接着就是这段程式码的主要逻辑,透过movzbl (%rbx,%rax,1),%ecx这条语句,每次将字串上的字元存放到%ecx暂存器上,然后%rax加1然后将当前取出的字元进行AND运算,也就是and $0xf,%edx语句,然后将%edx加上0x4024b0,并将这个地址存放的字元储存到栈上由此可知这一连串的地址将会存放16个字元信息,我们透过查看记忆体x/16c 0x4024b0可以打印出与0xf AND运算结果对应字元0m1a2d3u4i5e6r7s8n9fAoBtCvDbEyFl然后在调用string_not_equal()之前可以发现mov $0x40245e,%esi更新%rsi语句,推测就是目标字串,
透过x/s 0x40245e打印出字串flyers,因此我们需要对应上表中的映射关係,组合出flyers这个单字在终端机中输入man ascii查看字元对应的hex form,然后组合出解答,例如ionefgIONEFG

Phase 6

   0x00000000004010f4 <+0>:push   %r14   0x00000000004010f6 <+2>:push   %r13   0x00000000004010f8 <+4>:push   %r12   0x00000000004010fa <+6>:push   %rbp   0x00000000004010fb <+7>:push   %rbx   0x00000000004010fc <+8>:sub    $0x50,%rsp   0x0000000000401100 <+12>:mov    %rsp,%r13   0x0000000000401103 <+15>:mov    %rsp,%rsi   0x0000000000401106 <+18>:callq  0x40145c <read_six_numbers>   0x000000000040110b <+23>:mov    %rsp,%r14   0x000000000040110e <+26>:mov    $0x0,%r12d   0x0000000000401114 <+32>:mov    %r13,%rbp   0x0000000000401117 <+35>:mov    0x0(%r13),%eax   0x000000000040111b <+39>:sub    $0x1,%eax   0x000000000040111e <+42>:cmp    $0x5,%eax   0x0000000000401121 <+45>:jbe    0x401128 <phase_6+52>   0x0000000000401123 <+47>:callq  0x40143a <explode_bomb>   0x0000000000401128 <+52>:add    $0x1,%r12d   0x000000000040112c <+56>:cmp    $0x6,%r12d   0x0000000000401130 <+60>:je     0x401153 <phase_6+95>   0x0000000000401132 <+62>:mov    %r12d,%ebx   0x0000000000401135 <+65>:movslq %ebx,%rax   0x0000000000401138 <+68>:mov    (%rsp,%rax,4),%eax   0x000000000040113b <+71>:cmp    %eax,0x0(%rbp)    0x000000000040113e <+74>:jne    0x401145 <phase_6+81>   0x0000000000401140 <+76>:callq  0x40143a <explode_bomb>   0x0000000000401145 <+81>:add    $0x1,%ebx   0x0000000000401148 <+84>:cmp    $0x5,%ebx   0x000000000040114b <+87>:jle    0x401135 <phase_6+65>   0x000000000040114d <+89>:add    $0x4,%r13   0x0000000000401151 <+93>:jmp    0x401114 <phase_6+32>   0x0000000000401153 <+95>:lea    0x18(%rsp),%rsi   0x0000000000401158 <+100>:mov    %r14,%rax   0x000000000040115b <+103>:mov    $0x7,%ecx   0x0000000000401160 <+108>:mov    %ecx,%edx   0x0000000000401162 <+110>:sub    (%rax),%edx   0x0000000000401164 <+112>:mov    %edx,(%rax)   0x0000000000401166 <+114>:add    $0x4,%rax   0x000000000040116a <+118>:cmp    %rsi,%rax   0x000000000040116d <+121>:jne    0x401160 <phase_6+108>   0x000000000040116f <+123>:mov    $0x0,%esi   0x0000000000401174 <+128>:jmp    0x401197 <phase_6+163>   0x0000000000401176 <+130>:mov    0x8(%rdx),%rdx   0x000000000040117a <+134>:add    $0x1,%eax   0x000000000040117d <+137>:cmp    %ecx,%eax   0x000000000040117f <+139>:jne    0x401176 <phase_6+130>   0x0000000000401181 <+141>:jmp    0x401188 <phase_6+148>   0x0000000000401183 <+143>:mov    $0x6032d0,%edx   0x0000000000401188 <+148>:mov    %rdx,0x20(%rsp,%rsi,2)   0x000000000040118d <+153>:add    $0x4,%rsi

phase_6其实是一个重新组合链状表的程式,我把整个组与转换成C比较好懂。这题的逻辑就是依照输入的6个参数(均小于6),透过使用7去减去,然后将计算完的6个值去寻找对应的节点(node),然后依序将它们连接起来

最终成功与否,是依据整个链表中各节点存储值从头到尾是一个递减规律

#include <stdio.h>#define LEN 6typedef int six_arr[LEN];typedef struct Node{    int val;    int index;    struct Node* next;}Node;Node node[LEN] = {{332,1,NULL},{168,2,NULL},                {924,3,NULL},{691,4,NULL},{477,5,NULL},{433,6,NULL}};/**  * 初始化: node(1) --> node(2) --> node(3) --> node(4) --> node(5) --> node(6) --> NULL */void node_init(){    for(int i=0; i<5; i++){        node[i].next = &(node[i+1]);    }    node[5].next = NULL;}void phase_6(six_arr arr){        /* 参数需要小于6 */    for(int i=0; i<LEN; i++){        if(arr[i] >= LEN){            // bomb!        }        /* 参数必须是独一无二 */        for(int j=0; j<LEN; j++){            if(arr[i] == arr[j]){                // bomb!            }        }    }        /* 更新输入参数 */    for(int i=0; i<6; i++){        arr[i] = 7-arr[i];    }        /* 重新组装链状表 */    for(int i=0; i<5; i++){        node[arr[i]].next = &(node[arr[i+1]]);    }    node[arr[5]].next = NULL;        /* 确保链状表数值为递减 */    for(int i=0; i<5; i++){        if(node[arr[i]].val < node[arr[i+1]].val){            // bomb!        }    }    }

从组语可以推测出节点的记忆体位置在0x6032d0,透过(gdb) x/24xw 0x6032d0我们将所有节点的信息打印来(小端机器)

0x6032d0 <node1>:       0x0000014c      0x00000001      0x006032e0      0x000000000x6032e0 <node2>:       0x000000a8      0x00000002      0x006032f0      0x000000000x6032f0 <node3>:       0x0000039c      0x00000003      0x00603300      0x000000000x603300 <node4>:       0x000002b3      0x00000004      0x00603310      0x000000000x603310 <node5>:       0x000001dd      0x00000005      0x00603320      0x000000000x603320 <node6>:       0x000001bb      0x00000006      0x00000000      0x00000000

因此每个节点对应的val

node6.val = 433

node5.val = 477

node4.val = 691

node3.val = 924

node2.val = 168

node1.val = 332

依照大到小排列后结果为node3 --> node4 --> node5 --> node6 --> node1 --> node2 --> null,所以反推输入应当为4 3 2 1 6 5(考虑减去7后的结果)

Secret Phase

当我们解完第6个炸弹后会看到下面这段注解,貌似还有什么为完成

    /* Wow, they got it!  But isn't something... missing?  Perhaps     * something they overlooked?  Mua ha ha ha ha! */

其实隐层彩蛋就躲在phase_defused()里头,当输入数据等于6行时就会触发。但是要进入secret_phase还有令一个条件,我们需要将phase_3的输出改为3个,最后一个为字串类型

   0x00000000004015c4 <+0>:sub    $0x78,%rsp   0x00000000004015c8 <+4>:mov    %fs:0x28,%rax   0x00000000004015d1 <+13>:mov    %rax,0x68(%rsp)   0x00000000004015d6 <+18>:xor    %eax,%eax   0x00000000004015d8 <+20>:cmpl   $0x6,0x202181(%rip)        # 0x603760 <num_input_strings> phase 6后自动触发   0x00000000004015df <+27>:jne    0x40163f <phase_defused+123>   0x00000000004015e1 <+29>:lea    0x10(%rsp),%r8   0x00000000004015e6 <+34>:lea    0xc(%rsp),%rcx   0x00000000004015eb <+39>:lea    0x8(%rsp),%rdx   0x00000000004015f0 <+44>:mov    $0x402619,%esi # "%d %d %s"   0x00000000004015f5 <+49>:mov    $0x603870,%edi # phase4 input   0x00000000004015fa <+54>:callq  0x400bf0 <__isoc99_sscanf@plt>   0x00000000004015ff <+59>:cmp    $0x3,%eax   0x0000000000401602 <+62>:jne    0x401635 <phase_defused+113>   0x0000000000401604 <+64>:mov    $0x402622,%esi # "DrEvil"   0x0000000000401609 <+69>:lea    0x10(%rsp),%rdi   0x000000000040160e <+74>:callq  0x401338 <strings_not_equal> # compare strings   0x0000000000401613 <+79>:test   %eax,%eax   0x0000000000401615 <+81>:jne    0x401635 <phase_defused+113>   0x0000000000401617 <+83>:mov    $0x4024f8,%edi   0x000000000040161c <+88>:callq  0x400b10 <puts@plt>   0x0000000000401621 <+93>:mov    $0x402520,%edi   0x0000000000401626 <+98>:callq  0x400b10 <puts@plt>   0x000000000040162b <+103>:mov    $0x0,%eax   0x0000000000401630 <+108>:callq  0x401242 <secret_phase> # 进入secret_phase   0x0000000000401635 <+113>:mov    $0x402558,%edi   0x000000000040163a <+118>:callq  0x400b10 <puts@plt>   0x000000000040163f <+123>:mov    0x68(%rsp),%rax   0x0000000000401644 <+128>:xor    %fs:0x28,%rax   0x000000000040164d <+137>:je     0x401654 <phase_defused+144>   0x000000000040164f <+139>:callq  0x400b30 <__stack_chk_fail@plt>   0x0000000000401654 <+144>:add    $0x78,%rsp   0x0000000000401658 <+148>:retq

secret_phase

   0x0000000000401242 <+0>:push   %rbx   0x0000000000401243 <+1>:callq  0x40149e <read_line>   0x0000000000401248 <+6>:mov    $0xa,%edx   0x000000000040124d <+11>:mov    $0x0,%esi   0x0000000000401252 <+16>:mov    %rax,%rdi # 输入字串   0x0000000000401255 <+19>:callq  0x400bd0 <strtol@plt> # strtol()   0x000000000040125a <+24>:mov    %rax,%rbx # 返回字串中转换成整数的数字   0x000000000040125d <+27>:lea    -0x1(%rax),%eax   0x0000000000401260 <+30>:cmp    $0x3e8,%eax # 输入输自需要小于等于1001   0x0000000000401265 <+35>:jbe    0x40126c <secret_phase+42>   0x0000000000401267 <+37>:callq  0x40143a <explode_bomb>   0x000000000040126c <+42>:mov    %ebx,%esi # 输入数字   0x000000000040126e <+44>:mov    $0x6030f0,%edi # 根节点   0x0000000000401273 <+49>:callq  0x401204 <fun7> # 进入func7   0x0000000000401278 <+54>:cmp    $0x2,%eax   0x000000000040127b <+57>:je     0x401282 <secret_phase+64>   0x000000000040127d <+59>:callq  0x40143a <explode_bomb>   0x0000000000401282 <+64>:mov    $0x402438,%edi   0x0000000000401287 <+69>:callq  0x400b10 <puts@plt>   0x000000000040128c <+74>:callq  0x4015c4 <phase_defused>   0x0000000000401291 <+79>:pop    %rbx   0x0000000000401292 <+80>:retq  

func7

   0x0000000000401204 <+0>:sub    $0x8,%rsp   0x0000000000401208 <+4>:test   %rdi,%rdi # 节点是否为NULL   0x000000000040120b <+7>:je     0x401238 <fun7+52>   0x000000000040120d <+9>:mov    (%rdi),%edx   0x000000000040120f <+11>:cmp    %esi,%edx # 比较跟节点value与输入整数大小   0x0000000000401211 <+13>:jle    0x401220 <fun7+28>   0x0000000000401213 <+15>:mov    0x8(%rdi),%rdi # 若跟节点较大,则调用左子节点   0x0000000000401217 <+19>:callq  0x401204 <fun7>   0x000000000040121c <+24>:add    %eax,%eax # 完成调用后返回2*rax   0x000000000040121e <+26>:jmp    0x40123d <fun7+57>   0x0000000000401220 <+28>:mov    $0x0,%eax   0x0000000000401225 <+33>:cmp    %esi,%edx # 如果相等则返回0   0x0000000000401227 <+35>:je     0x40123d <fun7+57>   0x0000000000401229 <+37>:mov    0x10(%rdi),%rdi # 若根节点较小,则调用右子节点   0x000000000040122d <+41>:callq  0x401204 <fun7>   0x0000000000401232 <+46>:lea    0x1(%rax,%rax,1),%eax # 完成调用后返回2*rax+1   0x0000000000401236 <+50>:jmp    0x40123d <fun7+57>   0x0000000000401238 <+52>:mov    $0xffffffff,%eax   0x000000000040123d <+57>:add    $0x8,%rsp   0x0000000000401241 <+61>:retq 

如果将fun7()写成C,大致上可以表示成以下,其实它就是一个binary search tree

typedef struct{    unsigned val;    Node* left;    Node* right;}Node;unsigned fun7(Node* n, int val){    if(n == NULL){   reutrn 0xffffffff;         }        if(n->val == val){        return 0;    }else if(n->val < val){        return 2*(fun7(n->right, val))+1;    }else{        return 2*fun7(n->left, val);    }    }

fun7返回数值后的这条语句cmp $0x2,%eax可以判断,无论如何一定要使返回值等于2,简单的看一下C code可以推断出一个结论: 为了使结果为2必须各调用向左与向右遍历一次,顺序是先向左再向右 → 2*(2*0+1) = 2

我们知道逻辑结构后,需要整棵binary tree储存的数值来决定输入,透过简单的x/3xg 0x6030f0指令可以查看单个节点的所有讯息,x/60xg 0x6030f0 来查看到底有多少节点

0x6030f0 <n1>:0x00000000000000240x00000000006031100x603100 <n1+16>:0x00000000006031300x00000000000000000x603110 <n21>:0x00000000000000080x00000000006031900x603120 <n21+16>:0x00000000006031500x00000000000000000x603130 <n22>:0x00000000000000320x00000000006031700x603140 <n22+16>:0x00000000006031b00x00000000000000000x603150 <n32>:0x00000000000000160x00000000006032700x603160 <n32+16>:0x00000000006032300x00000000000000000x603170 <n33>:0x000000000000002d0x00000000006031d00x603180 <n33+16>:0x00000000006032900x00000000000000000x603190 <n31>:0x00000000000000060x00000000006031f00x6031a0 <n31+16>:0x00000000006032500x00000000000000000x6031b0 <n34>:0x000000000000006b0x00000000006032100x6031c0 <n34+16>:0x00000000006032b00x00000000000000000x6031d0 <n45>:0x00000000000000280x00000000000000000x6031e0 <n45+16>:0x00000000000000000x00000000000000000x6031f0 <n41>:0x00000000000000010x00000000000000000x603200 <n41+16>:0x00000000000000000x00000000000000000x603210 <n47>:0x00000000000000630x00000000000000000x603220 <n47+16>:0x00000000000000000x00000000000000000x603230 <n44>:0x00000000000000230x00000000000000000x603240 <n44+16>:0x00000000000000000x00000000000000000x603250 <n42>:0x00000000000000070x00000000000000000x603260 <n42+16>:0x00000000000000000x00000000000000000x603270 <n43>:0x00000000000000140x00000000000000000x603280 <n43+16>:0x00000000000000000x00000000000000000x603290 <n46>:0x000000000000002f0x00000000000000000x6032a0 <n46+16>:0x00000000000000000x00000000000000000x6032b0 <n48>:0x00000000000003e90x00000000000000000x6032c0 <n48+16>:0x00000000000000000x0000000000000000

透过这段资料我们重新整理一下整个binary tree:

顺着这个逻辑我们可以找到22就是这题的解

资源

https://github.com/WeiLin66/CMU-15-213/tree/main/Labs/bomb-lab


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章