用分区表覆盖 LUKS

sat*_*low 11 data-recovery 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..|
Run Code Online (Sandbox Code Playgroud)

到目前为止,自从发生这种情况以来,唯一触及磁盘的是 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"|
Run Code Online (Sandbox Code Playgroud)

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

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  |................|
*
Run Code Online (Sandbox Code Playgroud)

它足够完整吗

fro*_*utz 16

cryptsetup repair,第二部分 \xe2\x80\x94 完整标头恢复

\n\n

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

\n

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

\n

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

\n

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

\n
\n

设置:

\n
# truncate -s 128M disk.img\n# losetup --find --show --partscan disk.img\n/dev/loop0\n# parted /dev/loop0 -- mklabel gpt\n# parted /dev/loop0 -- mkpart luks $((RANDOM%100))MiB 100%\n# cryptsetup luksFormat --type luks2 /dev/loop0p1\n\nWARNING!\n========\nThis will overwrite data on /dev/loop0p1 irrevocably.\n\nAre you sure? (Type \'yes\' in capital letters): YES\nEnter passphrase for /dev/loop0p1:\nVerify passphrase:\n# cryptsetup open /dev/loop0p1 luks\nEnter passphrase for /dev/loop0p1:\n# mkfs.ext2 -L encrypted /dev/mapper/luks\n# blkid /dev/mapper/luks\n/dev/mapper/luks: LABEL="encrypted" [\xe2\x80\xa6] TYPE="ext2"\n
Run Code Online (Sandbox Code Playgroud)\n

闪亮的新加密文件系统!

\n

损害:

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

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

\n
\n

元数据恢复:

\n

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

\n
# stdbuf -oL strings -n 64 -t d disk.img | grep \'"keyslots":\'\n60837888 {"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"}}\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
# jq < header.json\n{\n  "keyslots": {\n    "0": {\n      "type": "luks2",\n[\xe2\x80\xa6]\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

分区恢复:

\n

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

\n

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

\n

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

\n
\n

关键材料回收:

\n

根据 JSON 元数据,此 LUKS2 标头有一个密钥槽,其密钥材料可在 中找到"offset":"32768","size":"258048"。让我们用以下方法来获取它dd

\n
# partition=60817408\n# offset=32768\n# size=258048\n# dd bs=1 skip=$((partition+offset)) count=$((size)) if=disk.img of=header.$((offset))\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n
# hexdump -C header.32768\n00000000  f1 3b 23 73 98 d7 8f e3  22 24 9a 9d 5a 2c a9 ae  |.;#s...."$..Z,..|\n00000010  95 82 3e c6 df e7 0e a0  f4 ba 54 6c 7f e9 fa f6  |..>.......Tl....|\n00000020  b7 12 64 8d 7d a5 ca 4b  c8 89 89 08 3e de 59 0d  |..d.}..K....>.Y.|\n[\xe2\x80\xa6]\n0003efe0  b2 b3 bc cd de 60 17 a7  57 bb 1a 84 5a 15 68 95  |.....`..W...Z.h.|\n0003eff0  7f 1f 07 ee ee d1 e8 a2  6c cf 5f 40 0b 73 00 0b  |........l._@.s..|\n0003f000\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
# gzip < header.32768 > header.32768.gz\n# stat -c %s header.*\n258048\n258106\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

只有当它接受或不接受您的密码时,才可能在最后 \xe2\x80\x94 处进行真正的验证。

\n
\n

完整标头恢复:

\n

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

\n
# truncate -s 16M luks.recovery\n# cryptsetup luksFormat --type luks2 luks.recovery\n# cryptsetup luksErase luks.recovery\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

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

以及关键材料:

\n
# dd conv=notrunc bs=1 seek=32768 if=header.32768 of=luks.recovery\n
Run Code Online (Sandbox Code Playgroud)\n

至此,我们终于完成了,除了:

\n
# cryptsetup luksDump luks.recovery\nDevice luks.recovery is not a valid LUKS device.\n# cryptsetup repair luks.recovery\nDevice luks.recovery is not a valid LUKS device.\n
Run Code Online (Sandbox Code Playgroud)\n
\n

校验和恢复:

\n

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

\n
# cryptsetup luksDump --debug luks.recovery\n[\xe2\x80\xa6]\n# LUKS2 header version 2 of size 16384 bytes, checksum sha256.\n# Checksum:5babf58f0f788911897989ff3d9a580de1c22db8869b3b08cd0d6d56906005cb (on-disk)\n# Checksum:b2ff5dd7b53978723402103ba914ed87ef2c5b5d9a9062d68363e4df38aebf6f (in-memory)\n[\xe2\x80\xa6]\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

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

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

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

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

\n
# cryptsetup repair luks.recovery\n# cryptsetup luksDump luks.recovery\nLUKS header information\nVersion:        2\n[\xe2\x80\xa6]\n
Run Code Online (Sandbox Code Playgroud)\n
\n

总结一下:

\n
# losetup --find --show --read-only --offset 60817408 disk.img\n/dev/loop0\n\n# cryptsetup open --read-only --header luks.recovery /dev/loop0 luksrecovery\nEnter passphrase for /dev/loop0:\n\n# blkid /dev/mapper/luksrecovery\n/dev/mapper/luksrecovery: LABEL="encrypted" [\xe2\x80\xa6] TYPE="ext2"\n
Run Code Online (Sandbox Code Playgroud)\n

完毕。最后

\n

  • 对看似绝望的情况做出极其复杂的反应,而且效果完美。这比我自己从 dm-luks 文档中看到的要多得多,绝对是出色的深入研究。我的数据回来了,标头也终于备份了。 (8认同)