1 hook
Hook翻译过来为‘挂钩’、‘钩子’,是一种非常形象的叫法,它的原理并不难懂:在合适的地点挂上一个钩子,等待目标进入。
将其比喻为现实中的例子,可以想象有一条河流,河流自上而下流动可以看作程序的执行过程,而Hook就是在河流的某个地方布上一张大网。
河流想要流下去,那就必须要通过这张网,而我们在其通过的这一瞬间完成一系列巧妙的事情,比如修改其原本的运行流程、变量的值等等。
这只是一种形象的比喻,实际上Hook远比河流中的一张网更加强大。
事实上一个简单的C++程序就能演示这一过程:
#include<iostream>
using namespace std;
void fun(int t) {
cout << t << endl;
}
int main()
{
int a = 10;
fun(a);
}
上面的程序就演示了一个最基本的运行逻辑,代码从main
函数开始,向fun
函数传入变量a
,然后进入函数中打印这个a
的值。
此时如果有一张网,也就是Hook代码,插在了函数调用前,去修改了a的值:
int a=10;
{ //假设有一段hook代码插入在了这里
a=20;
}
fun(a);
那么此时fun函数打印出来的值自然也会跟着变化。
虽然看起来似乎很简单,但这确实就是Hook的底层原理,唯一的难点只是我们应该如何去插入这段我们自己写的代码而已。
一个更具实际意义的简单例子就是C++中的虚函数表,这在前面的逆向分析技术中也已经提到过。
由于它是动态查表来寻找到要调用的函数地址,所以如果此时你写一个与某个虚函数签名一致的函数,并将其地址复制到那张虚函数表的相应位置覆盖掉原本的函数地址,那么程序自然就会来执行你写的这个函数。
如果放在汇编语言中,其实就更简单了,因为汇编语言执行流程其实就是从上向下:
1
2
3
想要hook,直接在其必经之路上,执行一个jmp
之类的跳转指令,让其跳转到自己的代码中去执行,执行完后,再让其跳转回来:
1
2
jmp 0x100000 #假设跳转到0x100000处
3
.... 0x100000
#执行一系列自定义的代码
jmp 3 #然后跳转回去继续执行
此时,只需要把我们想要让其执行的代码复制到0x100000
处,就自然会被执行,执行完后再让其跳转回去,也不影响程序的正常工作。
使用到的函数大致就是win api读写、分配内存的函数,过程大致如下:
- 在本进程中通过
OpenProcress
函数打开想要Hook的程序 - 使用
VirutalAllocEx
在目标进程中分配一块空间。 - 用
WriteProcessMemory
函数在分配的空间中写上我们想要让其执行的代码,以及在哪里进行跳转等等 - 一般还需要用
ReadProcessMemory
函数读取、保留其原本位置的数据,用于后面的恢复 - 可能还需要用
VirutalProtectEx
函数更改目标内存的权限,使其允许我们读写目标进程的内存。
但这是一个比较繁琐的过程,操作的数据也都是二进制,代码中也就是十六进制的数据。