From 9df99d44f555ff7d73cdfcda8d88d46c64e4dbd5 Mon Sep 17 00:00:00 2001 From: harmj0y Date: Mon, 6 Jun 2022 14:02:46 -0700 Subject: [PATCH] Version 1.11.3 -Added ability to use "/password" with "/target" for masterkey command -Small update to README --- CHANGELOG.md | 7 +++++++ README.md | 4 +++- SharpDPAPI/Commands/Masterkeys.cs | 12 ++++++++++++ SharpDPAPI/Domain/Version.cs | 2 +- SharpDPAPI/lib/Dpapi.cs | 13 +++++++++++-- SharpDPAPI/lib/Triage.cs | 30 +++++++++++++++++++++++------- 6 files changed, 57 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 564e48a..c89af3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.11.3] - 2022-06-06 + +### Fixed +* **SharpDPAPI** project + * `masterkeys` command now accepts a `/password:X` argument with `/target:X` + + ## [1.11.2] - 2022-01-12 ### Fixed diff --git a/README.md b/README.md index 7575a15..a596178 100755 --- a/README.md +++ b/README.md @@ -223,6 +223,8 @@ Specific cookies/logins/statekey files can be specified with `/target:X`, and a The **masterkeys** command will search for any readable user masterkey files and decrypt them using a supplied domain DPAPI backup key. It will return a set of masterkey {GUID}:SHA1 mappings. +`/password:X` can be used to decrypt a user's current masterkeys. If `/target` is also supplied with `/password`, the `/sid:X` full domain SID of the user also needs to be specified. + The domain backup key can be in base64 form (`/pvk:BASE64...`) or file form (`/pvk:key.pvk`). C:\Temp>SharpDPAPI.exe masterkeys /pvk:key.pvk @@ -1255,7 +1257,7 @@ The **logins** command will search for Chrome 'Login Data' files and decrypt the Login Data files can also be decrypted with a) any "{GUID}:SHA1 {GUID}:SHA1 ..." masterkeys passed, b) a `/mkfile:FILE` of one or more {GUID}:SHA1 masterkey mappings, c) a supplied DPAPI domain backup key (`/pvk:BASE64...` or `/pvk:key.pvk`) to first decrypt any user masterkeys, or d) a `/password:X` to decrypt any user masterkeys, which are then used as a lookup decryption table. DPAPI GUID mappings can be recovered with Mimikatz' `sekurlsa::dpapi` command. -A specific Login Data file can be specified with `/target:FILE`. A remote `/server:SERVER` can be specified if a `/pvk` is also supplied. If triaging newer Chrome/Edge instances, a `/statekey:X` AES state key can be specified. +A specific Login Data file can be specified with `/target:FILE`. A remote `/server:SERVER` can be specified if a `/pvk` or `/password` is also supplied. If triaging newer Chrome/Edge instances, a `/statekey:X` AES state key can be specified. By default, logins are displayed in a csv format. This can be modified with `/format:table` for table output. Also, by default only non-null password value entries are displayed, but all values can be displayed with `/showall`. diff --git a/SharpDPAPI/Commands/Masterkeys.cs b/SharpDPAPI/Commands/Masterkeys.cs index d8f2562..ca67b4d 100755 --- a/SharpDPAPI/Commands/Masterkeys.cs +++ b/SharpDPAPI/Commands/Masterkeys.cs @@ -51,6 +51,18 @@ namespace SharpDPAPI.Commands { mappings = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password); } + else if (arguments.ContainsKey("/target")) + { + if (!arguments.ContainsKey("/sid")) + { + Console.WriteLine("[X] When using /password:X with /target:X, a /sid:X (domain user SID) is required!"); + return; + } + else { + Console.WriteLine("[*] Triaging masterkey target: {0}\r\n", arguments["/target"]); + mappings = Triage.TriageUserMasterKeys(null, true, "", password, arguments["/target"], arguments["/sid"]); + } + } else { mappings = Triage.TriageUserMasterKeys(null, true, "", password); diff --git a/SharpDPAPI/Domain/Version.cs b/SharpDPAPI/Domain/Version.cs index 97d287d..ece7e6b 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.11.2"; + public static string version = "1.11.3"; } } diff --git a/SharpDPAPI/lib/Dpapi.cs b/SharpDPAPI/lib/Dpapi.cs index 992e87a..b0d588e 100755 --- a/SharpDPAPI/lib/Dpapi.cs +++ b/SharpDPAPI/lib/Dpapi.cs @@ -1725,9 +1725,18 @@ namespace SharpDPAPI return masterKeySubBytes; } - public static byte[] CalculateKeys(string password, string directory, bool domain) + public static byte[] CalculateKeys(string password, string directory, bool domain, string userSID = "") { - var usersid = Path.GetFileName(directory).TrimEnd(Path.DirectorySeparatorChar); + var usersid = ""; + + if (String.IsNullOrEmpty(directory)) + { + usersid = Path.GetFileName(directory).TrimEnd(Path.DirectorySeparatorChar); + } + else + { + usersid = userSID; + } var utf16pass = Encoding.Unicode.GetBytes(password); var utf16sid = Encoding.Unicode.GetBytes(usersid); diff --git a/SharpDPAPI/lib/Triage.cs b/SharpDPAPI/lib/Triage.cs index 70429b4..36a99e1 100755 --- a/SharpDPAPI/lib/Triage.cs +++ b/SharpDPAPI/lib/Triage.cs @@ -10,21 +10,20 @@ namespace SharpDPAPI { public class Triage { - public static Dictionary TriageUserMasterKeys(byte[] backupKeyBytes, bool show = false, string computerName = "", string password = "", string target = "") + public static Dictionary TriageUserMasterKeys(byte[] backupKeyBytes, bool show = false, string computerName = "", string password = "", string target = "", string userSID = "") { // triage all *user* masterkeys we can find, decrypting if the backupkey is supplied - + var mappings = new Dictionary(); var canAccess = false; if (!String.IsNullOrEmpty(target)) { // if we're targeting specific masterkey files - - if (backupKeyBytes.Length == 0) + if (((backupKeyBytes == null) || (backupKeyBytes.Length == 0)) && String.IsNullOrEmpty(userSID)) { // currently only backupkey is supported - Console.WriteLine("[X] The masterkey '/target:X' option currently requires '/pvk:BASE64...'"); + Console.WriteLine("[X] The masterkey '/target:X' option currently requires '/pvk:BASE64...' or '/password:X'"); return mappings; } @@ -49,7 +48,16 @@ namespace SharpDPAPI if (Helpers.IsGuid(f.Name)) { var masterKeyBytes = File.ReadAllBytes(file); - plaintextMasterKey = Dpapi.DecryptMasterKey(masterKeyBytes, backupKeyBytes); + if ((backupKeyBytes != null) && (backupKeyBytes.Length != 0)) + { + plaintextMasterKey = Dpapi.DecryptMasterKey(masterKeyBytes, backupKeyBytes); + } + else + { + byte[] hmacBytes = Dpapi.CalculateKeys(password, "", true, userSID); + plaintextMasterKey = Dpapi.DecryptMasterKeyWithSha(masterKeyBytes, hmacBytes); + } + mappings.Add(plaintextMasterKey.Key, plaintextMasterKey.Value); } } @@ -65,7 +73,15 @@ namespace SharpDPAPI try { var masterKeyBytes = File.ReadAllBytes(target); - plaintextMasterKey = Dpapi.DecryptMasterKey(masterKeyBytes, backupKeyBytes); + if ((backupKeyBytes != null) && (backupKeyBytes.Length != 0)) + { + plaintextMasterKey = Dpapi.DecryptMasterKey(masterKeyBytes, backupKeyBytes); + } + else + { + byte[] hmacBytes = Dpapi.CalculateKeys(password, "", true, userSID); + plaintextMasterKey = Dpapi.DecryptMasterKeyWithSha(masterKeyBytes, hmacBytes); + } mappings.Add(plaintextMasterKey.Key, plaintextMasterKey.Value); } catch (Exception e)