总结一下hook吧

常见的有虚表vitualtables hook,inline hook,iat hook,callbackhook等

其它那些 unhook这种衍生技术就分到别的章节学习笔记

虚表vitualtables hook

什么是虚表HOOK?

首先我们应该要先了解什么是虚表
虚函数表->虚函数

这张表解决了继承、覆盖等问题,保证其能反应实际的函数。

如何实现

将虚表的虚函数地址更改成我们的即可

1
2
3
4
5
1.获得虚表指针
2.修改虚表的内存保护属性为可读可写
3.修改虚表中的虚函数地址为我们的函数地址.
4.还原HOOK,也就是将原函数地址,写回到虚表中
5.恢复原保护属性

1.获得虚表指针

1
2
int pVftableAddress = *(int *)&test;        //第一步,获取自己的虚表指针

2.修改虚表的内存保护属性为可读可写

1
2
3
DWORD OldProtect = 0;                       //第二步修改虚表指针的内存保护属性,下方更改虚表
VirtualProtect((void *)pVftableAddress, 0x1000, PAGE_EXECUTE_READWRITE, &OldProtect);//修改内存保护属性,其地址是虚表指针地址

3.修改虚表中的虚函数地址为我们的函数地址.

1
(*(int *)pVftableAddress) = (int)MyVirtual;//第三步,HOOK,也就是将我们的函数地址,写入到虚表中.

第四步,还原HOOK,也就是将原函数地址,写回到虚表中

1
(*(int *)pVftableAddress) = (int)__pOldFunction;//第四步,还原HOOK,也就是将原函数地址,写回到虚表中.

第五步:恢复原保护属性

1
VirtualProtect((void *)pVftableAddress, 0x1000, OldProtect, &NewProtect);   // 第五步:恢复原保护属性

优缺点

优点:相对其他hook来说比较简单

缺点:比较明显,只能用于虚函数,使用范围小。

c++中的RTTI

在对虚函数进行hook之前 需要先对C++的RRTI进行了解,如果不了解RTTI和虚表在内存上布局上的依赖关系,就无法hook虚表里面的所有函数

一些面向对象专家在传播自己的设计理念时,大多都主张在设计和开发中明智地使用虚拟成员函数,而不用 RTTI 机制。但是,在很多情况下,虚拟函数无法克服本身的局限。每每涉及到处理异类容器和根基类层次(如 MFC)时,不可避免要对对象类型进行动态判断,也就是动态类型的侦测。如何确定对象的动态类型呢?答案是使用内建的 RTTI 中的运算符:typeid 和 dynamic_cast。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include<iostream>
#include <windows.h>


ULONG_PTR __pOldFunction = NULL;
ULONG_PTR __pOldFunctionAddress = NULL;
class MyTest
{
public:
MyTest()
{
printf("MyTest::MyTest()\r\n");
}
~MyTest()
{
printf("MyTest::~MyTest()\r\n");
}

void print();
virtual void Vritual();
int m_Number;
};



void MyTest::Vritual()
{
printf("原虚函数Vritual,未被hook\r\n\r\n");
}

void MyVirtual() //我们将虚表中的函数地址换为我们的函数地址
{
printf("已被hook,我们自己的函数\r\n\r\n");

}

void MyTest::print()
{
printf("void MyTest::print()\r\n");
}


int main(int argc, char* argv[])
{
MyTest test;
MyTest &obj = test;
obj.Vritual(); //虚函数调用,测试作用

int pVftableAddress = *(int *)&test; //第一步,获取自己的虚表指针

__pOldFunction = (*(int *)pVftableAddress); //保存原函数地址.因为我这里比较简单只有一个虚函数,多个虚函数还需要考虑索引位置的问题


DWORD OldProtect = 0; //第二步修改虚表指针的内存保护属性,下方更改虚表
DWORD NewProtect = PAGE_EXECUTE_READWRITE;
VirtualProtect((void *)pVftableAddress, 0x1000, NewProtect, &OldProtect);//修改内存保护属性,其地址是虚表指针地址

(*(int *)pVftableAddress) = (int)MyVirtual;//第三步,HOOK,也就是将我们的函数地址,写入到虚表中.


obj.Vritual(); //重新调用,看看是否被HOOK

(*(int *)pVftableAddress) = (int)__pOldFunction;//第四步,还原HOOK,也就是将原函数地址,写回到虚表中.

VirtualProtect((void *)pVftableAddress, 0x1000, OldProtect, &NewProtect); // 第五步:恢复原保护属性

obj.Vritual(); //恢复地址后,重新调用。
return 0;
}


转载 https://blog.csdn.net/weixin_43742894/article/details/105998391

模糊假定

如果做一个假虚表且这个假虚表足够长,然后把假虚表的地址赋值给对象中的虚表指针,那么所有对虚函数的调用代码就会变成”call[虚表地址+虚表偏移]”。只要贾诩表的长度大于真虚表可能的最大偏移,同时使用贾诩表存放虚表监控函数的地址,这样对于虚函数中所有的调用都可以hook,这种就叫做模糊假定

步骤

1.先做一张很大的假虚表 可以是40000表项 因为虚表的最大容量为40000表项

