37.网络游戏逆向分析与漏洞攻防-游戏网络通信数据解析-解码器细化类的实现

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

如果看不懂、不知道现在做的什么,那就跟着做完看效果

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

上一个内容:36.数据解码器的实现

码云地址(master 分支):https://gitee.com/dye_your_fingers/titan

码云版本号:855b495f1be08ac56b15baa71d8f2accf5c3325b

代码下载地址,在 titan 目录下,文件名为:titan-解码器细化类的实现.zip

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

提取码:q9n5

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

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

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

提取码:78h8

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

36.数据解码器的实现 它的代码为基础进行修改

上一个内容中的代码存在问题,解析的登录人物数据不正确,本次已经对它进行的修改,详情看后面GameWinSock.cpp文件里OnloginOk函数

然后分析数据包发现,相同的职业的角色在下图红框中都是一样的英文,这个英文所代表的的意思,应该是武器

划分结构:

如下图,可以搞成C++类,下图必须看懂否则会看不懂下方的代码,下方的代码完全依据图中划分的结构来写的,也就是ROLE_DATA类,如果看不懂下图的内容,那就看不懂 ROLE_DATA类,下图的内容很简单不能看不懂

效果图:

GameWinSock.cpp文件的修改:

#include "pch.h"
#include "GameWinSock.h"
#include "extern_all.h"
#include "NetClass.h"
#include "EnCode.h"

typedef bool(* DealProc)(char*&, unsigned&);

DealProc SendDealProc[0x100];
DealProc RecvDealProc[0x100];


GameWinSock::PROC GameWinSock::_OnConnect{};
GameWinSock::PROC GameWinSock::_OnSend{};
GameWinSock::PROC GameWinSock::_OnRecv{};

bool DeafaultDeal(char*&, unsigned&) { return true; }

// 登录数据包的处理
bool Onlogin(char *& buff, unsigned& len) { 
	/* 修改账号密码
	len = sizeof(DATA_LOGIN) + 1;
	buff = new char[len];
	DATA_LOGIN data;
	PDATALOGIN _data = &data;
	buff[0] = 0x2;

	CStringA _id = "";// 补充账号
	CStringA _pass = "";// 补充密码
	memcpy(_data->Id, _id.GetBuffer(), _id.GetLength());
	memcpy(_data->Pass, _pass.GetBuffer(), _pass.GetLength());
	memcpy(buff + 1, _data, len - 1);
	*/
	/* 监控登录数据
	PDATALOGIN _data = (PDATALOGIN)buff;
	CStringA _id = _data->Id;
	_data = (PDATALOGIN)(buff + _data->lenId - 0x10);
	CStringA _pass = _data->Pass;
	CStringA _tmp;
	// 请求登录 账号[% s]密码[% s] 这个内容别人在逆向的时候就会看到
	// 所以这种东西需要自己搞个编码来代替它
	
	 _tmp.Format("请求登录 账号[%s]密码[%s]", _id, _pass);
#ifdef  Anly
	anly->SendData(TTYPE::I_DIS, 1, _tmp.GetBuffer(), _tmp.GetAllocLength());
#endif
	*/

	/*
		返回false,游戏无法发送数据包
		原因看调用此此函数的位置 OnSend 函数(if (SendDealProc[buff[0]]((buff + 1), len - 1)))
	*/
	return true;
}

bool Onloginfailed(char*&buff, unsigned& len) { 
	int* code = (int*)&buff[1];
	Client->loginfailed(code[0]);
	return true; 
}

