第八章 · PCR 7 的盲区:Evil Maid 攻击与 Secure Boot 的意义
第六章结尾我们留了一个悬念:把 LUKS 卷密钥绑到 PCR 7 之后,开机确实自动解锁了,但 PCR 7 到底度量了些什么?如果有人趁你不在动过 /boot 里的文件,TPM 会拒绝给密钥吗?
答案是:不会。而且不是“碰巧不会“,是“在设计上就不归它管“。
这一章把这个盲区彻底推演清楚。它是全书思辨性最强的一章,理解透了,你就明白为什么 Secure Boot(安全启动)这个常被嫌麻烦、甚至被人教你怎么关掉的东西,在可信启动体系里是必需品而不是摆设。
本章目标
- 搞清楚 PCR 7 里到底度量了什么,为什么它管不着内核和 initramfs
- 对着第六章的实验环境,完整推演一次 Evil Maid(恶意的女仆)攻击
- 讲透“度量只是记录,不是拒绝“这个最容易被误解的认知
- 比较两种防御哲学:预防(Secure Boot)与检测(systemd-pcrlock)
- 可选:在实验虚拟机上亲手复现一次攻击,看着防御失效
背景知识:PCR 分工速查表
先回顾 PCR 的分工。不同固件的度量细节有差异,下面这张表以我们的实验环境(OVMF 固件 + Limine 引导 + UKI + systemd)为准,对应第五章里你重放过的那份度量日志:
| PCR | 度量内容 | 谁度量 |
|---|---|---|
| 0 | OVMF 固件代码与配置 | 固件 |
| 4 | Limine、UKI 镜像(引导管理器及其加载的内核映像) | 固件 / bootloader |
| 5 | GPT 分区表、启动配置变化 | 固件 |
| 7 | SecureBoot 开关、PK / KEK / db / dbx 密钥库 | 固件 |
| 11 | UKI 各段(内核、initramfs、cmdline 等)+ systemd 启动阶段标记 | systemd |
注意:这张表不是从标准文档抄来的理想分工,而是实验环境实测出来的。换一台机器、换一个固件,同一编号的 PCR 里装的东西可能不同。所以第五章练的重放功夫不是白练的——想知道某台机器的 PCR 里有什么,去读它自己的 event log。
盯着 PCR 7 那一行多看几秒:它度量的是 Secure Boot 的验证规则和密钥库,而不是被验证的代码本身。这个区别是整章的钥匙。
PCR 7 里到底有什么
翻开第五章你重放过的日志,PCR 7 对应的事件类型是 EV_EFI_VARIABLE_DRIVER_CONFIG(EFI 驱动配置变量)。用 tpm2_eventlog 过滤出来看,大致是这样的四笔:
- EventNum: ...
PCRIndex: 7
EventType: EV_EFI_VARIABLE_DRIVER_CONFIG
...
UnicodeName: SecureBoot
- EventNum: ...
PCRIndex: 7
EventType: EV_EFI_VARIABLE_DRIVER_CONFIG
...
UnicodeName: PK
- EventNum: ...
PCRIndex: 7
EventType: EV_EFI_VARIABLE_DRIVER_CONFIG
...
UnicodeName: KEK
- EventNum: ...
PCRIndex: 7
EventType: EV_EFI_VARIABLE_DRIVER_CONFIG
...
UnicodeName: db
一笔一个变量:
- SecureBoot 变量:Secure Boot 开关的值(0 或 1)
- PK(Platform Key,平台密钥):平台的最高信任根
- KEK(Key Exchange Key,密钥交换密钥):用于更新 PK 和签名数据库
- db / dbx(签名数据库与吊销列表):哪些签名被信任、哪些被拉黑
注意看 EventType——它是“变量配置“,度量的动作发生在固件读取这些变量的时刻,而不是发生在“用这些变量验签某个文件“的时刻。固件度量的是这几个变量的内容哈希,和“我用这些规则验证过的那些文件“没有任何关系。所以 PCR 7 能回答的问题只有一个:
“Secure Boot 的配置和密钥库,和绑定那一刻相比,变了吗?”
它完全不知道、也不关心 Limine 是什么版本、内核是哪个构建、initramfs 里打了什么包——这些东西的哈希一个都不在 PCR 7 里。
而我们的实验环境 Secure Boot 是关着的(第二章为了折腾方便就没开)。也就是说 PCR 7 里躺着的是“SecureBoot=0 加一套默认密钥库“的哈希,一个基本静止的值。
Evil Maid 攻击:完整推演
现在把攻击走一遍。Evil Maid 得名于一个经典场景:你把笔记本留在酒店房间,清洁人员(或者任何能物理接触设备的人)趁你出门动了它。前提假设:攻击者有物理接触,但没有你的任何口令。
攻击面:明文的 /boot
回顾第六章实验环境的分区布局:
/dev/vda
├── vda1 /boot(vfat,明文,不在 LUKS 里)
└── vda2 /(LUKS 全盘加密,TPM 自动解锁)
/boot 为什么必须明文?因为固件要读它。OVMF 从 vda1 加载 Limine 和 UKI(Unified Kernel Image,统一内核映像)的时候,LUKS 还没解锁、TPM 也还没给密钥——这个阶段的磁盘内容对固件必须是可读的明文。
推论是:vda1 上的 UKI,任何能把磁盘挂到自己机器上的人都能读、能改。 LUKS 保护了根分区,但没有保护启动文件。
攻击动作
酒店场景,攻击者拿到 10 分钟物理接触:
- 引导一个自己带的 U 盘系统(或直接拆盘镜像)
- 把 vda1 上的 UKI 换成动了手脚的版本——比如在 initramfs 的启动脚本里加一行:拿到卷密钥解锁根分区之后,把密钥
curl到攻击者的服务器;或者更经典地,植入一个 rootkit 再正常挂载根分区,什么都不耽误 - 原样放回,走人
整个过程不需要任何口令,因为 vda1 是明文。
期望 TPM 救命,但落空了
你回到酒店,开机。此时我们“期望“的安全链条是:
- OVMF 加载被篡改的 UKI,会度量它 → PCR 4 和 PCR 11 的值变了 ✓(这一步确实发生,度量系统是忠实的)
- TPM 发现 PCR 值和绑定时不一致 → 拒绝释放 LUKS 密钥 ✗
第二步永远不会发生。回忆第六章绑定时的那条命令:
sudo systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=7 /dev/vda2
--tpm2-pcrs=7,白纸黑字:我们告诉 TPM,解锁的条件有且仅有 PCR 7。于是:
- Secure Boot 还是关着的,没人去动它
- PK / KEK / db / dbx 一个字节都没变
- 所以 PCR 7 的值和绑定那一刻一模一样
- TPM 核对策略:PCR 7 匹配,密钥照给
思考:这个场景里 TPM 出错了吗?没有,它忠实执行了你给的策略:“PCR 7 是这个值就放密钥”。度量系统出错了吗?也没有,PCR 4/11 忠实地记下了篡改。问题出在策略声明的关心范围里根本不包括 UKI。
攻击成功
恶意 initramfs 从 TPM 拿到卷密钥,解开根分区,攻击者的代码在一个完全解密的系统上运行。密钥外泄、rootkit 落地,都发生在系统正常启动的掩护下。
更隐蔽的版本:恶意 initramfs 拿到密钥、外泄之后,链式加载(kexec 或直接转交)回正版 initramfs,系统照常进桌面。你什么异常都看不到,而密钥已经在攻击者手里。
复盘整条链
整个链条里每个组件都“尽职尽责“:TPM 忠实执行了解封策略,度量系统忠实记录了篡改(PCR 4/11 确实变了),LUKS 忠实地用拿到的密钥解了密。
锁的钥匙,挂在了一扇没装锁的门上。
关键认知:度量只是记录,不是拒绝
这是本章最重要的一句话,值得单独讲透。
PCR 值的变化本身不阻止任何事。 PCR 是个只增不改的日志本(第三章说过,PCR 不能随意重置、只能 extend),它的职责是留证据,不是当保安。恶意代码被度量进了 PCR 4,恶意代码照样跑——度量和执行是两条平行的线。
真正起“门禁“作用的是你绑在 PCR 上的策略。当你把密钥 Seal 到 PCR 7,你实际是在向 TPM 声明:
“我只在乎 PCR 7 这份证据。其他 PCR 记得再准,与我无关。”
PCR 4/11 把篡改记录得清清楚楚,但你的策略没有引用它们,所以这些证据在解锁决策中被完全无视。篡改发生了、证据留存了、防御失效了——三位一体,互不干扰。
坑:很多人(包括我一开始)默认“绑了 TPM 就等于防篡改“。这是把“度量“和“度量 + 策略覆盖“混为一谈。防不防得住,取决于你的策略引用了哪些 PCR,以及那些 PCR 里度量的是什么。引用之前,先搞清楚里面有什么。
那绑 PCR 4/11 行不行?
既然 PCR 4/11 里有 UKI 的哈希,把密钥绑到它们上面,Evil Maid 不就解不开了吗?
行,攻击者确实解不开了。但你很快会发现自己也解不开了。
PCR 4/11 度量的是内核、initramfs、cmdline 这些每次升级都会合法变化的东西。Arch 上滚一次 pacman -Syu,内核更新了、initramfs 重建了,下次开机 PCR 值就变了——TPM 拒绝给密钥,你被锁在自己的系统门外。
除非你每次升级前都能预测新值、预先重绑(这正是下面哲学二干的事),否则这种绑定在生产上不可维护。这也是第六章选 PCR 7 的原因:不是因为它最强,而是因为它最稳——稳到连篡改都“稳定“地检测不到。
两种防御哲学
盲区讲清楚了,怎么补?业界有两条思路,哲学完全不同。
哲学一:预防——Secure Boot
思路:让攻击动作本身失败,而不是事后检测到。
Secure Boot 开启后,固件只执行带有合法签名的 EFI 文件。攻击者换上的恶意 UKI 没有你的签名,OVMF 直接拒绝加载——攻击在第一步就死了,根本轮不到 TPM 出场。
此时绑 PCR 7 反而就够了。PCR 7 绑定的语义变成了“Secure Boot 必须开着,且密钥库必须是我的“。逐条推演攻击者的选项:
- 跑未签名的恶意 UKI? 固件验签失败,拒绝执行。攻击失败。
- 进固件设置把 Secure Boot 关掉? SecureBoot 变量的值变了 → PCR 7 变化 → TPM 拒绝给密钥。攻击失败。
- 换掉密钥库,用自己的密钥给恶意 UKI 签名? PK / KEK / db 的内容变了 → PCR 7 变化 → TPM 拒绝。攻击失败。
三条路全部堵死。而且内核升级不影响 Secure Boot 密钥库,PCR 7 保持稳定,升级友好。这就是为什么生产中普遍选 PCR 7:它不是偷懒,而是和 Secure Boot 配套使用时刚刚好——一个管“规则没被绕过“,一个管“代码必须合法“。
注意:这里有个隐含前提——固件设置的入口要可靠(比如设了固件口令)。否则攻击者进 BIOS 关 Secure Boot、顺手清掉 TPM 重新绑定,PCR 7 这条防线也就没了。纵深防御,没有哪一层是孤立的。
哲学二:检测 + 可预测度量——systemd-pcrlock
思路:接受 PCR 11 会合法变化,但让变化变得可预测。
PCR 11 度量的 UKI 各段是可以预先计算的:升级前你已经知道新内核、新 initramfs 的内容,就能算出升级后 PCR 11 会是什么值。systemd-pcrlock(systemd 253 之后引入)干的就是这件事——升级时预测新的 PCR 11 值并预先重绑,让新旧值都能解开。
这条路线比 Secure Boot 更细:initramfs 的内容、内核 cmdline 的每个字节都纳入绑定范围,连“只改 cmdline 注入 init=/bin/bash“这种不动任何文件的攻击都能防住。代价是工具链复杂,升级流程和绑定流程耦合,对滚动发行版的运维是不小的负担。
业界参照
Windows BitLocker 的经典做法是混合绑定:PCR 0 + 2 + 4 + 11,再叠加 Secure Boot。固件、引导链、内核映像全在绑定范围里,由 Secure Boot 保证这些组件不会被非法替换,由 PCR 绑定保证验证规则不会被关闭。两条哲学它都要。
把两条哲学并排看一眼:
| 哲学一:Secure Boot + PCR 7 | 哲学二:systemd-pcrlock + PCR 11 | |
|---|---|---|
| 防御时机 | 事前预防,恶意代码跑不起来 | 事后检测,篡改留下证据并拒绝解密 |
| 覆盖粒度 | 整条启动链(凡未签名皆拒) | UKI 各段逐字节,含 cmdline |
| 内核升级 | 无感,PCR 7 不动 | 需要升级前预测并重绑 |
| 运维复杂度 | 低,一次配置长期有效 | 高,升级与绑定流程耦合 |
没有标准答案,只有你愿意为多大的覆盖面付多大的运维成本。
可选动手:亲手看一次防御失效
百闻不如一见。在实验虚拟机上,这次攻击五分钟就能复现:
# 先备份!玩坏了 /boot 里唯一的 UKI 就进不了系统了
sudo cp /boot/EFI/Linux/arch-linux.efi /boot/EFI/Linux/arch-linux.efi.bak
# 记下当前的 PCR 4 和 PCR 7
sudo tpm2_pcrread sha256:4,7
然后模拟 Evil Maid,把 UKI 改动一个字节(追加一段垃圾即可):
echo "evil maid was here" | sudo tee -a /boot/EFI/Linux/arch-linux.efi
重启,观察三件事:
- 系统照常自动解锁,正常进桌面——TPM 给了密钥,LUKS 解开了,一切“正常“
- 再读一次 PCR,PCR 4 的值和改之前不一样了——篡改被忠实记录
- PCR 7 纹丝不动——绑定策略没有感知到任何事
sudo tpm2_pcrread sha256:4,7
sha256:
4 : 0x...(和改之前不同的值)
7 : 0x...(和改之前完全相同的值)
篡改发生、证据留存、防御失效,三位一体。这就是你在第六章搭起来的系统的真实防御水位。玩完记得把备份的 UKI 还原回去再重启,别带着一个“被攻击过“的系统继续往下做实验。
Secure Boot 实操路线(简述)
既然 Secure Boot 是正解,为什么第六章没开?因为在虚拟机上完整配置它要另外一番功夫,本书不展开,只给路线图,做过功课的读者可以自行实践:
sbctl create-keys生成自己的 PK / KEK / dbsbctl enroll-keys把自有密钥注入 OVMF 的固件变量(OVMF 需处于 Setup Mode)sbctl sign给 Limine 和所有 UKI 签名- 在 OVMF 设置里开启 Secure Boot
- PCR 7 变了(SecureBoot 从 0 变 1,密钥库从默认变成你的)——必须重新 enroll LUKS 密钥,把新的 PCR 7 值绑进去
第 5 步本身就是个很好的印证:开启 Secure Boot 后 PCR 7 的变化,恰好说明它度量的是“规则“——规则换了,值就变。
小结
- PCR 7 度量的是 Secure Boot 的配置和密钥库,即“验证规则“,不含内核 / initramfs 的任何哈希
- Evil Maid 攻击利用明文的 /boot 替换 UKI;只绑 PCR 7 的系统会照常交出密钥——TPM 没出错,是策略的关心范围太窄
- 度量只是记录,不是拒绝;PCR 变了不阻止任何事,只有策略引用了那份证据,它才起作用
- 直接绑 PCR 4/11 防得住攻击但扛不住日常升级;两条现实路线:Secure Boot + PCR 7(预防,升级友好),或 systemd-pcrlock + PCR 11(检测,预测式重绑,覆盖更细)
- Secure Boot 不是可以顺手关掉的麻烦,而是 PCR 7 绑定策略成立的前提
下一章换一个方向:启动链到内核为止已经讲完了,但系统跑起来之后,每次执行的程序、加载的库谁来度量?这就是 IMA 登场的地方。