【考古向翻譯】Pwn2Own 2010 Windows 7 Internet Explorer 8 exploit

Ox9A82發表於2016-09-24
正好在Google搜到了這篇文章,就打算自己翻譯一下,也不清楚國內是否有人已經翻譯過了。作者是Pwn2Own 2010的獲獎者來自荷蘭的皮特·維萊格登希爾(Peter Vreugdenhil)。
2010的Pwn2Own是第一次公開亮相的攻破有ASLR+DEP保護的IE瀏覽器,所以也是我比較關注的一點。當然了就目前(2016)來說,這些都已經是漏洞利用的標配了。所以是一次考古性質的翻譯。
ps:我是根據對技術的理解去翻譯的,很多地方都有刪改,要求精準的可以去看原文。
http://vreugdenhilresearch.nl/Pwn2Own-2010-Windows7-InternetExplorer8.pdf

我決定寫一篇關於我在Windows7下攻破有DEP和ASLR保護的IE8瀏覽器的技術文章。

整個利用過程分為兩大步驟

第一步是找出一個確定的dll檔案的載入基地址,然後在第二步中會使用到第一步中得到資訊來使用一些ret2libc技術去bypass DEP,然後劫持流程去執行shellcode。我暫時還不能公開詳細的漏洞細節,但是等微軟修補過後我會逐步公開細節的。就像我前面說的那樣,我使用了兩個exp才實現最後的任意程式碼執行的結果。

在第一步中,為了獲得ie瀏覽器中一個dll模組載入的基地址,我使用了一個堆溢位覆寫了一個UTF8 String(物件?)的尾部結束符(\x00\x00),這樣當我讀這個字串的時候就可以讀到下一個物件的虛表指標了。當然了,下一個物件也是我精心佈置好的。實現這個想法花了我好幾天的時間,最終才實現了穩定的利用。

 

我畫一個堆記憶體的示意圖,

黃色的記憶體是發生溢位的堆塊。

綠色的記憶體是我們分配的Unicode String。其中結尾符\x00\x00由亮綠色表示。

紅色的記憶體是我們精心分配的物件,其中亮紅色表示物件的虛表指標。

雖然看著好像很簡單,但實際實現卻有很多困難。這需要一些技巧才能把記憶體佈局成我們想要的形式。首要的前提是瀏覽器要存在著一個可以控制的堆溢位,並且我們的這些操作不能讓瀏覽器崩潰掉。然而,經過了一番努力之後,我成功的構造了想要的佈局,而且可靠性很高。然後我觸發了堆溢位,覆蓋了字串結尾的字元。這樣字串就不再以\x00\x00作為結尾了。如果我們接下來用js函式去讀取這個字串的內容的話,那麼就會一直讀到下一個\x00\x00為止。這樣js函式就讀到了下一個物件的虛表地址。虛表地址有什麼用呢?虛表在編譯時就已經編譯到了dll模組中,就是說虛表的地址到模組起始的偏移是確定的。所以根據虛表地址就可以計算出來物件所處的dll模組的基地址。這個資訊可以用來編寫一個繞過DEP的exp(實質上是bypass ASLR)。如果我對寫入溢位的資料擁有完全控制的話,覆寫下一物件的虛表就很容易了。但是如果我對溢位資料的控制受到限制的話,我就不能確保可以覆寫虛表指標了。

 第二步就是想辦法繞過DEP的保護了,幾個月前,我寫了一個繞過IE8下DEP保護的exp,使用的技術是堆噴射+ROP。事實上是混合使用了堆噴射和物件呼叫偽造,但是我並不確定我是不是第一個使用這種技術的人。在我除錯一個IE瀏覽器的UAF漏洞時,我注意到大堆塊的分配是可以預測的。但是不是精準的預測,而是我發現分配地址的最後兩個位元組總是相同的。在XP系統上大於100位元組就會是可預測的。這樣一來我們就足夠去做堆噴射,並且很有可能猜到噴射到的地址。

舉一個例子,下面的程式碼將不斷的分配一些填充有固定內容的堆。這個堆的分配地址應該是開始於0xZZZZZY20的,其中Z的值是隨機的,Y則只能為0/4/8/C。因為我分配的單元大小是0x200個位元組。

 1 heap = new heapLib.ie(0x20000);
 2 var heapspray = unescape("%u4141%u4242");
 3 while(heapspray.length < 0x200) heapspray += unescape("%u4444");
 4     var heapblock = heapspray;
 5 while(heapblock.length < 0x40000) heapblock += heapblock; 
 6     finalspray = heapblock.substring(2, 0x40000 - 0x21);
 7 for(var i = 0; i < 500; i++) 
 8 {
 9     heap.alloc(finalspray);
10 }

這段程式碼的執行結果可能是這個樣子的

Heap alloc size(0x7ffc0) allocated at 063d0020

Heap alloc size(0x7ffc0) allocated at 06450020

Heap alloc size(0x7ffc0) allocated at 064d0020

Heap alloc size(0x7ffc0) allocated at 06550020

Heap alloc size(0x7ffc0) allocated at 065d0020

Heap alloc size(0x7ffc0) allocated at 06650020

Heap alloc size(0x7ffc0) allocated at 066d0020 

 如上可見,你得到了堆中彼此相鄰的塊。

上面例子中的分配大小對於你來說是沒有用的,因為這個大小取決於你的具體漏洞情況。

就像程式碼中寫的那樣,我使用heaplib來分配字串,這樣更加的方便。

