SSDT 的全稱是 System Services Descriptor Table,系統(tǒng)服務(wù)描述符表般甲。這個表就是一個把 Ring3 的 Win32 API 和 Ring0 的內(nèi)核 API 聯(lián)系起來敷存。SSDT 并不僅僅只包含一個龐大的地址索引表历帚,它還包含著一些其它有用的信息,諸如地址索引的基地址谱煤、服務(wù)函數(shù)個數(shù)等刘离。通過修改此表的函數(shù)地址可以對常用 Windows 函數(shù)及 API 進行 Hook硫惕,從而實現(xiàn)對一些關(guān)心的系統(tǒng)動作進行過濾恼除、監(jiān)控的目的曼氛。一些 HIPS舀患、防毒軟件聊浅、系統(tǒng)監(jiān)控、注冊表監(jiān)控軟件往往會采用此接口來實現(xiàn)自己的監(jiān)控模塊旷痕。
ssdt是一張表苦蒿,即系統(tǒng)服務(wù)描述符表
kd> dd? KeServiceDescriptorTable
第一個參數(shù)指向的地址存儲的是全部的內(nèi)核函數(shù)
這個參數(shù)代表ssdt表里面有多少個內(nèi)核函數(shù)
這個參數(shù)是一個指針指向一個地址佩迟,這里表示的是與上面的內(nèi)核函數(shù)相對應(yīng)的參數(shù)個數(shù)报强,例如第一個為18秉溉,參數(shù)個數(shù)就為18/4 = 6
這里找一下OpenProcess在SSDT表的索引召嘶,首先bp OpenProcess
斷在了kerner32.OpenProcess弄跌,這里OpenProcess會調(diào)用ntdll里面的ZwOpenProcess進入ring0铛只,在ring0ZwOpenProcess又會調(diào)用NtOpenProcess
趕緊去可以發(fā)現(xiàn)mov eax,0x7A淳玩,那么這里ZwOpenProcess的索引就為0x7A
然后通過KeServiceDescriptorTable找到所有的內(nèi)核函數(shù)蜕着,通過內(nèi)核函數(shù)+偏移找到OpenProcess函數(shù)
在 NT 4.0 以上的 Windows 操作系統(tǒng)中侮东,默認(rèn)就存在兩個系統(tǒng)服務(wù)描述表悄雅,這兩個調(diào)度表對應(yīng)了兩類不同的系統(tǒng)服務(wù)宽闲,這兩個調(diào)度表為:KeServiceDescriptorTable 和 KeServiceDescriptorTableShadow容诬,其中 KeServiceDescriptorTable 主要是處理來自 Ring3 層 Kernel32.dll 中的系統(tǒng)調(diào)用览徒,而 KeServiceDescriptorTableShadow 則主要處理來自 User32.dll 和 GDI32.dll 中的系統(tǒng)調(diào)用颂龙,并且KeServiceDescriptorTable 在ntoskrnl.exe(Windows 操作系統(tǒng)內(nèi)核文件,包括內(nèi)核和執(zhí)行體層)是導(dǎo)出的躲叼,而 KeServiceDescriptorTableShadow 則是沒有被 Windows 操作系統(tǒng)所導(dǎo)出枫慷。
關(guān)于 SSDT 的全部內(nèi)容則都是通過KeServiceDescriptorTable 來完成的或听。
SSDT表的結(jié)構(gòu)通過結(jié)構(gòu)體表示為如下:
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
? KSYSTEM_SERVICE_TABLE? ntoskrnl;? // ntoskrnl.exe 的服務(wù)函數(shù)
? KSYSTEM_SERVICE_TABLE? win32k;? // win32k.sys 的服務(wù)函數(shù)
(GDI32.dll/User32.dll 的內(nèi)核支持)
? KSYSTEM_SERVICE_TABLE? notUsed1;
? KSYSTEM_SERVICE_TABLE? notUsed2;
} KSERVICE_TABLE_DESCRIPTOR, * PKSERVICE_TABLE_DESCRIPTOR;
其中每一項又是一個結(jié)構(gòu)體: KSYSTEM_SERVICE_TABLE 再姑。通過結(jié)構(gòu)體表示為如下:
typedef struct _KSYSTEM_SERVICE_TABLE
{
? PULONG? ServiceTableBase;? ? ? // SSDT (System Service Dispatch Table)的基地址
? PULONG? ServiceCounterTableBase;? // 用于 checked builds, 包含 SSDT 中每個服務(wù)被調(diào)用的次數(shù)
? ULONG? NumberOfService;? ? ? // 服務(wù)函數(shù)的個數(shù), NumberOfService * 4 就是整個地址表的大小
? ULONG? ParamTableBase;? ? ? // SSPT(System Service Parameter Table)的基地址
} KSYSTEM_SERVICE_TABLE, * PKSYSTEM_SERVICE_TABLE;
進入0環(huán)時調(diào)用號是eax傳遞的元镀,但這個調(diào)用號并不只是一個普通的數(shù)字作為索引序號霎桅,系統(tǒng)會把他用32位數(shù)據(jù)表示滔驶,拆分成19:1:12的格式,如下:
分析一下0-11這低12位組成一個真正的索引號萝快,第12位表示服務(wù)表號揪漩,13-31位沒有使用奄容。而進入內(nèi)核后調(diào)用哪一張表产徊,就由調(diào)用號中的第12位決定舟铜,為0則調(diào)用SSDT表,為1則調(diào)用ShadowSSDT表塘娶。
這里函數(shù)準(zhǔn)備好以后,就要將該函數(shù)的指針覆蓋原來NtOpenProcess的指針官册。但是需要注意的是:我們自己改自己的代碼是不用管權(quán)限的难捌,改別人的代碼很有可能這塊內(nèi)存是只讀的根吁,并不可寫击敌。
那么本質(zhì)上就是SSDT對應(yīng)的物理頁是只讀的拴事,這里有兩種辦法刃宵,我們都知道物理頁的內(nèi)存R/W位的屬性是由PDE和PTE相與而來的,那么我們就可以改變SSDT對應(yīng)的PDE和PTE的R/W屬性哮针,將物理頁設(shè)置為可讀可寫的十厢。通過CR4寄存器判斷是2-9-9-12分頁還是10-10-12分頁蛮放。
if(RCR4 & 0x00000020)
{//說明是2-9-9-12分頁
? ? ? KdPrint(("2-9-9-12分頁 %p\n",RCR4));
? ? ? KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 9) &0x007FFFF8))));
? ? ? *(DWORD64*)(0xC0000000 + ((HookFunAddr >> 9) & 0x007FFFF8)) |= 0x02;
? ? ? KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 9) &0x007FFFF8))));
}
else
{//說明是10-10-12分頁
? ? ? KdPrint(("10-10-12分頁\n"));
? ? ? KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) & 0x003FFFFC))));
? ? ? *(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) & 0x003FFFFC)) |= 0x02;
? ? ? KdPrint(("PTE2 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) &0x003FFFFC))));
}
使用PsGetCurrentThread()函數(shù)可獲取當(dāng)前KTHREAD的首地址筛武。
但是需要注意的是SSDT表所在的內(nèi)存頁屬性是只讀挎塌,沒有寫入的權(quán)限榴都,所以需要把該地址設(shè)置為可寫入,這樣才能寫入自己的函數(shù)竿音,使用的是CR0寄存器關(guān)閉只讀屬性。
簡單介紹下CR0寄存器:
可以看到這里使用32位寄存器柴信,而在CR0寄存器中随常,我們重點關(guān)注的是3個標(biāo)志位:
PE - 是否啟用保護模式,置1則啟用绪氛。
PG - 是否使用分頁模式, 置1則開啟分頁模式, 此標(biāo)志置1時, PE 標(biāo)志也必須置1,否則CPU報異常涝影。
WP WP為1時, 不能修改只讀的內(nèi)存頁 , WP為0時, 可以修改只讀的內(nèi)存頁燃逻。
所以在進行HOOK時,只要把CR0寄存器中的WP位置為0猿涨,就能對內(nèi)存進行寫入操作嘿辟。
//關(guān)閉頁只讀保護
__asm
? ? {
? ? ? ? push eax;
? ? ? ? mov eax, cr0;
? ? ? ? and eax, ~0x10000; // 與0x10000相與后取反得到0
? ? ? ? mov cr0, eax;
? ? ? ? pop eax;
? ? ? ? ret;
? ? }
//開啟頁只讀保護
__asm
? ? {
? ? ? ? push eax;
? ? ? ? mov eax, cr0;
? ? ? ? or eax, 0x10000;
? ? ? ? mov cr0, eax;
? ? ? ? pop eax;
? ? ? ? ret;
? ? }
實現(xiàn)代碼
#include <ntddk.h>
#include <ntstatus.h>
// 記錄原函數(shù)的地址
ULONG uOldNtOpenProcess;
//內(nèi)核之SSDT-HOOK
//系統(tǒng)服務(wù)表
typedef struct _KSYSTEM_SERVICE_TABLE
{
? ? PULONG ServiceTableBase;? ? ? //函數(shù)地址表的首地址
? ? PULONG ServiceCounterTableBase;//函數(shù)表中每個函數(shù)被調(diào)用的次數(shù)
? ? ULONG? NumberOfService;? ? ? ? //服務(wù)函數(shù)的個數(shù)
? ? ULONG ParamTableBase;? ? ? ? ? //參數(shù)個數(shù)表首地址
}KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;
//服務(wù)描述符
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
? ? KSYSTEM_SERVICE_TABLE ntoskrnl;//ntoskrnl.exe的服務(wù)函數(shù),SSDT
? ? KSYSTEM_SERVICE_TABLE win32k;? //win32k.sys的服務(wù)函數(shù),ShadowSSDT
? ? KSYSTEM_SERVICE_TABLE notUsed1;//暫時沒用1
? ? KSYSTEM_SERVICE_TABLE notUsed2;//暫時沒用2
}KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
//定義HOOK的函數(shù)的類型
typedef NTSTATUS(NTAPI* FuZwOpenProcess)(
? ? _Out_ PHANDLE ProcessHandle,
? ? _In_ ACCESS_MASK DesiredAccess,
? ? _In_ POBJECT_ATTRIBUTES ObjectAttributes,
? ? _In_opt_ PCLIENT_ID ClientId
? ? );
//自寫的函數(shù)聲明
NTSTATUS NTAPI MyZwOpenProcess(
? ? _Out_ PHANDLE ProcessHandle,
? ? _In_ ACCESS_MASK DesiredAccess,
? ? _In_ POBJECT_ATTRIBUTES ObjectAttributes,
? ? _In_opt_ PCLIENT_ID ClientId
);
// KeServiceDescriptorTable 為 ntoskrnl.exe 所導(dǎo)出的全局變量
extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;
//記錄系統(tǒng)的該函數(shù)
FuZwOpenProcess g_OldZwOpenProcess;
//服務(wù)描述符表指針
KSERVICE_TABLE_DESCRIPTOR* g_pServiceTable = NULL;
//要保護進程的ID
ULONG g_Pid = 1624;
//安裝鉤子
NTSTATUS HookNtOpenProcess();
//卸載鉤子
NTSTATUS UnHookNtOpenProcess();
//關(guān)閉頁寫入保護
void ShutPageProtect();
//開啟頁寫入保護
void OpenPageProtect();
//卸載驅(qū)動
void DriverUnload(DRIVER_OBJECT* obj);
/***驅(qū)動入口主函數(shù)***/
NTSTATUS DriverEntry(DRIVER_OBJECT* driver, UNICODE_STRING* path)
{
? ? KdPrint(("驅(qū)動啟動成功!\n"));
? ? //安裝鉤子
? ? HookNtOpenProcess();
? ? driver->DriverUnload = DriverUnload;
? ? return STATUS_SUCCESS;
}
//卸載驅(qū)動
void DriverUnload(DRIVER_OBJECT* obj)
{
? ? //卸載鉤子
? ? UnHookNtOpenProcess();
? ? KdPrint(("驅(qū)動卸載成功昙读!\n"));
}
NTSTATUS HookNtOpenProcess()
{
? ? NTSTATUS Status;
? ? Status = STATUS_SUCCESS;
? ? //1.關(guān)閉頁只讀保護
? ? ShutPageProtect();
? ? //2.修改SSDT表
? ? uOldNtOpenProcess = KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7a];
? ? KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7a] =(ULONG)MyZwOpenProcess;
? ? //3.開啟頁只讀保護
? ? OpenPageProtect();
? ? return Status;
}
//卸載鉤子
NTSTATUS UnHookNtOpenProcess()
{
? ? NTSTATUS status;
? ? status = STATUS_SUCCESS;
? ? //1.關(guān)閉頁只讀保護
? ? ShutPageProtect();
? ? //2.寫入原來的函數(shù)到SSDT表內(nèi)
? ? KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7a] = uOldNtOpenProcess;
? ? //3.開啟頁只讀保護
? ? OpenPageProtect();
? ? return status;
}
//關(guān)閉頁只讀保護
void _declspec(naked) ShutPageProtect()
{
? ? __asm
? ? {
? ? ? ? push eax;
? ? ? ? mov eax, cr0;
? ? ? ? and eax, ~0x10000;
? ? ? ? mov cr0, eax;
? ? ? ? pop eax;
? ? ? ? ret;
? ? }
}
//開啟頁只讀保護
void _declspec(naked) OpenPageProtect()
{
? ? __asm
? ? {
? ? ? ? push eax;
? ? ? ? mov eax, cr0;
? ? ? ? or eax, 0x10000;
? ? ? ? mov cr0, eax;
? ? ? ? pop eax;
? ? ? ? ret;
? ? }
}
//自寫的函數(shù)
NTSTATUS NTAPI MyZwOpenProcess(
? ? _Out_ PHANDLE ProcessHandle,
? ? _In_ ACCESS_MASK DesiredAccess,
? ? _In_ POBJECT_ATTRIBUTES ObjectAttributes,
? ? _In_opt_ PCLIENT_ID ClientId
)
{
? ? //當(dāng)此進程為要保護的進程時
? ? if (ClientId->UniqueProcess == (HANDLE)g_Pid)
? ? {
? ? ? ? //設(shè)為拒絕訪問
? ? ? ? DesiredAccess = 0;
? ? }
? ? //調(diào)用原函數(shù)
? ? return NtOpenProcess(ProcessHandle,DesiredAccess,ObjectAttributes,ClientId);
}
實現(xiàn)效果如下
參考自hxd的r0下