黑客逆向《银河飞将》实现双人联机

黑客通过逆向工程实现《银河飞将》双人联机。他利用内存快照对比技术,从百万字节中锁定关键地址,成功控制飞船位置与速度,让童年驾驶双座战机的梦想成真。

作者:danielrh

内存快照比对锁定关键地址
视频演示(可在浏览器中播放)

童年时期,作者与弟弟帕特里克曾梦想驾驶《银河飞将》(Wing Commander)中的太空战斗机,并渴望成为“机长”系列中的双座战机伙伴,而非单一飞船内的两名乘员。这一愿景最终通过技术手段得以实现。

定位内存结构:从手动调试到自动化分析

2015年10月4日,作者在 Dosbox 模拟器中运行经典版《银河飞将》,试图通过网络数据包控制翼员(wingman)。由于 DOS 可寻址内存仅约一百万字节,且包含64K数据段,攻击者面临巨大的搜索空间问题。

DOS 可定位内存的兆字节和 64K 数据段分解示意图

为简化问题,作者尝试清除其他实体干扰。停止的玩家不应改变坐标,因此可以通过两次快照识别背景内存变化。若鼠标被禁用导致无法转向,则可能仅改变一个轴。初始阶段需移除翼员帕拉丁(Paladin),以免其移动干扰静止状态下的内存比对。在一次任务失败后,邪恶的太空猫击败了帕拉丁,作者利用 DOSBox 的 MEMDUMPBIN 调试命令进行内存快照。

发射时的黄蜂号驾驶舱,右侧视频显示器显示自动瞄准目标为空

作者需要在原始位置的记忆快照中寻找无变化的区域。通过对比静止与移动状态的快照,发现253个字节在静止时转换,650个字节在移动时转换,其中413个字节同时发生两种变化。结合追踪摄像头的交叉引用,剩余可疑字节缩减至132个。

字节变化统计:静止转换253字节,移动转换650字节,两者共有413字节;经摄像头交叉引用后剩132字节

数据段中有35个不同的字节集群发生变化。通过视觉检查浮动或小整数变化,搜索范围缩小至地址 13D3:A9C2。使用 DOSBox 调试器的 SM 命令在该地址添加整数,导致飞船向前冲击到新位置。此技术也成功识别了速度变量,位于 CAE2,因为当飞船停止时,三个32位整数归零。

2015年10月的64K数据段可视化:机器位置(A9C2)、摄像头副本(AC9E)、设定速度(B9F6)及速度(CAE2/CDBE)高亮显示

此时,作者已能通过内存编辑器驾驶飞船,并在爪子及其他NPC船只上复制该行为。

深入逆向工程:寻找旋转与向量存储

2016年9月,作者编写工具自动比较 DOSBox 快照,取代了 vbindiff GUI。数月搁置后于11月重启项目。

两个 DOSBox 内存倾倒的差异图像:数据段附近的明亮频段即为差异所在

为了找到旋转记忆位置,作者进行了更细致的搜索。游戏使用4字节标量表示向量,其中256代表1.0,正向量位置在0到255之间。这意味着大多数小旋转只改变一个字节。然而,程序员的一个“笑话”提供了线索:机组指挥官为实体属性保留了64个插槽(0x00至0x3F)。程序员将临时变量放在0x3F,而摄像机置于0x3D。鉴于当时3D图形尚处萌芽阶段,这可能是一个关于“3D”的双关语。

64个插槽阵列结构:0x3D为摄像机,0x3F为临时插槽,导致每个值在0x2DC字节间隔内出现两次

这种布局使得临时变量与摄像机变量相邻,扩大了单字节变化的搜索区域。作者首先识别出右向量的Y分量,随后通过重写实验发现,打印操作导致相邻3个字节从0x00切换至0xFF。虽然注意到临时和摄像机字节在六合倾倒中重叠,但额外写入的字节影响了下一个向量(向前[0]),导致星空场明显变化,从而开启了代码调查。

向量数组结构:偏移量0x3D存放相机副本,0x3E为空槽,0x3F为临时中间值。采用Int32格式,256表示1.0,单位向量轴在小端序下表现为 00 01 00 00
水平飞行转向时的字节变化:前方[0]有9字节变化,上方[0]从0001变为7E00

通过在地址上设置内存观察点,作者追踪了每船物理函数调用的矢量加函数。

内存监视点后的调用堆栈

随后,作者在 IDA Pro 中找到字节序列并开始注释游戏代码。多人游戏部分主要重复这些技巧,以操纵另一艘船的输入。

实现动作注入:处理 Borland 覆盖加载器

2016年12月,直接通过地址操控激光火焰或热探寻器等复杂功能变得不可行。为了获得准确体验,需要调用原始游戏功能,但这要求插入参数并从原位置返回。此外,许多函数涉及间接调用。Borland 编译器在每个编译单元(C++文件)上使用不同的覆盖(overlay),并根据玩家路径动态加载。

Borland 覆盖机制:JMP 远程图表

作者选择在字符串 "Loading WING COMMANDER" 附近(地址 13D3:0187 和 13D3:0395)插入指令指针转移代码。以下是用于激活激光炮的汇编片段:

