当前位置: 首页 > news >正文

33.网络游戏逆向分析与漏洞攻防-游戏网络通信数据解析-游戏登录数据包分析利用

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!

如果看不懂、不知道现在做的什么,那就跟着做完看效果

内容参考于:易道云信息技术研究院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文件的内容

#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  anlyanly = new CAnly();
#endif/*这里的 WinSock 是0没有创建对象,但是还是能调用Init函数这是因为Init函数里面没有用到this,没用到就不会报错*/WinSock->Init(); }void GameProc::InitInterface()
{LoadBase();// MessageBoxA(0, "1", "1", MB_OK);// 只会HOOK一次,一次性的HOOKhooker->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  Anlyanly->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  Anlyanly->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  Anlyanly->SendData(TTYPE::I_RECV, 0, buff, len);
#endifreturn RecvDealProc[buff[0]](buff + 1, len - 1);
}bool GameWinSock::OnRecv(char* buff, unsigned len)
{
//#ifdef  Anly
//	anly->SendData(1, buff, len);
//#endifreturn (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();
};

相关文章:

  • python的OA公文发文管理系统flask-django-php-nodejs
  • 【Node.js从基础到高级运用】十五、单元测试与集成测试
  • SQL:窗口函数之OVER()
  • Redis 的BGSAVE和BGREWRITEAOF操作
  • Vue模块化开发步骤—遇到的问题—解决办法
  • mac 同步安卓手机屏幕
  • 【3DsMax】展UV记录
  • 【每日一问】IOS手机上Charles证书过期怎么办?
  • 【python】python3基础
  • 机器学习 - 训练模型
  • RabbitMQ问题
  • VScode手动安装vsix格式插件,提示安装插件与code版本不兼容问题
  • Alibaba spring cloud Dubbo使用(基于Zookeeper或者基于Nacos+泛化调用完整代码一键启动)
  • FloodFill算法——图像渲染
  • 第十二届蓝桥杯省赛CC++ 研究生组
  • Docker容器管理
  • echarts的各种常用效果展示
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • Linux学习笔记6-使用fdisk进行磁盘管理
  • SQLServer之创建数据库快照
  • tweak 支持第三方库
  • 翻译--Thinking in React
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 爬虫模拟登陆 SegmentFault
  • 前端攻城师
  • 如何使用 JavaScript 解析 URL
  • 设计模式走一遍---观察者模式
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 用Node EJS写一个爬虫脚本每天定时给心爱的她发一封暖心邮件
  • 主流的CSS水平和垂直居中技术大全
  • NLPIR智能语义技术让大数据挖掘更简单
  • 不要一棍子打翻所有黑盒模型,其实可以让它们发挥作用 ...
  • ​插件化DPI在商用WIFI中的价值
  • ​软考-高级-信息系统项目管理师教程 第四版【第19章-配置与变更管理-思维导图】​
  • #前后端分离# 头条发布系统
  • (1)(1.13) SiK无线电高级配置(五)
  • (4)(4.6) Triducer
  • (zt)基于Facebook和Flash平台的应用架构解析
  • (第61天)多租户架构(CDB/PDB)
  • (二)构建dubbo分布式平台-平台功能导图
  • (非本人原创)我们工作到底是为了什么?​——HP大中华区总裁孙振耀退休感言(r4笔记第60天)...
  • (算法)N皇后问题
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (转)linux下的时间函数使用
  • *** 2003
  • .[hudsonL@cock.li].mkp勒索加密数据库完美恢复---惜分飞
  • .NET 6 在已知拓扑路径的情况下使用 Dijkstra,A*算法搜索最短路径
  • .NET CORE 2.0发布后没有 VIEWS视图页面文件
  • .net core 3.0 linux,.NET Core 3.0 的新增功能
  • .NET Core、DNX、DNU、DNVM、MVC6学习资料
  • .net websocket 获取http登录的用户_如何解密浏览器的登录密码?获取浏览器内用户信息?...
  • .NET精简框架的“无法找到资源程序集”异常释疑
  • /boot 内存空间不够
  • /etc/X11/xorg.conf 文件被误改后进不了图形化界面
  • ?php echo ?,?php echo Hello world!;?