95.网络游戏逆向分析与漏洞攻防-ui界面的设计-ui的设计与架构

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!

如果看不懂、不知道现在做的什么,那就跟着做完看效果,代码看不懂是正常的,只要会抄就行,抄着抄着就能懂了

内容参考于:易道云信息技术研究院

上一个内容:94.利用数据包实现使用技能

码云版本号:2827cd4dfd810afb514b838b6a7c1ee0a11e639e

代码下载地址,在 titan 目录下,文件名为:titan-ui的设计与架构.zip

链接:https://pan.baidu.com/s/1W-JpUcGOWbSJmMdmtMzYZg

提取码:q9n5

--来自百度网盘超级会员V4的分享

HOOK引擎,文件名为:黑兔sdk升级版.zip

链接:https://pan.baidu.com/s/1IB-Zs6hi3yU8LC2f-8hIEw

提取码:78h8

--来自百度网盘超级会员V4的分享

94.利用数据包实现使用技能 它的代码为基础进行修改

现在实现了好多功能,但是没有一个界面显示都有什么功能,没有界面感觉有一点黑灯瞎火的,所以接下来先把ui给实现了,再搞其它的

下方的ui界面是黑兔sdk模板里的(去百度网盘下载)

tab控件的属性,属性可不改,改它只是为了好看点的

添加一个list控件,属性可不改,改它只是为了好看点的

给控件添加一个变量

输入个变量名,直接点完成,推荐变量名与下图中一致(lstlog)方便以后跟节奏

效果图:下图的窗口在游戏中按键盘上的Home键就会弹出,实现弹出的代码在htdMfcDll.cpp里的KeyCallBack函数中实现

CUIWnd_0.cpp文件也修改了,它修改的内容不重要,所以不在这把代码贴出来了

TextManger.h文件的修改:新加 TextManger构造函数

#pragma once
#include <iostream>
#include <fstream>

/*
03E1B73B | 68 58120304              | push fxgamelogic.4031258         | 4031258:"gui"
03E1B740 | E8 7BEF0F00              | call fxgamelogic.3F1A6C0         |
03E1B745 | 83C4 04                  | add esp,4                        |
03E1B748 | 85C0                     | test eax,eax                     |
03E1B74A | 0F84 10090000            | je fxgamelogic.3E1C060           |
03E1B750 | 68 4C120304              | push fxgamelogic.403124C         | 403124C:"TextManager"
03E1B755 | 50                       | push eax                         |
03E1B756 | 8D4C24 64                | lea ecx,dword ptr ss:[esp+64]    |
03E1B75A | 51                       | push ecx                         |
03E1B75B | E8 00091000              | call fxgamelogic.3F1C060         |
03E1B760 | 8D5424 68                | lea edx,dword ptr ss:[esp+68]    |
03E1B764 | 52                       | push edx                         |
03E1B765 | E8 16EF0F00              | call fxgamelogic.3F1A680         | 111111111111
03E1B76A | 83C4 10                  | add esp,10                       |
03E1B76D | 85C0                     | test eax,eax                     |
03E1B76F | 894424 14                | mov dword ptr ss:[esp+14],eax    | [esp+14]:CD3DDDIDX10_DrawIndexedPrimitive+113
*/
// 游戏中的中文表结构是 中文名英文id 这样的
typedef class TextTable {
public:
	int next = ((int)0xFFFFFFFF);
	int un = sizeof(TextTable);
	wchar_t* Txt;// 中文
	char TxtId[0x24];// 中文对应的英文id
}*PTextTable;

typedef class TextManger
{
public:
	static unsigned ReadTextProc;
	static const wchar_t NullText[2];
	TextManger(const char* file);
private:
	//int un[0x20];// 0x20 * 0x4 = 0x80
	int un[0x1E];
	union {
		char* TextBuff;
		unsigned* Count;
	};
	PTextTable textDatas = nullptr; // 中文结构
public:
	PTextTable* textTable = nullptr; // 暂时无用
private:
	int un1;
public:
	unsigned hashCount;
	unsigned* HashIndexTable = nullptr; // 哈希表
public:
	bool CreateTextDat(const char* filename); // 导出语言包
	bool LoadTextData(const char* _filename); // 加载 CreateTextDat函数写出的文件
	const wchar_t* ReadTextById(const char* _id); // 根据id获取中文名
	unsigned Hashcode(const char* id);// 哈希函数
	// 获取一个未使用的中文与对应id结构,用与存放从文件中读取的中文与对应id
	PTextTable GetNullText(PTextTable _table);
}*PTextManger;


