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  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();
};


最近更新

  1. TCP协议是安全的吗?

    2024-03-23 15:48:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-23 15:48:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-23 15:48:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-23 15:48:02       18 阅读

热门阅读

  1. v-for=“item in arr“ 的理解

    2024-03-23 15:48:02       17 阅读
  2. 24计算机考研调剂 | 西北民族大学

    2024-03-23 15:48:02       19 阅读
  3. 面试算法-79-搜索旋转排序数组

    2024-03-23 15:48:02       16 阅读
  4. Vue 面试题(二)

    2024-03-23 15:48:02       22 阅读
  5. 003-基于Jetson Nano平台的在线二维码检测

    2024-03-23 15:48:02       22 阅读
  6. C语言判断回⽂字符串

    2024-03-23 15:48:02       18 阅读
  7. MySQL知识总结

    2024-03-23 15:48:02       18 阅读
  8. Linux - IO

    2024-03-23 15:48:02       15 阅读
  9. capl实现crc校验码计算

    2024-03-23 15:48:02       19 阅读
  10. 蓝桥杯/减肥/c\c++

    2024-03-23 15:48:02       15 阅读
  11. LeetCode算法(一) 之 Python基础

    2024-03-23 15:48:02       19 阅读
  12. 力扣-字符串的最长公共前缀

    2024-03-23 15:48:02       19 阅读
  13. 力扣由浅至深 每日一题.11 加一

    2024-03-23 15:48:02       18 阅读
  14. 前端面试题整理

    2024-03-23 15:48:02       17 阅读