内核态下基于动态感染技术的应用程序执行保护(四 Hook SSDT)

今天这一章,我们就完成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,那又死。所以还是得非常注意。

好了,今天就写到这里吧。测试一下今天的成果:

内核态下基于动态感染技术的应用程序执行保护(四 Hook SSDT)

下一章我会讲如何将我们的代码注入到新进程中并最先执行。

你可能感兴趣的:(应用程序)