目录
1.Get ready(getbuf&&test)
将bufbomb的汇编代码输出到文件:
getbuf函数:
test函数:
2.LEVEL0 Candle
Your task is to get BUFBOMB to execute the code for smoke when getbuf executes its return statement, rather than returning to test. Note that your exploit string may also corrupt parts of the stack not directly related to this stage, but this will not cause a problem, since smoke causes the program to exit directly.
smoke函数的首地址:0x08048e0a
题目要求在test函数中调用getbuf后不返回test而跳到smoke的函数入口处。
看到getbuf函数汇编代码中,第4、5、6行,正是为调用Gets函数做准备,传入的参数是栈内的一个地址,容易知道是用来存储输入字符串的数组的首地址(%ebp-0x28),getbuf函数的栈帧大致如下:
|
|
ebp+0x4 | 返回地址 |
ebp | 旧ebp |
ebp-0x4 | Buf[35]~buf[39] |
ebp-0x8 | Buf[30]~buf[34] |
…… |
|
ebp-0x28 | Buf[0]~buf[3] |
| Call Gets |
- 如果要使得getbuf调用结束后转移到smoke函数的入口地址,只需要将函数的返回地址进行修改,可以通过数组的溢出,用字符串覆盖原来的返回地址处。
- 从图中可以看到,buf数组溢出到buf[44]~buf[47]的部分将覆盖返回地址。
- 那么我们输入的字符串应该含有至少48个字符,根据小端法,最后四个字符应的ascii码应该为:0a 8e 04 08
但是发现结果不对:段错误
注意到输入的字符串序列中含有0x0a是换行符,getbuf调用Gets函数用力啊输入数据,遇到0x0a会停止输入。
解决办法:
想想可不可以在原来的返回地址的基础上稍微增减,因为我们的getbuf函数最终是回不到test函数的,因此push ebp指令没有实际意义,因此把返回地址改为下一句mov esp,ebp即0x8048e0b
结果:
3.LEVEL1 Sparkler
Similar to Level 0, your task is to get BUFBOMB to execute the code for fizz rather than returning totest. In this case, however, you must make it appear to fizz as if you have passed your cookie as its argument. How can you do this?
FIZZ:
- fizz函数有一个参数 val,题目要求我们进入fizz函数的时候,需要构造函数的参数(为cookie),进入fizz函数可以有如下的入口地址:0x8048daf、0x8048db0
- 还是分析getbuf函数栈帧的结构:当我们要从getbuf函数退出,转移到返回地址处的时候,栈帧的esp指向返回地址,将返回地址弹出以后,esp继续上移4个字节
- 进入fizz后,如果从0x8048db0进入,首先执行mov esp,ebp,那么根据函数栈帧结构,此时的ebp或esp所指处,往上8个字节才是fizz函数的参数,因此我们传递给fizz的参数在返回地址上方12个字节处。
- 看到fizz的5、6行,知道cookie应该存在地址0x804d104内,gdb调试查看cookie的值为0x1005b2b7:
getbuf函数退出,转移到返回地址处的时候,getbuf函数栈帧:
|
|
|
|
esp |
|
| 返回地址 |
旧ebp | |
Buf[35]~buf[39] | |
………… | |
Buf[0]~buf[3] | |
| Call Gets |
结果:
4.LEVAL2 Firecracker
Similar to Levels 0 and 1, your task is to get BUFBOMB to execute the code for bang rather than returning to test. Before this, however, you must set global variable global_value to your userid's cookie. Your exploit code should set global_ value, push the address of bang on the stack, and then execute a ret instruction to cause a jump to the code for bang.
bang:
题目要求进入getbuf函数后不返回test,而是①修改全局变量global_value的值为我的cookie②进入bang函数
- 因为global_value是全局变量,首先我们gdb调试来查看它的地址:0x804d10c
- 要修改全局变量的值,需要相应的赋值代码,我们只能利用的是字符数组缓冲区溢出,那么我们想到先获取执行代码的权力,函数getbuf在return 返回地址前都拥有代码的执行权,那么我们可以将返回地址修改为我们已经设置好的代码的首地址,所以我们要得到buf的运行时地址:0x55683588
- 在getbuf中调用ret指令之前执行了leave指令:
Leave等价于:
movl %ebp %esp
popl %ebp
那么此时的getbuf函数栈帧中, ebp已经改变为相应保存的旧ebp处,esp转移到保存返回地址的位置。因此如果我们把返回地址更改为buf的起始位置,执行ret指令eip指向我们设置的代码处。执行完代码后想转移到bang函数入口,如果我们的代码不会改变esp指针的值,那么可以先更改esp指向的地址内容为bang函数的入口地址,然后再ret。
mov $0x1005b2b7,%eax /*将cookie保存到eax内*/
movl %eax,0x804d10c /*将eax值保存到global_value内*/
pushl $0x8048d52 /*将bang函数地址存到栈顶*/
ret /*eip转移到返回地址*/
因为我们输入文本的是二进制的数据,因此可以将以上的代码先汇编为.o文件:
|
|
| |
| |
| |
ebp+0x4 | 返回地址 |
ebp | 旧ebp |
ebp-0x4 | Buf[35]~buf[39] |
ebp-0x8 | Buf[30]~buf[34] |
…… |
|
ebp-0x28 | Buf[0]~buf[3] |
| Call Gets |
结果:
5.LEVEL3 Dynamite
![](https://i-blog.csdnimg.cn/blog_migrate/920eb9aa6e6e3f0ff666a5f77740b5cb.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3db416be8eb1a35cbaa1dd0864eb51f1.png)
![](https://i-blog.csdnimg.cn/blog_migrate/fa3df8ed6a4ba23a1e5854325c2b0d87.png)
mov $0x1005b2b7,%eax /*将cookie保存到eax内*/
pushl $0x8048e50 /*将test函数调用getbuf原本返回地址地址存到栈顶*/
ret /*eip转移到返回地址*/
|
|
| |
| |
| 执行buf中代码时,esp指向此处 |
ebp+0x4 | 返回地址 |
ebp | 旧ebp |
ebp-0x4 | Buf[35]~buf[39] |
ebp-0x8 | Buf[30]~buf[34] |
…… |
|
ebp-0x28 | Buf[0]~buf[3] |
| Call Gets |
![](https://i-blog.csdnimg.cn/blog_migrate/21f4f66dcacfac096d1045559a60a367.png)
6.LEVEL4 Nitroglycerin
![](https://i-blog.csdnimg.cn/blog_migrate/b1308ebf736877ca922f7097528dcdae.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a853a701f7073225fc54a3cf11e906d8.png)
这个函数与getbuf所不同的是,分配了512字节的字符数组,而调用getbufn的函数会在栈中随机分配一段存储区,这导致getbufn使用的栈基址ebp随机变化。此外,在Nitro模式运行时,bufbomb会要求提供5次输入字符串,每一次都要求getbufn的返回值为实验者的cookie。
- 因为调用完getbufn后转移到我们的恶意代码,执行完毕后再回到testn,那么要保证我们回到testn时寄存器状态不变,即ebp和esp不能改变
- 因为我们要覆盖返回地址,那么必然会覆盖旧ebp的值,在覆盖的时候我们无法确定一个固定的testn的ebp的值,所以我们在代码中恢复ebp的正确值。
- 看testn中调用getbufn时esp指针距离ebp指针0x28个字节(在esp-0x24之前还有push ebx),getbufn函数没有参数,因此在getbufn中调用ret指令后指向getbufn函数栈帧ebp再往上8个字节,即恢复到了testn函数调用getbufn时的esp位置,因此旧ebp的恢复可以通过指令:leal 0x28(%esp),%ebp
- 所以我们的恶意代码的任务:①将寄存器eax的内容改为cookie②恢复testn的ebp③执行完跳转到testn中调用getbufn的下一条指令地址(指令的地址是不变的,至少运行时栈的地址变化:call的下一条指令地址是0x8048ce2。
- 代码:
leal -0x28(esp),ebp /*恢复testn的ebp*/
mov $0x1005b2b7,%eax /*将cookie保存到eax内*/
pushl $0x8048ce2 /*将返回地址压栈*/
ret
要一开始修改返回地址到我们的恶意代码的起始位置,我们无法确定一个固定的地址,因此在代码前加入很长的一段nop序列,成为空操作雪橇,如果我们的返回地址是这段序列中的一个,那么就可以顺着nop序列到达我们的恶意代码的入口:
知道我们的代码恶意代码序列长度为15字节,结束位置在ebp+0x3,因此入口在ebp-0xb,buf数组起始地址是ebp-0x208,因此nop序列长度为0x208-0xb=509
|
|
| |
| |
| 执行buf中代码时,esp指向此处 |
ebp+0x4 | 返回地址 |
ebp | 旧ebp |
ebp-0x4 |
|
…… | …… |
ebp-0xc | nop 恶意代码入口 |
| nop nop nop nop |
nop nop nop nop | |
…… | |
ebp-0x208 | Buf[0]~buf[3]:nop nop nop nop |
|
gdb调试查看运行时调用四次getbufn时,其ebp的值各为多少:发现最大为0x556835e0最小为0x55683560,那么恶意代码的首地址最小为ebp-0xb=0x55683560-0xb=0x55683555
结果:
7.Summarize
- 这个实验利用缓冲区的溢出来进行攻击,要求我们十分熟悉函数栈以及函数调用时栈帧的变化情况
- 综合利用反汇编后端代码和gdb调试来查看程序运行时的寄存器状态和内存里的数据,来必须能够熟练地预判栈指针的变化情况
- 在最后的几个LEVEL更是要求我们掌握编写汇编代码
- 要利用函数调用结束后的ret指令使得eip的值修改为函数的返回地址,以此让恶意代码获得执行权
- 要注意当我们在执行完恶意代码以后如果要把执行权力还给原本要返回的函数,要恢复寄存器的状态