Bomb Lab实验纪录
GDB常用指令
gcc -g test.c
生成可除错文件
gdb a.out
进行除错r
程式开始执行q
离开gdblist 行数
显示以该行数为中心的前后总共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进制打印ip i + 3
x addr
x/nfu addr
n, 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 main
b test
b 行数b 10
删除节点clear 20clear funcd Numwatch
建立监视点,一旦该变数发生变化,gdb就会停下来并显示数值c
继续执行到下一个断点s
单步调试s 步数s 10n
多步调试finish
跳出函数bt
back trace查看stack调用消息frame
当前所处函数help
查看帮助手册可以指定查找指令help breaklayout
layout srcsource file界面layout asm组合语言界面组合语言界面单步为si
ctrl + 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
的程式码,可以看到lea
,sscanf()
这两个关键指令,可以推测出它要求我们输入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
后进入func4
func4
为一个递迴函式,它会将使用者的输入参数与%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 0
当rcx
的值小于或大于input
时就会进入递迴环节,从上面的C代码可以看出,无论如何我们都不希望rcx
小于input
,因为不管rax
为何,返回值始终不为0。因此在撇除rcx
与input
相等的状况下(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
可以打印出string_not_equal()
之前可以发现mov $0x40245e,%esi
更新%rsi
语句,推测就是目标字串,透过
x/s 0x40245e
打印出字串flyers
,因此我们需要对应上表中的映射关係,组合出flyers
这个单字在终端机中输入man ascii
查看字元对应的hex form,然后组合出解答,例如ionefg
或IONEFG
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