文章摘要

本文深入探討 Linux Unified Key Setup (LUKS) 全扇區加密架構,說明 NAS 在 mdadm RAID + LVM 環境下的加密配置方式。當系統發生故障、硬碟損壞或忘記密碼時,如何透過 LUKS Header 結構分析Hashcat 字典攻擊記憶體鑑識取得 Master Key 等方法進行資料救援。本文提供完整的技術細節與指令參考。

當你對 NAS 或 Linux OS 做了全扇區加密(FDE),其實這是用了 Linux Unified Key Setup,簡稱 LUKS。但做了這加密後,萬一系統出了狀況、硬碟損壞、需要資料救援,或忘記密碼等…又會是怎樣的狀況?這篇將會做全面分析。

對於全扇區 FDE 架構建議先參考 OSSLab 這篇文章

NAS 陣列與分區架構

首先我們要瞭解現代 NAS 陣列與分區架構如圖:

NAS LUKS 加密架構圖

(圖引用自 http://www.hivestream.de/tag/luks.html)

在一般 NAS 下,多顆 HDD 透過 mdadm 組好的 MD,再分區。前面通常會分割給 NAS 作業系統,最大塊的數據分區會使用 LVM 來分割。

回顧一下 LVM:

  • Physical Volume (PV):實體卷軸
  • Volume Group (VG):卷軸群組,將許多的 PV 整合成為一個卷軸群組
  • Logical Volume (LV):邏輯卷軸,最終將 VG 再切割出類似 partition 的 LV 即是可使用的裝置

最大數據分區直接切 PV,PV 組 VG:

# 建立 Physical Volume
pvcreate /dev/md1
# 輸出: Physical volume "/dev/md1" successfully created

# 建立 Volume Group
vgcreate vghdd /dev/md1
# 輸出: Volume group "vghdd" successfully created

# 建立 Logical Volume
lvcreate -l 100%VG -n user1 vgssd

對 sda2 分區做 LUKS 加密格式初始化:

cryptsetup luksFormat /dev/sda2

對 VG 做 LUKS 加密格式初始化,通常 NAS 加密 Volume 就是這樣格式化的。如果對整個 MD 加密,那當 RAID 參數丟失時,因為沒有其他明文文件系統,推測 RAID 參數需要額外處理:

cryptsetup luksFormat /dev/vgssd/user1

掛載 LUKS 跟分割成 ext4 檔案系統(當然要割成其他檔案系統如 BTRFS 也沒問題):

cryptsetup open /dev/vgssd/user1 vgssd-user1
mkfs.ext4 /dev/mapper/vgssd-user1

在全扇區加密下的檔案分區已經格式化完畢。

NAS 全扇區加密對於資料救援會有何影響?

我們都知道全扇區加密,一定要有地方存 metadata 內有被加密的密鑰。LUKS 是將明文數據切割成若干同樣大小的 Blocks,使用同一個對稱密鑰塊和確定的算法對每個數據塊進行加解密。

所以在 LUKS metadata 完整之下,資料恢復會是這樣的:

  1. 物理硬碟壞軌:如果有做 RAID 1、5、6,從其他顆正常扇區 mirror 或 XOR 補齊。但如果小量整體,不會影響數據太大。
  2. 物理硬碟整顆損壞:如果有做 RAID 1、5、6,從其他顆正常扇區 mirror 或 XOR 補。如果沒有則需要將單顆硬碟整顆修理好。
  3. 檔案刪除 ext4:從日誌系統撈 inode,或使用檔案碎片搜索法一樣可以正常運作。
  4. 分區掉失:一樣可以資料恢復。

如果有先組 RAID,那 RAID MD 需要下面參數,整個 MD 資料才能正確無誤:

  1. 走向方法
  2. Stripe size
  3. 硬碟順序

所以假設 RAID 參數也丟失時候,就要利用文件系統來對 RAID 參數的逆向推測。如果 MD 內留有未加密分區,則可以文件系統順利推導回去 RAID 參數。

如果做了全扇區加密,那該怎推敲 RAID 參數?此時利用 LUKS metadata,來推算出 Master Key。再對每個單顆硬碟做扇區解密轉換,才可求得明文文件系統,再利用文件系統特性參數來拼湊出 RAID 參數。(本文最後有寫推導方法與轉換指令)

假設是 LUKS 有問題狀況時候,該如何來做資料救援取得資料?

我們先來看整顆硬碟上 LUKS 架構:

  • volume (or partition) header (208 byte)
  • 8 x key slots (48 byte * 8)
  • key material(加密的 split master key)

以上都為 LUKS metadata (Header)

  • encrypted (volume) data:被加密的密文數據

LUKS Header 結構

LUKS Header 結構圖

第一步要先備份 LUKS metadata (Header),指令如下:

cryptsetup luksHeaderBackup <device> --header-backup-file <file>

查看 LUKS volume (or partition) Header 與 Key slot:

cryptsetup luksDump /dev/sda2

輸出範例:

LUKS header information for /dev/sda2

Version:        1
Cipher name:    aes
Cipher mode:    xts-plain64
Hash spec:      sha256
Payload offset: 4096
MK bits:        256
MK digest:      xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
MK salt:        xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
                xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
MK iterations:  371000
UUID:           28c39f66-dcc3-4488-bd54-11ba239f7e68

Key Slot 0: ENABLED
    Iterations:         2968115
    Salt:               xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
                        xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
    Key material offset: 8
    AF stripes:         4000
Key Slot 1: ENABLED
    Iterations:         2968115
    Salt:               xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
                        xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
    Key material offset: 264
    AF stripes:         4000
Key Slot 2: DISABLED
Key Slot 3: DISABLED
Key Slot 4: DISABLED
Key Slot 5: DISABLED
Key Slot 6: DISABLED
Key Slot 7: DISABLED

LUKS volume (or partition) header + key slot 為 592 bytes,結構如下(以下為 big endian):

Offset Size (byte) Description
0 6 簽名 “LUKSxbaxbe”(ASCII 顯示為 “LUKS..”)
6 2 版本
8 32 加密方法 (Cipher name),例如:AES
40 32 加密模式 (Cipher mode),例如:xts-plain64
72 32 哈希模式,例如:sha256(用於用戶密鑰計算和 AF 擴散)
104 4 加密卷開始扇區 (Payload offset)
108 4 Master Key 大小(通常為 128、256、512 bit)
112 20 Master Key digest
132 32 Master Key salt
164 4 Master Key 迭代次數 (MK iterations)
168 40 卷標識符 (UUID)
208 8 x 48 Key slots,總共有 8 個

Key Slot 結構

Key slot is 48 bytes,內容如下:

Offset Size (byte) Description
0 4 狀態:0x0000dead → inactive;0x00ac71f3 → active
4 4 Key material 迭代次數 (Iterations)
8 32 Key material salt
40 4 Key material 開始扇區(encrypted SMK 開始位置)
44 4 每個 anti-forensic stripes 有多少個 Key material (AF stripes)

重要:主密鑰 Master Key (MK)

這是最關鍵的密鑰,如果記憶體中撈得出來,你也不需要 User 密碼跟 LUKS Header,直接就可以解開加密的數據。MK 大小值取決於卷標頭中 MK bits,通常是 128、256 或 512 bit。

要特別注意!這跟 WD 全扇區硬體加密硬碟一樣,當變更用戶密碼時候,MK 主密鑰不會改變,變動的是 Key material (encrypted SMK)

LUKS 加密架構

LUKS 加密流程圖

下面為 LUKS 初始加密時寫入 Header 的 Pseudo code:

masterKey = must be available (from initialisation or recovered by password)
masterKeyLength = phdr.key-bytes
emptyKeySlotIndex = find inactive key slot index in phdr
keyslot ks = phdr.keyslots[emptyKeySlotIndex]

PBKDF2-IterationsPerSecond = benchmark system
ks.iteration-count = PBKDF2-IterationsPerSecond * intendedPasswordCheckingTime
ks.salt = generate random vector (length: LUKS_SALTSIZE)

splitKey = AFsplit(masterKey, masterKeyLength, ks.stripes)
splitKeyLength = masterKeyLength * ks.stripes

pwd = read password from user input
pwd-PBKDF2ed = PBKDF2(password, ks.salt, ks.iteration-count, masterKeyLength)

encryptedKey = encrypt(phdr.cipher-name, phdr.cipher-mode, pwd-PBKDF2ed, splitKey, splitKeyLength)

write to partition(encryptedKey, ks.key-material-offset, splitKeyLength)
ks.active = LUKS_KEY_ACTIVE
update keyslot ks in phdr

LUKS 解密流程圖

下面為 LUKS metadata (Header + Key material) + 用戶密碼,完成 MK 驗證的 Pseudo code:

read phdr from disk
check for correct LUKS_MAGIC and compatible version number
masterKeyLength = phdr.key-bytes
pwd = read password from user input

foreach active keyslot in phdr do as ks {
    pwd-PBKDF2ed = PBKDF2(pwd, ks.salt, ks.iteration-count, masterKeyLength)
    read from partition(encryptedKey, ks.key-material-offset, masterKeyLength * ks.stripes)
    
    splitKey = decrypt(phdr.cipherSpec, pwd-PBKDF2ed, encryptedKey)
    masterKeyCandidate = AFmerge(splitKey, masterKeyLength, ks.stripes)
    MKCandidate-PBKDF2ed = PBKDF2(masterKeyCandidate, phdr.mk-digest-salt, 
                                   phdr.mk-digest-iter, LUKS_DIGEST_SIZE)
    
    if equal(MKCandidate-PBKDF2ed, phdr.mk-digest) {
        return masterKeyCandidate as correct master key
    }
}
error: password does not match any keyslot

忘了用戶密碼怎辦?

方法一:Shell Script 暴力破解

由於 LUKS 沒有限制輸入密碼次數,因此可以寫 script 嘗試爆破。參考自 nmattia.com

crack_maybe=$(cat <<'EOF'
    echo PASS | cryptsetup open --test-passphrase ./encrypted-file
    rc=$?
    if [ "$rc" -ne "2" ]; then
        echo "return code $rc on input PASS"
        exit 255
    fi
EOF
)

我們將過程存儲在 shell 變量中,以便傳遞給 xargs。stutter 將潛在的密碼短語提供給 xargs,crack_maybe 在 PASS 用潛在密碼短語替換所有出現的密碼後調用。如果 cryptsetup 返回任何其他內容 2,我們退出 exit 255,這基本上是告訴 xargs 停止的唯一方法。

方法二:bruteforce-luks

bruteforce-luks (GitHub) 是用調用 cryptsetup API 爆破,速度有好一點。

不過上面二個方法當然不夠有效率,來看看 Hashcat 爆破 LUKS 的思路…

LUKS Master Key 推導流程

第一次 PBKDF2 跟 AF 轉換後就有主密鑰,就可以開始嘗試做解密攻擊,而不需要如正統流程再跑一次 PBKDF2,這非常耗時間。

Hashcat 發現 LUKS 加密過的數據會像是隨機數據,因此對數據區做熵檢查,如果熵低於某個閾值,可以假設密碼是正確的。要鏡像超過 2MB 超過 LUKS metadata,就是因為需要包含數據區。

這思路很酷!以資料救援設備 MRT 來講,爆破 FDE 密鑰是利用驗證第 0 扇區末端 magic number 為 55AA。Hashcat 開發者 ATOM 非常自豪他的 LUKS 破解思路,他認為比起市面上商業軟體如 Passware 都快了 20 倍以上!

Hashcat 破解 LUKS 指令

首先要取得 LUKS Header 2.1MB(是的,要比 LUKS metadata 再大一點):

# 提取 LUKS Header
dd if=/dev/XXXX of=header.luks bs=512 count=4097

# 使用 Hashcat 破解
./hashcat64.bin -m 14600 -a 0 -w 3 header.luks Dictionary.txt -o luks_password.txt

參數說明:

  • -m 14600:hash method,14600 為 LUKS encryption
  • -a 0:Crack method,0 為 standard dictionary(3 為 bruteforce)
  • -w 3:resource allocation,3 為 high
  • header.luks:加密分區檔頭
  • Dictionary.txt:字典檔
  • -o:輸出檔案

破解成功後,就可從 luks_password.txt 得到用戶密碼。爆破 LUKS 其實很困難,配上 1070 顯卡也才 3000 Hash/s 上下,因此一般狀況下不必擔心 LUKS 用戶密碼被破解。

如果電腦沒有關機下,LUKS 還有其他破解方式

記憶體鑑識方法:

假設加密方法是 AES,那可做記憶體鑑識,將記憶體 DUMP,使用 findaes 找出在記憶體中的 MK。由於取得是 MK 就直接可以用了。

你要知道 Cipher mode,並且還有加密區大小:

echo "0 <size> crypt aes-xts-plain64 <key> 0 </dev/drive> 4096" | sudo dmsetup create luks-volume

比較有趣的是,這個方法在 Mount 時候就可以忽略到 LUKS Header。

那當 LUKS metadata (Header + encrypted SMK) 如果遭遇硬碟扇區損壞跟惡意擦除,這會是怎樣狀況?

先來看看破壞方法,比如 sdX1 為 LUKS 分區,下 DD 擦除前端 2MB 就可:

dd if=/dev/urandom of=/dev/sdX1 bs=512 count=20480

這樣還有機會做資料救援嗎?看一下主密鑰 MK 是怎樣產生的:

pwd-PBKDF2ed = PBKDF2(pwd, ks.salt, ks.iteration-count, masterKeyLength)
read from partition(encryptedKey, ks.key-material-offset, masterKeyLength * ks.stripes)
splitKey = decrypt(phdr.cipherSpec, pwd-PBKDF2ed, encryptedKey)
masterKeyCandidate = AFmerge(splitKey, masterKeyLength, ks.stripes)

這會跟 Hashcat 不一樣,這時反而有 User Key (pwd),只求破解用 masterKeyCandidate 就可以做 MK(沒做第二次 PBKDF2 驗證)所以只需要下面:

以下這二個是最必備的:

  1. encryptedKey:Key material,encrypted SMK,以最上面為例,大小 4000 x 256bit = 125KB
  2. ks.salt:Key slot 中的 Key material salt,大小 32 byte

以下為好推測的(同一款 NAS 版本下應該是一樣):

  1. masterKeyLength:這好推測 128、256、512bit
  2. cipherSpec:cbc、ecb、xts
  3. ks.iteration-count:Key material 迭代次數
  4. ks.key-material-offset:Key slot 中的 Key material 開始扇區
  5. ks.stripes:Key slot Stripe 數
  6. encrypted:Material Key 大小 = masterKeyLength * ks.stripes

算出 MK 一樣檢查對數據區檢查熵,一定低於某個閾值,就應該為正確 MK。求得 MK 後,一樣直接用 crypt 來解密:

echo "0 <size> crypt <ciphername+cipherSpec> <key> 0 </dev/drive> 4096" | sudo dmsetup create luks-volume

前述中若 NAS RAID 參數丟失,而 RAID MD 中只有單一加密分區,LUKS metadata 也完整時,也是用上面方法生成 MK 先,用上面指令做單顆硬碟解密,再根據解密文件系統分析推導出 RAID 參數。

重要提醒:

確實當 Key material (encrypted SMK)、ks.salt (Key slot 中的 Key material salt) 被擦除掉或是這部位硬碟扇區損壞,也沒有從記憶體中取得 MK 時,是無法解密資料的

結論

會寫這篇文其實是因為客戶遇到這樣狀況詢問我們:一台 6 Bay NAS 做了加密,無法掛載資料,當初拿給原廠 Support Team,但原廠也沒辦法作處理…之後也不確定 RAID 組態跟 LUKS Header 是否還在。

(這方面來講,原廠其實不該負責客戶資料,或是客戶應該要做全硬碟備份先,再給原廠處理。)

OSSLab 之前會使用 LUKS 攻擊工具但不知道其原理,這次研究整合必須一次做到:

  1. 一般 NAS 架構上套件安裝流程才會知道架構
  2. 加密程式原始碼與演算法架構
  3. 多種破解手法與破解程式經驗與思路,最終編寫自己程式,才有機會做如此高難度的資安與資料恢復處理
若需要 LUKS 加密 NAS 或硬碟的資料救援服務,請聯繫 OSSLab

👉 線上估價系統


參考資料

Thx Chang

Author Thx Chang

More posts by Thx Chang