標籤:des blog http io os 使用 ar strong for
目錄
- SSDT Hook
- SSDT簡介
- SSDT結構
- SSDT HOOK原理
- Hook前準備
- 如何獲得SSDT中函數的地址呢
- SSDT Hook流程
- SSDT Hook實現進程保護
- Ring3與Ring0的通訊
- 如何安裝啟動停止卸載服務
- 參考文獻
- 源碼附件
- 著作權
SSDT Hook載入驅動並成功Hook NtTerminateProcess函數:當對 指定的進程進行保護後,嘗試使用“工作管理員”結束進程的時候,會彈出“拒絕訪問”的視窗,說明,我們的目的已經達到:SSDT簡介SSDT 的全稱是 System Services Descriptor Table,系統服務描述符表。
這個表就是一個把 Ring3 的 Win32 API 和 Ring0 的核心 API 聯絡起來。
SSDT 並不僅僅只包含一個龐大的地址索引表,它還包含著一些其它有用的資訊,諸如地址索引的基地址、服務函數個數等。
通過修改此表的函數地址可以對常用 Windows 函數及 API 進行 Hook,從而實現對一些關心的系統動作進行過濾、監控的目的。
一些 HIPS、防毒軟體、系統監控、註冊表監視軟體往往會採用此介面來實現自己的監控模組。
SSDT結構SSDT即系統服務描述符表,它的結構如下(參考《Undocument Windows 2000 Secretes》第二章):
// KSYSTEM_SERVICE_TABLE 和 KSERVICE_TABLE_DESCRIPTOR// 用來定義 SSDT 結構typedef struct _KSYSTEM_SERVICE_TABLE{ PULONG ServiceTableBase; // SSDT (System Service Dispatch Table)的基地址 PULONG ServiceCounterTableBase; // 用於 checked builds, 包含 SSDT 中每個服務被調用的次數 ULONG NumberOfService; // 服務函數的個數, NumberOfService * 4 就是整個地址表的大小 ULONG ParamTableBase; // SSPT(System Service Parameter Table)的基地址} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;typedef struct _KSERVICE_TABLE_DESCRIPTOR{ KSYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe 的服務函數 KSYSTEM_SERVICE_TABLE win32k; // win32k.sys 的服務函數(GDI32.dll/User32.dll 的核心支援) KSYSTEM_SERVICE_TABLE notUsed1; KSYSTEM_SERVICE_TABLE notUsed2;}KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR; 核心中有兩個系統服務描述符表,一個是KeServiceDescriptorTable(由ntoskrnl.exe匯出),一個是KeServieDescriptorTableShadow(沒有匯出)。 兩者的區別是,KeServiceDescriptorTable僅有ntoskrnel一項,KeServieDescriptorTableShadow包含了ntoskrnel以及win32k。一般的Native API的服務地址由KeServiceDescriptorTable指派,gdi.dll/user.dll的核心API調用服務地址由KeServieDescriptorTableShadow指派。還有要清楚一點的是win32k.sys只有在GUI線程中才載入,一般情況下是不載入的,所以要Hook KeServieDescriptorTableShadow的話,一般是用一個GUI程式通過IoControlCode來觸發(想當初不明白這點,藍色當機畫面了N次都想不明白是怎麼回事)。SSDT HOOK原理關於核心 Hook 有多種類型,下面也給出一副圖示:SSDT HOOK只是其中一種Hook技術,本篇文章主要講解SSDT Hook的使用。SSDT HOOK原理圖通過Kernel Detective工具,我們可以發現,SSDT Hook前後,NtTerminateProcess的當前地址會發生變化,其中,變化後的當前地址:0xF885A110為我們自訂的Hook函數(即:HookNtTerminateProcess)的地址。這樣,以後每次執行NtTerminateProcess的時候,就會根據執行“當前地址”所指向的函數了,這也就是SSDT Hook的原理。另外,看雪的"墮落天才"寫的不錯,我直接引用下:SSDT HOOK 的原理其實非常簡單,我們先實際看看KeServiceDescriptorTable是什麼樣的。
lkd> dd KeServiceDescriptorTable 8055ab80 804e3d20 00000000 0000011c 804d9f48 8055ab90 00000000 00000000 00000000 00000000 8055aba0 00000000 00000000 00000000 00000000 8055abb0 00000000 00000000 00000000 00000000
如上,80587691 805716ef 8057ab71 80581b5c 這些就是系統服務函數的地址了。比如當我們在ring3調用OpenProcess時,進入sysenter的ID是0x7A(XP SP2),然後系統查KeServiceDescriptorTable,大概是這樣KeServiceDescriptorTable.ntoskrnel.ServiceTableBase(804e3d20) + 0x7A * 4 = 804E3F08,然後804E3F08 ->8057559e 這個就是OpenProcess系統服務函數所在,我們再跟蹤看看:
lkd> u 8057559e nt!NtOpenProcess: 8057559e 68c4000000 push 0C4h 805755a3 6860b54e80 push offset nt!ObReferenceObjectByPointer+0x127 (804eb560) 805755a8 e8e5e4f6ff call nt!InterlockedPushEntrySList+0x79 (804e3a92) 805755ad 33f6 xor esi,esi
原來8057559e就是NtOpenProcess函數所在的起始地址。
嗯,如果我們把8057559e改為指向我們函數的地址呢?比如 MyNtOpenProcess,那麼系統就會直接調用MyNtOpenProcess,而不是原來的NtOpenProcess了。這就是SSDT HOOK 原理所在。另外,關於Ring3層轉入Ring0層的具體流程,可以參考下我的這篇博文,對加深理解SSDT Hook技術還是有協助的:Ring3轉入Ring0跟蹤Hook前準備我們要修改SSDT表,首先這個表必須是可寫的,但在xp以後的系統中他都是唯讀,三個辦法來修改記憶體保護機制(1) 更改註冊表 恢複頁面保護:HKLM\SYSTEM\CurrentControlset\Control\Session Manger\Memory Management\EnforceWriteProtection=0去掉頁面保護:HKLM\SYSTEM\CurrentControlset\Control\Session Manger\Memory Management\DisablePagingExecutive=1(2)改變CR0寄存器的第1位Windows對記憶體的分配,是採用的分頁管理。其中有個CR0寄存器,如:
其中第1位叫做保護屬性位,控制著頁的讀或寫屬性。如果為1,則可以讀/寫/執行;如果為0,則只可以讀/執行。SSDT,IDT的頁屬性在預設下都是唯讀,可執行檔,但不能寫。代碼如下:
//設定為不可寫void DisableWrite(){ __try { _asm { mov eax, cr0 or eax, 10000h mov cr0, eax sti } } __except(1) { DbgPrint("DisableWrite執行失敗!"); }}// 設定為可寫void EnableWrite(){ __try { _asm { cli mov eax,cr0 and eax,not 10000h //and eax,0FFFEFFFFh mov cr0,eax } } __except(1) { DbgPrint("EnableWrite執行失敗!"); }}(3)通過Memory Descriptor List(MDL)
具體做法可以google下,這裡就不介紹了
如何獲得SSDT中函數的地址呢?
這裡主要使用了兩個宏:
①擷取指定服務的索引號:SYSCALL_INDEX
②擷取指定服務的當前地址:SYSCALL_FUNCTION
這兩個宏的具體定義如下:
//根據 ZwServiceFunction 擷取 ZwServiceFunction 在 SSDT 中所對應的服務的索引號 #define SYSCALL_INDEX(ServiceFunction) (*(PULONG)((PUCHAR)ServiceFunction + 1)) //根據ZwServiceFunction 來獲得服務在 SSDT 中的索引號,然後再通過該索引號來擷取ntServiceFunction的地址 #define SYSCALL_FUNCTION(ServiceFunction) KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[SYSCALL_INDEX(ServiceFunction)]
SSDT Hook流程
在驅動的入口函數中(DriverEntry),對未進行SSDT Hook前的SSDT表進行了備份(用一個數組儲存),備份時,一個索引號對應一個當前地址,如所示。
這樣,在解除Hook的時候,就可以從全域數組中根據索引號擷取未Hook前的服務名的當前地址,以便將原來的地址寫回去,這一步很重要。
當使用者選擇保護某個進程的時候,就會通過DeviceIoControl發送一個IO_INSERT_PROTECT_PROCESS控制碼給驅動程式,此時驅動程式會產生一個IRP:IRP_MJ_DEVICE_CONTROL,我們事先已經在驅動程式中為
IRP_MJ_DEVICE_CONTROL指定了一個派遣函數:SSDTHook_DispatchRoutine_CONTROL。在該派遣函數中:我們通過擷取控制碼(是保護進程還是取消保護進程),如果是要保護某個進程,則通過DeviceIoControl的第3個參數將要保護的進程的pid傳遞給驅動程式。然後在派遣函數SSDTHook_DispatchRoutine_CONTROL中從緩衝區中讀取該pid,如果是要保護進程,則將要“保護進程”的pid添加到一個數組中,如果是要“取消保護進程”,則將要取消保護的進程PID從數組中移除。
在Hook NtTermianteProcess函數後,會執行我們自訂的函數:HookNtTerminateProcess,在HookNtTerminateProcess函數中,我們判斷當前進程是否在要保護的進程數組中,如果該數組中存在該pid,則我們返回一個“許可權不夠”的異常,如果進程保護數組中不存在該pid,則直接調用原來 SSDT 中的 NtTerminateProcess 來結束進程。
SSDT Hook實現進程保護有了上面的理論基礎之後,接下來可以談談SSDT Hook實現進程保護的具體實現了。實現進程保護,可以Hook NtTermianteProcess,另外也可以Hook NtOpenProcess,這裡,我是Hook NtTermianteProcess。SSDT Hook原理一節中已經說過,SSDT Hook原理的本質是:自訂一個函數(HookNtTerminateProcess),讓系統服務NtTermianteProcess的當前地址指向我們自訂函數地址。這一步工作是在驅動入口函數中執行的。當驅動載入的時候,將自訂函數的地址寫入SSDT表中NtTermianteProcess服務的當前地址:
// 實現 Hook 的安裝,主要是在 SSDT 中用 newService 來替換掉 oldServiceNTSTATUS InstallHook(ULONG oldService, ULONG newService){ __try { ULONG uOldAttr = 0; EnableWrite(); //去掉頁面保護 KdPrint(("偽造NtTerminateProcess地址: %x\n",(int)newService)); //KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[SYSCALL_INDEX(oldService)] = newService; SYSCALL_FUNCTION(oldService) = newService;// DisableWrite(); //恢複頁面保護 return STATUS_SUCCESS; } __except(1) { KdPrint(("安裝Hook失敗!")); }}這裡需要注意的是:在Hook前,需要去掉記憶體的頁面保護屬性,Hook後,需要回複記憶體的頁面保護屬性。HookNtTerminateProcess函數的代碼如下:
//************************************// 函數名稱 : HookNtTerminateProcess// 描 述 : 自訂的 NtOpenProcess,用來實現 Hook Kernel API// 日 期 : 2013/06/28// 參 數 : ProcessHandle:進程控制代碼 ExitStatus:// 返 回 值 : //************************************NTSTATUS HookNtTerminateProcess(__in_opt HANDLE ProcessHandle,__in NTSTATUS ExitStatus){ ULONG uPID; NTSTATUS rtStatus; PCHAR pStrProcName; PEPROCESS pEProcess; ANSI_STRING strProcName; // 通過進程控制代碼來獲得該進程所對應的 FileObject 對象,由於這裡是進程對象,自然獲得的是 EPROCESS 對象 rtStatus = ObReferenceObjectByHandle(ProcessHandle, FILE_READ_DATA, NULL, KernelMode, (PVOID*)&pEProcess, NULL); if (!NT_SUCCESS(rtStatus)) { return rtStatus; } // 儲存 SSDT 中原來的 NtTerminateProcess 地址 pOldNtTerminateProcess = (NTTERMINATEPROCESS)oldSysServiceAddr[SYSCALL_INDEX(ZwTerminateProcess)]; // 通過該函數可以擷取到進程名稱和進程 ID,該函數在核心中實質是匯出的(在 WRK 中可以看到) // 但是 ntddk.h 中並沒有到處,所以需要自己聲明才能使用 uPID = (ULONG)PsGetProcessId(pEProcess); pStrProcName = _strupr((TCHAR *)PsGetProcessImageFileName(pEProcess));//使用微軟未公開的PsGetProcessImageFileName函數擷取進程名 // 通過進程名來初始化一個 ASCII 字串 RtlInitAnsiString(&strProcName, pStrProcName); if (ValidateProcessNeedProtect(uPID) != -1) { // 確保調用者進程能夠結束(這裡主要是指 taskmgr.exe) if (uPID != (ULONG)PsGetProcessId(PsGetCurrentProcess())) { // 如果該進程是所保護的的進程的話,則返回許可權不夠的異常即可 return STATUS_ACCESS_DENIED; } } // 對於非保護的進程可以直接調用原來 SSDT 中的 NtTerminateProcess 來結束進程 rtStatus = pOldNtTerminateProcess(ProcessHandle, ExitStatus); return rtStatus;}Ring3與Ring0的通訊請看考:張帆《Windows驅動開發技術詳解》一書第7章:派遣函數如何安裝、啟動、停止、卸載服務請參考我的另一篇博文:NT式驅動載入器(附帶源碼)參考文獻SSDT Hook的妙用-對抗ring0 inline hook:http://bbs.pediy.com/showthread.php?t=40832進程隱藏與進程保護(SSDT Hook 實現):http://www.cnblogs.com/BoyXiao/archive/2011/09/03/2164574.html張帆的《Windows驅動開發技術詳解》一書源碼附件
SSDT Hook實現核心級的進程保護著作權 著作權,歡迎轉載,但轉載請註明: 轉載自
SSDT Hook結構