什麼是模塊篡改保護?模塊篡改保護是一種緩解措施,可防止對進程主映像的早期修改,例如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 設置菜單中的應用程序-可選功能時執行。我不知道為什麼這個特定的進程會使用這種緩解措施,或者為什麼它是唯一一個這樣做的,但這是迄今為止我設法找到的唯一一個啟用模塊篡改保護的過程。
Recommended Comments