├── .gitignore
├── EnterpriseWifiPasswordRecover
├── UsernameStore.cs
├── Properties
│ └── AssemblyInfo.cs
├── EnterpriseWifiPasswordRecover.csproj
└── Program.cs
├── EnterpriseWifiPasswordRecover.sln
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs/*
2 | EnterpriseWifiPasswordRecover/bin/*
3 | EnterpriseWifiPasswordRecover/obj/*
--------------------------------------------------------------------------------
/EnterpriseWifiPasswordRecover/UsernameStore.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace EnterpriseWifiPasswordRecover
6 | {
7 | public class UsernameStore
8 | {
9 | public string username = "";
10 | public string password = "";
11 | public string domain = "";
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/EnterpriseWifiPasswordRecover.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27004.2008
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnterpriseWifiPasswordRecover", "EnterpriseWifiPasswordRecover\EnterpriseWifiPasswordRecover.csproj", "{34132F7D-D499-44F1-AAD4-E21B83F51A95}"
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 | {34132F7D-D499-44F1-AAD4-E21B83F51A95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {34132F7D-D499-44F1-AAD4-E21B83F51A95}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {34132F7D-D499-44F1-AAD4-E21B83F51A95}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {34132F7D-D499-44F1-AAD4-E21B83F51A95}.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 = {78A78C08-334E-4FE3-A684-6ACFB2687047}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/EnterpriseWifiPasswordRecover/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("EnterpriseWifiPasswordRecover")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("EnterpriseWifiPasswordRecover")]
13 | [assembly: AssemblyCopyright("Copyright © 2017")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("34132f7d-d499-44f1-aad4-e21b83f51a95")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/EnterpriseWifiPasswordRecover/EnterpriseWifiPasswordRecover.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {34132F7D-D499-44F1-AAD4-E21B83F51A95}
8 | Exe
9 | EnterpriseWifiPasswordRecover
10 | EnterpriseWifiPasswordRecover
11 | v2.0
12 | 512
13 |
14 |
15 | AnyCPU
16 | true
17 | full
18 | false
19 | bin\Debug\
20 | DEBUG;TRACE
21 | prompt
22 | 4
23 |
24 |
25 | AnyCPU
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EnterpriseWifiPasswordRecover
2 | This is a tool that recovers WPA2 Enterprise Wifi Credentials from a machine.
3 |
4 | # What does this do?
5 | - This tool recovers enterprise WPA2 MGT PEAP credentials from Windows machines
6 | - This repo has basic documentation on the format Windows stores the credentials in
7 |
8 | # Please note:
9 | - This solution has only been tested successfully on Windows 10. Additional testing to verify if this solution works on older versions of Windows is required.
10 |
11 | # How do I use this?
12 | - Compile it yourself using Visual Studio Community Edition (free download)
13 | - Alternately, grab the latest release from the releases section
14 | - The executable needs to be run multiple times
15 | - You need to run the executable as the `NT AUTHORITY\SYSTEM` user to decrypt the first layer of encryption
16 | - After that, it needs to be run in the context of the user who owns the WiFi network
17 |
18 | # How do I run the executable as NT AUTHORITY\SYSTEM ?
19 | - Download `psexec` which is part of the `Sysinternals Suite` from Microsoft
20 | - Open an administartive command prompt window
21 | - Type the following to get a system level command prompt `psexec -s -i cmd`
22 | - Type `whoami` to confirm that the command prompt is running as system
23 | - Execute the application using the system level command prompt
24 |
25 | # How are WPA2 MGT (Enterprise PEAP) Credentials stored in Windows 10?
26 | - There are a few edge cases, such as accessing a Wireless profile via the lock screen, however, this is the most typical case.
27 | - A user (e.g. Administrator) is logged into a PC.
28 | - The user connects to the WiFi access point and enters their credentials
29 | - The following ondisk directory structure is created:
30 | - `C:\ProgramData\Microsoft\Wlansvc\Profiles\Interfaces\{interfaceID}\{profileID}.xml`
31 | - The following registry entry is created to store the enterprise credentials
32 | - `HKEY_CURRENT_USER\Software\Microsoft\Wlansvc\UserData\Profiles\{profileID}\`
33 | - Sometimes it is stored in: `HKEY_LOCAL_MACHINE\Software\Microsoft\Wlansvc\UserData\Profiles\{profileID}\`
34 | - Sometimes it is stored in: `HKEY_LOCAL_MACHINE\Software\Microsoft\Wlansvc\Profiles\{profileID}\`
35 | - The "MSMUserData" binary value contains the encrypted credentials
36 | - In Windows 10, the first layer can be decrypted using the following C# code, run in the context of the system user:
37 | - `ProtectedData.Unprotect(, null, DataProtectionScope.LocalMachine);`
38 | - This will decrypt and the result will be a blog of binary data, looking at the data, it's easy to spot the domain and username in clear text.
39 | - The username comes directly after the following bytes `{ 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 }`, and terminates after a null byte if it is encrypted.
40 | - If the blob isn't encrypted with the user's credentials, then the username will be after `{ 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00 }` in plain text, then a series of null bytes, followed by a clear text password, then a series of null bytes and possibly a domain, if you reach `0x01` then there is no domain.
41 | - The domain comes after a bunch of the username, there will be a series of null bytes. If you reach a `0xE6` character, then there is no domain present in the credentials.
42 | - The password is then encrypted again using the same C# call, however, it is encrypted using the context of the user who first connected to the wireless network (or the user who first logged in after the connection was made)
43 | - The encrypted password section starts with (and includes) the following bytes `{ 0x01, 0x00, 0x00, 0x00, 0xD0, 0x8C, 0x9D, 0xDF, 0x01 }`
44 | - Decrypting this section gives the password in clear text, it also appears as though some additional null bytes may be present at the end, so they should be stripped.
45 |
46 | # What about WPA2 PSK (Pre Shared Key)?
47 | - You don't need a special tool to recover those
48 | - Open an admin command prompt
49 | - Type: `netsh wlan show profile` to list all of the wireless profiles
50 | - Type: `netsh wlan show profile name="" key=clear` which will reveal the PSK
51 | - For reference, the pre shared key is stored directly in the following location: `C:\ProgramData\Microsoft\Wlansvc\Profiles\Interfaces\{interfaceID}\{profileID}.xml`
52 | - The pre shared key needs to be converted from HEX into binary format, and then it can be decrypted using `ProtectedData.Unprotect(, null, DataProtectionScope.LocalMachine);` in the context of a system user.
53 |
54 | # What if I need a certificate to connect to a Wireless network?
55 | - I don't know -- more research to come
56 | - The certificate might actually be stored in the same blob
57 |
58 | # What about Windows 7 or earlier?
59 | - I need to do more research to figure this out
60 | - I made the program generic enough that it should catch the keys from earlier versions of Windows, however, I haven't officially tested it
61 | - I think they _may_ be stored in `HKEY_LOCAL_MACHINE` instead of `HKEY_CURRENT_USER`, but I haven't confirmed this yet
62 |
63 | # I have made an improvement, what do I do?
64 | - Make a pull request on the repo
65 | - I will review the pull request
66 | - If it's good to go, I will merge it
67 |
--------------------------------------------------------------------------------
/EnterpriseWifiPasswordRecover/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Win32;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Security.Cryptography;
5 | using System.Text;
6 |
7 | namespace EnterpriseWifiPasswordRecover
8 | {
9 | class Program
10 | {
11 | // The files we will store the output into
12 | public static string workingDirectory = System.IO.Directory.GetCurrentDirectory() + "/profiles/";
13 | public static string stage1Prefix = "stage1_";
14 | public static string stage2Prefix = "stage2_";
15 | public static string stage3Prefix = "decrypted_";
16 | public static string extension = ".txt";
17 |
18 | static void Main(string[] args)
19 | {
20 | // Ensure we have a profiles directory
21 | try
22 | {
23 | System.IO.Directory.CreateDirectory(workingDirectory);
24 | }
25 | catch
26 | {
27 | // do nothing
28 | }
29 |
30 | // Decrypting enterprise wifi passwords is done in 3 stages
31 | Stage1(); // Extract reg keys Typically runas: SYSTEM (it will pickup the keys of every user)
32 | Stage2(); // Try to decrypt first layer Typically runas: SYSTEM
33 | Stage3(); // Show creds, where possible Typically runas: the user that owns the connection
34 | }
35 |
36 | // Retreive the payloads from registry
37 | public static void Stage1()
38 | {
39 | // The location where the magic is stored in the registry
40 | string keyName = @"Software\Microsoft\Wlansvc\UserData\Profiles\";
41 | string keyName2 = @"Software\Microsoft\Wlansvc\Profiles\";
42 |
43 | // Will contain a list of places to search in the registry for profiles
44 | List possibleKeys = new List();
45 |
46 | // Try access all other user's registries
47 | try
48 | {
49 | string[] allUsers = Registry.Users.GetSubKeyNames();
50 |
51 | for(int i=0; i 0)
220 | {
221 | // Generate the payload
222 | string theOutput = profile + "\n";
223 | theOutput += "Domain: " + theStore.domain + "\n";
224 | theOutput += "Username: " + theStore.username + "\n";
225 | theOutput += "Password: " + theStore.password + "\n";
226 |
227 | System.IO.File.WriteAllText(workingDirectory + "/" + stage3Prefix + profile + extension, theOutput);
228 | }
229 | }
230 | catch
231 | {
232 | // Failed to read, continued
233 | Console.WriteLine("Failed to read " + profile);
234 | continue;
235 | }
236 | }
237 | }
238 |
239 | public static int SigScan(byte[] hayStack, byte[] needle, int startAt = 0, bool invert = false)
240 | {
241 | // Try every position
242 | for (int i = startAt; i < hayStack.Length; ++i)
243 | {
244 | // Attempt to perform the match
245 | bool broken = false;
246 | for (int j = 0; j < needle.Length; ++j)
247 | {
248 | if(invert)
249 | {
250 | // If there is a match, it's not the right signature
251 | if (needle[j] == hayStack[i + j])
252 | {
253 | broken = true;
254 | break;
255 | }
256 | } else
257 | {
258 | // If the bytes don't match, it's not the right signature
259 | if (needle[j] != hayStack[i + j])
260 | {
261 | broken = true;
262 | break;
263 | }
264 | }
265 | }
266 |
267 | if (!broken)
268 | {
269 | // We found a full match!
270 | return i;
271 | }
272 | }
273 |
274 | // No full match found
275 | return -1;
276 | }
277 |
278 | // Copies part of an existing byte[], or clones the whole thing
279 | public static byte[] Slice(byte[] data, int startIndex = -1, int endIndex = -1)
280 | {
281 | // Sanity checking / argument fixing
282 | if (startIndex < 0) startIndex = 0;
283 | if (endIndex < 0 || endIndex > data.Length) endIndex = data.Length;
284 | if (endIndex < startIndex) return new byte[0];
285 |
286 | // Allocate the memory for the new byte[] we will return
287 | int totalSize = endIndex - startIndex;
288 | byte[] toReturn = new byte[totalSize];
289 |
290 | // Copy the data into the new byte[]
291 | for(int i=0; i