应用层
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;
}
(offset=0x1e 0x1e+5=0x22 0xdd+0x22=0x100)
关于unsigned int offset = (unsigned int)hook_function - (unsigned int)function-5;
-5的原因:
也就是说jmp一个指令占5个字节,跳转offset要算上当前jmp指令,所以要-5
VirtualProtect函数:
更改对调用进程虚拟地址空间中已提交页面区域的保护。
BOOL VirtualProtect(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);
逆向
拖入ida
看的还是挺清楚的
IAT HOOK
一个程序的所有代码一般不会全部都编译到一个模块中,分拆到不同的模块既有利于合作开发,也有利于代码管理,降低耦合。
动态链接库就提供了这样的能力,将不同的模块编译成一个个的动态库文件,在使用时引入调用。
在Windows平台上,动态链接库一般以DLL文件的形式存在,主程序模块一般是EXE文件形式存在。无论是EXE还是DLL,都是属于PE文件。
一个模块引用了哪些模块的哪些函数,是被记录在PE文件的导入表IAT中。这个表格位于PE文件的头部,里面记录了模块的名字,函数的名字。
在模块加载时,模块加载器将解析对应函数的实际地址,填入到导入表中。
通过修改导入表IAT中函数的地址,这种HOOK叫IAT HOOK。
类似于dll注入的静态修改导入表
SEH HOOK
SEH是Windows操作系统上结构化异常处理的缩写,在代码中通过try/except来捕获异常时,操作系统将会在线程的栈空间里安置一个异常处理器(其实就是一个数据结构),里面定义了发生异常时该去执行哪里的代码处理异常。
异常处理可以多级嵌套,那多个异常处理就构成了一个链表,存在于栈空间之上。
当发生异常时,操作系统系统就从最近的异常处理器进行寻求处理,如果能处理则罢了,不能处理就继续寻求更上一级的异常处理器,直到找到能处理的异常处理器。如果都没法处理,那对不起,只好弹出那个经典的报错对话框,进程崩溃。
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;
}
通过上面的实例,总结一下对象、虚函数表和虚函数代码之间的关系如下图所示:
每个包含虚函数的类对象,在内存中都有一个指针,位于对象头部,指向的是一个虚函数表,表中的每一项都是虚函数地址。
类继承后,如果重写了父类的虚函数,子类对象指向的表格中对应函数的地址将会更新为子类的函数。
这样,使用父类指针指向子类对象,通过指针调用虚函数时,就能调用到子类重写的虚函数,从而实现多态性。
既然是记录函数地址的表格,那就有存在被篡改的可能,这就是C++ virtable HOOK。
通过篡改对应虚函数的地址,实现对相应函数调用的拦截。
实施这种HOOK,需要逆向分析目标C++对象的结构,掌握虚函数表中各个函数的位置,才能精准打击。