Rookit技術之SSDT_Hook
0x0 SSDT簡介
SSDT全稱爲System Services Descriptor Table,中文爲系統服務描述符表,ssdt表就是把ring3的Win32 API和ring0的內核API聯繫起來。SSDT並不僅僅只包含一個龐大的地址索引表,它還包含着一些其它有用的信息,諸如地址索引的基地址、服務函數個數等。SSDT通過修改此表的函數地址可以對常用windows函數進行hook,從而實現對一些核心的系統動作進行過濾、監控的目的。一些HIPS、防毒軟件、系統監控、註冊表監控軟件往往會採用此接口來實現自己的監控模塊(引用百度)
SSDT_Hook即掛鉤系統服務描述表中函數.這是一種古老的技術,相傳是在病毒中首次使用這樣的技術.惡意軟件爲了提高自己的權限,來進行底層的掛鉤,於是SSDT這個表被利用了.
我們知道,用戶層調用函數都會調用到ntdll.dll中,其實這個ntdll.dll就是用戶層到內核層的接口.裏邊維護着兩張表-KeServiceDescriptorTable 和KeServiceDescriptorTableShadow.
前者簡稱SSDT,其中是函數地址數組,多數操作系統進程相關的函數都在其中,後者簡稱shadowSSDT,是用戶界面相關的函數地址數組,比如來自user32.dll或者GDI32.dll的函數
0x1 測試環境
系統: 虛擬機 Windows 7 32bit
工具: Windbg,驅動加載工具等
這個技術可以穩定的在windows 32位的系統下進行,32位系統其實被各種Hook技術被用爛了,所以現在在64位上的Hook技術被微軟限制了
0x2 達到目標
Hook NtOpenProcess函數,使我們的目標程序(計算器程序)不能被其他任務使用這個函數獲取句柄,結束這個程序.
0x3 主要思路
1.加載驅動,獲取需要保護進程的PID
2.保存系統的老的NtOpenProcess函數地址
3.修改內存保護屬性使得SSDT表可讀可寫
4.修改SSDT表中NtOpenProcess函數處的地址爲我們自己寫的函數的地址,調用時候執行我們的函數
5.函數中做判斷,如果是我們要保護的進程就直接返回,不讓其獲得句柄,如果不是,就主動調用老的系統NtOpenProcess函數
6.驅動卸載,還原被Hook的地址
0x4 關鍵代碼
#include <ntddk.h>
//SSDT HOOK
//Hook NtOpenProcess 使進程對象不會再次被打開
//SSDT表結構體
typedef struct _ServiceDesriptorEntry
{
ULONG *ServiceTableBase; // 服務表基址
ULONG *ServiceCounterTableBase; // 計數表基址
ULONG NumberOfServices; // 表中項的個數
UCHAR *ParamTableBase; // 參數表基址
}SSDTEntry, *PSSDTEntry;
// 導入SSDT
NTSYSAPI SSDTEntry KeServiceDescriptorTable;
//被保護程序的PID
ULONG g_uProtectPID = 0;
//OpenProcess函數原型
typedef NTSTATUS(NTAPI *NTOPENPROCESS)(__out PHANDLE ProcessHandle,
__in ACCESS_MASK DesiredAccess,
__in POBJECT_ATTRIBUTES ObjectAttributes,
__in_opt PCLIENT_ID ClientId
);
//老的OpenProcess函數地址
NTOPENPROCESS g_OldOpenProcess = NULL;
//自己的HookNtProcess函數
NTSTATUS NTAPI HookNtOpenProcess(__out PHANDLE ProcessHandle,
__in ACCESS_MASK DesiredAccess,
__in POBJECT_ATTRIBUTES ObjectAttributes,
__in_opt PCLIENT_ID ClientId)
{
//判斷是不是要針對的進程PID
if ((ULONG)ClientId->UniqueProcess == g_uProtectPID)
{
return STATUS_ABANDONED;
}
//過濾完成再次調用原來的NtOpenprocess
return g_OldOpenProcess(
ProcessHandle,
DesiredAccess,
ObjectAttributes,
ClientId
);
}
//關閉內存保護裸函數
void _declspec(naked)OffMemoryProtect()
{
__asm { //關閉內存保護
push eax;
mov eax, cr0;
and eax, ~0x10000;
mov cr0, eax;
pop eax;
ret;
}
}
//開啓內存保護裸函數
void _declspec(naked)OnMemoryProtect()
{
__asm { //恢復內存保護
push eax;
mov eax, cr0;
or eax, 0x10000;
mov cr0, eax;
pop eax;
ret;
}
}
//開啓Hook
void OnHook()
{
//保存環境,OpenProcess函數在SSDT表中的第0xBE項(190)
g_OldOpenProcess= (NTOPENPROCESS)KeServiceDescriptorTable.ServiceTableBase[0xBE];
//開始Hook前需要修改內存屬性,修改完地址後恢復內存屬性
OffMemoryProtect();
KeServiceDescriptorTable.ServiceTableBase[0xBE] = (ULONG)HookNtOpenProcess;
OnMemoryProtect();
}
//關閉Hook
void OffHook()
{
//修改地址前,先修改內存屬性,改完後還原內存屬性
OffMemoryProtect();
KeServiceDescriptorTable.ServiceTableBase[0xBE] = (ULONG)g_OldOpenProcess;
OnMemoryProtect();
}
//驅動卸載函數
VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
OffHook();
DbgPrint("Driver Unload\n");
}
//驅動入口函數
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath)
{
//DbgBreakPoint();
DbgPrint("Hello World\n");
g_uProtectPID = 4000;
//開啓Hook
OnHook();
pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
0x5 測試效果
爲了簡單,直接在驅動中寫死了PID,所以我們在任務管理器中查看其PID,然後在寫到驅動中.
加載驅動,使用任務管理器結束”計算器”程序
卸載驅動,再次結束程序測試,正常結束了計算器.