梦的忧伤 发表于 2017-6-1 17:26:27

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

分类: 技术--汇编

hookattributeswindowsobjectnull测试


今天这一章,我们就完成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

assume edi:ptrServiceDescriptorEntry

.if (eax>=.ulNumberOfServices)

jmp @F

.endif

mov edi,.pvSSDTBase

assume edi:nothing

rol eax,2

add edi,eax

M2M dwOldData,dword ptr

mov edx,dwOldData

.if lpFunc!=edx

invoke IoAllocateMdl,edi,4,0,0,NULL

.if eax

mov esi,pOldValue

.if esi

M2M dword ptr,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 ).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,那又死。所以还是得非常注意。

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



下一章我会讲如何将我们的代码注入到新进程中并最先执行。
页: [1]
查看完整版本: 内核态下基于动态感染技术的应用程序执行保护(四 Hook SSDT)