For the complete documentation index, see llms.txt. This page is also available as Markdown.

游戏中的 BSD

如果你已经开始阅读本文,那很可能意味着你对安全、嵌入式软件感兴趣,或者你在游戏行业工作。无论哪种情况,请立刻放下你的抗精神病药物,因为我们需要你处于最偏执的状态,准备好戴上安全的黑白两顶帽子。

多年来安全一直被反复讨论,尤其是围绕保护客户数据方面。这些数据可以是简单数据,如电子邮件地址或用户名,也可以是更复杂的数据,比如用于解密客户在在线商店购物内容的密码。这些服务通常基于数据保护,但不保证所使用的硬件或软件就是实际交付的产品。就服务器而言,谈论硬件保护毫无意义,因为只有系统管理员才应该有这些机器的物理访问权限。

作者:Roberto Fernández

在游戏行业,我们同时面对软件和硬件,两者都必须经过检查,以确保交付的产品未被篡改。例如,篡改可以通过更换产品的某些硬件组件或插入绕过其保护机制的途径来实现。除了硬件入侵检测系统外,强烈建议为产品开发的软件也具备某种保护。FreeBSD 提供了若干工具,从加密安装程序的磁盘开始,到检测它们是否被某种方式更改为止。我们不应忘记,并非每一行代码都会用在游戏系统中。例如,freebsd-update(8) 可能用也可能不用。如果你不使用某个功能,将其留在发布版本中可能成为系统的安全风险。如果你确实使用它,就必须控制哪些安全问题会影响你的软件,以及如何向系统应用补丁。但事情不止是保护硬件或软件。作为玩家,我只关心我保存的数据不会丢失。当我在游戏中保存进度时,我讨厌看到它丢失并不得不重做已完成的部分。想象一下,你终于通过了一个可能花了五个小时才过的关卡,然后存档点丢失了。你会“开心”还是想把游戏机拆了?

以嵌入式方式构建

确保软件尽可能少地存在安全漏洞的最佳方式是使用 YAGNI(You aren’t going to need it,你不会需要它)。虽然这是编写高质量代码的技巧,但在此处可以用来判断某个程序或库是否会被使用,从而避免将其纳入。为更好地理解这一点是如何实现的,构建部分分为三个部分:构建 world、构建内核和构建 Ports。

构建 World

完成此目标有两种方式:

  • 构建一切,安装部分。

  • 只构建需要的。

第一条意味着用众所周知的命令进行完整构建:

root@ASUS-R752L: /usr/src # make buildworld
root@ASUS-R752L: /usr/src # make installworld DESTDIR=/work/target

完成后,所有条目通过 shell 脚本进入最终系统。如果你不想折腾 FreeBSD 的构建过程,这是一种起步方式。此流程的一种优化技术是编写 src.conf(5) 文件并在环境中设置变量 SRCCONF。这样做可以大幅提升构建速度,提升幅度取决于构建过程中排除了多少设置。第二条需要开发类似 src.conf(5) 中的构建变量。完整解释需要一整篇文章,这不是此处的重点。通过正确实现该变量,构建过程可加速 50%,从而避免漫长的等待时间。

构建内核

内核配置文件中有许多设备和选项可能永远不会在你的机器上使用。例如 NFS 选项或 RAID 设备。同样,如果你不需要它们,应该编写一个不含它们的新内核配置文件,以便内核运行更快,并且在有人想为最终产品添加硬件时,如果不支持就降低安全风险。内核已经配置为运行更快并包含所需设备的支持,但这还不是全部。启动分区不得加密(上次我检查时,Allan Jude 还没完成 geli 启动加载器,至少还没有实现无需输入密码即可自动启动的方式),而 loader.conf(5) 保存在那里,这使得攻击者可以修改它并获得所需硬件的支持。

构建 Ports

