读书频道 > 安全 > 游戏外挂攻防艺术
1.4.2 游戏协议之发包模型
2013-02-26 13:00:54     我来说两句 
收藏    我要投稿   

本文所属图书 > 游戏外挂攻防艺术

随着网络的普及,网络游戏得到了众多网民的青睐。但是,网络游戏的盛行,也给游戏玩家和游戏公司带来了很多安全问题,如木马盗号、外挂作弊等。对于正常的游戏玩家和游戏公司来说,外挂的危害尤其突出。因为一款...  立即去当当网订购

游戏协议是客户端与服务器以及客户端与客户端之间进行通信的基础。这里讨论的游戏协议是指Windows网络协议栈中的应用层协议。事实上,协议就是封装或解封数据所采用的某种特定格式的通信约定。当通信的一端按一定的格式封装数据并将其发送到另一端后,另一端再按同等格式来解封这些数据,这样就完成了一次通信。

在Windows网络协议栈中,游戏数据封装/解封的过程如图1-16所示。


 

图1-16展示的封装/解封过程都是由Windows系统中的协议模块处理的。游戏通信端为了快速分类处理游戏数据,也为游戏数据设计了相应的协议,即游戏采用某种格式来封装数据。

游戏中数据的格式划分各不相同,但是从宏观上看,通常采用数据包头加数据包体的格式。为了说明游戏协议数据的格式,下面先给出一种模拟格式。
// 数据包头
typedef struct _PACKET_HEAD
{
  BYTE   bPacketType;  // 包的类型,命令包为1,通知包为2
WORD  wProtocolId;   // 协议的ID
  DWORD dwLength;   // 包的长度,包头加包体的字节数
DWORD dwCrc32;    // 包体的CRC32校验值,防止包体被篡改

}PACKET_HEAD, *PPACKET_HEAD;

// 数据包体
typedef struct _PACKET_BODY
{
DWORD dwPacketCount;  // 包的计数  
BYTE   bBodyContent[1];  // 根据具体的游戏协议ID来确定内容
}PACKET_BODY, *PPACKET_BODY;

// 数据包
typedef struct _PACKET
{
   PACKET_HEAD stPacketHead;
   PACKET_BODY stPacketBody;
}PACKET,*PPACKET;

游戏客户端接收到用户的操作指令,然后根据具体的协议,把数据封装成包(Packet)发送出去。

在上面的模拟格式的基础上,为了帮助读者更好地理解游戏协议,下面给出一段用户摆摊出售物品的模拟行为代码。摆摊出售物品的行为至少涉及3条基本协议,分别是建立商店、摆上物品出售、关闭商店,对应的协议ID放在一个枚举类型中。
enum ENUM_PROTOCOL_ID
{
   ENUM_CMDPACKET_CREATE_SHOP,   // 建立商店
   ENUM_CMDPACKET_SELL_ITEMS,   // 摆上物品出售
   ENUM_CMDPACKET_CLOSE_ SHOP  // 关闭商店
};

下面我们看看将ENUM_CMDPACKET_SELLER_ITEMS(摆上物品出售)这条协议封装到包中的过程。
// ENUM_CMDPACKET_SELLER_ITEMS对应的数据内容
typedef struct _SELL_ITEMS{
DWORD  dwShopNameLen;    // 商店名字的长度
   CHAR    szName[dwShopNameLen]; // 商店的名字
   WORD  wItemColumNum;    // 物品在物品栏中的编号
   DWORD dwItemSellMoney;   // 某个物品的售价
   WORD  wShopColumNum;    // 商店栏编号
   DWORD dwSellCount;    // 同一个物品有多少个拿来出售
}SELL_ITEMS, *PSELL_ITEMS;

// 下面是伪代码
SELL_ITEMS stSellItem = {0};  // 初始化要出售商品的信息
stSellItem. dwShopNameLen = strlen(“winsunshop”);
    // 初始化商店名称的长度
strncpy(stSellItem.szName, “winsunshop”, strlen(“winsunshop”));
    // 初始化商店名称
stSellItem.wItemColumNum = 1;   // 物品栏编号
stSellItem.dwItemSellMoney = 1000;  // 物品的售价
stSellItem.wShopColumNum = 2;   // 商店栏编号
stSellItem.dwSellCount = 1;   // 出售个数
// 为数据包分配空间
DWORD dwPacketLen = sizeof(PACKET_HEAD) + 4 + sizeof(SELL_ITEMS);
PPACKET pPacket = new BYTE[dwPacketLen];
// 初始化包头
pPacket->stPacketHead.bPacketType = 0x01;   // 命令包
pPacket->stPacketHead. wProtocolId = ENUM_CMDPACKET_SELL_ITEMS;
    // 摆摊出售商品
pPacket->stPacketHead. dwLength = dwPacketLen; // 数据包的长度
pPacket->stPacketBody. dwPacketCount = 1;   // 包计数
// 复制协议内容到包体中
memcpy(pPacket->stPacketBody. bBodyContent,  // 复制协议内容
&stSellItem,
sizeof(SELL_ITEMS));
// 计算并重新设置CRC值
pPakcet->stPacketHead. dwCrc32 = CRC32(pPacket->stPacketBody);


 

经过上面的数据封装之后,接下来就是将pPacket指向的数据包发送出去。但是,发包通常不是简单地调用send函数,而是先对数据进行加密,然后才发送出去。

接下来给出一个高效的发包模型。通过对这个模型的分析,读者可以更好地理解断下send函数回溯定位Call方法的由来以及整个游戏的通信逻辑,如图1-17所示。可以看到,用户的每个动作在游戏客户端逻辑中都有一个类似Switch结构的case与之对应,可能的Switch如下。
switch(OP)

case —— 开启商店。
  // 省略部分代码
  break;

case —— 摆摊出售物品,步骤如下。

(1)收集数据。

(2)将数据复制到待发送缓存中(以包的形式)。

(3)读取待发送的包并进行加密

(4)用加密后的包覆盖原始包。

(5)调用send函数发送加密后的包。

case —— 关闭商店。
// 省略部分代码
break;

每一个用户操作指令,最终都会变成一个被加密的包,存储在发包模型的可循环利用的缓存中,然后才发送出去。这个发包模型的优点如下。

统一了发包管理,例如,图1-17中的步骤2、步骤3、步骤4可以封装成一个发包出口函数。

可循环利用的缓存提高了内存的利用率。

但是,这个发包模型也有不足之处。从游戏安全的角度看,它的弱点在于比较容易从send函数进行回溯分析,从而定位Switch中的某个case,进而挖掘出有价值的Call。如果要对这个发包模型进行改正,可以将图1-17中步骤2、步骤3、步骤4的操作放在一个线程中专门处理,将步骤1的操作放在另外的线程中处理,线程间采用信号量或其他方式同步。这样,即使断下send函数来回溯,也不太容易定位。

当服务器端收到ENUM_CMDPACKET_SELL_ITEMS这个协议包的时候,就会为用户建立商店并摆上物品,所有这些都以数据的形式体现。下面以一幅简单的描述图来结束本节的协议之旅,如图1-18所示。


 

点击复制链接 与好友分享!回本站首页
分享到: 更多
您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:1.4.1 游戏资源的加/解密
下一篇:1.4.3 游戏内存对象布局
相关文章
图文推荐
2.9.3 静态可信根与
2.8 远程管理
2.7.6 微内核中的安
2.7.3 直通技术
排行
热门
文章
下载
读书

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训
版权所有: 红黑联盟--致力于做最好的IT技术学习网站