对于系统卷全盘加密(FDE),任何不将分区解锁密钥存储在硬件安全设备(如 TPM)的自动解锁方法都不应认为是安全的

即使使用硬件安全设备,仍需要额外保护(如 TPM PIN)和内存加密才能保证基本安全

笔者之前见过一个 Linux 发行版,为了限制用户权限,系统安装时会自动将分区加密,防止用户修改系统文件,此加密使用 LUKS,解锁密钥在 grub-install 的过程中写入磁盘,开机时使用磁盘中的密钥自动解锁,如前面两段话所说,这种加密方法并不安全,“防君子不防小人”,可以直接从内存中提取密钥(因为开机后,系统若要正常运行,必须将 AES 密钥载入内存),这种从内存中提取密钥的破解方法被称为“冷启动攻击”

此文章仅展示 LUKS 自动解锁时可能存在的安全问题,其他磁盘加密机制如 BitLocker 也存在此问题,请不要将此教程用于违法用途

若要保证数据安全,请配置 TPM PIN 或其他预验证机制,并启用内存加密。这可以有效防止冷启动攻击

其中,内存加密可以防止攻击者读取你的内存获取明文密钥,预验证机制则在密钥写入内存前要求验证,防止攻击者将硬盘拆下,安装至其他计算机进行攻击

因为这种加密的自动解锁方式安全性几乎为 0,所以写一篇破解教程进行演示

什么是冷启动攻击?
冷启动攻击(cold boot attack)是在攻击者使用冷启动重新启动计算机后,通过技术手段转储内存,从内存中提取密钥或其他敏感信息

本教程演示的操作全部在本地搭建的 KVM 虚拟机完成,对于虚拟机来说,攻击成本极低,而对于实体机,攻击则需要准备液氮(用于冷却内存,防止数据快速丢失)和专用读取器

准备

  • Linux 机器做为宿主机
  • 配置了 LUKS 的虚拟机,且开机时 grub 会在磁盘中读取密钥进行解密

操作过程

1 - 安装操作系统

创建虚拟机并安装操作系统,这个操作就不讲了,本文直接使用上面提到的“为了限制用户权限”的发行版演示,敏感内容均已打码

Proxmox VE create VM

2 - 分析分区结构

操作系统安装完成后,使用工具 cfdisk 查看磁盘分区表:

cfdisk

可以看到大致分区结构如下:

  • 前 32768 个扇区(16 MiB)保留
  • 一个 vfat EFI 分区,存储 Grub EFI 文件
  • 一个 ext4 启动分区,存储 Grub 文件和操作系统内核
  • 一个 LUKS 加密卷

在未加密的启动分区可以找到 grub.cfg,打开后确认是开机时自动解密系统分区,且未直接将解锁密钥存储在配置文件中

Grub menuentry

Grub 在启动时已经自动解锁系统分区,启动系统过程中,Grub 会将加密系统卷的 UUID(红线)、加密系统卷的密钥路径(黄线)和内核所在分区的 UUID(绿线)传递给内核,以便在 initrd 中正常解锁

如果 Grub 没有配置密码,或者你可以更改 Grub 配置文件删除密码,你可以直接在 Grub Shell 中使用 cat 命令跟上密钥路径获取密钥

但如果 Grub 设置了密码,同时引导分区加密,将无法使用此方法,只能使用冷启动攻击。冷启动攻击是一种通用的攻击方法

3 - 转储内存

使用正常方法开机(此步骤被称为冷启动,这也是冷启动攻击一词的由来)

等待操作系统完成磁盘解密后(出现登录屏幕),执行下面的操作:

  • 对于虚拟机:
    在宿主机上,休眠该虚拟机,创建的休眠文件包含内存转储
  • 对于物理机:
    使用液氮冷却剂为内存冷却,将内存拆下并安装到硬件转储器,创建内存转储

建议使用允许系统正常启动的最小内存,大多数情况 4 GiB 是足够的,大内存可能会导致转储文件很大

正常情况下,转储文件的大小最大为内存大小的 3 倍,请保留充足的磁盘空间

Proxmox VE 默认配置中创建的休眠文件是一个 LVM 卷,如下:

1
2
3
4
Disk /dev/mapper/pve-vm--299--state--suspend--2024--03--31: 8.49 GiB, 9114222592 bytes, 17801216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 65536 bytes / 65536 bytes

我们可以使用命令将 LVM 卷转换为文件:

1
dd if=/dev/mapper/pve-vm--299--state--suspend--2024--03--31 of=MEMORY.dmp

4 - 提取 AES 密钥

安装 findaes 工具,然后使用下面的命令提取转储文件中的 AES 密钥:

1
findaes MEMORY.dmp

输出类似:

1
2
3
4
5
6
7
8
9
Searching MEMORY.dmp
Found AES-128 key schedule at offset 0x1bab8a9:
85 f1 dd c6 ea 68 09 6c d9 8d e8 39 65 8c 61 70
Found AES-128 key schedule at offset 0x1baba99:
a3 9e cb 7d 01 b3 fb 9e 4e 1a e0 a4 ec a3 7e a8
Found AES-128 key schedule at offset 0x129589f8:
b7 40 59 a2 0c c0 2e a3 0c 89 71 a3 16 ff 20 66
Found AES-256 key schedule at offset 0x1b564786:
ae 66 77 dd e6 e6 10 3c b5 f6 a2 a0 56 1a b1 df 41 f0 ce c3 d7 5b 4c ef c3 4a eb a7 e6 e8 2d 1e

如果有两个连续的 AES-128 密钥,请拼接为 AES-256 密钥(地址靠后的 + 地址靠前的),例如上面输出中 0x1baba990x1bab8a9 地址连续,拼接为 a3 9e cb 7d 01 b3 fb 9e 4e 1a e0 a4 ec a3 7e a8 85 f1 dd c6 ea 68 09 6c d9 8d e8 39 65 8c 61 70
同样的,如果有四个连续的 AES-128 或两个连续的 AES-256,则拼接为 AES-512

然后将所有提取到的密钥整理一下,去掉空格,从上面的输出我们就得到了这三个密钥:

  • AES-128 b74059a20cc02ea30c8971a316ff2066
  • AES-256 a39ecb7d01b3fb9e4e1ae0a4eca37ea885f1ddc6ea68096cd98de839658c6170(由两个 AES-128 拼接)
  • AES-256 ae6677dde6e6103cb5f6a2a0561ab1df41f0cec3d75b4cefc34aeba7e6e82d1e

5 - 解密卷

将密钥转换为二进制写入文件:

1
echo a39ecb7d01b3fb9e4e1ae0a4eca37ea885f1ddc6ea68096cd98de839658c6170 | xxd -r -p > key.txt

然后尝试使用 AES 密钥为加密卷添加密码:

1
cryptsetup luksAddKey /dev/vda3 --master-key-file key.txt

如果提示 Enter new passphrase for key slot: ,则此 AES 密钥与分区解密密钥对应,继续设置密码即可

如果密钥错误,则提示 Volume key does not match the volume. ,此时需要继续尝试其他密钥,直到解密成功

如果有多个分区,则为每个分区尝试上面的步骤

6 - 挂载

密码设置完成后,我们就可以直接使用密码解密 LUKS 卷并挂载,理论上此方法也完全适用于 BitLocker,但是笔者并未进行测试

做好了对冷启动攻击的防护也不能保证数据绝对安全,因为除了冷启动攻击,针对磁盘加密还有许多其他类型攻击,比如 DMA 攻击,通常是将计算机的网卡或其他硬件更换为特殊读取器进行攻击,部分操作系统现在已经默认开启 DMA 攻击的内核防护