Skip to content

Pari's blog

WindowsインストーラーにHDDの先頭を喰われた

WindowsインストーラーにHDDの先頭を喰われた

TL; DR

  • Linuxで LUKSとかLVMを壊しちゃった場合の対処方法を書いてます
    • Linuxなら、困っても大体なんとかなる
    • HDDのファイルシステムが破損しても復旧できた
  • Windowsはクソ

前情報

私はLinux Desktopユーザーで、もう5年以上メイン環境はLinuxを使っている。。 データを捨てられない性格なので、結構な容量のHDDを持っている (半分ぐらいが今まで乗り換えてきたOSのHDDイメージだったりするのだが)。

HDDの構成はこんな感じ。チョットダケ説明しよう。

HDD構成

平たく言うと、HDDドライブを複数枚使ってバックアップやデータ保存用の領域を作っている。その構成が若干特殊なだけ。 特殊構成にしている目的は、「セキュリティ」と「それによる不都合を相殺するため」。

構成としては、大きく3つの部分に分けられる。図の①②③である。

  1. まず①、一番下の "/dev/sda" とあるのがHDD本体で、データが入っている。 ただし、この領域は LUKSによって暗号化されているので、そのままではデータは読めない1 。 LUKSを通して1のみデータが読めるようになっている。 この暗号化を行うことでHDDを盗まれてもパスフレーズを知らなければ開けられない(ちなみに私は12桁以上のパスフレーズを用途ごとに分けて使っている)。 (1)のセキュリティの理由。

  2. 次に②、読み込めた複数ディスクのデータをそれぞれ別枠で扱うのは面倒なので、 LVMによってグループ化している。 これによって複数のディスクを1枚として扱うことができる。 解錠されたHDDデータに直接的にアクセスするのはLVMとなる。 (2)の、暗号化による不都合の相殺のため。

  3. 最後に③、LVMで作成された論理ボリューム (LV)を適当なディレクトリにマウントして、 やっとOSから読むことができるようになる。 これも分類としては(2)で、単にこの方が使いやすくなるから。

この方式の特徴

  • ディスクの入れ替えが容易 ディスクに寿命が来て不良セクタが増えてきた場合、よほどの壊れ方をしない限り^[今回は"よほどの壊れ方をしたパターン"の話です]問題なく入れ替えが行える。↓ sdaに保存されたデータを他のディスクに退避するコマンド
    pvmove /dev/mapper/MAP_PATH_TO_SDA
    
  • 盗難などに遭っても元のディスクがLUKS下にあるので、読めない
  • 1枚だけに致命的な破損があった場合にも、全体が読めなくなる ← ???

何があったか

普段は「Linux万歳 外出時はMacbook」みたいな生活が長い訳だが、ある仕事でWindows環境が必要になった。 「SSDスロット空きあるしWindows入れて、使いたいときはBIOSから起動すりゃいいか〜」みたいな軽い気持ちでWindowsのISOを焼き、インストールメディアを指して空きのSSDにインストールしようとしたら、、 https://storage.googleapis.com/zenn-user-upload/edced8b64b43-20240213.png

先頭100MB * 9回分が喰われてる (8回途中で失敗した)

Windowsインストーラーはインストールの度に100MBの予備領域が確保されるようだった。

  • なぜ指定していない領域に???
  • LUKSを認識できなかったから空き領域だと思ったっぽい???
  • ディスク先頭にある既知のマジックナンバー↓を照会するだけやろ (サボらないで)

先頭の、4c55 4b53 みたいなのがマジックナンバー、ファイルやディスクの形式ごとに存在する (これによって、ファイルシステムとかの判定をしてる)。

対処

ファイルシステムの先頭が破壊されてしまったので、復旧しなければならない。 着地点を考えてみよう。

  • 元に戻す
  • 一部破損でも良いから元に戻ったように見える状態で使う
  • 復元ソフトで読めるファイルだけ読む
  • HDD初期化

落ち着いて考えてみれば、破壊された部分は全体の極々一部なので、最悪の方法 (HDD初期化)にはならないと考えていた。実際、3つめの案で何とかなった。

元に戻そうとした過程

手順を整理しよう。

  1. LUKSを解除する
  2. LVMを再構築
  3. ファイルシステムチェック&マウント

この流れを辿ることができれば復元できたと言える。

