CSAPP - 反编译 initialize_bomb()

CSAPP - 保持好奇,反汇编 initialize_bomb()

相比于直接看 bomblab phase_1 的答案,我更想搞懂答案之外涉及的每个函数的反汇编 - 反正是一个实验,代码能复杂到哪里去? 而搞懂这些函数, 无疑对于实际工程中的各种 debug 问题, 能补全基本的 gdb 调试技能。这一篇是分析 initialize_bomb() 函数.

好奇 - Ctrl-C 被接管了?

查看 bomb.c 可以看到,除了读取文件、打印提示信息,在 phase_1() 之前还做了一件事:初始化 bomb:

int main()
{
    ...
    /* Do all sorts of secret stuff that makes the bomb harder to defuse. */
    initialize_bomb(); // 好奇这里
    
    read_line();
    phase_1();
    ...
}

如果是第一次运行 bomb, 输入字符串后希望临时退出, 会发现 Ctrl+C 并不能立即退出。原因是 initialize_bomb() 里使用了信号量,捕获了 Ctrl+C。 当然,这是一个猜测,需要从汇编代码验证。 和上一篇的方式一样,先获取汇编代码, 再逐句翻译, 随后整理和简化C代码。

反汇编 initialize_bomb() - 翻译出来了,但是很懵

(gdb) disassemble initialize_bomb
Dump of assembler code for function initialize_bomb:         // void initialize_bomb() {
   0x00000000004013a2 <+0>:     sub    rsp,0x8               //
   0x00000000004013a6 <+4>:     mov    esi,0x4012a0          // void* p2 = 0x4012a0;
   0x00000000004013ab <+9>:     mov    edi,0x2               // int p1 = 2;
   0x00000000004013b0 <+14>:    call   0x400b90 <signal@plt> // signal(p1, p2);
   0x00000000004013b5 <+19>:    add    rsp,0x8               //
   0x00000000004013b9 <+23>:    ret                          // }
End of assembler dump.

于是踉踉跄跄的写出 C 代码:

void initialize_bomb()
{
    void* p2 = 0x4012a0;
    signal(2, p2);
}

其中 void* p2 = 0x4012a0 让人费解, 需要结合 signal 的函数原型分析:

man signal


NAME
       signal - ANSI C signal handling

SYNOPSIS
       #include 

       typedef void (*sighandler_t)(int);

       sighandler_t signal(int signum, sighandler_t handler);

可以发现, signal() 第二个参数是一个函数指针, 对应到 initialize_bomb() 函数中, 0x4012a0 是这个函数指针的值, 查看其汇编代码:

神奇的 0x4012a0 - 来反汇编吧!

(gdb) disassemble 0x4012a0
Dump of assembler code for function sig_handler:                    // sig_handler() {
   0x00000000004012a0 <+0>:     sub    rsp,0x8                      //
   0x00000000004012a4 <+4>:     mov    edi,0x4024c0                 // const char* p1 = (char*)0x4024c0; , 用 x /s 查看知道是 "So you think you can stop the bomb with ctrl-c, do you?"
   0x00000000004012a9 <+9>:     call   0x400b10 <puts@plt>          // puts(p1);
   0x00000000004012ae <+14>:    mov    edi,0x3                      // int p2 = 3;
   0x00000000004012b3 <+19>:    call   0x400c50 <sleep@plt>         // sleep(p2);
   0x00000000004012b8 <+24>:    mov    esi,0x402582                 // int p3 = 0x402582;    0x402582 值为 "Well..."
   0x00000000004012bd <+29>:    mov    edi,0x1                      // int p4 = 1;
   0x00000000004012c2 <+34>:    mov    eax,0x0                      // int ret = 0;
   0x00000000004012c7 <+39>:    call   0x400c00 <__printf_chk@plt>  // __printf_chk(p4, p3);  __printf_chk 是 printf 的安全版本,会检查格式字符串有效性
   0x00000000004012cc <+44>:    mov    rdi,QWORD PTR [rip+0x20246d]        # 0x603740 
   0x00000000004012d3 <+51>:    call   0x400be0 <fflush@plt>        // fflush(stdou);
   0x00000000004012d8 <+56>:    mov    edi,0x1                      // int p5 = 1;
   0x00000000004012dd <+61>:    call   0x400c50 <sleep@plt>         // sleep(p5);
   0x00000000004012e2 <+66>:    mov    edi,0x40258a                 // const char* p6 = (char*)0x40258a; "OK. :-)"
   0x00000000004012e7 <+71>:    call   0x400b10 <puts@plt>          // puts(p6);
   0x00000000004012ec <+76>:    mov    edi,0x10                     // int p7 = 16;
   0x00000000004012f1 <+81>:    call   0x400c20 <exit@plt>          // exit(p7);   // 返回16
