免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!
如果看不懂、不知道现在做的什么,那就跟着做完看效果
现在的代码都是依据数据包来写的,如果看不懂代码,就说明没看懂数据包
内容参考于: 易道云信息技术研究院VIP课
上一个内容:44.角色创建服务器反馈数据包分析
码云地址(master 分支):https://gitee.com/dye_your_fingers/titan
码云版本号:75c763a02e224b8d293d71284044da98f53e2c19
代码下载地址,在 titan 目录下,文件名为:titan-解码发送数据内容输出到日志.zip
HOOK引擎,文件名为:黑兔sdk升级版.zip
以 44.角色创建服务器反馈数据包分析 它的代码为基础进行修改
根据上一个内容游戏登录之后的操作基本上都是0A开头,然后里面有数据参数个数,其中数据参数里就有02开头的数据,现在做的是把这些数据参数全部解读出来,打印到一个文件里,这样将来看这个文件的时候就能知道有多少种规则,大概都是怎样的
效果图:
效果图:
EnCode.h文件的修改:修改了 EnCode结构体
#pragma once
/*
EnCode结构体
un[6]、index、op是8字节
union里也是8字节
pointer、back它俩虽然是char类型,但它俩是指针类型,所以它们一共是8字节
EnCode结构体一共是24字节
*/
class EnCode// 用于表示数据解析约定的数据
{
private:
char un[6]{};//
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;
char* back = 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();
};
NetClass.h文件的修改:新加 SC_CHOOSECAMP宏
#pragma once
#include "EnCode.h"
// 发送数据操作码定义
#define SC_CHOOSECAMP 591 // 阵营选择操作码
// 发送数据标准头
typedef struct NET_SEND_HEAD {
// 为了内存对齐,当前结构不可能超过255,所以这样写是没问题的
char len = sizeof(NET_SEND_HEAD) - 1;
char op = 0x0A;
short unst = 0;
int unnt[4]{};
short unst1 = 0;
short count = 0;
}*PNET_SEND_HEAD;
// 申请创建角色结构体
typedef struct NET_CREATEROLE_START {
char un[0x02];// 用来做内存对齐
char len = sizeof(NET_CREATEROLE_START) - 3;// 用来做内存对齐
char op = 0x03;
int code = 0x01;
}*PNET_CREATEROLE_START;
// 删除角色数据结构
typedef struct DATA_DELROLE {
char op;
char buff[0x1F];
int len;
int un[8] = { 0x01 , 0x03};
}*PDATADELROLE;
/*
数据包还原结构体要注意内存对齐,如果数据不满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;
NetClient.h文件的修改:新加 OnSendCustom函数、DecodeChooseCamp函数
#pragma once
#include "NetClass.h"
class NetClient // 监视客户端每一个操作
{
private:
PROLEDATA roles;
unsigned rolecount;
bool logined = false;
bool DelRole(wchar_t* rolename, unsigned _len);
public:
/*
模拟登陆的方法
Id是账号
Pass是密码
它要基于发送的方法实现,因为我们没有连接socket的操作
*/
bool login(const char* Id, const char* Pass);
bool DelRole(wchar_t* rolename);
bool StartCreateRole();// 用于创建角色
// 根据角色名字获取一个登录成功数据包(选择角色列表里的一个数据)
PROLEDATA GetRoleByName(wchar_t* rolename);
public:
// 用于拦截游戏删除角色功能
bool virtual OnDelRole(wchar_t* rolename, unsigned _len);
// 用于拦截游戏登录功能
void virtual Onlogin(const char* Id, const char*Pass);
// 用于拦截游戏创建角色功能
bool virtual OnStartCreateRole(int code);
// opcode意思是操作码,count意思是数量,buffStart意思是解码的内容开始,buffend意思是解码的内容结束,buffer是原始的数据,len是原始数据的长度
// char& buffer, int& len这俩参数带&的原因是,在 OnSendCustom 里进行修改之后,通过&的方式传递回去
bool virtual OnSendCustom(int opcode, short count, char* buffStart, char* buffend, char*& buffer, unsigned& len);
public:
bool virtual DecodeChooseCamp(char* buffStart, char* buffend, short count);
public:
// 处理失败,参数是错误码
bool virtual Tips(int code);
void virtual loginok(ROLE_DATA* _roles, int count);
bool virtual OnScrStartCreateRole(short code,wchar_t* _txt);
};
NetClient.cpp文件的修改:新加 OnSendCustom函数、DecodeChooseCamp函数
#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;
return WinSock->OnSend(buff, sizeof(buff));
}
bool NetClient::DelRole(wchar_t* rolename)
{
PROLEDATA _role = GetRoleByName(rolename);
if (_role == nullptr) {
return false;
}
else {
return DelRole(rolename, _role->name.lenth);
}
return false;
}
bool NetClient::StartCreateRole()
{
NET_CREATEROLE_START _data;
return WinSock->OnSend(&_data.op, _data.len);
}
PROLEDATA NetClient::GetRoleByName(wchar_t* rolename)
{
//PROLEDATA result = nullptr;
for (int i = 0; i < rolecount; i++)
{
// StrCmpW判断两个字符串是否相同
// 比较时区分大小写,如果字符串相同返回0
if (StrCmpW(roles[i].name.value(), rolename) == 0) {
return &roles[i];
}
}
return nullptr;
}
bool NetClient::DelRole(wchar_t* rolename, unsigned _len)
{
DATA_DELROLE _data;
_data.op = 0x06;
_data.len = _len;
memcpy(_data.buff, rolename, _len);
return WinSock->OnSend((char*)&_data, sizeof(DATA_DELROLE) - 1);
}
bool NetClient::OnDelRole(wchar_t* rolename, unsigned _len)
{
AfxMessageBox(rolename);
return true;
}
void NetClient::Onlogin(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;
return WinSock->OnSend(buff, sizeof(buff));
*/
}
bool NetClient::OnStartCreateRole(int code)
{
return true;
}
bool NetClient::OnSendCustom(int opcode, short count, char* buffStart, char* buffend, char*& buffer, unsigned& len)
{
switch (opcode)
{
case SC_CHOOSECAMP:
return DecodeChooseCamp(buffStart, buffend, count);
break;
default:
break;
}
return true;
}
bool NetClient::DecodeChooseCamp(char* buffStart, char* buffend, short count)
{
GCHAR coder;
coder.Init(buffStart, 1);
return true;
}
bool NetClient::Tips(int code)
{
#ifdef Anly
CString txt;
if (code == 51001) {
txt = L"登陆失败,易道云通行证不存在!";
}else if (code == 51002) {
txt = L"登录失败,密码错误!";
}
else {
txt.Format(L"未知登录错误:%d", code);
}
anly->SendData(TTYPE::I_LOG, 0, txt.GetBuffer(), txt.GetLength()*2);
#endif
return true;
}
void NetClient::loginok(ROLE_DATA* _roles, int count)
{
logined = true;
if(roles) delete[] roles;
roles = _roles;
rolecount = count;
}
bool NetClient::OnScrStartCreateRole(short code, wchar_t* _txt)
{
return true;
}
GameWinSock.cpp文件的修改:修改了 OnSendCustom函数、AnlyBuff函数
#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) {
PDATALOGIN _data = (PDATALOGIN)(buff + 1);
char* _id = _data->Id;
_data = (PDATALOGIN)(buff + 1 + _data->lenId - 0x10);
char* _pass = _data->Pass;
Client->Onlogin(_id, _pass);
/* 修改账号密码
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 OnTips(char*& buff, unsigned& len) {
int* code = (int*)&buff[1];
return Client->Tips(code[0]);
}
bool OnDelRole(char*& buff, unsigned& len) {
PDATADELROLE p = (PDATADELROLE)buff;
return Client->OnDelRole((wchar_t*)(p->buff), p->len);
// 返回值改为false将拦截发送的删除角色数据包
// 详情看注册 OnDelRole 函数的位置,Init函数
// 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 OnStartCreateRole(char*& buff, unsigned& len){
// 申请进入创建角色界面
int* code = (int*)&buff[1];
return Client->OnStartCreateRole(code[0]);
}
bool OnSendCustom(char*& buff, unsigned& len) {
PNET_SEND_HEAD head = (PNET_SEND_HEAD)(buff - 1);
int icount = head->count;
char* buffStart = (char*)head + sizeof(NET_SEND_HEAD);
if (buffStart[0] != 0x02) {
#ifdef Anly
anly->SendData(TTYPE::I_DIS, I_SEND_CUSTOM, "SEND_CUSTOM 发现异常数据", 25);
#endif
return true;
}
// 解析一个长度后面的数据
EnCode coder(buffStart, len, 1);
GINT* intCoder = (GINT*)&coder;
// 得到数据
int opcode = intCoder->value();
#ifdef Anly
WinSock->AnlyBuff(buffStart, buff + len, 1);
#endif
return Client->OnSendCustom(opcode, icount - 1, buffStart, buff + len, buff, len);
}
bool OnSvrStartCreateRole(char*& buff, unsigned& len) {
short* _st = (short*)&buff[1];
wchar_t* _txt = (wchar_t*)&buff[3];
#ifdef Anly
CString txt;
CStringA txtA;
txt.Format(L"code:%d\r\n%s", _st[0], _txt);
txtA = txt;
//AfxMessageBox(txtA);
anly->SendData(TTYPE::I_DIS, S_CREATEROLE_START, txtA.GetBuffer(), txt.GetAllocLength() + 1);
#endif
return Client->OnScrStartCreateRole(_st[0], _txt);
}
// 这个函数拦截了游戏的连接
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;
SendDealProc[I_DELROLE] = &OnDelRole;
SendDealProc[I_CREATEROLE_START] = &OnStartCreateRole;
SendDealProc[I_SEND_CUSTOM] = &OnSendCustom;
// 注册数据登录失败数据包处理函数
RecvDealProc[S_TIPS] = &OnTips;
RecvDealProc[S_LOGINOK] = &OnloginOk;
RecvDealProc[S_CREATEROLE_START] = &OnSvrStartCreateRole;
}
// 它会生成一个结构体,详情看效果图
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("%f\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;
// 5号之前分析的忘记截图了,现在找不到它的数据包了,如果后面再见到05的时候再详细补充说明
// 之前的分析05就是double类型
case 5:
_doublecoder = (GDOUBLE*)&_coder;
tmp.Format("%lf\r\n", _doublecoder->value());
txt = txt + tmp;
break;
case 8:
case 3:
_int64coder = (GINT64*)&_coder;
tmp.Format("%l64d\r\n", _int64coder->value());
txt = txt + tmp;
break;
default:
break;
}
}
if (_coder.index == 1) {
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("%f\r\n", _floatcoder->value());
txt = txt + tmp;
break;
case 6:
_asccoder = (GCHAR*)&_coder;
tmp.Format("%s\r\n", _asccoder->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 5:
_doublecoder = (GDOUBLE*)&_coder;
tmp.Format("%lf\r\n", _doublecoder->value());
txt = txt + tmp;
break;
case 8:
case 3:
_int64coder = (GINT64*)&_coder;
tmp.Format("%l64d\r\n", _int64coder->value());
txt = txt + tmp;
break;
default:
break;
}
}
}
anly->SendData(TTYPE::I_DIS, 0, txt.GetBuffer(), txt.GetAllocLength() + 1);
#endif
}