diff --git a/README.md b/README.md
index 8be22a4..977fab9 100755
--- a/README.md
+++ b/README.md
@@ -104,6 +104,7 @@ SharpDPAPI is licensed under the BSD 3-Clause license.
/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)
/server:SERVER - triage a remote server, assuming admin access
+ /local - machine is non domain-joined. Keys use SHA1 instead of NTLM. Password may be supplied in plaintext or as a SHA1 hash
Arguments for the credentials|vaults|rdg|keepass|triage|blob|ps commands:
diff --git a/SharpChrome/SharpChrome.csproj b/SharpChrome/SharpChrome.csproj
index 70692df..db14ecf 100755
--- a/SharpChrome/SharpChrome.csproj
+++ b/SharpChrome/SharpChrome.csproj
@@ -9,7 +9,7 @@
Properties
SharpChrome
SharpChrome
- v3.5
+ v4.8
512
@@ -23,6 +23,7 @@
prompt
4
true
+ false
AnyCPU
@@ -33,6 +34,7 @@
prompt
0
true
+ false
diff --git a/SharpChrome/app.config b/SharpChrome/app.config
index 2fa6e95..3e0e37c 100644
--- a/SharpChrome/app.config
+++ b/SharpChrome/app.config
@@ -1,3 +1,3 @@
-
+
diff --git a/SharpDPAPI/Commands/Blob.cs b/SharpDPAPI/Commands/Blob.cs
index a1ccdac..c0c0335 100755
--- a/SharpDPAPI/Commands/Blob.cs
+++ b/SharpDPAPI/Commands/Blob.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
+using System.Text.RegularExpressions;
namespace SharpDPAPI.Commands
{
@@ -63,7 +64,11 @@ 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);
+
+ Console.WriteLine("[*] Will decrypt user masterkeys with {0}: {1}\r\n",
+ Regex.IsMatch(password, @"^([a-f0-9]{32}|[a-f0-9]{40})$", RegexOptions.IgnoreCase)
+ ? "hash" : "password", password);
+
if (arguments.ContainsKey("/server"))
{
masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password);
diff --git a/SharpDPAPI/Commands/Credentials.cs b/SharpDPAPI/Commands/Credentials.cs
index a83c7c6..9ad4549 100755
--- a/SharpDPAPI/Commands/Credentials.cs
+++ b/SharpDPAPI/Commands/Credentials.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Text.RegularExpressions;
namespace SharpDPAPI.Commands
{
@@ -44,7 +45,11 @@ 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);
+
+ Console.WriteLine("[*] Will decrypt user masterkeys with {0}: {1}\r\n",
+ Regex.IsMatch(password, @"^([a-f0-9]{32}|[a-f0-9]{40})$", RegexOptions.IgnoreCase)
+ ? "hash" : "password", password);
+
if (arguments.ContainsKey("/server"))
{
masterkeys = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password);
diff --git a/SharpDPAPI/Commands/Masterkeys.cs b/SharpDPAPI/Commands/Masterkeys.cs
index aee4f58..fdcff32 100755
--- a/SharpDPAPI/Commands/Masterkeys.cs
+++ b/SharpDPAPI/Commands/Masterkeys.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Text.RegularExpressions;
namespace SharpDPAPI.Commands
{
@@ -46,7 +47,11 @@ public void Execute(Dictionary arguments)
else if (arguments.ContainsKey("/password"))
{
password = arguments["/password"];
- Console.WriteLine("[*] Will decrypt user masterkeys with password: {0}\r\n", password);
+
+ Console.WriteLine("[*] Will decrypt user masterkeys with {0}: {1}\r\n",
+ Regex.IsMatch(password, @"^([a-f0-9]{32}|[a-f0-9]{40})$", RegexOptions.IgnoreCase)
+ ? "hash" : "password", password);
+
if (arguments.ContainsKey("/server"))
{
mappings = Triage.TriageUserMasterKeys(null, true, arguments["/server"], password);
@@ -55,12 +60,13 @@ public void Execute(Dictionary arguments)
{
if (!arguments.ContainsKey("/sid"))
{
- Console.WriteLine("[X] When using /password:X with /target:X, a /sid:X (domain user SID) is required!");
+ Console.WriteLine("[X] When using /password:X with /target:X, a /sid:X (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"]);
+ mappings = Triage.TriageUserMasterKeys(null, true, "", password, arguments["/target"],
+ arguments["/sid"], false, arguments.ContainsKey("/local"));
}
}
else
@@ -79,7 +85,7 @@ public void Execute(Dictionary arguments)
{
if (!arguments.ContainsKey("/sid"))
{
- Console.WriteLine("[X] When dumping hashes with /target:X, a /sid:X (domain user SID) is required!");
+ Console.WriteLine("[X] When dumping hashes with /target:X, a /sid:X (user SID) is required!");
return;
}
else
diff --git a/SharpDPAPI/Commands/Protect.cs b/SharpDPAPI/Commands/Protect.cs
new file mode 100644
index 0000000..d7f4ee9
--- /dev/null
+++ b/SharpDPAPI/Commands/Protect.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using static SharpDPAPI.Crypto;
+
+namespace SharpDPAPI.Commands
+{
+ public class Protect : ICommand
+ {
+ public static string CommandName => "protect";
+
+ public void Execute(Dictionary arguments)
+ {
+ Console.WriteLine("\r\n[*] Action: Encrypt DPAPI blob");
+
+ if (!arguments.ContainsKey("/mkfile"))
+ {
+ Console.WriteLine("[!] Error: Provide a master key file using /mkfile:");
+ return;
+ }
+
+ if (!arguments.ContainsKey("/password"))
+ {
+ Console.WriteLine("[!] Error: Provide a password");
+ return;
+ }
+
+ if (!arguments.ContainsKey("/input"))
+ {
+ Console.WriteLine("[!] Error: provide an input file path or base64 using /input:");
+ return;
+ }
+
+ if (!arguments.ContainsKey("/output"))
+ {
+ Console.WriteLine("[!] Error: provide an output file path using /output:");
+ return;
+ }
+
+ byte[] plainBytes;
+ byte[] entropy = null;
+
+ string inputFile = arguments["/input"].Trim('"').Trim('\'');
+ string outputFile = arguments["/output"].Trim('"').Trim('\'');
+ string sid = arguments.ContainsKey("/sid") ? arguments["/sid"] : string.Empty;
+ string masterKeyFile = arguments["/mkfile"].Trim('"').Trim('\'');
+ string password = arguments["/password"];
+ bool isLocalMachine = arguments.ContainsKey("/local");
+ string description = arguments.ContainsKey("/description") ? arguments["/description"] : string.Empty;
+
+ if (arguments.ContainsKey("/entropy"))
+ {
+ entropy = Helpers.StringToByteArray(arguments["/entropy"]);
+ }
+
+ if (File.Exists(inputFile))
+ {
+ plainBytes = File.ReadAllBytes(inputFile);
+ }
+ else
+ {
+ plainBytes = Convert.FromBase64String(inputFile);
+ }
+
+ Console.WriteLine("[*] Using masterkey: {0}", masterKeyFile);
+
+ string userSID = string.Empty;
+ Dictionary keyDict;
+ KeyValuePair keyPair = default;
+
+ byte[] masterKeyBytes = null;
+ Guid masterKeyGuid = default;
+
+ try
+ {
+ if (!isLocalMachine)
+ {
+ userSID = string.IsNullOrEmpty(sid) ? sid : Dpapi.ExtractSidFromPath(masterKeyFile);
+ keyDict = Triage.TriageUserMasterKeys(null, password: password, target: masterKeyFile, local: true, userSID: userSID);
+ if (keyDict.Count == 1)
+ {
+ keyPair = keyDict.First();
+ masterKeyBytes = Helpers.StringToByteArray(keyPair.Value);
+ masterKeyGuid = new Guid(keyPair.Key);
+ }
+ }
+ else
+ {
+ keyPair = Dpapi.DecryptMasterKeyWithSha(File.ReadAllBytes(masterKeyFile), Helpers.StringToByteArray(password));
+ masterKeyBytes = Helpers.StringToByteArray(keyPair.Value);
+ masterKeyGuid = new Guid(keyPair.Key);
+ }
+ }
+ catch
+ {
+ }
+
+ if (masterKeyBytes == null || masterKeyGuid == null)
+ {
+ Console.WriteLine("[!] Failed to decrypt masterkey. Wrong password?");
+ return;
+ }
+
+ byte[] enc = Dpapi.CreateDPAPIBlob(plainBytes, masterKeyBytes,
+ EncryptionAlgorithm.CALG_AES_256,
+ HashAlgorithm.CALG_SHA_512,
+ masterKeyGuid,
+ isLocalMachine: isLocalMachine,
+ entropy: entropy,
+ description: description
+ );
+
+ File.WriteAllBytes(outputFile, enc);
+ Console.WriteLine("[+] Done! Wrote {0} bytes to: {1}", enc.Length, outputFile);
+ Console.WriteLine("[*] {0}", Convert.ToBase64String(enc));
+ }
+ }
+}
\ No newline at end of file
diff --git a/SharpDPAPI/Domain/CommandCollection.cs b/SharpDPAPI/Domain/CommandCollection.cs
index 8f1bd41..2c9f67f 100755
--- a/SharpDPAPI/Domain/CommandCollection.cs
+++ b/SharpDPAPI/Domain/CommandCollection.cs
@@ -33,6 +33,7 @@ public CommandCollection()
_availableCommands.Add(Certificate.CommandName, () => new Certificate());
_availableCommands.Add(Search.CommandName, () => new Search());
_availableCommands.Add(SCCM.CommandName, () => new SCCM());
+ _availableCommands.Add(Protect.CommandName, () => new Protect());
}
public bool ExecuteCommand(string commandName, Dictionary arguments)
diff --git a/SharpDPAPI/Domain/Info.cs b/SharpDPAPI/Domain/Info.cs
index f11fc48..9a69767 100755
--- a/SharpDPAPI/Domain/Info.cs
+++ b/SharpDPAPI/Domain/Info.cs
@@ -45,7 +45,7 @@ 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 - first decrypt the current user's masterkeys using a plaintext password or hash (works remotely)
/server:SERVER - triage a remote server, assuming admin access
@@ -53,7 +53,7 @@ public static void ShowUsage()
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.
+ /password:X - first decrypt the current user's masterkeys using a plaintext password or hash. 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
@@ -73,7 +73,17 @@ public static void ShowUsage()
/machine - use the local machine store for certificate triage
/mkfile | /target - for /machine triage
/pvk | /mkfile | /password | /server | /target - for user triage
-
+
+
+Encryption:
+ Arguments for the 'protect' command:
+ /input - the input file or base64 encoded string you want to protect using DPAPI
+ /output - the output file the encrypted blob will be written to
+ /mkfile - the path to the masterkey file to use for encryption
+ /password - the password, or password hash (SHA1 or NTLM) to decrypt the masterkey file
+ /local - encrypt the data with the local machine context instead of the user (requires SYSTEM DPAPI key)
+ /sid - provide the SID to use for decrypting the masterkey (by default this is guessed from the path)
+
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.
diff --git a/SharpDPAPI/SharpDPAPI.csproj b/SharpDPAPI/SharpDPAPI.csproj
index 05b1ba5..ffbe41d 100755
--- a/SharpDPAPI/SharpDPAPI.csproj
+++ b/SharpDPAPI/SharpDPAPI.csproj
@@ -9,7 +9,7 @@
Properties
SharpDPAPI
SharpDPAPI
- v3.5
+ v4.8
512
publish\
true
@@ -72,6 +72,7 @@
+
diff --git a/SharpDPAPI/app.config b/SharpDPAPI/app.config
index cf7e7ab..2c0f559 100755
--- a/SharpDPAPI/app.config
+++ b/SharpDPAPI/app.config
@@ -1,3 +1,3 @@
-
+
diff --git a/SharpDPAPI/lib/Crypto.cs b/SharpDPAPI/lib/Crypto.cs
index 25079c0..ce18d1e 100755
--- a/SharpDPAPI/lib/Crypto.cs
+++ b/SharpDPAPI/lib/Crypto.cs
@@ -3,11 +3,35 @@
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.IO;
+using System.Collections.Generic;
namespace SharpDPAPI
{
public class Crypto
{
+ public enum EncryptionAlgorithm
+ {
+ CALG_3DES = 26115,
+ CALG_AES_256 = 26128
+ }
+
+ public enum HashAlgorithm
+ {
+ CALG_SHA1 = 32772,
+ CALG_SHA_256 = 32780,
+ CALG_SHA_512 = 32782
+ }
+
+ public static byte[] GetRandomBytes(int length)
+ {
+ using (var rng = new RNGCryptoServiceProvider())
+ {
+ var randomBytes = new byte[length];
+ rng.GetBytes(randomBytes);
+ return randomBytes;
+ }
+ }
+
public static string KerberosPasswordHash(Interop.KERB_ETYPE etype, string password, string salt = "", int count = 4096)
{
// use the internal KERB_ECRYPT HashPassword() function to calculate a password hash of a given etype
@@ -38,15 +62,70 @@ public static string KerberosPasswordHash(Interop.KERB_ETYPE etype, string passw
return BitConverter.ToString(output).Replace("-", "");
}
+ public static byte[] EncryptBlob(byte[] plaintext, byte[] key,
+ EncryptionAlgorithm algCrypt, PaddingMode padding = PaddingMode.Zeros)
+ {
+ // encrypts a DPAPI blob using 3DES or AES
+
+ switch (algCrypt)
+ {
+ case EncryptionAlgorithm.CALG_3DES:
+ {
+ // takes a byte array of plaintext bytes and a key array, encrypt the blob with 3DES
+ var desCryptoProvider = new TripleDESCryptoServiceProvider();
+
+ var ivBytes = new byte[8];
+
+ desCryptoProvider.Key = key;
+ desCryptoProvider.IV = ivBytes;
+ desCryptoProvider.Mode = CipherMode.CBC;
+ desCryptoProvider.Padding = padding;
+ try
+ {
+ var ciphertextBytes = desCryptoProvider.CreateEncryptor()
+ .TransformFinalBlock(plaintext, 0, plaintext.Length);
+ return ciphertextBytes;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("[x] An exception occured: {0}", e);
+ }
+
+ return new byte[0];
+ }
+
+ case EncryptionAlgorithm.CALG_AES_256:
+ {
+ // takes a byte array of plaintext bytes and a key array, encrypt the blob with AES256
+ var aesCryptoProvider = new AesManaged();
+
+ var ivBytes = new byte[16];
+
+ aesCryptoProvider.Key = key;
+ aesCryptoProvider.IV = ivBytes;
+ aesCryptoProvider.Mode = CipherMode.CBC;
+ aesCryptoProvider.Padding = padding;
+
+ var ciphertextBytes = aesCryptoProvider.CreateEncryptor()
+ .TransformFinalBlock(plaintext, 0, plaintext.Length);
+
+ return ciphertextBytes;
+ }
+
+ default:
+ throw new Exception($"Could not encrypt blob. Unsupported algorithm: {algCrypt}");
+ }
+ }
+
public static byte[] DecryptBlob(byte[] ciphertext, byte[] key, int algCrypt, PaddingMode padding = PaddingMode.Zeros)
{
// decrypts a DPAPI blob using 3DES or AES
// reference: https://docs.microsoft.com/en-us/windows/desktop/seccrypto/alg-id
- switch (algCrypt)
+ switch ((EncryptionAlgorithm)algCrypt)
{
- case 26115: // 26115 == CALG_3DES
+ case EncryptionAlgorithm.CALG_3DES:
{
// takes a byte array of ciphertext bytes and a key array, decrypt the blob with 3DES
var desCryptoProvider = new TripleDESCryptoServiceProvider();
@@ -71,7 +150,7 @@ public static byte[] DecryptBlob(byte[] ciphertext, byte[] key, int algCrypt, Pa
return new byte[0];
}
- case 26128: // 26128 == CALG_AES_256
+ case EncryptionAlgorithm.CALG_AES_256:
{
// takes a byte array of ciphertext bytes and a key array, decrypt the blob with AES256
var aesCryptoProvider = new AesManaged();
@@ -93,6 +172,75 @@ public static byte[] DecryptBlob(byte[] ciphertext, byte[] key, int algCrypt, Pa
throw new Exception($"Could not decrypt blob. Unsupported algorithm: {algCrypt}");
}
}
+ /*
+def CryptSessionKeyWin7(masterkey, nonce, hashAlgo, entropy=None, strongPassword=None):
+ """Computes the decryption key for XP DPAPI blob, given the masterkey and optional information.
+
+ This implementation relies on an RFC compliant HMAC implementation
+ This algorithm is also used when checking the HMAC for integrity after decryption
+
+ :param masterkey: decrypted masterkey (should be 64 bytes long)
+ :param nonce: this is the nonce contained in the blob or the HMAC in the blob (integrity check)
+ :param entropy: this is the optional entropy from CryptProtectData() API
+ :param strongPassword: optional password used for decryption or the blob itself (integrity check)
+ :returns: decryption key
+ :rtype : str
+ """
+ if len(masterkey) > 20:
+ masterkey = hashlib.sha1(masterkey).digest()
+
+ digest = M2Crypto.EVP.HMAC(masterkey, hashAlgo.name)
+ digest.update(nonce)
+ if entropy is not None:
+ digest.update(entropy)
+ if strongPassword is not None:
+ digest.update(strongPassword)
+ return digest.final()
+ */
+
+ public static byte[] DeriveKey(byte[] key, byte[] nonce, int hashAlgorithm, byte[] blob, byte[] entropy = null)
+ {
+ HMAC hmac;
+ switch (hashAlgorithm)
+ {
+ case (int)HashAlgorithm.CALG_SHA1:
+ hmac = new HMACSHA1(key);
+ break;
+ case (int)HashAlgorithm.CALG_SHA_256:
+ hmac = new HMACSHA256(key);
+ break;
+ case (int)HashAlgorithm.CALG_SHA_512:
+ hmac = new HMACSHA512(key);
+ break;
+ default:
+ throw new Exception($"Unsupported hash algorithm: {hashAlgorithm}");
+ }
+
+ var keyMaterial = new List();
+ keyMaterial.AddRange(nonce);
+ if (entropy != null)
+ {
+ keyMaterial.AddRange(entropy);
+ }
+ keyMaterial.AddRange(blob);
+
+ return hmac.ComputeHash(keyMaterial.ToArray());
+ }
+
+ /*
+ def CryptDeriveKey(h, cipherAlgo, hashAlgo):
+ """Internal use. Mimics the corresponding native Microsoft function"""
+ if len(h) > hashAlgo.blockSize:
+ h = hashlib.new(hashAlgo.name, h).digest()
+ if len(h) >= cipherAlgo.keyLength:
+ return h
+ h += "\x00" * hashAlgo.blockSize
+ ipad = "".join(chr(ord(h[i]) ^ 0x36) for i in range(hashAlgo.blockSize))
+ opad = "".join(chr(ord(h[i]) ^ 0x5c) for i in range(hashAlgo.blockSize))
+ k = hashlib.new(hashAlgo.name, ipad).digest() + hashlib.new(hashAlgo.name, opad).digest()
+ k = cipherAlgo.do_fixup_key(k)
+ return k
+ */
public static byte[] DeriveKey(byte[] keyBytes, byte[] saltBytes, int algHash, byte[] entropy = null)
{
@@ -101,10 +249,12 @@ public static byte[] DeriveKey(byte[] keyBytes, byte[] saltBytes, int algHash, b
//Console.WriteLine("[*] key : {0}", BitConverter.ToString(keyBytes).Replace("-", ""));
//Console.WriteLine("[*] saltBytes : {0}", BitConverter.ToString(saltBytes).Replace("-", ""));
//Console.WriteLine("[*] entropy : {0}", BitConverter.ToString(entropy).Replace("-", ""));
- //Console.WriteLine("[*] algHash : {0}", algHash);
+ //Console.WriteLine("[*] algHash : {0}", (HashAlgorithm)algHash);
- if (algHash == 32782)
+ if (algHash == (int)HashAlgorithm.CALG_SHA_512)
{
+ // TODO: pretty sure this is wrong. It only calculates the session key but doesn't do derivation
+
// calculate the session key -> HMAC(salt) where the sha1(masterkey) is the key
// 32782 == CALG_SHA_512
@@ -117,7 +267,7 @@ public static byte[] DeriveKey(byte[] keyBytes, byte[] saltBytes, int algHash, b
{
return HMACSha512(keyBytes, saltBytes);
}
- } else if (algHash == 32772)
+ } else if (algHash == (int)HashAlgorithm.CALG_SHA1)
{
// 32772 == CALG_SHA1
@@ -156,6 +306,7 @@ public static byte[] DeriveKey(byte[] keyBytes, byte[] saltBytes, int algHash, b
}
else
{
+ Console.WriteLine("[!] Unsupported Hash Algorithm");
return new byte[0];
}
}
diff --git a/SharpDPAPI/lib/Dpapi.cs b/SharpDPAPI/lib/Dpapi.cs
index 576b02d..932b012 100755
--- a/SharpDPAPI/lib/Dpapi.cs
+++ b/SharpDPAPI/lib/Dpapi.cs
@@ -10,6 +10,8 @@
using System.Security.Principal;
using System.Text;
using System.Text.RegularExpressions;
+using static SharpDPAPI.Crypto;
+using HashAlgorithm = SharpDPAPI.Crypto.HashAlgorithm;
namespace SharpDPAPI
{
@@ -851,6 +853,176 @@ public static Dictionary PVKTriage(Dictionary ar
return masterkeys;
}
+ public static byte[] CreateDPAPIBlob(byte[] plaintext, byte[] masterKey, EncryptionAlgorithm algCrypt,
+ HashAlgorithm algHash, Guid masterKeyGuid, byte[] entropy = null, string description = "", bool isLocalMachine = false)
+ {
+ var descBytes = string.IsNullOrEmpty(description) ?
+ new byte[2] : Encoding.Unicode.GetBytes(description);
+
+ var flags = 0;
+ flags |= isLocalMachine ? 0x4 : 0; // CRYPTPROTECT_LOCAL_MACHINE
+
+ var saltBytes = GetRandomBytes(32); // Default salt length (TODO: check)
+ var hmacKeyLen = 0; // Default HMAC key length (TODO: check)
+ var hmac2KeyLen = 32; // Default HMAC2 key length (TODO: check)
+
+ var hmacKey = GetRandomBytes(hmacKeyLen);
+ var hmac2Key = GetRandomBytes(hmac2KeyLen);
+
+ var algHashLen = 0;
+ var algCryptLen = 0;
+ var signLen = 0;
+
+ switch (algCrypt)
+ {
+ case EncryptionAlgorithm.CALG_3DES:
+ algCryptLen = 192;
+ break;
+ case EncryptionAlgorithm.CALG_AES_256:
+ algCryptLen = 256;
+ break;
+ }
+
+ switch (algHash)
+ {
+ case HashAlgorithm.CALG_SHA1:
+ algHashLen = 160;
+ signLen = 20;
+ break;
+ case HashAlgorithm.CALG_SHA_256:
+ algHashLen = 256;
+ signLen = 32;
+ break;
+ case HashAlgorithm.CALG_SHA_512:
+ algHashLen = 512;
+ signLen = 64;
+ break;
+ }
+
+ // Deriving key
+ var derivedKeyBytes = Crypto.DeriveKey(masterKey, saltBytes, (int)algHash, entropy);
+ var finalKeyBytes = new byte[algCryptLen / 8];
+ Array.Copy(derivedKeyBytes, finalKeyBytes, algCryptLen / 8);
+
+ // Encrypting data
+ var encData = EncryptBlob(plaintext, finalKeyBytes, algCrypt, PaddingMode.PKCS7);
+
+ var blobLength = 0;
+ blobLength += 4; // dwVersion
+ blobLength += 16; // guidProvider
+ blobLength += 4; // dwMasterKeyVersion
+ blobLength += 16; // guidMasterKey
+ blobLength += 4; // dwFlags
+ blobLength += 4; // dwDescriptionLen
+ blobLength += descBytes.Length; // szDescription
+ blobLength += 4; // algCrypt
+ blobLength += 4; // dwAlgCryptLen
+ blobLength += 4; // dwSaltLen
+ blobLength += saltBytes.Length; // pbSalt
+ blobLength += 4; // dwHmacKeyLen
+ blobLength += hmacKey.Length; // pbHmackKey
+ blobLength += 4; // algHash
+ blobLength += 4; // dwAlgHashLen
+ blobLength += 4; // dwHmac2KeyLen
+ blobLength += hmac2Key.Length; // pbHmack2Key
+ blobLength += 4; // dwDataLen
+ blobLength += encData.Length; // pbData
+ blobLength += 4; // dwSignLen
+ blobLength += signLen; // pbSign
+
+ var blobBytes = new byte[blobLength];
+ var offset = 0;
+
+ // Setting version
+ var version = 1;
+ Array.Copy(BitConverter.GetBytes(version), 0, blobBytes, offset, 4);
+ offset += 4;
+
+ // Provider GUID (df9d8cd0-1501-11d1-8c7a-00c04fc297eb)
+ var providerGuid = new Guid("df9d8cd0-1501-11d1-8c7a-00c04fc297eb");
+ Array.Copy(providerGuid.ToByteArray(), 0, blobBytes, offset, 16);
+ offset += 16;
+
+ // Master key version
+ var masterKeyVersion = 1;
+ Array.Copy(BitConverter.GetBytes(masterKeyVersion), 0, blobBytes, offset, 4);
+ offset += 4;
+
+ // Master key GUID
+ Array.Copy(masterKeyGuid.ToByteArray(), 0, blobBytes, offset, 16);
+ offset += 16;
+
+ // Flags
+ Array.Copy(BitConverter.GetBytes(flags), 0, blobBytes, offset, 4);
+ offset += 4;
+
+ // Description length
+ Array.Copy(BitConverter.GetBytes(descBytes.Length), 0, blobBytes, offset, 4);
+ offset += 4;
+
+ // Description
+ Array.Copy(descBytes, 0, blobBytes, offset, descBytes.Length);
+ offset += descBytes.Length;
+
+ // Algorithm ID
+ Array.Copy(BitConverter.GetBytes((int)algCrypt), 0, blobBytes, offset, 4);
+ offset += 4;
+
+ // Algorithm key length
+ Array.Copy(BitConverter.GetBytes(algCryptLen), 0, blobBytes, offset, 4);
+ offset += 4;
+
+ // Salt length
+ Array.Copy(BitConverter.GetBytes(saltBytes.Length), 0, blobBytes, offset, 4);
+ offset += 4;
+
+ // Salt
+ Array.Copy(saltBytes, 0, blobBytes, offset, saltBytes.Length);
+ offset += saltBytes.Length;
+
+ // Copying HMAC key length
+ Array.Copy(BitConverter.GetBytes(hmacKeyLen), 0, blobBytes, offset, 4);
+ offset += 4;
+
+ // HMAC key
+ Array.Copy(hmacKey, 0, blobBytes, offset, hmacKeyLen);
+ offset += hmacKeyLen;
+
+ // Hash algorithm ID
+ Array.Copy(BitConverter.GetBytes((int)algHash), 0, blobBytes, offset, 4);
+ offset += 4;
+
+ // Hash length
+ Array.Copy(BitConverter.GetBytes(algHashLen), 0, blobBytes, offset, 4);
+ offset += 4;
+
+ // HMAC2 key length
+ Array.Copy(BitConverter.GetBytes(hmac2KeyLen), 0, blobBytes, offset, 4);
+ offset += 4;
+
+ // HMAC2 key
+ Array.Copy(hmac2Key, 0, blobBytes, offset, hmac2KeyLen);
+ offset += hmac2KeyLen;
+
+ // Data length
+ Array.Copy(BitConverter.GetBytes(encData.Length), 0, blobBytes, offset, 4);
+ offset += 4;
+
+ // Encrypted data
+ Array.Copy(encData, 0, blobBytes, offset, encData.Length);
+ offset += encData.Length;
+
+ // Sign (HMAC) over the entire blob except for the sign field
+ var signBlob = Helpers.SubArray(blobBytes, 20, offset - 20);
+ var sign = Crypto.DeriveKey(masterKey, hmac2Key, (int)algHash, signBlob, entropy);
+
+ // Copying sign length and sign
+ Array.Copy(BitConverter.GetBytes(signLen), 0, blobBytes, offset, 4);
+ offset += 4;
+ Array.Copy(sign, 0, blobBytes, offset, signLen);
+
+ return blobBytes;
+ }
public static byte[] DescribeDPAPIBlob(byte[] blobBytes, Dictionary MasterKeys, string blobType = "credential", bool unprotect = false, byte[] entropy = null)
{
@@ -904,6 +1076,7 @@ public static byte[] DescribeDPAPIBlob(byte[] blobBytes, Dictionary DecryptMasterKey(byte[] masterKeyBytes, byte[] backupKeyBytes)
@@ -2033,6 +2174,29 @@ private static bool IsValidHMAC(byte[] plaintextBytes, byte[] masterKeyFull, byt
return false;
}
+ private static byte[] ComputeHMAC(byte[] key, byte[] data, int offset, int count, HashAlgorithm algHash)
+ {
+ using (var hmac = CreateHMAC(key, algHash))
+ {
+ return hmac.ComputeHash(data, offset, count);
+ }
+ }
+
+ private static HMAC CreateHMAC(byte[] key, HashAlgorithm algHash)
+ {
+ switch (algHash)
+ {
+ case HashAlgorithm.CALG_SHA1:
+ return new HMACSHA1(key);
+ case HashAlgorithm.CALG_SHA_256:
+ return new HMACSHA256(key);
+ case HashAlgorithm.CALG_SHA_512:
+ return new HMACSHA512(key);
+ default:
+ throw new ArgumentException("Unsupported hash algorithm.");
+ }
+ }
+
public static KeyValuePair FormatHash(byte[] masterKeyBytes, string sid, int context = 3)
{
if (string.IsNullOrEmpty(sid) || masterKeyBytes == null)
diff --git a/SharpDPAPI/lib/Helpers.cs b/SharpDPAPI/lib/Helpers.cs
index bb1f274..c52ae38 100755
--- a/SharpDPAPI/lib/Helpers.cs
+++ b/SharpDPAPI/lib/Helpers.cs
@@ -13,6 +13,13 @@ namespace SharpDPAPI
{
public static class Helpers
{
+ public static byte[] SubArray(byte[] data, int index, int length)
+ {
+ byte[] result = new byte[length];
+ Array.Copy(data, index, result, 0, length);
+ return result;
+ }
+
public static void EncodeLength(BinaryWriter stream, int length)
{
if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be non-negative");
diff --git a/SharpDPAPI/lib/Triage.cs b/SharpDPAPI/lib/Triage.cs
index 56f9511..49a68ad 100755
--- a/SharpDPAPI/lib/Triage.cs
+++ b/SharpDPAPI/lib/Triage.cs
@@ -11,10 +11,9 @@ 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 local = false)
{
// triage all *user* masterkeys we can find, decrypting if the backupkey is supplied
-
var mappings = new Dictionary();
var preferred = new Dictionary();
var canAccess = false;
@@ -22,7 +21,7 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy
if (!String.IsNullOrEmpty(target))
{
// if we're targeting specific masterkey files
- if (((backupKeyBytes == null) || (backupKeyBytes.Length == 0)) && String.IsNullOrEmpty(userSID))
+ if (((backupKeyBytes == null) || (backupKeyBytes.Length == 0)) && String.IsNullOrEmpty(password))
{
// currently only backupkey is supported
Console.WriteLine("[X] The masterkey '/target:X' option currently requires '/pvk:BASE64...' or '/password:X'");
@@ -59,7 +58,7 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy
}
else if (!String.IsNullOrEmpty(password) && !String.IsNullOrEmpty(userSID))
{
- byte[] hmacBytes = Dpapi.CalculateKeys(password, "", true, userSID);
+ byte[] hmacBytes = Dpapi.CalculateKeys(password, "", !local, userSID);
plaintextMasterKey = Dpapi.DecryptMasterKeyWithSha(masterKeyBytes, hmacBytes);
}
else if (dumpHash)
@@ -90,7 +89,7 @@ public static Dictionary TriageUserMasterKeys(byte[] backupKeyBy
}
else if (!String.IsNullOrEmpty(password))
{
- byte[] hmacBytes = Dpapi.CalculateKeys(password, "", true, userSID);
+ byte[] hmacBytes = Dpapi.CalculateKeys(password, "", !local, userSID);
plaintextMasterKey = Dpapi.DecryptMasterKeyWithSha(masterKeyBytes, hmacBytes);
}
else if (dumpHash)