当前位置: 首页 > news >正文

CSAPP实验2:Bomb

介绍

(貌似有好多Bomb版本,这个版本是北理工特供版)
本实验要求你使用课程所学知识拆除“binary bombs(二进制炸弹,下文将简称为炸弹)”,增强对程序的机器级表示、汇编语言、调试器和逆向工程等方面原理与技能的掌握。 这里的炸弹是一个Linux可执行程序,包含了6个阶段(或层次、关卡)。炸弹运行的每个阶段要求你输入一个特定字符串,你的输入符合程序预期的输入,该阶段的炸弹就被拆除引信即解除了,否则炸弹“爆炸”打印输出 “BOOM!!!”。实验的目标是拆除尽可能多的炸弹关卡。

每个炸弹阶段考察了机器级程序语言的一个不同方面,难度逐级递增:

  • 阶段1:字符串比较
  • 阶段2:循环
  • 阶段3:条件/分支
  • 阶段4:递归调用和栈
  • 阶段5:指针
  • 阶段6:链表/指针/结构

另外还有一个隐藏阶段,只有当你在第4阶段的解后附加一特定字符串后才会出现。

为完成二进制炸弹拆除任务,你需要使用gdb调试器和objdump来反汇编炸弹的可执行文件并跟踪调试每一阶段的机器代码,从中理解每一汇编语言代码的行为或作用,进而设法推断拆除炸弹所需的目标字符串。比如在每一阶段的开始代码前和引爆炸弹的函数前设置断点。

phase 1

phase_1函数汇编代码:

通过objdump -d bomb进行反汇编,查看汇编代码,其中的phase_1函数部分如下

0000000000400e6d <phase_1>:400e6d:	48 83 ec 08          	sub    $0x8,%rsp400e71:	be d0 23 40 00       	mov    $0x4023d0,%esi400e76:	e8 cf 04 00 00       	call   40134a <strings_not_equal>400e7b:	85 c0                	test   %eax,%eax400e7d:	75 05                	jne    400e84 <phase_1+0x17>400e7f:	48 83 c4 08          	add    $0x8,%rsp400e83:	c3                   	ret400e84:	e8 be 05 00 00       	call   401447 <explode_bomb>400e89:	eb f4                	jmp    400e7f <phase_1+0x12>

在gdb调试时给break phase_1打上断点,并查看disas phase_1

(gdb) disas phase_1
Dump of assembler code for function phase_1:
=> 0x0000000000400e6d <+0>:     sub    $0x8,%rsp0x0000000000400e71 <+4>:     mov    $0x4023d0,%esi0x0000000000400e76 <+9>:     call   0x40134a <strings_not_equal>0x0000000000400e7b <+14>:    test   %eax,%eax0x0000000000400e7d <+16>:    jne    0x400e84 <phase_1+23>0x0000000000400e7f <+18>:    add    $0x8,%rsp0x0000000000400e83 <+22>:    ret0x0000000000400e84 <+23>:    call   0x401447 <explode_bomb>0x0000000000400e89 <+28>:    jmp    0x400e7f <phase_1+18>

第二行有一个移动数据的操作 mov ,所以查看这个esi的值:

(gdb) print(char*)0x4023d0
$1 = 0x4023d0 "Slave, thou hast slain me. Villain, take my purse."

得到答案"Slave, thou hast slain me. Villain, take my purse."

把答案写入 secret.txt的第一行,运行 run secret.txt ,看到“Phase 1 defused. How about the next one?”说明成功完成了第一道题。

image-20251026205626214

phase 2

phase_2函数汇编代码:

(gdb) disas phase_2
Dump of assembler code for function phase_2:
=> 0x0000000000400e8b <+0>:     push   %rbx0x0000000000400e8c <+1>:     sub    $0x20,%rsp0x0000000000400e90 <+5>:     mov    %fs:0x28,%rax0x0000000000400e99 <+14>:    mov    %rax,0x18(%rsp)0x0000000000400e9e <+19>:    xor    %eax,%eax0x0000000000400ea0 <+21>:    mov    %rsp,%rsi0x0000000000400ea3 <+24>:    call   0x401469 <read_six_numbers>0x0000000000400ea8 <+29>:    cmpl   $0x0,(%rsp)0x0000000000400eac <+33>:    js     0x400eb5 <phase_2+42>0x0000000000400eae <+35>:    mov    $0x1,%ebx0x0000000000400eb3 <+40>:    jmp    0x400ec6 <phase_2+59>0x0000000000400eb5 <+42>:    call   0x401447 <explode_bomb>0x0000000000400eba <+47>:    jmp    0x400eae <phase_2+35>0x0000000000400ebc <+49>:    add    $0x1,%rbx0x0000000000400ec0 <+53>:    cmp    $0x6,%rbx0x0000000000400ec4 <+57>:    je     0x400ed8 <phase_2+77>0x0000000000400ec6 <+59>:    mov    %ebx,%eax0x0000000000400ec8 <+61>:    add    -0x4(%rsp,%rbx,4),%eax0x0000000000400ecc <+65>:    cmp    %eax,(%rsp,%rbx,4)0x0000000000400ecf <+68>:    je     0x400ebc <phase_2+49>

简单观察这段代码,注意到"phase_2"函数还引用了一个名为"read_six_numbers"的函数,猜测与读取我们的输入有关,于是断点查看该函数的汇编代码:

(gdb) disas read_six_numbers
Dump of assembler code for function read_six_numbers:0x0000000000401469 <+0>:     sub    $0x8,%rsp0x000000000040146d <+4>:     mov    %rsi,%rdx0x0000000000401470 <+7>:     lea    0x4(%rsi),%rcx0x0000000000401474 <+11>:    lea    0x14(%rsi),%rax0x0000000000401478 <+15>:    push   %rax0x0000000000401479 <+16>:    lea    0x10(%rsi),%rax0x000000000040147d <+20>:    push   %rax0x000000000040147e <+21>:    lea    0xc(%rsi),%r90x0000000000401482 <+25>:    lea    0x8(%rsi),%r80x0000000000401486 <+29>:    mov    $0x4025c3,%esi0x000000000040148b <+34>:    mov    $0x0,%eax0x0000000000401490 <+39>:    call   0x400ba0 <__isoc99_sscanf@plt>0x0000000000401495 <+44>:    add    $0x10,%rsp0x0000000000401499 <+48>:    cmp    $0x5,%eax0x000000000040149c <+51>:    jle    0x4014a3 <read_six_numbers+58>0x000000000040149e <+53>:    add    $0x8,%rsp0x00000000004014a2 <+57>:    ret0x00000000004014a3 <+58>:    call   0x401447 <explode_bomb>

果然这段代码有对输入的判断,甚至还会调用"explode_bomb"引爆炸弹。并且在401486行看到一个比较明显的地址0x4025c3,读出其内容为:

(gdb) x/s 0x4025c3
0x4025c3:       "%d %d %d %d %d %d"