这是所有问题中最棘手的。我们可以在任何版本的 FreeBSD 上构建操作系统而不影响自己的构建系统。但这不适用于 Ports。如果你想从 Ports 树中获取一组程序或库并安装到产品中,你需要另想办法。有几种技术可供选择。用于创建最终用户所用软件包的工具是 ports-mgnt/poudriere。该工具创建一个 Jail 并在其中使用相同版本的基本系统构建 Ports。构建并暂存 Ports 后,你应该编写一个脚本,只获取所需的库和二进制文件。一种简单的方式是编写一个类似大多数 Ports 中 pkg-plist 的文件,描述实际系统上必须安装哪些组件。然后前面提到的脚本读取该文件,获取库和二进制文件,复制到目标 bintree 中。现在你拥有了运行游戏系统所需的一切,但安装在产品上的程序可能有许多安全报告。应该追踪这些报告,确保它们不影响你的软件,确保你的产品不会被攻破。

写入原子数据

如前所述,我们写入磁盘的数据必须一致,这意味着写入不能处于空闲状态,否则读取的数据将显示为损坏而无法使用。数据丢失有几种原因。第一种,也是游戏行业最常见的,是硬关机。这意味着系统不是由软件关闭,而是由硬件关闭,导致数据未完全写入磁盘。FreeBSD 已开发出一种方式来确保数据一致性——要么完全写入,要么完全不写。实现这一目标的方式是 gjournal(8)。通常,将用户所需数据写入非加密设备或分区会更容易、更方便。这样用户可以备份保存的游戏和玩家档案,或复制这些信息带到另一个系统。此外,支持团队可以在开始维修前备份用户数据。对于这个示例,我们将使用一块只包含用户数据的 1 TB 硬盘。GEOM 分区方案应如下所示:

为了在 gpt/UserData 分区中加入日志(该系统有 8 GB 物理内存),必须运行以下命令:

之后,/dev/ufs/UserData 条目应写入 fstab(5)。使用它而非 GEOM 标识符或驱动程序名很重要,因为后者可能导致挂载错误的分区或设备。

保护关键数据

嵌入式行业最大的担忧之一是专有软件被修改或其行为被发现。这样做的目的是绕过用于拒绝执行未知软件的认证方法,使攻击者能够使用修改后的数据,或在游戏行业的情况下运行你的软件的盗版副本。保护数据的一种简单方式是用加密 GEOM 类(又称 geli(8))加密,你选择要保护的程序。你可以加密整个系统,或只加密一个存放程序的分区,以便在启动时检查是否发生了任何篡改,如果没有则解密该分区。无论哪种情况,你都必须找到一个安全的地方存储密钥。它可以在运行时生成,或存储在连接到系统的芯片中,在特定条件下芯片会将密钥返还给你。可信计算组织为此目的开发了一项标准——可信平台模块(Trusted Platform Module,TPM),它是一个芯片,除其他功能外,还拥有非易失性存储器,你可以在其中写入信息并用存储在寄存器中的值密封它。通常,BIOS 会计算自身和 MBR 的校验和并存储在 TPM 的寄存器中。系统完全启动后,其他寄存器可以写入一些计算好的数据,以访问芯片存储器中保存的信息,该存储器持有解密根分区的密钥。如果你的平台使用 UEFI 启动,那么安全启动(Secure Boot)是一个可用的额外步骤,允许机器拥有签名的启动过程并检测启动链中的篡改。

认证专有软件

专有软件的认证在机器完全启动后进行。最简单的方式是在文件中写明哪些文件必须被检查以及它们必须有什么签名,FreeBSD 对发布文件(安装介质)就是这么做的。另一种方式是编写一个程序,使用定义的协议从二进制文件中读取并检查程序是否被某种方式修改过。这种版本需要对认证方法和如何计算二进制文件校验和有透彻理解。但现在有趣的不是如何检测软件篡改,而是检测到篡改后该怎么办。举例来说,Microsoft® 禁止在 Xbox 360® 上使用备份游戏副本的用户访问在线服务。它禁止用户更新系统或使用该账户下载游戏。这迫使用户创建新账户并重玩所有游戏,以恢复玩家的在线状态。这是理想的做法,但如果被篡改的是系统的工作方式,那么“忘记”解密正确分区的密钥是个好主意,这样用户就会拥有一个永远不会再次启动的昂贵镇纸。值得停下来考虑篡改的严重程度并采取相应行动。例如,当用户尝试玩盗版游戏而最坏的结果只是玩了游戏时,销毁解密密钥并非明智之举。另一方面,如果篡改可能导致系统被用作欺诈工具,那么这个选项就是正确的选择。

