SSDT 的全称是 System Services Descriptor Table,系统服务描述符表。
内核中有两个系统服务描述符表,一个是KeServiceDescriptorTable,另一个是KeServieDescriptorTableShadow。
KeServiceDescriptorTable 主要是处理来自 Ring3 层得 Kernel32.dll 中的系统调用,而 KeServiceDescriptorTableShadow 则主要处理来自 User32.dll 和 GDI32.dll 中的系统调用,并且 KeServiceDescriptorTable 在 ntoskrnl.exe(Windows 操作系统内核文件,包括内核和执行体层)是导出的,而 KeServiceDescriptorTableShadow 则是没有被 Windows 操作系统所导出,而关于 SSDT 的全部内容则都是通过 KeServiceDescriptorTable 来完成的。
// 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;
Ntdll.dll 中的 API 是一个简单的包装函数,当 Kernel32.dll 中的 API 通过 Ntdll.dll 时,会完成参数的检查,再调用一个中断(int 2Eh 或者 SysEnter 指令),从而实现从 Ring3 进入 Ring0 层,并且将所要调用的服务号(即 SSDT 数组中的索引值)存放到寄存器 EAX 中,并且将参数地址放到指定的寄存器(EDX)中,再将参数复制到内核地址空间中,再根据存放在 EAX 中的索引值来在 SSDT 数组中调用指定的服务。
nt!ZwOpenProcess: 840736cc b8be000000 mov eax,0BEh 840736d1 8d542404 lea edx,[esp+4] 840736d5 9c pushfd 840736d6 6a08 push 8 840736d8 e8b1190000 call nt!KiSystemService (8407508e) 840736dd c21000 ret 10h
kd> !idt 2e
Dumping IDT:
2e: 8407508e nt!KiSystemService
在上面调试中可以看到在 Ring0 下的 ZwOpenProcess将 0BEh 放入了寄存器 eax 中,然后调用了系统服务分发函数 KiSystemService,而 KiSystemService 函数则是根据 eax 寄存器中的索引值,然后再 SSDT 数组中找到索引值为 eax 寄存器中存放的值得那个 SSDT 项,最后就是根据这个 SSDT 项中所存放的系统服务的地址来调用这个系统服务了,比如在这里就是调用 KeServiceDescriptorTable[0BEh] 处所保存的地址所对应的系统服务了 ,也就是调用 Ring0 下的 NtOpenProcess了。

如图可通过 ZwOpenProcess 获取 NtOpenProcess 索引号 0BEh,通过 KeServiceDescriptorTable 可获得 SSDT 首地址 840b5d9c。可以根据 SSDT 系统服务地址= SSDT 首地址 + 4 * 索引号(Address = 840b5d9c + 4 * 0BEh = 840b6094;),然后 840b6094->84249aa0 这个就是 NtOpenProcess 系统服务函数所在。我们需要做的就是将 84249aa0 地址替换成我们的Hook函数地址。
SDT Hook前后,NtOpenProcess 的当前地址会发生变化。变化后的当前地址:9d31b050 为我们自定义的Hook函数(即 HookNtOpenProcess)的地址。之后每次执行 NtOpenProcess 的时候,都会根据执行“当前地址”所指向的函数了,由于SSDT所在的物理页是只读、可执行的,需要修改页属性为可写(1.通过页表基址直接修改;2.通过修改CR0寄存器修改;3.通过MDL修改;)。
1.加载驱动,获取需要保护进程的PID;
2.保存系统的原NtOpenProcess函数地址 ;
3.修改内存保护属性使得SSDT表可读可写 ;
4.修改SSDT表中NtOpenProcess函数处的地址为我们自定义Hook函数的地址,使调用时执行自定义的函数;
5.函数中做判断,如果是我们要保护的进程就直接返回,不让其获得句柄,其他则执行原NtOpenProcess函数;
6.驱动卸载,还原被Hook的地址;
#include <ntddk.h>
// ntdll!NtWriteFile:
// 001b : 77656a68 b88c010000 mov eax, 18Ch
// 001b : 77656a6d ba0003fe7f mov edx, offset SharedUserData!SystemCallStub(7ffe0300)
// 001b : 77656a72 ff12 call dword ptr[edx]
// 001b : 77656a74 c22400 ret 24h
// 001b : 77656a77 90 nop
//
//call dword ptr[edx]:
//ntdll!KiFastSystemCall:
// 001b : 776570b0 8bd4 mov edx, esp
// 001b : 776570b2 0f34 sysenter
// SSDT HOOK 实现进程保护
// 定义修复和恢复页属性的函数
PMDL MDSystemCall;
PVOID *MappedSCT;
// SSDT表结构体
typedef struct _ServiceDesriptorEntry
{
ULONG *ServiceTableBase; // SSDT (System Service Dispatch Table)的基地址
ULONG *ServiceCounterTableBase; // 用于 checked builds, 包含 SSDT 中每个服务被调用的次数
ULONG NumberOfServices; // 服务函数的个数, NumberOfService * 4 就是整个地址表的大小
UCHAR *ParamTableBase; // SSPT(System Service Parameter Table)的基地址
}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()
{
// MDSystemCall = MmCreateMdl(NULL, KeServiceDescriptorTable.ServiceTableBase, KeServiceDescriptorTable.NumberOfServices * 4);
// if (!MDSystemCall)
// return STATUS_UNSUCCESSFUL;
// MmBuildMdlForNonPagedPool(MDSystemCall);
// MDSystemCall->MdlFlags = MDSystemCall->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;
// MappedSCT = MmMapLockedPages(MDSystemCall, KernelMode);
// 关闭内存保护
__asm {
push eax;
mov eax, CR0;
and eax, not 0x10000;
mov CR0, eax;
pop eax;
ret;
}
}
void _declspec(naked)OnMemoryProtect()
{
// if (MDSystemCall)
// {
// MmUnmapLockedPages(MappedSCT, MDSystemCall);
// IoFreeMdl(MDSystemCall);
// }
// 恢复内存保护
__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("------驱动卸载------\r\n");
}
// 驱动入口函数
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath)
{
// DbgBreakPoint();
DbgPrint("------驱动加载------\r\n");
g_uProtectPID = 3632;
// 开启Hook
OnHook();
pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}

原文:https://www.cnblogs.com/Schicksal/p/11437769.html