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);
}
}

这篇文章写得不是很细,因为文档上记载了很多不一样的方法以及操作,微软的文档写得巨细靡遗的同时又极度严谨。因为感觉做的时候资料不是太多,所以写出来希望能提供一些正确且有意义的参考。除此之外,工具装不上就自己写一个既定作用的这件事还是蛮好玩的,虽然效率应该是比不上某装了蛮久没装上的工具。