查询无线设置并解密无线密钥






4.89/5 (16投票s)
本文解释了如何使用 Crypto API 来获取无线设置和解密无线密钥。
简介
附件中的示例应用程序查询并显示已连接无线接口的无线设置,并列出可用的网络。代码本身易于理解。唯一需要解释的部分是解密密钥密文的部分。如果您使用的是 Windows 7 或更高版本,您将以明文形式获取无线密钥,无需解密。如果您使用的是 Windows Vista,您将以密文形式获取无线密钥,需要进行解密。为了使解密正常工作,最好以管理员身份运行。
背景
此示例使用 Native Wifi API。Native Wifi 应用程序编程接口 (API) 函数有两个目的:管理无线网络配置文件和管理无线网络连接。API 元素由自动配置模块 (ACM) 公开。Native Wi-Fi API 包含支持无线网络连接和无线配置文件管理的函数、结构和枚举。该 API 可用于基础结构网络和临时网络。
XP 用户
Windows XP Service Pack 2 (SP2) 和 Windows XP Service Pack 3 (SP3) 支持 Native Wi-Fi API 功能的子集。此功能已包含在 Windows XP SP3 中。
在 Windows XP SP2 中,可以通过应用一个称为“Windows XP Service Pack 2 (SP2) 无线 LAN API”的热修复程序来添加此功能。有关更多信息或下载热修复程序,请参阅知识库 http://support.microsoft.com/kb/918997。
使用代码
查询无线设置
该应用程序链接到 wlan 库,使用 WlanOpenHandle
连接到服务器,然后枚举无线接口并从第一个连接的接口查询无线设置。
#pragma comment(lib, "wlanapi.lib") // WLAN
dwResult = WlanOpenHandle (WLAN_API_MAKE_VERSION(2,0), NULL, &dwWLANVersion , &hClient);
在 XP (SP2/SP3) 中,如果无线零配置 (WCZ) 服务未运行或无响应,WlanOpenHandle
将返回错误消息。
WlanEnumInterfaces
函数枚举本地计算机上当前启用的所有无线 LAN 接口。如果成功,此函数将返回指向 WLAN_INTERFACE_INFO_LIST
的指针。从该列表中,WLAN_INTERFACE_INFO
代表每个检测到的接口。
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);
.....
}
当函数成功时,WlanEnumInterfacesz
函数会为返回的接口列表分配内存,该列表在 ppInterfaceList
参数指向的缓冲区中返回。在不再需要缓冲区后,应调用 WlanFreeMemory
函数释放使用的内存。
一旦我们获得了一个连接的接口,我们就可以使用 WlanQueryInterface
来查询指定接口的各种参数。在此示例中,我只对查询指定接口的连接属性感兴趣。从连接属性中,我们将获得配置文件、SSID、信号强度、安全类型和密钥。您还可以查询该接口的许多其他有用属性(无线状态、信道号、统计信息等)。
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");
....
....
}
一旦我们从连接属性中获得了无线配置文件,我们就可以使用 WlanGetProfile
API 获取有关该配置文件的所有其他信息。如果 WlanGetProfile
函数成功,无线配置文件将在 pstrProfileXml
参数指向的缓冲区中返回。该缓冲区包含一个字符串,该字符串是所查询配置文件的 XML 表示。
获取无线密钥(以管理员身份运行应用程序)
Windows 7 及更高版本:
当 WlanGetProfile
函数在 pdwFlags
参数指向的值中设置 WLAN_PROFILE_GET_PLAINTEXT_KEY
标志时,可以在配置文件架构中返回的 keyMaterial
元素作为明文请求。
为了使 WlanGetProfile
调用返回明文密钥,调用线程必须具有 WLAN_SECURABLE_OBJECT
枚举类型中的 wlan_secure_get_plaintext_key
权限。DACL 还必须包含一个 ACE,该 ACE 向调用线程的访问令牌授予 WLAN_READ_ACCESS
权限。默认情况下,仅允许本地计算机上的管理员组成员检索明文密钥。
如果调用线程缺少所需的权限,WlanGetProfile
函数将在 pstrProfileXml
参数指向的缓冲区中返回的配置文件中的 keyMaterial
元素中返回加密的密钥。如果调用线程缺少所需的权限,则不会返回错误。
if(dwOSMajorVersion > 5 && dwOSMinorVersion > 1) //Win7 and above
{
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 和 Windows Vista
在 pstrProfileXml
指向的配置文件架构中返回的 keyMaterial
元素始终是加密的。如果您的进程在 LocalSystem 帐户的上下文中运行,那么您可以通过调用 CryptUnprotectData
函数来解密密钥材料。
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 SP3 和 Windows XP SP2 无线 LAN API:它没有加密。
解密密钥
因此,为了能够解密密钥,我们必须在 LocalSystem 安全上下文中调用 CryptUnprotectData
。如果您的程序已经在 LocalSystem 上下文下运行,您可以直接执行此操作。如果不是,但您拥有管理员权限或至少拥有 Debug 权限,您可以从计算机上运行的另一个进程借用 LocalSystem 令牌。在此示例中,我获取“winlogon.exe”进程的进程令牌并进行模拟。
Windows 为进程添加了完整性级别,并为安全对象(资源)提供了访问控制和第二级安全(Vista 及更高版本)。每个进程/资源都有一个完整性级别(低、中、高、系统)。当进程访问资源时,会将其完整性级别与资源的完整性级别进行比较。如果进程的完整性级别低于资源,则拒绝访问。默认情况下,管理员启动的进程的完整性级别为中等;如果管理员以提升模式运行进程,则该进程的完整性级别将变为高。但有些系统资源,即使进程以高完整性级别运行,也无法访问(例如,Crypto API)。为此,我们需要将该特定线程的完整性级别提升到系统完整性级别。因此,如果提升的进程上设置了 SeDebugPrivilege
,则该进程/线程可以访问系统完整性级别的资源。此函数展示了如何设置 SeDebugPrivilege
。权限存储在访问令牌中。因此,我们需要像下面这样向令牌添加新权限。
bool CWirelessHelper::SetPrivilege (std::wstring strPrivilege, HANDLE hToken, bool bEnable)
{
BOOL bReturn = false;
LUID luid;
bReturn = LookupPrivilegeValue (NULL, strPrivilege.c_str(), &luid); // This is the val
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;
}
在此示例中,我使用 WMI 枚举所有正在运行的进程,并找到 winlogon.exe 的进程 ID,该进程的模拟令牌用于调整此线程令牌的特权。您可以使用任何其他机制来枚举进程(例如 NtQuerySystemInformation
、Enumprocess
、Toolhelp API)。
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;
}
一旦您的进程获得了使用 Crypt API 的足够权限,您就可以按如下方式解密密文。
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;
}
列出可用网络
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));
}
}
结论
附件中的示例是使用 VS2012 和 Windows8 开发的。
Native Wi-Fi 提供了比我在此提到的更广泛的功能。MSDN 是探索其更多功能的正确场所。祝您玩得开心!