目录

CS:APP Bomblab 游玩记录

假期无聊的自己想起之前读 CS:APP 但是没有仔细做里面的 lab, 于是挑个 (也许) 最好玩的先来玩一玩正好边玩边写

Bomblab 所用到的资源可以在 GitHub 找到.

以下二进制分析基于上述链接中的 bomb 文件, x86-64 汇编使用 Intel 语法.

初步分析

lab 中包含一个 ELF 格式的 bomb 以及一个 bomb.c. 其中 bomb.c 包含程序的大致逻辑但不包含各个 phase 的具体实现.

先分析下 bomb 的信息, 使用 readelf 可以得到:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
❯ readelf -h bomb
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400c90
  Start of program headers:          64 (bytes into file)
  Start of section headers:          18616 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         36
  Section header string table index: 33

根据得到的信息可以知道这个可执行文件的指令集是 x86-64 且使用 System V ABI.

bomb.c 中的大致逻辑为, 整个 bomb 包含 6 个 phase, 每个 phase 需要用户输入一行字符串. 如果带参数则会优先从文件中读取字符串, EOF 之后自动切换到标准输入读取. 每个 phase 中会将输入的字符串作为参数传递给 void phase_x(char*), 只要该函数正常执行完毕即算该 phase 通过.

P.S.: bomb.c 里面有一些很有意思的彩蛋注释

Phase 1

objdump 稍微反汇编一下就可以发现是简单的字符串比较.

1
2
3
4
5
6
7
8
9
0000000000400ee0 <phase_1> (File Offset: 0xee0):
  400ee0:   48 83 ec 08             sub    rsp,0x8
  400ee4:   be 00 24 40 00          mov    esi,0x402400
  400ee9:   e8 4a 04 00 00          call   401338 <strings_not_equal> (File Offset: 0x1338)
  400eee:   85 c0                   test   eax,eax
  400ef0:   74 05                   je     400ef7 <phase_1+0x17> (File Offset: 0xef7)
  400ef2:   e8 43 05 00 00          call   40143a <explode_bomb> (File Offset: 0x143a)
  400ef7:   48 83 c4 08             add    rsp,0x8
  400efb:   c3                      ret

根据 x86-64 的 calling convention 可以知道调用 string_not_equalesi 中的地址 0x402400 即为比较目标. objdump -h 一下找这个内存地址对应的文件偏移可以得到 0x2400, 用 xxd 找一下对应的文件偏移 0x2400 的位置可以找到:

1
2
3
4
00002400: 426f 7264 6572 2072 656c 6174 696f 6e73  Border relations
00002410: 2077 6974 6820 4361 6e61 6461 2068 6176   with Canada hav
00002420: 6520 6e65 7665 7220 6265 656e 2062 6574  e never been bet
00002430: 7465 722e 0000 0000 576f 7721 2059 6f75  ter.....Wow! You

所以第一个 phase 的 key 就是

1
Border relations with Canada have never been better.

Phase 2

照例先看一下 phase_2 的逆向:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
0000000000400efc <phase_2> (File Offset: 0xefc):
  400efc:   55                      push   rbp
  400efd:   53                      push   rbx
  400efe:   48 83 ec 28             sub    rsp,0x28
  400f02:   48 89 e6                mov    rsi,rsp
  400f05:   e8 52 05 00 00          call   40145c <read_six_numbers> (File Offset: 0x145c)
  400f0a:   83 3c 24 01             cmp    DWORD PTR [rsp],0x1
  400f0e:   74 20                   je     400f30 <phase_2+0x34> (File Offset: 0xf30)
  400f10:   e8 25 05 00 00          call   40143a <explode_bomb> (File Offset: 0x143a)
  400f15:   eb 19                   jmp    400f30 <phase_2+0x34> (File Offset: 0xf30)
  400f17:   8b 43 fc                mov    eax,DWORD PTR [rbx-0x4]
  400f1a:   01 c0                   add    eax,eax
  400f1c:   39 03                   cmp    DWORD PTR [rbx],eax
  400f1e:   74 05                   je     400f25 <phase_2+0x29> (File Offset: 0xf25)
  400f20:   e8 15 05 00 00          call   40143a <explode_bomb> (File Offset: 0x143a)
  400f25:   48 83 c3 04             add    rbx,0x4
  400f29:   48 39 eb                cmp    rbx,rbp
  400f2c:   75 e9                   jne    400f17 <phase_2+0x1b> (File Offset: 0xf17)
  400f2e:   eb 0c                   jmp    400f3c <phase_2+0x40> (File Offset: 0xf3c)
  400f30:   48 8d 5c 24 04          lea    rbx,[rsp+0x4]
  400f35:   48 8d 6c 24 18          lea    rbp,[rsp+0x18]
  400f3a:   eb db                   jmp    400f17 <phase_2+0x1b> (File Offset: 0xf17)
  400f3c:   48 83 c4 28             add    rsp,0x28
  400f40:   5b                      pop    rbx
  400f41:   5d                      pop    rbp
  400f42:   c3                      ret

P.S.: C语言写的炸弹不能直接逆向出函数签名有点难受

注意到它在函数 entry sequence 中将 rsp 拉低了 0x28 = 40, 结合将 rsp 的值放在 rsi 中传递给 read_six_number 可以推测它开辟了一块栈上的未知类型 buffer 并将其首地址作为第二个参数传递给 read_six_number. rdi 没有被改变, 说明 read_six_number 的第一个参数就是 phase_2 的第一个参数, 即键入的 key. 跟着 call 逆向 read_six_number 可以得到:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
000000000040145c <read_six_numbers> (File Offset: 0x145c):
  40145c:   48 83 ec 18             sub    rsp,0x18
  401460:   48 89 f2                mov    rdx,rsi
  401463:   48 8d 4e 04             lea    rcx,[rsi+0x4]
  401467:   48 8d 46 14             lea    rax,[rsi+0x14]
  40146b:   48 89 44 24 08          mov    QWORD PTR [rsp+0x8],rax
  401470:   48 8d 46 10             lea    rax,[rsi+0x10]
  401474:   48 89 04 24             mov    QWORD PTR [rsp],rax
  401478:   4c 8d 4e 0c             lea    r9,[rsi+0xc]
  40147c:   4c 8d 46 08             lea    r8,[rsi+0x8]
  401480:   be c3 25 40 00          mov    esi,0x4025c3
  401485:   b8 00 00 00 00          mov    eax,0x0
  40148a:   e8 61 f7 ff ff          call   400bf0 <__isoc99_sscanf@plt> (File Offset: 0xbf0)
  40148f:   83 f8 05                cmp    eax,0x5
  401492:   7f 05                   jg     401499 <read_six_numbers+0x3d> (File Offset: 0x1499)
  401494:   e8 a1 ff ff ff          call   40143a <explode_bomb> (File Offset: 0x143a)
  401499:   48 83 c4 18             add    rsp,0x18
  40149d:   c3                      ret

