Dumping Stored Enterprise Wifi Credentials with Invoke-WifiSquid

Kyle Mistele
8 min readJun 28, 2021

Overview

In this post, I’m going to show you how to find and decrypt stored WPA2-PSK and WPA2-Enterprise network credentials on compromised Windows machines. First I’ll cover the technical background, and then I’ll go over the tool I wrote to automate this often tedious task for you.

I initially had the idea to write a tool to do this after writing my last blog post about stealing saved credentials from web browsers. The post got me thinking about other places that passwords are likely to be stored on Windows machines, and this seemed like a likely location. It makes sense — your computer usually auto-connects to wifi networks, even enterprise networks, that you’ve connected before, so it stands to reason that the passwords have to be stored on disk somewhere. Because enterprise networks can have you sign on with your AD credentials, this can be a way to find domain credentials for users and to move laterally.

I was surprised to find that there weren’t many tools to do this. I found one C# project, but it’s more of a manual process (you have to run the script multiple times and switch users, which may not be easily possible if you’re pentesting and don’t have user credentials), and I wanted something to build something to completely automate the process.

If you’re just here for the tool drop, you can find it here. If, however, you enjoy learning how your tools work and learning what Windows is doing under the hood, keep reading!

Primer on the DPAPI and Wireless Credentials

Windows encrypts wireless credentials using the DPAPI’s Protectfunction and decrypts them using the Unprotect function. The function calls are quite simple:

The method header for Protect
The method header for Unprotect

To use the functions, you pass in a byte array to protect/unprotect, an optional byte array to use for entropy, and a data protection scope.

The DPAPI is designed to be transparent: end users only need to worry about the interface, not the implementation. A primary goal of the API is to provide for secure encryption and decryption while allowing users to not have to deal with storing key information. The DPAPI’s inner workings are not documented. To my knowledge, there are no open-source implementations of the DPAPI. The API is available in C#, VBA, and PowerShell. It is also available in C/C++, although the method headers are a little different. (That’s also why you may have seen me refer to the functions as CryptProtectData and CryptUnprotectData elsewhere — those are the C function calls).

Passkey Networks

Passkey Storage

Windows stores XML configuration files for passkey networks (as well as public networks and some other types) inside of the WLAN Service’s program files. C:\Programdata\Microsoft\Wlansvc\Profiles\Interfaces . Inside the Interfaces folder, there will be one or more subfolders (assuming the device has a wireless adapter) for each adapter. The subfolders are named as GUIDs, so they’ll look something like this:

Yep I’m using powershell

Inside of the directory (and make sure to use single quotes when you cd since there are pointy brackets), there will be a bunch of XML files each of which are also named as GUIDs:

Each of these XML files is a single network. Some may be WPA2-PSK networks, others may be public networks, and others may be whatever other passkey standard people use. The network’s name (SSID) is at the XPath of WLANProfile/name for all profiles. For passkey networks, the DPAPI-protected passkey is at WLANProfile/MSM/security/sharedKey/keyMaterial . Here’s an example:

The keyMaterial field has been truncated for obvious reasons

Unfortunately, we can’t just pass this string to the DPAPI and decrypt it — this is actually a hex string that we need to convert into bytes. Each two characters are a hex byte. For example, D08C9DDCF0 would need to be translated to [0xD0, 0x8C, 0xC9D, 0xDC, 0xF0] .

Passkey Decryption

Once we translate the string to a byte array appropriately, we can pass it to the DPAPI’s Unprotect function (or CryptUnprotectData for C/C++) along with a null value for the random entropy, and the LocalMachine data protection scope. Theoretically, since any process can access the LocalMachine scope, any process should be able to decrypt these keys.

Enterprise Networks

Credential Storage

Enterprise network credentials are stored differently than passkey and public networks. While there will still be an XML profile file (as described above) for the network, there will not be a keyMaterial field that can be processed. Instead, enterprise network keys are stored in the registry of the user who owns the key. If you’re logged in as the user, that’ll be in the HKEY_CURRENT_USER (or HKCU ) hive, otherwise it will be in the HKEY_USERS (or HKU ) hive under a subkey that is the user’s SID. The full path is going to be HKEY_USERS\<SID>\Software\Microsoft\Wlansvc\Userdata\Profiles . It may possibly be under HKEY_USERS\<SID>\Software\Microsoft\Wlansvc\Profiles , although I have not seen this.

