免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!
如果看不懂、不知道现在做的什么,那就跟着做完看效果
内容参考于:易道云信息技术研究院VIP课
上一个内容:32.网络数据分析原理与依据
码云地址(master 分支):https://gitee.com/dye_your_fingers/titan
码云版本号:57b7d17bb70c93cea427a82f1f3831b142164a3e
代码下载地址,在 titan 目录下,文件名为:titan-游戏登录数据包分析利用.zip
HOOK引擎,文件名为:黑兔sdk升级版.zip
以 31.其它消息的实现与使用优化 它的代码为基础进行修改
数据包分析的基本原理:
开始分析,登录:
首先打开我们的工具与注入,tiStart.exe与DataAnly.exe,百度网盘里有,这俩东西
然后使用tiStart.exe启动游戏,然后在游戏里输入123,账号密码都输入的123,然后点登录,然后就在DataAnly.exe里收到了数据,如下图
这个登录数据包是0203什么什么
虽然有搜索功能但是不用搜索就能看到,123,如下图
但是不知道哪个是密码哪个是账号,所以接下来把账号输入成1234,密码写成123,如下图,这样就可以看出上面的是账号下面的是密码
然后比对数据包
然后分析一下其它的数据
然后把账号密码输入的内容超过16看看数据包有什么变化
然后在给它拆分一下,至于为什么这样拆分,是感觉,数字基本是4字节,状态、类型都可以用数字,下图中的02也可以分析成02 00两字节,但是考虑到这是数据包的头部,这里就假设它是1字节
然后到这不见得分析的数据是准确的,对于利用肯定是够了,但是完整的去看这个数据包目前来讲不见得是准确的
比如下图红框位置是不是这样的拆分,这种就确定不了,它也有可能有什么系统性的方法,这东西确定不了,确定不了就算,后面发现了在看,将来看的数据包多了就能发现了就能确定了
然后根据现在得到的信息,写出它的数据类型,可以发现少了头部的0x02,这个0x02是数据包头,数据包头不属于数据
然后登陆进游戏多看几个数据包,删除一个角色的数据包
删除的数据包,看着很复杂
然后用工具(DataAnly.exe)推测一下数据
然后在看一个登陆的
它这个东西自己写的很乱是不太现实的,所以它就会把第一个字节做成数据包的头,它不可能第一个消息用04当头,第个消息用06当头,这样写的代码它自己都会难受,所以正常来讲它一定是有一个固定的方法,这个东西可以分析的原理就是这个,现在的很简单,实际的肯定会遇到各种各样的问题,遇到问题之后,就要想对方是怎样解决这个问题的,当年它写代码是怎样处理这个问题的,这个数据包它乱组织一段怎么处理,这个核心问题还是跟人有关的一个事情。
然后游戏退出之后发现它还会拿发送数据包,如下图红框,这时的数据它是发送失败的,使用x96dbg在send函数里打断点是断不下来的,这就很明显说明发送失败了
然后接下来看登录的接收数据,接收的数据是03 39 C7 00 00
然后写c++代码
首先创建一个类,类名是 NetClient,监视客户端每一个操作
NetClient.cpp文件的内容
#include "pch.h"
#include "NetClient.h"
void NetClient::logfailed(int code)
{
}
NetClient.h文件的内容
#pragma once
class NetClient // 监视客户端每一个操作
{
public:
/*
模拟登陆的方法
Id是账号
Pass是密码
它要基于发送的方法实现,因为我们没有连接socket的操作
*/
bool login(const char* Id, const char*Pass);
public:
// 登陆失败,参数是错误码
void logfailed(int code);
};
GameProc.cpp文件的修改:修改了 Init函数
#include "pch.h"
#include "GameProc.h"
#include "extern_all.h"
// typedef bool(GameWinSock::* U)(char*, unsigned);
bool _OnRecv(HOOKREFS2) {
unsigned* _esp = (unsigned*)_ESP;
_EAX = WinSock->RecvPoint;
WinSock->OnRecving((char*)_esp[1], _esp[2]);
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);
return true;
}
GameProc::GameProc()
{
hooker = new htd::hook::htdHook2();
Init();
InitInterface();
}
void GameProc::LoadBase()
{
LoadLibraryA("fxnet2.dll");
}
void GameProc::Init()
{
#ifdef anly
anly = new CAnly();
#endif
/*
这里的 WinSock 是0没有创建对象,但是还是能调用Init函数
这是因为Init函数里面没有用到this,没用到就不会报错
*/
WinSock->Init();
}
void GameProc::InitInterface()
{
LoadBase();
// MessageBoxA(0, "1", "1", MB_OK);
// 只会HOOK一次,一次性的HOOK
hooker->SetHook((LPVOID)0x10617046, 0x1, _OnConnect, 0, true);
/*
第一个参数是HOOK的位置
第二个参数是HOOK的位置的汇编代码的长度(用于保证执行的汇编代码完整)
第三个参数是HOOK之后当游戏执行到第一个参数的位置的时候跳转的位置
*/
hooker->SetHook((LPVOID)0x10618480, 0x1, _OnRecv);
/*
在这里绑定游戏处理数据包函数(0x10618480函数)
然后0x10618480函数在上面一行代码已经进行了HOOK
所以在调用_OnRecv函数指针时,它就会进入我们HOOK
*/
InitClassProc(&GameWinSock::_OnRecv, 0x10618480);
}
GameWinSock.cpp文件的修改:修改了 OnSend函数、OnRecving函数,新加 DealProc函数指针、SendDealProc函数指针变量、RecvDealProc函数指针变量、Init函数
#include "pch.h"
#include "GameWinSock.h"
#include "extern_all.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 GameWinSock::OnConnect(char* ip, unsigned port)
{
#ifdef Anly
anly->SendData(TTYPE::I_DIS, 0, ip, 16);
// 长度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 + 1), len - 1)) {// 执行失败不让游戏发送数据包
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, 0, buff, len);
#endif
return RecvDealProc[buff[0]](buff + 1, len - 1);
}
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;
}
}
GameWinSock.h文件的修改:新加 Init函数
#pragma once
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();
};