虚函数
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. 对象与虚表指针、虚表的关系图结构
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