Look Mom, I dont use Shellcode議題Exploit復現
*本文作者:elli0tn0phacker,本文屬於FreeBuf原創獎勵計劃,未經許可禁止轉載
0×00 背景
Look Mom, I don’t use Shellcode/">Shellcode 是Moritz Jodeit在2016 HITB GSEC上的一個議題。議題詳細介紹了在64位Windows10上利用IE11的漏洞實現RCE,並進一步繞過EPM和EMET5.5等一系列Mitigation的技術細節。值得注意的是該議題介紹的技術內容獲得了2016年MSRC Mitigation Bypass Bounty program十萬美元的最高獎金。
筆者在學習了議題的PPT後嘗試了exploit的復現,其中遇到不少坑,最終還是實現了Win10下的RCE。這裡記錄下整個復現過程,與大家分享,不過水平有限,文中錯誤之處懇請斧正。
0×01 漏洞成因
議題中使用的漏洞是Typed Array Neutering漏洞 (CVE-2016-3210)。這是一個品相非常好的UAF漏洞。這裡使用了HTML5的特性web worker來觸發漏洞。傳統的JavaScript是在單執行緒環境中工作的,在一個Context中無法同時執行多個js指令碼,web worker提供了JavaScript多執行緒執行環境,主執行緒通過worker可以建立一個不含DOM操作的worker執行緒用來做資料處理,通過postMessage向worker執行緒傳送資料,被髮送的資料會將所有權從當前的Context轉移到Worker執行緒的Context。
該漏洞的成因是主執行緒使用postMessage將ArrayBuffer傳給Worker執行緒後,ArrayBuffer會在主執行緒的Context中被釋放,然而建立這個ArrayBuffer的TypedArray仍然保留了被釋放的ArrayBuffer的指標,從而造成UAF。
首先分析PPT裡的PoC:

開啟HPA和UST,在IE11中開啟PoC,很遺憾在我的環境裡並沒有Crash!寫資料不太好發現問題,嘗試換成讀資料分析沒有復現Crash的原因:

可以看到這裡讀取的資料顯示的是undefined,PoC中定義的Int8Array大小為0×42,這裡嘗試訪問索引為0×4141的陣列元素,結果顯示為undefined,可以猜測索引越界的Int8Array在我的除錯環境不會嘗試去讀Int8Array儲存的ArrayBuffer資料,這裡其實也可以用正常的js程式碼測試,結果也是一樣的。
通過分析jscript9.dll可以知道訪問陣列元素會呼叫 jscript9! Js::JavascriptOperators::OP_GetElementI 函式,在windbg中對這個函式下斷點,分析為什麼PoC不會Crash。

可以看到這裡有一個比較運算,比較的雙方正是陣列元素索引0×4141和TypedArray長度0×42,如果陣列索引大於等於TypedArray的長度,就會跳轉到剛才彈出undefined的分支,這裡嘗試修改TypedArray的長度為0×4142看看執行情況:

可以看到沒有進入剛才彈出undefined的分支,並且IE Crash了,所以我們只需要修改PoC保證TypedArray的Index小於TypedArray的大小即可,最終修改的PoC如下:

再次載入PoC,IE Crash,分析Crash現場:

