如何利用系统重启攻击获得Win11 Bitlocker秘钥?又是一个长期忽略安全功能的悲剧故事

芯片(集成电路)话题下的优秀答主

266 👍 / 35 💬

文章最后新增问答区回应

最近一个“新”发现的漏洞刷爆了我的朋友圈,在这篇文章 [1]中,作者成功利用重启攻击,在UEFI Shell下获得了Win11 Bitlocker解码硬盘内容的秘钥,非常可怕。重启攻击(platform reset attack)并不是什么新鲜的话题,我比较吃惊的有两点:

  1. 微软竟然在内存中存储了Bitlocker秘钥的明文!
  2. 现在居然有了这么傻瓜的工具,能非常方便地利用重启攻击来达成有意义的目标!

对付重启攻击实际上,十来年前就有了规避方案(MOR),但似乎应用该方案的情况十分令人担忧。为此,今天我们就来一起看看这个安全事件的原理、攻击过程和应该采取的应对方案。

原理和背景

先问你两个问题:如果系统忽然重启(Reset),内存中原先的内容是不变还是会归零?如果是忽然断电呢?

也许你认为重启后一切都归零了,对大部分硬件寄存器,是这样的。但内存并不是。内存单元实际上是一个个小电容:

重启动并不会让它们自然归0,而是要去主动清空(Zero),才会清成0。这就给了黑客上下其手的空间。

于此同理,断电,感觉应该让内存清零,但实际上因为小电容放电(fading)需要一个过程,个过程叫做Decay。JEDEC规定每过64ms就要refresh一次,也就是充电一次,这个由内存控制器来完成,不需要CPU内核参与。这个64ms实际上是个余量非常大的时间间隔,你有没有想过如果我们不给内存刷新,也就是不充电,会发生什么?有人做过一个实验 [2]

一张蒙娜丽莎的灰度图像,不刷新(充电),在间隔5秒、30秒、60秒和5分钟后读出来的样子,可以看到5秒钟后读出来,几乎看不出来有什么损失,甚至在1分钟后还可以勉强看出原理的样子,5分钟后才变成了黑白条(想想为什么不是纯黑):

内存不刷新会怎么样?

内存的电容特性,让黑客可以利用物理攻击( 老狼:内存不刷新会怎样?内存的物理攻击和旁路攻击)或者重启攻击来获得敏感的内容,本次就是利用了重启攻击。

攻击过程

作者利用意外重启来保持内存内容,并在UEFI Shell下制作了一些工具和脚本(开源了),来辅助攻击。

1。制作工具:

首先要制作一个UEFI Shell下可以识别的U盘和分区,为了防止读者连这个都不会,作者还“贴心”地制作了脚本flashimage.sh,并开源了 [3]。该脚本不但帮忙分配一个UEFI可以认识的分区,还把攻击工具链都copy到该分区中。

2。制造一次忽然重启

实际上非常简单,就是按下reset按键的事(作者为了怕读者不知道在那里或者没有连接reset pin,还贴心地提出前面板针脚图)。作者还给出了引发Reset的时机:在 Windows 加载但登录屏幕出现之前重新启动系统最有效。

3。UEFI Shell下Dump内存

reset后,立即用前面制作的U盘启动。因为前面步骤在U盘中放进去了Shell和内存Dump工具,接下来就是用该工具(app.efi)把没有清零的内存,分块存储在U盘中,方便后面分析:

4。得到秘钥

因为 FAT32 文件系统的4GB 大小限制,app.efi应用程序会生成多个转储文件。作者在工具目录中包含了一个名为的concatDumps程序,它可以按时间顺序将多个转储合并为一个。转储的内容将包括当时内存中的任何原始数据。还有一个searchMem搜索工具,可以在转储中搜索十六进制数据,它将找到该十六进制模式实例的偏移量。

接下来是比较让人吃惊的部分。作者分析发现一些明显的tag(应该是数据结构的signature),可以顺藤摸瓜揪出秘钥。作者写了一个名为的pooltag.txt的文件,其中包含tag列表及其各自用途的详细信息。

在继续之前,我想对微软表示感谢,因为他们非常善良,清楚地标记了加密密钥在内存中出现的位置。回到 Windows 7,密钥恢复就像找到FVEc池标记一样简单,该标记对应于下的加密分配fvevol.sys。在 Windows 8.1 和 10 上,可以在标记为的内存池中找到密钥, Cngb该标记对应于ksecdd.sys模块。在我对 Windows 11 内存转储的整个研究过程中,我都无法在这两个地方找到密钥,但我在另外两个位置找到了它。

