虚函数(涉及汇编原理)

虚函数

1. 多态

​ 对象的多态性需要通过虚表和虚表指针来完成

2. 虚表指针

1)位置

​ 定义在对象首地址的前4字节处(32位)或前8个字节(64位)处

2)定义

​ 一个二维指针,一个存储一个或多个虚函数地址的数组的数组名,类中的隐藏数据成员

3)初始化

​ 通过编译器在构造函数内插入代码来完成,在用户没有编写构造函数时,由于必须初始化虚表指针,因此编译器会提供默认的构造函数,以完成虚表指针的初始化

3.虚表

1)定义

​ 一个存储一个或多个虚函数地址的数组,当类中定义有虚函数时,编译器将该类中所有虚函数首地址保存在一张地址表中,这张表被称为虚函数地址表,简称虚表

2)虚表中虚函数的排序

​ 依据虚函数在类中的声明顺序而定,先声明的虚函数的地址会被排列在虚表中靠前的位置。

3)初始化:在构造函数中初始化

​ 先取得虚表的首地址,然后赋值到虚表指针中

00401024  mov     [ebp-4], ecx              ;[ebp-4] 存储this指针
00401027  mov     eax, [ebp-4]              ;取出this指针并保存到eax中,这个地址将会作为指针保存虚函数表首地址中
0040102A  mov     dword ptr [eax], offset ??_7Person@@6B@  ;取虚表的首地址,保存到虚表指针中
00401030  mov     eax, [ebp-4]              ;返回对象首地址

注意:在用户没有编写构造函数时,因为必须初始化虚表指针,所以编译器会提供默认的构造函数,以完成虚表指针的初始化

析构函数
①在析构函数中填写虚表,还原虚表指针,让其指向自身的虚表首地址,防止在析构函数中调用虚函数时取到非自身虚表,从而导致函数调用错误

②识别析构函数的充分条件:写入虚表指针

​ 写入虚表指针,指对象的虚表指针可能是有效的,已经指向正确的虚函数表,将对象的虚表指针重新赋值后,其指针可能指向了另一个虚表,虚表内容不一定和原来一样

4)注意

​ 同一个类共享一个虚表

4. 虚函数

1)定义

​ 被virtual关键字修饰的成员函数

2)语法

class CVirtual{
public:
	virtual int GetNumber(){
		return m__nNumber;
	}
	virtual void setNumber (int nNumber) {
		m_nNumber = nNunber;
private:
	int m_nNumber;
};

​ 如果这个类没有定义虚函数,则其长度为4,定义了虚函数后,由于还含有隐藏数据成员(虚表指针),因此大小为8

3)使用方法

class A{
public:
	virtual void a(){printf("a");}
	void b(){printf("b");}
};
class B : public A{
   	public:
	void a(){printf("Ba");}
	void b(){printf("Bb");}
};
A *a = new B;
a->a();//Ba
a->b();//b

4)注意

​ 虚函数必须作为成员函数使用,由于非成员函数没有this指针,因此无法获得虚表指针,进而无法获取虚表,也就无法访问虚函数
​ 当创建对象的时候,如果发现有重写,就将子类的虚函数的地址覆盖原来父类虚函数的地址

5)机制

​ 对象执行构造函数得到虚表指针,当其他代码访问此对象的虚函数,根据对象的首地址取出相应的虚表元素。当函数被调用时,间接访问虚表,得到相应的虚函数首地址,并调用执行

①间接寻址访问:使用对象的指针或引用来调用虚函数,需要查表

 (&v1)->Eating();//间接调用,此时调用原本的Eating函数
	/*
	00855246 8B F4                mov         esi,esp  
    00855248 8B 45 D8             mov         eax,dword ptr [v1]  
	0085524B 8D 4D D8             lea         ecx,[v1]  
	0085524E 8B 10                mov         edx,dword ptr [eax]  
	00855250 FF D2                call        edx
	8/

②直接调用方式:直接使用对象调用自身的虚函数,没有必要查表访问

v1.Eating();
	/*
	00855259 8D 4D D8             lea         ecx,[v1]  
	0085525C E8 71 BE FF FF       call        _MYCLASS_::Eating (08510D2h) 
    */

6)识别虚函数

虚函数特点
①类中隐式定义了一个数据成员

​ ②该数据成员在首地址处占4/8字节

​ ③构造函数会将此数据成员初始化为某个数组的首地址

​ ④这个地址属于数据区,是相对固定的地址

​ ⑤在这个数组内,每个元素都是函数指针

​ ⑥这些函数被调用时,第一个参数必然是this指针(注意调用约定)

​ ⑦函数内部可能会对this指针使用相对间接的访问方式

5. 对象与虚表指针、虚表的关系图结构

image-20230206003635837

6. 虚表Hook

得到对象首地址前4个字节的数据(虚表指针),取出虚表指针指向的地址(虚表首地址),得到目标函数在虚表中的实现地址,替换为FakeFunc(需要改变页面属性)

_MYCLASS_ v1(_T("韩烁"), 19, 1);
int *v2 = *((int**)&v1);
mov eax,dword ptr[v1]//将v1指向的值赋给eax
mov edx,dword ptr[eax]//将eax里地址指向的值赋给edx
mov v3,edx//现在v3里面存的是虚表首地址,即第一个虚函数

7. 直接用指针取出虚函数

#include<iostream>
#include<windows.h>
#include<tchar.h>
using namespace std;

class _MYCLASS_
{
public:
	_MYCLASS_(const TCHAR*Name, int Age, int Math)
	{
		this->Age = Age;
		memcpy(this->Name, Name, (_tcslen(Name) + 1) * sizeof(TCHAR));//注意构造函数中涉及指针的地方进行深拷贝
		this->Math = Math;
	}
	virtual void Eating() { _tprintf(_T("%s eating"),this->Name); }
	int Age;
	TCHAR Name[20];
	int Math;
};
class _BOY_ :public _MYCLASS_
{
public:
	virtual void Eating() { _tprintf(_T("%s noeating"), this->Name); }
};

class _GIRL_ :public _MYCLASS_
{
public:
	virtual void Eating() { _tprintf(_T("%s lieeating"), this->Name); }
};

typedef void(*lpfn_f1)();
void _tmain(int argc, TCHAR* argv[], TCHAR*envp[])
{
	_tsetlocale(LC_ALL, _T("Chinese-sinmplified"));

	_MYCLASS_ v1(_T("韩烁"), 19, 1);
	int *v2 = *((int**)&v1);
	(lpfn_f1(*v2))();//直接用指针取出虚函数
	_tprintf(_T("Input AnyKey To Exit\r\n"));
	system("pause");
}

8. 虚表 Hook 的完整代码

#pragma once
#include <windows.h>
#include <iostream>
#include <tchar.h>

using namespace std;



class _MYCLASS_
{
public:
	_MYCLASS_()
	{
	//	m_1 = new int;

		//_tprintf(_T("Construct()\r\n"));
	
	}
	_MYCLASS_(const TCHAR* Name, int Age, int BufferLength)
	{
	
		//_tprintf(_T("ParameterData Construct()\r\n"));
		memcpy(this->m_Name, Name, (_tcslen(Name) + 1) * sizeof(TCHAR));
		this->m_Age = Age;
		this->m_1  = new int[BufferLength];
		*this->m_1 = 100;
		
	}
	~_MYCLASS_()
	{
		//_tprintf(_T("Disconstruct()\r\n"));
		if (m_1!=NULL)
		{
			delete m_1;
			m_1 = NULL;
		}
	}
	_MYCLASS_(_MYCLASS_& ParameterData)
	{
		//_tprintf(_T("Copy Construct()\r\n"));
		/*this->m_Age = ParameterData.m_Age;
		memcpy(this->m_Name, ParameterData.m_Name, (_tcslen(ParameterData.m_Name) + 1) * sizeof(TCHAR));

		this->m_1 = new int;
		*(this->m_1) = *(ParameterData.m_1);
		*/
		//_tprintf(_T("Copy Construct Function\r\n"));
	}
	virtual void Eating()
	{
		_tprintf(_T("%s:Lie Down()\r\n"),this->m_Name);
	}
	virtual void Sleeping()
	{

	}
	void ShowData()
	{
		_tprintf(_T("%s    %d    %d    %p\r\n"), this->m_Name, this->m_Age, *this->m_1, this->m_1);
	}
protected:
public:
	TCHAR m_Name[20];    //40
	int   m_Age;         //4
	int*  m_1;           //4
};

