游戏开发中的最佳安全做法

本文讨论在游戏开发中使用的最佳做法。

简介

越来越多的人玩网络游戏和用户定制内容的游戏。 再加上 Microsoft Windows 操作系统的安全性不断提高,这意味着游戏成为攻击者日益诱人的攻击目标。 游戏开发人员应高度重视确保自己发布的游戏不会为攻击者留下新的安全漏洞。 游戏开发人员有责任帮助防止其客户的计算机被恶意网络数据、用户修改或篡改入侵,并也将从中获益。 如果漏洞被利用,可能会导致客户和/或资金损失。 本文概述并解释了一些常见的方法和工具,可在不过度增加开发时间的情况下提高代码安全性。

开发团队在发布产品时最常犯的三个错误包括:

  • 需要管理权限。 游戏不应需要管理权限。 有关详细信息,请参阅游戏开发人员的用户帐户控制
  • 不使用自动保护。 开发人员一般不使用 /GS/SAFESEH/NX。 使用这些编译/链接标志可以发现或消除许多基本的安全漏洞,而不会明显增加工作负荷。 本文稍后将讨论这些标记。
  • 使用被禁止的 API。 有许多 API(strcpystrncpy 等)容易导致程序员出错,并容易产生安全漏洞。 开发人员应将这些 API 替换为安全版本。 Visual Studio 2005 随附了一个用于分析二进制文件的工具,它可以自动检查对象文件中对不安全 API 的引用。 有关如何处理该工具生成的信息的详细信息,请参阅 Martyn Lovell 撰写的用 Visual Studio 2005 安全 C 和 C++ 库抵御代码攻击一文。 此外,还可以获取 banned.h 头文件,它有助于删除代码中的禁用函数(请参阅 Microsoft的免费安全工具 - banned.h)。

所列的每个错误不仅很常见,而且很容易改正,不会对开发工作负载、编码标准或功能造成重大改变。

不安全代码示例

下面是攻击者执行缓冲区溢出攻击所造成的全部损失的简单示例:

void GetPlayerName(char *pDatafromNet)
{
    char playername[256]; 
    strncpy(playername, pDatafromNet, strlen(pDatafromNet));

    // ...
}

从表面上看,这段代码没有问题,毕竟它调用了一个安全函数。 来自网络的数据被复制到一个 256 字节的缓冲区中。 strncpy 函数依赖于在源字符串中找到一个 NULL 终止符,或受限于所提供的缓冲区计数。 问题在于缓冲区的大小不正确。 如果来自网络的数据未经验证或缓冲区大小错误(如本例所示),攻击者只需提供一个大缓冲区,即可在缓冲区结束后用网络数据包中的任何数据覆盖堆栈数据。 这样,攻击者就可以通过覆盖指令指针和更改返回地址来执行任意代码。 本例给我们的最基本启示是,在输入内容得到验证之前,千万不要轻信。

即使数据最初不是来自网络,也仍会存在潜在风险。 现代游戏开发需要许多人设计、开发和测试同一个代码库。 无法知道将来会如何调用该函数。 经常问问自己数据从何而来,攻击者能控制什么? 虽然基于网络的攻击最为常见,但它们并不是制造安全漏洞的唯一方法。 攻击者能否以打开安全漏洞的方式来创建修改器或编辑保存的文件? 用户提供的图像和声音文件怎么办? 这些文件的恶意版本可能会发布到 Internet 上,给客户带来危险的安全风险。

顺便提一句,可以使用 strsafe.h 或 Safe CRT 代替 strncpy 来更正示例。 Safe CRT 是对 C 运行时的全面安全检查,并且也是 Visual Studio 2005 的一部分。 有关安全 CRT 的详细信息,请参阅 Michael Howard 撰写的增强 CRT 的安全性

提高安全性的方法

有几种方法可以提高开发周期中的安全性。 以下是一些最佳方法:

阅读有关安全性的内容

