文章摘要
本文深入探討 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 陣列與分區架構如圖:
(圖引用自 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 完整之下,資料恢復會是這樣的:
- 物理硬碟壞軌:如果有做 RAID 1、5、6,從其他顆正常扇區 mirror 或 XOR 補齊。但如果小量整體,不會影響數據太大。
- 物理硬碟整顆損壞:如果有做 RAID 1、5、6,從其他顆正常扇區 mirror 或 XOR 補。如果沒有則需要將單顆硬碟整顆修理好。
- 檔案刪除 ext4:從日誌系統撈 inode,或使用檔案碎片搜索法一樣可以正常運作。
- 分區掉失:一樣可以資料恢復。
如果有先組 RAID,那 RAID MD 需要下面參數,整個 MD 資料才能正確無誤:
- 走向方法
- Stripe size
- 硬碟順序
所以假設 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 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 初始加密時寫入 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 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 的思路…
第一次 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 為 highheader.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 驗證)所以只需要下面:
以下這二個是最必備的:
- encryptedKey:Key material,encrypted SMK,以最上面為例,大小 4000 x 256bit = 125KB
- ks.salt:Key slot 中的 Key material salt,大小 32 byte
以下為好推測的(同一款 NAS 版本下應該是一樣):
- masterKeyLength:這好推測 128、256、512bit
- cipherSpec:cbc、ecb、xts
- ks.iteration-count:Key material 迭代次數
- ks.key-material-offset:Key slot 中的 Key material 開始扇區
- ks.stripes:Key slot Stripe 數
- 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 攻擊工具但不知道其原理,這次研究整合必須一次做到:
- 一般 NAS 架構上套件安裝流程才會知道架構
- 加密程式原始碼與演算法架構
- 多種破解手法與破解程式經驗與思路,最終編寫自己程式,才有機會做如此高難度的資安與資料恢復處理
參考資料
- https://github.com/libyal/libluksde
- https://hashcat.net/forum/thread-6225.html
- http://www.hivestream.de/tag/luks.html
- http://netinfo-security.org/CN/abstract/abstract5917.shtml
- https://www.freebuf.com/articles/database/181010.html
- https://blog.pnb.io/2018/02/bruteforcing-linux-full-disk-encryption.html
- https://blog.appsecco.com/breaking-full-disk-encryption-from-a-memory-dump-5a868c4fc81e





