Jump to content

本文將介紹SKPG的數據結構和組件,更具體地說,是它被激活的方式。 SKPG 初始化的過程請您點擊這裡了解。

內部HyperGuard激活在SKPG 初始化一文中,我介紹了HyperGuard並描述了它的不同初始化途徑。無論我們怎麼選擇,最終都會在普通內核完成初始化時到達SkpgConnect。此時內核中所有重要的數據結構已經初始化,可以開始受到PatchGuard 和HyperGuard 的監控和保護。

經過幾次標準輸入驗證後,skpgconect獲得SkpgConnectionLock並檢查SkpgInitialized全局變量,以告訴HyperGuard是否已經被初始化。如果設置了變量,函數將根據接收到的信息返回STATUS_ACCESS_DENIED或STATUS_SUCCESS。在任何一種情況下,它都不會做任何其他事情。

如果SKPG 尚未被初始化,SkpgConnect 將開始對其進行初始化。首先,它計算並保存多個隨機值,以便稍後在多個不同的檢查中使用。然後它分配並初始化一個背景結構,保存在全局SkpgContext 中。在我們繼續討論其他SKPG 領域之前,有必要花點時間討論一下SKPG 的背景。

SKPG背景這個SKPG背景結構在SkpgConnect中被分配和初始化,並將在所有SKPG檢查中使用。它包含HyperGuard監控和保護系統所需的所有數據,如NTPTE信息、加密算法、KCFG範圍等,以及另一個計時器和回調,與我們在SKPG 初始化一文看到的計時器和回調不同。不幸的是,像HyperGuard的其他部分一樣,這個結構(我將稱之為SKPG_CONTEXT)沒有文檔記錄,因此我們需要盡最大努力弄清楚它包含什麼以及如何使用它。

首先,需要分配背景。這個背景具有一個動態大小,它取決於從普通內核接收到的數據。因此,它是在運行時使用函數SkpgComputeContextSize計算的。結構的最小大小是0x378字節(隨著背景結構獲得新字段,這個數字往往會隨著Windows構建的次數增加而增加),並且根據從普通內核發送的數據,該結構將被添加一個動態大小。

只有在通過PatchGuard代碼路徑初始化SKPG時才發送的輸入數據是一個名為Extents的結構數組。這些範圍描述了HyperGuard保護的不同內存區域、數據結構和其他系統組件。我將在後面的文章中更詳細地介紹所有這些,但是有幾個例子包括GDT和IDT、某些受保護模塊中的數據部分和具有安全含義的MSR。

計算出所需的大小後,分配SKPG_CONTEXT結構,並在SkpgAllocateContext中設置一些初始字段。其中兩個字段包括另一個安全計時器和一個相關的回調,其函數被設置為SkpgHyperguardTimerRoutine和SkpgHyperguardRuntime。它還設置了與PTE地址相關的字段和其他與分頁相關的屬性,因為很多HyperGuard檢查都驗證了正確的Virtual-Physical頁面轉換。

然後,調用SkpgInitializeContext來使用普通內核提供的範圍完成背景的初始化。這基本上意味著遍歷輸入數組,使用數據初始化內部結構, 我將調用SKPG_EXTENT,並將它們粘貼在SKPG_CONTEXT 結構的末尾,我選擇調用ExtentOffset 的字段指向范圍數組(請注意,這些結構都沒有記錄,因此所有結構和字段名稱都是由組成的):

1.png

SKPG範圍有許多不同類型的範圍,每個SKPG_EXTENT結構都有一個Type字段指示其類型。每個範圍還具有一個哈希,在某些情況下,該哈希用於驗證對所監控的內存區域沒有做任何更改。然後是被監控內存的基址和字節數的字段,最後是包含每個範圍類型特有數據的聯合。作為參考,下面是反向工程的SKPG_EXTENT結構:

2.png

