一:准备工作
1,三个二进制文件
bufbomb:一个有缓冲区溢出漏洞的程序。
makecookie:可以根据用户不同的userid生成的唯一的cookie,userid不同,cookie不同,所要解决的方法就不同。
hex2raw:使编写的缓冲区利用代码的转换为一个字符串的格式,只有经过转换以后才可以输入到getbuf中。
2,生成cookie的方法
3,bufbomb函数原型及介绍
getbuf函数类似于gets函数,它是读入一段字符串,字符串以\n或者eof表示结束,并把存储起来,但是getbuf提供的缓冲区只有32个字符大小,但是getbuf本身又对输入的字符是否超过缓冲区大小进行安全检查,从而带来了缓冲区溢出漏洞。
使用方法:
参数:
-u,确保不同的userid用不同的cookie。
-n,为了level4,栈基址随机化模式的时候使用。
-s,上传到服务器进行打分。
4,提交方式
5,提示
二:实验
Level 0:
实验描述:
test函数调用getbuf函数,调用完以后还返回test函数,现在我们要做的是调用getbuf函数后,通过输入我们的exploit,使得调用完以后不返回test函数了而是执行smoke函数。
test函数:
解决方法:
反汇编:
在反汇编结果中找到getbuf函数,smoke函数:
Getbuf函数:
画出test函数调用getbuf函数的栈帧结构:
由反汇编结果可知,给输入的字符串分配的空间是从%ebp-0x28开始的,换为10进制就是40个字节,而返回地址是在%ebp+0x4处,push %ebp本身又占了四个字节,所以结构为:
返回地址 4字节 |
Getbuf的,从%ebp到输入字符串的空间为44个字节 |
Smoke函数:
由反汇编可得smoke函数的入口地址为0x08048e0a
综上,我们需要做的就是把上面的44个字节随意填满(不要填换行),然后把原来的返回地址改为smoke函数的入口地址。0x0a是换行\n的ASCII值,所以不可以输入,那么我们就输入0x8048e0b来代替。
Level1:
实验描述:
test函数调用getbuf函数,调用完getbuf以后不返回getbuf的调用者test而是去执行fizz函数。fizz函数要求传入参数,参数必须是cookie。
fizz函数:
解决方法:
反汇编bufbomb找到fizz函数:
和level0类似,通过上一题已经知道了栈帧结构,所以我们需要做的还是把那44个字节填满,然后再填写fizz函数的入口地址(0x08048daf)用来覆盖原来的返回地址。
关键:找到fizz函数的参数从栈中的什么地方传入的,然后我们把我们的cookie写进这个fizz会获取参数的地方。实参只在主调函数中有效,形参只在被调函数中有效,我们要做的就是修改实参,它的位置就是在返回地址的上面4个单位。而返回地址已经被我们破坏,会默认它上面四个字节为返回地址,然后再向上4个字节来取参数。
执行leave后
所以答案如下:
Level2:
实验描述:
执行完getbuf()后,不返回到test,而是去执行函数bang,但是区别是bang也要传入参数,且参数是是一个全局变量。
bang函数:
解决方法:
反汇编:
看bang函数的反汇编代码
可以看到,bang、函数的入口地址为0x08048d52.
接下来要做的就是改变全局变量global_value的值,使他的值为cookie
由汇编代码,第四行 mov 0x804d10c,%eax 可以知道,global_value存放的位置是0x804d10c
由此写下汇编代码:
首先把我们的cookie写到全局变量的地址中,然后在把bang的入口地址入栈,通过ret指令来执行bang函数
然后把.s文件变成字节码:
利用gdb调试找到我们的exploit的地址了,用我们的地址来覆盖返回地址,从而执行我们的代码。
所以答案如下:
Level3:
修改getbuf()函数的返回值(正常状态为0x1)为你的cookie值,然后让函数正常返回到test.
解决方法:
函数调用栈,函数调用结束以后,栈被释放,而返回结果会放在eax寄存器中。test不需知道调用的getbuf是怎么执行的,只需要到eax寄存器中去取返回值,所以可以在getbuf执行完以后,再把eax寄存器中的值动手脚修改为cookie。
有关栈的恢复:需要两个部分,一个是ebp一个是eip的地址,一个是恢复pop出test的原ebp,所以在破坏之前,用gdb调试出来test的原ebp是多少记录下来,恢复的时候在赋值给它。
用gdb来调试得到ebp的值:
原test的ebp是0x556839f0
eip的地址,就是返回地址,也就是test中在callgetbuf函数的下一条指令的地址:
call的下一条指令的地址是0x8048e50
所以.s代码:
%eax的值改为cookie
%ebp的恢复,改成0x556839f0
把下一条指令地址0x8048e50压入
返回
将.s文件变成字节码:
所以答案是:
第一句 第三句 ret
填充0
%ebp恢复
自己的返回地址
Level4:
调用getbufn函数,
其缓冲区大小为512个字节, 且每次栈的位置都会变化
nop只是执行eip自加1不进行其他的操作。在无法猜测的时候,只需要找到最大的地址。
解决方法:
反汇编,查看getbufn反汇编结果。
buf的首地址为-0x208(%ebp)为十进制520个字节大小。
每次运行testn的ebp都不同,所以每次getbufn里面保存的test的ebp也是随机的,但是栈顶的esp是不变的,我们就要找到每次随机的ebp与esp之间的关系来恢复ebp。我们先通过调试来看一下getbuf里面保存的ebp的值的随机范围为多少。
ebp的值 减去0x208为buf的首地址
0x556839c0 0x556837b8
0x556839f0 0x556837e8
0x55683970 0x55683768
0x556839f0 0x556837e8
0x55683950 0x55683748
看testn的反汇编代码:
call getbufn的下一条指令的地址为0x8048ce2
此外,还可以看到,mov %esp,%ebp 此时esp和ebp相等
push %ebx 此时ebp=esp+0x4
sub $0x24,%esp 这个时候执行完后,ebp=esp+0x28,这就是esp和ebp每次的变化关系,通过esp来恢复我们的每次的ebp
由此写出以下汇编代码:
将.s文件变成字节码:
509个nop
15个字节码,覆盖篡改保存ebp
Buf首地址覆盖返回地址,是可能的最大的首地址
三:总结
要想做明白这个实验,就需要弄懂缓冲区溢出原理,以及堆栈的过程,函数调用的实现过程,函数传参的底层实现等问题。并且,光理解原理也还远远不够,还需要会应用。本次实验的几个level是逐步跟进的,一点一点的深入。Level0只需要理解原理,进行覆盖地址,level1是修改参数,level2是修改全局变量,level3是恢复栈结构,level4是在level3第基础上实现随机化。在实验的过程中还需要注意避免输入换行对应的数字码等等。
另外,在做本次实验的过程中还遇到一个比较特别的问题,这个问题很隐蔽,难以发现,花费了很久才找到。在将自己写的code4.s汇编的过程中,出现了机器码生成错误的问题,mov指令对应的机器码应该是b8,而在我的机器中,level4,却生成了a1,至今还不知道为什么会出现这个问题。在与其他同学交流的过程中,发现其他同学也有过这种情况,是push指令对应的机器码发生了错误。
截图如下: