3、度量架构设计:TCG规范简介、PCR寄存器原理、度量事件日志

好,我们进入第三章。这一章是固件度量的地基。

你想想看,不管你是做可信启动、远程证明,还是搞什么机密计算方案,最终都绕不开三个东西:谁定的规矩、度量值存哪、度量过程怎么查。说白了,就是 TCG 规范、PCR 寄存器和事件日志。

我个人习惯把这套东西叫做「信任的铁三角」。缺一个,你的度量体系就立不起来。今天咱们就把这三个角一个一个掰开看。

3.1 TCG 规范:度量世界的通用语言

TCG,Trusted Computing Group,可信计算组织。这帮人定了一整套标准,从硬件到固件到软件,怎么度量、怎么存储、怎么报告,全给你规定好了。

为什么要有这个规范?

因为没有标准,各家各厂各玩各的。你的度量日志格式跟我不一样,我的 PCR 扩展算法跟他不兼容,那整个生态就乱套了。TCG 规范就是让大家说同一种语言。

核心规范族(跟固件度量最相关的):

  • TCG PC Client Platform Firmware Profile:PC 客户端平台固件规范,定义了 BIOS/UEFI 怎么度量、PCR 怎么用。
  • TCG EFI Protocol Specification:定义了 UEFI 下度量相关的协议接口,比如 EFI_TCG2_PROTOCOL。
  • TCG Event Log Format Specification:事件日志的格式标准,二进制怎么排、字段怎么定义。

我在项目中遇到过最头疼的事,就是某厂商的固件号称「兼容 TCG 规范」,结果日志格式自己魔改了一版。嗯,那对接起来简直是噩梦。所以我的建议是:不要自己发明格式,严格按规范走。

3.2 PCR 寄存器:度量值的保险柜

PCR,Platform Configuration Register,平台配置寄存器。这东西是 TPM 里面的一组硬件寄存器,专门用来存度量值的哈希摘要。

为什么叫「寄存器」?因为它真的在硬件里。TPM 芯片内部有一小块 SRAM,上电后自动清零,掉电就丢。这保证了每次启动的度量都是从头开始的。

3.2.1 PCR 的核心操作:Extend

PCR 不能直接写值。你没法说「PCR[0] = 0xABCD」。它只接受一种操作——Extend(扩展)。

PCR_new = SHA256(PCR_old || 度量值)

什么意思?就是把当前 PCR 里的旧值,和新度量值拼在一起,做一次哈希,结果写回 PCR。

这样做的好处是什么?

  • 不可逆:你没法从 PCR 新值反推出之前度量过什么。
  • 顺序敏感:度量顺序变了,最终 PCR 值就变了。这保证了启动流程的完整性。
  • 累积性:PCR 值记录了整个度量链的完整历史。

一个小技巧:调试的时候,如果你想验证某个阶段的度量是否正确,可以自己手动模拟 Extend 过程。把已知的 PCR 初始值和度量值按规范拼起来算一次哈希,跟 TPM 读出来的 PCR 值对比。我曾经靠这个办法抓到一个 BIOS 在度量顺序上搞错的 bug。

3.2.2 PCR 的分配与用途

TCG 规范给每个 PCR 索引分配了明确的用途。PC 客户端平台通常有 24 个 PCR(0-23),但常用的就前面几个。

PCR 索引 用途 度量内容
PCR[0] BIOS 固件代码 BIOS/ UEFI 的 Flash 内容、微码补丁
PCR[1] BIOS 配置 CMOS 设置、NVRAM 变量
PCR[2] Option ROM 代码 扩展卡上的固件
PCR[3] Option ROM 配置 扩展卡的配置数据
PCR[4] MBR / GPT 代码 主引导记录或 GPT 头
PCR[5] MBR / GPT 分区表 分区表数据
PCR[6] 状态变更与唤醒 S3 恢复、平台状态切换
PCR[7] 安全处理器 Intel ME / AMD PSP 等

这里要注意一点:PCR[0] 和 PCR[1] 的区别。PCR[0] 度量的是「代码本身」,PCR[1] 度量的是「配置」。代码没变但配置变了,PCR[0] 不变,PCR[1] 会变。这在远程证明里很有用——你可以判断对方是不是只改了配置没改固件。

我曾经踩过的坑:有些平台在 S3 唤醒时不会重新度量 PCR[0]-PCR[5],只更新 PCR[6]。如果你在 S3 流程里依赖 PCR[0] 的值做判断,可能会拿到跟冷启动不一样的结果。嗯,这个要特别小心。

3.3 度量事件日志:度量过程的流水账

PCR 只存了最终的哈希值,但没存「谁度量了谁」「度量了什么内容」。事件日志就是干这个的。