Michael Howard 和 David LeBlanc 合著的《编写安全代码,第二版》一书深入浅出地阐述了防止攻击和减少漏洞利用的策略和方法。 该书从设计发行版的安全方法开始,到网络应用程序的安全技术,涵盖了游戏开发人员需要帮助保护自己、产品和客户免受攻击的各个方面。 该书可用于在开发工作室中灌输安全文化。 不要认为代码安全只是开发人员或测试人员的问题。 将安全性视为整个团队(从项目经理、设计师、开发人员到测试人员)在开展项目时都应考虑的问题。 参与审查过程的人员越多,在发布前发现安全漏洞的几率就越大。

《编写安全代码,第二版》可在 Microsoft Press Store 中找到,更多一般安全信息可在 Michael Howard 的《通过减少攻击面抵御未来攻击》中找到。

Michael Howard、David LeBlanc 和 John Viega 就这一主题撰写了另一本涵盖所有常见操作系统和编程语言的书,名为《软件安全的 19 宗罪》

有关游戏的安全演示可在 Microsoft XNA 开发人员演示下载页面上找到。

威胁建模分析

威胁建模分析是一种评估系统安全的好方法,它不是用一种特定的语言或使用一种工具,而是一种广泛的、端到端的方法,只需几次会议就能完成。 如果实施得当,线程模型可以找出系统的所有优缺点,而不会给项目增加大量的工作量或会议时间。 威胁建模方法还强调了在开发之前和开发过程中评估系统安全性的理念,以帮助确保进行全面评估,同时关注风险最大的功能。 可以把它看作是一个安全探查器。 威胁建模不基于特定的语言,也不依赖特定的工具,因此可用于任何开发工作室的任何类型项目。 威胁建模也是一种很好的方法,可以强化安全是每个人的责任而不是别人的问题这一理念。

在进行威胁建模时,要特别注意:

  • UDP 数据源
  • 无需对数据源进行身份验证
  • 作为大范围信息收集工作的一部分,经常和通常被探查的数据源
  • 客户端直接向其他客户端发送数据的任何功能

这些领域很有可能存在安全漏洞。

有关威胁建模的更多信息,请参阅威胁建模以及 Frank Swiderski 和 Window Snyder 合著的《威胁建模》一书。

数据执行防护 (/NX)

数据执行防护 (DEP) 是一种缓解多重漏洞利用的最新工具。 如果在编译命令中包含开关 /NX,Visual Studio 就会在内存页上标记标志,表示代码是否有权执行。 任何试图在没有 EXECUTE 权限标记的内存页中执行的程序都会被强制终止。 该预防措施在处理器层面实施,将对使用自修改代码或本地 JIT 语言编译器的开发人员产生影响。 目前,只有 AMD 的 Athlon64 和 Opteron 处理器以及 Intel 的 Itanium 和最新的 Pentium 4 处理器支持防止执行功能,但预计未来所有 32 位和 64 位处理器都将支持防止执行功能。 (开发人员使用的拷贝保护方案可能会受到执行保护的影响,但 Microsoft 一直在与拷贝保护供应商合作,以尽量减少这种影响)。使用 DEP 是一种良好的做法。

有关 DEP 的更多详细信息,请参阅数据执行防护

缓冲区安全检查 (/GS) 和映像具有安全异常处理程序 (/SAFESEH)

缓冲区安全检查(由编译器标志 /GS 指定)和图像有安全异常处理程序(由链接器标志 /SAFESEH 指定,首次在 Visual Studio .NET 2003 中实现)可以使开发人员保护代码安全的工作变得更容易一些。

使用 /GS 标记会导致编译器针对某些形式的基于堆栈的缓冲区超限进行检查,这些超限可能会被利用来覆盖函数的返回地址。 使用 /GS 无法检测到所有潜在的缓冲区超限,因此不应将其视为万能技术,而应视为一种良好的深度防御技术。

使用 /SAFESEH 标记将指示链接器仅在生成可执行文件或 DLL 的安全异常处理程序表时才生成可执行文件或 DLL。 安全结构化异常处理 (SafeSEH) 通过确保在调度异常之前,异常处理程序已在位于映像文件中的函数表中注册,从而消除了作为缓冲区超限攻击目标的异常处理。 Windows XP SP2、Windows Server 2003、Windows Vista 和 Windows 7 可启用这些保护功能。 此外,要使 /SAFESEH 正常工作,必须采用全有或全无的方法。 所有包含绑定到可执行文件或 DLL 的代码的库都必须使用 /SAFESEH 进行编译,否则将无法生成表格。