检测硬件篡改

安装产品时,可以获取系统信息并存储在文件中,系统启动后读取该文件以检查硬件是否发生变化。连接到机器的硬件列表及其连接位置可以通过 pciconf(8)usbconfig(8) 获取,因此你可以控制哪些组件通过 PCI 卡连接,哪些通过 USB 端口或 HUB 连接。让我们考虑一个示例。以下第一个脚本将获取 pciconf(8)usbconfig(8) 收集的信息,并以纯文本形式存储在 /var/db/hw 下,命名为 pci.dbusb.db。第二个脚本将获取信息并与文件中存储的信息比较。为了更好地理解这个过程,让我们从文件中移除一层安全保护(加密、认证或其他此类机制),它将以纯文本形式存储。

如果产品上安装了 TPM 芯片或选择了 UEFI 安全启动,你的 BIOS 已经被检查过,因此无需再次检查。Port sysutils/dmidecode 提供了检查 BIOS 或主板供应商和版本的选项(如果存在 SMBIOS 或 DMI 入口点)。如果 dmidecode(8) 不适用,你应该找到一种读取此信息的方法,长远来看是值得的。机器的处理器也值得检查,sysctl(8) 足以胜任。通过检查 hw.model 节点,可以返回 CPU 型号,使产品能够检查自身的处理器,并在处理器未被更换时启动。

接口

到目前为止,我们讨论的都是产品。本节解释与外部世界任何接口(例如网卡)相关的风险。针对与外部世界的通信,存在若干已知的攻击变体。让我们通过接口来逐一考虑。

网络

今天,在物联网(Internet of Things,IoT)时代,将系统连接到 Internet 是常态,无论它是烤面包机还是服务器。这对你的产品而言是安全风险,在将设备连接到外部世界之前需要深思熟虑。要问的问题是:为什么必须连接到 Internet?应该允许哪些服务?产品可能需要连接到 Internet,因为可能有游戏的在线多人版本,或系统需要更新服务以保持基础软件的最新安全版本。如果只有第一个条件相关,那么产品应该禁止用户使用其他服务。但如果产品允许用户使用 HTTP 浏览器,那么浏览器必须配置为拒绝可能危及产品的页面。另一个问题是“中间人”问题,即如何保护你的通道以避免通信被嗅探。是使用 IPsec、SSL、TLS,还是为你的通信实现新的加密层?我无法断言哪个更好,因为每种方式都有优缺点。评估最终产品的需求并设置最安全的配置,是系统开发者的使命。

USB

如今 USB 几乎用于一切。有硬盘、以太网或无线连接、网络摄像头、触摸屏和串行通信的适配器。有人可能认为 USB 足够安全,但这取决于你使用的上层协议。如果开发使用 libusb(3) 库的程序或内核驱动程序,它必须足够健壮以避免内存泄漏或代码注入。

其他

没有两个系统是完全相同的,所以我无法断言哪些接口应该被保护,哪些不需要。但请保持偏执,逐一检查所需的驱动程序,搜索安全漏洞,尝试攻击它们并改进驱动程序。FreeBSD 需要游戏行业的系统开发者的帮助来让它更安全!


ROBERTO FERNÁNDEZ 居住在德国柏林,在游戏行业工作了两年,使用 FreeBSD 编程三年。不陪伴儿子和妻子时,他玩电子游戏。

最后更新于