你可以把 PCR 想象成账本的总账余额,事件日志就是明细账。光看余额你只知道钱对不对,看明细你才知道每笔钱花哪了。

3.3.1 事件日志的结构

TCG 定义的事件日志是一个二进制结构体数组。每个事件条目包含以下字段:

typedef struct {
    UINT32    PCRIndex;       // 度量值扩展到了哪个 PCR
    UINT32    EventType;      // 事件类型(EV_EFI_BOOT_SERVICES_APPLICATION 等)
    UINT8     Digest[20];     // 度量值的哈希摘要(SHA1 或 SHA256)
    UINT32    EventSize;      // 事件数据的长度
    UINT8     Event[];        // 事件数据(变长,比如被度量的文件路径)
} TCG_PCR_EVENT;

日志在内存中的布局是连续的。固件每做一次度量,就往日志末尾追加一条记录。

3.3.2 事件类型

事件类型告诉验证者「这次度量的是什么类型的东西」。常见的类型有:

  • EV_EFI_BOOT_SERVICES_APPLICATION:度量了一个 UEFI 应用(比如 bootmgfw.efi)。
  • EV_EFI_VARIABLE_DRIVER_CONFIG:度量了一个 UEFI 变量。
  • EV_EFI_ACTION:记录了一个动作事件(比如「准备启动到 OS」)。
  • EV_SEPARATOR:分隔符事件,标记一个阶段的结束。

个人经验:调试日志的时候,我最常用的就是 EV_EFI_ACTION 类型的事件。它不携带具体的度量数据,只记录一个字符串描述。比如 "Calling ExitBootServices" 这种。你可以用它来验证固件的执行流程是否跟预期一致。

3.3.3 日志的获取方式

在 UEFI 环境下,你可以通过 EFI_TCG2_PROTOCOL 的 GetEventLog 函数拿到日志的起始地址和大小。

EFI_STATUS
EFIAPI
GetEventLog (
    IN  EFI_TCG2_PROTOCOL    *This,
    IN  EFI_TCG2_EVENT_LOG_BITMAP  EventLogLocation,
    OUT EFI_PHYSICAL_ADDRESS *EventLogLastEntry,
    OUT BOOLEAN              *EventLogTruncated,
    OUT UINTN                *LastLogSize
);

到了 OS 里,Linux 下你可以通过 /sys/kernel/security/tpm0/binary_bios_measurements 读取日志。Windows 下可以用 TBS(TPM Base Services)API 来获取。

3.4 三者的关系:一张图说清楚

好,理论讲完了,咱们用一张图把 TCG 规范、PCR 和事件日志的关系串起来。

固件度量架构:信任的铁三角 TCG 规范 定义规则与格式 PCR 寄存器 存储度量哈希摘要 事件日志 记录度量过程明细 规定 PCR 分配 定义日志格式 通过 PCRIndex 关联 核心逻辑 固件按 TCG 规范度量组件 → 哈希摘要 Extend 到 PCR 度量明细记录到事件日志 → 验证者比对 PCR 与日志 验证者(Verifier) 读取 PCR 解析日志

这张图你看懂了吗?

固件启动时,按照 TCG 规范的要求,对每个要度量的组件(BIOS 代码、Option ROM、引导加载器等等)做哈希计算。哈希值 Extend 到对应的 PCR 里,同时把这次度量的详细信息(度量了谁、度量结果是什么、扩展到了哪个 PCR)追加到事件日志里。

验证者拿到 PCR 值和事件日志后,可以自己重新走一遍 Extend 流程:从日志里取出每次的度量值,按顺序做 Extend,看最终结果是否跟 PCR 值一致。一致,说明度量过程没被篡改;不一致,说明中间有人动了手脚。

注意:验证者不能只看 PCR。PCR 只告诉你「最终值对不对」,但没告诉你「中间度量了哪些东西」。事件日志才是你判断「度量内容是否合理」的依据。我曾经见过一个攻击案例,攻击者把恶意固件刷进去,PCR 值算出来跟正常的一样——因为他在日志里把恶意组件的度量记录删掉了。所以,PCR 和日志必须一起验证。

3.5 小结

这一章我们讲了三个核心概念:

  • TCG 规范:度量世界的通用语言,定义了怎么做、存哪、怎么记。
  • PCR 寄存器:硬件保险柜,通过 Extend 操作累积存储度量哈希,不可逆、顺序敏感。
  • 事件日志:度量过程的流水账,记录了每次度量的详细信息,是验证 PCR 的依据。

这三者缺一不可。没有规范,大家各搞一套;没有 PCR,度量值没地方存;没有日志,你没法验证度量过程是否可信。