首页 > 编程语言 >虚函数(涉及汇编原理)

虚函数(涉及汇编原理)

时间:2023-02-06 00:55:24浏览次数:44  
标签:汇编 虚表 函数 int v1 原理 Name 指针

虚函数

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"));
}

标签:汇编,虚表,函数,int,v1,原理,Name,指针
From: https://www.cnblogs.com/XiuzhuKirakira/p/17094267.html

相关文章

  • Shell函数
    Shell函数一、Shell函数函数的作用就是把程序里需要多次使用的部分代码列出来,然后为这部分代码起个名字,其它所有的重复调用这部分代码都只用调用这个名字就可以(类似于别......
  • Dubbo2.7的Dubbo SPI实现原理细节
    总结/朱季谦本文主要记录我对DubboSPI实现原理的理解,至于什么是SPI,我这里就不像其他博文一样详细地从概念再到JavaSPI细细分析了,直接开门见山来分享我对DubboSPI的见解......
  • 屏幕图像渲染原理
    对应一个客户端开发来说,平时做的的最多的就是写页面,所以有必要了解从视图代码到图像显示到屏幕上的整个过程和原理。下面以从视图代码到显示器图像的中间产物帧缓冲区图像......
  • C++ const成员函数如何改变类的成员变量
    C++const成员函数不能改变类的普通成员变量。可以改变类的静态成员变量。可以改变类的被mutable修饰的成员变量。#include<bits/stdc++.h>usingnamespacestd;s......
  • Linux系统Shell脚本第四章:shell函数
    一、shell函数1.函数的作用定义较为复杂的但是需要重复使用的内容,以便再次使用可以直接调用函数节约时间,提高效率2.函数使用步骤①首先是定义函数②其次是调用函数(......
  • RAM算法原理
    1应用场景 信道的不对称性和信道的高波动是移动环境中无线信道的两个显著特征。因此,当在车辆网络等移动环境中使用IEEE802.11设备时,有一个能够处理这些问题的有效速......
  • 创建my_strstr函数
    #include<assert.h>char*my_strstr(char*p1,char*p2){assert(p1!=NULL);assert(p2!=NULL);//保证指针有效性char*s1=p1;char*s2=p2;char*cur=p1......
  • 《分布式技术原理与算法解析》学习笔记Day02
    分布式系统发展历程分布式的发展过程经历了三个阶段:单机模式(单兵模式)数据并行或者数据分布式(游击队模式)任务并行或者任务分布式(集团军模式)什么是单机模式,它的优缺点......
  • 完胜的Scan(Excel函数集团)
    Scan看上去简单,就四个字母,其实,嗯,很内涵……Scan的基础用法就三个参数,好吧,实际应该算是四个参数:=Scan(初始值,数据源,Lambda(定义名称1,定义名称2,运算))以上,不算废话的......
  • 小程序云开发联表数据查询以及云函数中的应用
    点击观看视频↓↓↓小程序云开发联表数据查询以及在云函数中的应用|多表查询|连表lookup|聚合函数文章目录​​1、联表查询​​​​(1)lookup联接两个表格​​​​(2)使......