當你對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 整合成為一個捲軸群組 (VG)
Logical Volume, LV, 邏輯捲軸:最終將 VG 再切割出類似 partition 的 LV 即是可使用的裝置了
最大數據分區直接切PV ,PV 組VG
pvcreate /dev/md1 Physical volume "/dev/md1" successfully created vgcreate vgssd /dev/md1 Volume group "vghdd" successfully created lvcreate -l 100%VG -n user1 vgsdd
對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 metdata(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 <div>
LUKS volume (or partition) header +key slot 為 592 bytes 結構如下,建議上下核對 以下為big eddian
Offset | Size(byte) | Value | Description |
---|---|---|---|
0 | 6 | “LUKS\xba\xbe” | 簽名 ASCII 顯示為 “LUKS..” |
6 | 2 | 1 | 版本 |
8 | 32 | 加密方法 (Cipher name) etc:AES Contains an ASCII string with an end-of-string character |
|
40 | 32 | 加密模式 (Cipher mode) etc:xts-plain64 Contains an ASCII string with an end-of-string character |
|
72 | 32 | 哈希模式 example:sha256 用於用戶密鑰計算和抗碎片分析(AF)擴散的散列方法 Contains an ASCII string with an end-of-string character |
|
104 | 4 | 加密卷開始扇區 (Payload offset) | |
108 | 4 | Master Key大小 (注意MK bits顯示為bit 通常為128,256,512) Contains the size of the key in bytes |
|
112 | 20 | Master Key digiest | |
132 | 32 | Master key salt | |
164 | 4 | Master key 迭代次數(MK iteration) | |
168 | 40 | 卷標識符(UUID) 包含帶有字符串結尾字符的ASCII字符串,該字符串由小寫的UUID組成 |
|
208 | 8 x 48 | Key slots 48 bytes總共有8個 |
Key slot
Key slot is 48 bytes 內容如下
Offset | Size(byte) | Value | Description |
---|---|---|---|
0 | 4 | 狀態 (of key slot) 0x0000dead ⇒ inactive (dead) 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
</span>masterKey = must be available, either because it is still in memory from initialisation or because it has been recovered by a correct password masterKeyLength = phdr.key-bytes emptyKeySlotindex = find inactive key slot indexin phdr by scanning the keyslot.active field for LUKS_KEY_DISABLED. keyslot ks = phdr.keyslots[emptyKeySlotindex] PBKDF2-IterationsPerSecond = benchmark system ks.iteration-count = PBKDF2-IterationsPerSecond intendedPassword Checking Time (in seconds) ks. salt = generate random vector, length:LUKS_SSALTSIZE splitKey = AFsplit(masterKey, source masterKey Length, source length ks.stripes) // number of stripes splitKeyLength = masterKeyLength * ks.stripes pwd = read password from user input pwd-PBKDF2ed = PBKDF2(password, ks.salt, ks.iteration-count masterKeyLength)// key size is the same as for the bulk data encryptedKey = encrypt(phdr.cipher-name, phdr.cipher-mode, pwd-PBKDF2ed, splitKey, cipher name cipher mode key content splitKey Length) content length // write to partition (encrypted Key, ks.key-material-offset // splitKey Length) source sector number // length in bytes ks.active = LUKS_KEY_ACTIVE // mark key as active in phdr update keyslot ks in phdr <span class="text-only" data-eleid="3">
下面為LUKS metadata(Header+Key material)+用戶密碼的 ,完成mk驗證的Pseudo code
</span>read phdr from disk check for correct LUKS_MAGIC and compatible version number masterKeyLength = phdr.key-bytes pwd = read password from user input foreachactive keyslot in phdr do as ks { pwd-PBKDF2ed = PBKDF2(pwd, ks.salt, ks.iteration-count read from partition (encrypted Key, masterKey Length) destination // sector number ks.key-material-offset, masterKeyLength * ks.stripes) // number of bytes splitKey = decrypt(phdr.cipherSpec, // cipher spec. pwd-PBKDF2ed, encrypted Key, encrypted) // key content content length masterKeyCandidate = AFmerge(splitKey, masterkeyLength ks.stripes) MKCandidate-PBKDF2ed = PBKDF2( master Key Candidate, phdr.mk-digest-salt, phdr.mk-digest-iter, LUKS_DIGEST_SIZE) ifequal(MKCandidate-PBKDF2ed, phdr.mk-digest){ break loop and return masterKeyCandidate as correct master key } error, password does not match any keyslot <span class="text-only" data-eleid="3">
忘了用戶密碼怎辦?
一、由於LUKS沒有限制輸入密碼次數因此可以寫script嘗試爆破
下面參考自 https://nmattia.com/posts/2017-03-05-crack-luks-stutter-gnu-parallel.html
crack_maybe=$(cat &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;'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 (GitHub)是用調用cryptsetup API爆破, 速度有好一點
不過上面二個方法當然不夠有效率
來看看hastcat爆破LUKS的思路…..
來重看一下LUKS 解密架構
第一次PBKDF2 跟AF轉換後就有有主密鑰,就可以開始嘗試做解密攻擊.
而不需要如正統流程再跑一次PBKDF2,這非常耗時間..
hashcat發現LUKS加密的過的數據會像是隨機數據
因此對數據區做熵檢查,如果熵低於某個閾值,可以假設密碼是正確的
要鏡像超過2MB超過LUKS metadata,就是因為需要包含數據區。
這思路很酷!以資料救援設備MRT來講,爆破FDE密鑰是利用驗證第0扇區 末端magic number為55AA
Hastcat開發者ATOM非常自豪他的LUKS破解思路,他認為比起市面上商業軟體如Passware都快了20倍以上!
Hashcat破解LUKS指令
首先要取得LUKS Header 2.1MB (是的 要比LUKS metadata再大一點)
dd if = / dev / XXXX of = header.luks bs = 512 count = 4097 ./hashcat-3.5.0/hashcat64.bin -m 14600 -a 0 -w 3 header.luks Dictionary.txt -o luks_password.txt
-m = hash method – 14600 for LUK encryption
-a = Crack method – 0 for standard dictionary (3 for bruteforce)
-w = resource allocation – 3 for high
header.luks = 加密分區檔頭
Dictionary.txt = dictionary
-o = output luks_password.txt
破解成功後,就可從luks_password.txt 的到用戶密碼。
爆破LUKS其實很困難,配上1070顯卡也才3000 Hash/s 上下
因此一般狀況下不必擔心LUKS 用戶密碼被破解
如果電腦沒有關機下, LUKS 還有其他破解方式:
1.假設是加密方法是AES,那可做記憶體鑑識 ,將記憶體DUMP ,使用findaes 找出在記憶體中的MK ,由於取得是MK就直接可以用了.
你要知道Ciper 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</pre> <pre> masterKeyLength )</pre> <pre>read from partition (encryptedKey, //destination</pre> <pre> ks.key−material−offset, //sector number</pre> <pre> masterKeyLength*ks.stripes) //number of bytes</pre> <pre>splitKey =decrypt(phdr.cipherSpec, //cipher spec .</pre> <pre> pwd−PBKDF2ed,//key</pre> <pre> encryptedKey,//content</pre> <pre> encrypted) //content length</pre> <pre>masterKeyCandidate = AFmerge (splitKey,masterkeyLength ,</pre> <pre> ks.stripes)
這會跟Hashcat不一樣 ,這時反而有User Key (pwd)
只求破解用masterKeyCandidate就可以做MK (沒做第二次PBKDF2驗證)所以只需要下面
以下這二個 是最必備的
1.encryptedKey:Key material ,encrypted SMK 以最上面為例,大小4000 x 256bit=125KBytes
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 開始扇區 (encrypted SMK開始位置)
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-volum
前述中若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.多種破解手法與破解程式經驗與思路最終編寫自己程式,才有機會做如此高難度的資安與資料恢復處理.
上面圖引用自與參考
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