作者在dFVE tag后面的内存中找到了FVEK (Full Volume Encryption Keys,全卷加密密钥)密钥的第一个位置,它属于用于 BitLocker 驱动器加密的全卷加密崩溃转储过滤器。如下图蓝色下划线表示,而 FVEK 密钥用红色突出显示。它前面还带有0x0480,表示正在使用的加密类型的前缀,据推测是AES-128。

第二个位置位于None Tag下,与ExAllocatePool例程调用有关。这次可以看到密钥的前半部分出现了两次,后半部分出现了一次。

上图意味着密钥是:

b2cbc06071931b7cc50b59f8789571f4dd815c2008e93c02d5c6cd98c83ef54b

还需要将0x8004以小端格式添加到密钥的开头,如下所示:

0480b2cbc06071931b7cc50b59f8789571f4dd815c2008e93c02d5c6cd98c83ef54b

接下来,需要获取该十六进制并将其转储到文件中,可以像这样完成:

echo "0480b2cbc06071931b7cc50b59f8789571f4dd815c2008e93c02d5c6cd98c83ef54b" | xxd -r -p > output.fvek

如果正确完成了所有操作,则可以使用output.fvek来解锁受 Bitlocker 保护的分区并访问卷上的任何数据。

如何防护

实际上,TCG组织十几年前就对此有了应对方案,那就是MOR(memory overwrite,内存覆盖)特性。内存覆盖 (MOR) 功能是为了缓解平台重置攻击。它与 TPM 硬件无关,而是一项纯软件功能。TCG MOR规范定义了两个 UEFI 变量:

平台内存初始化模块(MRC Wrapper)应检查MemoryOverwriteRequestControl 变量。如果此变量不存在或此变量指示 MOR 请求,则内存初始化模块应在启用内存控制器后清除内存。

MOR 变量由MemoryOverwriteControl模块管理 。MOR 变量在TcgMor.c 入口点MorDriverEntryPoint () 处创建 ,并在 ReadyToBoot 事件OnReadyToBoot ()处清除变量。

由于 MOR 变量是读/写的,因此即使高权限软件请求 MOR,恶意软件实体也可能直接清除 MOR 请求。我们需要一个安全的 MOR 解决方案来防止此类攻击,为此引入 MorLock 变量来保护 MOR 变量。详细过程,感兴趣的同学可以阅读TCG Trusted Boot Chain [4]来仔细了解,这里就不展开了。

思考

既然MOR可以对抗重启动攻击,为何MOR特性在实践应用中打开的很少呢?因为它会带来额外的开销:人们必须等待很长时间才能让平台内存模块清除所有系统内存,尤其是在具有大内存的服务器平台上。尽管有些芯片有Fast Zero硬件来辅助完成内存清零,但也会需要一定时间,更别提有的平台没有这个功能,就需要软件清零了,对服务器这种大几百个GB的内存来说,耗时相当长。消费品市场,尽管内存很少,但对重启时间更加敏感,更是在大多数情况下是关闭的了。安全和性能,似乎永远不可得兼,需要我们仔细衡量,安全更重要,还是性能和便利更加重要?另外,我观察到,对抗内存物理攻击的TME,在我看到的大多数Intel Client平台都是默认关闭的,也许,这些主板厂商认为,大约2%的性能损失,比数据更加重要吧!

说起Fast Zero Memory(FZM),它尽管和本次攻击关系不大,却非常有趣。下一次,我们一起来了解一下。

这里统一回应一下热闹的评论区。看起来这次攻击之所以成功,有两个安全开关也被关闭了:

  1. 安全启动。打开的话,app.efi应该没有合格的签名,而不能够运行。但其实可以自己去用自带mmio去dump,就是麻烦一点。
  2. TME。如果打开的话,每次重启RNG重新生成一把新密钥,以前的内容也作废了,也分析不到内容

在我们的公众号中,意犹未尽的同学可以去那边做个Survey,看看别的小伙伴对AI和BIOS结合,是怎么想的。

参考

  1. Dumping Memory to Bypass BitLocker on Windows 11 https://noinitrd.github.io/Memory-Dump-UEFI/
  2. Memory Decay https://www.sciencedirect.com/science/article/pii/S1742287616300032
  3. 攻击源码 https://github.com/NoInitRD/Memory-Dump-UEFI
  4. TCG Trusted Boot Chain https://tianocore-docs.github.io/edk2-TrustedBootChain/release-1.00/3_TCG_Trusted_Boot_Chain_in_EDKII.html

专栏:UEFI和BIOS探秘