是的,您并沒有看錯:這是一篇關于如何保護漏洞利用代碼本身的文章,而不是介紹如何保護您不受漏洞利用代碼攻擊的文章。
我一直有這樣的想法,即如何將漏洞利用代碼提升到一個新的水平,這不僅僅指漏洞利用過程,而且還包括漏洞利用之前和之后。這一次,我將編寫一個文章系列,詳細介紹如何保護漏洞利用代碼。危險無處不在,包括黑客在內,因為他們已經失去了很多漏洞利用代碼。
在這個文章系列中,將探討不同的方法來檢測受攻擊的程序(在這種情況下是瀏覽器)是否正在被某種工具所分析,以便我們可以中止漏洞利用代碼,而不至于發生故障、崩潰和被檢測到。
為什么?
漏洞利用代碼是有價值的資產,所以你自然希望盡可能長時間地保護和持有它們。此外,大多數時候你也不希望被發現。但是為了實現這一點,你需要想法設法保證漏洞利用代碼也不會被發現。
首先,野外的漏洞利用代碼之所以被發現,通常是由于以下四個主要原因:
重用了其他漏洞利用代碼的部分,因此被檢測軟件察覺
因為不可靠而崩潰,后來經分析而曝光
由于程序被監視和分析(蜜罐)而導致崩潰
你分享給了朋友,他在使用過程中被檢測到
因為我們這篇文章專注于第三個原因,所以我們首先來介紹一下PageHeap。
PageHeap是如何工作的?
PageHeap是SDK/WDK中提供的一個Windows工具,用來盡快檢測出進程堆中的內存破壞情況。
為了實現這一點,它用另一個堆分配器替換了原來的那個分配器。這個分配器將通過VirtualAlloc進行所有的內存分配,使其至少返回一個指定大小(大多數系統中為4Kb)的內存頁。
除此之外,返回的地址將指向內存頁的末尾減去指定的大小。 因此,它能使任何堆緩沖區溢出到內存頁的末尾。
這也防止了許多廣泛使用的技術,這些技術依賴于特定的堆布局來以特定方式來布置內存的分配。這些技術的名稱很多,比如Heap Massaging、堆風水等。
所以,由于PageHeap打破了原來的布局,所以會導致大部分依賴某種堆棧布局的漏洞利用代碼崩潰。
如果這是您是初次接觸PageHeap的話,不妨在網上多搜索一些相關的資料,因為它的確是一個非常方便的調試工具。
如何檢測PageHeap?
受PageHeap影響的程序的行為的主要特點是,無論分配空間的大小是多少,堆分配都會慢很多。記住,堆早就為盡可能快地分配各種不同尺寸的內存對象而進行了相應的優化。
相反,使用PageHeap時,每次分配都要通過VirtualAlloc向內核提出請求,這就涉及上下文切換并在內核中進行相應的處理。因此,使用PageHeap時,與常規情況下分配大內存塊的過程相比,它所用的時間會跟分配小的內存塊的時間更加接近。
由于window.performance.now()計數器在大多數JavaScript引擎中都支持,因此可以用它來檢測這個時間的測量值,并且具有微秒的精度。
因為Chrome和Firefox擁有自己的分配器,所以啟用PageHeap不會引起太多的變化(請記住,它會劫持原始的malloc / free函數)。在這篇文章中,我們將重點介紹Windows環境中使用默認分配器的兩種瀏覽器:IE11和Edge。
在尋找分配不同數量字節的函數的過程中,我遇到了Uint8Array,它是一個在低層使用了ArrayBuffer的TypedArray。
它的用法很簡單,例如var buf = new Uint8Array(len)。通過跟蹤該函數,無論是在IE中還是Edge中,當創建ArrayBuffer時,都會直接使用我們指定的值調用msvcrt!malloc。
對于小大值,這里分別使用0x10和0x1000,注意,大的值會觸發對VirtualAlloc的調用。我這里使用的方法是,嘗試在20ms內為小型和大型的內存分配任務分配盡可能多的Uin8Array。
好的,下面看看具體代碼!
function doFor(fun, time) {
var i = 0;
var store = new Array();
var startTime = performance.now();
do {
for(var j=0; j
store.push(fun());
i++;
} while((performance.now() - startTime)
return i;
}
function allocPageBA() {
return new Uint8Array(0x1000);
}
function allocSmallBA() {
return new Uint8Array(0x10);
}
var bigRet = doFor(allocPageBA, ALLOC_TIME);
var smallRet = doFor(allocSmallBA, ALLOC_TIME);
alert(bigRet);
alert(smallRet);
當然,這段代碼不是很完美,因為我們在測量內存分配時引入其他的分配任務,肯定會帶來測量誤差。
為了解決這個問題,我創建了一個對象,并預先分配了Array,然后將對象存儲在數組中,這樣就不會引起新的分配任務了。
另外,要防止分配的對象被垃圾回收器回收而釋放它們的內存,這會在測量中引入另一個誤差。
function NoAllocStore(count) {
this.count = count;
this.array = new Array(count);
for(var i=0; i
this.array[i] = 0x41414141;
}
this.index = 0;
}
NoAllocStore.prototype.store = function(obj) {
if (this.index >= this.count) {
alert("bad");
throw false;
}
this.array[this.index] = obj;
this.index++;
}
最后的代碼在這里。為了這項實驗,運行了許多次(準確的說是250次),并比較了在兩臺瀏覽器在禁用和啟用PageHeap情況下的分布情況。
在IE11中測量的分布情況為:

下面看看混合分布詳情:

非常明顯的是,啟用了PageHeap的時候分布更為密集,因此它對分配時間影響更大。 這可以歸因于內存分配比堆分配更耗時,使得代碼的其他部分的耗時在整體上就不那么顯著了。
當然,您可能想要了解每次調用malloc后執行的總指令數(ring0和ring3),這時就需要使用系統仿真器或調試器了,這可以作為一項練習留給讀者自己完成。
對于Edge瀏覽器來說,分布是非常相似的,但你會注意到它們更加分散,3x是一個保守和非常好的閥值:

對于IE和Edge瀏覽器來說,我們將其閥值分別設置為2x和3x,在此閥值以下被視為啟用了PageHeap,否則就可以認為沒有啟用PageHeap。
您可以使用detect.html來檢測自己的IE或Edge瀏覽器,如果結果大相徑庭的話,請通知在下。此外,如果您有興趣,也可以查閱檢索和分析數據的相關代碼,其地址位于https://github.com/snf/exploit/tree/master/anomalies/pageheap 。
小結
事實證明,只需要40ms的時間,您就可以迅速確定PageHeap是否存在,從而決定是否繼續使用漏洞利用代碼了。當然,這只是一個實驗,結論未必絕對可靠,同時還需要在不同的cpus和虛擬化技術下做進一步的測試。但是別忘了,這只是ItWorksInMyPC(TM)項目中的一部分,還有更精彩的項目在等著您呢。
|