Added /target support for 'masterkeys'

-Added ability to manually decrypt a target masterkey file/folder
This commit is contained in:
harmj0y committed 2020-11-05 09:21:40 -08:00
1 parent ac98a888d0
commit cfed81d01f
6 files changed
+162 -89

No files matched your search

+16 -9
View File
@@ -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<string, string> 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<string, string> kvp in mappings)
{
Console.WriteLine("{0}:{1}", kvp.Key, kvp.Value);
}
}
}
}
+7 -3
View File
@@ -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 </pvk:BASE64... | /pvk:key.pvk>
/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
+1 -1
View File
@@ -4,6 +4,6 @@ namespace SharpDPAPI
{
public static class Version
{
public static string version = "1.9.0";
public static string version = "1.9.1";
}
}
+1 -1
View File
@@ -232,6 +232,7 @@ namespace SharpDPAPI
{
var plaintextBytes = DescribeDPAPICertBlob(certificateBytes, MasterKeys);
var keypairTuple = new Tuple<string, string>("", "");
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)
{
+5
View File
@@ -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})$");
}
}
}
+132 -75
View File
@@ -10,98 +10,160 @@ namespace SharpDPAPI
{
public class Triage
{
public static Dictionary<string, string> TriageUserMasterKeys(byte[] backupKeyBytes, bool show = false, string computerName = "", string password = "")
public static Dictionary<string, string> 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<string, string>();
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<string, string>();
// 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<string, string> 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-<NETBIOSDOMAINNAME> 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<string, string> 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, string>();
}
}
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-<NETBIOSDOMAINNAME> 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<string, string> 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)
{