5种隐蔽的外挂获取执行时机方法介绍
一、模拟前的设定
本次模拟外挂获取执行时机,新建一MFC程序FakeGamke,并模拟游戏中玩家攻击后的伤害计算,界面如下:
模拟游戏中玩家相关的类如下:
class CGameObject //游戏对象基类
{
public:
CGameObject()
{
m_pAttack = new int;
}
~CGameObject()
{
delete m_pAttack;
}
int GetAttack()
{
return *m_pAttack;
}
void SetAttack(int nAttack)
{
*m_pAttack = nAttack;
}
int GetAdditionAttack()
{
return m_nAdditionAttack;
}
void SetAdditionAttack(int nAdditionAttack)
{
m_nAdditionAttack = nAdditionAttack;
}
virtual TCHAR * GetName()
{
return _T("");
}
//其他成员函数...
virtual float ComputeDamage() //计算伤害
{
float fDamage = (GetAttack() + GetAdditionAttack()) * 1.0;
return fDamage;
}
private:
int *m_pAttack; //基础攻击力
int m_nAdditionAttack; //攻击力加成
//....游戏对象其他属性
};
class CGamePlayer : public CGameObject
{
public:
CGamePlayer()
{
m_szPlayerName = new TCHAR[20];
_tcscpy(m_szPlayerName, _T("天下第一"));
SetAttack(55);
SetAdditionAttack(12);
SetPreAttack(0.234);
}
~CGamePlayer()
{
delete[] m_szPlayerName;
}
float GetPreAttack()
{
return m_fPreAttrack;
}
void SetPreAttack(float fPreAttack)
{
m_fPreAttrack = fPreAttack;
}
virtual TCHAR * GetName()
{
return m_szPlayerName;
}
virtual float ComputeDamage() //计算伤害的函数
{
float fDamage = (__super::ComputeDamage())*(1 + GetPreAttack());
return fDamage;
}
private:
float m_fPreAttrack; //攻击比例加成
TCHAR* m_szPlayerName; //玩家名称
// ... 玩家其他属性
};
其中假设 输出伤害 = (攻击力 + 攻击力加成)*(1+攻击比例加成)。当点击攻击按键时表示玩家攻击动作,此时计算输出伤害并显示。
#include "FakePlayer.h"
CGameObject* g_LocalPlayer = new CGamePlayer(); //本地玩家
void CFakeGameDlg::OnBnClickedButtonAttack() //点击攻击将计算伤害
{
float fDamage = g_LocalPlayer->ComputeDamage();
CString stTmp;
stTmp.Format(_T("%0.3f"), fDamage);
SetDlgItemText(IDC_EDIT_DAMAGE, stTmp);
stTmp.Format(_T("[TFFLAG] ThreadID:0x%x"), GetCurrentThreadId());
OutputDebugString(stTmp);
}
void CFakeGameDlg::OnBnClickedButtonDisplay() //点击显示玩家属性
{
// TODO: Add your control notification handler code here
CString stTmp;
stTmp.Format(_T("%d"), g_LocalPlayer->GetAttack());
SetDlgItemText(IDC_EDIT_ATTACKPOWER, stTmp);
stTmp.Format(_T("%d"), g_LocalPlayer->GetAdditionAttack());
SetDlgItemText(IDC_EDIT_ATTACKADDITION, stTmp);
stTmp.Format(_T("%0.3f"), ((CGamePlayer*)g_LocalPlayer)->GetPreAttack());
SetDlgItemText(IDC_EDIT_ATTACKPER, stTmp);
stTmp.Format(_T("%s"), g_LocalPlayer->GetName());
SetDlgItemText(IDC_EDIT_NAME, stTmp);
}
此次模拟 输出伤害为82.678 = (55 + 12)*(1+0.234)。FakeGame正常执行可得伤害值82.678。现模拟如何获取FakeGame的伤害计算时机,并改变其中部分属性或输出伤害结果值,达到高额伤害效果。
二、方法
1、虚函数地址&虚表地址替换
C++中的虚函数的作用主要是实现了多态的机制,虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表保存的是类的虚函数地址,这张表解决了类的继承、多态、覆盖问题。有虚函数的类的实例中这个表地址会被分配到这个实例的内存中。
既然虚表中存放的是类的成员函数的地址,那如果将其替换,就给了外挂代码一次执行的机会。在FakeGame中,CGamePlayer类继承于CGameObject类。其中ComputeAttack(计算伤害函数)为虚函数。这两个类的结构如下:
CGameObject 与CGamePlayer的虚表会被分配到PE文件中的.rdata区段,具体如下:
在生成CGameObject、CGamePlayer对象时,虚表地址会赋值为对象内存的第一个四字节。(C++对象的内存布局非本文重点,读者自己了解一下)。
在CGamePlayer对象调用函数ComputeAttack时,通过对象地址获取虚表地址,再通过虚表获取函数地址。
根据上面的知识,现在我们通过替换虚表内的函数地址的方式,替换CGamePlayer对象虚表内的ComputeAttack函数地址,使得FakeGame在攻击计算伤害时执行替换后的函数,以此达到劫持FakeGame执行时