562 lines
14 KiB
C++
562 lines
14 KiB
C++
//
|
|
// Copyright (c) 2014 ANSCENTER. All rights reserved.
|
|
//
|
|
|
|
#include "precomp.h"
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include "wmihelper.h"
|
|
|
|
#else // !_WIN32
|
|
|
|
#if defined(__i386__) || defined(__amd64__)
|
|
#include <cpuid.h>
|
|
#endif
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <ifaddrs.h>
|
|
#include <netinet/in.h>
|
|
#include <net/if.h>
|
|
#ifdef __linux__
|
|
# include <sys/ioctl.h>
|
|
# include <netinet/in.h>
|
|
# include <unistd.h>
|
|
# include <string.h>
|
|
|
|
// Match Linux to FreeBSD
|
|
# define AF_LINK AF_PACKET
|
|
#else
|
|
# include <net/if_dl.h>
|
|
#endif
|
|
#endif
|
|
|
|
#include <list>
|
|
#include <string>
|
|
#include <functional>
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <stdio.h>
|
|
#include <set>
|
|
|
|
#include "bitstream2.h"
|
|
#include "bitstream3.h"
|
|
#include "base32.h"
|
|
#include "crc32.h"
|
|
|
|
#include "hwid.h"
|
|
#include "except.h"
|
|
#include "uniconv.h"
|
|
|
|
using namespace std;
|
|
|
|
string HardwareId::hardwareId;
|
|
|
|
#ifndef _WIN32
|
|
|
|
const sockaddr_in* castToIP4(const sockaddr* addr) {
|
|
if (addr == NULL) {
|
|
return NULL;
|
|
}
|
|
else if (addr->sa_family == AF_INET) {
|
|
// An IPv4 address
|
|
return reinterpret_cast<const sockaddr_in*>(addr);
|
|
}
|
|
else {
|
|
// Not an IPv4 address
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void GetCPUIDPropertyList(std::list<std::string>* propList)
|
|
{
|
|
unsigned int level = 1, eax = 0, ebx, ecx, edx = 0;
|
|
|
|
#if defined(__i386__) || defined(__amd64__)
|
|
__get_cpuid(level, &eax, &ebx, &ecx, &edx);
|
|
#endif
|
|
|
|
char buf[17];
|
|
sprintf(buf, "%08X%08X", edx, eax);
|
|
propList->push_back(buf);
|
|
}
|
|
|
|
void GetMACPropertyList(std::list<std::string>* propList, int maxCount)
|
|
{
|
|
// Head of the interface address linked list
|
|
ifaddrs* ifap = NULL;
|
|
|
|
int r = getifaddrs(&ifap);
|
|
|
|
if (r != 0) {
|
|
return;
|
|
}
|
|
|
|
ifaddrs* current = ifap;
|
|
|
|
if (current == NULL) {
|
|
return;
|
|
}
|
|
|
|
while (current != NULL && maxCount > 0) {
|
|
|
|
const sockaddr_in* interfaceAddress = castToIP4(current->ifa_addr);
|
|
const sockaddr_in* broadcastAddress = castToIP4(current->ifa_dstaddr);
|
|
const sockaddr_in* subnetMask = castToIP4(current->ifa_netmask);
|
|
|
|
if ((current->ifa_addr != NULL) && (current->ifa_addr->sa_family == AF_LINK)) {
|
|
#ifdef __linux__
|
|
struct ifreq ifr;
|
|
|
|
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
|
|
|
ifr.ifr_addr.sa_family = AF_INET;
|
|
strcpy(ifr.ifr_name, current->ifa_name);
|
|
ioctl(fd, SIOCGIFHWADDR, &ifr);
|
|
close(fd);
|
|
|
|
uint8_t* MAC = reinterpret_cast<uint8_t*>(ifr.ifr_hwaddr.sa_data);
|
|
|
|
#else // Posix/FreeBSD/Mac OS
|
|
|
|
sockaddr_dl* sdl = (struct sockaddr_dl*)current->ifa_addr;
|
|
uint8_t* MAC = reinterpret_cast<uint8_t*>(LLADDR(sdl));
|
|
#endif
|
|
if (MAC[0] != 0 || MAC[1] != 0 || MAC[2] != 0 || MAC[3] != 0 || MAC[4] != 0 || MAC[5] != 0)
|
|
{
|
|
char buf[18];
|
|
sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", MAC[0], MAC[1], MAC[2], MAC[3], MAC[4], MAC[5]);
|
|
propList->push_back(buf);
|
|
maxCount--;
|
|
}
|
|
}
|
|
|
|
current = current->ifa_next;
|
|
}
|
|
|
|
freeifaddrs(ifap);
|
|
ifap = NULL;
|
|
}
|
|
|
|
void GetHDDPropertyList(std::list<std::string>* propList, int maxCount)
|
|
{
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
unsigned short HardwareId::HashString(const char* str)
|
|
{
|
|
unsigned int crc = Crc32::Compute((const unsigned char*)str, strlen(str));
|
|
|
|
return (unsigned short)(crc >> 16);
|
|
}
|
|
|
|
unsigned short HardwareId::HashInt(int val)
|
|
{
|
|
unsigned char buf[4] = { (unsigned char)(val >> 24), (unsigned char)(val >> 16), (unsigned char)(val >> 8), (unsigned char)(val & 0xFF) };
|
|
unsigned int crc = Crc32::Compute(buf, 4);
|
|
|
|
return (unsigned short)(crc >> 16);
|
|
}
|
|
|
|
// LOCALE-INDEPENDENT trim from start (in place)
|
|
static inline void ltrim(std::string& s) {
|
|
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
|
|
return ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r' && ch != 0xA0;
|
|
}));
|
|
}
|
|
|
|
// LOCALE-INDEPENDENT trim from end (in place)
|
|
static inline void rtrim(std::string& s) {
|
|
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
|
|
return ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r' && ch != 0xA0;
|
|
}).base(), s.end());
|
|
}
|
|
|
|
// trim from both ends (in place)
|
|
static inline void trim(std::string& s) {
|
|
ltrim(s);
|
|
rtrim(s);
|
|
}
|
|
|
|
// LOCALE-INDEPENDENT uppercase conversion
|
|
static inline void toUpperInvariant(std::string& s) {
|
|
for (char& c : s) {
|
|
if (c >= 'a' && c <= 'z') {
|
|
c = c - 32;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Normalize hardware string for consistent hashing
|
|
static std::string NormalizeHardwareString(const std::string& input) {
|
|
std::string s = input;
|
|
|
|
// Trim all types of whitespace
|
|
trim(s);
|
|
|
|
// Remove all remaining whitespace characters
|
|
s.erase(std::remove_if(s.begin(), s.end(), [](unsigned char c) {
|
|
return c <= 32 || c == 0xA0; // Remove control chars and non-breaking space
|
|
}), s.end());
|
|
|
|
// Locale-independent uppercase for ASCII
|
|
toUpperInvariant(s);
|
|
|
|
// Remove non-ASCII characters (Korean text, etc.)
|
|
s.erase(std::remove_if(s.begin(), s.end(), [](unsigned char c) {
|
|
return c > 127;
|
|
}), s.end());
|
|
|
|
return s;
|
|
}
|
|
|
|
const char* HardwareId::GetCurrentHardwareId()
|
|
{
|
|
#ifdef _WIN32
|
|
|
|
WmiHelper wmi;
|
|
#endif
|
|
|
|
BitStream3 bits(125);
|
|
list<string> propList;
|
|
unsigned char version = 1;
|
|
unsigned short hash1, hash2, hash3, hash4;
|
|
unsigned short h0 = HashInt(0);
|
|
|
|
bits.Write(&version, 3);
|
|
|
|
propList.clear();
|
|
|
|
#ifdef _WIN32
|
|
|
|
wmi.GetPropertyList("SELECT * FROM Win32_ComputerSystemProduct", "UUID", &propList);
|
|
|
|
hash1 = h0;
|
|
|
|
string s;
|
|
|
|
for (list<string>::iterator iter = propList.begin(); iter != propList.end(); iter++)
|
|
{
|
|
s = NormalizeHardwareString(*iter);
|
|
|
|
if ((s.length() > 1)
|
|
&& (s != "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
|
|
&& (s != "00000000000000000000000000000000"))
|
|
{
|
|
hash1 = HashString(s.c_str());
|
|
}
|
|
}
|
|
|
|
#else
|
|
propList.push_back("4294967296");
|
|
#endif
|
|
|
|
bits.WriteUInt16(hash1);
|
|
|
|
// CPU IDs - get up to 4 for better resilience
|
|
propList.clear();
|
|
|
|
#ifdef _WIN32
|
|
wmi.GetPropertyList("SELECT ProcessorId FROM Win32_Processor", "ProcessorId", &propList, 4);
|
|
#else
|
|
GetCPUIDPropertyList(&propList);
|
|
#endif
|
|
|
|
// Normalize CPU IDs
|
|
for (auto& cpu : propList) {
|
|
cpu = NormalizeHardwareString(cpu);
|
|
}
|
|
|
|
hash1 = (propList.size() > 0) ? HashString(propList.front().c_str()) : h0;
|
|
hash2 = (propList.size() > 1) ? HashString(propList.back().c_str()) : h0;
|
|
|
|
bits.WriteUInt16(hash1);
|
|
bits.WriteUInt16(hash2);
|
|
|
|
// MAC Addresses - get MORE adapters and sort for consistency
|
|
propList.clear();
|
|
|
|
#ifdef _WIN32
|
|
// Try physical PCI adapters first
|
|
wmi.GetPropertyList("SELECT MACAddress FROM Win32_NetworkAdapter WHERE (AdapterType = \"Ethernet 802.3\") AND (MACAddress IS NOT NULL) AND PNPDeviceId LIKE \"%PCI%\"", "MACAddress", &propList, 6);
|
|
|
|
// Fallback to VMBUS for VMs
|
|
if (propList.size() == 0)
|
|
{
|
|
wmi.GetPropertyList("SELECT MACAddress FROM Win32_NetworkAdapter WHERE (AdapterType = \"Ethernet 802.3\") AND (MACAddress IS NOT NULL) AND PNPDeviceId LIKE \"%VMBUS%\"", "MACAddress", &propList, 6);
|
|
}
|
|
#else
|
|
GetMACPropertyList(&propList, 6);
|
|
#endif
|
|
|
|
// Normalize and sort MAC addresses for consistent ordering
|
|
for (auto& mac : propList) {
|
|
mac = NormalizeHardwareString(mac);
|
|
}
|
|
propList.sort();
|
|
|
|
// Store up to 4 MAC hashes
|
|
auto macIter = propList.begin();
|
|
hash1 = (propList.size() > 0 && macIter != propList.end()) ? HashString((macIter++)->c_str()) : h0;
|
|
hash2 = (propList.size() > 1 && macIter != propList.end()) ? HashString((macIter++)->c_str()) : h0;
|
|
hash3 = (propList.size() > 2 && macIter != propList.end()) ? HashString((macIter++)->c_str()) : h0;
|
|
hash4 = (propList.size() > 3 && macIter != propList.end()) ? HashString((macIter++)->c_str()) : h0;
|
|
|
|
bits.WriteUInt16(hash1);
|
|
bits.WriteUInt16(hash2);
|
|
bits.WriteUInt16(hash3);
|
|
bits.WriteUInt16(hash4);
|
|
|
|
// HDD Serial Numbers - get more drives and sort
|
|
propList.clear();
|
|
|
|
#ifdef _WIN32
|
|
wmi.GetPropertyList("SELECT SerialNumber FROM Win32_PhysicalMedia WHERE SerialNumber IS NOT NULL", "SerialNumber", &propList, 4);
|
|
#else
|
|
GetHDDPropertyList(&propList, 4);
|
|
#endif
|
|
|
|
// Normalize and sort HDD serials
|
|
for (auto& serial : propList) {
|
|
serial = NormalizeHardwareString(serial);
|
|
}
|
|
propList.sort();
|
|
|
|
auto hddIter = propList.begin();
|
|
hash1 = (propList.size() > 0 && hddIter != propList.end()) ? HashString((hddIter++)->c_str()) : h0;
|
|
hash2 = (propList.size() > 1 && hddIter != propList.end()) ? HashString((hddIter++)->c_str()) : h0;
|
|
|
|
bits.WriteUInt16(hash1);
|
|
bits.WriteUInt16(hash2);
|
|
|
|
unsigned char* rawHwid = bits.GetBuffer();
|
|
|
|
BASE32 base32;
|
|
|
|
auto encHwid = base32.encode(rawHwid, 16);
|
|
|
|
hardwareId = encHwid.c_str();
|
|
|
|
hardwareId.erase(hardwareId.length() - 1);
|
|
|
|
unsigned i;
|
|
unsigned insertPos;
|
|
// separate character groups
|
|
for (i = 0, insertPos = 5; i < 4; i++)
|
|
{
|
|
hardwareId.insert(insertPos, "-");
|
|
insertPos += 6;
|
|
}
|
|
|
|
return hardwareId.c_str();
|
|
}
|
|
|
|
bool HardwareId::MatchCurrentHardwareId(const char* hwid)
|
|
{
|
|
#ifdef _WIN32
|
|
WmiHelper wmi;
|
|
#endif
|
|
|
|
unsigned short h0 = HashInt(0);
|
|
string hardwareId((char*)hwid);
|
|
|
|
for (int i = 0, erasePos = 0; i < 4; i++)
|
|
{
|
|
erasePos += 5;
|
|
hardwareId.erase(erasePos, 1);
|
|
}
|
|
|
|
BASE32 base32;
|
|
int bufLen;
|
|
|
|
int padLen;
|
|
int len = base32.encode_pad_length(((int)hardwareId.length() * 5 + 7) >> 3, &padLen);
|
|
|
|
if (len > (int)hardwareId.length())
|
|
hardwareId.append(len - hardwareId.length(), 'A');
|
|
|
|
if (padLen)
|
|
hardwareId.append(padLen, '=');
|
|
|
|
auto buf = base32.decode(hardwareId.c_str(), hardwareId.length(), &bufLen);
|
|
|
|
BitStream3 bits;
|
|
bits.Attach(buf.data(), 125);
|
|
unsigned char version = 0;
|
|
|
|
bits.Read(&version, 3);
|
|
|
|
if ((version & 0x7) > 1)
|
|
throw new LicensingException(STATUS_GENERIC_ERROR, "invalid hardware id version");
|
|
|
|
list<string> propList;
|
|
set<unsigned short> storedHashes; // Store all hashes from license
|
|
unsigned short hash1, hash2, hash3, hash4;
|
|
|
|
// Use a points-based matching system for resilience
|
|
int matchPoints = 0;
|
|
const int REQUIRED_POINTS = 2; // Need at least 2 components to match
|
|
|
|
// Read UUID hash
|
|
bits.ReadUInt16(&hash1);
|
|
|
|
if (version == 0)
|
|
{
|
|
// Legacy version 0 support (memory size based)
|
|
if (hash1 != h0)
|
|
{
|
|
unsigned long long memSize = 0;
|
|
|
|
#ifdef _WIN32
|
|
wmi.GetPropertyList("SELECT Capacity FROM Win32_PhysicalMemory", "Capacity", &propList);
|
|
#else
|
|
propList.push_back("4294967296");
|
|
#endif
|
|
|
|
if (propList.size() > 0)
|
|
{
|
|
for (list<string>::iterator iter = propList.begin(); iter != propList.end(); iter++)
|
|
{
|
|
memSize += _atoi64(iter->c_str());
|
|
}
|
|
|
|
memSize = memSize / (1024 * 1024);
|
|
|
|
if (hash1 != HashInt((int)memSize))
|
|
return false;
|
|
}
|
|
}
|
|
matchPoints += 2; // UUID/Memory match gives 2 points
|
|
}
|
|
else
|
|
{
|
|
// Version 1+ - UUID based
|
|
#ifdef _WIN32
|
|
wmi.GetPropertyList("SELECT * FROM Win32_ComputerSystemProduct", "UUID", &propList);
|
|
#endif
|
|
if (propList.size() > 0)
|
|
{
|
|
string s = NormalizeHardwareString(propList.front());
|
|
|
|
if ((s.length() > 1)
|
|
&& (s != "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
|
|
&& (s != "00000000000000000000000000000000"))
|
|
{
|
|
if (hash1 != h0 && hash1 == HashString(s.c_str()))
|
|
{
|
|
matchPoints += 2; // UUID match is worth 2 points (most stable)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read CPU hashes
|
|
bits.ReadUInt16(&hash1);
|
|
bits.ReadUInt16(&hash2);
|
|
|
|
storedHashes.clear();
|
|
if (hash1 != h0) storedHashes.insert(hash1);
|
|
if (hash2 != h0) storedHashes.insert(hash2);
|
|
|
|
propList.clear();
|
|
|
|
#ifdef _WIN32
|
|
wmi.GetPropertyList("SELECT ProcessorId FROM Win32_Processor", "ProcessorId", &propList, 4);
|
|
#else
|
|
GetCPUIDPropertyList(&propList);
|
|
#endif
|
|
|
|
// Check if ANY current CPU matches stored hashes
|
|
int cpuMatches = 0;
|
|
for (auto& cpu : propList) {
|
|
string s = NormalizeHardwareString(cpu);
|
|
unsigned short currentHash = HashString(s.c_str());
|
|
if (storedHashes.find(currentHash) != storedHashes.end()) {
|
|
cpuMatches++;
|
|
}
|
|
}
|
|
|
|
if (cpuMatches > 0) {
|
|
matchPoints += 1; // CPU match is worth 1 point
|
|
}
|
|
|
|
// Read MAC hashes (now 4 hashes for better resilience)
|
|
bits.ReadUInt16(&hash1);
|
|
bits.ReadUInt16(&hash2);
|
|
bits.ReadUInt16(&hash3);
|
|
bits.ReadUInt16(&hash4);
|
|
|
|
storedHashes.clear();
|
|
if (hash1 != h0) storedHashes.insert(hash1);
|
|
if (hash2 != h0) storedHashes.insert(hash2);
|
|
if (hash3 != h0) storedHashes.insert(hash3);
|
|
if (hash4 != h0) storedHashes.insert(hash4);
|
|
|
|
propList.clear();
|
|
|
|
#ifdef _WIN32
|
|
wmi.GetPropertyList("SELECT MACAddress FROM Win32_NetworkAdapter WHERE (AdapterType = \"Ethernet 802.3\") AND (MACAddress IS NOT NULL) AND PNPDeviceId LIKE \"%PCI%\"", "MACAddress", &propList, 10);
|
|
|
|
if (propList.size() == 0)
|
|
{
|
|
wmi.GetPropertyList("SELECT MACAddress FROM Win32_NetworkAdapter WHERE (AdapterType = \"Ethernet 802.3\") AND (MACAddress IS NOT NULL) AND PNPDeviceId LIKE \"%VMBUS%\"", "MACAddress", &propList, 10);
|
|
}
|
|
#else
|
|
GetMACPropertyList(&propList, 10);
|
|
#endif
|
|
|
|
// Check if ANY current MAC address matches stored hashes
|
|
int macMatches = 0;
|
|
for (auto& mac : propList) {
|
|
string s = NormalizeHardwareString(mac);
|
|
unsigned short currentHash = HashString(s.c_str());
|
|
if (storedHashes.find(currentHash) != storedHashes.end()) {
|
|
macMatches++;
|
|
}
|
|
}
|
|
|
|
if (macMatches > 0) {
|
|
matchPoints += 1; // MAC match is worth 1 point
|
|
}
|
|
|
|
// Read HDD hashes
|
|
bits.ReadUInt16(&hash1);
|
|
bits.ReadUInt16(&hash2);
|
|
|
|
storedHashes.clear();
|
|
if (hash1 != h0) storedHashes.insert(hash1);
|
|
if (hash2 != h0) storedHashes.insert(hash2);
|
|
|
|
propList.clear();
|
|
|
|
#ifdef _WIN32
|
|
wmi.GetPropertyList("SELECT SerialNumber FROM Win32_PhysicalMedia WHERE SerialNumber IS NOT NULL", "SerialNumber", &propList, 10);
|
|
#else
|
|
GetHDDPropertyList(&propList, 10);
|
|
#endif
|
|
|
|
// Check if ANY current HDD serial matches stored hashes
|
|
int hddMatches = 0;
|
|
for (auto& serial : propList) {
|
|
string s = NormalizeHardwareString(serial);
|
|
unsigned short currentHash = HashString(s.c_str());
|
|
if (storedHashes.find(currentHash) != storedHashes.end()) {
|
|
hddMatches++;
|
|
}
|
|
}
|
|
|
|
if (hddMatches > 0) {
|
|
matchPoints += 1; // HDD match is worth 1 point
|
|
}
|
|
|
|
// Validation passes if we have enough matching points
|
|
// Typical valid scenarios:
|
|
// - UUID (2) + CPU (1) = 3 points ✓
|
|
// - UUID (2) + MAC (1) = 3 points ✓
|
|
// - UUID (2) + HDD (1) = 3 points ✓
|
|
// - CPU (1) + MAC (1) + HDD (1) = 3 points ✓ (if UUID changed but hardware same)
|
|
return matchPoints >= REQUIRED_POINTS;
|
|
} |