看到这就知道函数"read_six_numbers"的任务是读取6个整数,那么我们的输入也应该是6个整数.

那么回到函数"phase_2",程序会对输入的第一个值进行判断,若其小于0,则炸弹直接爆炸;之后从400eae行开始进入循环,寄存器ebx存储的值即是循环的计数器,取值从1到6并在取值达到6时退出循环;这个题解题的关键在于400ec6-400ecc三行汇编。因为ebx是循环的计数器,在下面以i代指他的值

  # 将ebx的值赋给eax, 也就是说eax这里的值即为i400ec6:	89 d8                	mov    %ebx,%eax# eax的大小是i这都没问题, 而-0x4(%rsp,%rbx,4)即是M[R(rsp)+4*(i-1)], 也就是我们输入的第i个值# 那么,这一行代码的意思就是eax += 输入的第i个值400ec8:	03 44 9c fc          	add    -0x4(%rsp,%rbx,4),%eax# 判断eax是否与输入的第i+1个值相等 400ecc:	39 04 9c             	cmp    %eax,(%rsp,%rbx,4)# 总结, 最后判断的是: 输入的第i+1个值  是否与  i+输入的第i个值  相等

所以,需要输入的六个数字需要是,第i个数字是第(i-1)个数字加(i-1)

比如:1 2 4 7 11 16

输入之后也是成功拆解第二个炸弹了:

(gdb) run secret.txt
Starting program: /home/sun/codes/CSAPP/Bomblab/bomb secret.txt
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2.  Keep going!

最后记录一下完整phase_2汇编代码的注解:

0000000000400e8b <phase_2>:400e8b:	53                   	push   %rbx                       # 保存 %rbx(callee-saved),函数会改变 %rbx 的值400e8c:	48 83 ec 20          	sub    $0x20,%rsp                 # 在栈上分配 0x20(32) 字节的局部空间400e90:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax               # 从 %fs:0x28 读取线程/栈金丝雀值或 TLS 基址到 %rax(用于栈保护)400e97:	00 00 400e99:	48 89 44 24 18       	mov    %rax,0x18(%rsp)             # 将上面读取的 canary 保存到栈帧中的偏移 0x18 处,后续用于校验400e9e:	31 c0                	xor    %eax,%eax                  # 将 %eax 清零(eax=0);常用于设置参数或初始化寄存器400ea0:	48 89 e6             	mov    %rsp,%rsi                  # 将当前栈指针传给 %rsi,作为 read_six_numbers 的目标缓冲区地址(第2参数)400ea3:	e8 c1 05 00 00       	call   401469 <read_six_numbers>  # 调用 read_six_numbers,读取 6 个整数到 [%rsp]..[%rsp+20]400ea8:	83 3c 24 00          	cmpl   $0x0,(%rsp)                # 比较第一个读入的整数 a[0](位于 (%rsp))与 0400eac:	78 07                	js     400eb5 <phase_2+0x2a>       # 如果 a[0] 带符号为负 (sign set),跳到 0x400eb5(调用 explode_bomb)400eae:	bb 01 00 00 00       	mov    $0x1,%ebx                  # 将 %ebx 置为 1,作为循环索引 i 的初始值(从 a[1] 开始检查)400eb3:	eb 11                	jmp    400ec6 <phase_2+0x3b>      # 跳转到循环检查的计算/比较入口400eb5:	e8 8d 05 00 00       	call   401447 <explode_bomb>     # 跳转目标:调用 explode_bomb(a[0] < 0 时爆炸)400eba:	eb f2                	jmp    400eae <phase_2+0x23>      # 跳回到 0x400eae(汇编布局用,实际爆炸后不会继续正常执行)400ebc:	48 83 c3 01          	add    $0x1,%rbx                  # rbx = rbx + 1;循环索引 i++(准备检查下一个元素)400ec0:	48 83 fb 06          	cmp    $0x6,%rbx                  # 比较 rbx 与 6(检查是否已完成 i = 1..5 的所有检查)400ec4:	74 12                	je     400ed8 <phase_2+0x4d>      # 如果 rbx == 6,跳转到成功退出处(通过本阶段)400ec6:	89 d8                	mov    %ebx,%eax                  # eax = ebx(把当前索引 i 复制到 eax,准备参与计算)400ec8:	03 44 9c fc          	add    -0x4(%rsp,%rbx,4),%eax     # eax += *(int *)(%rsp + rbx*4 - 4)# 地址解释:(%rsp + rbx*4) 指向 a[rbx],再 -4 即 a[rbx-1]# 因此这里计算 eax = i + a[i-1]400ecc:	39 04 9c             	cmp    %eax,(%rsp,%rbx,4)         # 比较上一步的结果(i + a[i-1])与 a[rbx](即 a[i])400ecf:	74 eb                	je     400ebc <phase_2+0x31>      # 如果相等,跳回 0x400ebc:i++ 并继续下一轮检查400ed1:	e8 71 05 00 00       	call   401447 <explode_bomb>     # 否则调用 explode_bomb(比较失败则爆炸)400ed6:	eb e4                	jmp    400ebc <phase_2+0x31>      # 跳回 i++ 的位置(汇编布局,爆炸后通常不会继续)400ed8:	48 8b 44 24 18       	mov    0x18(%rsp),%rax            # 准备函数返回前的金丝雀校验:把栈上的 canary(之前保存的值)读回到 %rax400edd:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax              # 将当前线程的 %fs:0x28 与保存的 canary 异或,检查是否被修改400ee4:	00 00 400ee6:	75 06                	jne    400eee <phase_2+0x63>      # 如果不为 0(说明金丝雀被篡改),跳到 __stack_chk_fail400ee8:	48 83 c4 20          	add    $0x20,%rsp                 # 清理栈空间(恢复 %rsp)400eec:	5b                   	pop    %rbx                       # 恢复保存的 %rbx400eed:	c3                   	ret                               # 返回,阶段通过400eee:	e8 0d fc ff ff       	call   400b00 <__stack_chk_fail@plt> # 金丝雀校验失败,调用 __stack_chk_fail(栈溢出保护)

phase 3

获得的汇编代码:

