第一个炸弹

2025-05-18
1662字

gdb基本用法

进入bombbomb.c所在的目录之后,运行gdb -tui bomb进入GDB Text User Interface界面。敲一下回车然后依次输入layout splitlayout regs,此时你的终端应该长这样:

最上面的一栏显示寄存器情况(现在程序还没运行所以没有内容),中间一栏显示汇编代码,下面一栏用于输入命令。

接下来查看bomb.c,发现第74行是phase_1的入口,因此我们用break 74或者b 74命令在这一行打上断点,这样在执行run命令时,程序就会在这一行执行之前停下。

打完断点之后,执行run answer.txt命令让程序运行。这样在后续执行过程中,程序就会从answer.txt读取内容。

解密

执行run answer.txt之后如下图:

call phase_1之前,还执行了mov %rax,%rdi,这条指令的意思是把%rax的值放到%rdi中,同时%rax的值保持不变。对照通用寄存器的用途约定,这个操作的意思是将read_line函数的返回值作为phase_1的第1个参数。和input = read_line(); phase_1(input);这两行源码是对应的。也就是说,%rdi此时是一个char *,指向的字符串就是answer.txt的第一行(实际上是这个字符串的值是第一行的内容,能明白这个意思就行)。

接下来我们输入stepi 2,意思是再执行两条指令,然后停下,这时候我们应该进入函数phase_1内部了:

函数逻辑很简单,总共也就8条指令,我们一条条分析:

  • sub $0x8,%rsp:栈顶指针往下移8个字节,也就是开辟8个字节的临时空间给当前函数用。
  • mov $0x402400,%esi:将0x402400这个数字放到%esi中,显然这应该是一个地址,地址只需要32bit(因为是%esi而不是%rsi)说明编译代码时的环境是32的系统,但是没关系,在64位系统上也是兼容的。%esi的约定功能是函数的第2个参数,注意此时%rdi的值还没有改变,也就是函数string_not_equal的入参是两个地址,从函数名字来看,这两个地址都是字符串的首地址,函数内部判断这两个字符串是否不相等。
  • call 0x401338:调用位于这个地址的函数。<string_not_equal>是注释内容,不包括在指令内。不过从这个注释也能看出,在编译器眼中,函数名其实就是函数的入口地址,这和数组名就是数组首地址是一样的逻辑。
  • test %eax,%eax%eax的值和自己做与运算,但是不保存计算结果,只是用这个计算结果更新标志寄存器。
  • je 0x400ef7:Jump if Equal to zero。检查ZF标志位,如果ZF是1(意味着上一条指令的计算结果是0),就跳转到0x400ef7这条指令。
  • call 0x40143a:从注释来看,这个函数用于引爆炸弹。
  • add $0x8,%rsp:归还栈帧空间。
  • retphase_1函数指定完毕,返回。参考Introduction中的介绍。

总结起来就是,比较answer.txt的第一行是否和0x402400这个地址的字符串相等,相等返回0否则返回1(这也和函数名含义一致),然后判断返回值是否是0,如果是0,跳过引爆函数。

所以第一个炸弹的密码就是0x402400这里的字符串,直接用x /s 0x402400命令读取这个字符串即可。

结果是Border relations with Canada have never been better.

然后输入qquit退出程序,将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?

BOOM!!!
The bomb has blown up.

总结

第一个炸弹比较简单,只需要了解基本的x86指令和gdb用法即可拆除。

在实际操作中,可以多使用stepi(没有参数默认执行1条指令)一步步执行指令,观察寄存器值的变化,辅助理解或者验证指令的执行。另外对于非核心函数,也可以用nexti(也可以指定参数)跳过函数的执行,或者在函数内部时用finish命令执行完当前函数。

退出gdb可以输入qquitexit