bool OnloginOk(char*& buff, unsigned& len) {
	CStringA txt;
	CStringA tmp;
	CString utmp;
	PDATALOGINOK _p = (PDATALOGINOK)&buff[1];

	ROLE_DATA* roleDatas = nullptr;
	if (_p->RoleCount > 0) {
	/*	char* buffStart = buff + 1 + sizeof(DATA_LOGIN_OK);
		roleDatas = new ROLE_DATA[_p->RoleCount];
		for (int i = 0; i < _p->RoleCount; i++)
		{
			roleDatas[i].byte.Init(buffStart);
			roleDatas[i].un.Init(buffStart);
			roleDatas[i].un1.Init(buffStart);
			roleDatas[i].name.Init(buffStart);
			roleDatas[i].infos.Init(buffStart);
			roleDatas[i].un2.Init(buffStart);
			roleDatas[i].un3.Init(buffStart);
		}
		Client->loginok(roleDatas, _p->RoleCount);*/
		char* buffStart = buff + 1 + sizeof(DATA_LOGIN_OK);
		char* buffEnd = buff + len;
		while (buffStart < buffEnd) {
			/*
				由于 EnCode 的入参带了& 所以可以在EnCode里直接对buffStart或len进行操作
				也就是说只要入参带了&在函数里的操作会影响它原本的值
				比如 
				char*a = 0;
				aaa(a)
				aaa(char*&AA){
					AA = new char[10]; // 它就会把a的值改成new char[10]
				}
			*/
			EnCode* _coder = new EnCode(buffStart, len);
			tmp.Format("%s:",data_desc[_coder->index][_coder->op].name);
			txt = txt + tmp;

			if (_coder->op == 0x7) {
				utmp = (wchar_t*)_coder->pointer;
				tmp = utmp;
				txt = txt + tmp;
			}else
			if (_coder->op == 0x3) {
				tmp.Format("[%f]", _coder->fval);
				txt = txt + tmp;
			}
			else {
				tmp.Format("[%d]", _coder->val);
				txt = txt + tmp;
			}
		}

#ifdef  Anly
		// 长度24的原因,它是宽字节要,一个文字要2个字节,一共是10个文字加上结尾的0是11个
		// 所以 11 乘以2,然后再加2 
		anly->SendData(TTYPE::I_DIS, 0, txt.GetBuffer(), txt.GetAllocLength() + 1);
#endif
	}
	return true;
}

// 这个函数拦截了游戏的连接
bool GameWinSock::OnConnect(char* ip, unsigned port)
{
#ifdef  Anly
	// 长度24的原因,它是宽字节要,一个文字要2个字节,一共是10个文字加上结尾的0是11个
	// 所以 11 乘以2,然后再加2 
	anly->SendData(TTYPE::I_LOG, 0, L"服务器正在连接。。。", 24);
#endif
    // this是ecx,HOOK的点已经有ecx了
    WinSock = this;
	bool b = (this->*_OnConnect)(ip, port);
	// 下方注释的代码时为了防止多次注入,导致虚函数地址不恢复问题导致死循环,通过一次性HOOK也能解决
	/*unsigned* vtable = (unsigned*)this;
	vtable = (unsigned*)vtable[0];
	union {
		unsigned value;
		bool(GameWinSock::* _proc)(char*, unsigned);
	} vproc;

	vproc._proc = _OnConnect;

	DWORD oldPro, backProc;
	VirtualProtect(vtable, 0x10x00, PAGE_EXECUTE_READWRITE, &oldPro);
	vtable[0x34 / 4] = vproc.value;
	VirtualProtect(vtable, 0x10x00, oldPro, &backProc);*/

    return b;
}

bool GameWinSock::OnSend(char* buff, unsigned len)
{
	
	/*
		这里就可以监控游戏发送的数据了
	*/

#ifdef  Anly
	anly->SendData(TTYPE::I_SEND, buff[0], buff, len);
#endif
	/*
		数据包的头只有一字节所以它的取值范围就是0x0-0xFF
	*/
	if (SendDealProc[buff[0]]((buff), len)) {// 执行失败不让游戏发送数据包
		return (this->*_OnSend)(buff, len);
	}
	else {// 发送失败屏蔽消息
		return true;// 屏蔽消息
	}

}

bool GameWinSock::OnRecving(char* buff, unsigned len)
{
	// MessageBoxA(0, "11111111111111", "0", MB_OK);
	/*
		监控游戏接收的数据包
	*/
#ifdef  Anly
	anly->SendData(TTYPE::I_RECV, buff[0], buff, len);
#endif
	return RecvDealProc[buff[0]](buff, len);
}

bool GameWinSock::OnRecv(char* buff, unsigned len)
{
//#ifdef  Anly
//	anly->SendData(1, buff, len);
//#endif
	return (this->*_OnRecv)(buff, len);
}

void GameWinSock::Init()
{
	for (int i = 0; i < 0x100; i++) {
		SendDealProc[i] = &DeafaultDeal;
		RecvDealProc[i] = &DeafaultDeal;
	}
	// 注册登录数据包处理函数
	// SendDealProc[I_LOGIN] = &Onlogin;
	// 注册数据登录失败数据包处理函数
	 RecvDealProc[S_LOGINFAIL] = &Onloginfailed;
	 RecvDealProc[S_LOGINOK] = &OnloginOk;
}

