在前面一篇中讲到了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拦截了也能正确解密, 但是你的发送数据是什么思路呢?我这边发包就是没有收到相应。请留个联系方式请教一下
-
站点信息
- 文章2300
- 用户1336
- 访客10859999
每日一句
True success inspires others to act.
真正的成功是激励他人行动。
真正的成功是激励他人行动。
语法错误: 意外的令牌“标识符”
全面理解Gradle - 定义Task
Motrix全能下载工具 (支持 BT / 磁力链 / 百度网盘)
谷歌Pixel正在开始起飞?
获取ElementUI Table排序后的数据
Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is
亲测!虚拟机VirtualBox安装MAC OS 10.12图文教程
华为手机app闪退重启界面清空log日志问题
android ndk开发之asm/page.h: not found
手机屏幕碎了怎么备份操作?
免ROOT实现模拟点击任意位置
新手必看修改DSDT教程
thinkpad t470p装黑苹果系统10.13.2
新会员