這種技術在Windows7上依然可以使用, 但是分配的次數要比在XP上更多一些。比如在XP上以500為堆噴的次數,0x0a042020作為起始地址。那麼到了Windows7上則要進行900次噴射,並且以0x16402020作為起始地址。這樣的結果是隻要噴射的次數足夠多的話,那麼我們就能預測出堆分配的地址(而且這個地址可以我們來指定)。 

 下面來講解一下簡單的UAF漏洞如何進行利用。我發現的UAF漏洞通常都是這樣子的:發生UAF的物件在不同行的js中被分配和釋放,所以我們有充足的時間去我們指定的資料佔位被釋放的物件記憶體空間。

這是我們假設的存在UAF漏洞的程式碼:

var MyObject = new Object();//分配物件
var MyObjRef = MyObject.SomeProperty; //引用物件
MyObject.CleanUp(); //釋放物件
alert(MyObjectRef.parent);//解引用物件

現在我們希望在物件被解引用之前,用我們控制的資料對釋放的物件記憶體地址進行佔位。在大多數的UAF漏洞中,發生UAF物件的虛擬函式都會發生呼叫。虛表指標就是一個物件的前四個位元組的值,所以我們需要進行某種型別的堆噴射可以準確的填充到我們在物件虛表值覆蓋的值的位置。

一個好訊息是IE對剛剛釋放的堆塊進行了記錄,如果我們申請一個大小大約相同的堆,那麼那塊記憶體就會被重新分配(我很懷疑真的是大約相同嗎?)。這意味著,如果我們知道釋放物件的記憶體大小,我們就可以用相同大小的我們自定義的資料去分配堆,而且結果會是相同的記憶體。

想知道物件的大小並不能,只需要對ntdll中的堆分配函式下斷即可。我們必須再次分配正確的記憶體大小,一般我都是透過對div標籤陣列新增className屬性來實現的這一點。這麼做的優點是className屬性是一個字串,你可以對這個字串指定任意大小。這個過程不會導致額外的堆分配(額外的堆分配可能會導致不能正確佔位),並且字串的內容是你自定義的,你可以指定第一個DWORD大小的資料為任意值。唯一的缺點是不能在字串中使用\x00\x00,但是事實上佔位根本用不到\x00\x00。 

所以我們需要做一下幾件事

  1. 建立陣列來存放div元素( var DivArray = new Array(); )
  2. 用50個物件來填充這個陣列
  3. 當UAF物件被釋放掉後,執行js語句對記憶體進行佔位
  4. 給divs標籤新增classname屬性( DivArray*i+.className = unescape(“%u4141%u4141...... 
  5. 解引用UAF物件

這樣操作之後的結果會怎麼樣呢?

最可能的結果是這個樣子的:

move eax, [ecx] ecx = our object memory.
call [eax+0x34] eax now holds 0x41414141 

到目前為止,我們佔位了UAF物件,控制了虛表指標。但是我們該怎麼樣繞過DEP保護呢?很簡單,我們已經控制了eax暫存器。我們也清楚了佔位堆的記憶體佈局。我們首先要做的是把eax值指向堆噴的頭地址。這樣的話就會取堆噴處的0x34偏移的值來call了。但是由於有DEP保護,不能直接的在堆上執行程式碼。我們需要做的就是想辦法呼叫VirtualProtect函式,把shellcode所處的記憶體的屬性由可讀可寫設定為可讀可寫可執行。
舉個例子,假設我們已經發現一些如下的指令序列(作為gadgets存在)

0x6ff02348 :

mov ecx,eax

call [eax+10]

 

and another that goes like:

0x6FF01234 :

push [eax+70]

push [eax+60]

push [eax+50]

push [eax+40]

push [eax+34]

push [eax+20]

call [ecx+14]

.....

.....

retn 

那麼,如果我們這麼去設定堆噴的堆內容的話:(根據引用順序排序)

  1. +0x34:0x6FF02348
  2. +0x10:0x6FF01234 
  3. +0x14:0x7c801ad4 (VirtualProtect)
  4. +0x20:堆噴的首地址
  5. +0x30:0x200(單個堆噴塊的大小) 
  6. +0x40:0x40(READWRITEEXCUTE)
  7. +0x50:無意義的值,佔位用

這樣的話我們就成功的呼叫了VirtualProtect函式把記憶體的屬性改成了可執行的。這種方法在XP上能夠得到很好的執行,因為XP系統上我們可以準確的知道VirtualProtect的地址。在Windows7上利用我們需要更多的創造性。

我們在不知道kernel32.dll模組的準確基地址的情況下該如何呼叫VirtualProtect呢?IE的許多dll模組包含有ATL庫,這些ATL庫中會呼叫VirtualProtect函式。這意味著VirtualProtect函式處於我們已知的一個固定的偏移(相對dll基址)。假如我們知道dll載入的基地址是0x6fff0000,0x6fff1288是VirtualProtect函式的地址。我們需要做的就是在諸如call [eax+8]這樣語句之前把eax設定為0x6fff1280。這可以透過想上面那樣利用程式碼並設定堆噴的內容來實現。當我使用這種方法時,我經常把我的堆噴內容設定為連續增長的值,比如:

var pattern = unescape(“%u0000%u0001%u0002%u0003%u0004%u0005%u0006........”) 

透過這種方法,就可以更容易的找出你的字串中需要編輯的值。

本質上,我們要做的全部事情就是連線起已經準備好的這些程式碼塊(gadgets)。但是隻能使用call或是jmp去連線,直到成功的執行了VirtualProtect。然後我們的堆噴就具有了可執行許可權。如果我們的執行流中向棧中壓入的引數多於VirtualProtect函式需要的,那麼返回堆疊將會被破壞,並且會結束掉我們的流程。

Peter Vreugdenhil 

翻譯 by:Ox9A82

 

 

 




相關文章