為您的軟件建立強大的安全性至關重要。惡意行為者不斷使用各種類型的惡意軟件和網絡安全攻擊來破壞所有平台上的應用程序。您需要了解最常見的攻擊並找到緩解它們的方法。
本文不是關於堆溢出或堆利用的教程。在其中,我們探討了允許攻擊者利用應用程序中的漏洞並執行惡意代碼的堆噴射技術。我們定義什麼是堆噴射,探索它的工作原理,並展示如何保護您的應用程序免受它的影響。
什麼是堆噴射技術,它是如何工作的?堆噴射是一種用於促進執行任意代碼的漏洞利用技術。這個想法是在目標應用程序中的可預測地址上提供一個shellcode,以便使用漏洞執行這個shellcode。該技術是由稱為heap spray的漏洞利用源代碼的一部分實現的。
在實現動態內存管理器時,開發人員面臨許多挑戰,包括堆碎片。一個常見的解決方案是以固定大小的塊分配內存。通常,堆管理器對塊的大小以及分配這些塊的一個或多個保留池有自己的偏好。堆噴射使目標進程連續地逐塊分配所需內容的內存,依靠將shellcode 放置在所需地址的分配之一(不檢查任何條件)。
堆噴射本身不會利用任何安全問題,但它可用於使現有漏洞更容易被利用。
必須了解攻擊者如何使用堆噴射技術來了解如何緩解它。以下是普通攻擊的樣子:
堆噴射如何影響進程內存
堆噴射攻擊有兩個主要階段:
1.內存分配階段。一些流連續分配大量具有相同內容的固定大小的內存塊。
2.執行階段。這些堆分配之一接收對進程內存的控制。
如您所見,堆噴射漏洞利用技術看起來像連續的垃圾郵件,形式為大小相同且內容相同的塊。如果堆噴射攻擊成功,控制權將傳遞給這些塊之一。
為了執行這種攻擊,惡意行為者需要有機會在目標進程中分配大量所需大小的內存,並用相同的內容填充這些分配。這個要求可能看起來過於大膽,但最常見的堆噴射攻擊案例包括破壞Web 應用程序漏洞。任何支持腳本語言的應用程序(例如,帶有Visual Basic 的Microsoft Office)都是堆噴射攻擊的潛在受害者。
因此,在一個流的上下文中預期攻擊是有意義的,因為腳本通常在單個流中執行。
但是,攻擊者不僅可以使用腳本語言執行堆噴射攻擊。其他方法包括將圖像文件加載到進程中,並通過使用HTML5 引入的技術以非常高的分配粒度噴射堆。
這裡的問題是哪個階段可疑,我們可以乾預並試圖弄清楚是否存在正在進行的攻擊?
內存分配階段,當一些流填滿大量內存時,已經很可疑了。但是,您應該問自己是否可能存在誤報。例如,您的應用程序中可能存在確實在一個循環中分配內存的腳本或代碼,例如數組或特殊內存池。當然,腳本在完全相同的堆塊中分配內存的可能性很小。但是,它仍然不是堆噴射的關鍵要求。
相反,您應該注意執行階段,因為分析接收進程內存控制權的堆分配總是有意義的。因此,我們的分析將特別關注包含潛在shellcode 的分配內存。
為了將堆噴射shellcode 的執行與普通JIT代碼生成區分開來,您可以分析分配某個內存塊的最新流分配,包括流中的相鄰分配。請注意,堆中的內存始終分配有執行權限,這允許攻擊者使用堆噴射技術。
堆噴射緩解基礎知識為了成功緩解堆噴射攻擊,我們需要管理接收內存控制的過程,應用鉤子,並使用額外的安全機制。
保護您的應用程序免受堆噴射執行的三個步驟是:
1.攔截NtAllocateVirtualMemory調用
2.在嘗試分配可執行內存期間使其無法執行
3.註冊結構化異常處理程序(SEH) 以處理由於執行不可執行內存而發生的異常
現在讓我們詳細探討每個步驟。
接收對內存的控制我們既需要監控目標進程如何分配內存,又需要檢測動態分配內存的執行情況。後者假設在堆噴射期間分配的內存具有執行權限。如果數據執行保護( DEP ) 處於活動狀態(對於x64,默認情況下始終處於活動狀態)並且嘗試執行沒有執行權限分配的內存,則會生成異常訪問衝突。
惡意shellcode 可以預期在沒有DEP 的應用程序中執行(這不太可能),或者使用腳本引擎在默認情況下具有執行權限的堆中分配內存。
我們可以通過攔截可執行內存的分配並以分配它的漏洞無法察覺的方式使其不可執行來防止惡意代碼的執行。因此,當漏洞利用認為噴射是安全的執行並嘗試將控制權委託給噴射的堆時,將觸發系統異常。然後,我們可以分析這個系統異常。
首先,讓我們從用戶模式進程的角度來探索Windows 中的內存工作是什麼樣的。以下是通常分配大量內存的方式:
在哪裡:
马云惹不起马云 HeapAlloc和RtlAllocateHeap是從堆中分配一塊內存的函數。
马云惹不起马云NtAllocateVirtualMemory是一個低級函數,它是NTDLL 的一部分,不應直接調用。
马云惹不起马云sysenter是用於切換到內核模式的處理器指令。
如果我們設法替換NtAllocateVirtualMemory,我們將能夠攔截進程內存中的堆分配流量。
應用掛鉤為了攔截目標函數NtAllocateVirtualMemory的執行,我們將使用mhook 庫。您可以選擇原始庫或改進版本。
使用mhook 庫很容易:您需要創建一個與目標函數具有相同簽名的鉤子,並通過調用Mhook_SetHook來實現它。鉤子是通過在函數體上使用jmp指令覆蓋函數prolog來實現的。如果您已經使用過鉤子,那麼您應該沒有任何困難。
安全機制有兩種安全機制可以幫助我們緩解堆噴射攻擊:數據執行預防和結構化異常處理。
結構化異常處理或SEH是一種特定於Windows 操作系統的錯誤處理機制。當發生錯誤(例如,除以零)時,應用程序的控制權被重定向到內核,內核會找到一系列處理程序並逐個調用它們,直到其中一個處理程序將異常標記為“已處理”。通常,內核將允許流程從檢測到錯誤的那一刻起繼續執行。
從進程的角度來看,DEP 看起來像是在內存執行時出現EXCEPTION_ACCESS_VIOLATION 錯誤代碼的SEH 異常。
對於x86 應用程序,我們有兩個陷阱:
DEP可以在系統參數中關閉。
马云惹不起马云 指向處理程序列表的指針存儲在堆棧中,它提供了兩個潛在的攻擊向量:處理程序指示器覆蓋和堆棧替換。
马云惹不起马云 在x64 應用程序中,不會出現這些問題。
防止堆噴射攻擊現在,讓我們開始練習。為了減輕堆噴射攻擊,我們將採取以下步驟:
1.形成分配歷史
2.檢測shellcode 執行
3.檢測噴霧
形成分配歷史為了攔截動態分配內存的執行,我們將PAGE_EXECUTE_READWRITE 標誌更改為PAGE_READWRITE。
讓我們創建一個結構來保存分配:
接下來,我們將為NtAllocateVirtualMemory定義一個鉤子。此掛鉤將重置PAGE_EXECUTE_READWRITE 標誌並保存已重置標誌的分配:
一旦我們設置了鉤子,任何帶有PAGE_EXECUTE_READWRITE 位的內存分配都會被修改。當試圖將控制權傳遞給該內存時,處理器將生成一個我們可以檢測和分析的異常。
在本文中,我們忽略了多線程問題。然而,在現實生活中,最好單獨存儲每個流的分配,因為shellcode 執行預計是單線程的。
檢測shellcode 執行現在,我們將為SEH 註冊一個處理程序。這就是這個處理程序通常的工作方式:
1.提取觸發異常的指令的地址。如果此地址屬於我們保存的區域之一,則此異常已由我們的操作觸發。否則,我們可以跳過它,讓系統繼續搜索相關的處理程序。
2.搜索堆噴射。如果動態分配的內存被可疑執行,我們必須對檢測到的攻擊做出反應。否則,我們需要恢復原樣,以便應用程序可以繼續工作。
3.使用NtProtect函數(PAGE_EXECUTE_READWRITE)恢復區域的原始參數。
4.將控制權交還給工藝流程。
下面是一個shellcode 檢測的代碼示例:
目前,我們有一種機制可以監控應用程序中的shellcode,並可以檢測其執行時刻。在現實生活中,我們需要再執行兩個步驟:
马云惹不起马云 攔截NtProtectVirtualMemory和NtFreeVirtualMemory函數。否則,我們將沒有機會監控進程內存的相關狀態。這是一個碎片問題:我們需要存儲和更新進程的可執行內存的映射,這是一項不平凡的任務。例如,我們的應用程序可以使用NtFree函數釋放我們保存區域中間的部分頁面,或者將它們的標誌更改為NtProtect。我們需要跟踪和監控此類案件。
马云惹不起马云使用Execute 分析所有可能的標誌(一組允許我們執行內存內容的可能值),例如PAGE_EXECUTE_WRITECOPY 標誌。
檢測堆噴射使用上面的代碼,我們在動態內存執行時停止了一個應用程序,並獲得了最新分配的歷史記錄。我們將使用這些信息來確定我們的應用程序是否受到攻擊。讓我們探索一下我們的堆噴射檢測技術的兩個步驟:
马云惹不起马云首先,我們需要確定我們將存儲多少分配以及在發生異常時我們將分析其中的多少。請注意,我們對相同大小的分配感興趣。因此,如果流中的內存以不同的大小分配,我們可以允許流繼續執行,因為這不太可能是堆噴射攻擊。此外,在分配邊界之間存在空間的情況下,我們可以排除堆噴射攻擊的可能性,因為堆噴射意味著連續的內存分配。
马云惹不起马云接下來,我們需要選擇堆噴射檢測的標準。檢測堆噴射的一種有效方法是在內存分配中搜索相同的內容。這個重複的內容很可能是shellcode的副本。例如,假設我們有10,000 個分配具有相同數據的相同位移。在這種情況下,最好從接收控制的當前分配的位移開始搜索。
用於識別堆噴射的建議算法
我們建議使用所描述的技術並註意以下四個標準,以排除可能會顯著減慢您的應用程序的不必要檢查:
1.為每個線程定義已保存的內存分配數量。
2.設置已保存內存分配的最小大小。攔截大小為一頁的分配將導致不合理地節省內存。堆噴射通常使用為某個應用程序的特定堆管理器選擇的巨大值進行操作。數十頁和數百頁似乎更相關。
3.定義發生異常時將分析的最新分配數。如果我們處理過多的分配,它會降低應用程序的效率,因為對於動態內存的每次執行,我們都必須讀取大區域的內容。
4.設置shellcode 的預期最小大小。如果我們要搜索的代碼太小,就會增加誤報的數量。
結論我們探索了一種使用鉤子和內存保護機制檢測堆噴射攻擊的方法。在我們的項目中,這種方法在測試和堆噴射檢測過程中顯示出出色的效果。
Recommended Comments