内核态下基于动态感染技术的应用程序执行保护(四 Hook SSDT)
3667 点击·0 回帖
![]() | ![]() | |
![]() | 今天这一章,我们就完成HookSSDT中NtCreateThread的代码,在原来的DynamicHook.asm文件的HookNtCreateThread中加入了如下的代码: HookNtCreateThread proc local dwNtCreateThreadIndex local dwNtCreateThreadAddress local status:NTSTATUS mov status,STATUS_UNSUCCESSFUL ;获取NtCreateThread服务序号 invoke GetSysCallIndex,addr g_usNtCreateThread .if eax mov dwNtCreateThreadIndex,eax ;获取NtCreteThread在内核中的地址 invoke GetSSDTOrigValue,dwNtCreateThreadIndex .if eax mov dwNtCreateThreadAddress,eax invoke DbgPrint,$CTA0("NtCreateThreadindex:%08X,address:%08X"),dwNtCreateThreadIndex,dwNtCreateThreadAddress invoke HookSSDT,dwNtCreateThreadIndex,offsetHook_NtCreateThread,addr g_lpOldNtCreateThread .if eax==STATUS_SUCCESS push dwNtCreateThreadIndex pop g_dwNtCreateThreadIndex invoke DbgPrint,$CTA0("Hook NtCreateThreadsuccess") mov status,STATUS_SUCCESS .else mov g_lpOldNtCreateThread,0 .endif .endif .endif mov eax,status ret HookNtCreateThread endp 来看一下HookSSDT这个函数的内容: HookSSDT proc uses edi esi edx dwServiceIndex:Dword,lpFunc:Dword,pOldValue:PDword local status:NTSTATUS local pMdlSystemCall:PMDL local dwOldData:Dword mov status,STATUS_UNSUCCESSFUL invoke KeGetCurrentIrql .if eax!=PASSIVE_LEVEL jmp @F .endif mov eax,dwServiceIndex mov edi,dwordptr [KeServiceDescriptorTable] assume edi:ptrServiceDescriptorEntry .if (eax>=[edi].ulNumberOfServices) jmp @F .endif mov edi,[edi].pvSSDTBase assume edi:nothing rol eax,2 add edi,eax M2M dwOldData,dword ptr [edi] mov edx,dwOldData .if lpFunc!=edx invoke IoAllocateMdl,edi,4,0,0,NULL .if eax mov esi,pOldValue .if esi M2M dword ptr[esi],dwOldData .endif mov pMdlSystemCall,eax invoke MmBuildMdlForNonPagedPool,eax invoke MmMapLockedPages,pMdlSystemCall,KernelMode push eax fastcall interlockedExchange,eax,lpFunc pop eax invoke MmUnmapLockedPages,eax,pMdlSystemCall invoke IoFreeMdl,pMdlSystemCall mov status,STATUS_SUCCESS .endif .endif @@: mov eax,status ret HookSSDT endp 为了实现这个函数,添加了许多新的代码和ASM文件,这里我就不再一一举出来,我已经把我们这篇文章到今天为止的代码上传,你可以在这里下载: http://download.csdn.net/source/3432817 使用时你要保证你安装了VS2003、MASM32和KmdKit。 HookSSDT函数的参数dwServiceIndex:SSDT服务序号,对NtCreateThread来说就是0x35,上一章我们已经获取到了;lpFunc:钩子函数的地址;pOldValue:一个指针,用来返回原来SSDT函数的地址,这个地址我们必须保存,因为我们的钩子函数在完成处理后必须回到原函数,而且在我们的驱动卸载时必须用这个地址去恢复原来的SSDT: DriverUnload proc pDriverObject:PDRIVER_OBJECT .if g_dwNtCreateThreadIndex;; g_lpOldNtCreateThread invoke UnHookSSDT,g_dwNtCreateThreadIndex,g_lpOldNtCreateThread invoke DbgPrint,$CTA0("UnhookNtCreateThread") .endif invoke DbgPrint,$CTA0("Driverunload") invoke IoDeleteSymbolicLink,addrg_usSymbolicLinkName mov eax,pDriverObject invoke IoDeleteDevice,(DRIVER_OBJECTptr [eax]).DeviceObject ret DriverUnload endp 来具体看下HookSSDT中的代码,原理也很简单:在SSDT中用我们钩子函数的地址去替换原来表中第dwServiceIndex项的内容,并将这一项中原来的值回传给pOldValue,这里我们使用了InterlockedExchange这个函数,用来保证在替换这个内容时是原子操作。 这里我就来解释一下为什么我极不推荐大家对SSDT使用Inline Hook。绝大部分通常的Inline Hook都在函数首使用一条Jmp指令跳到钩子函数。第一点麻烦的是Jmp指令覆盖了原函数5字节,那么你必须在钩子函数的最后实现大于等于5字节的最小字节的指令(不晓得我这样的表述清不清楚)并跳到原函数正确的位置去继续执行。当然你也可以简化一点写硬编码,但这也是我不推荐的。那么你至少就要用代码实现计算opcode的长度。 这还不是最重要的,重要的是这些操作是在内核中,内核代码与用户代码不同,用户代码的线程有限,而且线程切换并不频繁,你甚至可以在做Hook的时候先挂起所有线程,在内核中因为有中断等因素线程切换得非常频繁,况且中断又不能挂起。你用memcpy向目标地址拷贝5字节代码时,你并不能保证在完成拷贝前没有任何一个线程切换去执行目标函数。若有,必然立即蓝屏。不幸的是,根据经验,这样的几率是非常大的。当然,你又可以用: mov eax,cr0 and eax,7fffffffh mov cr0,eax 这样的代码来关闭分页。如此一来,情况好一些了(关闭内存分页本身会对依赖于分页机制的Windows内核造成影响),但如果是多处理器或多处理器呢?上面的代码仅关闭了当前处理器的内存分页,如果有线程此时此刻在其它处理器来调用目标函数,又立即蓝屏。 KeSetAffinityThread这个函数可以设置线程跟cpu的亲和性,但根据观察来看,并不能立即将线程切换到到指定CPU。 后来我在与某网络牛人讨论这个问题时,他提出可以通过Hook IDT表接管某中断(例如INT 1),在需要Inline Hook的函数起始位置直接用原子操作写入INT 1代码即可。但问题又回到前面,你必须保证接管所有CPU的INT 1中断。 好了,又扯远了。在内核中,可以用PsSetCreateProcessNotifyRoutine这个函数来监视进程的创建,但这不是我们要的,因为通过这个函数注册回调来监视进程,我们已经失去了在进程中做手脚的机会。看下我们的钩子函数: Hook_NtCreateThread proc ThreadHandle:PHANDLE,DesiredAccess:Dword,ObjectAttributes:POBJECT_ATTRIBUTES,ProcessHandle:HANDLE,ClientId:PCLIENT_ID,ThreadContext:PCONTEXT,InitialTeb:PVOID,CreateSuspended:Dword pushad pushfd .if ThreadContext;;CreateSuspended;;ProcessHandle;;ProcessHandle!=-1 invoke DbgPrint,$CTA0("Newprocess") .endif popfd popad invoke g_lpOldNtCreateThread,ThreadHandle,DesiredAccess,ObjectAttributes,ProcessHandle,ClientId,ThreadContext,InitialTeb,CreateSuspended ret Hook_NtCreateThread endp 同样也可以监视到进程创建,同时,在这一时刻,我们有该进程的第一个线程的一些重要信息,并且,在我们钩子函数处理完之前,进程是无法启动的,因此,我们可以在这里做很多事情。需要注意的是,在钩子函数中,不应过多的做处理,特别是字符串之类的操作,内核中很多字符串操作对IRQL是有要求的。比较好的做法是在调用函数前先用KeGetCurrentIrql检查一下(其它的代码因为自己知道自己在什么IRQL级别下,所以都不用检查)。同时我们Hook了NtCreateThread,如果你的钩子代码中有什么地方的调用深入下去又调到NtCreateThread,那又死。所以还是得非常注意。 好了,今天就写到这里吧。测试一下今天的成果: ![]() | |
![]() | ![]() |