// // Copyright (c) 2014 ANSCENTER. All rights reserved. // #include "precomp.h" #ifdef _WIN32 #include "wmihelper.h" #else // !_WIN32 #if defined(__i386__) || defined(__amd64__) #include #endif #include #include #include #include #include #ifdef __linux__ # include # include # include # include // Match Linux to FreeBSD # define AF_LINK AF_PACKET #else # include #endif #endif #include #include #include #include #include #include #include #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(addr); } else { // Not an IPv4 address return NULL; } } void GetCPUIDPropertyList(std::list* 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* 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(ifr.ifr_hwaddr.sa_data); #else // Posix/FreeBSD/Mac OS sockaddr_dl* sdl = (struct sockaddr_dl*)current->ifa_addr; uint8_t* MAC = reinterpret_cast(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* 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 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::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 propList; set 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::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; }