zoukankan      html  css  js  c++  java
  • easyhook源码分析二——注入

    EasyHook 中的注入方法。

    函数原型

    // EasyHook 中的命名比较有意思,Rh 代表的就是Remote Hook,此函数就是远程钩子的一个子过程----注入,前面的宏代表它是导出函数。
    EASYHOOK_NT_EXPORT RhInjectLibrary(
            ULONG InTargetPID,
                ULONG InWakeUpTID,//如果当前函数是通过RhCreateAndInject 调用过来的,这个指示主线程的ID。
                                //之后可以通过RhWakeUpProcess 唤醒进程。否则传0.
            ULONG InInjectionOptions,
            WCHAR* InLibraryPath_x86,
            WCHAR* InLibraryPath_x64,
            PVOID InPassThruBuffer,
            ULONG InPassThruSize)

    详细介绍

           函数前面的部分就是一些参数检查以及针对X86 和 X64 的不同而准备的变量的不同初始化。之后检查是否进行跨WOW64 注入,是的话报错并退出。另外,代码需要使用PROCESS_ALL_ACCESS 权限调用OpenProcess 。经过上面的检查之后后面才是真正的的注入过程。要了解注入的过程就必须需要了解REMOTE_INFO 结构体以及其与注入代码在目标进程地址空间的位置关系。

    REMOTE_INFO 结构
    #define WRAP_ULONG64(Decl)
    union
    {
        ULONG64 UNUSED;
        Decl;
    }
    typedef struct _REMOTE_INFO_
    {
        // will be the same for all processes
        WRAP_ULONG64(wchar_t* UserLibrary); // fixed 0
        WRAP_ULONG64(wchar_t* EasyHookPath); // fixed 8
        WRAP_ULONG64(wchar_t* PATH); // fixed 16
        WRAP_ULONG64(char* EasyHookEntry); // fixed 24
        WRAP_ULONG64(void* RemoteEntryPoint); // fixed 32
        WRAP_ULONG64(void* LoadLibraryW); // fixed; 40
        WRAP_ULONG64(void* FreeLibrary); // fixed; 48
        WRAP_ULONG64(void* GetProcAddress); // fixed; 56
        WRAP_ULONG64(void* VirtualFree); // fixed; 64
        WRAP_ULONG64(void* VirtualProtect); // fixed; 72
        WRAP_ULONG64(void* ExitThread); // fixed; 80
        WRAP_ULONG64(void* GetLastError); // fixed; 88
    
        BOOL            IsManaged;     // 指示是否为托管代码
        HANDLE          hRemoteSignal; // 用于指示是否注入成功
        DWORD           HostProcess;
        DWORD           Size;
        BYTE*           UserData;      // 自定义参数
        DWORD           UserDataSize;  // 自定义参数的大小
        ULONG           WakeUpThreadID;
    }REMOTE_INFO, *LPREMOTE_INFO;

      该结构体中前面的值为函数指针,按照8 字节对齐。另外,这些函数指针都是通过GetRemoveFuncAddress 函数得到的。在填充完远程函数地址之后,代码通过GetInjectionSize 函数根据汇编代码中尾部的特征码判断汇编代码的大小,之后一次性在目标进程中申请“代码大小 + REMOTE_INFO 结构大小 + 字符串 ‘HookCompleteInjection’- X86或者’_HookCompleteInjection’-X64 长度+1 + 当前工作目录长度 + 当前模块位置长度 + 目标DLL 路径长度”。

    目标进程的内存使用详情

    我们可以看到,这段注入代码的原型及实现符合线程函数的规范,而且其参数就是REMOGE_INFO 结构的指针。在目标进程中申请内存并写入数据(包含代码)之后,我们应该想办法执行我们的代码,或者称为远程线程函数,这里有两种方式:

    使用远程线程函数的方式注入
    参考我之前介绍远程线程注入的文章http://blog.csdn.net/qq_18218335/article/details/75246816

    使用线程劫持的方法注入
    X64
    http://blog.csdn.net/qq_18218335/article/details/75308957
    Win32–http://blog.csdn.net/qq_18218335/article/details/75268251
    这里我们介绍的方法的思想与之前是相同的,不过EasyHook 中使用的方法更加的稳定,考虑的更加周到。

    EasyHook 中线程劫持的实现

    函数原型

    EASYHOOK_NT_EXPORT RhCreateStealthRemoteThread(
                ULONG InTargetPID,
                LPTHREAD_START_ROUTINE InRemoteRoutine,
                PVOID InRemoteParam,
                HANDLE* OutRemoteThread)

    使用到的数据结构

    typedef struct _STEALTH_CONTEXT_
    {
        union
        {
            struct
            {
                /*00*/ WRAP_ULONG64(PVOID      CreateThread);           
                /*08*/ WRAP_ULONG64(PVOID      RemoteThreadStart);
                /*16*/ WRAP_ULONG64(PVOID      RemoteThreadParam);
                /*24*/ WRAP_ULONG64(PVOID      WaitForSingleObject);
                /*32*/ WRAP_ULONG64(HANDLE     hCompletionEvent);
                /*40*/ WRAP_ULONG64(PVOID      CloseHandle);
                /*48*/ union
                {
                    WRAP_ULONG64(HANDLE     hRemoteThread);
                    WRAP_ULONG64(HANDLE     hSyncEvent);
                };
                /*56*/ WRAP_ULONG64(PVOID      SetEvent);
            };
    
            ULONGLONG       __Unused__[8];
        };
    
        ULONGLONG           Rax;
        ULONGLONG           Rcx;
        ULONGLONG           Rdx;
        ULONGLONG           Rbp;
        ULONGLONG           Rsp;
        ULONGLONG           Rsi;
        ULONGLONG           Rdi;
        ULONGLONG           Rbx;
        ULONGLONG           Rip;
        ULONGLONG           RFlags;
        ULONGLONG           R8;
        ULONGLONG           R9;
        ULONGLONG           R10;
        ULONGLONG           R11;
        ULONGLONG           R12;
        ULONGLONG           R13;
        ULONGLONG           R14;
        ULONGLONG           R15;
    }STEALTH_CONTEXT, *PSTEALTH_CONTEXT;

    劫持线程通用的做法,找到目标进程的一个活动的子线程,然后视图暂停其执行,成功后通过GetThreadContext 得到其ThreadContext,然后在目标进程中申请并写入代码,设置RIP或者EIP,然后恢复线程执行。EasyHook 的不同在于以下几点:
    <1> 在恢复线程执行之前就保存了ThreadContext 中的寄存器的值,而不是在恢复了线程执行之后在通过代码将寄存器的值保存在栈中,这样做的好处就是靠谱、稳定。在介绍自己实现的线程劫持的实现的时候,我发现,在线程暂停后得到的ThreadContext 的值和之后恢复线程执行后执行第一个指令之前,部分寄存器的值会发生改变,虽然后来的运行结果正确,目标进程也没有发生崩溃的情况,但是我们应该按照EasyHook 的方式来进行ThreadContext 的保存与恢复。
    <2> 劫持后执行的代码为创建新线程,在新线程中完成我们的任务,EasyHook 中的注入代码称为StealthRemoteThread 原因就在这里,我们通过目标进程已存在的线程创建新线程以执行注入的过程,以达到隐藏注入行为的目的。
    <3> 在主线程中等待注入的完成,并判断注入是否成功,这是我当时没有注意到的一点。

    ebx/rbx 是 STEALTH_CONTEXT 结构体指针
    StealthStub_ASM_x86
    public StealthStub_ASM_x86@0
    
    StealthStub_ASM_x86@0 PROC
    
    ; Create thread...
        push        0
        push        0
        push        dword ptr [ebx + 16]        ; save stealth context
        push        dword ptr [ebx + 8]         ; RemoteThreadStart
        push        0
        push        0
        call        dword ptr [ebx + 0]         ; CreateThread(0, NULL, RemoteThreadStart, RemoteThreadParam, 0, NULL);
    
    
    ; signal thread creation...
        push        dword ptr [ebx + 48]        
        mov         dword ptr [ebx + 48], eax
        call        dword ptr [ebx + 56]        ; SetEvent(hSyncEvent);
    
    ; wait for completion
        push        -1
        push        dword ptr [ebx + 32]
        call        dword ptr [ebx + 24]        ; WaitForSingleObject(hCompletionEvent, INFINITE)
    
    ; close handle
        push        dword ptr [ebx + 32]        
        call        dword ptr [ebx + 40]        ; CloseHandle(hCompletionEvent);
    
    ; close handle
        push        dword ptr [ebx + 48]        
        call        dword ptr [ebx + 40]        ; CloseHandle(hSyncEvent);
    
    
    ; restore context
        mov         eax, [ebx + 64 + 8 * 0]
        mov         ecx, [ebx + 64 + 8 * 1]
        mov         edx, [ebx + 64 + 8 * 2]
        mov         ebp, [ebx + 64 + 8 * 3]
        mov         esp, [ebx + 64 + 8 * 4]
        mov         esi, [ebx + 64 + 8 * 5]
        mov         edi, [ebx + 64 + 8 * 6]
        push        dword ptr[ebx + 64 + 8 * 9] ; push EFlags   
        push        dword ptr[ebx + 64 + 8 * 8] ; save old EIP
        mov         ebx, [ebx + 64 + 8 * 7]
    
        add         esp, 4
        popfd
    
    ; continue execution...
        jmp         dword ptr [esp - 8] 
    
    ; outro signature, to automatically determine code size
        db 78h
        db 56h
        db 34h
        db 12h
    StealthStub_ASM_x86@0 ENDP

      注释打的已经很清楚了,首先创建注入的线程,即之前介绍的远程注入线程,参数还是原来的参数。然后将线程句柄保存在结构体中,触发事件,之后等待主线程得到新创建线程的句柄之后关闭两个事件。返回原EIP 之前,恢复各类寄存器,之后直接跳转到原EIP 处继续运行。

    Injection_ASM_x86

    public Injection_ASM_x86@0
    Injection_ASM_x86@0 PROC
    ; no registers to save, because this is the thread main function
    ; save first param (address of hook injection information)
    
        mov esi, dword ptr [esp + 4]
    
    ; call LoadLibraryW(Inject->EasyHookPath);
        push dword ptr [esi + 8]
    
        call dword ptr [esi + 40] ; LoadLibraryW@4
        mov ebp, eax
        test eax, eax
        je HookInject_FAILURE_A
    
    ; call GetProcAddress(eax, Inject->EasyHookEntry);
        push dword ptr [esi + 24]
        push ebp
        call dword ptr [esi + 56] ; GetProcAddress@8
        test eax, eax
        je HookInject_FAILURE_B
    
    ; call EasyHookEntry(Inject);
        push esi
        call eax
        push eax ; save error code
    
    ; call FreeLibrary(ebp)
        push ebp
        call dword ptr [esi + 48] ; FreeLibrary@4
        test eax, eax
        je HookInject_FAILURE_C
        jmp HookInject_EXIT
    
    HookInject_FAILURE_A:
        call dword ptr [esi + 88] ; GetLastError
        or eax, 40000000h
        jmp HookInject_FAILURE_E
    HookInject_FAILURE_B:
        call dword ptr [esi + 88] ; GetLastError
        or eax, 10000000h
        jmp HookInject_FAILURE_E    
    HookInject_FAILURE_C:
        call dword ptr [esi + 88] ; GetLastError
        or eax, 30000000h
        jmp HookInject_FAILURE_E    
    HookInject_FAILURE_E:
        push eax ; save error value
    
    HookInject_EXIT:
    
        push 0
        push 0
        push 0; // shadow space for executable stack part...
    
    ; call VirtualProtect(Outro, 4, PAGE_EXECUTE_READWRITE, &OldProtect)
        lea ebx, dword ptr [esp + 8] ; we'll write to shadow space
        push ebx
        push 40h
        push 12
        push ebx
        call dword ptr [esi + 72] ; VirtualProtect@16
        test eax, eax
    
        jne HookInject_EXECUTABLE
    
        ; failed to make stack executable
            call dword ptr [esi + 88] ; GetLastError
            or eax, 20000000h
            add esp, 16
            ret
    
    HookInject_EXECUTABLE:
    ; save outro to executable stack
        mov dword ptr [esp],     0448BD3FFh     ; call ebx [VirtualFree()]
        mov dword ptr [esp + 4], 05C8B0C24h     ; mov eax, [esp + 12]
        mov dword ptr [esp + 8], 0E3FF1024h     ; mov ebx, [esp + 16]
                                                ; jmp ebx [exit thread]
    
    ; save params for VirtualFree(Inject->RemoteEntryPoint, 0, MEM_RELEASE);
        mov ebx, [esi + 64] ; VirtualFree()
        push 08000h
        push 0
        push dword ptr [esi + 16]
    
        lea eax, dword ptr [esp + 12]
        jmp eax
    
    ; outro signature, to automatically determine code size
        db 78h
        db 56h
        db 34h
        db 12h
    
    Injection_ASM_x86@0 ENDP

    远程线程首先调用LoadLibraryW 函数加载目标DLL,然后调用GetProcAddress函数得到规定必须实现的DLL 的导出函数,得到后调用该函数,并传入用户指定的参数及参数长度。调用完成后,函数调用FreeLibrary 函数释放目标DLL。之后的动作比较厉害了,开辟三个字节的栈区,修改该栈区的保护属性为可读写执行,然后拷贝指令,并执行,该指令功能为:释放注入所需要的内存,然后退出线程。这个代码写的就非常完善了,运行完了就将所申请的内存自己释放了。而且释放内存的代码在栈区,又不用担心在释放内存后执行执行会造成非法访问。棒!!!

    X64 注入的代码及注释

    public StealthStub_ASM_x64
        int 3
    StealthStub_ASM_x64 PROC
        sub         rsp, 8 * 4
    
        mov         qword ptr[rsp + 40], 0
        mov         qword ptr[rsp + 32], 0
        mov         r9, qword ptr [rbx + 16]    ; RemoteThreadParam
        mov         r8, qword ptr [rbx + 8]     ; RemoteThreadStart
        mov         rdx, 0
        mov         rcx, 0
        call        qword ptr[rbx]              ; CreateThread
        cmp         rax, 0
    
    ; signal completion
        mov         rcx, qword ptr [rbx + 48]       
        mov         qword ptr [rbx + 48], rax
        call        qword ptr [rbx + 56]        ; SetEvent(hSyncEvent);
    
    ; wait for completion
        mov         rdx, -1
        mov         rcx, qword ptr [ebx + 32]
        call        qword ptr [ebx + 24]        ; WaitForSingleObject(hCompletionEvent, INFINITE)   
    
    ; close handle
        mov         rcx, qword ptr [rbx + 32]       
        call        qword ptr [rbx + 40]        ; CloseHandle(hCompletionEvent);
    
    ; close handle
        mov         rcx, qword ptr [rbx + 48]       
        call        qword ptr [rbx + 40]        ; CloseHandle(hSyncEvent);
    
    ; restore context
        mov         rax, [rbx + 64 + 8 * 0]
        mov         rcx, [rbx + 64 + 8 * 1]
        mov         rdx, [rbx + 64 + 8 * 2]
        mov         rbp, [rbx + 64 + 8 * 3]
        mov         rsp, [rbx + 64 + 8 * 4]
        mov         rsi, [rbx + 64 + 8 * 5]
        mov         rdi, [rbx + 64 + 8 * 6]
        mov         r8, [rbx + 64 + 8 * 10]
        mov         r9, [rbx + 64 + 8 * 11]
        mov         r10, [rbx + 64 + 8 * 12]
        mov         r11, [rbx + 64 + 8 * 13]
        mov         r12, [rbx + 64 + 8 * 14]
        mov         r13, [rbx + 64 + 8 * 15]
        mov         r14, [rbx + 64 + 8 * 16]
        mov         r15, [rbx + 64 + 8 * 17]
        push        qword ptr[rbx + 64 + 8 * 9] ; push EFlags   
        push        qword ptr[rbx + 64 + 8 * 8] ; save old EIP
        mov         rbx, [rbx + 64 + 8 * 7]
    
        add         rsp, 8
        popfq
    
    ; continue execution...
        jmp         qword ptr [rsp - 16]    
    
    ; outro signature, to automatically determine code size
        db 78h
        db 56h
        db 34h
        db 12h
    StealthStub_ASM_x64 ENDP
    
    
    public Injection_ASM_x64
    
    Injection_ASM_x64 PROC
    ; no registers to save, because this is the thread main function
        mov         r14, rcx ; r14 当前存储的为LPREMOTE_INFO
        sub         rsp, 40  ; space for register parameter stack, should be 32 bytes... no idea why it only works with 40
    
    ; call LoadLibraryW(Inject->EasyHookPath);
        mov         rcx, qword ptr [r14 + 8]
        call        qword ptr [r14 + 40] ; LoadLibraryW
        mov         r13, rax
        test        rax, rax
        je          HookInject_FAILURE_A
    
    ; call GetProcAddress(hModule, Inject->EntryPoint)
        mov         rdx, qword ptr [r14 + 24] 
        mov         rcx, rax 
        call        qword ptr [r14 + 56] ; GetProcAddress 
        test        rax, rax
        je          HookInject_FAILURE_B
    
    ; call EasyHookEntry(Inject);
        mov         rcx, r14
        call        rax
        mov         r15, rax ; save error code to non-volatile register
    
    ; call FreeLibrary(hEasyHookLib)
        mov         rcx, r13
        call        qword ptr [r14 + 48] ; FreeLibrary
        test        rax, rax
        je          HookInject_FAILURE_C
    
        jmp         HookInject_EXIT
    
    HookInject_FAILURE_A:
        call        qword ptr [r14 + 88] ; GetLastError
        or          rax, 40000000h
        jmp         HookInject_FAILURE_E
    HookInject_FAILURE_B:
        call        qword ptr [r14 + 88] ; GetLastError
        or          rax, 10000000h
        jmp         HookInject_FAILURE_E    
    HookInject_FAILURE_C:
        call        qword ptr [r14 + 88] ; GetLastError
        or          rax, 30000000h
        jmp         HookInject_FAILURE_E    
    HookInject_FAILURE_E:
        mov         r15, rax ; save error value
    
    HookInject_EXIT:
    
    ; call VirtualProtect(Outro, 8, PAGE_EXECUTE_READWRITE, &OldProtect)
    ; 这里的Outro 也就是 &OldProtect,即此线程函数开始时申请的一个局部变量
        lea         rbx, qword ptr [rsp + 8] ; writes into register parameter stack
        mov         r9, rbx
        mov         r8, 40h
        mov         rdx, 8
        mov         rcx, rbx
        call        qword ptr [r14 + 72] ; VirtualProtect
        test        rax, rax
    
        jne HookInject_EXECUTABLE
    
        ; failed to make stack executable
            call        qword ptr [r14 + 88] ; GetLastError
            or          rax, 01000000h
            mov         rcx, rax
            call        qword ptr [r14 + 80] ; ExitThread
    
    HookInject_EXECUTABLE:
    ; save outro to executable stack
        mov         rbx, [r14 + 64] ; VirtualFree()
        mov         rbp, [r14 + 80] ; ExitThread()
    
        mov         rax, 000D5FFCF8B49D3FFh
            ; 类似于shellcode,后面会跳转到&OldProtect 处执行这三个指令
            ; r15 为 EsayHookEntry 的返回值代码
            ; call rbx
            ; mov rcx, r15
            ; call rbp
    
        mov qword ptr [rsp + 8], rax
    
    ; save params for VirtualFree(Inject->RemoteEntryPoint, 0, MEM_RELEASE);
    ; 这里直接把RemoteEntryPoint 参数删除了.....
        mov r8, 8000h
        mov rdx, 0h
        mov rcx, qword ptr [r14 + 32]
    
        lea rax, qword ptr [rsp + 8]
        sub rsp, 48
        jmp rax
    
    ; outro signature, to automatically determine code size
        db 78h
        db 56h
        db 34h
        db 12h
    
    Injection_ASM_x64 ENDP

    引用

    EasyHook 中部分函数的实现分析----注入

  • 相关阅读:
    [LeetCode]2. Add Two Numbers链表相加
    Integration between Dynamics 365 and Dynamics 365 Finance and Operation
    向视图列添加自定义图标和提示信息 -- PowerApps / Dynamics365
    Update the Power Apps portals solution
    Migrate portal configuration
    Use variable to setup related components visible
    Loyalty management on Retail of Dynamic 365
    Modern Fluent UI controls in Power Apps
    Change screen size and orientation of a canvas app in Power App
    Communication Plan for Power Platform
  • 原文地址:https://www.cnblogs.com/code1992/p/11580382.html
Copyright © 2011-2022 走看看