00			terminates any string pointing here.
pushf; pusha		the game is mid-frame; leave registers as we found them
xor si,si; push si	argument: ship 0. Later thunks use mov si,imm16 for each argument
call far 12D7:0156	through the stub, so it works wherever the overlay is loaded
pop si; popa; popf	cdecl: caller cleans the arguments
retf			back to the CS:IP DOSBox pushed
DOSBox 推送 CS:IP,在死字符串上写入 thunk,指向它,thunk 的 retf 返回游戏结束位置

2017年3月,作者在主循环中找到可预测的连接点(0560:20E3),避免在游戏不可预测部分执行代码。7月,找到 do_damage 函数并阻止游戏将其称为次要玩家。

网络同步架构:TCP 客户端/服务器与 Protobuf

系统采用简单的 TCP 客户端/服务器架构,使用 protobuf 游戏消息。领导者发送每艘船的位置、火灾事件、失踪和损害信息。若发射激光或发生灾难性事件(弹射、降落、死亡),客户端回复自己的位置。

每个玩家拥有独立的实体0,另一玩家为实体1,接收其他模拟器数据包时需进行交换。

位置发送与事件进入工作队列的流程

数据包直接修改内存,或被添加到必须在那个时刻发生的事件的任务队列中。初始床代码非常简单:

pushf
pusha
nop
popa
popf
retf

当 Borland 覆盖加载器交换代码页面时,它会遍历堆上的 BP 链路并修补指向覆盖的返回地址。上述手工制造的卡片没有框架,导致行走读数为垃圾,游戏随机死亡。这一问题困扰团队从10月到12月。

修复方案是使床代码看起来像标准的 Borland 函数:

push bp
mov bp, sp
nop (0x90) ; welcome to the 0x90's
pop bp
retf
围绕 0x90 NOP 的正确 Borland 框架,DOSBox 监控注入下一个排队调用,retf 再次落在 NOP 上

配置 DOSBox 检测程序计数器何时达到 NOP,并从任务队列注入应执行的调用。工作排队对于多人场景正常运行至关重要。

2017年10月,发现短暂物体在不同玩家的不同位置。通过 DOSBox 的新子模块确定函数返回时间。短暂物体有自己的发育规则,占据61个实体槽的共享区域。作者修改短暂物体位置,将其限制在12到15位。

12月,客户端更改为存储服务器船号码与本地号码的映射,停止强制生成函数返回,允许数据包在本地和主机ID之间翻译。小行星开始在多人游戏中正常工作。

同一艘船可以出现在两台机器的不同槽位

2018年,重点转向动画爆炸、导弹和自动驾驶。

胜利条件与聊天功能的逆向

2019年6月,任务胜利计算不稳定。通过二进制搜索整个64K数据段复制到网络中,逐步切割,最终在 13D3:D248 找到80个字节。10月,确定总胜利积分计数在 13D3:C280,任务号码在 13D3:C255 和 C256。

数据段分割至80字节的过程

2020年初,确定了负责通信文本的功能。这允许通过网络发送聊天内容,并将键入的信息直接接入系统,通过队友的视频显示单元(VDU)播放,实现了沉浸式聊天体验。

两位飞行员通过打字互相发送信息

尽管2020年底游戏处于可玩状态,但分享乐趣仍困难重重:用户需寻找旧游戏副本、编译或下载特殊 DOSBox 版本、共享公网 IP 并穿透防火墙。

机组指挥官多人游戏发现时间表

2026年:WebRTC 与 Lobbylink 实现的浏览器端联机

随着 WebRTC 的普及和脚本支持的增强,作者构建了新系统 Lobbylink,实现了真正的在线合作。

Lobbylink 系统

作者开发了 Lobbylink,这是一个基于 Go 语言的简单大厅系统,仅依赖 websocket 库,用于编排点对点 WebRTC 连接。它可以在任何小型服务器上运行,允许玩家通过共享房间代码连接。服务器维护静态来源允许列表,支持浏览器或几乎任何语言的客户端软件连接。房间代码用于定位同房间的其他玩家,并通知所有加入者。服务器还运行 coturn 并铸造 TURN 凭证,帮助受限防火墙后的玩家建立连接。

Lobby 允许具有相同房间代码的人通过 WebRTC 直接通信。TURN 中继在其他选项不可用时帮助建立连接
房间代码加入步骤

传输层已移植到 Lobbylink。作者请求 LLM 配置构建脚本以针对 emscripten,提取 GOG Inno Setup 安装包中的 Wing Commander 作为数据文件。这使得用户可以从 GOG 下载游戏,拖放到浏览器存储,输入房间代码即可游玩。

游戏可在 https://fireflyk64.github.io/dosbox/ 游玩,模拟器代码位于 https://github.com/danielrh/wcmulti。

此外,作者请求 LLM 添加了语音聊天功能,使用第二个 WebRTC 对等连接传输音频,让玩家能够一起飞行并欢笑。

评论 0

0/500

评论需审核后展示,请文明发言

💬
还没有评论,来说两句

相关阅读