End of assembler dump.

这段代码,相比于 phase_1 本身的代码,有意思的多。

首先我们验证, 当启动 bomb 程序后,输入 Ctrl+C, 并等待5秒左右,是否会退出,返回值是什么:

zz@Legion-R7000P% ./bomb
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
^CSo you think you can stop the bomb with ctrl-c, do you?
Well...OK. :-)
zz@Legion-R7000P% echo $?
16

返回值的确是16,和直接从汇编代码看到的一致。

对应的C代码,整理一下:

void sig_handler(int )
{
    puts("So you think you can stop the bomb with ctrl-c, do you?");
    sleep(3);
    printf("Well...");
    fflush(stdout);
    sleep(1);
    puts("OK. :-)");
    exit(16);
}

void initialize_bomb()
{
    signal(2, sig_handler);
}

验证 - 捕获 Ctrl-C, 是这么玩的吗?

首先拿出这几次反汇编中,人工写出的C代码:

#include 
#include 
#include 
#include 
#include 

int string_length(const char* str)
{
    for (const char* p = str; ; p++)
    {
        int ret = p - str;
        if (*p == 0)
        {
            return ret;
        }
    }
}

int strings_not_equal(const char* s1, const char* s2)
{
    const char* p1 = s1;
    const char* p2 = s2;
    int len1 = string_length(s1);
    int len2 = string_length(s2);
    if (len1 != len2)
    {
        return 1;
    }
    while (true)
    {
        char c1 = *p1;
        if (c1 =='\0')
        {
            return 0;
        }
        if (c1 != *p2)
        {
            return 1;
        }
        p1++;
        p2++;
    }
}

void sig_handler(int)
{
    puts("So you think you can stop the bomb with ctrl-c, do you?");
    sleep(3);
    printf("Well...");
    fflush(stdout);
    sleep(1);
    puts("OK. :-)");
    exit(16);
}

void initialize_bomb()
{
    signal(2, sig_handler);
}

然后添加一小段调用代码, 也就是 main() 函数: 每隔1秒打印一个 hello world N, N 是递增的数字。 而在打印 hello world N 的过程中, 如果按下了 Ctrl+C, 就会捕获到信号,打印出和 bomblab 一样的输出,并最终结束:

int main()
{
    initialize_bomb();
    int i = 0;
    while (true)
    {
        printf("hello world, %d\n", i);
        i += 1;
        sleep(1);
    }
    return 0;
}

运行一下, 的确如此, 就是这么玩的:

zz@Legion-R7000P% gcc test.c
zz@Legion-R7000P% ./a.out
hello world, 0
hello world, 1
hello world, 2
^CSo you think you can stop the bomb with ctrl-c, do you?
Well...OK. :-)

总结

  1. 保持好奇, 保持一定可以搞清楚的信念, 汇编之下, 了无秘密。
  2. x /s 0x4024c0 命令再次被使用,使用了好几次, gdb 命令得到了强化
  3. 看到往 rsi 寄存器(函数第二个参数)存入莫名奇妙的数字,不要慌,它就是一个函数的地址, 照常去 disas 它,没有难度, sig_handler() 很简单
  4. printf 和 __printf_chk, 这里确实是可以忽略和猜测的函数
  5. 整理好每一个反汇编出来的函数, 然后按自己想法,添加 main 函数去验证, bomb lab 的神秘性一点一点被拨开。

你可能感兴趣的:(汇编,c语言,gdb)