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
{
/**/ WRAP_ULONG64(PVOID CreateThread);
/**/ WRAP_ULONG64(PVOID RemoteThreadStart);
/**/ WRAP_ULONG64(PVOID RemoteThreadParam);
/**/ WRAP_ULONG64(PVOID WaitForSingleObject);
/**/ WRAP_ULONG64(HANDLE hCompletionEvent);
/**/ WRAP_ULONG64(PVOID CloseHandle);
/**/ union
{
WRAP_ULONG64(HANDLE hRemoteThread);
WRAP_ULONG64(HANDLE hSyncEvent);
};
/**/ WRAP_ULONG64(PVOID SetEvent);
}; ULONGLONG __Unused__[];
}; 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@

StealthStub_ASM_x86@ PROC

; Create thread...
push
push
push dword ptr [ebx + ] ; save stealth context
push dword ptr [ebx + ] ; RemoteThreadStart
push
push
call dword ptr [ebx + ] ; CreateThread(, NULL, RemoteThreadStart, RemoteThreadParam, , NULL); ; signal thread creation...
push dword ptr [ebx + ]
mov dword ptr [ebx + ], eax
call dword ptr [ebx + ] ; SetEvent(hSyncEvent); ; wait for completion
push -
push dword ptr [ebx + ]
call dword ptr [ebx + ] ; WaitForSingleObject(hCompletionEvent, INFINITE) ; close handle
push dword ptr [ebx + ]
call dword ptr [ebx + ] ; CloseHandle(hCompletionEvent); ; close handle
push dword ptr [ebx + ]
call dword ptr [ebx + ] ; CloseHandle(hSyncEvent); ; restore context
mov eax, [ebx + + * ]
mov ecx, [ebx + + * ]
mov edx, [ebx + + * ]
mov ebp, [ebx + + * ]
mov esp, [ebx + + * ]
mov esi, [ebx + + * ]
mov edi, [ebx + + * ]
push dword ptr[ebx + + * ] ; push EFlags
push dword ptr[ebx + + * ] ; save old EIP
mov ebx, [ebx + + * ] add esp,
popfd ; continue execution...
jmp dword ptr [esp - ] ; outro signature, to automatically determine code size
db 78h
db 56h
db 34h
db 12h
StealthStub_ASM_x86@ ENDP

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

Injection_ASM_x86

public Injection_ASM_x86@
Injection_ASM_x86@ 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 + ] ; call LoadLibraryW(Inject->EasyHookPath);
push dword ptr [esi + ] call dword ptr [esi + ] ; LoadLibraryW@
mov ebp, eax
test eax, eax
je HookInject_FAILURE_A ; call GetProcAddress(eax, Inject->EasyHookEntry);
push dword ptr [esi + ]
push ebp
call dword ptr [esi + ] ; GetProcAddress@
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 + ] ; FreeLibrary@
test eax, eax
je HookInject_FAILURE_C
jmp HookInject_EXIT HookInject_FAILURE_A:
call dword ptr [esi + ] ; GetLastError
or eax, 40000000h
jmp HookInject_FAILURE_E
HookInject_FAILURE_B:
call dword ptr [esi + ] ; GetLastError
or eax, 10000000h
jmp HookInject_FAILURE_E
HookInject_FAILURE_C:
call dword ptr [esi + ] ; GetLastError
or eax, 30000000h
jmp HookInject_FAILURE_E
HookInject_FAILURE_E:
push eax ; save error value HookInject_EXIT: push
push
push ; // shadow space for executable stack part... ; call VirtualProtect(Outro, , PAGE_EXECUTE_READWRITE, &OldProtect)
lea ebx, dword ptr [esp + ] ; we'll write to shadow space
push ebx
push 40h
push
push ebx
call dword ptr [esi + ] ; VirtualProtect@
test eax, eax jne HookInject_EXECUTABLE ; failed to make stack executable
call dword ptr [esi + ] ; GetLastError
or eax, 20000000h
add esp,
ret HookInject_EXECUTABLE:
; save outro to executable stack
mov dword ptr [esp], 0448BD3FFh ; call ebx [VirtualFree()]
mov dword ptr [esp + ], 05C8B0C24h ; mov eax, [esp + ]
mov dword ptr [esp + ], 0E3FF1024h ; mov ebx, [esp + ]
; jmp ebx [exit thread] ; save params for VirtualFree(Inject->RemoteEntryPoint, , MEM_RELEASE);
mov ebx, [esi + ] ; VirtualFree()
push 08000h
push
push dword ptr [esi + ] lea eax, dword ptr [esp + ]
jmp eax ; outro signature, to automatically determine code size
db 78h
db 56h
db 34h
db 12h Injection_ASM_x86@ ENDP

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

