THUAI7 THUBG saiblo.net 漏洞解析 外挂 writeup

清华大学人工智能比赛 - 未来战场 外挂解析

清华大学五月十几号到五月二十几号的 THUAI-7 未来战场 THUBG 在 saiblo.net 上举行。本来准备炼丹,但是赛制非常不友好,并且发现服务器等都是开源的,突然感觉开挂比较好玩

官方后端源码地址
外挂 目前获取对手 token 的功能已经失效

游戏简介

在一个 256*256 的二维平面上,坐标是浮点数,有 1x1 的障碍物,两个(娱乐赛有多个)玩家,一秒 20 ticks,可以走路、捡物资、开枪、扔手雷等。获取自己的整数坐标使用向下取整。

基于浮点数的攻击

浮点数移动攻击

首先,交互流程大概是用 websocket 传 JSON。以上是处理选手移动的入口,每 tick 执行一次。

以下是 Position Normalize:

于是我们有了第一个漏洞:如果 x=1e-200, y=1e-200 我们可以使得 Normalize 之后变成 (inf, inf)。把其中一个变成 0 可以获得 (inf, nan) 等组合,负数就是 -inf。

接下来,我们从走路的入口寻找漏洞:


以下是 GetRealEndPosition 的源码,比较长,可以不管:

这里引入了一个 DoS 漏洞。这个函数会先递归一次并且 independentAxisCalculating=true。以下是 GetXaxisBlock:


C# 一个特性是,inf nan -inf 转 int 之后全都是 -2147483648。省略一些过程之后(实际上只需要把所有方向的组合试一遍),我们有了第一个 DoS 漏洞:

出生点选择在 (0.0, 0.0),然后走到 (-1e-200, 0) 服务器处理的时候会陷入一个从 -2147483648 到 0 的循环,把这里面所有数都加到一个动态数组里。实测由于在 docker-compose 里加入了内存限制 4096M,会导致服务器几乎无法动弹,十几秒钟处理一个 tick 并丢出一个错误。如果没有内存限制会直接崩溃。

这样我们有了一个赢得天梯比赛的思路:使用 DoS 让服务器一直出错,十分钟后服务器自动退出,这时如果对手发现连接断开并且自己主动退出了,评测系统会算每退出的那个人赢。如果对手不退出,自己也不退出,会算评测失败不扣分。这样理论上来说这个 DoS 是永远不会扣分的。本人懒得实现这个了,后面还有一个更强的 DoS,有兴趣的可以去天梯炸鱼

浮点数开枪攻击

流程:出生在 (0.0, 0.0) 朝 (1e-200, 1e-200) 打拳。关键代码如下:

问题出在前面的 Normalize。

打拳(开枪)的逻辑是,算出 targetPosition - playerPosition 并且 Normalize,然后算对手是否被攻击到了。由于 Normalize 已经被污染了,我们会传两个类似 inf 的东西进去,然后机缘巧合地这个代码就死循环了。distance 会算出 inf,然后服务器就会彻底卡死。用这个方法 DoS 服务器然后用前面那个方法去天梯上炸鱼应该不错,而且这个方法更优的地方在于不会在服务器日志中生成任何信息。嘿嘿

这里另一个漏洞是,站在 (0, 0) 可以提高射程,或者站在 x=0 或 y=0 朝垂直方向开枪也可以提高射程。具体原理是这样的:浮点数标准中有一个最小能被表示的浮点数,后面被 truncated 的东西会取整(至于取整朝哪个方向取整,RFC 并没有规定)。Normalize 是同时除以模长,模长由模长平方的根号得到。

