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