相比于直接看 bomblab phase_1 的答案,我更想搞懂答案之外涉及的每个函数的反汇编 - 反正是一个实验,代码能复杂到哪里去? 而搞懂这些函数, 无疑对于实际工程中的各种 debug 问题, 能补全基本的 gdb 调试技能。这一篇是分析 initialize_bomb() 函数.
查看 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代码。
(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 是这个函数指针的值, 查看其汇编代码:
(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);
}
首先拿出这几次反汇编中,人工写出的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. :-)
x /s 0x4024c0
命令再次被使用,使用了好几次, gdb 命令得到了强化