博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
easyhook源码分析二——注入
阅读量:4493 次
发布时间:2019-06-08

本文共 13815 字,大约阅读时间需要 46 分钟。

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@0StealthStub_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 12hStealthStub_ASM_x86@0 ENDP

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

 

Injection_ASM_x86

public Injection_ASM_x86@0Injection_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_EXITHookInject_FAILURE_A:    call dword ptr [esi + 88] ; GetLastError    or eax, 40000000h    jmp HookInject_FAILURE_EHookInject_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 valueHookInject_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        retHookInject_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 12hInjection_ASM_x86@0 ENDP

 

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

X64 注入的代码及注释

public StealthStub_ASM_x64    int 3StealthStub_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 12hStealthStub_ASM_x64 ENDPpublic Injection_ASM_x64Injection_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_EXITHookInject_FAILURE_A:    call        qword ptr [r14 + 88] ; GetLastError    or          rax, 40000000h    jmp         HookInject_FAILURE_EHookInject_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 valueHookInject_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] ; ExitThreadHookInject_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 12hInjection_ASM_x64 ENDP

 

 

 

 

 

引用

转载于:https://www.cnblogs.com/code1992/p/11580382.html

你可能感兴趣的文章
PCL Examples
查看>>
spring boot
查看>>
浏览器URL传参最大长度问题
查看>>
学习进度条
查看>>
Linux crontab 定时任务详解
查看>>
string成员函数
查看>>
onSaveInstanceState()方法问题
查看>>
[转]CocoaChina上一位工程师整理的开发经验(非常nice)
查看>>
大数据时代侦查机制有哪些改变
查看>>
雷林鹏分享:jQuery EasyUI 菜单与按钮 - 创建链接按钮
查看>>
Apache Traffic Server服务搭建
查看>>
poj1990两个树状数组
查看>>
学习python-day1
查看>>
Zend_Db_Table->insert ()和zend_db_adapter::insert方法返回值不同
查看>>
递归问题
查看>>
Hyperledger下子项目
查看>>
Linq-查询上一条下一条
查看>>
常见前端开发的题目,可能对你有用
查看>>
BeautifulSoap库入门
查看>>
乐观锁与悲观锁
查看>>