顯然在boom函式中 執行到 array[0x41] = 0×42 時,array儲存的ArrayBuffer已經被 worker.postMessage(0,[array.buffer]) 釋放了,導致array[0x41]的記憶體讀取失敗。
目前為止,我們的得到的資訊有:
1)通過worker.postMessage我們可以釋放TypedArray的ArrayBuffer的記憶體; 2)TypedArray仍保持被釋放ArrayBuffer記憶體的引用,並且被釋放記憶體大小指令碼可控;
因此通過這個漏洞,我們可以獲得一個懸掛指標TypedArray,指向記憶體的大小指令碼可控,我們可以填充期望的資料結構到這塊記憶體,並通過懸掛指標來讀寫這片記憶體,最終任意地址讀寫。
0×02 任意地址讀寫
Jscript9.dll中一般使用TypedArray作為任意地址讀寫的利用物件,通過修改TypedArray+0x1C的length和+0×20的ArrayBufferAddress就可以實現使用者態任意地址讀寫(可以參考筆者之前的文章: ofollow,noindex">https://www.freebuf.com/vuls/188558.html )。
PPT中的方法可以簡述為如下步驟:
1)通過觸發漏洞可以獲得一個0xb8 Bytes大小的懸掛指標,使用同樣分配在CRT堆的LargeHeapBlock佔位(LargeHeapBlock是IE Custom Heap 維護大塊記憶體的資料結構);LargeHeapBlock在Unlink的時候存在一個未受保護的雙向連結串列解除安裝操作,通過corrupt這個LargeHeapBlock前向指標和後向指標可以實現任意2個DWORD的修改;
2)通過HeapSpray進行期望記憶體的排布,通過讀取LargeHeapBlock+0×8的資料洩露IE custom Heap中裡某個JavascriptArray的Data區域地址,再通過1)的寫操作corrupt JavascriptArray Data區域的長度欄位,從而實現Array的越界讀寫;
3)通過越界讀寫JavascriptArray修改期望的TypedArray,最終實現任意地址讀寫;
可惜的是,筆者在第一步嘗試復現LargeHeapBlock的Unlink操作的時候就遇到的問題,無法實現DWORD寫入,因此這裡筆者嘗試換了一種比較暴力的方式實現任意地址讀寫:
1)由於這個漏洞得到的懸掛指標可以指向指令碼可控大小的記憶體,這裡可以考慮控制一塊較大的記憶體塊如1MB;
2)通過大量申請和釋放CRT堆記憶體操作觸發系統堆的釋放操作,使得大塊記憶體被合併釋放;
3)通過大量TypedArray的申請操作,讓jscript9.dll從上面那個被合併釋放記憶體申請新的IE custom heap,從而有可能會有TypedArray被儲存在懸掛指標指向的1MB記憶體;
4)通過懸掛指標找到一個TypedArray並修改長度實現任意地址讀寫;記憶體佈局部分的指令碼實現如下:
preheapspray() 函式用來在CRT堆申請一百多MB的大塊記憶體,然後申請觸發漏洞獲得了1MB懸掛指標的記憶體,釋放記憶體後,再通過 heapspray() 做期望利用的記憶體佈局,動態除錯情況如下:
1)preheapspray申請大塊記憶體:
2)觸發漏洞,釋放ia指向的記憶體,再利用heapspray佔位記憶體:
最終我們利用HeapSpray在ia指向的記憶體區域找到了可以利用的TypedArray,接下來可以在js指令碼中利用ia搜尋TypedArray,並修改其+0x1c的length屬性0×20的ArrayBufferAddress屬性即可實現任意地址讀寫:
獲得任意地址讀寫許可權後我們仍然需要獲得洩露物件地址的方法,這裡通過之前的HeapSpray我們可以在0x0x1cee0020處穩定找到一個JavascriptArray的data區,我們可以利用這個JavascriptArray的第一個元素實現物件地址洩露:
0×03 執行Shellcode
因為Win10下CFG保護的存在,通過篡改受CFG保護的物件虛擬函式指標執行Shellcode的方法不再奏效。但是作者發現存在漏洞的Win10中jscript9.dll的safemode標誌並未受hash保護,因此通過修改safemode標誌開啟Godmode通過ActiveX執行shellcode的方式又可以奏效了:
這裡我們就可以通過之前獲得任意地址讀寫許可權和洩露物件地址的許可權來修改safemode,通過ActiveX第一次成功彈出系統的計算器:
顯然僅僅彈出系統的計算器是不夠的,接下來我們需要執行自己的payload,比如讓一個PE落地並執行。首先先寫一個簡單的彈出cmd的win32程式,為了能在目標環境成功執行還需要將Runtime lib修改為MT並將編譯優化開到最大減少PE的體積:
再將生成的PE檔案轉成base64編碼儲存在js指令碼的payload變數,通過ADODB.Stream儲存到Temp路徑執行,這樣一個自己定義的payload就成功執行起來了:
看上去我們的exploit編寫工作已經完成了,然而當我們嘗試通過訪問web server的方式載入這個exploit的時候,發現payload並未如預期出現,開啟IE的除錯視窗,發現如下錯誤:
看上去我們還需要繞過瀏覽器的同源策略。通過分析可以知道,當指令碼呼叫 bStream.SaveToFile() 儲存資料的時候,會呼叫 msado15!CStream::SaveToFile 函式, msado15!CStream::SaveToFile 在儲存資料之前會呼叫 msado15!SecurityCheck 做安全檢查,我們在 msado15!SecurityCheck 函式入口點下斷點:
可以看到這裡edx和ecx分別儲存了源地址和目的地址,如果將源地址修改為“C:\”的話就可以發現payload.exe成功落地了,現在我們就需要從指令碼層面來修改源地址:
這時候我們再通過本地檔案的方式(注意,這裡只能通過本地檔案的方式來測試)開啟這個exploit。可以發現payload.exe已經成功落地並運行了,但是當我們通過web訪問的時候,我們的payload依然不能執行,這就涉及到最後一個問題了,IE sandbox逃逸。
0×04 IE Sandbox 逃逸
Windows10的IE11預設開啟Protect Mode, 用Process Explorer檢視訪問web server的IE render程序會發現render程序是工作在Low Integrity許可權下的:
low Integrity下的程序在檔案操作,建立程序等都會受到許可權限制,因此我們無法在low Integrity下儲存我們的payload並執行,所以要突破low Integrity的限制。
IE瀏覽器有著Zone的概念,對於local intranet或者Trusted sites,Protect Mode將不會生效(比如之前我們通過本地檔案開啟exploit就是Medium Integrity):
因此這裡的思路是將exploit分成兩個stage,第一個stage的exploit成功後啟動一個web server,然後通過localhost的方式載入第二個stage的exploit,此時第二個stage的exploit將會以Medium Integrity執行,這時候第二個stage的exploit就可以成功下載payload併成功執行:
最終exploit的效果:
當然PPT裡還提到談到了EPM(AppContainer)和EMET的bypass方法,感興趣的同學可以自己嘗試復現。
0×05 參考文獻
1) https://labs.bluefrostsecurity.de/files/Look_Mom_I_Dont_Use_Shellcode-WP.pdf
2) https://www.w3schools.com/html/html5_webworkers.asp
3) https://www.blackhat.com/docs/us-14/materials/us-14-Yu-Write-Once-Pwn-Anywhere.pdf
4) https://www.freebuf.com/vuls/188558.html
*本文作者:elli0tn0phacker,本文屬於FreeBuf原創獎勵計劃,未經許可禁止轉載