免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!
如果看不懂、不知道现在做的什么,那就跟着做完看效果
内容参考于:易道云信息技术研究院VIP课
上一个内容:32.网络数据分析原理与依据
码云地址(master 分支):https://gitee.com/dye_your_fingers/titan
码云版本号:57b7d17bb70c93cea427a82f1f3831b142164a3e
代码下载地址,在 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的分享
以 31.其它消息的实现与使用优化 它的代码为基础进行修改
数据包分析的基本原理:
1.数据包的组织结构有可能是数据约定和解码约定同时存在
2.看数据包最重要的突破口就是先分析可以分析出来的东西,也就是明文的内容,比如聊天里面的聊天信息登录里面的账号密码,游戏走路的时候的坐标
3.通过反复比对同一个操作的数据包,不断改变其中的变量,确定数据内的常量与变量,比如就登录这件事,可以不断的改变账号和密码,这就可以把里面的变量确定出来,比如账号密码时间戳等,不变的比如它的数据包头一些固定的常量,这就是反复比对同一个数据包
4.尝试先分析能够识别的内容,有些时候有些内容是可以分析出来的,有些内容是分析不出来的,分析不出来的东西,就是是在卡主分析不出来就先放着,把所有的数据包都分析一遍,它们之间是极有可能是有关联的,做安全就要有一个思维,就是你面对的不是计算机,你面对的是人,那么他(t她)在设计这些东西的时候往往都会再用跟之前类似的地方。
5.数据包里的常量可以不分析,比如说这个东西它就是固定的它不变,这种不变的东西就没必要看它到底是什么,因为它反正不会变,所以就不用分析
开始分析,登录:
首先打开我们的工具与注入,tiStart.exe与DataAnly.exe,百度网盘里有,这俩东西
然后使用tiStart.exe启动游戏,然后在游戏里输入123,账号密码都输入的123,然后点登录,然后就在DataAnly.exe里收到了数据,如下图
这个登录数据包是0203什么什么
虽然有搜索功能但是不用搜索就能看到,123,如下图
但是不知道哪个是密码哪个是账号,所以接下来把账号输入成1234,密码写成123,如下图,这样就可以看出上面的是账号下面的是密码
然后比对数据包
然后就会发现其它的位置都没有变化,只有下面两个图红框里的内容有变化,所以这样就可以确定它们就是账号密码了
然后分析一下其它的数据
下图中的10是十六进制,换成十进制就是16,然后10后面的数据也正好是16,所以可以猜测这个10是长度
然后02 00 03 00可能是操作类型,这个需要登录进游戏,对比其它数据包,这里先不管
然后下图红框位置没有数据,但是前面有一个10,可以猜测它是预留的接口,比如用来存放验证码
然后01不知道什么意思
然后把账号密码输入的内容超过16看看数据包有什么变化
发现长度变成了十六进制的20
然后在给它拆分一下,至于为什么这样拆分,是感觉,数字基本是4字节,状态、类型都可以用数字,下图中的02也可以分析成02 00两字节,但是考虑到这是数据包的头部,这里就假设它是1字节
然后又由于它的数据长度可能默认是16,00 03 00 00 后面的数据直到下一个代表长度的数据,也就是这里的20,这些数据长度是16,如下图红框位置,它有16个字节,这也是把02猜测成1字节的原因
然后后面的数据,仅仅只是满足于字符串的形式,前面跟着一个数字,后面是数据,这个数字代表了后面数据的长度,字符串一般都会这样,这种形式要牢牢记住。
然后下图红框的 01 00 00 00 这个就不知道是什么了,只能把01改成00或者其它数字,给服务端发送一个数据包试一试,这种就属于可能永远不知道是什么的数据
然后到这不见得分析的数据是准确的,对于利用肯定是够了,但是完整的去看这个数据包目前来讲不见得是准确的
比如下图红框位置是不是这样的拆分,这种就确定不了,它也有可能有什么系统性的方法,这东西确定不了,确定不了就算,后面发现了在看,将来看的数据包多了就能发现了就能确定了
然后根据现在得到的信息,写出它的数据类型,可以发现少了头部的0x02,这个0x02是数据包头,数据包头不属于数据
int = 0x300;
char un[0x10]{};
int lenId;
char id[lenId];
int lenPs;
char Pass[lenPs];
int lenun;
char bun[lenun];
int un;
然后登陆进游戏多看几个数据包,删除一个角色的数据包
删除的数据包,看着很复杂
然后用工具(DataAnly.exe)推测一下数据
可以看到下图中选择的6个字节是美美美这三个字,这三个字是刚刚的角色名,然后数据包的头部是一个6,这个6加上美美美这三个字的长度,可以猜测这个6是名字的长度,然后后面的内容暂时不知道是什么内容
为了确定这个6是长度,还是仅仅是数据包的头,接下来创建一个更长的名字,现在的名字有四个字,应该是8个字节才对
然后看出这个6并不是名字长度,它仅仅是数据包的头
现在可以确定数据包的头是一字节,删除角色操作的数据包是06
然后在看一个登陆的
然后可以发现,登陆的角色的数据包头是4,然后后面是登陆的角色名字
它这个东西自己写的很乱是不太现实的,所以它就会把第一个字节做成数据包的头,它不可能第一个消息用04当头,第个消息用06当头,这样写的代码它自己都会难受,所以正常来讲它一定是有一个固定的方法,这个东西可以分析的原理就是这个,现在的很简单,实际的肯定会遇到各种各样的问题,遇到问题之后,就要想对方是怎样解决这个问题的,当年它写代码是怎样处理这个问题的,这个数据包它乱组织一段怎么处理,这个核心问题还是跟人有关的一个事情。
然后游戏退出之后发现它还会拿发送数据包,如下图红框,这时的数据它是发送失败的,使用x96dbg在send函数里打断点是断不下来的,这就很明显说明发送失败了
然后接下来看登录的接收数据,接收的数据是03 39 C7 00 00
现在的登录状态是通行证不存在,假设03是返回的数据包头,C739是错误码,也就是51001是通行证不存在,数据包里明明是39 C7,为什么要写成C739,是因为大端序小端序的问题,随便百度大端序小端序就也可以找到答案
然后是通行证存在密码不正确,是C73A
51002是密码错误
然后写c++代码
首先创建一个类,类名是 NetClient,监视客户端每一个操作
NetClient.cpp文件的内容
cpp
#include "pch.h"
#include "NetClient.h"
void NetClient::logfailed(int code)
{
}
NetClient.h文件的内容
cpp
#pragma once
class NetClient // 监视客户端每一个操作
{
public:
/*
模拟登陆的方法
Id是账号
Pass是密码
它要基于发送的方法实现,因为我们没有连接socket的操作
*/
bool login(const char* Id, const char*Pass);
public:
// 登陆失败,参数是错误码
void logfailed(int code);
};
GameProc.cpp文件的修改:修改了 Init函数
cpp
#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函数
cpp
#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函数
cpp
#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();
};