Introduction
The attached sample application queries and displays the wireless settings of the connected wireless interface and lists the available networks.
The code itself is self-explanatory. The only area that needs some explanation is the part that decrypt's the key cipher text. If you are in Windows 7 or above,
then you will be getting the wireless key as plain text and no need to decrypt. If you are in Windows Vista, you will be getting the wireless key as
a cipher text and needs to decrypt. For the decryption to work, it is good to run as administrator.
Background
This sample uses Native Wifi API’s. The Native Wifi application programming interface (API) functions have two purposes: to manage wireless network profiles
and to manage wireless network connections. The API elements are exposed by the Auto Configuration Module (ACM). The Native Wi-Fi API contains functions,
structures, and enumerations that support wireless network connectivity and wireless profile management. The API can be used for both infrastructure and ad hoc networks.
XP Users:
A subset of the Native
Wi-Fi API functionality is supported on Windows XP with Service Pack 2 (SP2) and Windows XP with Service Pack 3(SP3).
This functionality is included in Windows XP with SP3 by default.
In Windows XP with SP2, this functionality can be added by applying a hot
fix, which is known as Wireless LAN API for Windows XP with Service Pack 2 (SP2). For more information or to download the hotfix, see Knowledge Base
at http://support.microsoft.com/kb/918997.
Using the code
Querying the Wireless Settings
The application links to wlan library and connects to the server using WlanOpenHandle
and after that enumertates the wireless interfaces and query the wireless settings
from the first connected interface.
#pragma comment(lib, "wlanapi.lib") // WLAN
dwResult = WlanOpenHandle (WLAN_API_MAKE_VERSION(2,0), NULL, &dwWLANVersion , &hClient);
In XP (SP2/SP3) WlanOpenHandle
will return an error message if Wireless Zero Configuration (WCZ) service is not running or not responsive.
The WlanEnumInterfaces
function enumerates all of the wireless LAN interfaces currently enabled on the local computer. This function returns a pointer
WLAN_INTERFACE_INFO_LIST
if succeeded. From this list WLAN_INTERFACE_INFO
represents each detected interface.
dwResult = WlanEnumInterfaces (hClient, NULL, &pWInfoList);
for (int i = 0; i < (int)pWInfoList->dwNumberOfItems; i++)
{
WLAN_INTERFACE_INFO *pInfo = &pWInfoList->InterfaceInfo[i];
StringFromGUID2 (pInfo->InterfaceGuid, (LPOLESTR) strGuidString, 39);
wifi.Set_InterfaceGUID (strGuidString);
wifi.Set_Adapter (pInfo->strInterfaceDescription);
.....
}
The WlanEnumInterfacesz
function allocates memory for the list of returned interfaces that is returned in the buffer pointed
to by the ppInterfaceList
parameter when the function succeeds. The memory used should be released by calling the
WlanFreeMemory
function after the buffer is no longer needed.
Once we got a connected interface, we can use WlanQueryInterface
to query various parameters of a specified interface.
In this example I am only interested in querying the Connection Attributes of the specified interface. From Connection Attributes, we will get Profile,
SSID, Signal Strength, Security type and key. There are lot of other useful attributes of the interface you can query. (Radio state, Channel numbers,
statistics, etc..)
dwResult = WlanQueryInterface (hClient, &pInfo->InterfaceGuid,
wlan_intf_opcode_current_connection,
NULL, &dwSize, (PVOID*)&pConnectionAttributes, NULL);
if (dwResult == ERROR_SUCCESS)
{
wifi.Set_Profile (pConnectionAttributes->strProfileName);
wifi.Set_SSID (GenerateSSID(pConnectionAttributes->wlanAssociationAttributes.dot11Ssid.ucSSID));
wifi.Set_Strength (pConnectionAttributes->wlanAssociationAttributes.wlanSignalQuality);
wifi.Set_Type (pConnectionAttributes->wlanAssociationAttributes.dot11BssType ==
2 ? L"Adhoc" : L"Infrastructure");
....
....
}
Once we got the wireless profile from connection attributes, we can get all other information's regarding that profile using the WlanGetProfile
API.
If the WlanGetProfile
function succeeds, the wireless profile is returned in the buffer pointed to by the pstrProfileXml
parameter.
The buffer contains a string that is the XML representation of the queried profile.
Getting the Wireless Key (Run the app as administrator)
Windows 7 and above :
The keyMaterial
element returned in the profile schema pointed to by the pstrProfileXml
may be requested as plaintext if the WlanGetProfile
function is called with the WLAN_PROFILE_GET_PLAINTEXT_KEY
flag set in the value pointed to by the pdwFlags
parameter on input.
For the WlanGetProfile
call to return the plain text key, the
wlan_secure_get_plaintext_key
permissions from the WLAN_SECURABLE_OBJECT
enumerated type must be set on the calling thread. The DACL must also contain an ACE that grants WLAN_READ_ACCESS
permission to the access token of the calling thread. By default, the permissions for retrieving the plain text key is allowed only to the members of the Administrators group on a local machine.
If the calling thread lacks the required permissions, the WlanGetProfile
function returns the encrypted key in the keyMaterial
element of the profile returned in the buffer pointed to by the pstrProfileXml
parameter. No error is returned if the calling thread lacks the required permissions.
if(dwOSMajorVersion > 5 && dwOSMinorVersion > 1) {
dwResult = WlanGetProfile (hClient, &pInfo->InterfaceGuid,wifi.Get_Profile().c_str(),
NULL, &pProfileXML, &dwFlags, &dwGranted);
if(dwResult == ERROR_SUCCESS)
{
wifi.Set_Security(ReadValuefromXML(pProfileXML, L"authentication"));
wifi.Set_Password(ReadValuefromXML(pProfileXML, L"keyMaterial"));
}
}
Windows Server 2008 and Windows Vista:
The keyMaterial
element returned in the profile schema pointed to by the pstrProfileXml
is always encrypted. If your process runs in the context of the LocalSystem account, then you can unencrypt key material by calling the CryptUnprotectData
function.
dwResult = WlanGetProfile (hClient, &pInfo->InterfaceGuid,wifi.Get_Profile().c_str(),
NULL, &pProfileXML, 0, 0);
if (dwResult == ERROR_SUCCESS)
{
wifi.Set_Security(ReadValuefromXML(pProfileXML, L"authentication"));
std::wstring strKey = ReadValuefromXML(pProfileXML, L"keyMaterial");
std::wstring strPWD = DecryptData(strKey);
wifi.Set_Password(strPWD);
}
Windows XP with SP3 and Wireless LAN API for Windows XP with SP2: Its not encrypted.
Decrypting the Key
So to be able to decrypt the key we have to call CryptUnprotectData
in LocalSystem security context. If your program is already running under LocalSystem context you can do this directly. If it's not so, but you have administrative rights or you have at least Debug privilege, you can borrow the LocalSystem token from some other process running on the computer. In this example I am getting the process token of "winlogon.exe" process and impersonate it.
Windows Added Integrity Levels to Process and Secured Objects (Resources) to control access to resource and provide a second level security. (Vista onwards) Each process/Resource is assigned an integrity level (LOW, MEDIUM, HIGH, SYSTEM). When a process is accessing a resource its integrity level is compared with the Resource's integrity level. If integrity level of process is less than resources , access is denied. By default a process launched by administrator has an integrity level of medium, if administrator runs the process in an elevated mode, the integrity level of that process becomes high. But there are certain system resources, process cannot access even its running on high integrity level. (E.g.: Crypto APIs) For that we need to elevate the integrity level of that particular thread to system integrity level. So if SeDebugPrivilege
is set on elevated process, the process/thread can access System integrity level resources. This functions shows how to set SeDebugPrivilege
. Privilege is stored in Access Token. So we need to add new privilege to the Token as shown below.
bool CWirelessHelper::SetPrivilege (std::wstring strPrivilege, HANDLE hToken, bool bEnable)
{
BOOL bReturn = false;
LUID luid;
bReturn = LookupPrivilegeValue (NULL, strPrivilege.c_str(), &luid); if (bReturn == false) return false;
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;
if (AdjustTokenPrivileges (hToken, FALSE, &tp, 0, NULL, NULL) != ERROR_SUCCESS)
{
return false;
}
return true;
}
In this sample I am using WMI to enumerate all running process and find the process ID of
winlogon.exe, whose impersonate token is what I am using to adjust the privilege of this thread's token. you can use any other mechanism to enumerate process (like NtQuerySystemInformation
, Enumprocess
, Toolhelp APIs).
bool CWirelessHelper :: ImpersonateToLoginUser()
{
HANDLE hProcess;
HANDLE hToken;
BOOL bSuccess;
DWORD dwProcID = GetProcessID (L"winlogon.exe");
if (dwProcID < 1) return false;
hProcess = OpenProcess (MAXIMUM_ALLOWED, FALSE, dwProcID);
if (hProcess == INVALID_HANDLE_VALUE)
return false;
bSuccess = OpenProcessToken (hProcess, MAXIMUM_ALLOWED, &hToken);
if (bSuccess)
{
SetPrivilege (SE_DEBUG_NAME, hToken, true); bSuccess = ImpersonateLoggedOnUser (hToken);
}
if (hProcess)
CloseHandle (hProcess);
if (hToken)
CloseHandle (hToken);
return bSuccess;
}
Once your process got enough privilege for using the Crypt API's you can decrypt the cipher text as shown below.
std::wstring CWirelessHelper:: DecryptData(const std::wstring& strKey)
{
std::wstring strPWD = L"";
BYTE byteKey[1024] = {0};
DWORD dwLength = 1024;
DATA_BLOB DataOut, DataVerify;
ImpersonateToLoginUser();
BOOL bReturn = CryptStringToBinary (strKey.c_str(), strKey.length(),
CRYPT_STRING_HEX, byteKey, &dwLength, 0, 0);
if(bReturn)
{
DataOut.cbData = dwLength;
DataOut.pbData = (BYTE*) byteKey;
}
if ( CryptUnprotectData (&DataOut, NULL, NULL, NULL, NULL, 0, &DataVerify))
{
CHAR str[MAX_PATH] = {0};
sprintf_s(str, "%hs", DataVerify.pbData);
std::string strData(str);
std::wstring strPassword(strData.begin(), strData.end());
strPWD = strPassword;
}
RevertToSelf();
return strPWD;
}
Listing the Available Networks
WLAN_AVAILABLE_NETWORK_LIST *pWnwList;
dwResult = WlanGetAvailableNetworkList(hClient, &pInfo->InterfaceGuid, 0, NULL, &pWnwList);
if(dwResult == ERROR_SUCCESS)
{
for (int i = 0; i < (int) pWnwList->dwNumberOfItems; i++)
{
WLAN_AVAILABLE_NETWORK *pNetwok = &pWnwList->Network[i];
wifi.Set_Available_NW(GenerateSSID(pNetwok->dot11Ssid.ucSSID));
}
}
Conclusion
The sample attached is developed using VS2012 and Windows8.
Native Wi-Fi provides a vast capabilities than what I have mentioned here. MSDN is the right place to explore more features that it supports. Have fun!