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

分类:技术汇编

源代码:http://download.csdn.net/detail/hitetoshi/3633188

        绝影做尘,铁甲四方逐鹿而争。风起云涌,八面九锡更兼策马奔。南面独傲,铜雀二乔,欲休却报诏到。假节钺,赞拜不名,剑履入殿谁能?青梅煮酒,本初不数,惊起雷霆咋休。玺绶之册,即阼亦重,却累身三秋。观之沧海,星汉一粟,不道身成五彩。隔江叹:景升之子,豚犬之流。

        扯远了,赶紧返回。今天这章是这篇系列文章的最后一章。

        前面我们的驱动程序已经可以监视进程的创建,下面我们希望做的事情有两点:一、判断进程是不是notepad.exe。二、如果是,我们向其进程注入一段代码并先于它原有的代码执行。所以,这里有三个重要的问题:一、从进程句柄获取进程名;二、向进程分配内存写入自定位的代码;三、修改进程原来的入口点,改为我们代码的入口点,在我们的代码执行完毕后,还得跳转到原来的入口点去。这整个过程本质上与文件感染没多少不同。

        如何从进程句柄获取到进程名呢?答案是EPROCESS,ObReferenceObjectByHandle可以让我们通过进程句柄获取它的EPROCESS。微软在WDK中对EPROCESS的说明非常简短:The EPROCESS structure is an opaque structure that serves as the process object for a process.EPROCESS结构中保存有进程名,但不幸的是EPROCESS结构的定义随操作系统的不同而不同,这也许也是为什么微软对它的描述非常少,他也不推荐你使用EPROCESS结构。

        在KmdKit中有个SharedEvent – ProcessMon例子,其中演示了通过EPROCESS获取进程名,它的做法是针对不同的系统定义不同的EPROCESS结构,在使用时先获取当前系统。

        在《Windows内核安全编程》中提到了另外一种方法:当我们的内核模块DriverEntry被调用时,我们的内核正处于System进程中,我们可以使用PsGetCurrentProcess获取到此时的EPROCESS,在其中暴力搜索“System”,如果搜索出来,我们就可以确定本系统中进程名相对于EPROCESS首地址的偏移,以后就可以用这个偏移加别的进程的EPROCESS首地址来获取别的进程的名了。

        后者显然要简洁得多,所以本文也采用了后者的办法。在DriverEntry中增加:

        invoke  GetNameOffset

        mov     g_uNameOffset,eax

        invoke  DbgPrint,$CTA0("Driver entry, name offset:%08X"),g_uNameOffset

GetNameOffset   proc    uses    ebx

    local   pProcess

    local   len

    local   dwOffset

   

    and     dwOffset,0

    invoke  PsGetCurrentProcess

    .if eax

        mov     pProcess,eax

        invoke  strlen,$CTA0("System")

        mov     len,eax

        xor     ebx,ebx

        .while  ebx<1024*3*4

            mov     eax,pProcess

            add     eax,ebx

            invoke  _strnicmp,$CTA0("System"),eax,len

            .if !eax

                mov     dwOffset,ebx

                .break

            .endif

            inc     ebx

        .endw

    .endif

   

    mov     eax,dwOffset

    ret

GetNameOffset   endp

这个问题解决了,我们先把HookProc.asm中Hook_NtCreateThread代码贴出来:

include Append.asm

   

