在前面一篇中讲到了HOOK系统的send和recv函数,本篇继续讲解后期的数据包处理,这也是重中之重!
开始之前,我必须要说明一点。要做外挂必须要有丰富的开发经验,要对基本数据很懂。比如位运算,与运算,大端序,小端序……总之,经验越丰富越容易成功!攻城掠地这款游戏是没有对数据包加密的,但是简单的处理过,也并不是明文(肉眼可见通讯)
比如:【0x0,0x0,0x0,0x0A,0x01,0x01,0x010x01,0x01,0x01,0x01,0x010x01,0x01】前面4个字节转成10进制就是10嘛,后面10个0x1就是数据内容。转成可见字符,在Java里面直接new String(byte[]...);,在C++直接看就行了。char*就是字符串了。这里强调一点,在攻城掠地游戏数据包中(游戏通讯是以json格式),json部分是被zlib压缩过的,需要用开源库mz.lib的uncompress来解压,才能变成可见字符,不然压缩过的数据是乱码哦!
以下是我解析数据库的代码,现在的游戏版本也是可以用的,刚刚测试过(我很久没弄了,今天是了为写博客)
因为Flash的语法和Java很相似,我用C++写了一个类似Java的数据流类DataInputStream.h
DataInputStream.cpp
其实就是对char*操作的一个封装。继续讲解ParseData(CDataInputStream &dis)的说明。在这之前,我们先看一段已经解析好的数据包,是Json格式的。该游戏是json通讯的。
我们拿这条数据包来举例说明。
首先int data_length = dis.readInt();读取4个字节的数据包长度。前面讲过的。不懂往前看。
接下来就是正式的内容,然后再读取32个字节的命令行,byte *command = new byte[32];//command dis.read(command,32);这个command其实就是world@getManzuShoumaiInfo,再接下来是4个字节的token值,这个貌似没啥用,估计是用来验证游戏的通讯,每次通讯一次会自增1.this->token = dis.readInt();//token 有人到这里估计会问,你怎么知道是读32字符为command,4字节是token值呢?哈哈,我是破解了人家的swf文件,看了人家的通讯源码。
继续往下看,上面已经读取了多少个字节呢?一个32,一个4,那就位移就到了32+4=36了,这里再次举例说明数据包的格式:
假如总数据包是100的话,4个字节头是内容的长度,剩余96就是内容了,然后32字节是command,4字节是token值,也就是剩余60个字节就是Json数据了,这60字节是需要用uncompress解压。
我这里用500KB的内存空间存储被解压的json数据,因为人家通讯数据包不可能超过500的。这个temp就是json数据啦,是UTF-8格式的,要转成可见的还需要转成ascii
然后content就是可见的json数据包了。好了,可能有些地方大家不明白,可以留言或者加我Q。
下面截的图就是破译的内容:
开始之前,我必须要说明一点。要做外挂必须要有丰富的开发经验,要对基本数据很懂。比如位运算,与运算,大端序,小端序……总之,经验越丰富越容易成功!攻城掠地这款游戏是没有对数据包加密的,但是简单的处理过,也并不是明文(肉眼可见通讯)
开发思路
一,首先要知道对方通讯协议,那么最简单的方法就是看人家的源码喽,看不到就用汇编进行调试(这个本人汇编比较菜,找找简单的还行),幸好flash是加载到本地再执行的(swf文件)。网上有很多破解swf的软件,这个度娘一下就知道了我就不多复述了。我花了几个小时破解了该游戏协议,数据包格式是4个字节的int类型,也就是该包的长度。后面就是内容喽,比如:【0x0,0x0,0x0,0x0A,0x01,0x01,0x010x01,0x01,0x01,0x01,0x010x01,0x01】前面4个字节转成10进制就是10嘛,后面10个0x1就是数据内容。转成可见字符,在Java里面直接new String(byte[]...);,在C++直接看就行了。char*就是字符串了。这里强调一点,在攻城掠地游戏数据包中(游戏通讯是以json格式),json部分是被zlib压缩过的,需要用开源库mz.lib的uncompress来解压,才能变成可见字符,不然压缩过的数据是乱码哦!
以下是我解析数据库的代码,现在的游戏版本也是可以用的,刚刚测试过(我很久没弄了,今天是了为写博客)
void CParseGcldJsonData::ParseData(CDataInputStream &dis)
{
try
{
////数据长度
int data_length = dis.readInt();//数据包的长度
if(data_length < 32) return; byte *command = new byte[32];//command dis.read(command,32); CString str_command(command); OutputDebugString(str_command); OutputDebugString("\r\n"); this->token = dis.readInt();//token
int srcLength = data_length - 36;//减去token和command长度-32-4
byte *srcData = new byte[srcLength];//就等于content的长度
dis.read(srcData,srcLength);
unsigned long dLen = 512000;
byte *temp = new byte[dLen];//500kb数据
memset(temp,0,dLen);
uncompress(temp,&dLen,srcData,srcLength);
CString content(temp);
GT_UTF8toANSI(content);
if(command != NULL)
delete[] command;
command = NULL;
if(temp != NULL)
delete[] temp;
temp = NULL;
if(srcData != NULL)
delete[] srcData;
srcData = NULL;
if(str_command.Left(5) == "world")
TRACE("\r\nrecv[%s]\r\n",str_command+content);
if(str_command == ROLE_GET_LIST)
ParsePlayerList(content);
else if(str_command == ROLE_GET_INFO)
ParsePlayerInfo(content);
else if(str_command == MC_GET_BUILDING_INFO)
ParseBuildInfo(content);
else if(str_command == NATION_POWER_REWARD_INFO)
ParseReward(content);
else if(str_command == ZEROONLINE_GET_NUM)
ParseOnlineGiftNumber(content);
else if(str_command == OPEN_MARKET_PANEL)
ParseMarket(content);
else if(str_command == MC_GET_MAIN_CITY_INFO)
ParseMainCityInfo(content);
else if(str_command == POLITICS_GETEVENT_INFO)
ParseEventInfo(content);
else if(str_command == GET_BUY_BARBARIAN)
parseManzuShoumaiInfo(content);
else if(str_command == GET_SHOP_ITEMS || str_command == REFRESH_SHOP_ITEMS)
ParseItem(content);
else if(str_command == BUILDING_CD_SPEED_UP)
ParseSpeedCd(content);
else if(str_command == GET_EQUIPS_LIST)
ParseEquips(content);
else if(str_command == GET_REFRESH_INFO)
ParseQuenching(content);
else if(str_command == GET_FORCE_INFO || str_command == SWITCH_FORCE_INFO)
ParsePowerInfo(content);
else if(str_command == GENERAL_GET_GENERALSIMPLEINFO)
ParseGeneralSimpleInfo(content);
else if(str_command == BATTLE_PERMIT)
ParseBattlePermit(content);
else if(str_command == PUSH_BATTLE)
ParseBattle(content);
else if(str_command == GET_CITIES)
ParseWorldScene(content);
else if(str_command == PUSH_GENBATTLE)
ParseGeneralBattle(content);
else if(str_command == WAR_PREPARE)
ParseBattlePrepare(content);
else if(str_command == INCENSE_GET_INCENSEINFO)
ParseIncenseInfo(content);
else if(str_command == CITY_DETAIL_INFO)
ParseCityDetailInfo(content);
else if(str_command == PUSH_CITIES)
ParseCities(content);
else if(str_command == WORLD_ASSEMBLE_BATTLE)
ParseAssembleBattleAll(content);
else if(str_command == OPEN_DINNER)
ParseDinner(content);
else if(str_command == WORLD_AUTO_MOVE)
ParseAutoMove(content);
else if(str_command == OPEN_SLAVE_GETINFO)
ParseGetSlaveInfo(content);
else if(str_command == TAVERN_GETGENERALS)
ParseTavern(content);
else if(str_command == FEAT_LIST_GET_INFO)
ParseRankInfo(content);
else if(str_command == FARM_GET_INFO)
ParseFarmInfo(content);
//有可能多个数据包组合在一起
if (dis.available() > 4)
ParseData(dis);
}
catch (CException* e)
{
char errorMsg[INFO_SIZE];
e->GetErrorMessage(errorMsg,INFO_SIZE);
strcat_s(errorMsg,"P002");
GT_WriteReleaseLog(errorMsg);
}
}因为Flash的语法和Java很相似,我用C++写了一个类似Java的数据流类DataInputStream.h
#pragma once
class CDataInputStream
{
private:
byte *m_pData;//16进制的数据
int m_nLen;
int m_nPos;
public:
CDataInputStream(char *pData,int nLen);
virtual ~CDataInputStream(void);
int readInt();
void read(byte *buf,int nLen);
void close();
int getLength(void);
int available(void);
};
DataInputStream.cpp
#include "stdafx.h"
#include "DataInputStream.h"
CDataInputStream::CDataInputStream(char *pData,int nLen)
{
this->m_nLen = nLen;
this->m_pData = new byte[m_nLen];
memcpy_s(this->m_pData,m_nLen,pData,nLen);
this->m_nPos = 0;
}
CDataInputStream::~CDataInputStream(void)
{
if(this->m_pData != NULL)
delete[] this->m_pData;
this->m_pData = NULL;
}
int CDataInputStream::readInt()
{
try
{
if (m_nPos+4 > m_nLen)
{
TRACE("ReadInt Memory overflow pos:%d len:%d\r\n",m_nPos,m_nLen);
return -1;
}
int ch1 = m_pData[m_nPos++];
int ch2 = m_pData[m_nPos++];
int ch3 = m_pData[m_nPos++];
int ch4 = m_pData[m_nPos++];
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); } catch (CException* e) { char errorMsg[1024]; e->GetErrorMessage(errorMsg,1024);
strcat_s(errorMsg,"D001");
AfxMessageBox(errorMsg);
}
return -1;
}
void CDataInputStream::read(byte *buf,int nLen)
{
try
{
if (m_nPos+nLen > m_nLen)
{
TRACE("Read %d Memory overflow pos:%d len:%d\r\n",nLen,m_nPos,m_nLen);
return;
}
memcpy_s(buf,nLen,m_pData+m_nPos,nLen);
m_nPos += nLen;
}
catch (CException* e)
{
char errorMsg[1024];
e->GetErrorMessage(errorMsg,1024);
strcat_s(errorMsg,"D002");
AfxMessageBox(errorMsg);
}
}
void CDataInputStream::close()
{
this->m_nLen = 0;
this->m_nPos = 0;
if(m_pData != NULL)
delete[] m_pData;
this->m_pData = NULL;
}
int CDataInputStream::getLength(void)
{
return m_nLen;
}
int CDataInputStream::available(void)
{
return this->m_nLen - this->m_nPos;
}
其实就是对char*操作的一个封装。继续讲解ParseData(CDataInputStream &dis)的说明。在这之前,我们先看一段已经解析好的数据包,是Json格式的。该游戏是json通讯的。
[world@getManzuShoumaiInfo{"action":{"state":1,"data":{"manzuInfo":[{"cityId":250,"isOurs":true},{"cityId":251,"isOurs":false,"canShoumai":true,"canFadong":true,"qinMiDu":228},{"cityId":252,"isOurs":false,"canShoumai":true,"canFadong":true,"qinMiDu":301}]}}}]我们拿这条数据包来举例说明。
首先int data_length = dis.readInt();读取4个字节的数据包长度。前面讲过的。不懂往前看。
接下来就是正式的内容,然后再读取32个字节的命令行,byte *command = new byte[32];//command dis.read(command,32);这个command其实就是world@getManzuShoumaiInfo,再接下来是4个字节的token值,这个貌似没啥用,估计是用来验证游戏的通讯,每次通讯一次会自增1.this->token = dis.readInt();//token 有人到这里估计会问,你怎么知道是读32字符为command,4字节是token值呢?哈哈,我是破解了人家的swf文件,看了人家的通讯源码。
继续往下看,上面已经读取了多少个字节呢?一个32,一个4,那就位移就到了32+4=36了,这里再次举例说明数据包的格式:
假如总数据包是100的话,4个字节头是内容的长度,剩余96就是内容了,然后32字节是command,4字节是token值,也就是剩余60个字节就是Json数据了,这60字节是需要用uncompress解压。
int srcLength = data_length - 36;//减去token和command长度-32-4 byte *srcData = new byte[srcLength];//就等于content的长度 dis.read(srcData,srcLength); unsigned long dLen = 512000; byte *temp = new byte[dLen];//500kb数据 memset(temp,0,dLen); uncompress(temp,&dLen,srcData,srcLength);
我这里用500KB的内存空间存储被解压的json数据,因为人家通讯数据包不可能超过500的。这个temp就是json数据啦,是UTF-8格式的,要转成可见的还需要转成ascii
static void GT_UTF8toANSI(CString &strUTF8)
{
//获取转换为多字节后需要的缓冲区大小,创建多字节缓冲区
UINT nLen = MultiByteToWideChar(CP_UTF8,NULL,strUTF8,-1,NULL,NULL);
WCHAR *wszBuffer = new WCHAR[nLen+1];
nLen = MultiByteToWideChar(CP_UTF8,NULL,strUTF8,-1,wszBuffer,nLen);
wszBuffer[nLen] = 0;
nLen = WideCharToMultiByte(CP_ACP,NULL,wszBuffer,-1,NULL,NULL,NULL,NULL);
CHAR *szBuffer = new CHAR[nLen+1];
nLen = WideCharToMultiByte(CP_ACP,NULL,wszBuffer,-1,szBuffer,nLen,NULL,NULL);
szBuffer[nLen] = 0;
strUTF8 = szBuffer;
//清理内存
delete []szBuffer;
delete []wszBuffer;
}然后content就是可见的json数据包了。好了,可能有些地方大家不明白,可以留言或者加我Q。
下面截的图就是破译的内容:

收藏的用户(0) X
正在加载信息~
推荐阅读
最新回复 (5)
-
-
-
-
hupengpeng 2017-9-24引用 5楼老哥请教下,我这边也反编译了他的swf也看到了 readBytes(contentBytes, 0, this._dataLength - 4 - 32); 他这边的协议。用winpcap拦截了也能正确解密, 但是你的发送数据是什么思路呢?我这边发包就是没有收到相应。请留个联系方式请教一下 -
站点信息
- 文章2313
- 用户1336
- 访客11759810
每日一句
Pride in your steps to dreams.
为追梦的每一步而自豪。
为追梦的每一步而自豪。
新会员