目录
实验题目:缓冲区溢出攻击实验
实验目的:加深对IA-32函数调用规则和栈结构的具体理解。
实验环境(硬件和软件):C语言、Linux系统
一、实验内容
目标:
对一个可执行程序“bufbomb”实施一系列缓冲区溢出攻击。
要求:
设法通过造成缓冲区溢出来改变该可执行程序的运行内存映像,继而执行一些原来程序中没有的行为,例如将给定的字节序列插入到其本不应出现的内存位置等。
概述:
实验中你需要对目标可执行程序BUFBOMB分别完成5个难度递增的缓冲区溢出攻击。5个难度级分别命名为Smoke(level 0)、Fizz(level 1)、Bang(level 2)、Boom(level 3)和Nitro(level 4),其中Smoke级最简单而Nitro级最困难。
二、实验过程
阶段1:Smoke
1.任务描述:构造一个攻击字符串作为bufbomb的输入,而在getbuf()中造成缓冲区溢出,使得getbuf()返回时不是返回到test函数继续执行,而是转向执行smoke函数。
2.实验设计:设计攻击字符串用来覆盖getbuf函数内的数组buf,进而溢出并覆盖ebp和ebp上面的返回地址。
3.实验过程:
在bufbomb的反汇编源代码中找到smoke函数,记下它的开始地址,如下图所示:
可以看见smoke函数首地址为0x8048c18。同样在bufbomb的反汇编源代码中找到getbuf函数,观察它的栈帧结构,如下图所示:
从上图我们可以看到getbuf的栈帧是0x38+4个字节,而buf缓冲区的大小是0x28(即40个字节)。所以构建攻击字符串的大小应该是0x28+4+4=48个字节。攻击字符串的最后4个字节应是smoke函数的地址。这样的攻击字符串如下图所示:
4.实验结果:如下图所示,阶段一成功!
阶段2:Fizz
1.任务描述:构造一个攻击字符串作为bufbomb的输入,而在getbuf()中造成缓冲区溢出,使得getbuf()返回时不是返回到test函数继续执行,而是转向执行fizz()。
2.实验设计:设计攻击字符串用来覆盖getbuf函数内的数组buf,进而溢出并覆盖ebp和ebp上面的返回地址以及传入fizz的cookie值。
3.实验过程:
在bufbomb的反汇编源代码中找到fizz函数,记下它的开始地址以及调用参数时的关于ebp偏移量,如下图所示:
从上图我们可以看见fizz函数首地址为0x8048c42,而且参数存放在[ebp+0x8]处。通过makecookie制造自己的cookie值,如下图所示:
于是攻击字符串如下图所示:
其中最后4个字节应是cookie的值。
4.实验结果:如下图所示,阶段二成功!
阶段3:Bang
1.任务描述:设计包含攻击代码的攻击字符串,所含攻击代码首先将全局变量global_value设置为你的cookie值,然后转向执行bang()。
2.实验设计:设计攻击字符串用来覆盖getbuf函数内的数组buf,进而溢出并覆盖ebp和ebp上面的返回地址,同时攻击字符串在覆盖缓冲区时写入函数的栈帧,当被调用函数返回时,将转向执行这段攻击代码。
3.实验过程:
在bufbomb的反汇编源代码中找到bang函数,记下它的开始地址以及调用全局变量时的偏移地址,如下图所示:
可以看见bang函数首地址为0x8048c9d,然后利用x/x命令来判断一些参数存放在0x804d100还是0x804d108处,如下图所示:
于是攻击字符串中代码部分应该如下图所示:
然后寻找栈桢中字符串存放的起始地址即0x55683150-0x28=0x55683128,如下图所示:
于是攻击字符串如下图所示:
其中最后4个字节0x55683128就是攻击代码存放的首地址。
4.实验结果:如下图所示,阶段三成功!
阶段4:Boom
1.任务描述:构造一个攻击字符串,使得getbuf函数不管获得什么输入,都能将正确的cookie值返回给test函数,而不是返回值1。
2.实验设计:设计攻击字符串用来覆盖getbuf函数内的数组buf,进而溢出并覆盖ebp和ebp上面的返回地址,同时攻击字符串在覆盖缓冲区时写入函数的栈帧,当被调用函数返回时,将转向执行这段攻击代码。
3.实验过程:
在bufbomb的反汇编源代码中找到test函数,记下它的调用getbuf的下一条语句的地址(0x8048dbe),如下图所示:
由于不能破坏栈桢,所以要执行到test函数调用gutbuf后,输入“123456”,观察test函数的栈桢中存放的ebp值,如下图所示:
其中0x08048dbe为getbuf返回值,所以0x55683180为所需要的ebp值。
由于C语言返回时是用eax来作为返回值的,所以攻击字符串中攻击代码部分应该如下图所示:
所以攻击字符串应该如下图所示:
其中反选部分就是防止栈破坏被发现而保护的ebp值。
4.实验结果:如下图所示,阶段四成功!
阶段5:Nitro
1.任务描述:与阶段四类似,构造一攻击字符串使得getbufn函数返回cookie值至testn函数,而不是返回值1。
2.实验设计:设计攻击字符串用来覆盖getbuf函数内的数组buf,进而溢出并覆盖ebp和ebp上面的返回地址,同时攻击字符串要求需要将cookie值设为函数返回值,复原/清除所有被破坏的状态,并将正确的返回位置压入栈中,然后执行ret指令以正确地返回到testn函数。
3.实验过程:
在bufbomb的反汇编源代码中找到getbufn函数,观察它的栈帧结构,如下图所示:
从上图我们可以发现字符串区域变大了很多,而且这次实验不同之处在于栈的位置不确定,显然不可能完全不确定,应该是一定范围内的不确定,仔细思考发现这个区域应该比0x208小,所以可以在攻击字符串中插入适量的nop指令来达到顺利的执行攻击代码。
在bufbomb的反汇编源代码中找到testn函数,记下它的调用getbufn的下一条语句的地址(0x8048e3a),如下图所示:
我们再来想一想攻击代码该怎么写,其实和level3也差不多,不同的是由于每次ebp的值可能是不同的,所以还原ebp只能是相对地址,于是又想到其实每次旧的ebp – 新的ebp = 常数。而这个常数是多少呢,gdb一下,得出0x20。但由于当我们的攻击代码运行时新的ebp的值已被覆盖,所以要另想一个办法间接地得出新的ebp。于是,想到当函数返回时,esp = 新的ebp - 8,得到:
旧的 ebp – (esp + 8)= 0x20
旧的 ebp = esp + 0x28
所以攻击字符串中的攻击代码应该如下图所示:
接下来寻找一个合适的返回地址,打开-n开关,执行五次testn后,观察攻击字符串的开始地址,所以总的攻击字符串如下图所示:
地址依次为0x55682f48、0x55682ef8、0x55682fa8、0x55682ed8、0x55682fb8,取最大的地址0x55682fe8作为返回地址,这样就能滑行到恶意代码处,然后执行恶意代码。
综上所述,总的攻击字符串如下图所示:
4.实验结果:如下图所示,阶段五成功!
三、实验小结
在这次二进制炸弹实验的过程中,我们得以深入探索了程序的机器级表现、汇编语言的运用、调试器的操作,以及逆向工程的技巧。借助反汇编和gdb调试工具,我们精准地设置了断点并逐步调试,最终成功地解析出了正确的字符串和数字,从而成功地拆除了多个炸弹阶段。此外,我们还从实验中观察到了一些程序的有趣现象和规律,这进一步加深了我们对程序结构和执行过程的理解。这次实验不仅锻炼了我们的实践能力和问题解决能力,也让我们对计算机科学有了更为深刻的认识。