wifiExtract

The other day my grandmother forgot her Windows WiFi SSID and password when she wanted to share it with a friend. So I thought if I could just automate the retrieval of her wireless profiles, she would never forget them again in the future.

It turns out, the Windows API offers a nice way to enumerate WLAN information. First, we open a handle to the WLAN system by first calling the WlanOpenHandle function, which we can then use to enumerate WLAN interfaces with the WlanEnumInterfaces function. 1

DWORD WlanEnumInterfaces(
  [in]  HANDLE                    hClientHandle,
  [in]  PVOID                     pReserved,
  [out] PWLAN_INTERFACE_INFO_LIST *ppInterfaceList
);

Once we’ve found a wireless network interface, we can iterate through its profiles. WLAN profiles are stored in XML format. We can see an example profile here.

>type %SYSTEMDRIVE%"\ProgramData\Microsoft\Wlansvc\Profiles\Interfaces\{FA6CC5AF-E3EC-4CDB-A7E9-014E5352F6FA}\{A181B5A9-A72D-445B-948A-DA29AC041866}.xml"
<?xml version="1.0"?>
<WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
        <name>networked</name>
        <SSIDConfig>
                <SSID>
                        <hex>6E6574776F726B6564</hex>
                        <name>networked</name>
                </SSID>
                <nonBroadcast>false</nonBroadcast>
        </SSIDConfig>
        <connectionType>ESS</connectionType>
        <connectionMode>manual</connectionMode>
        <MSM>
                <security>
                        <authEncryption>
                                <authentication>WPA2PSK</authentication>
                                <encryption>AES</encryption>
                                <useOneX>false</useOneX>
                        </authEncryption>
                        <sharedKey>
                                <keyType>passPhrase</keyType>
                                <protected>true</protected>
                                <keyMaterial>01000000D08C9DDF0115D1118C7A00C04FC297EB01000000C3D1445ECDFED24C989C1BE14BC7ABF5000000000200000000001066000000010000200000007F040AECC06A879362E949BAB5C2810179CABF3300DA399698E4E9D0F8814DD3000000000E80000000020000200000002C48A4A12EABFC325564EC54E086C181A46707218CF19C65151D89430EF5466010000000CC8C4E3C6AACC202F85168DD75B291A5400000004E136AF57200DD275D323E73BBEB2AEABF7AFA8BFEC60B0DA56972203222ABA576E751BF5F5B678CB367A2D6E6272BC691ACCEABDF3959B932EB1C9EE5426065</keyMaterial>
                        </sharedKey>
                </security>
        </MSM>
</WLANProfile>

For each WLAN profile, we try to locate the <keyMaterial> tags, which contain the SSID’s passphrase, and get its value.

But we have to account for the size of the <keyMaterial> tag itself when we do our check. So, when we find the tag, we add the size (13) to the beginning marker, as well as subtract the size of the ending marker—ensuring we only extract the passphrase within the key material tags and not any strings or characters from the tags themselves.

Altogether, for each WLAN profile we find, we use WlanGetProfile to acquire the SSID, aka the profileName, and its related passphrase.

DWORD WlanGetProfile(
  [in]                HANDLE     hClientHandle,
  [in]                const GUID *pInterfaceGuid,
  [in]                LPCWSTR    strProfileName,
  [in]                PVOID      pReserved,
  [out]               LPWSTR     *pstrProfileXml,
  [in, out, optional] DWORD      *pdwFlags,
  [out, optional]     DWORD      *pdwGrantedAccess
);

We decrypt the passphrase on-the-fly by appending the GET_PLAINTEXT_KEY flag to the WlanGetProfile2 function call.

