【二进制分析】注入技术与 EasyHook
在研究使用 S2E 分析 wannacry 时,用到了 DLL 注入实现 hook。这里系统复习一下关于 Hook 的知识。
Hook 概述
hook 技术指的是在程序实现正常功能的情况下,实现嵌入代码的功能(一般用于获取程序的目标参数,信息等)。
Hook 的实现方式有很多种。除了 SetWindowsHookEx 这种用于截获系统中消息的 Hook,主要有以下几种 API hook 方式:
调试 Hook
调试 Hook :与调试器的原理非常相似,都是令进程运行时发生异常,通过捕获到异常进而获得对被调试程序的控制权;
内联 Hook(Inline Hook)
直接修改 API 代码。修改 API 函数的前 5 个字节,将其修改为跳转指令,进而跳转到自定义的代码上操作,劫持执行流程。
使用该技术,必须要在控制程序流之后将原有 API 函数的前几个字节修复。
另一种方式是修改 API 函数的前 7 个字节。这种 API 通常有两个特征:
函数以
mov edi, edi
为首指令(该指令是无意义的);函数最上方有至少 5 字节的空余。
这样就可以修改 mov edi, edi
为一个短跳转,在前面 5 字节存放一个长跳转,这样就不会破坏原有的 API 函数代码了。
导入表 Hook(IAT Hook)
和 inline hook 不同,导入表 Hook 只需要去修改 IAT 即可。
注入和 Hook 的关系
https://www.zhihu.com/question/28710950
在安卓上,静态的篡改方式是反编译apk,修改或添加代码后重打包,用户只要安装了这个修改过的apk,运行时攻击者的代码就会被加载到进程空间。
动态的篡改方法就是hook。如果我要篡改代码,那么我要实现的就是在程序将要执行某段逻辑的时候控制它去执行我的代码,这个行为就叫hook。一个运行时的程序表现形式是进程,代码跟数据都放在自己的进程里面。那么问题来了,操作系统隔离了进程,我的代码在我的进程里,别人的代码在别人的进程里,别人的进程是不能跳到我的进程来执行我的代码的,这怎么办呢,所以要先想办法把代码注入到别人的进程里。之前提到的重打包也算是一种静态的注入方法,动态的注入方法在安卓上与Linux的共享库注入是类似的,这种方法网上用的最多的应该是看雪的古河发布的libinject。另外还有Xposed,它采取了一种特殊的注入方法,是动静结合的。
注入技术
参考链接:
DLL 注入指的是将 DLL 注入到某个其他进程中,令其执行 DLL 中的代码。由于 DLL 已经属于被注入进程的一部分,因此他们共享内存空间和其他进程资源。如果在 DLL 中放置监控代码,则可以监控被注入程序的执行。
根据前面提到的 Hook 技术,我们可以将 hook 相关的代码放到 DLL 中,然后注入到其他进程中。
需要注意的问题是,其他进程并不会主动的加载我们编写的 DLL,因此我们需要别的手段使其加载,这个过程就是“注入”。
EasyHook 使用的是 Inline Hook。参考官方给出的注入代码,可以使用 RhInjectLibrary()
函数进行远线程注入。也可以使用 RhCreateAndInject()
先创建一个挂起的进程, 将 DLL 注入至进程后再使其继续运行。这里我主要探讨后者。
这种技术叫做劫持进程创建注入(和 PROCESS HOLLOWING 有点类似)。来看 CreateProcess 的函数原型:https://blog.csdn.net/flowwaterdog/article/details/116564999
1 | BOOL CreateProcess ( |
dwCreationFlags 参数可以赋值为 CREATE_SUSPENDED
,此时该进程的主线程将以挂起的方式启动。想要恢复主线程的运行,需要调用 ResumeThread() 函数。因此,劫持进程加载就是在主线程被挂起到恢复的这段时间内实现注入代码的。
例:EasyHook — RhCreateAndInject
这里用 EasyHook 作为例子,探讨一下如何实现这种先挂起,再注入,最后恢复执行的注入方式。如何使用 EasyHook 可以参考官方文档。总的来说,RhCreateAndInject 是远线程注入。
RhCreateAndInject 函数定义如下:(EasyHookDll/RemoteHook/thread.c)
1 | EASYHOOK_NT_EXPORT RhCreateAndInject( |
该函数主要调用 RtlCreateSuspendedProcess 创建被挂起的进程,然后调用 RhInjectLibrary 注入 DLL:
1 | // all other parameters are validate by called APIs... |
RhInjectLibrary 则是采用了远线程注入的方式实现。其部分源码如下:(这里只选取 32 位的相关代码)
1 | EASYHOOK_NT_EXPORT RhInjectLibrary(...) |
总结一下,流程主体过程分为以下几个步骤:
打开被注入的进程,获得其句柄(OpenProcess );
准备好要向目标进程写入的内容:
EasyHook 路径,目标 DLL 路径,目标进程的一些函数地址等;
Injection_ASM_x86 的代码,创建远线程时就会执行这段代码。猜想它会调用 LoadLibrary 导入目标 DLL。
在目标程序申请空间 (VirtualAllocEx,附带 rwx 属性);
创建远线程,执行 Injection_ASM_x86 函数 (NtCreateThreadEx 或 CreateRemoteThread);
等待远线程运行完毕 (WaitForMultipleObjects)