在说到SSDTHook实现之前,我们首先来了解一下Win32API的调用过程,我们以ReadFile()为例,来看看从Ring3层到Ring0层的调用过程:
在一个Ring3进程Test.exe中调用ReadFile()函数,此时我们调用的是kernel32.dll中的导出函数,接下来ReadFile()会调用到位于ntdll.dll中的NtReadFile()或者ZwReadFile(),在ntdll.dll中的这两个函数实现是一样的,通过Windbg调试器查看如下:
通过调用ntdll.dll中的NtReadFile()函数会将系统调用号放入EAX寄存器中,以便切入内核时在SSDT表中找到对应的内核函数;如果内核采用自陷指令int 0x2e时,将堆栈上的参数块起始地址放入EDX寄存器中,以便于在内核层拷贝用户层所传递参数;接下来会通过int 0x2E自陷指令,切入到内核中去,此处的0x2E是系统中断向量表中的中断向量,也是IDT表的数组下标,在IDT表的对应偏移处找到中断程序入口地址,执行中断处理函数KiSystemService()。如果采用快速调用指令sysenter时,将堆栈指针保存在EDX寄存器中,进入内核,系统的的处理函数就是KiFastCallEntry()。
切入内核之后,会执行内核层(也就是内核模块ntoskrnl.exe)的ZwReadFile()函数,取出系统调用号,在SSDT表中找到服务表基地址,根据调用号找到对应的函数,即为最终实现系统调用实现NtReadFile()。
所以我们想要进行SSDTHook,只需要根据索引找到SSDT表中对应Nt系列函数的地址,修改索引所对应的函数地址为我们的Fake函数,即可实现SSDTHook。
以上为原理分析,下面进入正题,看代码:
此处代码的重点在于构建MDL,而构建MDL的目的是为了修改内存属性,因为原本的属性是写保护,将目标虚拟内存对应的内容拷贝一份到新建的MDL虚拟内存块上,而这两块内存是映射到同一片物理内存上。最终使用InterlockedExchange原子锁操作方式用Fake函数地址替换了目标函数地址。
NTSTATUS SSDTHook(
PUCHAR TargetFunctionAddress,
PVOID FakeFunctionAddress,
PVOID* OriginalFunctionAddress)
{
NTSTATUS Status = STATUS_SUCCESS;
__Mdl = MmCreateMdl(NULL,
KeServiceDescriptorTable->ServiceTableBase,
KeServiceDescriptorTable->NumberOfService * sizeof(PVOID));
if (!__Mdl)
{
return STATUS_UNSUCCESSFUL;
}
MmBuildMdlForNonPagedPool(__Mdl);
//修改属性为可写
__Mdl->MdlFlags = __Mdl->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;
__ServiceTableBase = MmMapLockedPages(__Mdl, KernelMode);
if (__ServiceTableBase)
{
*OriginalFunctionAddress = (PVOID)InterlockedExchange(
(PLONG)(&__ServiceTableBase[GetSSDTIndex(TargetFunctionAddress)]),
(LONG)FakeFunctionAddress);
}
return Status;
}
复制代码
也可以使用嵌入式汇编的方法,修改内存写保护属性,那就是修改CR0寄存器得第16位为0,则废掉写保护
_asm
{
cli; //禁止任何中断发生
push eax;
mov eax, cr0;
mov Attribute, eax;
and eax, 0xFFFEFFFF; // CR0 16 BIT = 0
mov cr0, eax;
pop eax;
};
复制代码
上面代码用到了宏GetSSDTIndex是得到系统服务号,也就是函数地址表的索引,这里对函数指针强转为PUCHAR类型再加1是有原因的,我觉得有必要解释以下
#define GetSSDTIndex(ServiceFunction) (*(PULONG)((PUCHAR)ServiceFunction + 1))
复制代码
2: kd> u ZwReadFile
nt!ZwReadFile:
84841354 b811010000 mov eax,111h
84841359 8d542404 lea edx,[esp+4]
8484135d 9c pushfd
8484135e 6a08 push 8
84841360 e859130000 call nt!KiSystemService (848426be)
84841365 c22400 ret 24h
复制代码
我想大家看一下这段Windbg调试器的内容就能明白为何强转成PUCHAR类型的指针再加一了吧,就是为了越过初始的b8指令,取出函数索引0111h。
至此,Hook结束,其实也没什么难的,重要的是懂得其中Hook得原理。Hook完毕时候,等到驱动卸载时候,一定要注意恢复SSDT表嗷!!
在下才疏学浅,写帖子过程中难免存在疏忽和纰漏,还请各位大牛指出?。