我们可以精心构造一个很小的浮点数,使得模长平方开根号前的值被取整,让模长变小,从而让射程变大。令 m m m 表示最小的正浮点数,则我们构造一个 p p p 使得 p 2 < m × 1.5 p^2 < m \times 1.5 p2<m×1.5 并且尽可能大。这样,一个向量 Normalize 之后,它的长度就会与 1 偏差一些,具体来说我们最大能得到的是 1.5 ≈ 1.22 \sqrt{1.5}\approx 1.22 1.5 1.22。也就是说我们卡在墙的时候可以提升 22% 射程,注意是朝垂直方向开枪。卡在 (0, 0) 墙角的时候,可以朝任意方向开枪,会比较复杂,但确实可以实现。

还有一个老版本卡墙的漏洞,可惜 C# 的浮点数取整太魔性了。经过很长时间的瞪眼,我发现一个漏洞:a+b-b 不一定等于 a。这是老版本服务器代码里唯一一个可能卡墙的漏洞。我们考察 python 中的一个例子。辛苦了好久才想出来的

服务器中的逻辑是:

每次走 0.02,如果碰到墙就用减法回到上一步。这里因为 a+b-b != a,我们可以精心构造一组方案。我至少构造了六个小时吧,最后被 C# 给杀了,因为 C# 的浮点数取整跟 python 不一样,所以这个漏洞最终被语言特性给拦截了。如果浮点数处理跟 python 一样的话,我们可以在 2 的整数次方的位置卡墙。代码也在 github 上,文件名叫 glitch_2pow。换加法减法方向还是不行,只能等服务器代码更新了。果不其然更新之后新增了有漏洞。

基于网络的攻击

管理员非常贴心地给了我 docker-compose 用于“本地调试”。以下是 docker-compose:


我们直接新建一个类似的,然后试试能不能监听到通信。

直接监听发现不能,但是有一些很有趣的广播,形如 ARP Who is …? Tell … 看得出来 arp 表是没有初始化的,这里漏洞也是比较简单:arpspoof。用一些简单的手段获取到服务器和对手的 IP,然后就可以监听他们之间的通信。google websocket payload decoder 出来一个 0 star 的库 https://github.com/tdhoward/decode-websocket-packets,只有几十行 python,居然他妈就是我想要的代码。现在是 1 star 了。然后试着装各种 python 监听库发现都要 NET-ADMIN 权限,我没有啊,只能用最朴实的办法 tcpdump -w victim.pcap -U {filter} 然后读 victim.pcap。于是这个外挂可以直接控制对手出生在手雷把自己原地炸死。

决赛前那个月黑风高的晚上一点钟,有个人把还在调试的几个人分别开了十场测试自己的 AI,当时的效果如下:

(没有截图,现在找不到了)效果大概就是,那个人 20 场全输了,零胜率……

当时因为外挂打有些人出问题,调了很久也不知道怎么回事。后来发现大概是拦截的 timeout 太小了。然后决赛前一天中午被修了,可恶

Bonus

决赛前交 DoS 发现交不上去,大概问题是我不是我们队的队长,然后用 burpsuite 把名字改成队长的名字就可以修改已经交了的 AI 了。整个过程大概就几分钟,还是比较熟练了

其他可能的漏洞:

  • 平台判断胜负 由于后端代码不知道谁是平台上的玩家1 玩家2,平台判断胜负大概率是一个额外的脚本。群里有管理员说漏嘴了,大概是一个 regex 判断胜负。但是我没法把我输出的内容不分行,也就是我输出的内容每一行都是 client0/1 开头的,这个没有仔细研究,可能最后无法实现。
  • ICMP redirect 这是网络安全空间导论课程的一个附加实验,但是我本地测都默认开了防御机制还关不掉。不知道行不行,而且现在不知道管理员针对 arp spoof 做了什么措施,可能需要设置一个 reverse ssh 看看能不能获取对手 IP 等消息,以及是否有防火墙或网络隔离。

总结

花了很多时间,当时用 arpspoof 拿到对手 token 的时候非常激动!!大概是这辈子除了竞赛之外最激动的时候了!可惜社会心理学不过关,赛前一天没绷住……

好像现在天梯关了,不能炸鱼了

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值