TextManger.cpp文件的修改:新加 TextManger构造函数

#include "pch.h"
#include "TextManger.h"

unsigned TextManger::ReadTextProc = 0x10295FB0;
const wchar_t TextManger::NullText[2]{};

TextManger::TextManger(const char* file)
{
	LoadTextData(file);
}

bool TextManger::CreateTextDat(const char* _filename)
{
	if (!textTable) {
		return false;
	}
	std::ofstream out(_filename, std::ios::out|std::ios::binary);

	if (out.bad())return false;
	out.write((char*)&hashCount, sizeof(hashCount));
	for (int i = 0; i < hashCount; i++) {
		// 游戏中的中文表结构是 中文名英文id 这样的
		int lenId = strlen(textTable[i]->TxtId) + 1;
		// 宽字节一个字是2字节,wcslen函数返回值是字的个数
		int lenTxt = wcslen(textTable[i]->Txt) + 1;
		lenTxt = lenTxt * 2;
		out.write(textTable[i]->TxtId, lenId);
		out.write((char*)textTable[i]->Txt, lenTxt);
	}
	out.close();
	return true;
}

bool TextManger::LoadTextData(const char* _filename)
{
	CString txt;
	if (textDatas)return false;
	std::ifstream file(_filename, std::ios::in | std::ios::binary|std::ios::ate);

	if(file.bad()) {
		return false;
	}
	unsigned buffSize = file.tellg();
	TextBuff = new char[buffSize];
	file.seekg(std::ios::beg);
	file.read(TextBuff, buffSize);
	// textTable = new PTextTable[Count[0]];
	textDatas = new TextTable[Count[0]];
	txt.Format(L"%X", &textDatas);
	AfxMessageBox(txt);
	char* buffStart = TextBuff + 4;
	char* buffEnd = buffStart + buffSize;
	int icount = 0;
	while(buffStart < buffEnd){// 读取语言文件的数据到 textDatas 变量里
		int lenId = strlen(buffStart) + 1;
		memcpy(textDatas[icount].TxtId, buffStart, 0x24);
		buffStart = buffStart + lenId;
		int lenTxt = wcslen((wchar_t*)buffStart) + 1;
		lenTxt = lenTxt * 2;
		textDatas[icount].Txt = (wchar_t*)buffStart;
		buffStart = buffStart + lenTxt;
		icount++;
	}

	hashCount = Count[0];
	HashIndexTable = new unsigned[hashCount];
	memset(HashIndexTable, 0xFF, hashCount * sizeof(unsigned));

	for (int i = 0; i < Count[0]; i++) {// 给 textDatas 变量添加哈希算法索引(哈希表存放 textDatas的下标)
		unsigned hash = Hashcode(textDatas[i].TxtId);
		hash = hash % (hashCount + 1);
		if (HashIndexTable[hash] == -1) {
			HashIndexTable[hash] = i;
		}
		else {
			PTextTable nullTable = GetNullText(&textDatas[HashIndexTable[hash]]);
			nullTable->next = i;
		}
	}

	return false;
}

const wchar_t* TextManger::ReadTextById(const char* _id)
{
	// unsigned callProc = 0x10295FB0;

	if (textTable) {
		int index;
		_asm {
			push ebx
			mov ebx, this
			push _id
			call ReadTextProc
			mov index, eax
			pop ebx
		}
		if (index < 0)return NullText;
		PTextTable* _table = textTable;

		return (wchar_t*)_table[index]->Txt;
	}
	else {
		unsigned hash = Hashcode(_id);
		hash = hash % (hashCount + 1);
		unsigned index = HashIndexTable[hash];
		if (index > hashCount) {// index的值如果是-1那么它肯定比hashCount大
			return NullText;
		}
		while (strcmp(textDatas[index].TxtId, _id)) {
			index = textDatas[index].next;
			if (index > hashCount)return NullText;
		}
		return textDatas[index].Txt;
	}
}