1. LUKS解錠

まず、LUKSを解錠しないことには生データすら読めないので、LUKSを解錠したい。 逆にLUKSを解錠できれば、断片化されているとしても、生データにはアクセス可能なので、何とかなりそうだ。

LUKS にはキー (パスフレーズ)との照会を行うためのヘッダー部分でパスフレーズが管理されている。

パスフレーズの照会は複数枚のHDDに対して何度も入力する必要があり、毎回入力するのは手間なので、スクリプトにしている。

:::details 解錠スクリプト

#!/bin/bash

DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"&>/dev/null &&pwd)" # SCRIPT_DIR
set -u

if [ ! -d "$DIR/ext/lost+found" ]; then
  uuids=()
  while read f; do
    uuid=$(echo "$f" | awk '{print $1}')
    if ! [[ $uuid =~ ^\{?[A-F0-9a-f]{8}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{12}\}?$ ]]; then
      continue
    fi
    uuids+=($uuid)
  done< <(sudo lsblk -o UUID,NAME,TYPE | grep disk | grep -v nvme)

  read -sp "Enter passphrase: " passwd; echo

  cnt=0
  for uuid in "${uuids[@]}"; do
    id=$(echo $uuid | awk -F'-' '{print $1}')
    printf "[uuid] $uuid .."
    if [ "$(lsblk | grep "lv-$id")" ]; then
      echo Already mounted
      continue
    fi
    echo $passwd | sudo cryptsetup luksOpen /dev/disk/by-uuid/$uuid lv-$id
    if [ $? -eq 0 ]; then
      echo OK
    else
      echo NG
    fi
  done

  sleep 1
  sudo mount /dev/mapper/vg--ext-default $DIR/ext
fi

:::

今回確認したところ、 同じパスフレーズを使っているディスクのヘッダーは同じ内容となっていたため (以下の↓バイトコードが完全一致)、 これを /dev/sda に転写すれば良いということになる。

cryptsetupに専用のユーティリティがあったので、それを使ってみた。

# 正常なディスクからヘッダのバックアップを取って
sudo cryptsetup luksHeaderBackup /dev/sdb --header-backup-file sdb.backup
# 壊れたほうに復元する
sudo cryptsetup luksHeaderRestore /dev/sda --header-backup-file sdb.backup

これでLUKSを解錠することができた!! (最初の図における①が解決できた)

2. LVMを再構築

LVMはそもそも少しややこしい。 PV, LV, VG の概念がある。

  • 物理ディスク: HDDのこと。
  • PV: LVMが認識している物理ボリュームのこと。 物理ディスク != PV であることに注意
  • LV: 論理ボリューム。LVMが認識する仮想ディスクと考えて良い。
  • VG: 論理ボリュームグループ。LVの集合体。

通常は ディスク → PV → LV → VG の順で構成し、VGをディレクトリにマウントして使用する。

今回は PV一覧の出力コマンドの履歴が残っていた。↓

$ sudo pvs
  WARNING: Couldn't find device with uuid tLMBcr-RgOH-cdYP-4DpE-K47x-JYOT-c3sTIy.
  WARNING: VG vg-ext is missing PV tLMBcr-RgOH-cdYP-4DpE-K47x-JYOT-c3sTIy (last written to /dev/mapper/lv-20e9515a).
  PV                      VG     Fmt  Attr PSize   PFree
  /dev/mapper/lv-03e87be2 vg-ext lvm2 a--   <7.28t    0
  /dev/mapper/lv-348aa360 vg-ext lvm2 a--   <7.28t    0
  (省略)
  [unknown]               vg-ext lvm2 a-m   <7.28t    0

ご覧のように、ディスクの認識が欠落していて警告が出ていて、これは通常出ない。 今回、[unknown]と表示されているPVのUUIDは明らかに 欠落したtLMBcr-RgOH-cdYP-4DpE-K47x-JYOT-c3sTIy なので、[unknown] と表示されているPVを無理やり tLMBcr-RgOH-cdYP-4DpE-K47x-JYOT-c3sTIy と認識させたい。

ここで、PVのこういったメタ情報がどこで管理されているかといえば、それはVG単位で管理されていて、VGを構成するLVやPVの情報がVG単位で記録されているので、それらを書き換えてやれば良いということになる。

具体的には現在のVGの設定をバックアップして、バックアップを編集して再適用してみる。