0000000000400ef3 <phase_3>:400ef3:	48 83 ec 18          	sub    $0x18,%rsp400ef7:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax400efe:	00 00 400f00:	48 89 44 24 08       	mov    %rax,0x8(%rsp)400f05:	31 c0                	xor    %eax,%eax400f07:	48 8d 4c 24 04       	lea    0x4(%rsp),%rcx400f0c:	48 89 e2             	mov    %rsp,%rdx400f0f:	be cf 25 40 00       	mov    $0x4025cf,%esi400f14:	e8 87 fc ff ff       	call   400ba0 <__isoc99_sscanf@plt>400f19:	83 f8 01             	cmp    $0x1,%eax400f1c:	7e 10                	jle    400f2e <phase_3+0x3b>400f1e:	83 3c 24 07          	cmpl   $0x7,(%rsp)400f22:	77 42                	ja     400f66 <phase_3+0x73>400f24:	8b 04 24             	mov    (%rsp),%eax400f27:	ff 24 c5 40 24 40 00 	jmp    *0x402440(,%rax,8)400f2e:	e8 14 05 00 00       	call   401447 <explode_bomb>400f33:	eb e9                	jmp    400f1e <phase_3+0x2b>400f35:	b8 35 02 00 00       	mov    $0x235,%eax400f3a:	eb 3b                	jmp    400f77 <phase_3+0x84>400f3c:	b8 a7 01 00 00       	mov    $0x1a7,%eax400f41:	eb 34                	jmp    400f77 <phase_3+0x84>400f43:	b8 2b 02 00 00       	mov    $0x22b,%eax400f48:	eb 2d                	jmp    400f77 <phase_3+0x84>400f4a:	b8 6c 00 00 00       	mov    $0x6c,%eax400f4f:	eb 26                	jmp    400f77 <phase_3+0x84>400f51:	b8 f1 02 00 00       	mov    $0x2f1,%eax400f56:	eb 1f                	jmp    400f77 <phase_3+0x84>400f58:	b8 3e 00 00 00       	mov    $0x3e,%eax400f5d:	eb 18                	jmp    400f77 <phase_3+0x84>400f5f:	b8 48 02 00 00       	mov    $0x248,%eax400f64:	eb 11                	jmp    400f77 <phase_3+0x84>400f66:	e8 dc 04 00 00       	call   401447 <explode_bomb>400f6b:	b8 00 00 00 00       	mov    $0x0,%eax400f70:	eb 05                	jmp    400f77 <phase_3+0x84>400f72:	b8 21 01 00 00       	mov    $0x121,%eax400f77:	39 44 24 04          	cmp    %eax,0x4(%rsp)400f7b:	74 05                	je     400f82 <phase_3+0x8f>400f7d:	e8 c5 04 00 00       	call   401447 <explode_bomb>400f82:	48 8b 44 24 08       	mov    0x8(%rsp),%rax400f87:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax400f8e:	00 00 400f90:	75 05                	jne    400f97 <phase_3+0xa4>400f92:	48 83 c4 18          	add    $0x18,%rsp400f96:	c3                   	ret400f97:	e8 64 fb ff ff       	call   400b00 <__stack_chk_fail@plt>

观察汇编语句,注意到调用了sscanf函数,按 x86-64 System V 调用约定,函数前六个整数/指针参数依次放在
rdi, rsi, rdx, rcx, r8, r9

看到的寄存器设置顺序是:

  • lea 0x4(%rsp), %rcx → 第 4 个参数(rcx)指向 rsp+4
  • mov %rsp, %rdx → 第 3 个参数(rdx)指向 rsp
  • mov $0x4025cf, %esi → 第 2 个参数(rsi)是地址 0x4025cf
  • 还有第 1 个参数:函数被调用时 rdi 已经含有传进来的字符串(由调用者设置)

这对应 sscanf(rdi, rsi, rdx, rcx)sscanf(str, format, &first, &second)。所以 rsi 应该是格式串(比如 "%d %d"),而 rdx/rcx 是两个 int*(分别写入到 rsprsp+4)。

根据这个猜想把0x4025cf,%esi的地址在gdb中调试,看到

(gdb) x/s 0x4025cf
0x4025cf:       "%d %d"

这证明了刚才的猜想,第三颗炸弹需要输入两个数字。 而这两个数字分别存在rsprsp+4中,为了方便起见,把这两个数叫做xy.

cmpl $0x7,(%rsp) / ja explode:检查 x(在 (%rsp))是否 >7,若大于 7(unsigned >7)就爆炸。

mov (%rsp),%eaxjmp *0x402440(,%rax,8):将x的值放到 %eax,然后跳转到 0x402440 + eax * 8 的地址

而地址 0x402440 + eax * 8 的写法是数组的地址表示方法,eax 也就是x是索引.

那么再往后,多个形如 mov $imm,%eax; jmp 0x400f77:每个 case 把一个立即数 imm 装到 %eax(这是该索引对应的“正确值”),然后跳到统一的比较处。

到这里就可以确定下来了,我们需要输入的两个数,第一个数是索引,第二个数是表中对应索引的值,需要和表一致才不会让炸弹爆炸。那么接下来需要通过gdb调试来找到表中的值。

0x0000000000400f27 <+52>:    jmp    *0x402440(,%rax,8)# 跳转表在反汇编里出现为 0x402440。先查看表里 8 个 8 字节条目(对应索引 0..7 的目标地址):
(gdb) x/8gx 0x402440
0x402440:       0x0000000000400f72      0x0000000000400f35
0x402450:       0x0000000000400f3c      0x0000000000400f43
0x402460:       0x0000000000400f4a      0x0000000000400f51
0x402470:       0x0000000000400f58      0x0000000000400f5f# 这个表有八个项,但我们只需要一个,所以找第一个,也就是索引0的位置
(gdb) x/i 0x0000000000400f720x400f72 <phase_3+127>:      mov    $0x121,%eax

但我们得到的是16进制数,需要再转换成10进制,也就是: 0 289## phase 4函数"phase_4"汇编代码:```yaml
0000000000400fdb <phase_4>:400fdb:	48 83 ec 18          	sub    $0x18,%rsp400fdf:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax400fe6:	00 00 400fe8:	48 89 44 24 08       	mov    %rax,0x8(%rsp)400fed:	31 c0                	xor    %eax,%eax400fef:	48 8d 4c 24 04       	lea    0x4(%rsp),%rcx   # 4400ff4:	48 89 e2             	mov    %rsp,%rdx   # 3400ff7:	be cf 25 40 00       	mov    $0x4025cf,%esi    # 2400ffc:	e8 9f fb ff ff       	call   400ba0 <__isoc99_sscanf@plt>401001:	83 f8 02             	cmp    $0x2,%eax401004:	75 06                	jne    40100c <phase_4+0x31>401006:	83 3c 24 0e          	cmpl   $0xe,(%rsp)40100a:	76 05                	jbe    401011 <phase_4+0x36>40100c:	e8 36 04 00 00       	call   401447 <explode_bomb>401011:	ba 0e 00 00 00       	mov    $0xe,%edx401016:	be 00 00 00 00       	mov    $0x0,%esi40101b:	8b 3c 24             	mov    (%rsp),%edi40101e:	e8 79 ff ff ff       	call   400f9c <func4>401023:	83 f8 03             	cmp    $0x3,%eax401026:	75 07                	jne    40102f <phase_4+0x54>401028:	83 7c 24 04 03       	cmpl   $0x3,0x4(%rsp)40102d:	74 05                	je     401034 <phase_4+0x59>40102f:	e8 13 04 00 00       	call   401447 <explode_bomb>401034:	48 8b 44 24 08       	mov    0x8(%rsp),%rax401039:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax401040:	00 00 401042:	75 05                	jne    401049 <phase_4+0x6e>401044:	48 83 c4 18          	add    $0x18,%rsp401048:	c3                   	ret401049:	e8 b2 fa ff ff       	call   400b00 <__stack_chk_fail@plt>

