mirror of
https://github.com/GhostPack/SharpDPAPI.git
synced 2026-11-09 17:13:48 -10:00
Added /target support for 'masterkeys'
-Added ability to manually decrypt a target masterkey file/folder
This commit is contained in:
1 parent
ac98a888d0
commit
cfed81d01f
6 files changed
+162
-89
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,6 @@ namespace SharpDPAPI
|
||||
{
|
||||
public static class Version
|
||||
{
|
||||
public static string version = "1.9.0";
|
||||
public static string version = "1.9.1";
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user