内容纲要

应用层

Inline HOOK

原理

直接修改程序编译后的指令。

#include <iostream>
#include<Windows.h>
#include<iomanip>
using namespace std;
void function() {
    cout << "function is running." << endl;
}
void hook_function() {
    cout << "hook is running." << endl;
}

int main(){
    function();
    cout <<"address of function:"<< function << endl;
    cout << "address of hook_function:" << hook_function << endl;
    unsigned char code[5] = { 0xe9,0x00,0x00,0x00,0x00 };//0xe9=jmp,一个跳转指令
    unsigned int offset = (unsigned int)hook_function - (unsigned int)function-5;
    *(unsigned int*)&code[1] = offset;
    cout << "value of code:";
    for (int i = 0;i < 5;i++)    cout << hex<<int(code[i]) << " ";
    cout << endl;
    unsigned long old = 0;
    VirtualProtect(function, 0x1000, PAGE_EXECUTE_READWRITE, &old);//改写页面权限为读写可执行
    memcpy(function, code, 5);//把code赋给funciton
    cout << "after modifying the address of function:" << function << endl;
    function();
    return 0;
}

image-20210623231244427

(offset=0x1e 0x1e+5=0x22 0xdd+0x22=0x100)

关于unsigned int offset = (unsigned int)hook_function - (unsigned int)function-5;-5的原因:

img

也就是说jmp一个指令占5个字节,跳转offset要算上当前jmp指令,所以要-5

VirtualProtect函数:

更改对调用进程虚拟地址空间中已提交页面区域的保护。

BOOL VirtualProtect(
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD  flNewProtect,
  PDWORD lpflOldProtect
);

image-20210622020124200

image-20210622020144490

逆向

拖入idaimage-20210622021309634

看的还是挺清楚的

IAT HOOK

一个程序的所有代码一般不会全部都编译到一个模块中,分拆到不同的模块既有利于合作开发,也有利于代码管理,降低耦合。

动态链接库就提供了这样的能力,将不同的模块编译成一个个的动态库文件,在使用时引入调用。

在Windows平台上,动态链接库一般以DLL文件的形式存在,主程序模块一般是EXE文件形式存在。无论是EXE还是DLL,都是属于PE文件。

一个模块引用了哪些模块的哪些函数,是被记录在PE文件的导入表IAT中。这个表格位于PE文件的头部,里面记录了模块的名字,函数的名字。

在模块加载时,模块加载器将解析对应函数的实际地址,填入到导入表中。

通过修改导入表IAT中函数的地址,这种HOOK叫IAT HOOK

类似于dll注入的静态修改导入表

SEH HOOK

SEH是Windows操作系统上结构化异常处理的缩写,在代码中通过try/except来捕获异常时,操作系统将会在线程的栈空间里安置一个异常处理器(其实就是一个数据结构),里面定义了发生异常时该去执行哪里的代码处理异常。

异常处理可以多级嵌套,那多个异常处理就构成了一个链表,存在于栈空间之上。

image-20210623224742348

当发生异常时,操作系统系统就从最近的异常处理器进行寻求处理,如果能处理则罢了,不能处理就继续寻求更上一级的异常处理器,直到找到能处理的异常处理器。如果都没法处理,那对不起,只好弹出那个经典的报错对话框,进程崩溃。

image-20210623224751974

SEH HOOK针对的目标就是修改这些异常处理器中记录的函数指针,当异常发生时就能获得执行,从而劫持到执行流!因为这些异常处理器都位于线程的栈空间,修改起来并非难事。

C++ virtable HOOK

C++是一门面向对象的编程语言,支持面向对象的三大特性:封装性继承性多态性

其中的多态性,各个C++编译器基本上都是通过一种叫虚函数表的机制来实现。

下面通过一个实际的例子来感受一下虚函数表在C++多态性上发挥的作用。

#include <iostream>
using namespace std;

class Animal {
public:
 virtual void breathe() {
  cout << "Animal breathe" << endl;
 }
 virtual void eat() {
  cout << "Animal eat" << endl;
 }
};

class Fish : public Animal {
public:
 virtual void breathe() {
  cout << "Fish breathe" << endl;
 }
 virtual void eat() {
  cout << "Fish eat" << endl;
 }
};

class Cat : public Animal {
public:
 virtual void breathe() {
  cout << "Cat breathe" << endl;
 }
 virtual void eat() {
  cout << "Cat eat" << endl;
 }
};

int main() {
 Animal* animal = nullptr;
 Fish* fish = new Fish();
 Cat* cat = new Cat();

 animal = fish;
 animal->breathe();
 animal->eat();

 cout << "--------------" << endl;
 cout << "sizeof(fish) = " << sizeof(fish) << endl;
 cout << "sizeof(cat) = " << sizeof(cat) << endl;
 cout << "--------------" << endl;

 animal = cat;
 animal->breathe();
 animal->eat();

 delete fish;
 delete cat;
 return 0;
}

image-20210623225252320

image-20210623225336710

image-20210623225345062

通过上面的实例,总结一下对象、虚函数表和虚函数代码之间的关系如下图所示:

image-20210623225401903

每个包含虚函数的类对象,在内存中都有一个指针,位于对象头部,指向的是一个虚函数表,表中的每一项都是虚函数地址。

类继承后,如果重写了父类的虚函数,子类对象指向的表格中对应函数的地址将会更新为子类的函数。

这样,使用父类指针指向子类对象,通过指针调用虚函数时,就能调用到子类重写的虚函数,从而实现多态性

既然是记录函数地址的表格,那就有存在被篡改的可能,这就是C++ virtable HOOK

通过篡改对应虚函数的地址,实现对相应函数调用的拦截。

实施这种HOOK,需要逆向分析目标C++对象的结构,掌握虚函数表中各个函数的位置,才能精准打击。

内核

SSDT HOOK

IDT HOOK

IRP HOOK

TDI HOOK && NDIS HOOK

Windows Message HOOK

参考资料

https://cloud.tencent.com/developer/article/1768574