Under the Profiles subkey, there will be a subkey that’s the GUID of the network interface (matching the XML file for the profile), like this:

The full path to an enterprise network

Under that subkey will be a MSMUserData registry value of type REG_BINARY . Note that to read these registry values and perform the decryption, you will need either the privileges of the target user (in which case you can look in HKCU), otherwise, you will need or Administrator (to read, this may not suffice for decryption) or NT Authority\SYSTEM privileges.

Credential Decryption

To begin decrypting the credentials, you need to read this key, and then pass it to the Unprotect function with a null entropy value, and the LocalMachine scope. This first decryption step will make available the username and the domain name. One of two circumstances is possible: the password is further encrypted and still must be decrypted, or the password is now fully unencrypted (unlikely but perhaps possible.

First, we locate the username. If the password still needs to be decrypted, then the username will be prefixed by [0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00] . If the password has been fully decrypted, then the username will be prefixed by [0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00] . In either case, the username field will be null-terminated, and will then be directly be followed by the domain name, which is also null-terminated

Then, if the password is already decrypted, the password will follow and will similarly be null-terminated. If the password needs further decryption, then it will be prefixed by [0x01, 0x00, 0x00, 0x00, 0xd0, 0x8c, 0x9d, 0xdf, 0x01] , and will stretch until the end of the byte array. To decrypt it, we take the slice of the REG_BINARY that begins immediately after that sequence until the end, and pass it to the DPAPI’s Unprotect call. Note that this last step MUST be performed as the user who the profile belongs to. This may be difficult if you areNT Authority\SYSTEM or an Administrator, and are unwilling or unable to change the user’s password.

Executing commands as another user in PowerShell

To solve this problem, of executing the last step of the decryption as the user that the profile belongs to, I looked at a number of solutions. The other tool I found punts on this problem and says to switch users. There are a couple ways of achieving this including prompting the user for their credentials (that’s probably not stealthy), supplying their password if you know it, or something similar.

However, for my tool, I settled on a different option: scheduling a task. Since the Administrator and SYSTEM users can schedule tasks to run as other users (notably, without prompting for credentials or otherwise requiring them), I settled on using a scheduled task to perform the decryption. By writing out a PowerShell script to perform the decryption and then scheduling a task as the target user to execute it, we can obtain the decrypted password. I’ll go into more details on how it works in the next section.

Automating the Process with Invoke-WifiSquid

Unlike the tool that I found which is in C#, I elected to write this tool in PowerShell so that it’s portable across architectures, doesn’t require compilation, and can be executed in memory without touching the disk ( Powershell IEX,, anyone?). Invoke-WifiSquid automates the entire process of locating profile files and decrypting passkeys, and then it digs through users’ registries to locate keys for any enterprise networks.

Invoke-WifiSquid also automates the process of scheduling tasks to decrypt these enterprise network credentials. Once it finds them, it will check if additional decryption of the password is necessary (which is usually the case). If so, it writes the password out to a randomly-named .bin file in C:\Users\Public, and then writes out a secondary, randomly named PowerShell script to the same location.

When run, that secondary script will read in the binary file, perform the decryption, and then write out a randomly named text file to the same location containing the decrypted password. Invoke-WifiSquid creates a randomly named scheduled task to execute the secondary script as the target user, waits for it to complete, reads in the resulting password file, and then cleans up the files and the scheduled task. Here’s a snippet (thanks to @_Bobby_Tables ) to do task scheduling as another user:

Note that for this snippet, you need to fill in a file path and a time to execute. Invoke-WifiSquid handles all of this for you by scheduling a task five seconds into the future and keeping track of all the filenames.

Here’s the link to the project (again):

Note that for the full functionality of WPA2-Enterprise network credential decryption to work, you need to run the script as the NT AUTHORITY\SYSTEM account.

Final Thoughts

This tool was kind of a quick-and-dirty attempt at writing a script to automate this process. The code is probably not laid out super well, and I will continue working on it. Here are a few to-do’s I’m working on and will push soon:

  • More error handling, and more robust error handling
  • Testing on more platforms — so far, I have only tested it on Windows 10
  • Automatic elevation to SYSTEM from Administrator (maybe with a PowerShell implementation of PsExec?)

If you thought this post was cool, check out some of my other posts on stealing and decrypting saved browser credentials or attacking Kerberos with Impacket! Or, follow me on Twitter to stay up-to-date with more cool stuff!

--

--

Kyle Mistele

Student, hacker, OSCP. My other computer is your computer.