1
pJmptable = VitualAlloc(NULL,假虚表的个数 * sizeof(DWORD),MEM_COMMIT,PAGE_EXECUTE_READWAIT)

2.建立跳转表并初始化跳转表和假虚表

3.重定向对象的虚表指针

inline hook

inline hook 本质:就是jmp到我们需要执行的代码,执行完后再jmp回来。

1、安装Inline Hook

参数校验
判断要替换的硬编码长度是否合适
将要Hook的内存修改为可写:VirtualProtectEx或VirtualProtect
创建内存空间,存储替换前的硬编码
得到要跳转的值
要跳转的地址 = E9的地址 + 5 +真正跳转的地址
–>E9后面的地址 = 要跳转的地址 - E9的地址 - 5
将要Hook的内存全部初始化成NOP (长度>5 )
修改要Hook的硬编码
修改hook状态

2、使用被Hook的函数

这时使用的就是钩子函数
钩子函数得是裸函数
保存寄存器
获取数据
恢复寄存器
执行被覆盖的硬编码
执行完毕 跳回Hook地址

3、卸载Inline Hook

判断是否需要卸载,没Hook当然就不需要卸载
修改内存为可写:VirtualProtectEx或VirtualProtect
恢复原来的硬编码
修改hook状态
==Inline Hook 最需要注意的就是自己更改的硬编码需要将其还原,不能多加也不能少添==

iat hook

IAT hook 还是需要有先行条件的,就是导入表里面得有所使用函数的地址。

导入表里面没有被调函数的地址情况:

被调函数与调用函数在同一模块
直接自己使用LoadLibrary,GetProcAddress来加载函数
对于这种无法用IAT hook 的情况,我们可以使用inline hook。

讲到iat又要讲到PE表的双桥就够 再来复习一遍https://blog.csdn.net/weixin_43742894/article/details/106150321

双桥结构

简略说一下就是

1
2
3
4
1 INT
可以让你找到调用的函数名称或函数的索引编号
2 IAT
可以帮你找到该函数指令代码在内存空间的地址

在这里插入图片描述

当PE被加载进虚拟地址空间以后,IAT的内容会被操作系统更改为函数的VA。这个修改最终导致通向“值-名称”描述的桥2发生断裂,如在这里插入图片描述

当桥2发生断裂后,如果没有桥1作为参照(因为桥1和桥2维护了两个一一对应的函数RVA),我们就无法重新找到该地址到底是调用了那个函数。这就是为什么在导入表数据结构中存在两个桥的原因,也是为什么单桥导入表结构中无法实施绑定的原因。

==双桥断裂的过程:==
在PE加载的时候,双桥结构会断裂,IAT 会被PE加载器重写,PE加载器先搜索INT,PE加载器迭代搜索INT数组中的每个指针,找出 INT所指向的IMAGE_IMPORT_BY_NAME结构中的函数在内存中的真正的地址,并把它替代原来IAT中的值。
也就是当程序加载到内存以后,导入表部分发生变化的值正是IMAGE_IMPORT_DESCRIPTOR结构中的FirstThunk字段指向的函数指针表内容。
==这些内容已经不是指向函数名的指针了,而是指向了虚拟内存中该函数的可执行代码的地址!所以其含义也由原来的函数指针更改为函数的入口地址。==现在看来,所有的这些值最终都指向了同一片连续的区域,从而形成了我们常说的IAT。

==总结:==

1
2
3
1.在内存中,桥1可以让你找到调用的函数名称或函数的索引编号;桥2却可以帮助你找到该函数指令代码在内存空间的地址。
2.IAT原本指向函数名的指针,双桥结构断裂后,指向了虚拟内存中该函数的可执行代码的地址。
3.为什么要有INT和IAT俩个桥:当桥2发生断裂后,如果没有桥1作为参照(因为桥1和桥2维护了两个一一对应的函数RVA),我们就无法重新找到该地址到底是调用了那个函数。

==双桥结构断裂之后,IAT中存放的就是函数的真实地址,我们修改这个地址也就是实现了Hook。==

==实现原理:==
在进行Windows编程的时候,我们会经常使用Windows的API函数。而我们的API函数一般都是写在dll里,导入表里写着加载的dll和函数,代表该模块调用了哪些外部API,模块被加载到内存后, PE加载器会修改该表,地址改成外部API重定位后的真实地址, 我们只要直接把里面的地址改成我们新函数的地址, 就可以完成对相应API的Hook。

1
2
3
4
5
6
1、在Dll里构造Detour函数(也就是我们自己的函数)
2、获取Target函数地址,并找到Target函数所在的IAT的地址
3、保存原始的IAT地址和IAT地址所存储的内容
4、修改IAT地址中的数据(前提:修改内存属性为可写)
5、恢复IAT
如果需要调用原来API函数,可以直接使用保存的API地址,可以就保证了HOOK的有效性

https://blog.csdn.net/weixin_43742894/article/details/106151669

callbackhook

回调函数callback:所调用函数执行完,之后调用的函数
钩子函数hook:消息到达目的地之前,进行拦截,处理消息


总结一下hook吧
http://example.com/总结一下hook吧.html
Author
CDxiaodong
Posted on
November 2, 2022
Licensed under