新的緩解措施:模塊篡改保護
?什么是模塊篡改保護?
模塊篡改保護是一種緩解措施,可防止對進程主映像的早期修改,例如 IAT 掛鉤或進程空心化。它一共使用了三個 API:NtQueryVirtualMemory、NtQueryInformationProcess 和 NtMapViewOfSection。如果啟用,加載程序將在調用入口點之前檢查主圖像標頭和 IAT 頁面中的更改。它通過使用信息類 MemoryWorkingSetExInformation 調用 NtQueryVirtualMemory 來做到這一點。返回的結構包含有關頁面共享狀態的信息,以及是否從其原始視圖修改。如果標頭或 IAT 已從其原始映射修改。例如,如果主圖像已被取消映射,并且已在其位置映射了另一個圖像,則加載器將使用類 ProcessImageSection 調用 NtQueryInformationProcess 以獲取主圖像部分,然后將使用 NtMapViewOfSection 重新映射它。這樣,新部分將被使用,篡改的圖像副本將被忽略。
此緩解從 RS3 開始可用,并且可以使用 PROCESS_CREATION_MITIGATION_POLICY2_MODULE_TAMPERING_PROTECTION_MASK 在進程創建時啟用。
模塊篡改保護緩解措施是如何發現的
如果微軟從未宣布或記錄某些緩解措施,那人們如何才能發現這些緩解措施?因此,一個值得關注的好地方是 EPROCESS 結構中的各種 MitigationFlags 字段。目前存在三個 MitigationFlags 字段(MitigationFlags、MitigationFlags2、MitigationsFlags3),每個字段包含 32 位。在前兩個中,整個32位已經被使用,所以最近添加了MitigationFlags3,目前包含三個緩解措施,我相信很快會添加更多。這些標志代表進程中啟用的緩解措施。例如,我們可以使用 WinDbg 為當前進程打印 EPROCESS.MitigationFlags:
最后,在位 28 和 29 中,我們可以看到值 EnableModuleTamperingProtection 和 EnableModuleTamperingProtectionNoInherit。不幸的是,搜索這些名稱并沒有得到任何好的結果。有幾個網站只顯示結構而沒有解釋,一個模糊的堆棧溢出答案簡要提到了 EnableModuleTamperingProtectionNoInherit 而沒有添加細節,還有這條推文:
不出所料,最詳細的解釋是 Alex Ionescu 2017 年發布的一條推文。這雖并不是完整的文檔,但它是一個開始。如果你已經了解并理解構成此緩解措施的概念,那么這一系列的推文可能會非常清楚地解釋有關該特性的所有內容。
開始搜索進程緩解實現的第一個地方通常是內核:ntoskrnl.exe。然而,這是一個巨大的二進制文件,不容易搜索。似乎沒有與此緩解措施完全相關的函數名稱,所以沒有明顯的地方可以開始。
相反,你可以嘗試不同的方法并嘗試找到對 EPROCESS 的 MitigationFlags 字段的引用,并可以訪問這兩個標志中的一個。但除非你可以訪問 Windows 源代碼,否則沒有簡單的方法可以做到這一點。但是,你可以做的是利用 EPROCESS 是一個大型結構并且 MitigationFlags 存在于它的末尾,偏移量 0x9D0 的事實。一種非常粗暴但有效的方法是使用 IDA 搜索功能并搜索所有對 9D0h 的引用:
這會很慢,因為它是一個很大的二進制文件,并且一些結果與 EPROCESS 結構無關,因此你必須手動搜索結果。此外,僅查找對該字段的引用是不夠的,MitigationFlags 包含 32 位,其中只有兩個與當前上下文相關。所以,你必須搜索所有的結果,找出以下情況:
0x9D0被用作EPROCESS結構的偏移量——因為無法保證知道每種情況使用的結構類型,盡管對于較大的偏移量,只有少數選項可以是相關的,它主要可以通過函數名稱和上下文來猜測。
比較或設置MitigationFlags字段為0x10000000 (EnableModuleTamperingProtection)或0x20000000 (EnableModuleTamperingProtectionNoInherit)。或者通過諸如bt或bts之類的匯編指令,按位數測試或設置位28或位29。
運行搜索后,結果看起來像這樣:
你現在可以瀏覽結果并了解內核使用了哪些緩解標志以及在哪些情況下使用。然后我會告訴你,這個努力完全沒有用,因為 EnableModuleTamperingProtection 在內核中的一個地方被引用:PspApplyMitigationOptions,當創建一個新進程時調用:
因此,內核會跟蹤是否啟用了此緩解措施,但從不對其進行測試。這意味著緩解措施本身在其他地方實現。這種搜索可能對這種特定的緩解措施毫無用處,但它是找出緩解措施實現位置的幾種方法之一,并且對其他流程緩解措施很有用,所以我想提一下它。
現在讓我們回到模塊篡改保護,有時會實現進程緩解的第二個位置是 ntdll.dll,它是每個進程中要加載的第一個用戶模式映像。此 DLL 包含所有進程所需的加載程序、系統調用存根和許多其他基本組件。在這里實現這種緩解是有意義的,因為顧名思義它與模塊加載有關,這通過 ntdll.dll 中的加載器發生。此外,這是一個包含 Alex 在他的推文中提到的功能的模塊。
即使我們沒有這條推文,只要打開 ntdll 并搜索“tampering”,我們就能很快找到一個結果:函數 LdrpCheckPagesForTampering。尋找這個函數的調用者,我們看到它是從LdrpGetImportDescriptorForSnap調用的:
在截圖的第一行,我們可以看到兩個檢查:第一個驗證當前正在處理的條目是主圖像,因此該模塊被加載到主圖像模塊中。第二個檢查是 LdrSystemSllInitBlock.MitigationOptionsMap.Map中的兩個位。我們可以看到這里檢查的確切字段只是因為我對 LdrSystemDllInitBlock 應用了正確的類型,如果你在沒有應用正確類型的情況下查看這個函數,你會看到一些隨機的、未命名的內存地址被引用。 LdrSystemDllInitBlock 是一個數據結構,包含加載程序所需的所有全局信息,例如進程緩解選項。它沒有記錄,但具有符號中可用的 PS_SYSTEM_DLL_INIT_BLOCK 類型,因此我們可以在此處使用它。請注意,雖然此結構在 NTDLL 符號中不可用,而是你可以在 ole32.dll 和 combase.dll 的符號中找到它。 MitigationOptionsMap 字段只是三個 ULONG64 的數組,其中包含標記為此過程設置的緩解選項的位。我們可以在 WinBase.h 中找到所有緩解標志的值。以下是模塊篡改保護的值:
這些值與 Map 頂部的 DWORD 相關,因此模塊篡改保護位實際上位于 Map的第 44 位,在 Hex Rays 屏幕截圖中以及在 PspApplyMitigationOptions 中檢查的是相同位。
現在我們知道該緩解措施在哪里應用了檢查,因此我們可以開始查看實現并了解該緩解措施的作用。
實現細節
再次查看 LdrpGetImportDescriptorForSnap:在我們已經看到的兩次檢查之后,該函數獲取主圖像的 NT 標頭并調用 LdrpCheckPagesForTampering 兩次。第一次發送的地址是 imageNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT],圖像的導入表,大小為 8 個字節。第二次使用 NT 標頭本身的地址和大小調用該函數。如果這些頁面中的一個被認為被篡改了,LdrpMapCleanModuleView將被調用(根據名稱判斷)映射主圖像模塊的一個干凈的視圖。
讓我們看看 LdrpCheckPagesForTampering 內部,看看 NTDLL 如何判斷一個頁面是否被篡改:
首先,此函數計算請求的字節范圍內的頁數(在本文的兩個示例中,該數字都是 1)。然后它分配內存并使用 MemoryInformationClass == 4 (MemoryWorkingSetExInformation) 調用 ZwQueryVirtualMemory。這個系統調用和信息類是安全人員可能不太經常看到的,工作集是一種基于物理內存頁面當前狀態來管理和優先級排序的方法,因此大多數安全人員通常不感興趣。然而,工作集確實包含一些我們感興趣的屬性。具體來說,是“共享”標志。
我不會在這里詳細介紹映射和共享內存,因為它們在很多其他地方都有解釋。但簡而言之,系統盡量不復制內存,因為這意味著物理內存將很快被復制的頁面填滿,主要是那些屬于圖像和 DLL 的頁面, 像 ntdll.dll 或 kernel32.dll 的系統 DLL被映射到大多數系統中的進程,因此在物理內存中為每個進程單獨創建一個副本只是浪費。因此,這些圖像頁面在所有進程之間共享。也就是說,除非以任何方式修改圖像。圖像頁面使用一種稱為“寫時復制(copy-on-write)”的特殊保護,它允許頁面可寫,但如果頁面被寫入,則會在物理內存中創建一個新副本。這意味著對 DLL 的本地映射所做的任何更改(例如,用戶模式掛鉤的寫入或任何數據更改)都只會影響當前進程中的 DLL。
這些設置保存為可以通過 NtQueryVirtualMemory 查詢的標志,這里使用的信息類:MemoryWorkingSetExInformation。它將在 MEMORY_WORKING_SET_EX_INFORMATION 結構中返回有關查詢頁面的數據:
這個結構為你提供了被查詢的虛擬地址,以及包含頁面狀態信息的位,例如:它的有效性、保護以及它的共享狀態。有幾個不同的位與頁面的共享狀態相關:
共享——頁面是可共享的嗎?這并不一定意味著該頁當前與任何其他進程共享,但是,例如,除非進程特別請求,否則私有內存不會被共享。
ShareCount——此字段告訴你此頁面存在多少映射。對于當前未與任何其他進程共享的頁面,這將是 1。對于與其他進程共享的頁面,這通常會更高。
SharedOriginal ——該標志會告訴你該頁面是否存在映射。因此,如果一個頁面被修改,導致在物理內存中創建一個新副本,這將被設置為零,因為這不是頁面的原始映射。
此 SharedOriginal 位是由 LdrpCheckPagesForTampering 檢查的,以判斷此頁面是原始副本還是由于更改而創建的新副本。如果這不是原始副本,這意味著該頁面以某種方式被篡改,因此該函數將返回 TRUE。 LdrpCheckPagesForTampering 對正在查詢的每個頁面運行此檢查,如果其中任何一個被篡改,則返回 TRUE。
如果函數對任何檢查范圍返回 TRUE,則調用 LdrpMapCleanModuleView:
這個函數簡短而簡單:它使用 InformationClass == 89 (ProcessImageSection) 調用 NtQueryInformationProcess 來獲取主圖像的部分句柄,然后使用 NtMapViewOfSection 重新映射它并關閉句柄。它將新部分的地址寫入 DataTableEntry->SwitchBackContect,以代替原來的篡改映射。
為什么該特性特別選擇檢查這兩個范圍——導入表和 NT 標頭?
這是因為這兩個地方經常會成為試圖虛化進程的攻擊者的目標。如果主圖像未映射并被惡意圖像替換,則 NT 標頭將不同并被視為已篡改。進程空心化(Process hollowing)還可以篡改導入表,以指向與進程預期不同的函數。所以,這主要是一個反空心化的功能,目標是發現主圖像中的篡改企圖,并用一個沒有被篡改的新圖像副本替換它。
功能限制
不幸的是,此功能相對有限。你可以啟用或禁用它,僅此而已。實現緩解的函數是內部調用,不能在外部調用。因此,例如,除非你自己編寫代碼(并手動映射模塊,因為這些部分的句柄不方便地存儲在任何地方),否則不可能將緩解擴展到其他模塊。此外,此緩解不包含日志記錄或 ETW 事件。當緩解通知在主圖像中被篡改時,它會靜默映射并使用新副本,并且不會留下任何痕跡供安全產品或團隊查找。唯一的提示是 NtMapViewOfSection 將再次為主圖像調用并生成 ETW 事件和內核回調。但這很可能會被忽視,因為它并不一定意味著發生了不好的事情,并且可能不會導致任何警報或對可能是真正的攻擊的重大調查。
從好的方面來說,這種緩解非常簡單和有用,如果你想實現它,則很容易模仿,例如檢測放置在你的進程上的鉤子并映射一個新的、未掛鉤的頁面副本以供使用。你可以這樣做,而不是使用直接系統調用!
該緩解措施有人使用過嗎?
在 WinDbg 中運行查詢,我沒有發現任何啟用模塊篡改保護的進程的結果。經過一番探索,我設法找到了一個啟用此功能的進程:SystemSettingsAdminFlows.exe。此過程在你打開 Windows 設置菜單中的應用程序->可選功能時執行。我不知道為什么這個特定的進程會使用這種緩解措施,或者為什么它是唯一一個這樣做的,但這是迄今為止我設法找到的唯一一個啟用模塊篡改保護的過程。
本文翻譯自:https://windows-internals.com/understanding-a-new-mitigation-module-tampering-protection/