注意到代码中有调用 PLT 定位的项, 按照名字推测是标准库中的 sscanf 函数, 可以以它的参数为突破口观察 sscanf 都读取了哪些信息. entry sequence 结束之后, 它将 rsi 中上文提到的 buffer 地址和该地址 +4 偏移的值分别加载到 rdxrcx. 注意到直到 sscanf 被调用为止 rdxrcx 的值都没有变化, 根据 System V ABI 的 calling convention 可知这两个地址分别是 sscanf 的第 3/4 个参数. 同理还有 r8r9, 可知第 5/6 个参数分别是 rsi+8rsi+12. System V ABI 在寄存器中最多传递 6 个整数参数, 其余两个参数在栈上传递且按照机器字长对齐, 可知 QWORD PTR [rsp+0x8] 即为第 8 个参数, QWORD PTR [rsp] 为第 7 个. 所以第 3~8 个参数为: rsi, rsi+4, rsi+8, rsi+12, rsi+16, rsi+20.

加载完参数 3~8 后它将一个静态地址 0x4025c3 加载进 esi, 即 sscanf 第 2 个参数的位置. 找一下对应的文件偏移 0x25c3 可以找到:

1
2
000025c0: 702e 0025 6420 2564 2025 6420 2564 2025  p..%d %d %d %d %
000025d0: 6420 2564 0045 7272 6f72 3a20 5072 656d  d %d.Error: Prem

所以实际上就是串 %d %d %d %d %d %d. 同时也可以推测出传进来的 buffer 是 int 数组, 读入目标是数组的前 6 个值.

read_six_number 的参数相同, rdi 没有被改变, 可知键入的字符串被一路传递到了 sscanf 作为第一个参数, 也就是读入目标.

sscanf 返回之后, read_six_numbersscanf 的返回值 (保存在 eax 里) 与 5 进行比较, 如果大于 5 则跳转到 401499 也就是 exit sequence 的位置返回, 否则会调用 explode_bomb. 推测要求 6 个整数全部成功读入.

回到 phase_2 中, 先判断 buffer[0] 是否为 1, 如果是则跳转到 400f30&buffer[1]&buffer[6] 分别赋给 rbxrbp, 跳转回 400f17, 推断此时 rbxrbp 都是 int*. 接着计算出 *(rbx - 1) * 2 的值并和 *rbx 比较, 推断要求 buffer[i+1]=2*buffer[i], 然后将 rbx 指向下一个 int 地址并与 rbp 比较判断是否跳出到 exit sequence, 推断是一个循环判断整个长度为 6 的 buffer 是否满足 buffer[i+1]=2*buffer[i]. 据此构造出 key:

1
1 2 4 8 16 32

Phase 3

照例先从入口开始:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
0000000000400f43 <phase_3> (File Offset: 0xf43):
  400f43:   48 83 ec 18             sub    rsp,0x18
  400f47:   48 8d 4c 24 0c          lea    rcx,[rsp+0xc]
  400f4c:   48 8d 54 24 08          lea    rdx,[rsp+0x8]
  400f51:   be cf 25 40 00          mov    esi,0x4025cf
  400f56:   b8 00 00 00 00          mov    eax,0x0
  400f5b:   e8 90 fc ff ff          call   400bf0 <__isoc99_sscanf@plt> (File Offset: 0xbf0)
  400f60:   83 f8 01                cmp    eax,0x1
  400f63:   7f 05                   jg     400f6a <phase_3+0x27> (File Offset: 0xf6a)
  400f65:   e8 d0 04 00 00          call   40143a <explode_bomb> (File Offset: 0x143a)
  400f6a:   83 7c 24 08 07          cmp    DWORD PTR [rsp+0x8],0x7
  400f6f:   77 3c                   ja     400fad <phase_3+0x6a> (File Offset: 0xfad)
  400f71:   8b 44 24 08             mov    eax,DWORD PTR [rsp+0x8]
  400f75:   ff 24 c5 70 24 40 00    jmp    QWORD PTR [rax*8+0x402470]
  400f7c:   b8 cf 00 00 00          mov    eax,0xcf
  400f81:   eb 3b                   jmp    400fbe <phase_3+0x7b> (File Offset: 0xfbe)
  400f83:   b8 c3 02 00 00          mov    eax,0x2c3
  400f88:   eb 34                   jmp    400fbe <phase_3+0x7b> (File Offset: 0xfbe)
  400f8a:   b8 00 01 00 00          mov    eax,0x100
  400f8f:   eb 2d                   jmp    400fbe <phase_3+0x7b> (File Offset: 0xfbe)
  400f91:   b8 85 01 00 00          mov    eax,0x185
  400f96:   eb 26                   jmp    400fbe <phase_3+0x7b> (File Offset: 0xfbe)
  400f98:   b8 ce 00 00 00          mov    eax,0xce
  400f9d:   eb 1f                   jmp    400fbe <phase_3+0x7b> (File Offset: 0xfbe)
  400f9f:   b8 aa 02 00 00          mov    eax,0x2aa
  400fa4:   eb 18                   jmp    400fbe <phase_3+0x7b> (File Offset: 0xfbe)
  400fa6:   b8 47 01 00 00          mov    eax,0x147
  400fab:   eb 11                   jmp    400fbe <phase_3+0x7b> (File Offset: 0xfbe)
  400fad:   e8 88 04 00 00          call   40143a <explode_bomb> (File Offset: 0x143a)
  400fb2:   b8 00 00 00 00          mov    eax,0x0
  400fb7:   eb 05                   jmp    400fbe <phase_3+0x7b> (File Offset: 0xfbe)
  400fb9:   b8 37 01 00 00          mov    eax,0x137
  400fbe:   3b 44 24 0c             cmp    eax,DWORD PTR [rsp+0xc]
  400fc2:   74 05                   je     400fc9 <phase_3+0x86> (File Offset: 0xfc9)
  400fc4:   e8 71 04 00 00          call   40143a <explode_bomb> (File Offset: 0x143a)
  400fc9:   48 83 c4 18             add    rsp,0x18
  400fcd:   c3                      ret

第一眼看见里面一车的 jmp 感觉有种不祥的预感…

从头开始分析, 最开始是一个普通的开辟栈 buffer 和传参调用 sscanf 的过程, 和 read_six_number 差不多. 话不多说直接去找 0x25cf 写的啥:

1
2
000025c0: 702e 0025 6420 2564 2025 6420 2564 2025  p..%d %d %d %d %
000025d0: 6420 2564 0045 7272 6f72 3a20 5072 656d  d %d.Error: Prem

好家伙, 又是这

不过这次是从偏移 F 开始的, 所以只有 %d %d. 那就明白了, 输入是俩数, 第一个在 rsp+0x8 , 第二个在 rsp+0xc. 不妨设它们为 ik.