有关详细信息,请参阅缓冲区安全检查 (/GS) 和映像具有安全异常处理程序 (/SAFESEH)。

另请参阅有关 Microsoft Visual Studio 2012 的 /SDL 标志和 Visual Studio 2012 的 /GS 标志的增强功能的信息。

PREfast

PREfast 是 Microsoft 提供的一款免费工具,用于分析编译的 C 或 C++ 中的执行路径,以帮助查找运行时错误。 PREfast 通过排查所有函数中的所有执行路径并评估每条路径的问题来运行。 该工具最初用于开发驱动程序和其他内核代码,现在可以帮助游戏开发人员消除一些难以发现或被编译器忽略的错误,从而节省时间。 使用 PREfast 是减少工作负荷、集中开发团队和测试团队工作的绝佳方法。 在 Visual Studio Team Suite 和 Visual Studio Team Edition for Software Developers 中,PREfast 可作为代码分析使用,通过编译器开关 /analyze 启用。 (Windows 软件开发工具包随附的免费版编译器中也有该选项。)

注意

Visual Studio 2012 的所有版本都支持 /analyze。 有关 Visual Studio 所有版本中代码分析可用性的详细信息,请参阅代码分析中的新增功能

 

通过使用标头注释(尤其是缓冲指针参数),PREfast 可以暴露出更多问题,如内存覆盖 bug,这是崩溃和潜在安全漏洞的常见来源。 这是通过使用标准注释语言 (SAL) 来实现的,SAL 是 C/C++ 函数原型的一种标记形式,它提供了有关预期指针参数语义以及与长度参数、声明缓冲区大小等相关性的附加信息。Windows 操作系统的所有头文件都有注释,在自己库中的公共 API 头文件中添加 SAL 标记可使 PREfast 在客户端代码中对此类 API 执行更详细、更严格的检查。 有关 SAL 的介绍和更多信息的链接,请参阅 Michael Howard 的博客文章标准批注语言 (SAL) 简介

Windows 应用程序验证工具

Windows 应用程序验证程序或 AppVerifier 可以帮助测试人员在一个工具中实现多种功能。 AppVerifier 是一款为使常见的编程错误更易于测试而开发的工具。 AppVerifier 可以检查传递给 API 调用的参数,注入错误输入以检查错误处理能力,并记录注册表和文件系统的更改。 AppVerifier 还能检测堆中的缓冲区超限,检查访问控制列表 (ACL) 是否已正确定义,并强制安全使用套接字 API。 虽然 AppVerifier 并非详尽无遗,但它可以成为测试人员工具箱中的一个工具,帮助开发工作室发布高质量的产品。

有关应用程序验证程序的详细信息,请参阅应用程序验证程序在整个软件开发生命周期中使用应用程序验证程序

模糊测试

模糊测试是一种半自动化的测试方法,可增强当前的测试方法。 模糊测试背后的核心理念是通过输入随机数据对所有输入进行全面评估,看看哪些数据会被破坏;这包括所有网络数据、MOD 和保存的游戏等。模糊测试非常容易进行。 只需插入随机字节、翻转相邻字节或否定数值,即可更改格式良好的文件或网络数据。 0xff、0xffff、0xffffffffff、0x00、0x0000、0x00000000 和 0x80000000 等值在模糊测试时很容易暴露安全漏洞。 可以使用 AppVerifier 来观察由此产生的交互组合。 虽然模糊测试并非详尽无遗,但它易于实施和自动化,而且可以捕获到更难以捉摸和不可预测的错误。

有关模糊测试的详细信息,请参阅 Gamefest 2007 演示游戏安全中的实用步骤

Authenticode 签名

Authenticode 是一种确保用户收到的可执行文件、DLL 文件和 Windows 安装程序包(.msi 文件)与开发人员发布的内容一致的方法。 Authenticode 结合使用加密原理、可信实体和行业标准,可验证可执行内容的完整性。 Microsoft 微软提供了一个加密 API CryptoAPI,可用于自动检测签名代码的篡改。 如果在发布后出现安全漏洞,证书会被撤销,所有用该证书签署的代码都将停止验证。 撤销证书将撤销对使用该证书签署的所有游戏的验证。 Windows 设计为与 Authenticode 签名配合使用,在特定情况下会提醒用户注意未签名的代码,这些代码可能会使用户的电脑受到攻击。