EnCode.cpp文件的修改:新加 GBYTE类实现operator重载函数、GSHORT类实现operator重载函数、GINT类实现operator重载函数、GFLOAT类实现operator重载函数、GINT64类实现operator重载函数、GDOUBLE类实现operator重载函数、GCHAR类实现operator重载函数、GUTF16类实现operator重载函数

#include "pch.h"
#include "EnCode.h"
#include "extern_all.h"



int EnCode::Init(char*& buff, char EnIndex)
{
	index = EnIndex;
	op = buff[0];
	int len = data_desc[index][op].lenth;
	/*
		比如 07 0A 00 00 00 34 00 33 00 39 00 39 00 00 00
		buff + 1 的结果是 0A 00 00 00 34 00 33 00 39 00 39 00 00 00
		len是 07 所代表的类型的长度,也就是0A 00 00 00 这个东西
		memcpy(dataPool, buff + 1, len);也就是把 0A 00 00 00 复制到 dataPool里
		然后从下方的if ((op == 0x7))得到它的字符串
	*/
	memcpy(dataPool, buff + 1, len);
	buff = buff + 1 + len;// 下一个结构

	if (index == 0) {
		if ((op == 0x7)) {
			if (pointer)delete[]pointer;
			/*
				dataPool 与 lenth是一个联合体,联合体的特性就是
				所有变量共用一个内存,所以 dataPool 的值就是lenth的值
				从上方的注释得知现在dataPool的值是0A 00 00 00
				然后通过 lenth去读 0A 00 00 00结果就是十进制的 10
				然后创建一个10字节的空间给pointer
				然后在通过 memcpy(pointer, buff, lenth); 把字符串赋值给pointer
			*/
			pointer = new char[lenth];
			memcpy(pointer, buff, lenth);
			buff = buff + lenth;// 指向下一个字符串
		}
	}

	if (index == 1) {
		if ((op == 0x06) || (op == 0x7)) {
			if (pointer)delete[]pointer;
			pointer = new char[lenth];
			memcpy(pointer, buff, lenth);
			buff = buff + lenth;
		}
	}
	return 0;
}
/*
	buff是数据包
	_len暂时没用
	ExIndex是解析方式,因为分析的时候发现6有时是char类型有时是char*类型
	看懂此方法需要分析手动分析一次数据包(数据解析约定的数据包)
	然后带着数据包去看这个函数
*/
EnCode::EnCode(char*& buff, unsigned int& _len,char EnIndex)
{
	Init(buff,EnIndex);
}

EnCode::~EnCode()
{
	if(pointer)
		delete[] pointer;
}

EnCode::EnCode()
{
}

GBYTE::operator char()
{
	return this->byte;
}

GSHORT::operator short()
{
	return this->stval;
}

GINT::operator int()
{
	return this->val;
}

GFLOAT::operator float()
{
	return this->fval;
}

GINT64::operator long long()
{
	return this->lval;
}

GDOUBLE::operator double()
{
	return this->dbval;
}

GCHAR::operator const char*()
{
	return this->pointer;
}

GUTF16::operator const wchar_t*()
{
	return (wchar_t*)this->pointer;
}

EnCode.h文件的修改:新加 GBYTE类、GSHORT类、GINT类、GFLOAT类、GINT64类、GDOUBLE类、GCHAR类、GUTF16类

#pragma once

class EnCode
{
private:

	char un[2]{};// 这个是为了内存对齐
public:
	char index = 0;
	// op + un[3]是四字节,op与buffer是6字节,如果自动内存对齐op与buffer可能不会挨在一起
	char op = 0;

	union
	{
		char dataPool[0x8];
		int lenth;
		char byte;
		short stval;
		int val;
		float fval;
		double dbval;
		long long lval = 0;
	};
	char* pointer = 0;
public:
	EnCode();
	EnCode(char*& buff, unsigned int& _len, char ExIndex = 0);
	int Init(char*& buff, char EnIndex = 0);
	~EnCode();
};

class GBYTE :public EnCode {
	using EnCode::EnCode;
public:
	operator char();
};
class GSHORT :public EnCode {
	using EnCode::EnCode;
public:
	operator short();
};
class GINT :public EnCode {
	using EnCode::EnCode;
public:
	operator int();
};
class GFLOAT :public EnCode {
	using EnCode::EnCode;
public:
	operator float();
};
class GINT64 :public EnCode {
	using EnCode::EnCode;
public:
	operator long long();
};
class GDOUBLE :public EnCode {
	using EnCode::EnCode;
public:
	operator double();
};
class GCHAR :public EnCode {
	using EnCode::EnCode;
public:
	operator const char*();
};
class GUTF16 :public EnCode {
	using EnCode::EnCode;
public:
	operator const wchar_t*();
};

