引言
因为项目需要对单机版进行加密,进而需要要获取客户机一些硬件信息。现在把当时用到的一些方法列出来。
这里主要获取的是都是比较唯一的信息,不会随着系统重装而变化的,比如CPU ID、主板SN码、MAC地址(虽然MAC地址并不是绝对唯一,可以通过软件修改。不能选MAC的guid,它会随系统重装而变。)、显卡名称。
将这些信息按规则拼成字符串后再进行加密。
方法一:WMI
本文只关注怎么使用WMI技术获取硬件信息,具体它是个什么并未深究。摘一段百度的“硬解释”如下:
WMI(Windows Management Instrumentation,Windows 管理规范)是一项核心的 Windows 管理技术;用户可以使用 WMI 管理本地和远程计算机。
下面是获取主板SN码和显卡名称的代码:
HRESULT GetBaseBoardSNAndVideoInfos(std::string& o_strBaseBoard, std::list<std::string>& o_lstVideos)
{
HRESULT hRes = S_OK;
// Initialize COM.
hRes = CoInitializeEx(0, COINIT_APARTMENTTHREADED);
if (FAILED(hRes))
{
return hRes;
}
// Initialize
hRes = CoInitializeSecurity(
NULL,
-1, // COM negotiates service
NULL, // Authentication services
NULL, // Reserved
RPC_C_AUTHN_LEVEL_DEFAULT, // authentication
RPC_C_IMP_LEVEL_IMPERSONATE, // Impersonation
NULL, // Authentication info
EOAC_NONE, // Additional capabilities
NULL // Reserved
);
if (FAILED(hRes))
{
if (hRes != RPC_E_TOO_LATE)
{
CoUninitialize();
return hRes;
}
}
// Obtain the initial locator to Windows Management
// on a particular host computer.
IWbemLocator *pLoc = 0;
hRes = CoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID *)&pLoc);
if (FAILED(hRes))
{
CoUninitialize();
return hRes;
}
IWbemServices *pSvc = 0;
// Connect to the root\cimv2 namespace with the
// current user and obtain pointer pSvc
// to make IWbemServices calls.
hRes = pLoc->ConnectServer(
_bstr_t(L"ROOT\\CIMV2"), // WMI namespace
NULL, // User name
NULL, // User password
0, // Locale
NULL, // Security flags
0, // Authority
0, // Context object
&pSvc // IWbemServices proxy
);
if (FAILED(hRes))
{
CoUninitialize();
return 1; // Program has failed.
}
// Set the IWbemServices proxy so that impersonation
// of the user (client) occurs.
hRes = CoSetProxyBlanket(
pSvc, // the proxy to set
RPC_C_AUTHN_WINNT, // authentication service
RPC_C_AUTHZ_NONE, // authorization service
NULL, // Server principal name
RPC_C_AUTHN_LEVEL_CALL, // authentication level
RPC_C_IMP_LEVEL_IMPERSONATE, // impersonation level
NULL, // client identity
EOAC_NONE // proxy capabilities
);
if (FAILED(hRes))
{
pSvc->Release();
pLoc->Release();
CoUninitialize();
return hRes;
}
// Use the IWbemServices pointer to make requests of WMI.
// Make requests here:
// For example, query for all the running processes
IEnumWbemClassObject* pVideoEnumerator = NULL;
hRes = pSvc->ExecQuery(
bstr_t("WQL"),
bstr_t("SELECT * FROM Win32_VideoController"),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pVideoEnumerator);
ULONG uReturn = 0;
if (SUCCEEDED(hRes))
{
IWbemClassObject *pclsVideoObj;
while (pVideoEnumerator)
{
hRes = pVideoEnumerator->Next(WBEM_INFINITE, 1,
&pclsVideoObj, &uReturn);
if (0 == uReturn)
{
break;
}
VARIANT vtProp;
// Get the value of the Name property
hRes = pclsVideoObj->Get(L"Description", 0, &vtProp, 0, 0);
std::string strDes = (_bstr_t)vtProp.bstrVal;
o_lstVideos.push_back(strDes);
/*hRes = pclsVideoObj->Get(L"DeviceID", 0, &vtProp3, 0, 0);
wcout << "DeviceID: " << vtProp3.uintVal << endl;*/
VariantClear(&vtProp);
}
}
uReturn = 0;
IEnumWbemClassObject* pBordEnumerator = NULL;
hRes = pSvc->ExecQuery(
bstr_t("WQL"),
bstr_t("SELECT * FROM Win32_BaseBoard"),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pBordEnumerator);
if (SUCCEEDED(hRes))
{
IWbemClassObject *pclsBordObj;
while (pBordEnumerator)
{
hRes = pBordEnumerator->Next(WBEM_INFINITE, 1,
&pclsBordObj, &uReturn);
if (0 == uReturn)
{
break;
}
VARIANT vtProp;
// Get the value of the Name property
hRes = pclsBordObj->Get(L"SerialNumber", 0, &vtProp, 0, 0);
std::string strBordSN = (_bstr_t)vtProp.bstrVal;
o_strBaseBoard = strBordSN;
VariantClear(&vtProp);
}
}
// Cleanup
pSvc->Release();
pLoc->Release();
CoUninitialize();
return hRes;
}
代码中注释的已经很明确了,无需再多说什么,但是有几个点需要注意一下:
- 需要引入头文件、添加lib
#include <comdef.h>
#include <Wbemidl.h>
#pragma comment(lib, "wbemuuid.lib")
- CoInitializeSecurity在同一线程内不可连续多次调用,所以这里加了hRes != RPC_E_TOO_LATE的判断,以实现多次调用GetBaseBoardSNAndVideoInfos。我这个函数其实写的并不是很合理,对于这个获取硬件信息的小exe而言,CoInitializeEx、CoInitializeSecurity、CoUninitialize都应该只调用一次,不应多次调用。也就是说这三个函数应该在你程序的初始化、退出时调用,而非每一个获取硬件信息的子函数中调用。
- 核心代码在于wmi语句SELECT * FROM Win32_VideoController、SELECT * FROM Win32_BaseBoard。ExecQuery完语句后获取到IWbemClassObject,再去Get详细子信息。
- Win32_VideoController 显卡信息
Win32_BaseBoard 主板信息
Win32_NetworkAdapter 网卡信息 其下MACAddress即为MAC地址
Win32_Processor 网卡信息 其下SerialNumber即为CPU ID
具体想要哪一个,百度一下就行,网上挺全的。
再想详细了解的可以参考下这2个链接:
https://blog.csdn.net/wangshubo1989/article/details/51855895
https://www.cnblogs.com/yunsicai/articles/2986741.html
方法二:其他方法
以下这些其他方法是博主最初做项目时并不知道WMI,用的其他野路子,感觉相对WMI没有那么标准化、正规化。
获取CPU ID
//to get CPUID
#include "intrin.h"
int GetCpuId (std::string& o_sCpuId)
{
int nCPUInfo[4];
__cpuid(nCPUInfo, 1);
char chCPU[255];
sprintf_s(chCPU, 255, "%08X%08X%08X", nCPUInfo[0], nCPUInfo[2], nCPUInfo[3]); //do not use nCPUInfo[1], it might be different in different process, why?
o_sCpuId = chCPU;
return 1;
}
获取MAC地址
//to get the MAC
#include <iphlpapi.h>
#pragma comment (lib, "Ws2_32.lib")
#pragma comment(lib, "IPHLPAPI.lib")
HRESULT GetAllMacAddresses(std::list<std::string>& o_lstMacs)
{
// Allocate a 15 KB buffer to start with.
#define WORKING_BUFFER_SIZE 15000
#define MAX_TRIES 3
#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
DWORD dwRetVal = 0;
//unsigned int i = 0;
// Set the flags to pass to GetAdaptersAddresses
ULONG flags = GAA_FLAG_SKIP_UNICAST|GAA_FLAG_SKIP_ANYCAST|GAA_FLAG_SKIP_MULTICAST|GAA_FLAG_SKIP_DNS_SERVER|GAA_FLAG_SKIP_FRIENDLY_NAME;
// default to unspecified address family (both)
ULONG family = AF_UNSPEC;
PIP_ADAPTER_ADDRESSES pAddresses = NULL;
ULONG outBufLen = WORKING_BUFFER_SIZE;
ULONG Iterations = 0;
//step1. get all NIC info
do {
pAddresses = (IP_ADAPTER_ADDRESSES *) MALLOC(outBufLen);
if (pAddresses == NULL)
{
return E_OUTOFMEMORY;
}
dwRetVal = GetAdaptersAddresses(family, flags, NULL, pAddresses, &outBufLen);
if (dwRetVal == ERROR_BUFFER_OVERFLOW)
{
FREE(pAddresses);
pAddresses = NULL;
}
else
{
break;
}
Iterations++;
} while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (Iterations < MAX_TRIES));
if (dwRetVal != NO_ERROR)
{
return E_UNEXPECTED;
}
//step2. get all the names(which is guid)
//Mr.W 2019/06/26
//when the operating system is reinstalled,the guid of MAC changes
//but the address of MAC does not
//so we use address instead
PIP_ADAPTER_ADDRESSES pCurrAddresses = pAddresses;
while (pCurrAddresses)
{
if((MIB_IF_TYPE_ETHERNET != pCurrAddresses->IfType) && (IF_TYPE_IEEE80211 != pCurrAddresses->IfType))
{
pCurrAddresses = pCurrAddresses->Next;
continue;
}
pCurrAddresses->PhysicalAddress;
char chMAC[50] = "";
sprintf_s(chMAC, 50, "%02X-%02X-%02X-%02X-%02X-%02X",
pCurrAddresses->PhysicalAddress[0],
pCurrAddresses->PhysicalAddress[1],
pCurrAddresses->PhysicalAddress[2],
pCurrAddresses->PhysicalAddress[3],
pCurrAddresses->PhysicalAddress[4],
pCurrAddresses->PhysicalAddress[5]);
std::string t_strMAC = chMAC;
o_lstMacs.push_back(t_strMAC);
pCurrAddresses = pCurrAddresses->Next;
}
o_lstMacs.sort(std::less<std::string> ());
//sort(o_lstMacs.begin(), o_lstMacs.end(), less<string> ());
if (pAddresses)
{
FREE(pAddresses);
}
return S_OK;
}
上面2种方法是博主项目中实际所用,总体上更推荐方法一。