Hook_NtCreateThread proc    ThreadHandle:PHANDLE,DesiredAccess:DWORD,ObjectAttributes:POBJECT_ATTRIBUTES,ProcessHandle:HANDLE,ClientId:PCLIENT_ID,ThreadContext:PCONTEXT,InitialTeb:PVOID,CreateSuspended:DWORD

    local   pProcess:PVOID

    local   ulEntryPoint:ULONG

    local   dwAllocationSize:DWORD

    local   pBaseAddress:PVOID

    local   pProcessName

    local   dwMemorySize:DWORD

    local   pAppendStart:PVOID

    local   pOldEntry:PVOID

    local   pAppendEntry:PVOID

    pushad

    pushfd

    .if ThreadContext&&CreateSuspended&&ProcessHandle&&ProcessHandle!=-1

        invoke  ObReferenceObjectByHandle,ProcessHandle,PROCESS_ALL_ACCESS,NULL,UserMode,addr pProcess,NULL

        .if eax==STATUS_SUCCESS

            .if g_uNameOffset

                mov     eax,pProcess

                add     eax,g_uNameOffset

                mov     pProcessName,eax

                invoke  DbgPrint,$CTA0("New process:%s"),pProcessName

               

                invoke  _stricmp,pProcessName,$CTA0("notepad.exe")

                .if !eax

                    mov     dwMemorySize,APPEND_CODE_LENGTH

                    mov     pAppendStart,offset APPEND_CODE_START

                    mov     pOldEntry,offset _ulOldEntry

                    mov     pAppendEntry,offset _AppendCodeEntry

                    mov     edi,ThreadContext

                    assume  edi:ptr CONTEXT

                    M2M     ulEntryPoint,[edi].regEax       ;保存入口点

                    .if ulEntryPoint

                        invoke  KeDetachProcess

                        invoke  KeAttachProcess,pProcess

                        ;分配内存

                        M2M     dwAllocationSize,dwMemorySize

                        mov     pBaseAddress,NULL

                        invoke  ZwAllocateVirtualMemory,NtCurrentProcess,addr pBaseAddress,0,addr dwAllocationSize,MEM_COMMIT,PAGE_EXECUTE_READWRITE

                        .if eax==STATUS_SUCCESS

                            ;写入代码

                            invoke  memcpy,pBaseAddress,pAppendStart,dwMemorySize

                            ;填写Jmp地址

                            mov     eax,pOldEntry

                            sub     eax,pAppendStart

                            add     eax,pBaseAddress

                            M2M     dword ptr [eax],ulEntryPoint

                            ;修正入口点

                            mov     eax,pAppendEntry

                            sub     eax,pAppendStart

                            add     eax,pBaseAddress

                            mov     ulEntryPoint,eax

                        .endif

                        invoke  KeDetachProcess

                        M2M     [edi].regEax,ulEntryPoint

                    .endif

                .endif

            .endif

            assume  edi:nothing

            invoke  ObDereferenceObject,pProcess

        .endif

    .endif

    popfd

    popad

   

    invoke    g_lpOldNtCreateThread,ThreadHandle,DesiredAccess,ObjectAttributes,ProcessHandle,ClientId,ThreadContext,InitialTeb,CreateSuspended

   

    ret

