1. 程式人生 > >核心程式設計之SSDTHook(1)原理

核心程式設計之SSDTHook(1)原理

說驅動開發這麼長時間了,也玩玩核心鉤子,鉤子(Hook)技術是一種截獲對某一物件訪問的技術,不僅在Windows平臺,Linux平臺上也有Hook技術。Hook技術種類繁多,實現細節也不同,還可以靈活使用。

我之前寫過兩篇Ring3下的API Inline Hook的博文,這兩篇博文通過Inline Hook OpenProcess 實現對指定程序的保護,從而保護程序不能被其他程序開啟、結束、寫記憶體等等。可以先去看看:

我們先來看看都有那些常見的Hook技術:

首先是Ring3層下的Hook技術,Ring3層下的Hook包括訊息鉤子和API Hook,訊息鉤子又分為全域性訊息鉤子和程序內訊息鉤子,API Hook又分為IAT Hook和API Inline Hook,這兩種鉤子的區別和原理,看我上面的那兩篇博文。

核心下Hook就比較多了,比如Object Hook,SSDT Hook,SSDT Shadow Hook,Inline Hook,IDT Hook,SysEnter Hook等等。

我就來說說SSDT Hook,當年SSDT Hook火的時候,我可能當時連程式設計都不知道,這是一種非常古老的技術,據說這是RootKit病毒第一次採用的技術,當然也是殺軟和各種防護軟體青睞的技術。

首先大家要知道Windows系統的核心和執行體層是ntoskrnl.exe,這是整個系統的核心,而ntdll.dll是使用者模式到核心模式的介面,ntoskrnl中有兩張表,分別是:KeServiceDescriptorTable 和 KeServiceDescriptorTableShadow

Ring3層 Kernel32.dll 中的API經過ntdll.dll後一般由KeServiceDescriptorTable中的函式處理。而KeServiceDescriptorTableShadow則主要處理來自 User32.dll 和 GDI32.dll 中的API。

KeServiceDescriptorTable在ntoskrnl.exe中是匯出的。

如何知道他是匯出的呢?可以使用Dumpbin工具來檢視ntoskrnl的匯出表:

我這裡是32位系統,因此可以得到ntoskrnl匯出的KeServiceDescriptorTable,64位系統上微軟沒有匯出,就只能從32位其他電腦或虛擬機器中複製一份ntoskrnl.exe。



我們來看看KeServiceDescriptorTable究竟是個什麼東西。其實,他是這樣的一個數據結構,至少前面是這樣的:

typedef struct _KESERVICE_DESCRIPTOR_TABLE
{
	PULONG ServiceTableBase;
	PULONG ServiceCounterTableBase;
	ULONG NumberOfServices;
	PUCHAR ParamTableBase;
}KESERVICE_DESCRIPTOR_TABLE, *PKESERVICE_DESCRIPTOR_TABLE;

我最早學習SSDTHook的時候就有一個非常傻的疑問,我當初就想KeServiceDescriptorTable他匯出有什麼用啊,怎麼找到他在記憶體中的地址呢?需要先找到核心在記憶體中的基地址,然後根據KeServiceDescriptorTable在ntoskrnl中PE結構中的位置計算KeServiceDescriptorTable在記憶體中的地址,哈哈,要是這樣做第一步就需要硬編碼,而且還需要分析PE檔案結構。其實很簡單,KeServiceDescriptorTable是匯出的,只要我們匯入(宣告一下,並在程式碼中引用)不就行了,KeServiceDescriptorTable在ntoskrnl.dll中是匯出的,我們只需要和WDK中的ntoskrnl.lib連線即可,當驅動安裝時,系統核心的PE載入器會自動對我們匯入的KeServiceDescriptorTable進行重定向,我們就直接匯入即可,非常簡單。(關於重定向的細節,那就是PE檔案結構的知識了)

簡單說,就是我們宣告KeServiceDescriptorTable,並在程式碼中引用它,那麼編譯器就會把這個宣告編譯成弱符號,連線時連線ntoskrnl.lib裡的強符號即可。

就像這樣:

