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

来源:互联网 发布:02795522网络诈骗电话 编辑:程序博客网 时间:2024/05/21 18:26

分类: 技术--汇编
 
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 [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,那又死。所以还是得非常注意。

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

 

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

0 0
原创粉丝点击