sscanf 返回后是和 read_six_number 类似的判断输入合法性的命令. 确认合法后跳转到 400f6a 也就是这里的第 11 行.

然后判断了一下如果 i>7 就直接爆炸, i<=7 则进入一个动态跳转命令, 跳转目标是一个内存引用, 推测是一个跳转表, 基址为 402470.

话不多说赶紧找 0x2470 都有啥:

1
2
3
4
00002470: 7c0f 4000 0000 0000 b90f 4000 0000 0000  |.@.......@.....
00002480: 830f 4000 0000 0000 8a0f 4000 0000 0000  ..@.......@.....
00002490: 910f 4000 0000 0000 980f 4000 0000 0000  ..@.......@.....
000024a0: 9f0f 4000 0000 0000 a60f 4000 0000 0000  ..@.......@.....

果然是个跳转表. 注意小端序低位字节在先, 可以列出跳转目标:

1
2
3
4
5
6
7
8
400f7c
400fb9
400f83
400f8a
400f91
400f98
400f9f
400fa6

回到对应的地址可以发现, 都是一些形如 mov eax, *** 后跳转到同一个 400fbe 的指令. 而 400fbe 位置的指令将 keax 比较, 如果相等才进入 exit sequence 否则引爆炸弹. 推测此处有多解, 8 个值中任意选择一个即可. 所以 key 可以是:

1
3 512