unsigned TextManger::Hashcode(const char* id)
{
	unsigned hash = 0;
	for (int i = 0; id[i]; i++)
	{
		hash = hash * 131 + id[i];
	}
	return hash;
}

PTextTable TextManger::GetNullText(PTextTable _table)
{
	while (_table->next != -1) {// 从链表中找出最后一个(next的值是-1的)
		_table = &textDatas[_table->next];
	}
	return _table;
}

htdMfcDll.cpp文件的修改:修改了 InitInstance函数、KeyCallBack函数

// htdMfcDll.cpp: 定义 DLL 的初始化例程。这是一个MFC的dll
//

#include "pch.h"
#include "framework.h"
#include "htdMfcDll.h"
#include "extern_all.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// 注释掉下方宏,以入口点注入的方式注入
// #define WNDHOOK
#ifdef WNDHOOK
typedef struct htdDll
{
	HHOOK     keyHook;
	unsigned  KbdProc;
	unsigned  SetDll;

}*PHtdDll;
void htdSetDll(htdDll hDll);
htdDll mDll;
#endif

BEGIN_MESSAGE_MAP(ChtdMfcDllApp, CWinApp)
END_MESSAGE_MAP()


// ChtdMfcDllApp 构造

ChtdMfcDllApp::ChtdMfcDllApp()
{
	
}

ChtdMfcDllApp theApp;
ChtdMfcDllApp* PtheApp;

HHOOK keyHook;
LRESULT CALLBACK KeyCallBack(int nCode, WPARAM w, LPARAM l);

BOOL ChtdMfcDllApp::InitInstance()
{
	CWinApp::InitInstance();
	PtheApp = this;
#ifdef WNDHOOK
	mDll.KbdProc = (DWORD)(KeyCallBack);
	mDll.SetDll = 0;
#else
	keyHook = SetWindowsHook(WH_KEYBOARD, KeyCallBack);// 代码一开始,在这注册一个键盘事件的钩子
#endif
	PGameProc = new GameProc();
	wndMain = new CUI();
	wndMain->Create(IDD_MAIN);
	Client = wndMain;
	return TRUE;
}

// 钩子处理事件
LRESULT CALLBACK KeyCallBack(int nCode, WPARAM w, LPARAM l)
{

	if (nCode == 0)
	{
		if ((l & (1 << 31)) == 0)
		{
			switch (w)
			{
			case VK_HOME:

				PtheApp->wndMain->ShowWindow(TRUE);
				break;
			}
		}
	}
	return CallNextHookEx(keyHook, nCode, w, l);
}
#ifdef WNDHOOK
void htdInit(htdDll* hDll)
{
	hDll->KbdProc = mDll.KbdProc;
	hDll->keyHook = mDll.keyHook;
	hDll->SetDll = mDll.SetDll;
}

void htdSetDll(htdDll hDll)
{
	mDll = hDll;
}
#else
void htdInit()
{
}
#endif

GameProc.cpp文件的修改:修改了 Init函数

#include "pch.h"
#include "GameProc.h"
#include "extern_all.h"

// typedef bool(GameWinSock::* U)(char*, unsigned);

bool _OnComprase(HOOKREFS2) {
	return WinSock->OnRecv((char*)_EDI, _EBP);
}

bool _OnRecv(HOOKREFS2) {
	_EAX = WinSock->RecvPoint;
	return true;
}