观察代码,注意到在调用了scanf,并且和炸弹3一样,都是传入两个数字

(gdb) x/s 0x4025cf
0x4025cf:       "%d %d"

输入两个数后:

  • 检查第一个整数 (%rsp) 是否 ≤ 0xe(14)。
  • edi = (%rsp)esi = 0edx = 14,然后调用 func4(edi, esi, edx)(即 func4(n,0,14))。
  • 检查 func4 的返回值是否等于 3,并且要求第二个输入 0x4(%rsp) 等于 3
    因此问题化为:找出 n ∈ [0,14] 使 func4(n,0,14) == 3,并把第二个数字设为 3

至此,我们知道了我们输入的两个数的目标:输入的第一个数小于15并且要让func4输出eax=3;输入的第二个数只能是3

那么怎么能让函数"func4"输出eax=3呢?先来看func4的汇编代码:

0000000000400f9c <func4>:400f9c:	48 83 ec 08          	sub    $0x8,%rsp400fa0:	89 d0                	mov    %edx,%eax400fa2:	29 f0                	sub    %esi,%eax400fa4:	89 c1                	mov    %eax,%ecx400fa6:	c1 e9 1f             	shr    $0x1f,%ecx400fa9:	01 c1                	add    %eax,%ecx400fab:	d1 f9                	sar    $1,%ecx400fad:	01 f1                	add    %esi,%ecx400faf:	39 f9                	cmp    %edi,%ecx400fb1:	7f 0e                	jg     400fc1 <func4+0x25>400fb3:	b8 00 00 00 00       	mov    $0x0,%eax           # eax = 0400fb8:	39 f9                	cmp    %edi,%ecx400fba:	7c 11                	jl     400fcd <func4+0x31>400fbc:	48 83 c4 08          	add    $0x8,%rsp400fc0:	c3                   	ret400fc1:	8d 51 ff             	lea    -0x1(%rcx),%edx400fc4:	e8 d3 ff ff ff       	call   400f9c <func4>400fc9:	01 c0                	add    %eax,%eax     # eax = 2 * eax400fcb:	eb ef                	jmp    400fbc <func4+0x20>400fcd:	8d 71 01             	lea    0x1(%rcx),%esi400fd0:	e8 c7 ff ff ff       	call   400f9c <func4>400fd5:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax   # eax = 2 * eax + 1400fd9:	eb e1                	jmp    400fbc <func4+0x20>

可以看到func4的函数体内部多次调用了func4函数,所以应该是一个递归函数。

直接看 func4 中会对 eax 的值造成影响的地方,发现它们分别发生在:
0x400fa0, 0x400fa2, 0x400fb3, 0x400fc9, 0x400fd5

eax 的值在很多地方都会变化,一个一个跟踪比较复杂。但我们知道最后 eax 的输出结果是 3。因此可以从函数返回点开始反推:看在什么情况下函数最终能返回 3。

因为 0x400fbc 是所有路径最终的返回点(retq 前),而会跳转到这一点的路径主要有三条:

  • 来自 0x400fc9:执行 add %eax,%eax,即 eax = 2 * eax
  • 来自 0x400fd5:执行 lea 0x1(%rax,%rax,1), %eax,即 eax = 2 * eax + 1
  • 直接来自 0x400fb3mov $0x0,%eax,即 eax = 0

如果函数最后输出为 3,那么只能来自第二条路径(0x400fd5),因为只有 2 * eax + 1 = 3 可以成立,此时 eax 在调用返回前为 1
因此可以推断最后一次递归前的输出是 eax = 1

接下来,我们继续倒推:要在倒数第二层递归输出 eax = 1,再看这次返回时函数会走哪条路径。
若输出为 1,同样可以考虑两种可能:

  • eax = 2 * eax 得到:不可能(1 不是 2 的倍数)
  • eax = 2 * eax + 1 得到:需要输入时 eax = 0

因此,倒数第二次进入 func4 时的输出应为 eax = 0

继续向前推:
当输出为 0 时,只能来自 mov $0x0,%eax 这一条路径(对应 cmp %edi, %ecx 相等时)。
说明这一次递归中,%edi == %ecx,程序在此处不再递归,直接返回 0。

从逻辑上看,这个函数是一个递归的二分查找函数,每次计算中点 ecx = (high + low)/2,并根据 edi(目标值)与中点比较决定往左或往右递归。返回值通过左、右子树递归结果计算,形成一种“路径编码”的结果。
而最终 eax = 3 表示递归路径走了两步(右→右),即 func4 的搜索区间和输入的 edi 使它递归了两次到右子区间。

而[0,14] 中可以通过两次右子区间递归到的数,只有12和13

  • For n = 13:
    • Level0 mid=7: 13 > 7 → right
    • Level1 mid=11: 13 > 11 → right
    • Level2 mid=13: 13 == 13 → base return 0
    • 回溯: level1: 2*0 + 1 = 1; level0: 2*1 + 1 = 3
  • For n = 12:
    • Level0 mid=7: 12 > 7 → right
    • Level1 mid=11: 12 > 11 → right
    • Level2 mid=13: 12 < 13 → left → call func4(12,12,12)
    • Level3 mid=12: 12 == 12 → base return 0
    • 回溯: level2 (left): 2*0 = 0; level1 (right): 2*0 + 1 = 1; level0 (right): 2*1 + 1 = 3

所以答案应该是:

12 3

或者

13 3

phase 5

函数"phase_5"汇编代码:

000000000040104e <phase_5>:40104e:	48 83 ec 18          	sub    $0x18,%rsp401052:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax401059:	00 00 40105b:	48 89 44 24 08       	mov    %rax,0x8(%rsp)401060:	31 c0                	xor    %eax,%eax401062:	48 8d 4c 24 04       	lea    0x4(%rsp),%rcx401067:	48 89 e2             	mov    %rsp,%rdx40106a:	be cf 25 40 00       	mov    $0x4025cf,%esi40106f:	e8 2c fb ff ff       	call   400ba0 <__isoc99_sscanf@plt>401074:	83 f8 01             	cmp    $0x1,%eax401077:	7e 57                	jle    4010d0 <phase_5+0x82>401079:	8b 04 24             	mov    (%rsp),%eax# 取低 4 位:eax = eax & 0xf (把索引限制为 0..15)40107c:	83 e0 0f             	and    $0xf,%eax40107f:	89 04 24             	mov    %eax,(%rsp)401082:	83 f8 0f             	cmp    $0xf,%eax401085:	74 2f                	je     4010b6 <phase_5+0x68>401087:	b9 00 00 00 00       	mov    $0x0,%ecx40108c:	ba 00 00 00 00       	mov    $0x0,%edx# (循环开始)
401091: 83 c2 01           add    $0x1,%edx# edx++:进入一次跳表访问(计数)401094: 48 98              cltq# sign-extend %eax -> %rax(把索引装入 %rax 做 64 位地址计算)# 此时 %eax 是当前索引(初始为 masked first input)401096: 8b 04 85 80 24 40 00  mov    0x402480(,%rax,4),%eax# %eax = TABLE[ %rax ]   (从数据表 0x402480 以 4 字节步长索引)# 注意:读取后 %eax 变为表中值(下一轮会用它作为索引)40109d: 01 c1              add    %eax,%ecx# ecx += eax(累加表项到 ecx)40109f: 83 f8 0f           cmp    $0xf,%eax
4010a2: 75 ed              jne    401091 <phase_5+0x43># 如果读出的表项 != 15,继续循环(回到 edx++ 并再读 TABLE[ eax ])# 若表项 == 15,则跳出循环(到下一条)4010a4: c7 04 24 0f 00 00 00  movl   $0xf,(%rsp)# 将 0xf (15) 写入 (%rsp)(覆盖第一个输入),无实际必要,可能是清理或标记4010ab: 83 fa 03           cmp    $0x3,%edx
4010ae: 75 06              jne    4010b6 <phase_5+0x68># 如果循环迭代次数 edx != 3 则爆炸(需要恰好 3 次访问才到 15)4010b0: 39 4c 24 04        cmp    %ecx,0x4(%rsp)
4010b4: 74 05              je     4010bb <phase_5+0x6d># 比较累加和 ecx 与第二个输入(在 0x4(%rsp))# 若相等通过,否则爆炸4010b6: e8 8c 03 00 00     call   401447 <explode_bomb>
4010bb: 48 8b 44 24 08     mov    0x8(%rsp),%rax
4010c0: 64 48 33 04 25 28 00 00  xor    %fs:0x28,%rax
4010c7: 00 00
4010c9: 75 0c              jne    4010d7 <phase_5+0x89>
4010cb: 48 83 c4 18        add    $0x18,%rsp
4010cf: c3                 ret
4010d0: e8 72 03 00 00     call   401447 <explode_bomb>
4010d5: eb a2              jmp    401079 <phase_5+0x2b>
4010d7: e8 24 fa ff ff     call   400b00 <__stack_chk_fail@plt>

还是scanf,直接读0x4025cf的内容,得到:

(gdb) x/s 0x4025cf
0x4025cf:       "%d %d"

输入的值被存在%rsp(%rsp)+4 中。

0x401079-0x40107f,将我们输入的第一个值对16取余,并将其存到eax寄存器

0x401082检查第一个输入mod16后的结果是否是15,若是15则直接爆炸

0x40108c-0x4010a2是一个循环函数,当eax=0xf时退出循环;edx是循环的计数器,循环几次edx就等于几;ecx是eax的一个累加器,刚进入循环时初值为0,eax每次循环的值都会加到ecx上。

大致逻辑梳理成伪代码是:

step = 0; sum = 0;
do {step += 1;val = TABLE[idx];       // TABLE 位于 0x402480,按 4 字节索引sum += val;idx = val;              // 下一轮用 val 作为新的索引
} while (val != 15);

这道题关键的关键在于0x402480这个地址,读取它可以知道这个数组为:

(gdb) x/16dw 0x402480
0x402480 <array.3415>:  10      2       14      7
0x402490 <array.3415+16>:       8       12      15      11
0x4024a0 <array.3415+32>:       0       4       1       13
0x4024b0 <array.3415+48>:       3       9       6       5
a[0]a[1]a[2]a[3]a[4]a[5]a[6]a[7]a[8]a[9]a[10]a[11]a[12]a[13]a[14]a[15]
a2e78cfb041d3965

0x4010ab判断edx是否等于3,若不等于3则直接爆炸,说明上面的循环要进行三次就退出。

唯一满足“恰好 3 步到达 15 且三次读出的值之和等于第二输入”的起点是 start = 2

start=2 -> table[2]=14 -> table[14]=6 -> table[6]=15
values seen: 14, 6, 15
steps = 3
sum = 14 + 6 + 15 = 35

所以答案是:

2 35

phase 6

