用分区表覆盖 LUKS

用分区表覆盖 LUKS

我现在知道这是一个愚蠢的决定,我尝试使用 Windows 安装程序双重启动 Windows 和 Linux,启动 Windows 安装程序后,我选择了 2 个大小约为 500GB 的克隆硬盘之一进行擦除,因为它们是如果我选择其中一个而不是另一个,那并不重要。

这样做之后,安装程序说它更改了其中一个 500GB 硬盘的分区表,然后无法安装 Windows。在没有复制文件的情况下因错误而崩溃,或者它说,当它说它甚至无法开始安装时,我不确定我是否可以信任它。

所以我启动了我的 Linux 安装来检查它覆盖了哪个驱动器并手动安装它。相反,迎接我的是我的另一个驱动器,一个 6TB dm-luks 和 btrfs 驱动器,丢失了。不仅两个 500GB 驱动器都没有受到影响,而且 6TB 驱动器似乎还添加了一堆混乱的分区。 6个分区,顺序为499M、99M、499M、100M、499M、100M。

由于我的驱动器很大而且速度很慢,hexdump -C /dev/sda |grep LUKS到目前为止运行已经产生了这么多,我将在完成后更新:

8d411ce0  e1 ad 4c 55 4b 53 c0 85  22 3d de 49 dd 44 fd 08  |..LUKS.."=.I.D..|
e6449610  d5 cf 4a 86 9f cc 4c 55  4b 53 a9 a9 16 cc ba 1d  |..J...LUKS......|
446ea9a70  b3 db a9 bf 8b 2e 41 4c  55 4b 53 ef f0 75 b0 18  |......ALUKS..u..|
4732c6040  e0 b3 bb ff 4c 55 4b 53  4c c2 5b 12 c6 41 fc d6  |....LUKSL.[..A..|

到目前为止,自从发生这种情况以来,唯一触及磁盘的是 hexdump,我对运行 testdisk 犹豫不决,因为我听说它会覆盖驱动器上的数据,并且它没有将 luks 列为可以搜索的内容。

我可以看到其他人使用 hexdump 来检查完整的标头,但是,我不知道我到底在寻找什么。

此时我可以做什么来查看是否可以恢复标头的任何位。有没有办法运行 testdisk 或其他工具来查找 luks 标头以确定它们是否已被覆盖?任何能让我知道一切是否是 FUBAR 的方法都与恢复数据的方法一样受欢迎。

编辑

在没有 grep 的情况下在驱动器的第一个位上运行 hexdump 显示至少有一些完整的 JSON,从 到00005000显示00005310同样多,我什至不确定我现在特别要寻找的内容是否仍然完整。看起来它覆盖了数据直到这个确切的字符串。

00005000  7b 22 6b 65 79 73 6c 6f  74 73 22 3a 7b 22 30 22  |{"keyslots":{"0"|
00005010  3a 7b 22 74 79 70 65 22  3a 22 6c 75 6b 73 32 22  |:{"type":"luks2"|

删除中间的数据,因为它包含盐,但块结束于:

000052f0  22 2c 22 6b 65 79 73 6c  6f 74 73 5f 73 69 7a 65  |","keyslots_size|
00005300  22 3a 22 31 36 37 34 34  34 34 38 22 7d 7d 00 00  |":"16744448"}}..|
00005310  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*

它完好无损足够的

答案1

cryptsetup repair,第二部分 — 完整标头恢复

为了恢复部分覆盖的 LUKS2 标头,您至少需要两件事:一是至少一个键槽的密钥材料。第二,描述如何使用关键材料的元数据(算法、迭代计数、盐等)。

密钥材料大约是 256KB 的随机数据,通常在偏移量 32768 (0x8000) 及以上位置找到,但确切的偏移量和大小必须根据元数据确定。

元数据是一个 JSON 字符串,通常位于偏移量 4096 (0x1000) 和 20480 (0x5000) 处。 LUKS2 维护它的两个相同的副本(主标头和辅助标头)。密钥材料本身只存在一次。

如果分区表本身也丢失了,您还必须确定正确的分区偏移量。


设置:

# truncate -s 128M disk.img
# losetup --find --show --partscan disk.img
/dev/loop0
# parted /dev/loop0 -- mklabel gpt
# parted /dev/loop0 -- mkpart luks $((RANDOM%100))MiB 100%
# cryptsetup luksFormat --type luks2 /dev/loop0p1

WARNING!
========
This will overwrite data on /dev/loop0p1 irrevocably.

Are you sure? (Type 'yes' in capital letters): YES
Enter passphrase for /dev/loop0p1:
Verify passphrase:
# cryptsetup open /dev/loop0p1 luks
Enter passphrase for /dev/loop0p1:
# mkfs.ext2 -L encrypted /dev/mapper/luks
# blkid /dev/mapper/luks
/dev/mapper/luks: LABEL="encrypted" […] TYPE="ext2"

闪亮的新加密文件系统!

损害:

# cryptsetup close luks
# wipefs -a /dev/loop0p1
/dev/loop0p1: 6 bytes were erased at offset 0x00000000 (crypto_LUKS): 4c 55 4b 53 ba be
/dev/loop0p1: 6 bytes were erased at offset 0x00004000 (crypto_LUKS): 53 4b 55 4c ba be
# dd count=32 if=/dev/urandom of=/dev/loop0p1
32+0 records in
32+0 records out
16384 bytes (16 kB, 16 KiB) copied, 0.000334077 s, 49.0 MB/s
# wipefs -a /dev/loop0
/dev/loop0: 8 bytes were erased at offset 0x00000200 (gpt): 45 46 49 20 50 41 52 54
/dev/loop0: 8 bytes were erased at offset 0x063ffe00 (gpt): 45 46 49 20 50 41 52 54
/dev/loop0: 2 bytes were erased at offset 0x000001fe (PMBR): 55 aa
/dev/loop0: calling ioctl to re-read partition table: Success
# losetup -d /dev/loop0

所以这是一个 disk.img,其中有一个未知偏移量的 LUKS2 分区,其标头已损坏(魔法字节被擦除、部分覆盖、分区表被擦除)。


元数据恢复:

由于 LUKS2 JSON 字符串是纯 ASCII,因此可以使用 找到它strings,它也会显示偏移量:

# stdbuf -oL strings -n 64 -t d disk.img | grep '"keyslots":'
60837888 {"keyslots":{"0":{"type":"luks2","key_size":64,"af":{"type":"luks1","stripes":4000,"hash":"sha256"},"area":{"type":"raw","offset":"32768","size":"258048","encryption":"aes-xts-plain64","key_size":64},"kdf":{"type":"argon2id","time":13,"memory":1048576,"cpus":4,"salt":"R1z3arzSCjRb3STaCAnstIygkHCXf0CHf6kXl5yQj/E="}}},"tokens":{},"segments":{"0":{"type":"crypt","offset":"16777216","size":"dynamic","iv_tweak":"0","encryption":"aes-xts-plain64","sector_size":512}},"digests":{"0":{"type":"pbkdf2","keyslots":["0"],"segments":["0"],"hash":"sha256","iterations":324435,"salt":"0nSkpvmDJlvfkDaQteVVo6JdD/Oqt3vnndkZt1Qnd84=","digest":"lefQ21EaiuSdHFhSIFW3wDfMcRqG0HLCAO1bGI3SfvM="}},"config":{"json_size":"12288","keyslots_size":"16744448"}}

所以这里我们在偏移量 60837888 处有一个完整的 JSON 字符串。将其复制并粘贴到文件中header.json。该文件应以 开头{和结尾}。您可以使用它jq来确保它确实是有效的 JSON 字符串,并以更易于理解的形式显示它:

# jq < header.json
{
  "keyslots": {
    "0": {
      "type": "luks2",
[…]
  }
}

分区恢复:

LUKS2 标头中 JSON 元数据的偏移量通常为 4096 或 20480,具体取决于它是主标头还是辅助标头。您必须从strings之前找到的偏移量中减去这些值。

因此,在这种情况下,正确的分区偏移量可以是60837888 - 4096 = 60833792 = 58.02MiB60837888 - 20480 = 60817408 = 58 MiB。由于后者是 MiB 对齐的,因此它更可能是正确分区偏移的候选者。

如果有疑问,请尝试两者。


关键材料回收:

根据 JSON 元数据,此 LUKS2 标头有一个密钥槽,其密钥材料可在 中找到"offset":"32768","size":"258048"。让我们抓住它dd

# partition=60817408
# offset=32768
# size=258048
# dd bs=1 skip=$((partition+offset)) count=$((size)) if=disk.img of=header.$((offset))

如果有多个键槽,请对每个键槽重复此过程。

密钥材料应该看起来像随机数据。为了验证这一点,您可以使用 来查看整个事情hexdump -C

# hexdump -C header.32768
00000000  f1 3b 23 73 98 d7 8f e3  22 24 9a 9d 5a 2c a9 ae  |.;#s...."$..Z,..|
00000010  95 82 3e c6 df e7 0e a0  f4 ba 54 6c 7f e9 fa f6  |..>.......Tl....|
00000020  b7 12 64 8d 7d a5 ca 4b  c8 89 89 08 3e de 59 0d  |..d.}..K....>.Y.|
[…]
0003efe0  b2 b3 bc cd de 60 17 a7  57 bb 1a 84 5a 15 68 95  |.....`..W...Z.h.|
0003eff0  7f 1f 07 ee ee d1 e8 a2  6c cf 5f 40 0b 73 00 0b  |[email protected]..|
0003f000

或者你可以尝试压缩它,看看压缩后的结果是否更小:

# gzip < header.32768 > header.32768.gz
# stat -c %s header.*
258048
258106

随机数据通常根本无法压缩,因此如果 gzip 压缩版本不更小(甚至大几个字节),则很有可能整个数据都是随机数据。

真正的验证只有在最后才有可能——当它接受或不接受你的密码时。


完整标头恢复:

收集完上述必要的成分后,您可以尝试从中重建完整的标头:

# truncate -s 16M luks.recovery
# cryptsetup luksFormat --type luks2 luks.recovery
# cryptsetup luksErase luks.recovery

使用 cryptsetup 生成一个有效但不可用的标头(没有密钥槽)。这里的目的是获取一个使用所有正确的魔术字节、UUID 等设置的文件——与加密无关,但正是它使 LUKS 标头成为 LUKS 标头。

现在将您的元数据移植到它上面:

# printf "%s\0" "$(jq -c < header.json)" |
    dd conv=notrunc bs=1 seek=4096 of=luks.recovery
# printf "%s\0" "$(jq -c < header.json)" |
    dd conv=notrunc bs=1 seek=20480 of=luks.recovery

以及关键材料:

# dd conv=notrunc bs=1 seek=32768 if=header.32768 of=luks.recovery

此时,我们就最后完成,除了:

# cryptsetup luksDump luks.recovery
Device luks.recovery is not a valid LUKS device.
# cryptsetup repair luks.recovery
Device luks.recovery is not a valid LUKS device.

校验和恢复:

呃呃,现在怎么了?添加--debug即可了解:

# cryptsetup luksDump --debug luks.recovery
[…]
# LUKS2 header version 2 of size 16384 bytes, checksum sha256.
# Checksum:5babf58f0f788911897989ff3d9a580de1c22db8869b3b08cd0d6d56906005cb (on-disk)
# Checksum:b2ff5dd7b53978723402103ba914ed87ef2c5b5d9a9062d68363e4df38aebf6f (in-memory)
[…]

LUKS2 的主标头和辅助标头都有一个校验和。由于我们修改了 JSON 元数据而没有更新校验和,因此这是不匹配的。值得庆幸的是,cryptsetup 显示了预期值,因此我们不必手动计算它。

该校验和是二进制标头的一部分,因此您必须使用xxd -r -p将其转换为二进制:

# echo 5babf58f0f788911897989ff3d9a580de1c22db8869b3b08cd0d6d56906005cb | xxd -r -p | hexdump -C
00000000  5b ab f5 8f 0f 78 89 11  89 79 89 ff 3d 9a 58 0d  |[....x...y..=.X.|
00000010  e1 c2 2d b8 86 9b 3b 08  cd 0d 6d 56 90 60 05 cb  |..-...;...mV.`..|
00000020
# echo b2ff5dd7b53978723402103ba914ed87ef2c5b5d9a9062d68363e4df38aebf6f | xxd -r -p | hexdump -C
00000000  b2 ff 5d d7 b5 39 78 72  34 02 10 3b a9 14 ed 87  |..]..9xr4..;....|
00000010  ef 2c 5b 5d 9a 90 62 d6  83 63 e4 df 38 ae bf 6f  |.,[]..b..c..8..o|
00000020

将错误的磁盘校验和替换为正确的内存校验和:

# hexdump -C luks.recovery | grep '5b ab f5 8f 0f 78 89 11'
000001c0  5b ab f5 8f 0f 78 89 11  89 79 89 ff 3d 9a 58 0d  |[....x...y..=.X.|
# echo b2ff5dd7b53978723402103ba914ed87ef2c5b5d9a9062d68363e4df38aebf6f |
    xxd -r -p |
    dd conv=notrunc bs=1 seek=$((0x000001c0)) of=luks.recovery

这应该能让事情继续进行。

# cryptsetup repair luks.recovery
# cryptsetup luksDump luks.recovery
LUKS header information
Version:        2
[…]

总结一下:

# losetup --find --show --read-only --offset 60817408 disk.img
/dev/loop0

# cryptsetup open --read-only --header luks.recovery /dev/loop0 luksrecovery
Enter passphrase for /dev/loop0:

# blkid /dev/mapper/luksrecovery
/dev/mapper/luksrecovery: LABEL="encrypted" […] TYPE="ext2"

完毕。最后

相关内容