65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (16投票s)

2013年4月14日

CPOL

5分钟阅读

viewsIcon

60795

downloadIcon

5135

本文解释了如何使用 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,该进程的模拟令牌用于调整此线程令牌的特权。您可以使用任何其他机制来枚举进程(例如 NtQuerySystemInformationEnumprocess、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 是探索其更多功能的正确场所。祝您玩得开心!

© . All rights reserved.