C++浅析——虚表和虚表Hook

为了探究虚表的今生前世,先来一段测试代码

虚函数类:

class	CTest
{
public:
	int		m_nData;

	virtual	void	PrintData()
	{
		printf("Data = 0x%x\n", m_nData);
	}
};

class	CBase1
{
public:
	int		m_nData;

	virtual	void PrintData1() = 0;
};

class	CBase2
{
public:
	int		m_nData;

	virtual	void PrintData2() = 0;
};

class	CBaseTest : public CBase1, public CBase2
{
public:
	void	PrintData1()
	{
		printf("Data = 0x%x\n", CBase1::m_nData);
	}

	void	PrintData2()
	{
		printf("Data = 0x%x\n", CBase2::m_nData);
	}
};

测试代码:

void	Test()
{
	CTest	oCTest;
	CTest*	pCTest = new CTest();

	pCTest->m_nData = 0x8888;
	pCTest->PrintData();

	oCTest.m_nData = 888;
	oCTest.PrintData();

	delete	pCTest;
}

void	BaseTest()
{
	CBaseTest	oCBaseTest;

	oCBaseTest.PrintData1();
}

1、虚表位于何处?

WinDbg显示虚表的地址的属性:Usage    RegionUsageImage(代表此地址区域被映射到二进制文件的镜像),为只读属性。

2、同一个类对象的虚表位置相同吗?

同一个类对象的虚表位置相同。

加载模块的内存位置:0x00380000

虚表的VA = 0x003FDF2C - 0x00380000 = 0x0007DF2C

3、虚表需要在加载后进行初始化吗?

否,虚表的位置在PE文件的 .rdata 节中,.rdata 是存放程序常量的地方,属性为只读

从2的截图中可以看出,虚表第一个函数(CTest::PrintData)的地址 = 0x003A9EFB

其VA =  0x003A9EFB- 0x00380000 = 0x00029EFB

由于该PE文件的基址 = 0x00040000(默认情况下)

故其未重定位前的虚拟地址 = 0x00040000 + 0x00029EFB = 0x00429EFB

由于x86平台文件都是小尾储存,倒过来写就是 FB 9E 42 00,即如图左上红圈所示

说明PE文件在编译好后已经将虚表和虚表中的虚函数地址填写完毕并写入.rdata区

4、多父类继承的虚表如何存放?

多重继承的子类对象实际上是将每个父类的完整数据按顺序依次排布,所以拥有每个父类的虚表,父类每个虚表的位置同样在每个父类的起始位置。

oCBaseTest对象内存分布

5、何为虚表Hook?

通过以上对虚表的来龙去脉的分析,有IAT Hook基础的同学可以很容易的想到如何进行虚表Hook了。

5.1 改PE文件

直接改掉PE文件虚表位置的函数指针,指向自己伪造的虚表地址(当然必须保证自己的虚表函数指针有效,且函数调用形式和参数个数一致)

5.2 内存中Hook

当PE文件加载后,直接在内存中修改虚表函数指针(注意要去写保护,当然调试工具是没问题的)。

5.3 Hook测试(内存Hook的方式)

测试代码:

class	CVFHook
{
public:
	typedef	void (CVFHook::* MemberFunctionPtr)();

public:
	static	BOOL	Hook(void* pVirtrulFunctionVA);

private:
	void	PrintData();
};
BOOL	CVFHook::Hook(void* pVirtrulFunctionVA)
{
	HMODULE	hHookedModule = ::GetModuleHandle(NULL);

	if (NULL == hHookedModule)
	{
		_tprintf(_T("Get module handle fail\n"));
		return	FALSE;
	}

	//取重定位后的虚表地址
	MemberFunctionPtr* pMemberFunctionPtr = (MemberFunctionPtr*)((INT64)pVirtrulFunctionVA + (INT64)hHookedModule);

	//去除读写保护
	DWORD	dwOldProtect = 0;
	::VirtualProtect(pMemberFunctionPtr, sizeof(pMemberFunctionPtr), PAGE_READWRITE, &dwOldProtect);
	*pMemberFunctionPtr = &CVFHook::PrintData;

	return	TRUE;
}

void	CVFHook::PrintData()
{
	printf("PrintData is facked!!!\n");
}

然后再dll的加载位置传入虚表的VA参数调用即可:

BOOL APIENTRY DllMain( HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		{
			CVFHook::Hook((void*)0x0007DF2C);
			break;
		}
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

注意:这里我们伪造的虚函数 CVFHook::PrintData() 我直接用的是与要 Hook 的虚函数相同的成员函数形式,由于Release版的代码可能会被编译器优化掉函数调用形式,如果如此,需要我们手动编写汇编形式的虚函数代码。

启动测试程序 Console.exe ,然后用 APIMonitor 工具将 VirtrualFunctionHook.dll 注入,然后按任意键观察结果如下:

我们发现堆对象被成功Hook了,但局部对象没有被Hook掉,这是为什么呢?难道上面的分析错了?

我们来对前面的测试代码进行反汇编后观察:

发现对象调用PrintData时并没有通过虚表调用,而是直接使用普通成员函数的调用方式,以节约开销,只有使用对象指针或引用来调用虚函数时才会使用虚表调用的方式,故虚表Hook的方式失效.

版权声明:本文为博主原创文章,如需转载请说明转至http://blog.csdn.net/gufeng99

时间: 2024-08-29 02:11:21

C++浅析——虚表和虚表Hook的相关文章

C++学习 - 虚表,虚函数,虚函数表指针学习笔记

虚函数 虚函数就是用virtual来修饰的函数.虚函数是实现C++多态的基础. 虚表 每个类都会为自己类的虚函数创建一个表,来存放类内部的虚函数成员. 虚函数表指针 每个类在构造函数里面进行虚表和虚表指针的初始化. 下面看一段代码: // // main.cpp // VirtualTable // // Created by Alps on 15/4/14. // Copyright (c) 2015年 chen. All rights reserved. // #include <iostr

C++虚函数:虚指针、虚表、虚函数入口地址

测试程序: //test.c #include"stdio.h" #include"string.h" class GSVirtual { public: void gsv(char *src) { char buf[200]; strcpy(buf,src); vir2(); } virtual void vir1() { printf("vir1"); } virtual void vir2() { printf("vir2&quo

虚表的形成

一切结论都必须以事实为依据,这样才能形成长久记忆! 虚表的形成过程: ①对于非继承类而言:编译器会根据类中是否有虚函数产生虚表,如果有虚函数,则会形成虚表,虚表中会按照成员函数声明顺序存放函数的地址,从而形成存放函数入口地址的函数指针数组,最后把数组地址存放在类的开始的位置,只一个指针的大小. ②对于继承类而言:对于单继承,如果父类中有虚函数表,则编译器编译时,会把父类的虚表赋值一份,并把其地址放在类的开始,只占一个指针大小的空间:对于多继承,编译器会把多个父类的虚表地址按照继承顺序依次从类的开

获取C++虚表地址和虚函数地址

获取C++虚表地址和虚函数地址 By qianghaohao 学过C++的应该都对虚表有所耳闻,在此就不过多介绍概念了,通过实 例来演示一下如何获取虚表地址和虚函数地址. 简单说一下虚表的概念:在一个类中如果有虚函数,那么此类的实例中就有 一个虚表指针指向虚表,这个虚表是一块儿专门存放类的虚函数地址的内存. 图示说明本文的主题(先看图更容易后面代码中的指针操作): 代码如下(要讲解的都在代码的注释中说明了): class Base { public: virtual void f() { cou

COM接口函数通用Hook方法

本文是我的本科学位论文, 今发表在此, 以示原创之据 第1章 绪论 研究背景 研究意义 相关技术简介 COM概述 COM内存模型描述及C语言和C++语言实现 调用约定 Hook API原理 Windows钩子原理及进程注入 开发及调试环境 第2章 问题抽象及关键技术研究 实验01:通过调试器查看C++类的虚函数表 实验02:通过函数指针调用C++虚函数 实验03:交换两个相同C++类的虚函数表 实验04-1:替换C++虚函数表中的虚函数(__thiscall)地址 实验04-2:替换C++虚函数

C++ 继承与接口 知识点 小结(一)

[摘要] 要求理解覆盖.重载.隐藏的概念与相互之间的区别:熟记类继承中对象.函数的访问控制:掌握虚函数.虚函数表.虚函数指针的联系:理解区分虚函数和虚继承在虚方法.虚指针在空间分配上的重点与难点:熟练使用多重继承,要求能区分基类的同名函数和基类的空间布局. [正文] 类继承中的覆盖 #include<iostream> using namespace std; class A { protected: int m_data; public: A(int data = 0) { m_data =

C++虚函数表剖析

关键词:虚函数.虚表,虚表指针,动态绑定,多态 一.概述 为了实现C++的多态,C++使用了一种动态绑定的技术. 这个技术的核心是虚函数表(下文简称虚表).本文介绍虚函数表是怎样实现动态绑定的. 二.类的虚表 每一个包括了虚函数的类都包括一个虚表. 我们知道,当一个类(A)继承还有一个类(B)时.类A会继承类B的函数的调用权.所以假设一个基类包括了虚函数,那么其继承类也可调用这些虚函数,换句话说,一个类继承了包括虚函数的基类.那么这个类也拥有自己的虚表. 我们来看下面的代码. 类A包括虚函数vf

数据库编程1 Oracle 过滤 函数 分组 外连接 自连接

[本文谢绝转载原文来自http://990487026.blog.51cto.com] <大纲> 数据库编程1 Oracle 过滤 函数 分组 外连接 自连接 本文实验基于的数据表: winsows安装好Oracle11g之后,开始实验 SQLplus 登陆 ORacle sqlplus 退出的方式 查看用户之下有什么表 查看表的所有记录,不区分大小写 设置SQLplus行宽,页宽,列宽: 清屏命令 select as 语法 1,as别名的使用 2,没有引号带有空格的别名,无法识别: 3,带有

多态是什么

>多态定义的概念是:当不同的对象收到相同的消息时做出了不同工作这种现象就叫做多态. >那么在C++中是如何实现多态的呢? 首先多态分为编译时多态和运行时多态.在这里还有一个连编的概念,连编即把函数名和函数体中的代码连接在一起的过程.静态连编就是在编译阶段完成的连编,编译时多态就是通过静态连编完成的.静态连编时,系统通过实参与形参进行匹配,对于同名的重载函数就根据参数上的差异进行区分,然后进行连编,从而实现多态性.运行时多态使用动态连编实现的,动态连编是指运行阶段完成的连编.即当程序调用某一个函