# バックアップ vg-extが今回の対象VGの名称です
sudo vgcfgbackup -f vg-ext.backup vg-ext
# バックアップファイルを編集
sudo vim vg-ext.backup
# PV自体のメタ情報も再生成
sudo pvcreate --uuid "tLMBcr-RgOH-cdYP-4DpE-K47x-JYOT-c3sTIy" --restorefile vg-ext.backup /dev/mapper/lv-20e9515a
# バックアップから復元
sudo vgcfgrestore -f vg-ext.backup vg-ext
# file: vg-ext.backup
pv2 {
  id = "V09qt9-zuB3-zIfO-shx1-SjDA-Hjyt-nOC1U4"
  device = "/dev/mapper/lv-348aa360"	# Hint only

  status = ["ALLOCATABLE"]
  flags = []
  dev_size = 15628020400	# 7.27736 Terabytes
  pe_start = 2048
  pe_count = 1907717	# 7.27736 Terabytes
}

pv3 {
  id = "tLMBcr-RgOH-cdYP-4DpE-K47x-JYOT-c3sTIy"
-  device = "[unknown]"	# Hint only
+  device = "/dev/mapper/lv-20e9515a"	# Hint only

  status = ["ALLOCATABLE"]
-  flags = ["MISSING"]
+  flags = []
  dev_size = 15628020400	# 7.27736 Terabytes
  pe_start = 2048
  pe_count = 1907717	# 7.27736 Terabytes
}

最後にVGに変更を再読込する。

sudo vgchange -ay vg-ext

以下コマンドで警告がでなくなったらOK。

sudo pvs
sudo lvs
sudo vgs

なんとLVMを再構築することができた! (最初の図における②が解決できた)

3. ファイルシステムチェック&マウント

ここまで来たらもう少し。 ファイルシステムのチェックを。

$ sudo fsck /dev/mapper/vg--ext-default
fsck from util-linux 2.39.3
e2fsck 1.47.0 (5-Feb-2023)
ext2fs_check_desc: Corrupt group descriptor: bad block for block bitmap
fsck.ext4: Group descriptors look bad... trying backup blocks...
/dev/mapper/vg--ext-default: recovering journal
fsck.ext4: unable to set superblock flags on /dev/mapper/vg--ext-default


/dev/mapper/vg--ext-default: ***** FILE SYSTEM WAS MODIFIED *****

/dev/mapper/vg--ext-default: ********** WARNING: Filesystem still has errors **********

ダメみたい、、 強制的にマウントできないかな?

$ sudo mount -o ro,noload /dev/mapper/vg--ext-default mount-point
mount: /home/yuki/ext: mount(2) system call failed: Structure needs cleaning.
       dmesg(1) may have more information after failed mount system call
# 以下はdmesg出力
[  383.524943] EXT4-fs (dm-7): ext4_check_descriptors: Block bitmap for group 178880 not in group (block 12108825021912886424)!
[  383.524947] EXT4-fs (dm-7): group descriptors corrupted!

無理そうです。 似たようなコマンドも試しましたが無理だった。

復元ソフトを使う

- 元に戻す ← 無理
- 一部破損でも良いから元に戻ったように見える状態で使う ← 無理
- 復元ソフトで読めるファイルだけ読む
- HDD初期化

ということで、testdisk を使って復元してみます。

testdiskでは対象のディスク(仮想ディスク含め)を指定し、ファイル・ディレクトリをListすることができます。

復元対象を :で指定し、Cでコピーできる。

HDDより生きているディスクが十分な容量があれば一発でいけますが、 そうでない場合は、ある程度の塊ごとに復元していく必要があります。。

面倒なのは仕方ないかと思いつつ、現在も復元中、、

脚注

1

改めてこのページを読んでみると、私の実装とは別のことをやっているようだ。私のものは当たり前すぎて書いてないのか🤔 「お前の実装おかしいよ」って人がいたら教えてほしい

2

より正確には、LUKSのパスフレーズを使って暗号化を解除した場合。別にデーモンがある訳じゃない

3

これもWindowsPCが必要だったので古いのを引っ張り出して頑張った

4

これに関しては私が悪い。WindowsPCでインストーラーを作成しなかったのが悪かった模様。Windowsの作法に従えということ。Linuxとは常識が異なる。