for (DWORD i = 0; i < plist->dwNumberOfItems; ++i) {
        const WLAN_INTERFACE_INFO& interface_info = plist->InterfaceInfo[i];
        PWLAN_PROFILE_INFO_LIST profileList = NULL;

        result = WlanGetProfileList(hClient, &interface_info.InterfaceGuid, NULL, &profileList);
        if (result != ERROR_SUCCESS) {
            error("WlanGetProfileList failed: " + std::to_string(result));
            continue;
        }

        for (DWORD j = 0; j < profileList->dwNumberOfItems; ++j) {
            const WLAN_PROFILE_INFO& profileInfo = profileList->ProfileInfo[j];
            std::wstring profileName(profileInfo.strProfileName);

            LPWSTR xmlProfile = NULL;
            DWORD flags = WLAN_PROFILE_GET_PLAINTEXT_KEY;
            result = WlanGetProfile(hClient, &interface_info.InterfaceGuid, profileName.c_str(), NULL, &xmlProfile, &flags, NULL);
            if (result != ERROR_SUCCESS) {
                error("WlanGetProfile failed: " + std::to_string(result));
                continue;
            }

            std::wstring profileXml(xmlProfile);
            size_t key = profileXml.find(L"<keyMaterial>");
            if (key != std::wstring::npos) {
                size_t keyEnd = profileXml.find(L"</keyMaterial>", key);
                if (keyEnd != std::wstring::npos) {
                    std::wstring keyContent = profileXml.substr(key + 13, keyEnd - (key + 13));
                    std::wcout << L"[+] Found Wifi Profile:" << std::endl;
                    std::wcout << L"[+] SSID: " << profileName << std::endl;
                    std::wcout << L"[+] Key: " << keyContent << std::endl;
                    std::wcout << L" " << std::endl;
                }
            }
            else {
                std::wcout << L"[+] Found Wifi Profile:" << std::endl;
                std::wcout << L"[+] SSID: " << profileName << std::endl;
                std::wcout << L"[+] No key material found" << std::endl;
                std::wcout << L" " << std::endl;
            }

 // snipped
>msbuild wifiExtract\wifiExtract\wifiExtract.vcxproj
MSBuild version 17.13.19+0d9f5a35a for .NET Framework
Build started 3/19/2025 11:49:09 PM.

Project "C:\Users\augur\source\repos\wifiExtract\wifiExtract\wifiExtract.vcxproj" on node 1 (default targets)
.
PrepareForBuild:
  Structured output is enabled. The formatting of compiler diagnostics will reflect the error hierarchy. See
  https://aka.ms/cpp/structured-output for more details.
InitializeBuildStatus:
  Creating "wifiExtract\Debug\wifiExtract.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified.
  Touching "wifiExtract\Debug\wifiExtract.tlog\unsuccessfulbuild".
ClCompile:
  C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.43.34808\bin\HostX86\x86\CL.exe /c
   /ZI /JMC /nologo /W3 /WX- /diagnostics:column /sdl /Od /Oy- /D WIN32 /D _DEBUG /D _CONSOLE /D _UNICODE /D
  UNICODE /Gm- /EHsc /RTC1 /MDd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /permissive- /Fo"wifiExtr
  act\Debug\\" /Fd"wifiExtract\Debug\vc143.pdb" /external:W3 /Gd /TP /analyze- /FC /errorReport:queue wifiExt
  ract.cpp
  wifiExtract.cpp
Link:
  C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.43.34808\bin\HostX86\x86\link.exe
  /ERRORREPORT:QUEUE /OUT:"C:\Users\augur\source\repos\wifiExtract\wifiExtract\Debug\wifiExtract.exe" /INCREM
  ENTAL /ILK:"wifiExtract\Debug\wifiExtract.ilk" /NOLOGO kernel32.lib user32.lib gdi32.lib winspool.lib comdl
  g32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /MANIFEST /MANIFES
  TUAC:"level='asInvoker' uiAccess='false'" /manifest:embed /DEBUG /PDB:"C:\Users\augur\source\repos\wifiExtr
  act\wifiExtract\Debug\wifiExtract.pdb" /SUBSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT /IMPLIB:"C:\Users
  \augur\source\repos\wifiExtract\wifiExtract\Debug\wifiExtract.lib" /MACHINE:X86 wifiExtract\Debug\wifiExtra
  ct.obj
  wifiExtract.vcxproj -> C:\Users\augur\source\repos\wifiExtract\wifiExtract\Debug\wifiExtract.exe
FinalizeBuildStatus:
  Deleting file "wifiExtract\Debug\wifiExtract.tlog\unsuccessfulbuild".
  Touching "wifiExtract\Debug\wifiExtract.tlog\wifiExtract.lastbuildstate".
Done Building Project "C:\Users\augur\source\repos\wifiExtract\wifiExtract\wifiExtract.vcxproj" (default targ
ets).


Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:06.61
>wifiExtract.exe
[+] Found Wifi Profile:
[+] SSID: openNet
[+] No key material found

[+] Found Wifi Profile:
[+] SSID: networked
[+] Key: password123

wifiExtract on GitHub.