我提到過,HyperGuard使用的輸入區是由普通內核中的PatchGuard初始化器函數提供的。但是SKPG會初始化另一種類型的範圍,同樣安全的範圍。為了初始化這些,SkpgInitializeContext調用SkpgCreateSecureKernelExtents,提供SKPG_CONTEXT結構和當前範圍數組結束的地址,因此可以將安全範圍放置在那裡。安全範圍使用與常規範圍相同的SKPG_EXTENT結構,並保護安全內核中的數據,例如加載到安全內核和安全內核內存範圍中的模塊。

3.png

範圍類型如前所述,有許多不同類型的範圍,HyperGuard使用每種範圍來保護系統的不同部分。然而,我們可以將他們分成幾個具有相似特徵的組,並以相似的方式處理。為了清晰並將正常範圍與安全範圍分離,我將使用命名約定SkpgExtent用於正常範圍類型,SkpgExtentSecure用於安全範圍類型。

我想要介紹的第一個範圍非常簡單,它總是被發送到SkpgInitializeContext,而不管其他輸入。

初始化範圍有一個範圍不屬於任何組,因為它不涉及任何HyperGuard驗證。這是0x1000: SkpgExtentInit,此範圍不會復製到背景結構中的數組中。相反,這個範圍類型是由SkpgConnect創建的,並發送到SkpgInitializeContext,以設置背景結構本身中以前未填充的一些字段。這些字段具有與熱補丁相關的附加哈希值和信息,例如是否啟用熱補丁以及retpoline代碼頁的地址。它還在背景結構中設置一些標誌,以反映設備中的一些配置選項。

內存和模塊範圍這個組包括以下範圍類型:

4.png

所有這些範圍類型的共同點是,它們都表示HyperGuard要保護的內存範圍。其中大多數包含正常內核中的內存範圍,但是SkpgExtentSecureMemory和SkpgExtentSecureModule有VTL1內存範圍和模塊。儘管如此,不管內存類型或VTL如何,所有這些範圍類型都以類似的方式處理,所以我將它們組合在一起。

當向SKPG Context添加普通內存範圍時,將驗證所有普通內核地址範圍,以確保頁面具有用於SKPG保護的有效映射。為了使普通內核頁對SKPG保護有效,這個頁是不可寫的。 SKPG將監控所有請求頁面的更改,因此內容可以隨時更改的可寫頁面不是此類保護的有效“候選”頁面。因此,SKPG只能監控保護為“讀”或“執行”的頁面。顯然,只有有效的頁面(如PTE中的valid位所示)才能受到保護。當啟用HVCI時,與某些內存範圍有一些細微的差別,因為在這些條件下SKPG無法處理某些頁麵類型。

一旦映射和驗證,每個應該被保護的內存頁將被哈希,並將哈希保存到SKPG_EXTENT結構中,它將在未來的HyperGuard檢查中使用,以驗證頁面沒有被修改。

一些內存範圍描述了一個通用的內存範圍,還有一些,比如SkpgExtentImagePage,描述了一個特定的內存類型,需要稍微區別對待。這種範圍類型提到了普通內核中的特定映像,但是HyperGuard不應該保護整個映像,而應該保護其中的一部分。因此,輸入範圍包括映像基礎、映像中保護應該開始的頁面偏移量以及請求的大小。這裡要保護的內存區域也將被哈希,哈希值將被保存到SKPG_EXTENT中,以便在將來的驗證中使用。

但是寫入SKPG背景的SKPG_EXTENT結構通常只描述單個內存頁面,而係統可能希望保護映像中更大的區域。對於HyperGuard來說,一次處理一個頁面的內存驗證更簡單,這樣可以獲得更可預測的處理時間,避免在哈希大內存範圍時佔用太多時間。因此,當接收到請求大小大於頁面(0x1000字節)的輸入範圍時,SkpgInitializeContext遍歷請求範圍中的所有頁面,並為每個頁面創建一個新的SKPG_EXTENT。只有描述範圍中的第一個頁面的第一個範圍接收類型SkpgExtentImage。描述以下頁面的所有其他類型都接收不同的類型0x1014,我選擇調用SkpgExtentPartialMemory,並且原始的範圍類型位於SKPG_EXTENT結構中特定類型數據的前2個字節中。