X64 注入的代码及注释

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

引用

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

easyhook源码分析二——注入的更多相关文章

  1. 十、Spring之BeanFactory源码分析(二)

    Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...

  2. Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题

    4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...

  3. 框架-springmvc源码分析(二)

    框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...

  4. Tomcat源码分析二:先看看Tomcat的整体架构

    Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ...

  5. Vue源码分析(二) : Vue实例挂载

    Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...

  6. 多线程之美8一 AbstractQueuedSynchronizer源码分析<二>

    目录 AQS的源码分析 该篇主要分析AQS的ConditionObject,是AQS的内部类,实现等待通知机制. 1.条件队列 条件队列与AQS中的同步队列有所不同,结构图如下: 两者区别: 1.链表 ...

  7. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

  8. 5.2 Spring5源码--Spring AOP源码分析二

    目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...

  9. 5.2 spring5源码--spring AOP源码分析二--切面的配置方式

    目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...

随机推荐

  1. 367-基于zynq XC7Z100 FMC接口通用计算平台

    基于zynq XC7Z100 FMC接口通用计算平台 一.板卡概述 本板卡基于Xilinx公司的FPGA XC7Z100 FFG 9000 芯片, 该平台为设计和验证应用程序提供了一个完整的开发平台. ...

  2. Django学习系列8:django测试客户端

    """向浏览器返回真正的HTML响应,添加一个新的测试方法""" from django.test import TestCase from ...

  3. openssl x.509证书

    openssl x.509证书 openssl x.509证书 ngnix 支持

  4. 十一、S3C2440 裸机 — GPIO

    11.1 GPIO 介绍 11.1.1 GPIO 管脚 GPIO 即是输入输出端口,S3C2440A 包含了 130 个多功能输入/输出口引脚并且它们为如下显示的八个端口: 端口 A(GPA):25 ...

  5. JAVA并发编程的艺术 Java并发容器和框架

    ConcurrentHashMap ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成. 一个ConcurrentHashMap里包含一个Segment数组, ...

  6. UEditor富文本编辑器简单使用

    UEditor富文本编辑器简单使用 一.下载地址:https://ueditor.baidu.com/website/ 官网中并没有 python 版本的 UEditor 富文本编辑器,本文简单介绍 ...

  7. ZROI 19.07.31 AB班ACM

    写在前面:非常感谢cjc和djh两位神仙带我,非常感谢他们给了我一次躺赢的机会. 虽然我被硬点成了代码手,但我写的基本每次都有一堆罚时.然而djh爷全部1A,tql. 题目按照一血时间升序,大致符合难 ...

  8. thinkphp之session操作

    原理机制 配置部分 代码部分 助手函数 借助第三方介质存入session 从负载均衡角度考虑----最好放在memocache,redis

  9. Jenkins打包Maven项目

    Jenkins是一个用于持续集成的服务,简单些说,就是提交代码后,点一下(也可以设置自动检测),系统会自动下载你的代码并编译,然后复制或上传到指定位置,并给所有相关人发送邮件. 一.环境搭建 1.下载 ...

  10. 【ZJOJ1321】灯

    题目 贝希和她的闺密们在她们的牛棚中玩游戏.但是天不从人愿,突然,牛棚的电源跳闸了,所有的灯都被关闭了.贝希是一个很胆小的女生,在伸手不见拇指的无尽的黑暗中,她感到惊恐,痛苦与绝望.她希望您能够帮帮她 ...