class _BOY_ :public _MYCLASS_
{

public:
	_BOY_(const TCHAR* Name, int Age, int BufferLength) :_MYCLASS_(Name, Age, BufferLength)
	{

	}
	void Eating()
	{
		_tprintf(_T("%s:Stand()\r\n"),this->m_Name);
	}

};

class _GIRL_ :public _MYCLASS_
{
public:
	_GIRL_(const TCHAR* Name, int Age, int BufferLength) :_MYCLASS_(Name, Age, BufferLength)
	{

	}
	/*void Eating()
	{
		_tprintf(_T("%s:Sit()\r\n"), this->m_Name);
	}*/

};




#include "Demo.h"
#include "2.h"



//父类指针指向子类对象

typedef void (*lpfn_f1)();
void Sub_110();
void _tmain()
{
	setlocale(LC_ALL, "Chinese-simplified");

	_MYCLASS_ v1(_T("韩烁"), 19, 1);


	//虚表Hook
	int* v2 = *((int**)&v1);
	DWORD OldProtect = 0;

	int* v3 = NULL;   //

	(&v1)->Eating();
	
	_asm
	{
	
		 mov         eax, dword ptr[v1]
		 mov         edx, dword ptr[eax]
		 mov         v3,edx

	}
	
	
	VirtualProtect(v2, 4, PAGE_EXECUTE_READWRITE, &OldProtect);
	*v2 = (int)Sub_110;
	VirtualProtect(v2, 4, OldProtect, &OldProtect);
	(&v1)->Eating();  //间接调用  Hook
	
	
	
	
	VirtualProtect(v2, 4, PAGE_EXECUTE_READWRITE, &OldProtect);
	*v2 = (int)v3;
	VirtualProtect(v2, 4, OldProtect, &OldProtect);
	
	
	
	(&v1)->Eating();

	
	
	
	
	
	//	v1.Eating();      //直接调用





	//多态
	//

}

void Sub_1()
{

	/*_BOY_* v1 = new _BOY_(_T("韩烁"), 19, 1);
	_GIRL_* v2 = new _GIRL_(_T("镯樾"), 18, 1);
	//v1->Eating();
	//v2->Eating();


	if (v1 != NULL)
	{
		delete v1;
		v1 = NULL;
	}
	if (v2 != NULL)
	{
		delete v2;
		v2 = NULL;

	}

	//父类指针存储子类对象
	_MYCLASS_* v10 = new _BOY_(_T("韩烁"), 19, 1);

	v10->Eating();


	if (v10 != NULL)
	{
		delete v10;
	}

	v10 = new _GIRL_(_T("镯樾"), 18, 1);
	v10->Eating();
	if (v10 != NULL)
	{
		delete v10;
	}*/
}

void Sub_110()  //没有参数
{
	_tprintf(_T("HelloWorld\r\n"));
}

原文链接: https://www.cnblogs.com/XiuzhuKirakira/p/17094267.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    虚函数(涉及汇编原理)

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/316371

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月16日 下午2:11
下一篇 2023年2月16日 下午2:14

相关推荐