數組中的每個範圍都可以用不同的標誌來標記。其中一個是Protected標誌,它只能應用於普通的內核區,這意味著SKPG應該保護指定的地址範圍不受更改。在這種情況下,SkpgInitializeContext將在請求的地址範圍上調用SkmmPinNormalKernelAddressRange來固定並防止它被VTL0代碼釋放:

5.png

安全內存範圍本質上與普通內存範圍非常相似,主要區別在於它們由安全內核本身初始化以及它們所保護的內容的詳細信息。

生成SkpgExtentSecureModule類型的範圍來監控加載到安全內核空間中的所有映像。這是通過迭代SkLoadedModuleList全局列表來完成的,它和普通內核的PsLoadedModuleList一樣,是一個KLDR_DATA_TABLE_ENTRY結構的鍊錶,表示所有加載的模塊。對於其中的每個模塊,都調用SkpgCreateSecureModuleExtents來生成範圍。

為此,SkpgCreateSecureModuleExtents 一次接收一個加載的DLL 的KLDR_DATA_TABLE_ENTRY,驗證它是否存在於PsInvertedFunctionTable(包含所有加載的DLL 的基本信息的表,主要用於快速搜索異常處理程序),然後枚舉模塊。安全模塊中的大多數部分都使用SKPG_EXTENT 進行監控,但不受修改保護。只有一個部分受到保護,TABLERO 部分:

6.png

TABLERO 部分是僅存在於少數二進製文件中的數據部分。在普通內核中,它存在於Win32k.sys 中,其中包含win32k 系統服務表。在安全內核中,securekernel.exe 中存在TABLERO 部分,其中包含全局變量,例如SkiSecureServiceTable、SkiSecureArgumentTable、SkpgContext、SkmiNtPteBase 等:

7.png

當SkpgCreateSecureModuleExtents 遇到TABLERO 部分時,它會調用SkmmProtectKernelImageSubsection 將部分頁面的PTE 從默認的讀寫更改為只讀。

然後,對於每個範圍,無論其類型如何,都會創建一個類型為SkpgExtentSecureModule的範圍。如果段是可執行的,則每個內存區域將被哈希成範圍標記中的一個標誌。每個範圍生成的範圍數量可能不同:如果在計算機上啟用了hotpatch,將為受保護映像範圍中的每個頁面生成單獨的範圍。否則,每個受保護的部分會生成一個可能覆蓋多個頁面的範圍,所有這些範圍都使用SkpgExtentSecureModule類型:

8.png

如果啟用了hotpatch,則為每個安全模塊創建最後一個安全模塊區。變量SkmiHotPatchAddressReservePages將指示在模塊末尾有多少個頁面被預留給HotPatch使用,並為每個頁面創建一個範圍。與前面描述的普通內核模塊範圍的方式類似,每個範圍描述一個頁面,範圍類型是SkpgExtentPartialMemory,類型SkpgExtentSecureModule被放置在範圍的一個類型特定的字段中。

另一個安全範圍類型是SkpgExtentSecureMemory。這是一個通用範圍類型,用於指示安全內核中的任何內存範圍。但是,目前它僅用於監控由安全內核處理器塊(SKPRCB)指向的GDT。這是一個內部結構,其目的類似於普通內核的KPRCB(類似地,它們的數組存在於SkeProcessorBlock中)。對於系統中的每個處理器,都有一個這種類型的範圍。此外,該函數在每個KGDTENTRY64結構的Type字段中設置一個位,以表明這個條目已經被訪問,並防止它在以後被修改,但是在偏移量0x40處的TSS條目將被跳過:

9.png

這基本上涵蓋了內存區的初始化和使用。

0 Comments

Recommended Comments

There are no comments to display.

Guest
Add a comment...