bool _OnConnect(HOOKREFS2) {
	/*
		根据虚函数表做HOOK的操作
		截取 ecx 获取 winsock 的值(指针)
	*/
	unsigned* vtable = (unsigned*)_EDX;
	//WinSock = (GameWinSock *)_ECX;
	/*
		联合体的特点是共用一个内存
		由于 GameWinSock::OnConnect 的 OnConnect函数是 GameWinSock类的成员函数
		直接 vtable[0x34 / 4] = (unsigned)&GameWinSock::OnConnect; 这样写语法不通过
		所以使用联合体,让语法通过
	*/
	union {
		unsigned value;
		bool(GameWinSock::* _proc)(char*, unsigned);
	} vproc;
	DWORD oldPro, backProc;
	VirtualProtect(vtable, 0x100, PAGE_EXECUTE_READWRITE, &oldPro);
	/*
		vproc._proc = &GameWinSock::OnConnect;  这一句是把我们自己写的调用connect函数的地址的出来
	*/ 
	vproc._proc = &GameWinSock::OnConnect; 
	/*
		InitClassProc函数里做的是给指针赋值的操作
		InitClassProc(&GameWinSock::_OnConnect, vtable[0x34/4]);这一句的意思是把
		GameWinSock类里的_OnConnect变量的值赋值成vtable[0x34/4],这个 vtable[0x34/4] 是虚表里的函数
		vtable[0x34/4]是游戏中调用connect函数的函数地址,经过之前的分析调用connect是先调用了虚表中的
		一个函数,然后从这个函数中调用了connect函数
	*/
	InitClassProc(&GameWinSock::_OnConnect, vtable[0x34/4]);
	vtable[0x34 / 4] = vproc.value;


	vproc._proc = &GameWinSock::OnSend;
	InitClassProc(&GameWinSock::_OnSend, vtable[0x3C / 4]);
	vtable[0x3C / 4] = vproc.value;


	VirtualProtect(vtable, 0x100, oldPro, &backProc);

	PGameProc->InitTextManger();

	return true;
}

GameProc::GameProc()
{
	hooker = new htd::hook::htdHook2();
	Init();
	InitInterface();
}

void GameProc::LoadBase()
{
	LoadLibraryA("fxnet2.dll");
	hlogic = LoadLibraryA("fxgamelogic.dll");
	// 如果fxgamelogic.dll里用到了其它dll里面的东西,在这赋值可能会有问题
	// 最好在游戏网络连接成功哪里赋值,或者进入到游戏中之后再搞
	GetClassByName = (PGetClassByName)((unsigned)hlogic + 0x60A6C0);
	GetClassModByName = (PGetClassModByName)((unsigned)hlogic + 0x60C060);
	GetClassObject = (PGetClassObject)((unsigned)hlogic + 0x60A680);
}

void GameProc::InitTextManger()
{
	CLASS_OFFSET offSet;
	void* classPointer = GetClassByName("gui");
	GetClassModByName(&offSet, classPointer, "TextManager");
	txtManger = GetClassObject(&offSet);
}

void GameProc::Init()
{
#ifdef  Anly
	anly = new CAnly();
	GameAnlyer = new GameAnly();
#endif
	/*
		这里的 WinSock 是0没有创建对象,但是还是能调用Init函数
		这是因为Init函数里面没有用到this,没用到就不会报错
	*/
	// Client = new NetClient();
	init_datadesc();
}



void GameProc::InitInterface()
{
	LoadBase();
	// MessageBoxA(0, "1", "1", MB_OK);
	// 只会HOOK一次,一次性的HOOK
	hooker->SetHook((LPVOID)0x10617046, 0x1, _OnConnect, 0, true);
	/*
		第一个参数是HOOK的位置
		第二个参数是HOOK的位置的汇编代码的长度(用于保证执行的汇编代码完整)
		第三个参数是HOOK之后当游戏执行到第一个参数的位置的时候跳转的位置
		第四个参数是 _OnRecv 函数返回 false 之后跳转的位置
	*/
	hooker->SetHook((LPVOID)0x10618480, 0x1, _OnRecv, 0);
	hooker->SetHook((LPVOID)0x1061161D, 0x3, _OnComprase, (LPVOID)0x10611602);
	/*
		在这里绑定游戏处理数据包函数(0x10618480函数)
		然后0x10618480函数在上面一行代码已经进行了HOOK
		所以在调用_OnRecv函数指针时,它就会进入我们HOOK
	*/
	InitClassProc(&GameWinSock::_OnRecv, 0x10618480);

}




CUI.cpp文件的修改:新加 SetListView函数、getTxtManger函数,修改了 DoDataExchange函数、OnInitDialog函数、InstallPage函数

// CUI.cpp: 实现文件
//

#include "pch.h"
#include "htdMfcDll.h"
#include "CUI.h"
#include "afxdialogex.h"
#include "extern_all.h"

// CUI 对话框

IMPLEMENT_DYNAMIC(CUI, CDialogEx)

CUI::CUI(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_MAIN, pParent)
{

}

CUI::~CUI()
{
}

