我现在知道这是一个愚蠢的决定,我尝试使用 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.02MiB
或60837888 - 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"
完毕。最后。