CS:APP二进制炸弹开篇

来源:互联网 发布:知乎公司地址 编辑:程序博客网 时间:2024/06/04 19:09

写在前面

今天难得有空,特地找到《CS:APP》中的一个二进制炸弹实验来玩玩,试试自己能不能完成任务。这本书听说极其经典,有空还是要拜读拜读。

好,首先进入这本书的官网,点击左侧的Student Site后就可以看到阅读这本书时可以参考的额外资料,当然也包括本书提供的材料。点击进入labs的网页,发现第二个实验就是二进制炸弹。OK,下载本实验原材料bomb.tar,解压。建议在开始实际实验之前,阅读一下本实验的说明以及相关readme材料。

开始之前,交代一下我的环境,Ubuntu 17.04,, gdb 7.12.50.20170314-git,Binutils 2.28。OK,let's get started!!!


开始

解压缩bomb.tar,进入bomb目录:


README只是个简单说明,忽略之。

bomb很显然就是需要我折腾一番的可执行文件,bomb.c是源文件,但是不全(那是当然的了,不然还有什么意思呢?)。

先试着运行一下bomb可执行文件,看看啥情况:


我先胡乱输入一串字符“aaaaaaaaa”,触发了炸弹。第二次试着通过CTRL+C向程序发送SIGINT信号,没想到成功了,哈哈!bomb程序一定是安装了信号SIGINT的处理函数,并在信号处理函数中打印了以上两行字符,不过在打印过程中明显有停顿,停顿的时间点分别在打印字符串“Well ...”之前和之后,并且前一个停顿时间明显大于后一个停顿时间。这里先猜测是调用了什么延时函数吧! 当然,这只是猜测。

再次运行bomb程序,这次我发送了SIGQUIT信号(CTRL+\),程序强行退出了,证明程序并没有拦截SIGQUIT信号,至于有没有拦截其他信号也不得而知。无所谓啦,继续!

查看源程序bomb.c,满是“挑衅”性的注释,看来不“拆掉”这些炸弹,会被作者鄙视死的... ...

通过阅读bomb.c,可以总结以下两点:

  1. 可以通过两种方式来“拆”炸弹。第一种是通过标准输入stdin给出答案。第二种是在命令行上指定一个文件名,文件中包含需要的答案。我们可以一开始通过一步一步调试获得答案,然后将正确答案写在文件里,这样就避免了重复输入。
  2. 总共需要6个阶段才能完成“拆”弹任务。不过完成每个阶段的任务的方式是相同的 -- 从stdin或者文件中读取一行,如果读取的内容不对,触发炸弹,结束。内容正确,完成当前阶段,进入下一阶段,直到最后。

读取操作是通过封装的read_line函数做的,读取的内容传递给每个阶段的phase_X()函数,答案正确就printf一段文字,提示进入下一阶段。


小试牛刀

好了,在了解了程序的结构后,开始我们的拆弹之旅吧。

等一下,在bomb.c中我们好像并没有看到对信号SIGINT的处理啊,不过可以看到有个神秘的初始化函数调用initialize_bomb,从注释“Do all sorts of secret stuff that makes the bomb harder to defuse.”我猜测应该是在这个函数里安装了信号处理函数。用GDB反汇编探探究竟吧!

gdb bomb -q进入调试,输入start命令,程序运行,并停止在main函数入口。输入disassemble /m,找到调用initialize_bomb的汇编语句如下:


可以看到函数initialize_bomb的地址为0x4013a2,反汇编之:


initialize_bomb的地址果然是0x00000000004013a2。

函数initialize_bomb其实只做了一件事,就是调用signal函数安装SIGINT信号的处理函数,signal函数需要两个参数,第一个是信号在系统中的编号,第二个是信号处理函数的地址。通过阅读汇编代码,发现两个参数分别是通过寄存器%edi和%esi来传递的。传递给寄存器%edi的是立即数2,这正是SIGINT的系统编号,传递给寄存器%esi的是0x4012a0,这应该就是信号处理函数的地址了。这里要补充一下,因为这是x64平台,因此前几个参数默认通过寄存器来传递,不再像x86那样默认都是通过栈来传递的了。具体可以阅读文章《Stack frame layout on x86-64》或者直接参考x64 ABI文档。

通过命令info symbol 0x4012a0可以看到信号处理函数的名字叫sig_handler。反汇编之:


这个信号处理函数可以完美地解释上文观察到的现象。

先调用puts函数打印一段文字,文字存储在地址0x4024c0处,可以通过gdb的x命令验证内容为“So you think you can stop the bomb with ctrl-c, do you?”。

接着sleep 3秒。接着调用__printf_chk打印字符串“Well...”。接着调用fflush刷新标准输出stdout缓冲区。接着sleep 1秒。接着调用puts打印字符串“OK. :-)”。最后调用exit返回16。


总结

好了,针对这个二进制炸弹的开篇就到这里,接下来我打算对每个阶段的“拆弹”过程独自成篇,做到详尽明了。

《CS:APP二进制炸弹phase1》

《CS:APP二进制炸弹phase2》

《CS:APP二进制炸弹phase3》

《CS:APP二进制炸弹phase4》

《CS:APP二进制炸弹phase5》

《CS:APP二进制炸弹phase6》

最终所有的代码地址为:https://github.com/astrotycoon/CS-APP-binary-bomb