00000000004010dc <phase_6>:4010dc:	41 56                	push   %r144010de:	41 55                	push   %r134010e0:	41 54                	push   %r124010e2:	55                   	push   %rbp4010e3:	53                   	push   %rbx4010e4:	48 83 ec 60          	sub    $0x60,%rsp4010e8:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax4010ef:	00 00 4010f1:	48 89 44 24 58       	mov    %rax,0x58(%rsp)4010f6:	31 c0                	xor    %eax,%eax4010f8:	48 89 e6             	mov    %rsp,%rsi4010fb:	e8 69 03 00 00       	call   401469 <read_six_numbers>401100:	49 89 e4             	mov    %rsp,%r12401103:	49 89 e5             	mov    %rsp,%r13401106:	41 be 00 00 00 00    	mov    $0x0,%r14d40110c:	eb 25                	jmp    401133 <phase_6+0x57>40110e:	e8 34 03 00 00       	call   401447 <explode_bomb>401113:	eb 2d                	jmp    401142 <phase_6+0x66>401115:	83 c3 01             	add    $0x1,%ebx401118:	83 fb 05             	cmp    $0x5,%ebx40111b:	7f 12                	jg     40112f <phase_6+0x53>40111d:	48 63 c3             	movslq %ebx,%rax401120:	8b 04 84             	mov    (%rsp,%rax,4),%eax401123:	39 45 00             	cmp    %eax,0x0(%rbp)401126:	75 ed                	jne    401115 <phase_6+0x39>401128:	e8 1a 03 00 00       	call   401447 <explode_bomb>40112d:	eb e6                	jmp    401115 <phase_6+0x39>40112f:	49 83 c5 04          	add    $0x4,%r13401133:	4c 89 ed             	mov    %r13,%rbp401136:	41 8b 45 00          	mov    0x0(%r13),%eax40113a:	83 e8 01             	sub    $0x1,%eax40113d:	83 f8 05             	cmp    $0x5,%eax401140:	77 cc                	ja     40110e <phase_6+0x32>401142:	41 83 c6 01          	add    $0x1,%r14d401146:	41 83 fe 06          	cmp    $0x6,%r14d40114a:	74 05                	je     401151 <phase_6+0x75>40114c:	44 89 f3             	mov    %r14d,%ebx40114f:	eb cc                	jmp    40111d <phase_6+0x41>401151:	49 8d 4c 24 18       	lea    0x18(%r12),%rcx401156:	ba 07 00 00 00       	mov    $0x7,%edx40115b:	89 d0                	mov    %edx,%eax40115d:	41 2b 04 24          	sub    (%r12),%eax401161:	41 89 04 24          	mov    %eax,(%r12)401165:	49 83 c4 04          	add    $0x4,%r12401169:	4c 39 e1             	cmp    %r12,%rcx40116c:	75 ed                	jne    40115b <phase_6+0x7f>40116e:	be 00 00 00 00       	mov    $0x0,%esi401173:	eb 1a                	jmp    40118f <phase_6+0xb3>401175:	48 8b 52 08          	mov    0x8(%rdx),%rdx401179:	83 c0 01             	add    $0x1,%eax40117c:	39 c8                	cmp    %ecx,%eax40117e:	75 f5                	jne    401175 <phase_6+0x99>401180:	48 89 54 f4 20       	mov    %rdx,0x20(%rsp,%rsi,8)401185:	48 83 c6 01          	add    $0x1,%rsi401189:	48 83 fe 06          	cmp    $0x6,%rsi40118d:	74 14                	je     4011a3 <phase_6+0xc7>40118f:	8b 0c b4             	mov    (%rsp,%rsi,4),%ecx401192:	b8 01 00 00 00       	mov    $0x1,%eax401197:	ba d0 32 60 00       	mov    $0x6032d0,%edx40119c:	83 f9 01             	cmp    $0x1,%ecx40119f:	7f d4                	jg     401175 <phase_6+0x99>4011a1:	eb dd                	jmp    401180 <phase_6+0xa4>4011a3:	48 8b 5c 24 20       	mov    0x20(%rsp),%rbx4011a8:	48 8b 44 24 28       	mov    0x28(%rsp),%rax4011ad:	48 89 43 08          	mov    %rax,0x8(%rbx)4011b1:	48 8b 54 24 30       	mov    0x30(%rsp),%rdx4011b6:	48 89 50 08          	mov    %rdx,0x8(%rax)4011ba:	48 8b 44 24 38       	mov    0x38(%rsp),%rax4011bf:	48 89 42 08          	mov    %rax,0x8(%rdx)4011c3:	48 8b 54 24 40       	mov    0x40(%rsp),%rdx4011c8:	48 89 50 08          	mov    %rdx,0x8(%rax)4011cc:	48 8b 44 24 48       	mov    0x48(%rsp),%rax4011d1:	48 89 42 08          	mov    %rax,0x8(%rdx)4011d5:	48 c7 40 08 00 00 00 	movq   $0x0,0x8(%rax)4011dc:	00 4011dd:	bd 05 00 00 00       	mov    $0x5,%ebp4011e2:	eb 09                	jmp    4011ed <phase_6+0x111>4011e4:	48 8b 5b 08          	mov    0x8(%rbx),%rbx4011e8:	83 ed 01             	sub    $0x1,%ebp4011eb:	74 11                	je     4011fe <phase_6+0x122>4011ed:	48 8b 43 08          	mov    0x8(%rbx),%rax4011f1:	8b 00                	mov    (%rax),%eax4011f3:	39 03                	cmp    %eax,(%rbx)4011f5:	7d ed                	jge    4011e4 <phase_6+0x108>4011f7:	e8 4b 02 00 00       	call   401447 <explode_bomb>4011fc:	eb e6                	jmp    4011e4 <phase_6+0x108>4011fe:	48 8b 44 24 58       	mov    0x58(%rsp),%rax401203:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax40120a:	00 00 40120c:	75 0d                	jne    40121b <phase_6+0x13f>40120e:	48 83 c4 60          	add    $0x60,%rsp401212:	5b                   	pop    %rbx401213:	5d                   	pop    %rbp401214:	41 5c                	pop    %r12401216:	41 5d                	pop    %r13401218:	41 5e                	pop    %r1440121a:	c3                   	ret40121b:	e8 e0 f8 ff ff       	call   400b00 <__stack_chk_fail@plt>

在0x4010fb处调用的函数"read_six_numbers"就知道这个题要求的输入是6个整数,和第二颗炸弹一样。

在之后的汇编代码中有一个循环部分:

401136:  mov 0x0(%r13),%eax   ; 当前数
40113a:  sub $0x1,%eax        ; eax = x - 1
40113d:  cmp $0x5,%eax        ; 比较 x-1 与 5
401140:  ja  explode_bomb     ; 如果 x < 1 或 x > 6,则爆炸

每个输入必须在 1~6 之间。

接下来

40111d~401128
cmp (%rsp,%rax,4),%eax
jne ...
explode_bomb

→ 遍历前面的数,如果有重复就爆炸。

6 个数字必须互不相同且在 [1,6] 之间。

再然后

40115b~40116c:
mov $7,%edx
sub (%r12),%eax
mov %eax,(%r12)

这段循环对每个数做:num = 7 - num

接下来:

401197:  mov $0x6032d0,%edx
40119c:  cmp $0x1,%ecx
40119f:  jg 401175
401175~401180: 循环 rd = rd->next
401180: mov %rdx, 0x20(%rsp,%rsi,8)

说明 %rdx = 0x6032d0 是链表头节点地址。
链表中每个节点的结构类似:

struct node {int value;struct node *next;
};

那么大致的循环逻辑应该是:

for each i in [0..5]:index = numbers[i]   // 已经被 7-x 转换了p = headfor k in range(1, index):p = p->nextnode_ptr[i] = p

即:根据每个数(1-6),从链表头开始走 (index-1) 步,
将对应的节点地址存到一个数组里。

这 6 个数决定了节点的重排顺序。

然后重连链表

4011a3~4011d5:
mov 0x20(%rsp),%rbx
mov 0x28(%rsp),%rax
mov %rax,0x8(%rbx)
...
最后把最后一个节点 next=0
# 确保链表递减排列
4011f3:  cmp (%rbx),%eax
4011f5:  jge 4011e4
4011f7:  explode_bomb

那么到这里就很清楚了

  • 6 个互异、1~6;
  • 映射:每个输入 → 7-x
  • 映射结果决定链表节点选择;
  • 节点值需递减;
  • 从节点值的降序索引反推回输入。

所以在gdb调试中查看链表的内容:

(gdb) x/24gw 0x6032d0
0x6032d0 <node1>:       634     1       0       0
0x6032e0 <node2>:       851     2       6304464 0
0x6032f0 <node3>:       921     3       6304480 0
0x603300 <node4>:       310     4       6304496 0
0x603310 <node5>:       585     5       6304512 0
0x603320 <node6>:       138     6       6304528 0

降序排列是:

node3: 921
node2: 851
node1: 634
node5: 585
node4: 310
node6: 138

对应的节点标号是:[3, 2, 1, 5, 4, 6]

反推回原输入为 7 - t:[7-3, 7-2, 7-1, 7-5, 7-4, 7-6] = [4, 5, 6, 2, 3, 1]

所以答案就是:4 5 6 2 3 1

phase secret

在汇编代码中找了一下,隐藏函数"secret_phase"的唯一入口在函数"phase_defused",那么就先观察这个函数的汇编代码:

