SSDT把用户层的Win32 API与内核层的Native API做了一个关联,而整个Native API都保存在SSDT中的一个函数指针数组中,只要修改函数指针数组中的某一项,就相当于HOOK了某个Native API函数。比如,修改SSDT中函数指针数组中的最后一个函数指针,就相当于HOOK了NtQueryPortInformationProcess()函数。
下面HOOK一个比较熟悉的函数,即创建进程函数NtCreateProcessEx()。该函数在指针数组的第0x30项(该编号根据系统版本的不同而不同,是系统相关的)。通过编程获取SSDT表,然后找到Native API的函数指针数组,再修改其中第0x30项的内容为自己的函数地址。为了不影响进程的正常创建,在函数中调用NtCreateProcessEx()函数。代码如下:
#include <ntddk.h>
typedef struct _SERVICE_DESCRIPTOR_TABLE
{
PULONG ServiceTableBase;
PULONG ServiceCounterTableBase;
ULONG NumberOfServices;
PUCHAR ParamTableBase;
}SERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE;
extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;
typedef NTSTATUS (*NTCREATEPROCESSEX)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES,
HANDLE, ULONG, HANDLE, HANDLE, HANDLE, ULONG);
// 保存 NtCreateProcessEx 函数的地址
NTCREATEPROCESSEX ulNtCreateProcessEx = 0;
// 在指针数组中 NtCreateProcessEx 的地址
ULONG ulNtCreateProcessExAddr = 0;
VOID UN_PROTECT()
{
__asm
{
push eax
mov eax, CR0
and eax, 0FFFEFFFFh
mov CR0, eax
pop eax
}
}
VOID RE_PROTECT()
{
__asm
{
push eax
mov eax, CR0
or eax, 0FFFEFFFFh
mov CR0, eax
pop eax
}
}
VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
UN_PROTECT();
// 替换 NtCreateProcessEx 的地址为 MyNtCreateProcessEx
*(PULONG)ulNtCreateProcessExAddr = (ULONG)ulNtCreateProcessEx;
RE_PROTECT();
}
NTSTATUS
MyNtCreateProcessEx(
__out PHANDLE ProcessHandle,
__in ACCESS_MASK DesiredAccess,
__in_opt POBJECT_ATTRIBUTES ObjectAttributes,
__in HANDLE ParentProcess,
__in ULONG Flags,
__in_opt HANDLE SectionHandle,
__in_opt HANDLE DebugPort,
__in_opt HANDLE ExceptionPort,
__in ULONG JobMemberLevel
)
{
NTSTATUS Status = STATUS_SUCCESS;
KdPrint((“Enter MyNtCreateProcessEx! \r\n”));
Status = ulNtCreateProcessEx(ProcessHandle,
DesiredAccess,
ObjectAttributes,
ParentProcess,
Flags,
SectionHandle,
DebugPort,
ExceptionPort,
JobMemberLevel);
return Status;
}
VOID HookCreateProcess()
{
ULONG ulSsdt = 0;
// 保存 NtCreateProcess 的地址
// 获取 SSDT
ulSsdt = (ULONG)KeServiceDescriptorTable->ServiceTableBase;
// 获取 NtCreateProcessEx 地址的指针
ulNtCreateProcessExAddr = ulSsdt + 0x30 * 4;
// 备份 NtCreateProcessEx 的原始地址
ulNtCreateProcessEx = (NTCREATEPROCESSEX) *(PULONG)ulNtCreateProcessExAddr;
UN_PROTECT();
// 替换 NtCreateProcessEx 的地址为 MyNtCreateProcessEx
*(PULONG)ulNtCreateProcessExAddr = (ULONG)MyNtCreateProcessEx;
RE_PROTECT();
}
NTSTATUS DriverEntry(
PDRIVER_OBJECT pDriverObject,
PUNICODE_STRING pRegistryPath
)
{
NTSTATUS Status = STATUS_SUCCESS;
pDriverObject->DriverUnload = DriverUnload;
HookCreateProcess();
return Status;
}
DriverEntry()中调用了HookCreateProcess()函数,该函数的作用是将指针数组中NtCreateProcessEx()函数的地址替换为MyNtCreateProcessEx()函数的地址。而MyNtCreateProcessEx()函数是用来取代NtCreateProcessEx()函数的函数,在这里的函数中调用了一条KdPrint()用于输出代码。整个HOOK的过程非常简单,只要找到指针数组的位置,保存原地址后修改为新的地址即可。代码中出现了两个函数,分别是UN_PROTECT()和RE_PROTECT()。这两个函数的作用是禁止和开启CPU向标志为只读的内存页进行写入的操作。执行UN_PROTECT后, CPU可以向标志为只读的内存页进行写入操作。当写入完成后,调用RE_PROTECT()函数恢复到原来的状态。把它放到虚拟机中,打开DebugView,然后加载该驱动,加载成功后随便运行一个可执行程序。可以看到,DebugView中显示了在MyNtCreateProcessEx()中的输出,如图1所示,说明HOOK成功了。
图1 MyNtCreateProcessEx()函数的输出