网络游戏的网络协议设计之防外挂
我们不能期望提供完全安全的通讯,但我们可以让攻击者的麻烦大于其获得
- 任何对发送者的协议body(通讯的实际数据)序列进行的修改都应该被检测到
- 我们只处理body的发送
- 对于报文的顺序和可靠性问题交给底层协议栈去解决.
- 对于篡改报文
- 针对此类攻击的第一线防御是一个简单的checksum
- 校验和的计算范围需要包括包头在内的整个报文
- 发送者计算报文的checksum并与报文一起发送给接收者
- 一个完美的校验和算法能对任意修改过的报文计算出不同的值
- MD5算法是一个经过广泛测试,可以公开使用的单向hash函数
- 缺点:
- 客户端程序包含校验和计算代码 -> 攻击者可通过逆向工程获取校验和算法 -> 然后对任何消息计算有效校验和
- 攻击者可以捕获有效包并在稍后重发,即packet replay
- packet replay
- 恶意用户从客户端捕获报文,通常通过报文监听 -> 多次发送 -> 然后以超过游戏允许的速度来执行命令
- 服务器端可设置一个类似每秒一次的计时器来阻止这种攻击 -> 但是因为可变的网络延迟 ->导致合服的命令序列被拒绝
- 不希望我们的安全机制将合法玩家当成欺骗者
- 预防报文重放
- 每个报文需要包含一些状态信息->即使相同的有效body也要有不同的bit pattern
- 一个随着每个报文发送而累加的计数器之类的办法就可以做到 -> 但是这种策略使攻击者能够很容易预期
- 一个较好的方法是使用一个状态机为连续的报文生成连续的序列号->算法快速并且足够复杂
- State = (State + a ) * b
- a和b是仔细挑选出来的整数
- 发送一个报文时-> 发送者生产一个随机数并将之添加到报文中 -> 同时步进随机数生成器
- 接收者使用自己的生成器检查收到报文中的随机数->如果数字不匹配则表示报文已经被篡改->如果数字匹配,则接收者也步进随机数生成器以准备接收下一个报文
- 复杂之处
- 发送者和接收者如何初始化并同步他们的状态机
- 可以使用固定种子启动各自的状态机 -> 但是初始报文流的位模式总是一样 ->因为会成为可被分析的漏洞
- 替代办法:由服务器使用随机生产的种子值初始化其状态机
- 如何在通信中保持状态机的同步
- 可信连接中 -> 包永远不会丢失 -> 同步是有保障-
- 当报文丢失或重新排序 -> 情况变的更加复杂
- 如果消息被丢失 -> 发送者的状态机比接收者的状态机多步进一次 -> 后来的报文即使合法也都被拒绝
- 简单解决方案:使用一个在每个报文中发送的真实序列号 -> 通过这个序列号 -> 接收者可以决定需要步进它的状态机多少次以适合当前报文
- 如果应用程序允许无序发送(即可能会出现先发的数据包会后收到)->较老的状态机状态必须被保存以便在被打乱次序的报文抵达后使用
- 如发送顺序为A,B,C -> 但是收到的顺序是B,A,C
- 则对于如A包的校验需要将将B的state保存 -> 等到A包到达后用该state校验
- 发送者和接收者如何初始化并同步他们的状态机
- 其他技术
- 理想情况下 -> 为了阻扰对有效负载的分析->两个具有同样有效负载的报文在其模式上应该有尽可能少的相关性
- 一个简单的消除两个集合之间相关性的方法是将其数据与一系列的随机位进行异或操作(XOR)
- A ^ randomSeed ^ randomSeed = A,如100 ^ 101 = 001 ^ 101 = 100
- 上面描述的报文重发预防中 -> 发送者和接收者已经同步了随机数生成器 -> 发送者可以为每个报文生成一个随机数序列 -> 并将之与报文有效负载进行异或操作 -> 接收者生成相同的数字序列并以相同的方式获取原始数据
- 两个具有相同长度的报文可能给攻击者一个报文编码相似数据的线索 -> 进一步干扰攻击者 -> 每个报文可以包含一些可变长度的随机垃圾数据 -> 其仅用来改变报文长度
- 发送者检测其状态机决定需要生成并插入多少字节的随机数作为垃圾数据到发送的报文中
- 接收者只需要忽略垃圾数据
- 增加垃圾数据总量可以进一步隐藏有效负荷 -> 但需要消耗额外的带宽
- 逆向工程
- 客户端包括完整的加密算法 -> 总是可以进行逆向工程 -> 这是最难解决的问题 -> 也是任何阻止协议篡改机制的根本弱点 -> 可以采用一些步骤增加逆向工程的难度
牢记你的目标是让作弊的成本最大化 -> 而非完全禁止作弊
References
- 《Game Programming Gems 1》1.11 # "A Network Protocol for Online games"
网友评论