有人让 DOOM 在 PDF 文件里跑了起来。这说明了一些事

计算机圈里流传着一个老笑话:DOOM 终将在一切设备上运行。这不是夸这款游戏,而是一种挑战。这款 1993 年的射击游戏已经被塞进过自动柜员机、打印机、验孕棒、计算器、一块乐高积木,以及 MacBook 的触控栏。

2025 年 1 月,一名高中生又给这份名单添了一项。他让 DOOM 跑在了一个 PDF 文件里面

在 PDF 文件内部运行的 DOOM,画面由 ASCII 字符组成

简短版

一名高中生用 2020 年版的 Emscripten 把 DOOM 编译成 asm.js,并让它在 PDF 里跑起来:屏幕的每一行用一个表单字段来充当显示器,320×200,六级灰阶,每帧约 80 毫秒。整个过程没有用到任何漏洞。它用到的每一项功能都写在 PDF 规范里,并且完全按设计运作。这才是有意思的地方——也是值得为自己的文档花上一次十分钟的理由。

这是一件漂亮的作品,也是一个很好的玩笑。同时,它最清楚地印证了本站反复强调、而人们有理由难以相信的一点:PDF 不是一张页面的照片,而是一个能够执行代码的容器。

它究竟是怎么运作的

这个项目叫 doompdf,作者是一位自称 ading2210 的开发者。它的机制比这个噱头本身更有意思,一共由四部分组成。

PDF 支持 JavaScript。不是作为附加组件或插件——它就是格式的一部分,有自己的标准库,浏览器也在内置的 PDF 引擎里实现了它。它存在的目的是让表单能校验你输入的内容、让交互式文档能响应点击。没有人是为了游戏才加上它的。

DOOM 的源代码是公开的——不过实情比这句简写更细致。id Software 在 1997 年 12 月 23 日公开了代码,但许可证只允许用于教育用途,而且公开的是Linux 移植版的代码,不是 DOS 原版,原因是 DMX 音频库另有授权。真正转为 GPL 是将近两年之后,1999 年 10 月 3 日。要紧的是后面这个日期:它让 DOOM 从”可以读”变成了”可以合法重建”。这也正是这款游戏不断出现在离奇地方的原因。

把两者接起来的,是一个刻意选用的过时编译器。ading2210 用的是 Emscripten 1.39.20——2020 年的版本——因为它的产物是 asm.js 而不是 WebAssembly。现代 Emscripten 生成 WebAssembly,而 PDF 里没有 WebAssembly 运行时。asm.js 是普通 JavaScript 的一种受限方言,因此凡是能跑 JavaScript 的地方它都能跑。搬出旧工具链不是怀旧,那是唯一的入口。

接下来是显示,也是最让人笑出声的一部分。PDF 没有画布,没有帧缓冲,没有任何可以画像素的东西。但它有表单字段。于是游戏 320×200 的画面被渲染成 ASCII 字符——屏幕每一行对应一个文本字段,六级灰阶——刷新速度取决于这些文本能被重写多快,算下来大约每帧 80 毫秒。输入也走同一条路:用 PDF 引擎本来就会处理的字段和按钮。你确实是在一张可填写的表单里玩 DOOM。

它跑得很慢,是单色的,但完全可玩;它和所依赖的代码一样以 GPL v2 发布,你可以在基于 Chromium 的浏览器里自己试。构建版本甚至允许你换上自己的 WAD 文件。

后来他又在里面跑起了 Linux

几周之后,同一位开发者发布了 linuxpdf:一个完整的 Linux 内核在 PDF 文件内部启动,办法是用同样的 asm.js 技巧编译 RISC-V 模拟器 TinyEMU。内核启动需要 30 到 60 秒——作者形容为比真实硬件慢一百倍以上——操作则依靠一个由 PDF 按钮拼出来的虚拟键盘。

文档里的 DOOM 是个玩笑。文档里一颗模拟出来的 CPU 启动操作系统,就是一个论点了。在这两者之间的某处,这件事已经与 DOOM 无关。

不好笑的那一部分

下面这个念头值得停下来想一想。doompdf 里没有任何东西属于漏洞利用。没有发现漏洞,没有攻破什么,没有涉及任何安全缺口。它用到的每一样东西,都是 PDF 规范中有文档、有意为之的功能,完全按设计工作。

一只猫坐在电脑前——DOOM 几乎被移植到了所有能想到的设备上

这意味着:让 PDF 得以运行 1993 年射击游戏的那份能力,同样存在于你明天要打开的那张发票里。它不是潜伏,也不是隐藏——它就在那儿,没被用到,因为大多数文档没有理由去用。

这是这个故事诚实的版本,而且它两面都切。那句”文档能执行代码,所以要当心”的推理,也必须承认这段代码所处的世界有多狭小。

PDF 里的脚本能做什么、不能做什么

关于 doompdf 的报道大多在这里变得含糊,所以有必要说具体些。答案完全取决于你用什么打开了这个文件——而两种常见情形差别极大。

能力浏览器内置查看器(Chrome、Edge、Firefox)Adobe Acrobat Reader
打开时执行脚本可以可以,除非被关闭
读写表单字段可以可以
读取 Cookie 或浏览器存储不能不适用
访问外层的 HTML 页面(DOM)不能不适用
发起网络请求非常受限更宽——追踪像素和 NTLM 凭据外泄就活在这里
接触本地文件不能受限,但 API 面大得多
启动外部程序或附件不能需确认——历来是恶意文档走的路