Authenticode 不应被视为消除安全问题的方法,而应被视为在可执行文件发布后检测篡改的方法。 包含可利用安全问题的可执行文件或 DLL 可以使用 Authenticode 进行签名和验证,但仍会将安全问题引入新系统。 只有在验证了产品或更新的安全性后,才应对代码进行签名,以向用户保证所发布的产品未被篡改。

即使开发人员认为他们的发布版本不会被修改,其他技术和服务也依赖 Authenticode。 代码签名易于集成和自动化;开发人员没有理由不对其发布的版本进行签名。

有关 Authenticode 签名的详细信息,请参阅面向游戏开发人员的 Authenticode 签名

尽量减少特权

一般来说,进程应该以运行所需的最低权限运行。 在 Windows Vista 和 Windows 7 中,可通过使用用户帐户控制来实现这一功能,允许游戏以标准用户而非管理员身份运行。 对于 Windows XP,通常游戏总是以管理员身份运行。 即使在 Windows Vista 和 Windows 7 中,有时也需要提升到完全管理员权限才能进行某些特定操作。

在进程以完全管理权限运行的情况下,通常只需要超出标准用户权限的少数权限。 管理访问权限包括许多合法代码不需要的权限,但攻击者可以通过程序中的某些弱点使用这些权限。 此类权限的例子包括 SE_TAKE_OOWNERSHIP、SE_DEBUG、SE_CREATE_TOKEN、SE_ASSIGNPRIMARYTOKEN、SE_TCB、SE_SECURITY、SE_LOAD_DRIVER、SE_SYSTEMTIME、SE_BACKUP、SE_RESTORE、SE_SHUTDOWN 和 SE_AUDIT(请参阅权限常量)。

虽然程序一旦启动就无法获得更多权利,但它可以轻易放弃权利。 启动时,进程可以立即使用 Win32 API 来删除它不需要的权限。

使用 Windows 安全功能

Windows Vista 和 Windows 7 包含许多可提高代码安全性的新功能。 用户帐户控制当然是最需要了解和接受的功能,但还有其他功能。 除了 Windows XP SP2 技术(如 Windows 防火墙、数据执行防护、缓冲区安全检查和安全异常处理程序,这些技术在 Windows Vista 和 Windows 7 中也有提供)之外,还有三个更新的安全功能需要考虑:

  • 可选择的地址空间布局随机化功能。 可以通过在 Visual Studio 2005 Service Pack 1 或 Visual Studio 2008 上使用选项 /DYNAMICBASE 进行链接来启用此功能。 这会导致系统随机调整进程空间中许多关键系统 DLL 的位置,使编写可在 Internet 上广泛传播的攻击程序变得更加困难。 Windows XP 和旧版本的 Windows 会忽略此链接标志。
  • 堆损坏会导致一整类安全漏洞,因此 Windows Vista 和 Windows 7 的内存系统现在支持一种模式,一旦检测到堆损坏,就会终止进程。 调用 HeapSetInformationHeapEnableTermianteOnCorruption 将选择加入此行为。 此调用在 Windows XP 和旧版本的 Windows 上失效。
  • 在编写服务时,可以使用一项新功能对其进行配置,以指定哪些权限是实际需要的,以及限制对特定 SID 的资源访问。 这是通过 ChangeSeviceConfig2 使用 SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO 和 SERVICE_CONFIG_SERVICE_SID_INFO 来完成的。

总结

为当前和未来的市场开发一款游戏既费钱又费时。 发布一款存在安全问题的游戏,最终会花费更多的金钱和时间来妥善解决问题。 因此,为了所有游戏开发者的利益,在游戏发布前整合各种工具和技术以减少安全漏洞。

本文中的信息只是介绍了开发工作室可以做些什么来帮助自己和客户。 有关一般安全实践和安全信息的详细信息,请访问 Microsoft安全开发人员中心