00000000004015d6 <phase_defused>:4015d6:	48 83 ec 78          	sub    $0x78,%rsp4015da:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax4015e1:	00 00 4015e3:	48 89 44 24 68       	mov    %rax,0x68(%rsp)4015e8:	31 c0                	xor    %eax,%eax4015ea:	83 3d 7b 21 20 00 06 	cmpl   $0x6,0x20217b(%rip)        # 60376c <num_input_strings>4015f1:	74 15                	je     401608 <phase_defused+0x32>4015f3:	48 8b 44 24 68       	mov    0x68(%rsp),%rax4015f8:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax4015ff:	00 00 401601:	75 67                	jne    40166a <phase_defused+0x94>401603:	48 83 c4 78          	add    $0x78,%rsp401607:	c3                   	ret401608:	4c 8d 44 24 10       	lea    0x10(%rsp),%r840160d:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx401612:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx401617:	be 19 26 40 00       	mov    $0x402619,%esi40161c:	bf 70 38 60 00       	mov    $0x603870,%edi401621:	e8 7a f5 ff ff       	call   400ba0 <__isoc99_sscanf@plt>401626:	83 f8 03             	cmp    $0x3,%eax401629:	74 0c                	je     401637 <phase_defused+0x61>40162b:	bf 58 25 40 00       	mov    $0x402558,%edi401630:	e8 ab f4 ff ff       	call   400ae0 <puts@plt>401635:	eb bc                	jmp    4015f3 <phase_defused+0x1d>401637:	be 22 26 40 00       	mov    $0x402622,%esi40163c:	48 8d 7c 24 10       	lea    0x10(%rsp),%rdi401641:	e8 04 fd ff ff       	call   40134a <strings_not_equal>401646:	85 c0                	test   %eax,%eax401648:	75 e1                	jne    40162b <phase_defused+0x55>40164a:	bf f8 24 40 00       	mov    $0x4024f8,%edi40164f:	e8 8c f4 ff ff       	call   400ae0 <puts@plt>401654:	bf 20 25 40 00       	mov    $0x402520,%edi401659:	e8 82 f4 ff ff       	call   400ae0 <puts@plt>40165e:	b8 00 00 00 00       	mov    $0x0,%eax401663:	e8 f7 fb ff ff       	call   40125f <secret_phase>401668:	eb c1                	jmp    40162b <phase_defused+0x55>40166a:	e8 91 f4 ff ff       	call   400b00 <__stack_chk_fail@plt>

隐藏关被 phase_defused 触发的条件是:全局计数 num_input_strings 等于 6(即程序已经记录到了 6 个输入字符串),并且 phase_defused 读入的一行满足 sscanf 的格式且第三个读入的字符串 等于 程序中存放在地址 0x402622 的那个秘密字符串。要找这个“字符串” —— 直接在 gdb 里查看静态数据 0x402622 就能得到它;把它作为你在第四个炸弹后附加的那一行中的第三个字段传入即可触发隐藏关(前提是 num_input_strings==6)。

(gdb) x/s 0x402619
0x402619:       "%d %d %s"

进入后,函数用 sscanf(格式字符串在地址 0x402619)把 一行解析到三个缓冲区(位于栈:rsp+8, rsp+0xc, rsp+0x10)。然后它比较第三个缓冲区(rsp+0x10)与静态字符串在 0x402622,调试查看这个位置。

(gdb) x/s 0x402622
0x402622:       "urxvt"

所以,我们进入secret phase的密语是"urxvt"

之后查看函数"secret_phase"的汇编代码:

000000000040125f <secret_phase>:40125f:	53                   	push   %rbx401260:	e8 43 02 00 00       	call   4014a8 <read_line>401265:	ba 0a 00 00 00       	mov    $0xa,%edx40126a:	be 00 00 00 00       	mov    $0x0,%esi40126f:	48 89 c7             	mov    %rax,%rdi401272:	e8 09 f9 ff ff       	call   400b80 <strtol@plt>401277:	48 89 c3             	mov    %rax,%rbx40127a:	8d 40 ff             	lea    -0x1(%rax),%eax40127d:	3d e8 03 00 00       	cmp    $0x3e8,%eax401282:	77 27                	ja     4012ab <secret_phase+0x4c>401284:	89 de                	mov    %ebx,%esi401286:	bf f0 30 60 00       	mov    $0x6030f0,%edi40128b:	e8 90 ff ff ff       	call   401220 <fun7>401290:	83 f8 04             	cmp    $0x4,%eax401293:	74 05                	je     40129a <secret_phase+0x3b>401295:	e8 ad 01 00 00       	call   401447 <explode_bomb>40129a:	bf 08 24 40 00       	mov    $0x402408,%edi40129f:	e8 3c f8 ff ff       	call   400ae0 <puts@plt>4012a4:	e8 2d 03 00 00       	call   4015d6 <phase_defused>4012a9:	5b                   	pop    %rbx4012aa:	c3                   	ret4012ab:	e8 97 01 00 00       	call   401447 <explode_bomb>4012b0:	eb d2                	jmp    401284 <secret_phase+0x25>

secret_phase的汇编还是比较简短的,也容易理解

secret_phase 做了以下事情:

  1. 调用 read_line → 读入一行字符串。

  2. 调用 strtol(line, 0, 10) → 把输入的字符串转换为一个整数(十进制)。

  3. 检查输入是否在 1 ≤ x ≤ 1001(因为比较 eax-10x3e8=1000,超出就炸)。

  4. 调用eax = fun7(0x6030f0, input)

    其中 0x6030f0 是一棵二叉树的根节点地址

  5. 如果 func7 的返回值 == 4 → 成功(打印一句话,进入 phase_defused)。
    否则炸。

所以需要找到一个整数 x,使得 func7(0x6030f0, x) == 4。

那么接下来就看func7的函数体了:

0000000000401220 <fun7>:401220:	48 85 ff             	test   %rdi,%rdi401223:	74 34                	je     401259 <fun7+0x39>401225:	48 83 ec 08          	sub    $0x8,%rsp401229:	8b 17                	mov    (%rdi),%edx40122b:	39 f2                	cmp    %esi,%edx40122d:	7f 0e                	jg     40123d <fun7+0x1d>40122f:	b8 00 00 00 00       	mov    $0x0,%eax401234:	39 f2                	cmp    %esi,%edx401236:	75 12                	jne    40124a <fun7+0x2a>401238:	48 83 c4 08          	add    $0x8,%rsp40123c:	c3                   	ret40123d:	48 8b 7f 08          	mov    0x8(%rdi),%rdi401241:	e8 da ff ff ff       	call   401220 <fun7>401246:	01 c0                	add    %eax,%eax401248:	eb ee                	jmp    401238 <fun7+0x18>40124a:	48 8b 7f 10          	mov    0x10(%rdi),%rdi40124e:	e8 cd ff ff ff       	call   401220 <fun7>401253:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax401257:	eb df                	jmp    401238 <fun7+0x18>401259:	b8 ff ff ff ff       	mov    $0xffffffff,%eax40125e:	c3                   	ret

又是递归,而且还很晦涩难懂,不过可以先分析一下:

func7 的开头如下:

401220:  test   %rdi,%rdi
401223:  je     401259 <fun7+0x39>

这表示:如果 %rdi == 0,直接返回(最后 mov eax, -1)。

