免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!
如果看不懂、不知道现在做的什么,那就跟着做完看效果
内容参考于:易道云信息技术研究院VIP课
上一个内容:37.解码器细化类的实现
码云地址(master 分支):https://gitee.com/dye_your_fingers/titan
码云版本号:ccc3dd3b76e4f0fd13f8fc86cb062606004db2bd
代码下载地址,在 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的分享
以 37.解码器细化类的实现 它的代码为基础进行修改
解码器这个东西,具体还要看游戏中解析约定的内容多不多,如果不多这个解码的功能就亏了
效果图:
把下方内容复制出去,加个头加个尾就是一个完整的结构体了,比如class aaa{复制的内容}这样
![]()
比如
class a{ GBYTE none;//0 GINT none;//0 GINT none;//0 GUTF16 none;//[4399] GUTF16 none;//[1,1;12,21031;13,21039;15,;17,;18,;19,;24,122011;26,112011;27,132011;28,152011;29,142011;30,;35,1;36,1;37,1;38,1;39,;41,;42,289041;44,13352902839408;45,TY_Sword_0001;46,;53,22131;54,22132;55,22140;56,10400000;57,;58,;59,101011;60,;61,0;62,;63,0;64,0;65,296018;66,16777215;] GINT none;//0 GINT64 none;//64d }
NetClient.cpp文件的修改:
#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)
{// 入参 roles 存在内存泄漏
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", roles[i].byte.value());
txt += tmp;
tmp.Format(L"un=%d\r\n", roles[i].un.value());
txt += tmp;
tmp.Format(L"un1=%d\r\n", roles[i].un1.value());
txt += tmp;
tmp.Format(L"name=%s\r\n", roles[i].name.value());
txt += tmp;
tmp.Format(L"infos=%s\r\n", roles[i].infos.value());
txt += tmp;
tmp.Format(L"un2=%d\r\n", roles[i].un2.value());
txt += tmp;
tmp.Format(L"un3=%d\r\n", roles[i].un3.value());
txt += tmp;
}
AfxMessageBox(txt);
}
GameWinSock.h文件的修改:新加 AnlyBuff函数
#pragma once
#define I_LOGIN 0x2 // 登录
#define S_LOGINFAIL 0x3 // 登录失败的返回数据包的头
#define S_LOGINOK 0x4 // 登录成功的返回数据包的头
class GameWinSock
{
typedef bool(GameWinSock::* PROC)(char*, unsigned);
public:
unsigned* vatble;// 虚函数表
unsigned un[17];
unsigned RecvPoint; // 游戏recv之后调用处理一个数据包函数时的eax,这里偏移是0x48
public:
static PROC _OnConnect;
static PROC _OnSend;
static PROC _OnRecv;
bool OnConnect(char* ip, unsigned port);
bool OnSend(char* buff, unsigned len);
bool OnRecving(char* buff, unsigned len);
bool OnRecv(char* buff, unsigned len);
void Init();
// end为了避免超长解读
void AnlyBuff(char* start, char* end, char index = 0);
};
GameWinSock.cpp文件的修改:新加 AnlyBuff函数,修改了 OnloginOk函数
#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) {
PDATALOGINOK _p = (PDATALOGINOK)&buff[1];
ROLE_DATA* roleDatas = nullptr;
if (_p->RoleCount > 0) {
char* buffStart = buff + 1 + sizeof(DATA_LOGIN_OK);
WinSock->AnlyBuff(buffStart, buff + len);
roleDatas = new ROLE_DATA[_p->RoleCount];
for (int i = 0; i < _p->RoleCount; i++)
{
roleDatas[i].byte = buffStart;
roleDatas[i].un = buffStart;
roleDatas[i].un1 = buffStart;
roleDatas[i].name = buffStart;
roleDatas[i].infos = buffStart;
roleDatas[i].un2 = buffStart;
roleDatas[i].un3 = buffStart;
}
Client->loginok(roleDatas, _p->RoleCount);
}
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;
}
// 它会生成一个结构体,详情看效果图
void GameWinSock::AnlyBuff(char* start, char* end, char index)
{
#ifdef Anly
CStringA txt;
CStringA tmp;
CString utmp;
EnCode _coder;
GBYTE* _bytecoder;
GSHORT* _shortcoder;
GINT* _intcoder;
GFLOAT* _floatcoder;
GDOUBLE* _doublecoder;
GCHAR* _asccoder;
GUTF16* _utfcoder;
GINT64* _int64coder;
CStringA _opname = data_desc[_coder.index][_coder.op].name;
while (start < end) {
_coder.Init(start, index);
// _opname.MakeLower()是变为小写字母,会影响 _opname它的值
// 所以又写了一边 data_desc[_coder.index][_coder.op].name
tmp.Format("%s %s;//", data_desc[_coder.index][_coder.op].name, _opname.MakeLower());
txt = txt + tmp;
if (_coder.index == 0) {
switch (_coder.op)
{
case 1:
_shortcoder = (GSHORT*)&_coder;
tmp.Format("%d\r\n", _shortcoder->value());
txt = txt + tmp;
break;
case 2:
_intcoder = (GINT*)&_coder;
tmp.Format("%d\r\n", _intcoder->value());
txt = txt + tmp;
break;
case 4:
_floatcoder = (GFLOAT*)&_coder;
tmp.Format("%d\r\n", _floatcoder->value());
txt = txt + tmp;
break;
case 6:
_bytecoder = (GBYTE*)&_coder;
tmp.Format("%d\r\n", _bytecoder->value());
txt = txt + tmp;
break;
case 7:
_utfcoder = (GUTF16*)&_coder;
utmp.Format(L"[%s]\r\n", _utfcoder->value());
tmp = utmp;
txt = txt + tmp;
break;
case 3:
case 5:
case 8:
_int64coder = (GINT64*)&_coder;
tmp.Format("%.i64d\r\n", _int64coder->value());
txt = txt + tmp;
break;
default:
break;
}
}
}
// 长度24的原因,它是宽字节要,一个文字要2个字节,一共是10个文字加上结尾的0是11个
// 所以 11 乘以2,然后再加2
anly->SendData(TTYPE::I_DIS, 0, txt.GetBuffer(), txt.GetAllocLength() + 1);
#endif
// 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
}
extern_all.cpp文件的修改:修改了 init_datadesc函数
/*
此文件是用来存放全局变量、全局函数(通用函数)
*/
#include "pch.h"
#include "extern_all.h"
GameWinSock* WinSock = nullptr;
GameProc* PGameProc = nullptr;
NetClient* Client = nullptr;
#ifdef Anly
CAnly* anly = nullptr;
#endif
DATA_DESC data_desc[2][9]{};
void init_datadesc() {
data_desc[0][0] = { "none", 0 };
data_desc[0][1] = { "GSHORT", 2 };
data_desc[0][2] = { "GINT", 4 };
data_desc[0][3] = { "GINT64", 8 };
data_desc[0][4] = { "GFLOAT", 4 };
data_desc[0][5] = { "GINT64", 8 };
data_desc[0][6] = { "GBYTE", 1 };
data_desc[0][7] = { "GUTF16", 4 };
data_desc[0][8] = { "GINT64", 8 };
data_desc[1][0] = { "none", 0 };
data_desc[1][1] = { "GSHORT", 2 };
data_desc[1][2] = { "GINT", 4 };
data_desc[1][3] = { "GINT64", 8 };
data_desc[1][4] = { "GFLOAT", 4 };
data_desc[1][5] = { "GINT64", 8 };
data_desc[1][6] = { "GCHAR", 4 };
data_desc[1][7] = { "GUTF16", 4 };
data_desc[1][8] = { "GINT64", 8 };
}
void InitClassProc(LPVOID proc_addr, unsigned value)
{
unsigned* writer = (unsigned*)proc_addr;
writer[0] = value;
}
EnCode.h文件的修改:实现 value、operator=函数(只有EnCode类实现了,其余类都是声明它,实现用的是父类的也就是EnCode类的实现)
#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()
{
}
EnCode& EnCode::operator=(char*& buff)
{
Init(buff);
return *this;
}
GBYTE::operator char()
{
return this->byte;
}
char GBYTE::value()
{
return this->byte;
}
GSHORT::operator short()
{
return this->stval;
}
short GSHORT::value()
{
return this->stval;
}
GINT::operator int()
{
return this->val;
}
int GINT::value()
{
return this->val;
}
GFLOAT::operator float()
{
return this->fval;
}
float GFLOAT::value()
{
return this->fval;
}
GINT64::operator long long()
{
return this->lval;
}
long long GINT64::value()
{
return this->lval;
}
GDOUBLE::operator double()
{
return this->dbval;
}
double GDOUBLE::value()
{
return this->dbval;
}
GCHAR::operator const char*()
{
return this->pointer;
}
const char* GCHAR::value()
{
return this->pointer;
}
GUTF16::operator const wchar_t*()
{
return (wchar_t*)this->pointer;
}
const wchar_t* GUTF16::value()
{
return (wchar_t*)this->pointer;
}
EnCode.h文件的修改:新加 operator=函数、value函数(只有G开头的类里加的)
#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);
EnCode& operator=(char*& buff);
int Init(char*& buff, char EnIndex = 0);
~EnCode();
};
class GBYTE :public EnCode {
public:
using EnCode::EnCode;
using EnCode::operator=;
operator char();
char value();
};
class GSHORT :public EnCode {
public:
using EnCode::EnCode;
using EnCode::operator=;
operator short();
short value();
};
class GINT :public EnCode {
public:
using EnCode::EnCode;
using EnCode::operator=;
operator int();
int value();
};
class GFLOAT :public EnCode {
public:
using EnCode::EnCode;
using EnCode::operator=;
operator float();
float value();
};
class GINT64 :public EnCode {
public:
using EnCode::EnCode;
using EnCode::operator=;
operator long long();
long long value();
};
class GDOUBLE :public EnCode {
public:
using EnCode::EnCode;
using EnCode::operator=;
operator double();
double value();
};
class GCHAR :public EnCode {
public:
using EnCode::EnCode;
using EnCode::operator=;
operator const char*();
const char* value();
};
class GUTF16 :public EnCode {
public:
using EnCode::EnCode;
using EnCode::operator=;
operator const wchar_t*();
const wchar_t* value();
};