NetClient.h文件的修改:放开 loginok函数声明

#pragma once
#include "NetClass.h"
class NetClient // 监视客户端每一个操作
{
public:
	/*
		模拟登陆的方法
		Id是账号
		Pass是密码
		它要基于发送的方法实现,因为我们没有连接socket的操作
	*/
	bool login(const char* Id, const char*Pass);
public:
	// 登陆失败,参数是错误码
	void loginfailed(int code);
	void loginok(ROLE_DATA*roles, int count);
};


NetClient.cpp文件的修改:新加 loginok函数

#include "pch.h"
#include "NetClient.h"
#include "extern_all.h"

bool NetClient::login(const char* Id, const char* Pass)
{
    const int bufflen = sizeof(DATA_LOGIN) + 1;
    char buff[bufflen];
    DATA_LOGIN data;
    // 有些操作系统这样写会报错,因为内存不对齐,现在Windows下没事
    //PDATALOGIN _data = (PDATALOGIN)(buff + 1);
    // 这样写就能解决内存对齐问题
    PDATALOGIN _data =&data;
    int len = strlen(Id);
    memcpy(_data->Id, Id, len);
    len = strlen(Pass);
    memcpy(_data->Pass, Pass, len);
    memcpy(buff+1, _data, sizeof(DATA_LOGIN));
    buff[0] = I_LOGIN;
    WinSock->OnSend(buff, sizeof(buff));
    return true;
}

void NetClient::loginfailed(int code)
{
    CString txt;
    if (code == 51001) {
        txt = L"登陆失败,易道云通行证不存在!";
    }else if (code == 51002) {
        txt = L"登录失败,密码错误!";
    }
    else txt = L"未定义错误!";

#ifdef  Anly
    anly->SendData(TTYPE::I_LOG, 0, txt.GetBuffer(), txt.GetLength()*2);
#endif
}

void NetClient::loginok(ROLE_DATA* roles, int count)
{
    CString txt;
    CString tmp;
    txt.Format(L"游戏登录成功!角色数量[%d]\r\n", count);
    for (int i = 0; i < count; i++)
    {
        tmp.Format(L"byte=%d\r\n", (char)roles[i].byte);
        txt += tmp;
        tmp.Format(L"un=%d\r\n", (int)roles[i].un);
        txt += tmp;
        tmp.Format(L"un1=%d\r\n", (int)roles[i].un1);
        txt += tmp;
        tmp.Format(L"name=%s\r\n", (const wchar_t*)roles[i].name);
        txt += tmp;
        tmp.Format(L"infos=%s\r\n", (const wchar_t*)roles[i].infos);
        txt += tmp;
        tmp.Format(L"un2=%d\r\n", (int)roles[i].un2);
        txt += tmp;
        tmp.Format(L"un3=%d\r\n", (long long)roles[i].un3);
        txt += tmp;
    }
    AfxMessageBox(txt);
}

NetClass.h文件的修改:新加 ROLE_DATA类

#pragma once
#include "EnCode.h"
/*
	数据包还原结构体要注意内存对齐,如果数据不满4字节,它字段会补齐
	比如结构体里有一个char变量,它是1字节,在内存里它可能会为了内存对齐
	让它变成4字节,所以这要注意
*/
// 登录数据
typedef struct DATA_LOGIN {
	int op = 0x0300;
	char buff[0x10]{};
	int lenId = 0x10;
	/*
		这个是登录的账号,它可能会变成0x20或更长,现在默认让它0x10
		读的时候以长度为准就好了
	*/
	char Id[0x10]{};
	int lenPass = 0x10;
	/*
		这个是登录的密码,它可能会变成0x20或更长,现在默认让它0x10
		读的时候以长度为准就好了
	*/
	char Pass[0x10]{};
	int lenCode = 0x10;
	char Code[0x10]{};
	int eop = 0x01;
}*PDATALOGIN;
typedef struct DATA_LOGIN_OK {// 登录成功数据包头
	int un[8] = { 0, 0, 0x76B, 0x0C, 0x1E,0, 0, 0 };
	int index = 0;
	int RoleCount = 0;
}*PDATALOGINOK;
typedef class ROLE_DATA {// 登录成功数据包
public:
	GBYTE byte;
	GINT un;
	GINT un1;
	GUTF16 name;
	GUTF16 infos;
	GINT un2;
	GINT64 un3;
}*PROLEDATA;


评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值