继续看:

401229: 8b 17 mov (%rdi),%edx
这行取出节点结构的第一个字段(偏移量 0),放进 %edx
接着:

40123d:  48 8b 7f 08        mov 0x8(%rdi),%rdi
40124a:  48 8b 7f 10        mov 0x10(%rdi),%rdi

这说明:偏移 +0x8 和 +0x10 处是节点的另外两个数据;

而有这类似的数据结构的,就是二叉树的节点,不过现在还不是很确定

不过可以看一下在调用func7时传入的参数 edi是什么。

在gdb中调试多次后,发现通过x/24gw 0x6030f0命令可以显示出一个比较清晰的结构。

(gdb) x/24gw 0x6030f0
0x6030f0 <n1>:  U"$"
0x6030f8 <n1+8>:        U"\x603110"
0x603100 <n1+16>:       U"\x603130"
0x603108:       U""
0x60310c:       U""
0x603110 <n21>: U"\b"
0x603118 <n21+8>:       U"\x603190"
0x603120 <n21+16>:      U"\x603150"
0x603128:       U""
0x60312c:       U""
0x603130 <n22>: U"2"
0x603138 <n22+8>:       U"\x603170"
0x603140 <n22+16>:      U"\x6031b0"
0x603148:       U""
0x60314c:       U""
--Type <RET> for more, q to quit, c to continue without paging--
0x603150 <n32>: U"\026"
0x603158 <n32+8>:       U"\x603270"
0x603160 <n32+16>:      U"\x603230"
0x603168:       U""
0x60316c:       U""
0x603170 <n33>: U"-"
0x603178 <n33+8>:       U"\x6031d0"
0x603180 <n33+16>:      U"\x603290"
0x603188:       U""

看这个结构,就可以明显看出是二叉树了,

节点n1,存储一个值"$",有两个地址,分别指向:0x603110(n21)和0x603130(n22)

(这个储存的值有点不对劲,是因为输出的整数被打印成了Unicode编码的格式,查了一下ASCII码表,“$” 对应的是 36,所以n1实际存储的值应该是36)

整理了一下各节点,现在得到的树大概是这样,不过并不完整

               [36]/    \[8]      [50]/  \      /  \[??]  [22]  [45]  [??]

我们目前有了前两层,剩下的地址还指向:

  • 0x603190(8的左)
  • 0x603150(8的右 = 22)
  • 0x603170(50的左 = 45)
  • 0x6031b0(50的右)

需要在gdb中继续调试来找到完整的树:

(gdb) x/24gw 0x603190
(gdb) x/24gw 0x6031b0
(gdb) x/24gw 0x603230
(gdb) x/24gw 0x603270
(gdb) x/24gw 0x6031d0
(gdb) x/24gw 0x603290

返回结果有点长,就不列出了

最后整理完的结构是:

                     [36]/    \[8]         [50]/   \        /   \[6]   [22]   [45]   [107]/  \   /  \   /  \   /   \[1] [7][20][35][40][47][99][169]

很显然,这棵树很好看,是一棵二叉搜索树,那大胆推断func7函数应该就是用于在二叉搜索树中搜索的。

那么回过头来看func7,函数体内对于输出值 %eax都做了什么操作

cmpq $0x0, (%rdi)检查当前节点是否为空。如果为空,跳到 mov $-1, %eax。所以空节点时返回 -1

mov (%rdi), %eax取出当前节点的值,放进 %eax。例如,这个值可能是 36。

cmp %esi, %eax比较目标值和当前节点的值:

  • 如果 key < node->value:执行 jg,走左子树。
  • 如果 key > node->value:执行 jl,走右子树。
  • 如果相等:mov $0, %eax,直接返回 0。

所以,eax = 0 表示找到了目标节点。

如果走左子树:

mov 0x8(%rdi), %rdi
call func7
add %eax, %eax   # eax = eax * 2
add $0, %eax     # (其实就是保持 *2)

所以返回值 = 左子树返回值 × 2。

如果走右子树:

mov 0x10(%rdi), %rdi
call func7
lea 0x1(%rax,%rax), %eax   # eax = eax*2 + 1

所以返回值 = 右子树返回值 × 2 + 1。

总结一下 %eax 的意义,每次往下走:

  • 左子树 → *2
  • 右子树 → *2 + 1
  • 找到目标 → 0
  • 找不到 → -1

也就是说,%eax 编码了一条从根节点到目标节点的“路径”

而我们需要的值是4,也就是二进制100,但是返回值的二进制是“路径位”自下而上(从叶到根)排列的结果(因为输入40炸弹爆炸了(bushi))

相对应的不是 “右-左-左” ,而是“左-左-右”

对应二叉树中的节点值就是 7

所以隐藏炸弹的答案是 7

总结

这个bomblab整体难度好高,花费了好长时间,也整理了好久汇编的笔记,不过做完之后感觉对汇编和gdb的感悟深了很多。

http://www.dtcms.com/a/549729.html

相关文章:

  • [人工智能-大模型-118]:模型层 - RNN状态记忆是如何实现的?是通过带权重的神经元,还是通过张量?
  • 手机做网站需要多少天国外代理ip地址和端口
  • Unity-WebGL开发——用IIS(Internet Information Services)部署webGL工程
  • 怎么仿制别人的网站一个网站的建设流程有哪些
  • 学生信息管理系统oracle
  • Angular【router路由】
  • 如何网站建设网页宣传片拍摄报价明细
  • 曲阜做网站智能建造概论
  • 嘉峪关网站seo英文商城网站
  • 易混淆的点:栈的增长方向 和 缓冲区内的数据写入方向是相反的
  • 全流程掌握生态环评核心技术——涵盖生物量模拟、生物多样性计算、脆弱度评价及公路铁路、机场、水利项目实战
  • 【Embedded System】嵌入式C语言基础知识
  • PsTools 学习笔记(7.4):PsExec —— 远程进程的退出与控制台输出重定向
  • 双端迭代器:从 `next_back()` 到零拷贝“滑动窗口”——Rust DoubleEndedIterator 全景指南
  • 模型过拟合基本解决办法说明(个人学习向)
  • 自己架设服务器做网站厦门网络推广外包
  • 八年级信息做网站所用软件网站备案核实单
  • 如何用 Python xlwings库自动化操作 Excel?
  • 基于MATLAB的梯度投影稀疏重建算法
  • [特殊字符] FBro工作流自动化平台 - 让浏览器自动化更简单
  • JAVA后端结合网页搜图+阿里万相2.5实现自动化修图与返回
  • 和平区网站制作手机企业网站怎么做
  • 如何在好医生网站做二类学分dede中英文网站切换
  • Kubernetes 实战入门内容
  • 变量声明与可变性
  • 前端生产部署完全指南:从零到精通
  • 做外贸的人常用的网站wordpress视频站模板下载
  • Oracle数据库安装(Windows)
  • 跳转指令介绍
  • 关于网站制作的论文个人网页效果图