void CUI::SetListView(CListCtrl* lst)
{
	auto lStyle = GetWindowLongPtr(lst->m_hWnd, GWL_STYLE); // 获取窗口样式
	lStyle |= LVS_REPORT; // 设置为报表模式
	SetWindowLongPtr(lst->m_hWnd, GWL_STYLE, lStyle);// 给窗口设置样式
	auto dStyle = lst->GetExtendedStyle(); // 获取扩展样式
	dStyle |= LVS_EX_FULLROWSELECT; // 设置选择时选择一行
	dStyle |= LVS_EX_GRIDLINES; // 画网格线
	lst->SetExtendedStyle(dStyle); // 设置扩展样式
}

void CUI::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_TAB1, mTab);
	DDX_Control(pDX, IDC_LIST1, lstlog);
}

BOOL CUI::OnInitDialog()
{
	CDialogEx::OnInitDialog();
	
	SetListView(&lstlog);

	InstallPage(new CUIWnd_0(), IDD_PAGE_0, L"角色", TRUE);
	InstallPage(new CUIWnd_1(), IDD_PAGE_1, L"信息");

	lstlog.InsertColumn(0, L"消息", 0, 70);
	lstlog.InsertColumn(1, L"内容", 0, 700);
	lstlog.InsertColumn(2, L"时间", 0, 130);

	//PageINJ.Init(wAppPath);
	//PageRAN.SetAppPath(wAppPath);

	return TRUE;
}

bool CUI::InstallPage(CDialogEx* wnd, int IDD_WND, CString&& _Name, BOOL IsShow)
{

	if (CurPage >= MAX_PAGE_MAIN) return false;
	Pages[CurPage] = wnd;
	Pages[CurPage]->Create(IDD_WND, this);
	//Pages[CurPage]->SetParent(this);
	Pages[CurPage]->ShowWindow(IsShow);

	CRect rect;
	mTab.GetClientRect(&rect);
	rect.top += 28;
	rect.left += 5;
	rect.bottom -= 4;
	rect.right -= 5;
	Pages[CurPage]->MoveWindow(&rect);
	mTab.InsertItem(CurPage, _Name);

	CurPage++;
	return true;
}

BEGIN_MESSAGE_MAP(CUI, CDialogEx)
	ON_NOTIFY(TCN_SELCHANGE, IDC_TAB1, &CUI::OnTcnSelchangeTab1)
END_MESSAGE_MAP()


// CUI 消息处理程序


void CUI::OnTcnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult)
{
	// TODO: 在此添加控件通知处理程序代码
	*pResult = 0;
	int n = mTab.GetCurSel();
	for (int i = 0; i < CurPage; i++)
	{
		Pages[i]->ShowWindow(i == n);
	}
}

PTextManger CUI::getTxtManger()
{
	if (!txtManger)txtManger = new TextManger("F:\\语言包.txt");
	return txtManger;
}

CUI.h文件的修改:修改了 CUI类,引入 NetClient头文件、TextManger头文件,新加 SetListView函数、getTxtManger函数、lstlog变量

#pragma once
#include "afxdialogex.h"
#include "NetClient.h"
#include "TextManger.h"
//增加页面头文件
#include "CUIWnd_0.h"
#include "CUIWnd_1.h"
//游戏辅助UI类
// CUI 对话框
#define MAX_PAGE_MAIN 3

// 这里用了多重继承,这回有一个问题,函数名一样的会发生冲突
// 所以在继承的时候要注意函数名
class CUI : public CDialogEx,public NetClient
{
	DECLARE_DYNAMIC(CUI)

public:
	CUI(CWnd* pParent = nullptr);   // 标准构造函数
	virtual ~CUI();

// 对话框数据
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_MAIN };
#endif

protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持

	DECLARE_MESSAGE_MAP()

	CDialogEx* Pages[MAX_PAGE_MAIN];
	short      CurPage = 0;
public:
	CTabCtrl mTab;
	virtual BOOL OnInitDialog();
	bool    InstallPage(CDialogEx* wnd, int IDD_WND, CString&& _Name, BOOL IsShow=FALSE);
	afx_msg void OnTcnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult);
public:
	void SetListView(CListCtrl* lst);
	PTextManger getTxtManger();
	CListCtrl lstlog;
};


  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值