P.S.: 本来到这里觉得跳转表里可能有些值是非法的或者某些跳转表目标会引到陷阱位置, 仔细一看发现大概只是个普通的 switch (

Phase 4

还是先看入口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
000000000040100c <phase_4> (File Offset: 0x100c):
  40100c:   48 83 ec 18             sub    rsp,0x18
  401010:   48 8d 4c 24 0c          lea    rcx,[rsp+0xc]
  401015:   48 8d 54 24 08          lea    rdx,[rsp+0x8]
  40101a:   be cf 25 40 00          mov    esi,0x4025cf
  40101f:   b8 00 00 00 00          mov    eax,0x0
  401024:   e8 c7 fb ff ff          call   400bf0 <__isoc99_sscanf@plt> (File Offset: 0xbf0)
  401029:   83 f8 02                cmp    eax,0x2
  40102c:   75 07                   jne    401035 <phase_4+0x29> (File Offset: 0x1035)
  40102e:   83 7c 24 08 0e          cmp    DWORD PTR [rsp+0x8],0xe
  401033:   76 05                   jbe    40103a <phase_4+0x2e> (File Offset: 0x103a)
  401035:   e8 00 04 00 00          call   40143a <explode_bomb> (File Offset: 0x143a)
  40103a:   ba 0e 00 00 00          mov    edx,0xe
  40103f:   be 00 00 00 00          mov    esi,0x0
  401044:   8b 7c 24 08             mov    edi,DWORD PTR [rsp+0x8]
  401048:   e8 81 ff ff ff          call   400fce <func4> (File Offset: 0xfce)
  40104d:   85 c0                   test   eax,eax
  40104f:   75 07                   jne    401058 <phase_4+0x4c> (File Offset: 0x1058)
  401051:   83 7c 24 0c 00          cmp    DWORD PTR [rsp+0xc],0x0
  401056:   74 05                   je     40105d <phase_4+0x51> (File Offset: 0x105d)
  401058:   e8 dd 03 00 00          call   40143a <explode_bomb> (File Offset: 0x143a)
  40105d:   48 83 c4 18             add    rsp,0x18
  401061:   c3                      ret

开始几行甚至和 phase_3 完全一致, 不用多想了就是输入俩数(

这次设成 ab 吧.

验证过输入成功之后又判断了一下要求 a 不大于 15, 调用了一下 func4(a, 0, 0xe). 跟着看一下 func4 都在干嘛:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
0000000000400fce <func4> (File Offset: 0xfce):
  400fce:   48 83 ec 08             sub    rsp,0x8
  400fd2:   89 d0                   mov    eax,edx
  400fd4:   29 f0                   sub    eax,esi
  400fd6:   89 c1                   mov    ecx,eax
  400fd8:   c1 e9 1f                shr    ecx,0x1f
  400fdb:   01 c8                   add    eax,ecx
  400fdd:   d1 f8                   sar    eax,1
  400fdf:   8d 0c 30                lea    ecx,[rax+rsi*1]
  400fe2:   39 f9                   cmp    ecx,edi
  400fe4:   7e 0c                   jle    400ff2 <func4+0x24> (File Offset: 0xff2)
  400fe6:   8d 51 ff                lea    edx,[rcx-0x1]
  400fe9:   e8 e0 ff ff ff          call   400fce <func4> (File Offset: 0xfce)
  400fee:   01 c0                   add    eax,eax
  400ff0:   eb 15                   jmp    401007 <func4+0x39> (File Offset: 0x1007)
  400ff2:   b8 00 00 00 00          mov    eax,0x0
  400ff7:   39 f9                   cmp    ecx,edi
  400ff9:   7d 0c                   jge    401007 <func4+0x39> (File Offset: 0x1007)
  400ffb:   8d 71 01                lea    esi,[rcx+0x1]
  400ffe:   e8 cb ff ff ff          call   400fce <func4> (File Offset: 0xfce)
  401003:   8d 44 00 01             lea    eax,[rax+rax*1+0x1]
  401007:   48 83 c4 08             add    rsp,0x8
  40100b:   c3                      ret

后两个参数先叫 xy, 试着分析一下它的 C 代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int func4(int a, int x, int y){
    int eax = y;
    eax -= x;
    if(eax < 0)  // shr 0x1f 之后, 只有当 ecx 是负数时才能得到 1, 否则为 0. 然后加到 eax 上.
        ++eax;
    eax >>= 1;  // eax: (y - x) / 2 向零取整
    int ecx = eax + x;
    if(ecx <= a){
        eax = 0;
        if(ecx >= a)
            return eax;
        else{
            eax = func4(a, ecx + 1, y);
            eax = eax*2 + 1;
            return eax;
        }
    }
    else{
        eax = func4(a, x, ecx - 1);
        eax *= 2;
        return eax;
    }
}

看上去 xy 是类似边界的东西, 还是管它叫 l, r 比较好.

脑内优化一下可以得到:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int func4(int a, int l, int r){
    int mid = l + (r - l) / 2;
    if(mid <= a){
        if(mid == a)
            return 0;
        else
            return func4(a, mid+1, r) * 2 + 1;
    }
    else
        return func4(a, l, mid-1) * 2;
}

看上去它在做的就是一个递归的二分查找, 返回值是二分的历史. 如果取左半区间就在历史低位添一个 0, 右半部分就添 1. 当中点命中目标时直接返回 0.

搞清了 func4 之后回去看看 phase_4 拿返回值干啥了.

看上去就普通地判断了一下要求 func4 的返回值是 0, 同时要求 b 也是 0.

想了想有两种思路, 一个是让它只能取左半区间, 显然让 a 是二分左端点也就是 0 即可. 另一种思路是, 向左半区间二分的过程中的每一个中点值也是合法的答案, 也就是 7, 3, 1.

所以合法的 key 可以是:

1
7 0

Phase 5

先看入口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
0000000000401062 <phase_5> (File Offset: 0x1062):
  401062:   53                      push   rbx
  401063:   48 83 ec 20             sub    rsp,0x20
  401067:   48 89 fb                mov    rbx,rdi
  40106a:   64 48 8b 04 25 28 00    mov    rax,QWORD PTR fs:0x28
  401071:   00 00
  401073:   48 89 44 24 18          mov    QWORD PTR [rsp+0x18],rax
  401078:   31 c0                   xor    eax,eax
  40107a:   e8 9c 02 00 00          call   40131b <string_length> (File Offset: 0x131b)
  40107f:   83 f8 06                cmp    eax,0x6
  401082:   74 4e                   je     4010d2 <phase_5+0x70> (File Offset: 0x10d2)
  401084:   e8 b1 03 00 00          call   40143a <explode_bomb> (File Offset: 0x143a)
  401089:   eb 47                   jmp    4010d2 <phase_5+0x70> (File Offset: 0x10d2)
  40108b:   0f b6 0c 03             movzx  ecx,BYTE PTR [rbx+rax*1]
  40108f:   88 0c 24                mov    BYTE PTR [rsp],cl
  401092:   48 8b 14 24             mov    rdx,QWORD PTR [rsp]
  401096:   83 e2 0f                and    edx,0xf
  401099:   0f b6 92 b0 24 40 00    movzx  edx,BYTE PTR [rdx+0x4024b0]
  4010a0:   88 54 04 10             mov    BYTE PTR [rsp+rax*1+0x10],dl
  4010a4:   48 83 c0 01             add    rax,0x1
  4010a8:   48 83 f8 06             cmp    rax,0x6
  4010ac:   75 dd                   jne    40108b <phase_5+0x29> (File Offset: 0x108b)
  4010ae:   c6 44 24 16 00          mov    BYTE PTR [rsp+0x16],0x0
  4010b3:   be 5e 24 40 00          mov    esi,0x40245e
  4010b8:   48 8d 7c 24 10          lea    rdi,[rsp+0x10]
  4010bd:   e8 76 02 00 00          call   401338 <strings_not_equal> (File Offset: 0x1338)
  4010c2:   85 c0                   test   eax,eax
  4010c4:   74 13                   je     4010d9 <phase_5+0x77> (File Offset: 0x10d9)
  4010c6:   e8 6f 03 00 00          call   40143a <explode_bomb> (File Offset: 0x143a)
  4010cb:   0f 1f 44 00 00          nop    DWORD PTR [rax+rax*1+0x0]
  4010d0:   eb 07                   jmp    4010d9 <phase_5+0x77> (File Offset: 0x10d9)
  4010d2:   b8 00 00 00 00          mov    eax,0x0
  4010d7:   eb b2                   jmp    40108b <phase_5+0x29> (File Offset: 0x108b)
  4010d9:   48 8b 44 24 18          mov    rax,QWORD PTR [rsp+0x18]
  4010de:   64 48 33 04 25 28 00    xor    rax,QWORD PTR fs:0x28
  4010e5:   00 00
  4010e7:   74 05                   je     4010ee <phase_5+0x8c> (File Offset: 0x10ee)
  4010e9:   e8 42 fa ff ff          call   400b30 <__stack_chk_fail@plt> (File Offset: 0xb30)
  4010ee:   48 83 c4 20             add    rsp,0x20
  4010f2:   5b                      pop    rbx
  4010f3:   c3                      ret

前几行先放了个 stack protector 在 QWORD PTR [rsp+0x18], 然后判断了一下 key 的长度要求为 6.

跳出来把 eax 赋成 0 了之后又跳回去到一个长得像个循环的地方, 推测 40108b4010ac 这段是循环体.

观察循环中的操作, 对每个 key 中的字符都取最低 4 位, 然后在基址 0x4024b0 处查表并移动到 rsp+0x10 处. 果断看 0x24b0 有啥:

1
000024b0: 6d61 6475 6965 7273 6e66 6f74 7662 796c  maduiersnfotvbyl

果然是个表. 再看跳出循环后, 以 rsp+0x100x40245e 为参数调用 strings_not_equal, 看来目标就在 0x245e.

1
2
00002450: 7365 6372 6574 2073 7461 6765 2100 666c  secret stage!.fl
00002460: 7965 7273 0000 0000 0000 0000 0000 0000  yers............

目标字符串是 flyers, 构造一个低 4 位是 9FE567 的字符串即可.

懒得查 ASCII 表了, 直接在 xxd 的这段地址附近找低位能对上的字符了(xs

1
2
3
4
5
6
79  y
6f  o
6e  n
75  u
76  v
77  w

所以一个合法的 key 就是:

1
yonuvw

Phase 6

Phase 6 的逆向有点长, 一点点看

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
00000000004010f4 <phase_6> (File Offset: 0x10f4):
  4010f4:   41 56                   push   r14
  4010f6:   41 55                   push   r13
  4010f8:   41 54                   push   r12
  4010fa:   55                      push   rbp
  4010fb:   53                      push   rbx
  4010fc:   48 83 ec 50             sub    rsp,0x50
  401100:   49 89 e5                mov    r13,rsp
  401103:   48 89 e6                mov    rsi,rsp
  401106:   e8 51 03 00 00          call   40145c <read_six_numbers> (File Offset: 0x145c)
  40110b:   49 89 e6                mov    r14,rsp
  40110e:   41 bc 00 00 00 00       mov    r12d,0x0
  401114:   4c 89 ed                mov    rbp,r13
  401117:   41 8b 45 00             mov    eax,DWORD PTR [r13+0x0]
  40111b:   83 e8 01                sub    eax,0x1
  40111e:   83 f8 05                cmp    eax,0x5
  401121:   76 05                   jbe    401128 <phase_6+0x34> (File Offset: 0x1128)
  401123:   e8 12 03 00 00          call   40143a <explode_bomb> (File Offset: 0x143a)

entry sequence 有亿点长, 不祥的预感

寄存器保存之后又拉了一个 0x50 长度的栈帧 buffer

rsp 为第二个参数调用 read_six_numbers, 根据 Phase 2 的分析可以知道它会从输入中读取 6 个整数放在以 rsp 为基址的 int 数组. 不妨设这个 buffer 为 buf[].

然后来到第一个条件跳转, 将 r13 指向的 int 值 (此时也是 rsp 指向的值 buf[0]) 减去 1 并与 5 作比较, 若大于则引爆.

此时, rbp = r13 = r14 = rsp, r12d = 0.

继续看下一段

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  401128:   41 83 c4 01             add    r12d,0x1
  40112c:   41 83 fc 06             cmp    r12d,0x6
  401130:   74 21                   je     401153 <phase_6+0x5f> (File Offset: 0x1153)
  401132:   44 89 e3                mov    ebx,r12d
  401135:   48 63 c3                movsxd rax,ebx
  401138:   8b 04 84                mov    eax,DWORD PTR [rsp+rax*4]
  40113b:   39 45 00                cmp    DWORD PTR [rbp+0x0],eax
  40113e:   75 05                   jne    401145 <phase_6+0x51> (File Offset: 0x1145)
  401140:   e8 f5 02 00 00          call   40143a <explode_bomb> (File Offset: 0x143a)
  401145:   83 c3 01                add    ebx,0x1
  401148:   83 fb 05                cmp    ebx,0x5
  40114b:   7e e8                   jle    401135 <phase_6+0x41> (File Offset: 0x1135)
  40114d:   49 83 c5 04             add    r13,0x4
  401151:   eb c1                   jmp    401114 <phase_6+0x20> (File Offset: 0x1114)
  401153:   48 8d 74 24 18          lea    rsi,[rsp+0x18]
  401158:   4c 89 f0                mov    rax,r14
  40115b:   b9 07 00 00 00          mov    ecx,0x7
  401160:   89 ca                   mov    edx,ecx
  401162:   2b 10                   sub    edx,DWORD PTR [rax]
  401164:   89 10                   mov    DWORD PTR [rax],edx
  401166:   48 83 c0 04             add    rax,0x4
  40116a:   48 39 f0                cmp    rax,rsi
  40116d:   75 f1                   jne    401160 <phase_6+0x6c> (File Offset: 0x1160)

由上文知 r12d 被初始化为 0, 可知执行到 40112c 的比较语句时 r12d1, 401130 永远不会跳转.

接下来的两句可以推断 ebx = 1, rax = 1.

401138buf[1] 加载并在接下来与 buf[0] 做比较, 判断要求二者不相等.

ebx 自增 1, 此时 ebx = 2, 40114b 必定跳转, 40113540114b 组成循环(鈤 怎么打环了), ebx 遍历 [1,5], 即要求后面的字符不能与第一个相等.

循环结束后 r13 自增 4, 跳回 401114 草 原来是循环. 可知 r13 指向 buf 的下一个 int 位置. 检查这个位置的值大小之后 r12d 自增, 可以推知 r12d 一直保存着 r13 指向的地址在 buf 中的下标 +1 的值. 推知跳出到 401153 之前的代码判断了读入的六个数互不相等且不大于 6.

接下来, 由上文知 r14 = rsp, rax 中保存着一个指向 buf 中的一个值的指针. rsi 则保存着 buf[6] 的地址. 接下来令 [rax] = 7 - [rax], 同时将 rax 增加 4 并与 rsi 比较, 推断又是一个遍历六个数字的循环, 并将 buf[i] 设为 7 - buf[i].

1
2
  40116f:   be 00 00 00 00          mov    esi,0x0
  401174:   eb 21                   jmp    401197 <phase_6+0xa3> (File Offset: 0x1197)

esi 置为 0 之后突然跳转, 找到它的目标:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
  401176:   48 8b 52 08             mov    rdx,QWORD PTR [rdx+0x8]
  40117a:   83 c0 01                add    eax,0x1
  40117d:   39 c8                   cmp    eax,ecx
  40117f:   75 f5                   jne    401176 <phase_6+0x82> (File Offset: 0x1176)
  401181:   eb 05                   jmp    401188 <phase_6+0x94> (File Offset: 0x1188)
  401183:   ba d0 32 60 00          mov    edx,0x6032d0
  401188:   48 89 54 74 20          mov    QWORD PTR [rsp+rsi*2+0x20],rdx
  40118d:   48 83 c6 04             add    rsi,0x4
  401191:   48 83 fe 18             cmp    rsi,0x18
  401195:   74 14                   je     4011ab <phase_6+0xb7> (File Offset: 0x11ab)
  401197:   8b 0c 34                mov    ecx,DWORD PTR [rsp+rsi*1]
  40119a:   83 f9 01                cmp    ecx,0x1
  40119d:   7e e4                   jle    401183 <phase_6+0x8f> (File Offset: 0x1183)
  40119f:   b8 01 00 00 00          mov    eax,0x1
  4011a4:   ba d0 32 60 00          mov    edx,0x6032d0
  4011a9:   eb cb                   jmp    401176 <phase_6+0x82> (File Offset: 0x1176)

不难发现这一段代码除了 401195 跳出了之外是跳转封闭的, 推测是某种循环且结束条件是 rsi = 0x18. 附近可以看到 rsi 自增 4 的命令, 推测是循环状态更新. 结合跳入前 esi 初始化为 0, 推测这个循环会执行 6 次. 根据步长和范围基本可以确定 DWORD PTR [rsp+rsi*1] 就是遍历中的 buf[] 位置的值. 如果当前 buf[i] 不大于 1 则直接将 0x6032d0 加载入 rsp+0x20 为基址的 QWORD buffer 地址. 若大于 1 则一直循环执行 rdx = QWORD PTR [rdx+0x8] (草这怎么回事)

完了 感觉关键在 0x6032d0. 查表发现是 .data 段且对应文件偏移 0x32d0:

1
2
3
4
5
6
000032d0: 4c01 0000 0100 0000 e032 6000 0000 0000  L........2`.....
000032e0: a800 0000 0200 0000 f032 6000 0000 0000  .........2`.....
000032f0: 9c03 0000 0300 0000 0033 6000 0000 0000  .........3`.....
00003300: b302 0000 0400 0000 1033 6000 0000 0000  .........3`.....
00003310: dd01 0000 0500 0000 2033 6000 0000 0000  ........ 3`.....
00003320: bb01 0000 0600 0000 0000 0000 0000 0000  ................

看上去每次 rdx 都会指向一段数据, 而 rdx+0x8 指向下一个地址. 那么这样的话根据 401188 处的保存命令可以知道会将循环 buf[i] 次后的地址加载到 [rsp+0x20]QWORD buffer. 根据上面的数据可以知道大概是指向一个 8 字节的值(且长得像两个 4 字节整数拼一起)

我们可以按 16 字节为一组, 先管它叫一行. 相当于是 QWORD buffer 中都是上述某一行数据的地址.

剩下的部分在exit sequence之前的命令如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
  4011ab:   48 8b 5c 24 20          mov    rbx,QWORD PTR [rsp+0x20]
  4011b0:   48 8d 44 24 28          lea    rax,[rsp+0x28]
  4011b5:   48 8d 74 24 50          lea    rsi,[rsp+0x50]
  4011ba:   48 89 d9                mov    rcx,rbx
  4011bd:   48 8b 10                mov    rdx,QWORD PTR [rax]
  4011c0:   48 89 51 08             mov    QWORD PTR [rcx+0x8],rdx
  4011c4:   48 83 c0 08             add    rax,0x8
  4011c8:   48 39 f0                cmp    rax,rsi
  4011cb:   74 05                   je     4011d2 <phase_6+0xde> (File Offset: 0x11d2)
  4011cd:   48 89 d1                mov    rcx,rdx
  4011d0:   eb eb                   jmp    4011bd <phase_6+0xc9> (File Offset: 0x11bd)
  4011d2:   48 c7 42 08 00 00 00    mov    QWORD PTR [rdx+0x8],0x0
  4011d9:   00
  4011da:   bd 05 00 00 00          mov    ebp,0x5
  4011df:   48 8b 43 08             mov    rax,QWORD PTR [rbx+0x8]
  4011e3:   8b 00                   mov    eax,DWORD PTR [rax]
  4011e5:   39 03                   cmp    DWORD PTR [rbx],eax
  4011e7:   7d 05                   jge    4011ee <phase_6+0xfa> (File Offset: 0x11ee)
  4011e9:   e8 4c 02 00 00          call   40143a <explode_bomb> (File Offset: 0x143a)
  4011ee:   48 8b 5b 08             mov    rbx,QWORD PTR [rbx+0x8]
  4011f2:   83 ed 01                sub    ebp,0x1
  4011f5:   75 e8                   jne    4011df <phase_6+0xeb> (File Offset: 0x11df)

rbx 现在是奇怪的 QWORD buffer 的第一个值, 先管他叫 qbuf[0]. rax&qbuf[1], rsi&qbuf[6].

rcx = rbx = qbuf[0]. rdx = qbuf[1]. *(qbuf[0]+0x8) = rdx = qbuf[1], 相当于把 qbuf[0] 指向的行中的数据指向的地址改成了 qbuf[1] 中保存的地址.

然后将 rcx 更改为 rax 指向的值, rax 自增到下一个 qbuf 位置进行循环, 相当于每个循环的操作对象从 qbuf[0]/qbuf[1] 变成 qbuf[i]/qbuf[i+1].

.data 中的数据进行了一番修改之后进入验证过程. qbuf[5] 指向的行的地址段首先被清零, qbuf[0] 指向的行的地址被加载到 rax, 然后加载 rax 指向的值. 验证要求 qbuf[0] 指向的行的值大于等于这一行的地址段指向的行的值.

观察发现第二行的值最小, 当然是全都写 2 了 然而所有的值必须不相等.

稍微撕烤一下可以意识到只要 qbuf[] 中指向的行的地址对应的值依次变小就可以了. 排序可以知道正确的序列是 3 4 5 6 1 2.

注意中间有反相的过程, 所以 key 应该是:

1
4 3 2 1 6 5

One more thing…

逆向的时候看到 .rodata 段里有些奇怪的东西:

1
2
3
4
00002430: 7465 722e 0000 0000 576f 7721 2059 6f75  ter.....Wow! You
00002440: 2776 6520 6465 6675 7365 6420 7468 6520  've defused the
00002450: 7365 6372 6574 2073 7461 6765 2100 666c  secret stage!.fl
00002460: 7965 7273 0000 0000 0000 0000 0000 0000  yers............
1
2
3
4
5
6
7
000024f0: 646f 2079 6f75 3f00 4375 7273 6573 2c20  do you?.Curses,
00002500: 796f 7527 7665 2066 6f75 6e64 2074 6865  you've found the
00002510: 2073 6563 7265 7420 7068 6173 6521 0000   secret phase!..
00002520: 4275 7420 6669 6e64 696e 6720 6974 2061  But finding it a
00002530: 6e64 2073 6f6c 7669 6e67 2069 7420 6172  nd solving it ar
00002540: 6520 7175 6974 6520 6469 6666 6572 656e  e quite differen
00002550: 742e 2e2e 0000 0000 436f 6e67 7261 7475  t.......Congratu

看来还有东西在里面…

0x2438 对应的 VMA 是 0x402438, 找一下哪里有这个的引用:

1
2
  401282:   bf 38 24 40 00          mov    edi,0x402438
  401287:   e8 84 f8 ff ff          call   400b10 <puts@plt> (File Offset: 0xb10)

而它所属的函数是:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
0000000000401242 <secret_phase> (File Offset: 0x1242):
  401242:   53                      push   rbx
  401243:   e8 56 02 00 00          call   40149e <read_line> (File Offset: 0x149e)
  401248:   ba 0a 00 00 00          mov    edx,0xa
  40124d:   be 00 00 00 00          mov    esi,0x0
  401252:   48 89 c7                mov    rdi,rax
  401255:   e8 76 f9 ff ff          call   400bd0 <strtol@plt> (File Offset: 0xbd0)
  40125a:   48 89 c3                mov    rbx,rax
  40125d:   8d 40 ff                lea    eax,[rax-0x1]
  401260:   3d e8 03 00 00          cmp    eax,0x3e8
  401265:   76 05                   jbe    40126c <secret_phase+0x2a> (File Offset: 0x126c)
  401267:   e8 ce 01 00 00          call   40143a <explode_bomb> (File Offset: 0x143a)
  40126c:   89 de                   mov    esi,ebx
  40126e:   bf f0 30 60 00          mov    edi,0x6030f0
  401273:   e8 8c ff ff ff          call   401204 <fun7> (File Offset: 0x1204)
  401278:   83 f8 02                cmp    eax,0x2
  40127b:   74 05                   je     401282 <secret_phase+0x40> (File Offset: 0x1282)
  40127d:   e8 b8 01 00 00          call   40143a <explode_bomb> (File Offset: 0x143a)
  401282:   bf 38 24 40 00          mov    edi,0x402438
  401287:   e8 84 f8 ff ff          call   400b10 <puts@plt> (File Offset: 0xb10)
  40128c:   e8 33 03 00 00          call   4015c4 <phase_defused> (File Offset: 0x15c4)
  401291:   5b                      pop    rbx
  401292:   c3                      ret

追踪一下引用, 发现在 phase_defused

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
00000000004015c4 <phase_defused> (File Offset: 0x15c4):
  4015c4:   48 83 ec 78             sub    rsp,0x78
  4015c8:   64 48 8b 04 25 28 00    mov    rax,QWORD PTR fs:0x28
  4015cf:   00 00
  4015d1:   48 89 44 24 68          mov    QWORD PTR [rsp+0x68],rax
  4015d6:   31 c0                   xor    eax,eax
  4015d8:   83 3d 81 21 20 00 06    cmp    DWORD PTR [rip+0x202181],0x6        # 603760 <num_input_strings> (File Offset: 0x203760)
  4015df:   75 5e                   jne    40163f <phase_defused+0x7b> (File Offset: 0x163f)
  4015e1:   4c 8d 44 24 10          lea    r8,[rsp+0x10]
  4015e6:   48 8d 4c 24 0c          lea    rcx,[rsp+0xc]
  4015eb:   48 8d 54 24 08          lea    rdx,[rsp+0x8]
  4015f0:   be 19 26 40 00          mov    esi,0x402619
  4015f5:   bf 70 38 60 00          mov    edi,0x603870
  4015fa:   e8 f1 f5 ff ff          call   400bf0 <__isoc99_sscanf@plt> (File Offset: 0xbf0)
  4015ff:   83 f8 03                cmp    eax,0x3
  401602:   75 31                   jne    401635 <phase_defused+0x71> (File Offset: 0x1635)
  401604:   be 22 26 40 00          mov    esi,0x402622
  401609:   48 8d 7c 24 10          lea    rdi,[rsp+0x10]
  40160e:   e8 25 fd ff ff          call   401338 <strings_not_equal> (File Offset: 0x1338)
  401613:   85 c0                   test   eax,eax
  401615:   75 1e                   jne    401635 <phase_defused+0x71> (File Offset: 0x1635)
  401617:   bf f8 24 40 00          mov    edi,0x4024f8
  40161c:   e8 ef f4 ff ff          call   400b10 <puts@plt> (File Offset: 0xb10)
  401621:   bf 20 25 40 00          mov    edi,0x402520
  401626:   e8 e5 f4 ff ff          call   400b10 <puts@plt> (File Offset: 0xb10)
  40162b:   b8 00 00 00 00          mov    eax,0x0
  401630:   e8 0d fc ff ff          call   401242 <secret_phase> (File Offset: 0x1242)
  401635:   bf 58 25 40 00          mov    edi,0x402558
  40163a:   e8 d1 f4 ff ff          call   400b10 <puts@plt> (File Offset: 0xb10)
  40163f:   48 8b 44 24 68          mov    rax,QWORD PTR [rsp+0x68]
  401644:   64 48 33 04 25 28 00    xor    rax,QWORD PTR fs:0x28
  40164b:   00 00
  40164d:   74 05                   je     401654 <phase_defused+0x90> (File Offset: 0x1654)
  40164f:   e8 dc f4 ff ff          call   400b30 <__stack_chk_fail@plt> (File Offset: 0xb30)
  401654:   48 83 c4 78             add    rsp,0x78
  401658:   c3                      ret
  401659:   90                      nop
  40165a:   90                      nop

读一下代码, 中间 4015e140163a 一大段代码都是在 num_input_strings = 6 的时候才会执行. 然后丢了一些地址在参数寄存器调了 sscanf, 根据参数顺序知道 0x402619 是 format, 0x603870 是buffer. 0x402619.rodata 段可以直接从文件地址 0x2619 读:

1
2
00002610: 746f 6f20 6c6f 6e67 0025 6420 2564 2025  too long.%d %d %
00002620: 7300 4472 4576 696c 0067 7265 6174 7768  s.DrEvil.greatwh

格式串是 %d %d %s 并且后续验证了是否能读入三个信息. 而 0x603870.bss 段, 大概是全局静态 buffer. 查一下符号表:

1
2
3
    65: 00000000006030e0     0 NOTYPE  WEAK   DEFAULT   24 data_start
    66: 0000000000603780  1600 OBJECT  GLOBAL DEFAULT   25 input_strings
    67: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND strcpy@@GLIBC_2.2.5

看上去属于一个巨大全局静态 buffer, 根据名字推测大概是保存着所有的输入字符串, 0x603870 在 buffer 中的下标应该是 0xe0.

既然前 6 个 phase 都解完了不如直接上 gdb 动态看看对应的是哪段输入, 丢到 gdb 里在调用 sscanf 的地方也就是 *0x4015fa 下个断点直接翻 0x603870 附近的内存:

1
2
3
4
5
6
7
8
9
(gdb) x/64xc 0x603850
0x603850 <input_strings+208>:   0 '\000'        0 '\000'        0 '\000'        0 '\000'   0 '\000' 0 '\000'        0 '\000'        0 '\000'
0x603858 <input_strings+216>:   0 '\000'        0 '\000'        0 '\000'        0 '\000'   0 '\000' 0 '\000'        0 '\000'        0 '\000'
0x603860 <input_strings+224>:   0 '\000'        0 '\000'        0 '\000'        0 '\000'   0 '\000' 0 '\000'        0 '\000'        0 '\000'
0x603868 <input_strings+232>:   0 '\000'        0 '\000'        0 '\000'        0 '\000'   0 '\000' 0 '\000'        0 '\000'        0 '\000'
0x603870 <input_strings+240>:   55 '7'  32 ' '  48 '0'  0 '\000'        0 '\000'        0 '\000'    0 '\000'        0 '\000'
0x603878 <input_strings+248>:   0 '\000'        0 '\000'        0 '\000'        0 '\000'   0 '\000' 0 '\000'        0 '\000'        0 '\000'
0x603880 <input_strings+256>:   0 '\000'        0 '\000'        0 '\000'        0 '\000'   0 '\000' 0 '\000'        0 '\000'        0 '\000'
0x603888 <input_strings+264>:   0 '\000'        0 '\000'        0 '\000'        0 '\000'   0 '\000' 0 '\000'        0 '\000'        0 '\000'

'7', ' ', '0', '\0', 显然是 phase 3 的 key. 也就是说大概要在 phase 3 后面加点料(

继续看 phase_defused 的逆向, 根据 r8 的值知道这段字符串保存在 rsp+0x10. 判断是否成功读入之后又把 rsp+0x100x402622 一起传给了 string_not_equal. 这个静态地址指向的值是 DrEvil, 所以我们把 phase 3 的 key 改成:

1
7 0 DrEvil

再用 gdb 跑一下, 提示 Curses, you've found the secret phase! 并阻塞等待输入. 大概是成功进 secret_phase 了.

然后看 secret_phase 的逆向, 读入一行之后直接调用 strtol. 查标准库 prototype 可以知道参数含义, 推得语义是将读入的字符串转换为一个整数, 可知 secret phase 输入是一个整数. 先管它叫 x 好了.

接下来的判定可以发现要求 x <= 0x3e8, 然后将 0x6030f0x 作参数调用 fun7. 这个地址在 .data 段, 看一下符号信息:

1
2
3
   109: 0000000000400fce    62 FUNC    GLOBAL DEFAULT   13 func4
   110: 00000000006030f0    24 OBJECT  GLOBAL DEFAULT   24 n1
   111: 000000000040131b    29 FUNC    GLOBAL DEFAULT   13 string_length

一个大小为 24 字节的东西, 看一下对应的数据:

1
2
000030f0: 2400 0000 0000 0000 1031 6000 0000 0000  $........1`.....
00003100: 3031 6000 0000 0000 0000 0000 0000 0000  01`.............

0x24, 0x603110, 0x603130. 感觉又是俩地址, 找下符号:

1
2
   151: 0000000000603110    24 OBJECT  GLOBAL DEFAULT   24 n21
   107: 0000000000603130    24 OBJECT  GLOBAL DEFAULT   24 n22

又是俩 24 字节的东西, 继续找数据:

1
2
3
4
00003110: 0800 0000 0000 0000 9031 6000 0000 0000  .........1`.....
00003120: 5031 6000 0000 0000 0000 0000 0000 0000  P1`.............
00003130: 3200 0000 0000 0000 7031 6000 0000 0000  2.......p1`.....
00003140: b031 6000 0000 0000 0000 0000 0000 0000  .1`.............

0x8, 0x603190, 0x6031500x32, 0x603170, 0x6031b0

啊↑西↓, 又递归出俩地址, 你这tm是个树吧…

1
2
3
4
    61: 0000000000603190    24 OBJECT  GLOBAL DEFAULT   24 n31
   116: 0000000000603150    24 OBJECT  GLOBAL DEFAULT   24 n32
    68: 0000000000603170    24 OBJECT  GLOBAL DEFAULT   24 n33
   115: 00000000006031b0    24 OBJECT  GLOBAL DEFAULT   24 n34
1
2
3
4
5
6
7
8
00003150: 1600 0000 0000 0000 7032 6000 0000 0000  ........p2`.....
00003160: 3032 6000 0000 0000 0000 0000 0000 0000  02`.............
00003170: 2d00 0000 0000 0000 d031 6000 0000 0000  -........1`.....
00003180: 9032 6000 0000 0000 0000 0000 0000 0000  .2`.............
00003190: 0600 0000 0000 0000 f031 6000 0000 0000  .........1`.....
000031a0: 5032 6000 0000 0000 0000 0000 0000 0000  P2`.............
000031b0: 6b00 0000 0000 0000 1032 6000 0000 0000  k........2`.....
000031c0: b032 6000 0000 0000 0000 0000 0000 0000  .2`.............

彳亍口巴, 又炸出一堆地址…

不多 bb 了直接查符号找数据了

1
2
3
4
5
6
7
8
    73: 0000000000603230    24 OBJECT  GLOBAL DEFAULT   24 n44
    74: 0000000000603290    24 OBJECT  GLOBAL DEFAULT   24 n46
    75: 0000000000603250    24 OBJECT  GLOBAL DEFAULT   24 n42
    76: 00000000006032b0    24 OBJECT  GLOBAL DEFAULT   24 n48
   126: 0000000000603210    24 OBJECT  GLOBAL DEFAULT   24 n47
   127: 0000000000603270    24 OBJECT  GLOBAL DEFAULT   24 n43
   128: 00000000006031f0    24 OBJECT  GLOBAL DEFAULT   24 n41
   130: 00000000006031d0    24 OBJECT  GLOBAL DEFAULT   24 n45

一段连续的空间的样子…

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
000031d0: 2800 0000 0000 0000 0000 0000 0000 0000  (...............
000031e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000031f0: 0100 0000 0000 0000 0000 0000 0000 0000  ................
00003200: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00003210: 6300 0000 0000 0000 0000 0000 0000 0000  c...............
00003220: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00003230: 2300 0000 0000 0000 0000 0000 0000 0000  #...............
00003240: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00003250: 0700 0000 0000 0000 0000 0000 0000 0000  ................
00003260: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00003270: 1400 0000 0000 0000 0000 0000 0000 0000  ................
00003280: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00003290: 2f00 0000 0000 0000 0000 0000 0000 0000  /...............
000032a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000032b0: e903 0000 0000 0000 0000 0000 0000 0000  ................
000032c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

大概这一层应该是叶子了…以前是地址的地方现在是空指针…

感觉大概可以推测出这个结构体的定义:

1
2
3
4
5
struct Node{
    int val;
    Node* lch;
    Node* rch;
};

找一下 fun7 的逆向:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
0000000000401204 <fun7> (File Offset: 0x1204):
  401204:   48 83 ec 08             sub    rsp,0x8
  401208:   48 85 ff                test   rdi,rdi
  40120b:   74 2b                   je     401238 <fun7+0x34> (File Offset: 0x1238)
  40120d:   8b 17                   mov    edx,DWORD PTR [rdi]
  40120f:   39 f2                   cmp    edx,esi
  401211:   7e 0d                   jle    401220 <fun7+0x1c> (File Offset: 0x1220)
  401213:   48 8b 7f 08             mov    rdi,QWORD PTR [rdi+0x8]
  401217:   e8 e8 ff ff ff          call   401204 <fun7> (File Offset: 0x1204)
  40121c:   01 c0                   add    eax,eax
  40121e:   eb 1d                   jmp    40123d <fun7+0x39> (File Offset: 0x123d)
  401220:   b8 00 00 00 00          mov    eax,0x0
  401225:   39 f2                   cmp    edx,esi
  401227:   74 14                   je     40123d <fun7+0x39> (File Offset: 0x123d)
  401229:   48 8b 7f 10             mov    rdi,QWORD PTR [rdi+0x10]
  40122d:   e8 d2 ff ff ff          call   401204 <fun7> (File Offset: 0x1204)
  401232:   8d 44 00 01             lea    eax,[rax+rax*1+0x1]
  401236:   eb 05                   jmp    40123d <fun7+0x39> (File Offset: 0x123d)
  401238:   b8 ff ff ff ff          mov    eax,0xffffffff
  40123d:   48 83 c4 08             add    rsp,0x8
  401241:   c3                      ret

看上去有递归, 尝试改写成 C 代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
int fun7(Node* ptr, int x){
    if(ptr == 0)
        return 0xffffffff;
    else{
        int edx = ptr->val;
        if(edx <= x){
            if(edx == x)
                return 0;
            else
                return fun7(ptr->rch, x) * 2 + 1;
        }
        else
            return fun7(ptr->lch, x) * 2;
    }
}

看上去是个普通的 BST 搜索并记录路径.

回到 secret_phase 可以知道这个 phase 要求 fun7 返回值为 2, 需要给定 x 让它得到正确的搜索路径. 将硬编码的搜索树画出来可以得到:

如果要让返回值为 2 的话, 可以是 0x16 或者 0x14. 也就是十进制的 22 或 20.

于是最终答案就是:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
❯ cat bomb.key
Border relations with Canada have never been better.
1 2 4 8 16 32
3 256
7 0 DrEvil
yonuvw
4 3 2 1 6 5
20
❯ ./bomb bomb.key
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!
Halfway there!
So you got that one.  Try this one.
Good work!  On to the next...
Curses, you've found the secret phase!
But finding it and solving it are quite different...
Wow! You've defused the secret stage!
Congratulations! You've defused the bomb!

感觉好像没有想像的那么难

P.S.: 按 Ctrl-C 还可以体验 Dr. Evil 的特制 signal handler(