第五个炸弹

2025-05-18
1678字

phase_5的开头和结尾同样是保存寄存器和开辟栈空间操作:

1push   %rbx                          ;401062
2sub    $0x20,%rsp                    ;401063
3mov    %rdi,%rbx                     ;401067
36add    $0x20,%rsp                    ;4010ee
37pop    %rbx                          ;4010f2
38ret                                  ;4010f3

注意,这里将函数的第一个参数,也就是输入字符串的地址,放到了%rbx

接下来的部分与防止缓冲区溢出有关:

4mov    %fs:0x28,%rax                 ;40106a
5mov    %rax,0x18(%rsp)               ;401073
32mov    0x18(%rsp),%rax               ;4010d9
33xor    %fs:0x28,%rax                 ;4010de
34je     4010ee <phase_5+0x8c>         ;4010e7
35call   400b30 <__stack_chk_fail@plt> ;4010e9

这段代码和题目无关,如果感兴趣可以搜索栈金丝雀

PS:在2017年席卷全球Windows电脑的勒索病毒WannaCry,就是利用由缓冲区溢出引起的高危漏洞“永恒之蓝”进行攻击。直到今天,缓冲区溢出依然是“重要”漏洞贡献者。

 6xor    %eax,%eax                     ;401078
 7call   40131b <string_length>        ;40107a
 8cmp    $0x6,%eax                     ;40107f
 9je     4010d2 <phase_5+0x70>         ;401082
10call   40143a <explode_bomb>         ;401084
11jmp    4010d2 <phase_5+0x70>         ;401089

上面的代码比较好理解,xor用于将%eax置0(任何数异或本身的结果都是0),然后获取输入字符串的长度,长度不是6就引爆炸弹。所以这个炸弹的密码是一个长度为6的字符串。

4010d2处的代码如下:

30mov    $0x0,%eax                     ;4010d2
31jmp    40108b <phase_5+0x29>         ;4010d7

简单的将%eax置0,然后无条件跳转到40108b处,如果没有意外,这里的代码就是解密的核心:

12movzbl (%rbx,%rax,1),%ecx            ;40108b
13mov    %cl,(%rsp)                    ;40108f
14mov    (%rsp),%rdx                   ;401092
15and    $0xf,%edx                     ;401096
16movzbl 0x4024b0(%rdx),%edx           ;401099
17mov    %dl,0x10(%rsp,%rax,1)         ;4010a0
18add    $0x1,%rax                     ;4010a4
19cmp    $0x6,%rax                     ;4010a8
20jne    40108b <phase_5+0x29>         ;4010ac
21movb   $0x0,0x16(%rsp)               ;4010ae
22mov    $0x40245e,%esi                ;4010b3
23lea    0x10(%rsp),%rdi               ;4010b8
24call   401338 <strings_not_equal>    ;4010bd
25test   %eax,%eax                     ;4010c2
26je     4010d9 <phase_5+0x77>         ;4010c4
27call   40143a <explode_bomb>         ;4010c6
28nopl   0x0(%rax,%rax,1)              ;4010cb
29jmp    4010d9 <phase_5+0x77>         ;4010d0

整体浏览这个代码,发现在第20行jne回到了40108b,说明这是一个循环。在循环完成之后,进行了一次字符串比较,如果字符串相等就je 4010d9跳过引爆函数(4010d9是返回前的缓冲区检查入口)。

我们先看循环完成之后参与比较的两个字符串:

  • 第20行:将栈顶偏移22字节位置的那个字节置0。movb中的b代表Byte,也就是源操作数(第一个操作数)是一个字节大小。
  • 第21行:将0x40245e这个地址作为第二个参数,也就是比较的目标字符串地址。
  • 第22行:将栈顶偏移16字节处的地址作为第一个参数。这也和前面的6个字符以及22字节偏移呼应上了:偏移16 17 18 19 20 21存放这6个字符,偏移22作为C字符数组最后的\0\0就是数值0)。

那么下一步自然就是分析这个循环,看一下把哪6个字符赛到这个字符数组中了。

首先我们观察18-20行,有没有一种for(;i != 6; i ++)的既视感?对喽,这里的%rax就是用来充当这个i的,那初始值是多少呢?在跳过来之前%eax被置0 了,所以初值自然是0。先写上:

for (int i = 0; i != 6; i++) {

}

然后再分析这个只剩6行的核心代码:

12movzbl (%rbx,%rax,1),%ecx            ;40108b
13mov    %cl,(%rsp)                    ;40108f
14mov    (%rsp),%rdx                   ;401092
15and    $0xf,%edx                     ;401096
16movzbl 0x4024b0(%rdx),%edx           ;401099
17mov    %dl,0x10(%rsp,%rax,1)         ;4010a0
  • 第12行:z表示0扩展(高位补0),l表示目标操作数是一个long(4字节)。还记得%rbx是什么吧?——在函数入口处被用来保存输入字符串地址了。所以这条指令的意思就是把输入字符串的第%rax个元素放到%ecx中。
  • 第13-15行:将这个字符放到栈顶(%cl%rcx的低8位);又从栈顶放到%rdx;接着把这个字符和0xf做与运算,结果保存到%edx
  • 第16行:读取0x4024b0 + (%rdx)这个地址的字符(也就是首地址为0x4024b0的字符串的第%rdx个元素),放到%edx中。
  • 第17行:将这个字符(%dl%rdx的低8位)放到(%rsp) + 16 + (%rax) * 1这个地址上。这和前面的16也对上了。

s_input表示%rbx(输入字符串的地址)、s_key表示0x4024b0s_temp表示(%rsp) + 16s_ans表示0x40245e那么整个核心等价于以下C代码:

char s_temp[7];
s_temp[6] = 0;
for (int i = 0; i != 6; i++) {
    s_temp[i] = s_key[0xf & s_input[i]];
}
strings_not_equal(s_temp, s_ans);

s_keys_ans如下:

(gdb)x/s 0x4024b0
9x4024b0 <array.3449>: "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
(gdb)x/s 0x40245e
0x40245e: "flyers"

通过s_temp[i] = s_key[0xf & s_input[i]];逆推,应该很快就可以确定合法输入。

但是能用代码解决的事就不要动脑:

s_key = "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
s_ans = "flyers"

for i in range(6):
    target_c = s_ans[i]
    opt_indice = set(_i for _i, c in enumerate(s_key) if c == target_c)

    for x in range(127):
        if 0xF & x in opt_indice and chr(x).isprintable():
            print(chr(x), end=" ")
    print()

运行结果:

) 9 I Y i y
/ ? O _ o
. > N ^ n ~
% 5 E U e u
& 6 F V f v
' 7 G W g w

每行随便挑一个,组成长度为6的字符串就可以解除这个炸弹。以IO~U&'为例,写入answer.txt./bomb answer.txt运行:

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...

BOOM!!!
The bomb has blown up.