在 使用远程线程进行DLL的注入 中我们知道怎么注入,但是注入后我们还可以做更多的事情,比如 HOOK 掉一个函数。微软官方把这个也叫绕行,它就出了一个官方的库 Detours,当然也有一些替代品如:EasyHook
官方的称呼叫做 Detour(绕行),它描述的意思是:汇编指令跳转到了一个不同的位置,最重要的是重定向了执行的流。通常,我们通过这样来将代码绕行到我们自己代码区域内。这样就可以强制让程序来执行我们的代码。
这样做的关键是不要搞坏了 栈,我们执行我们自己写的代码,然后跳回到合适的地方。
Mid Function Hooking
Detour 有些时候也会被叫做函数中的 hook,不过如果你只是绕行了一个函数的第一个字节,这就不会叫做函数 hook。将绕行放到一个函数的第一个字节是非常容易被反检测系统检查到,而函数中间 hook 则更难检测到一些。反检测系统很容易检查第一个字节,但并不总是检查函数中的每个字节。如果反检测系统会读取函数的第一个字节,那么我们需要用汇编来编写我们的绕行,这就是一个函数中间 hook。
External Detouring/Hooking
通常,我们会通过 WriteProcessMemory
来将我们的代码写到目标进程中,然后使用这个函数来使已存的代码跳转到我们的代码。External Detouring/Hooking 会更复杂一些,因为我们必须将 shellcode 写到目标进程。在程序内部是很容易的。但在外部,我们必须将汇编代码转换成字节作为shellcode 写到目标进程。
Stolen Bytes
就我们自己的绕行代码覆盖写入的内存区域也叫做 窃取字节,我们知道重写了多少字节很重要,然后这些字节必须在我们绕行后执行,否则就会搞坏栈和寄存器。这些字节并不总是相同,比如你覆盖写入一个8字节的指令,我们的绕行只有 5 字节。这种情况下,我们需要从窃取字节中复制8字节出来。我们不能执行一个指令的 75%。
我们必须复制出窃取字节,然后执行我们自己的代码,最后再执行这些窃取字节来保证不会让程序崩溃。
如果窃取的字节包含了一个 JMP,跳转到相对地址,或者一些其他的相对寻址,它将会失败因为他们不会在一个不同的位置执行。相对地址指下来vyua EIP,因为我们将这些字节复制到了一个不同的内存地址,EIP 就不同了。
Code Caves
这指的是目标进程中未并程序使用的内存区域。通常我们认为它是已分配,但没有包含任何东西,程序却需要。当使用绕行的时候,我们可以将代码写在这个区域而不用分配内存。不用分配内存会更简单些。如果我们使用 Code Caves,内存页必须有执行权限。我们可以通过 VirtualProtect[Ex]
来修改它。
在早些时候,code caves 指的是进程分配却没有使用的内存。现在人们都会分配自己的内存,但也这样叫。大多数情况下,这无关紧要,分配我们的内存更容易。如果我们拥有一个写权限的进程,那可能我们就不会遇到反检测的问题。
HOOK
通常和 Detour 换用。当个人认为,hooking 指的是将代码重定向到我们自己的代码;而 detouring 并不一定绕行到我们的代码,只是绕行程序内存的其他位置。
基本的 X86 绕行代码
bool Detour32(void * src, void * dst, int len) |
- src 想要放 JMP 指令的内存地址
- dst 想要跳转到的内存地址(可能是我们自己写的代码)
- len 重写的指令占用的字节数
len 通常是 5 字节,但这依赖于我们要重写的东西,可能会更多。
我们检查最少需要 5 字节是因为 在 X86 中这是 JMP 指令的最小长度。我们将通过重写来销毁的指令最少也是5字节。
VirtualProtect
用来获取要重写区域内存的权限
将我们重写的内存区域初始化为 0x90
(这是 NOP)指令,这不是 100% 必要,不过是一个很好的方法。当我们的调试的时候,可以看到 0x90 是否被写入这样就知道在做正确的事情了。
然后我们计算在目标地址和 src 地址间的相对地址(相减,再减去 len)。这个计算的结果是 从我们重写的最后一个字节到我们要跳转到的地址 的偏移量。注意了:我们使用的是相对跳转,而不是绝对跳转,所以必须在运行时计算。
0xE9
是 JMP 指令,然后写入偏移。
最后,再重置权限。
我们自己的代码呢?
我们必须在 declspec naked
函数中执行我们的代码,必须保存寄存器和栈这样我们不会搞坏栈和寄存器,必须执行窃取字节。然后必须跳转到 src+len
地址。