声明
首先呢,这里会牵扯到虚函数,建议先看看以下博客(前三章就行),
c++类成员函数指针
详解虚函数的实现过程之初探虚表(1)
详解虚函数的实现过程之单继承(2)
详解虚函数的实现过程之多重继承(3)
详解虚函数的实现过程之虚基类(4)
详解虚函数的实现过程之菱形继承(5)
看了之后大家会了解到:虚函数的地址存放在虚表里面,虚表地址放在对象里面,虚表地址都是放在对象的前四个字节。
所以我们可以进行把存放在 虚表里面这个函数地址值给替换(这也就是Hook手法之一)了,当进行调用虚函数时,是不是就可以改变程序的走向,和原来不同。
过程
class Base
{
public:
virtual void Print() {
printf("这是一个虚函数");
}
};
void Myprint() {
printf("这是一个普通函数");
}
过程综述
Base类里面有个虚函数,虚表地址存放在对象的前四个字节里面,然后虚函数地址存放在虚表的前四个字节里面,
用Myprint
函数地址,把虚表里面前四个字节的地址值给替换了,这样就Hook成功。
代码讲解
- 创建一个Base对象,然后用指针指向它
Base pb;
Base* Pb = &pb;
或者这样也行
Base* Pb = new Base();
- 找到虚表中存放虚函数的地址的地址单元
DWORD* pVtAddr = (DWORD*)*(DWORD*)Pb;
首先,把这个指针的范围放在四个字节,因为虚表地址就四个字节,所以有了(DWORD *)
,然后出内容,加个*
号,即*(DWORD *)
,取出内容后,内容也就是虚表的地址值,此时这个指针也就指向了这个地址值,范围是整个虚表,然后还要强转一下范围(DWORD*)
(因为函数的地址值是前4个字节),所以就有了(DWORD*)*(DWORD*)Pb
- 修改一下这个地址单元的属性(原来是不可写的,改为可写)
DWORD dwOldProtect = 0;
VirtualProtect(pVtAddr, 4, PAGE_READWRITE, &dwOldProtect);
- 然后用函数地址值来覆盖虚表中存放虚函数的地址地址单元
*pVtAddr = (DWORD)Myprint;
- 调用函数
Pb->Print();
- 如果利用
Base* Pb = new Base();
出来的堆空间,别忘了把它delete掉
delete Pb;
正常代码:
#include <iostream>
#include<Windows.h>
class Base
{
public:
virtual void Print() {
printf("这是一个虚函数");
}
};
void Myprint() {
printf("这是一个普通函数");
}
int main()
{
Base* Pb = new Base();
Pb->Print();
delete Pb;
return 0;
}
Hook后:
#include <iostream>
#include<Windows.h>
class Base
{
public:
virtual void Print() {
printf("这是一个虚函数");
}
};
void Myprint() {
printf("这是一个普通函数");
}
int main()
{
Base* Pb = new Base();
DWORD* pVtAddr = (DWORD*)*(DWORD*)Pb;
DWORD dwOldProtect = 0;
VirtualProtect(pVtAddr, 4, PAGE_READWRITE, &dwOldProtect);
*pVtAddr = (DWORD)Myprint;
Pb->Print();
delete Pb;
return 0;
}