Bitlocker 的另類解鎖方式——記 WMI provider 下的 C# 開發

在做 StartCTF 的 Misc 題的時候裝不上爆破的工具,於是換了一條思路。既然是微軟的東西,自然可以 C# 一把梭寫一個爆破小工具,於是找到 Win32_EncryptableVolume 檔案順帶查了一些資料就開幹了。

準備工作

參考檔案

既然是要跟 WMI provider 打交道,WMI CRUD 是必不可少的,於是找了個輪子。

ORMi

Microsoft Docs

https://docs.microsoft.com/en-us/windows/win32/secprov/win32-encryptablevolume

Stack Overflow 參考

https://stackoverflow.com/questions/56360904/bitlocker-values

確認服務開啟

WMI Performance Adapter(wmiApSrv)、BitLocker Drive Encryption Service(BDESVC) 需要保持正在執行的狀態。

掛載磁碟

將 vhdx 虛擬磁碟檔案掛載到計算機中。

按照檔案構建

確定 helper scope

從檔案的 Requirements 查閱可知 Win32_EncryptableVolume 位於 Root\CIMV2\Security\MicrosoftVolumeEncryption。於是連線至 WMI 時需要將 scope 對應著更改。

1
WMIHelper helper = new WMIHelper("Root\\CIMV2\\Security\\MicrosoftVolumeEncryption");

構建類

ORMi 提供了 [WMIClass()] 的 attribute 用來給類指定別名,[WMIProperty()] 用來給屬性指定別名。在未指定別名時,名稱需要與檔案中保持一致。

1
2
3
4
5
6
7
8
[WMIClass("Win32_EncryptableVolume")]
public class EncryptableVolume : WMIInstance
{
public string DeviceID {get; set;}
public string PersistentVolumeID {get; set;}
public string DriveLetter {get; set;}
public int ProtectionStatus {get; set;}
}

寫方法

檔案中提供了幾種用於解鎖的方法。

1
2
3
4
5
6
UnlockWithAdSid : 使用 Active Directory security identifier (SID) 獲取 key
UnlockWithCertificateFile : 使用證書檔案及其密碼
UnlockWithCertificateThumbprint : 使用 CertThumbprint 和證書密碼
UnlockWithExternalKey : 使用 256 位的外部 key
UnlockWithNumericalPassword : 使用 48 位的字串
UnlockWithPassphrase : 使用驅動器密碼

這裡採用的是 UnlockWithPassphrase 方法樸素地使用驅動器密碼去解鎖 BitLocker。

1
2
3
uint32 GetLockStatus(
[out] uint32 LockStatus
);

檔案裡對於引數和返回值給出了託管物件格式,但是並不太淺顯,除錯之後發現 [out] 被塞進了一個 ExpandoObject,而檔案中標識的傳入 [in] 則是直接以 Object 的形式傳入。

因為方法的返回值為一個 ExpandoObject,因此需要使用一個類來接收值。同時需要配合 ORMi,使用 WMIMethod.ExecuteMethod 來呼叫對應的方法。這裡主要需要的是判斷磁碟當前 lock 狀態以及使用密碼解鎖的方法,對照著檔案將其還原。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public LockStatus GetLockStatus(){
return WMIMethod.ExecuteMethod<LockStatus>(this);
}
public class LockStatus{
[WMIProperty("LockStatus")]
public int Status{ get; set; }
public int ReturnValue { get; set; }
}
public UnlockStatus UnlockWithPassphrase(string passphrase){
return WMIMethod.ExecuteMethod<UnlockStatus>(this, new { Passphrase = passphrase });
}
public class UnlockStatus{
public uint ReturnValue { get; set; }
}

主要邏輯

第一步例項化了一個 WMIHelper,這裡可以使用它的 Query 方法去查詢出所有的 EncryptableVolume 得到一個 List。然後遍歷之後使用 GetLockStatus() 去查詢狀態,當狀態為 Locked1 的時候就讀取字典呼叫解鎖方法嘗試解鎖直到 ReturnValue 為 S_OK0x0 的時候 即解鎖成功,當解鎖不成功時將得到返回值 FVE_E_FAILED_AUTHENTICATION0x80310027

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void Main(string[] args){
const int LOCKED = 1;
const uint FVE_E_FAILED_AUTHENTICATION = 0x80310027;
WMIHelper helper = new WMIHelper("Root\\CIMV2\\Security\\MicrosoftVolumeEncryption");
List<EncryptableVolume> volumes = helper.Query<EncryptableVolume>().ToList();
foreach(EncryptableVolume volume in volumes){
var lockStatus = volume.GetLockStatus();
if(lockStatus.Status == LOCKED){
//BruteForceHere
}
Console.WriteLine(lockStatus.Status);
}
}

這篇文章寫得不是很細,因為檔案上記載了很多不一樣的方法以及操作,微軟的檔案寫得鉅細靡遺的同時又極度嚴謹。因為感覺做的時候資料不是太多,所以寫出來希望能提供一些正確且有意義的參考。除此之外,工具裝不上就自己寫一個既定作用的這件事還是蠻好玩的,雖然效率應該是比不上某裝了蠻久沒裝上的工具。