免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!
如果看不懂、不知道现在做的什么,那就跟着做完看效果
现在的代码都是依据数据包来写的,如果看不懂代码,就说明没看懂数据包
内容参考于: 易道云信息技术研究院VIP课
上一个内容:43.角色创建功能的数据包初步分析
码云地址(master 分支):https://gitee.com/dye_your_fingers/titan
码云版本号:073c2dd3c5f53f237037dd1cea1b1d4c456291ff
代码下载地址,在 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的分享
以 43.角色创建功能的数据包初步分析 它的代码为基础进行修改
创建角色
点击下图红框里的东西,也就是创建角色,会给服务端发送 03 01 00 00 00数据包
发送了03 01 00 00 00数据包之后,服务端会返回一个数据包,也就是下图里的内容,下图红框里是字符串,出了1-3字节,其余的都是字符串,05是数据包头,01当做short类型解读也即是读1字节数据
接下来使用程序把字符串解读出来,上方数据包解读翻译的内容如下图,看不出是什么来,它就先不分析了
分析阵营选择发送的数据包,如下图
对它的分析,然后0A开头的数据包,进入游戏之后会有很多,下图中的00 0A 00 00第一个00是我自己补的,为了看起来方便,之后见到00 0A 00 00也是这个意思
如下图进入游戏之后,基本上发送的数据包都是0A开头
随便找的一个0A开头的数据包,可以满足上方拆分的结果
下图应该是移动时发送的数据包,因为有多个浮点数
然后这些0A开头的数据包,前23字节都是固定的结构
只有下图红框位置的数据会根据后方数据的个数而变,但总之结构还是固定的,所以前23字节可以用结构体处理,为了内存对齐结构体定义时应该是24字节,要在0A前面补一个0
然后经过多个数据包的分析,下图红框的02 0F位置的东西应该是操作类型,就是用来表示这是一个什么操作
发送聊天数据的数据包,它是02 01每次长度后面好像跟的都是02
长度后面的数据,为了后续方便使用,现在把它们称作为数据参数,长度称为数据参数的个数,如下图
当前的代码搭建了,解析数据参数的框架
总结:现在得到的东西,下方的数据参数总结,分别在上方的某个图中可以看到真实数据
数据参数07开头是wchar_t*类型,也就是宽字节字符串,07开头后面的数据是字符串的长度
数据参数06开头是char*类型,也就是单字节字符串,06开头后面的数据是字符串的长度
数据参数04开头是浮点数,也就是float
数据参数03开头是一个数字,也就是long long类型
数据参数02开头是一个数字,也就是int类型
GameWinSock.h文件的修改:新加 I_SEND_CUSTOM宏、S_CREATEROLE_START宏
#pragma once #define I_LOGIN 0x2 // 登录 #define I_CREATEROLE_START 0x3 // 创建角色发送数据包的头 #define I_DELROLE 0x6 // 删除角色发送数据包的头 #define I_SEND_CUSTOM 0x0A // 进入游戏之后大部分数据包的头 #define S_TIPS 0x3 // 服务端返回错误的数据包头 #define S_LOGINOK 0x4 // 登录成功的返回数据包的头 // 创建角色服务端反馈的数据(发送03 01 00 00 00这个数据包服务端返回的数据包头) #define S_CREATEROLE_START 0x05 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文件的修改:新加 OnSendCustom函数、OnSvrStartCreateRole函数
#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(); } 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("%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; } } } 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 }
NetClass.h文件的修改:新加NET_SEND_HEAD结构体
#pragma once #include "EnCode.h" // 发送数据标准头 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文件的修改:新加 OnScrStartCreateRole函数声明
#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); public: // 处理失败,参数是错误码 bool virtual Tips(int code); void virtual loginok(ROLE_DATA* _roles, int count); bool virtual OnScrStartCreateRole(short code,wchar_t* _txt); };
NetClient.cpp文件的修改:新加 OnScrStartCreateRole函数
#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::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; }