//ntoskrnl.exe (ntoskrnl.lib) 匯出的 KeServiceDescriptorTable
extern "C" extern PKESERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

(注意:用C++寫程式碼必須用extern "C"修飾,C語言則不需要。這是符號修飾約定的問題,想必大家都明白,這裡不再展開,當初學SSDTHook時不加extern "C"總是出問題,當初把我急的,算是懷念下吧)

KeServiceDescriptorTable中有我們非常感興趣的內容,比如ServiceTableBase是SSDT的基地址,NumberOfServices是SSDT中函式的個數。

SSDT就是一張函式索引表,或者說就是一個數組。我們知道,核心函式中有兩類非常特殊的函式,ZwXXX函式和NtXXX函式,其中XXX是一樣的,在Ring3下他們是一樣的,由ntdll.dll匯出,而且只是名字不同,地址都是完全一樣的,所以在R3下呼叫ZwXXX函式和NtXXX函式是完全一樣的,

等等,不信?還是用dumpbin神器來看看ntdll.dll的匯出表把,隨便找兩個ZwXXX和NtXXX函式,看看他們的地址一樣嗎。


我們繼續,但是在核心,他們是由核心ntoskrnl.exe匯出的,而且ZwXXX函式和NtXXX函式是不一樣的,其中ZwXXX函式會呼叫同名的NtXXX函式。而這一個過程是需要查SSDT的,而如果你反彙編ZwXXX函式即可發現,ZwXXX函式只是簡單地把索引號存入暫存器,那麼我們可以很輕鬆地通過ZwXXX函式得到我們要Hook函式的索引號,WDK的標頭檔案中包含了大部分核心函式的宣告,我們可以直接得到ZwXXX函式的地址,也就是說我們可以很輕鬆地找到我們需要Hook函式的索引號,為了簡化這一個過程,我們可以定義這樣的一個巨集來通過ZwXXX函式的地址得到在SSDT中的索引號。

//根據 ZwXXXX的地址 獲取服務函式在 SSDT 中所對應的服務的索引號
#define SYSTEMCALL_INDEX(ServiceFunction) (*(PULONG)((PUCHAR)ServiceFunction + 1))

OK,我們只需要得到KeServiceDescriptorTable中的SSDT的基地址,加上4*索引號,就是我們需要修改的地址,把這個地址修改成我們函式的地址,就可以攔截所有對這個核心函式的呼叫。

另外我們需要注意,SSDTHook 在32位系統上可以很好的工作,而在64位系統上,有兩個困難:
1。我們編寫的驅動無法直接載入,除錯說我們可以引導系統時開啟系統的“測試模式”,但釋出時必須用受信CA頒發的證書數字簽名,而且不僅是受信CA,必須是微軟指定的幾個CA才行,自己頒發一張根證書就算匯入了系統的證書ROOT儲存區也不行。或者破解系統的簽名機制,需要關閉UEFI安全引導,破解bootmgr,winload,ntoskrnl等。

2。就算驅動載入了也不能隨便改SSDT,必須過PG才行。

所以要在64位系統上實現hook,如果要求不是很高,在R3下做hoom也是一種很好的選擇,具體看我的這篇博文:[Win32] API Hook(2)在64位系統上的實現

好了,這就是SSDTHook的原理了,下一篇博文我會給出一個SSDTHook的實現,最後我希望大家能想一下一個小問題,從而加深對SSDTHook的理解。

我們hook的到底是ZwXXX函式呢?還是hook的NtXXX函式呢?

我更傾向於hook的是NtXXX函式,如果你從Google、百度搜索SSDTHook,那麼肯定有人說是hook的ZwXXX,也肯定有人說是NtXXX,那麼請思考這幾個問題:

1。我們hook後,在驅動程式中呼叫ZwXXX會被攔截嗎?呼叫NtXXX呢?

2。我們hook的是ZwXXX嗎,我們改動了ZwXXX了嗎?

嘿嘿,或者說我們hook的就是SSDT,不是ZwXXX,也不是NtXXX。

下一篇將給出一個SSDTHook NtOpenProcess保護程序的例項。