Hook_NtCreateThread endp

        Append.asm中是我们要感染的代码,等会来看。进程创建时主线程也会被创建。但在我们的Hook中,线程还没有开始运行。其中ThreadContext(这个结构跟用户态下用GetThreadContext获取到的差不多)的regEax就是主线程的起始地址,也就是我们常说的入口点。代码的思路非常简单,在目标进程中分配一块APPEND_CODE_LENGTH大小的内存(这个大小刚好够装下我们需要注入的代码);把我们程序中APPEND_CODE_START地址拷贝那么多数据过去(这其实就是把我们的感染代码拷贝过去;把进程原来的入口点保存到拷贝过去的那份代码的某个位置(具体那个位置看Append.asm就明白了),吧ThreadContext.regEax设置为我们注入代码的起始地址。

        这下来看Append.asm

APPEND_CODE_START   equ this byte

include proto.inc

include Append.inc

_ulOldEntry             dd                      ?

_hKernel32              dd                      ?

_hUser32                dd                      ?

_szUser32               db                      'User32.dll',0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

_lpGetProcAddress       APIGetProcAddress       ?

_lpGetModuleHandleA     APIGetModuleHandle      ?

_lpLoadLibraryA         APILoadLibrary          ?

;-------------------------------------------------------------------------------

_szGetProcAddress       db                      'GetProcAddress',0

_szGetModuleHandleA     db                      'GetModuleHandleA',0

_szLoadLibraryA         db                      'LoadLibraryA',0,0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

_lpMessageBoxA          APIMessageBox           ?

;------------------------------------------------------------------------------

_szMessageBoxA          db                      'MessageBoxA',0,0

_lpTitle                db                      'DynamicHook',0

_lpMsg                  db                      'My append code!',0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

include GetKernel.asm

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

_AppendCodeEntry:

    call    @F

    @@:

    pop     ebx

    sub     ebx,offset @B

   

    .if dword ptr [esp]>7FFFFFFFh

        jmp     OLD_ENTRY

    .endif

    invoke  _GetKernelBase,[esp]

    .if !eax

        jmp     OLD_ENTRY

    .endif

    mov     [ebx+_hKernel32],eax

   

    invoke  _GetApi,[ebx+_hKernel32],addr [ebx+offset _szGetProcAddress]

    .if !eax

        jmp     OLD_ENTRY

    .endif

    mov     [ebx+_lpGetProcAddress],eax

   

    lea     esi,[ebx+offset _szGetModuleHandleA]

    lea     edi,[ebx+offset _lpGetModuleHandleA]

    .while  TRUE

        invoke  [ebx+_lpGetProcAddress],[ebx+_hKernel32],esi

        .if !eax

            jmp     OLD_ENTRY

        .endif

        mov     [edi],eax

        add     edi,4

        @@:

        lodsb

        or      al,al

        jnz     @B

        .break  .if ! byte ptr [esi]

    .endw

   

    invoke  [ebx+_lpGetModuleHandleA],addr [ebx+_szUser32]

    .if !eax

        invoke  [ebx+_lpLoadLibraryA],addr [ebx+_szUser32]

        .if !eax

            jmp     OLD_ENTRY

        .endif

    .endif

    mov     [ebx+_hUser32],eax

    lea     esi,[ebx+offset _szMessageBoxA]

    lea     edi,[ebx+offset _lpMessageBoxA]

    .while  TRUE

        invoke  [ebx+_lpGetProcAddress],[ebx+_hUser32],esi

        .if !eax

            jmp     OLD_ENTRY

        .endif

        mov     [edi],eax

        add     edi,4

        @@:

        lodsb

        or      al,al

        jnz     @B

        .break  .if ! byte ptr [esi]

    .endw

   

    invoke  [ebx+_lpMessageBoxA],NULL,addr [ebx+_lpMsg],addr [ebx+_lpTitle],0

OLD_ENTRY:

    jmp     [ebx+_ulOldEntry]

APPEND_CODE_END     equ this byte

APPEND_CODE_LENGTH  equ offset APPEND_CODE_END-offset APPEND_CODE_START

        这下大家明白APPEND_CODE_LENGTH、APPEND_CODE_START、_AppendCodeEntry、_ulOldEntry的含义了吧。在这里汇编语言的好处就显而易见了:可以精确获取指定代码段的长度(你若用高级语言的话,就得估摸着分配一块足够大的内存);可以使用相对于ShellCode来说稍微高级一点的语言写代码(ShellCode要想完成复杂一点的功能还相当麻烦呢)。

        这里要注意的是,首先对所有用户态API的调用我们都不能直接调用,一是内核的导入库中根本没有提供这些函数,二是我们访问全局变量不能直接来访问,因为随着进程不同,我们注入到目标进程的代码起始地址也是不同的(分配的pBaseAddress不同)。这需要我们手动加载需要的DLL,获取API函数地址,并且编写自定位代码。这些技术早几年前就已经科普,这里就不再科普了。

        GetKernel.asm中的_GetKernelBase用来获取kernel32.dll的基址。方法也有很多,这里直接用的《Windows环境下32位会变语言程序设计》中的代码。

        现在来试运行一下:

        哈哈!当我们启动notepad.exe时首先会弹出我们的对话框。如果你用OllyDBG调试一下notepad.exe,选“设置第一次暂停于:主模块入口点”,你会发现,当OllyDBG中断时,我们的代码已经执行过了。notepad.exe根本就没有机会知道我们的代码已经影响了它。

        但是!任何事情就怕但是!notepad.exe在后面还有机会来扫描内存…….如果我们在

        OLD_ENTRY:

        jmp     [ebx+_ulOldEntry]

        处用下面的办法呢?(伪代码)

        push    8000

        push    APPEND_CODE_LENGTH

        push    offset [ebx+APPEND_CODE_START]

        push    dword ptr [ebx+_ulOldEntry]

        jmp     [ebx+_lpVirtualFree]

        这种动态感染的办法后面还有后话,我们的代码虽然很早就执行了,但系统创建进程时,还得先运行很多代码,OllyDBG仍然可以中断在系统断点上。可是,我们也不应该把别人憋死。给别人留条活路就是给自己留条活路。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值