请从正确的方向读这张表。在浏览器里,带脚本的 PDF 跑在一个又小又无聊的盒子里:没有 Cookie,没有存储,接触不到外层页面。这正是 doompdf 只是趣闻而非安全事件的原因,也是”DOOM 能在 PDF 里跑”不构成害怕 PDF 之理由的原因。

值得注意的是桌面阅读器那一列。它拥有规范所描述的完整 JavaScript API,因为它本来就是用来处理真实商务文档的。正是在这种环境里,一份文档可以被要求在打开的瞬间向外报到、在不同阅读器里表现不同,或者请求打开它随身带着的东西。

因此,无论用什么软件,有三件事始终成立,也正是值得记住的三件事。

  • PDF 可以携带在打开瞬间就执行的指令,那时你还什么都没读到。
  • PDF 可以因打开它的软件不同而表现不同:你看到的文件,未必就是同事看到的那一份。
  • PDF 携带的内容多于它显示的内容:附件、隐藏图层、修订历史,以及你从未打算发出去的元数据。

这些都不会让 PDF 在日常意义上变得危险。这个月你会打开几十份,而且每一份都不会有事。但它确实说明,我们大多数人脑子里那套模型——”不过是个文档”——在某些偶尔要紧的地方是错的。

几乎没人知道的那个设置

标准建议是”在 PDF 阅读器里关掉 JavaScript”,这建议不错。但只做这一步是不完整的——因为大多数人早就不在 PDF 阅读器里打开 PDF 了。他们在浏览器标签页里打开,而浏览器有自己独立的开关。

Firefox 出厂时 PDF 脚本是开着的。设置项是 about:config 里的 pdfjs.enableScripting;在 Firefox 87 及以前为 false,自 Firefox 88 起为 true。总开关 javascript.enabled 管不到它:PDF 查看器是另一个世界,用另一个设置。

Chrome 和 Edge 根本没有专门针对 PDF 脚本的开关。PDF 里的脚本受该文件来源站点的 JavaScript 权限管辖,你可以在地址栏的控件里按站点关闭,或者在 chrome://settings/content/javascript 里全局关闭——那是个粗糙得多的工具。

Adobe Acrobat Reader 才有大家心里想的那个开关:首选项 → JavaScript → 启用 Acrobat JavaScript。这个勾值得取消,因为 Acrobat 正是 API 最大的那个阅读器,而在日常工作中几乎没人会有意去用带脚本的 PDF。

实际做法是:在 Acrobat 里关掉、浏览器不动,这是合理且适度的选择。而以为自己已经处处关掉、实际上只关了 Acrobat,就不是了。

知道了这些该做什么

实际要做的事很少,也值那十分钟。

在桌面阅读器里关掉 JavaScript。Acrobat Reader 中的路径是:首选项 → JavaScript。这会消掉一整类风险,代价只有——必须承认——在表单字段里玩 DOOM 的乐趣。

保持阅读器更新。脚本是一条入口。解析字体和图像的代码缺陷是另一条,而后者正是更新所修补的;从历史上看,那还是更常见的一条。

把意料之外的文档当作意料之外来处理。一张来自你从未买过东西的公司的发票,无论以什么格式送到,都值得停一秒想想。这比任何设置都值钱。

请注意这三条的共同点:没有一条要求你不再使用 PDF,也没有一条要求你怀疑一切文档。它们相当于出门锁门——便宜、无聊,而且正因为风险很小却不为零,所以才合理。

这个项目为什么值得它得到的关注

把 doompdf 归入”网络奇谈”然后翻篇,是很容易的。我认为它比那更有用,理由与安全表演毫无关系。

多数人对文件格式的理解,完全建立在这些格式被用来做什么之上。文档是用来读的。表格是用来算数的。图片是用来看的。这套模型几乎总是成立,正因如此它才极难撼动——也正因如此,”PDF 可以包含活动内容”这句解释通常只换来礼貌的怀疑。

一款能在文档里玩起来的游戏,十秒钟就做到了文章用一千字都吃力的事。它把一个抽象论断换成了一个可以点击的东西。

而更深一层的意思远远超出 PDF。格式会在几十年里不断累积功能。每一项新增对当时解决真实问题的人来说都合情合理——表单校验确实有用,一份能核对自己算术的文档同样有用。结果就是,我们每天交换的这些普通东西,能力远比它们的名字所暗示的要大。而一项能力一旦写进规范,就对每一个读规范的人开放。

对日常使用意味着什么

这里没有一句是在反对 PDF。要发送一份在任何地方、任何机器上、多年之后看起来都一样的文档,它仍然是最好的格式。这是个真正困难的问题,而 PDF 解决了它。让 doompdf 成为可能的那些功能,也正是让可填写的报税表成为可能的那些功能。

这是在主张:知道自己手里拿的是什么。让文档能装下一款游戏的那份多才多艺,同样让它能装下脚本、附件、隐藏图层,以及你从未打算发出去的元数据。这些都值得了解,而且没有一项值得惊慌。

这也是 PDF Manipulator 完全在你自己电脑上运行的部分原因。合并、拆分或转换文件时,参与方越少越好——不是因为在线服务居心不良,而是因为一份从未离开过你机器的文档,比一份在别人服务器上待过的文档要好判断得多。

完全离线处理你的 PDF——PDF Manipulator 免费 →

参考资料

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部