From f3da3fa850f611bfad987443aea052d20c748e0e Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 18:34:47 -0500 Subject: [PATCH 001/116] Harden parsing and curl escaping --- auth.cpp | 39 ++++++++++++++++++++++++++++++++------- auth.hpp | 4 ++-- utils.cpp | 8 +++++--- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/auth.cpp b/auth.cpp index 90fd644..10b123a 100644 --- a/auth.cpp +++ b/auth.cpp @@ -72,6 +72,7 @@ void checkFiles(); void checkRegistry(); void error(std::string message); std::string generate_random_number(); +std::string curl_escape(CURL* curl, const std::string& input); std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; @@ -100,7 +101,7 @@ void KeyAuth::api::init() XorStr("type=init") + XorStr("&ver=") + version + XorStr("&hash=") + hash + - XorStr("&name=") + curl_easy_escape(curl, name.c_str(), 0) + + XorStr("&name=") + curl_escape(curl, name) + XorStr("&ownerid=") + ownerid; // to ensure people removed secret from main.cpp (some people will forget to) @@ -1421,8 +1422,8 @@ std::string KeyAuth::api::webhook(std::string id, std::string params, std::strin auto data = XorStr("type=webhook") + XorStr("&webid=") + id + - XorStr("¶ms=") + curl_easy_escape(curl, params.c_str(), 0) + - XorStr("&body=") + curl_easy_escape(curl, body.c_str(), 0) + + XorStr("¶ms=") + curl_escape(curl, params) + + XorStr("&body=") + curl_escape(curl, body) + XorStr("&conttype=") + contenttype + XorStr("&sessionid=") + sessionid + XorStr("&name=") + name + @@ -1601,7 +1602,15 @@ void KeyAuth::api::logout() { int VerifyPayload(std::string signature, std::string timestamp, std::string body) { - long long unix_timestamp = std::stoll(timestamp); + long long unix_timestamp = 0; + try { + unix_timestamp = std::stoll(timestamp); + } + catch (...) { + std::cerr << "[ERROR] Invalid timestamp format\n"; + MessageBoxA(0, "Signature verification failed (invalid timestamp)", "KeyAuth", MB_ICONERROR); + exit(2); + } auto current_time = std::chrono::system_clock::now(); long long current_unix_time = std::chrono::duration_cast( @@ -1678,14 +1687,30 @@ std::string get_str_between_two_str(const std::string& s, const std::string& start_delim, const std::string& stop_delim) { - unsigned first_delim_pos = s.find(start_delim); - unsigned end_pos_of_first_delim = first_delim_pos + start_delim.length(); - unsigned last_delim_pos = s.find(stop_delim); + const auto first_delim_pos = s.find(start_delim); + if (first_delim_pos == std::string::npos) + return {}; + const auto end_pos_of_first_delim = first_delim_pos + start_delim.length(); + const auto last_delim_pos = s.find(stop_delim, end_pos_of_first_delim); + if (last_delim_pos == std::string::npos || last_delim_pos < end_pos_of_first_delim) + return {}; return s.substr(end_pos_of_first_delim, last_delim_pos - end_pos_of_first_delim); } +std::string curl_escape(CURL* curl, const std::string& input) +{ + if (!curl) + return input; + char* escaped = curl_easy_escape(curl, input.c_str(), 0); + if (!escaped) + return {}; + std::string out(escaped); + curl_free(escaped); + return out; +} + void KeyAuth::api::setDebug(bool value) { KeyAuth::api::debug = value; } diff --git a/auth.hpp b/auth.hpp index f229460..df3abe4 100644 --- a/auth.hpp +++ b/auth.hpp @@ -124,7 +124,7 @@ namespace KeyAuth { api::user_data.createdate = data[XorStr("createdate")]; api::user_data.lastlogin = data[XorStr("lastlogin")]; - for (int i = 0; i < data[XorStr("subscriptions")].size(); i++) { // Prompto#7895 & stars#2297 was here + for (size_t i = 0; i < data[XorStr("subscriptions")].size(); i++) { // Prompto#7895 & stars#2297 was here subscriptions_class subscriptions; subscriptions.name = data[XorStr("subscriptions")][i][XorStr("subscription")]; subscriptions.expiry = data[XorStr("subscriptions")][i][XorStr("expiry")]; @@ -153,7 +153,7 @@ namespace KeyAuth { api::response.success = data["success"]; // intentional. Possibly trick a reverse engineer into thinking this string is for login function api::response.message = data["message"]; api::response.channeldata.clear(); //If you do not delete the data before pushing it, the data will be repeated. github.com/TTakaTit - for (const auto sub : data["messages"]) { + for (const auto& sub : data["messages"]) { std::string authoroutput = sub[XorStr("author")]; std::string messageoutput = sub["message"]; diff --git a/utils.cpp b/utils.cpp index 0b09151..221e5cc 100644 --- a/utils.cpp +++ b/utils.cpp @@ -12,9 +12,11 @@ std::string utils::get_hwid() { } std::time_t utils::string_to_timet(std::string timestamp) { - auto cv = strtol(timestamp.c_str(), NULL, 10); - - return (time_t)cv; + char* end = nullptr; + auto cv = strtol(timestamp.c_str(), &end, 10); + if (end == timestamp.c_str()) + return 0; + return static_cast(cv); } std::tm utils::timet_to_tm(time_t timestamp) { From 74bf34af9dec8608794887556119b71415581025 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 18:40:05 -0500 Subject: [PATCH 002/116] Add periodic integrity check --- auth.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/auth.cpp b/auth.cpp index 10b123a..da2b4e3 100644 --- a/auth.cpp +++ b/auth.cpp @@ -73,6 +73,7 @@ void checkRegistry(); void error(std::string message); std::string generate_random_number(); std::string curl_escape(CURL* curl, const std::string& input); +auto check_section_integrity( const char *section_name, bool fix ) -> bool; std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; @@ -81,6 +82,7 @@ bool initialized; std::string API_PUBLIC_KEY = "5586b4bc69c7a4b487e4563a4cd96afd39140f919bd31cea7d1c6a1e8439422b"; bool KeyAuth::api::debug = false; std::atomic LoggedIn(false); +std::atomic last_integrity_check{ 0 }; void KeyAuth::api::init() { @@ -2084,6 +2086,15 @@ void checkInit() { if (!initialized) { error(XorStr("You need to run the KeyAuthApp.init(); function before any other KeyAuth functions")); } + const auto now = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count(); + const auto last = last_integrity_check.load(); + if (now - last > 30) { + last_integrity_check.store(now); + if (check_section_integrity(XorStr(".text").c_str(), false)) { + error(XorStr("check_section_integrity() failed, don't tamper with the program.")); + } + } } // code submitted in pull request from https://github.com/BINM7MD BOOL bDataCompare(const BYTE* pData, const BYTE* bMask, const char* szMask) From aea56e35dbe3c5a79499d6200cdbec2edf9f940c Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 18:44:49 -0500 Subject: [PATCH 003/116] Add redundant integrity checks --- auth.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/auth.cpp b/auth.cpp index da2b4e3..054086b 100644 --- a/auth.cpp +++ b/auth.cpp @@ -74,6 +74,8 @@ void error(std::string message); std::string generate_random_number(); std::string curl_escape(CURL* curl, const std::string& input); auto check_section_integrity( const char *section_name, bool fix ) -> bool; +void integrity_check(); +void integrity_watchdog(); std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; @@ -87,6 +89,7 @@ std::atomic last_integrity_check{ 0 }; void KeyAuth::api::init() { std::thread(runChecks).detach(); + std::thread(integrity_watchdog).detach(); seed = generate_random_number(); std::atexit([]() { cleanUpSeedData(seed); }); CreateThread(0, 0, (LPTHREAD_START_ROUTINE)modify, 0, 0, 0); @@ -1604,6 +1607,7 @@ void KeyAuth::api::logout() { int VerifyPayload(std::string signature, std::string timestamp, std::string body) { + integrity_check(); long long unix_timestamp = 0; try { unix_timestamp = std::stoll(timestamp); @@ -1720,6 +1724,7 @@ void KeyAuth::api::setDebug(bool value) { std::string KeyAuth::api::req(const std::string& data, const std::string& url) { signature.clear(); signatureTimestamp.clear(); + integrity_check(); CURL* curl = curl_easy_init(); if (!curl) { @@ -2086,6 +2091,10 @@ void checkInit() { if (!initialized) { error(XorStr("You need to run the KeyAuthApp.init(); function before any other KeyAuth functions")); } + integrity_check(); +} + +void integrity_check() { const auto now = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count(); const auto last = last_integrity_check.load(); @@ -2096,6 +2105,18 @@ void checkInit() { } } } + +void integrity_watchdog() { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution sleep_seconds(20, 50); + while (true) { + Sleep(static_cast(sleep_seconds(gen) * 1000)); + if (check_section_integrity(XorStr(".text").c_str(), false)) { + error(XorStr("check_section_integrity() failed, don't tamper with the program.")); + } + } +} // code submitted in pull request from https://github.com/BINM7MD BOOL bDataCompare(const BYTE* pData, const BYTE* bMask, const char* szMask) { From ca78ab7381619c282b7e5be18ff812846ad7cabd Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 18:45:05 -0500 Subject: [PATCH 004/116] Harden integrity watchdog --- auth.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/auth.cpp b/auth.cpp index 054086b..1a708b2 100644 --- a/auth.cpp +++ b/auth.cpp @@ -85,6 +85,7 @@ std::string API_PUBLIC_KEY = "5586b4bc69c7a4b487e4563a4cd96afd39140f919bd31cea7d bool KeyAuth::api::debug = false; std::atomic LoggedIn(false); std::atomic last_integrity_check{ 0 }; +std::atomic integrity_fail_streak{ 0 }; void KeyAuth::api::init() { @@ -2112,8 +2113,15 @@ void integrity_watchdog() { std::uniform_int_distribution sleep_seconds(20, 50); while (true) { Sleep(static_cast(sleep_seconds(gen) * 1000)); + if (!initialized || !LoggedIn.load()) + continue; if (check_section_integrity(XorStr(".text").c_str(), false)) { - error(XorStr("check_section_integrity() failed, don't tamper with the program.")); + const int streak = integrity_fail_streak.fetch_add(1) + 1; + if (streak >= 2) { + error(XorStr("check_section_integrity() failed, don't tamper with the program.")); + } + } else { + integrity_fail_streak.store(0); } } } From 0a349d27b37bb6fd4bcd1801684b3af8014f3c6e Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 18:48:11 -0500 Subject: [PATCH 005/116] Cleanups and safety hardening --- auth.cpp | 8 +++++++- auth.hpp | 3 +++ killEmulator.hpp | 3 ++- utils.hpp | 4 +++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/auth.cpp b/auth.cpp index 1a708b2..e1aaf62 100644 --- a/auth.cpp +++ b/auth.cpp @@ -109,6 +109,9 @@ void KeyAuth::api::init() XorStr("&hash=") + hash + XorStr("&name=") + curl_escape(curl, name) + XorStr("&ownerid=") + ownerid; + if (curl) { + curl_easy_cleanup(curl); // avoid leak from escape helper. -nigel + } // to ensure people removed secret from main.cpp (some people will forget to) if (path.find("https") != std::string::npos) { @@ -1404,7 +1407,7 @@ std::vector KeyAuth::api::download(std::string fileid) { XorStr("&fileid=") + fileid + XorStr("&sessionid=") + sessionid + XorStr("&name=") + name + - XorStr("&ownerid=").c_str() + ownerid; + XorStr("&ownerid=") + ownerid; auto response = req(data, url); auto json = response_decoder.parse(response); @@ -1761,6 +1764,9 @@ std::string KeyAuth::api::req(const std::string& data, const std::string& url) { } void error(std::string message) { + for (char& c : message) { + if (c == '&' || c == '|' || c == '\"') c = ' '; // minimize cmd injection surface. -nigel + } system((XorStr("start cmd /C \"color b && title Error && echo ").c_str() + message + XorStr(" && timeout /t 5\"")).c_str()); LI_FN(__fastfail)(0); } diff --git a/auth.hpp b/auth.hpp index df3abe4..31e0920 100644 --- a/auth.hpp +++ b/auth.hpp @@ -153,6 +153,9 @@ namespace KeyAuth { api::response.success = data["success"]; // intentional. Possibly trick a reverse engineer into thinking this string is for login function api::response.message = data["message"]; api::response.channeldata.clear(); //If you do not delete the data before pushing it, the data will be repeated. github.com/TTakaTit + if (!data.contains("messages") || !data["messages"].is_array()) { + return; // avoid invalid server payload crash. -nigel + } for (const auto& sub : data["messages"]) { std::string authoroutput = sub[XorStr("author")]; diff --git a/killEmulator.hpp b/killEmulator.hpp index 50c2ddf..d1cdcd3 100644 --- a/killEmulator.hpp +++ b/killEmulator.hpp @@ -1,3 +1,4 @@ +#pragma once #include #include #include @@ -20,7 +21,7 @@ namespace protection MODULEINFO info; // place holder for the information // use this function in order to get the module information. - bool result = GetModuleInformation(reinterpret_cast(-1), + bool result = GetModuleInformation(GetCurrentProcess(), module, &info, sizeof(info)); if (result) { diff --git a/utils.hpp b/utils.hpp index a2e0343..c0cbf91 100644 --- a/utils.hpp +++ b/utils.hpp @@ -1,10 +1,12 @@ #pragma once #include +#include #include +#include namespace utils { std::string get_hwid(); std::time_t string_to_timet(std::string timestamp); std::tm timet_to_tm(time_t timestamp); -} \ No newline at end of file +} From 644ecd21a6cc04b6b872f7c5d8b7e6c1a5ed2f0e Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 18:52:03 -0500 Subject: [PATCH 006/116] Detect hosts file overrides --- auth.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/auth.cpp b/auth.cpp index e1aaf62..3cd7d39 100644 --- a/auth.cpp +++ b/auth.cpp @@ -76,6 +76,8 @@ std::string curl_escape(CURL* curl, const std::string& input); auto check_section_integrity( const char *section_name, bool fix ) -> bool; void integrity_check(); void integrity_watchdog(); +std::string extract_host(const std::string& url); +bool hosts_override_present(const std::string& host); std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; @@ -1721,6 +1723,45 @@ std::string curl_escape(CURL* curl, const std::string& input) return out; } +std::string extract_host(const std::string& url) +{ + std::string host = url; + const auto scheme_pos = host.find("://"); + if (scheme_pos != std::string::npos) + host = host.substr(scheme_pos + 3); + const auto slash_pos = host.find('/'); + if (slash_pos != std::string::npos) + host = host.substr(0, slash_pos); + const auto colon_pos = host.find(':'); + if (colon_pos != std::string::npos) + host = host.substr(0, colon_pos); + return host; +} + +bool hosts_override_present(const std::string& host) +{ + if (host.empty()) + return false; + const char* sysroot = std::getenv("SystemRoot"); + std::string hosts_path = sysroot ? std::string(sysroot) : "C:\\Windows"; + hosts_path += "\\System32\\drivers\\etc\\hosts"; + std::ifstream file(hosts_path); + if (!file.good()) + return false; + std::string line; + while (std::getline(file, line)) { + auto hash_pos = line.find('#'); + if (hash_pos != std::string::npos) + line = line.substr(0, hash_pos); + if (line.find(host) == std::string::npos) + continue; + // basic whole-word check + if (line.find(" " + host) != std::string::npos || line.find("\t" + host) != std::string::npos) + return true; + } + return false; +} + void KeyAuth::api::setDebug(bool value) { KeyAuth::api::debug = value; } @@ -1729,6 +1770,10 @@ std::string KeyAuth::api::req(const std::string& data, const std::string& url) { signature.clear(); signatureTimestamp.clear(); integrity_check(); + const auto host = extract_host(url); + if (hosts_override_present(host)) { + error(XorStr("Hosts file override detected for API host.")); + } CURL* curl = curl_easy_init(); if (!curl) { From 13311d9668119d8d790b7721c975ec69d80f1793 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 18:57:00 -0500 Subject: [PATCH 007/116] Harden timestamp and module checks --- auth.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/auth.cpp b/auth.cpp index 3cd7d39..3339c31 100644 --- a/auth.cpp +++ b/auth.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -78,6 +79,7 @@ void integrity_check(); void integrity_watchdog(); std::string extract_host(const std::string& url); bool hosts_override_present(const std::string& host); +bool module_paths_ok(); std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; @@ -88,6 +90,7 @@ bool KeyAuth::api::debug = false; std::atomic LoggedIn(false); std::atomic last_integrity_check{ 0 }; std::atomic integrity_fail_streak{ 0 }; +std::atomic last_module_check{ 0 }; void KeyAuth::api::init() { @@ -1628,10 +1631,11 @@ int VerifyPayload(std::string signature, std::string timestamp, std::string body long long current_unix_time = std::chrono::duration_cast( current_time.time_since_epoch()).count(); - if (current_unix_time - unix_timestamp > 20) { - std::cerr << "[ERROR] Timestamp too old (diff = " - << (current_unix_time - unix_timestamp) << "s)\n"; - MessageBoxA(0, "Signature verification failed (timestamp too old)", "KeyAuth", MB_ICONERROR); + const long long diff = std::llabs(current_unix_time - unix_timestamp); + if (diff > 120) { + std::cerr << "[ERROR] Timestamp too skewed (diff = " + << diff << "s)\n"; + MessageBoxA(0, "Signature verification failed (timestamp skew)", "KeyAuth", MB_ICONERROR); exit(3); } @@ -1762,6 +1766,35 @@ bool hosts_override_present(const std::string& host) return false; } +static std::wstring to_lower_ws(std::wstring value) +{ + std::transform(value.begin(), value.end(), value.begin(), + [](wchar_t c) { return static_cast(towlower(c)); }); + return value; +} + +bool module_paths_ok() +{ + const wchar_t* kModules[] = { L"ntdll.dll", L"kernel32.dll", L"kernelbase.dll", L"user32.dll" }; + const wchar_t* sysroot_env = _wgetenv(L"SystemRoot"); + std::wstring sysroot = sysroot_env ? sysroot_env : L"C:\\Windows"; + std::wstring sys32 = to_lower_ws(sysroot + L"\\System32\\"); + std::wstring syswow = to_lower_ws(sysroot + L"\\SysWOW64\\"); + + for (const auto* name : kModules) { + HMODULE mod = GetModuleHandleW(name); + if (!mod) + continue; + wchar_t path[MAX_PATH] = {}; + if (!GetModuleFileNameW(mod, path, MAX_PATH)) + return false; + std::wstring p = to_lower_ws(path); + if (p.rfind(sys32, 0) != 0 && p.rfind(syswow, 0) != 0) + return false; + } + return true; +} + void KeyAuth::api::setDebug(bool value) { KeyAuth::api::debug = value; } @@ -2143,6 +2176,15 @@ void checkInit() { if (!initialized) { error(XorStr("You need to run the KeyAuthApp.init(); function before any other KeyAuth functions")); } + const auto now = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count(); + const auto last_mod = last_module_check.load(); + if (now - last_mod > 60) { + last_module_check.store(now); + if (!module_paths_ok()) { + error(XorStr("module path check failed, possible side-load detected.")); + } + } integrity_check(); } @@ -2166,6 +2208,15 @@ void integrity_watchdog() { Sleep(static_cast(sleep_seconds(gen) * 1000)); if (!initialized || !LoggedIn.load()) continue; + const auto now = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count(); + const auto last_mod = last_module_check.load(); + if (now - last_mod > 120) { + last_module_check.store(now); + if (!module_paths_ok()) { + error(XorStr("module path check failed, possible side-load detected.")); + } + } if (check_section_integrity(XorStr(".text").c_str(), false)) { const int streak = integrity_fail_streak.fetch_add(1) + 1; if (streak >= 2) { From 54e1acec5b89ccfc6e88e673a2ec0b3bd08cf6cc Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 21:05:04 -0500 Subject: [PATCH 008/116] Add extra module check --- auth.cpp | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/auth.cpp b/auth.cpp index 3339c31..aefd7f7 100644 --- a/auth.cpp +++ b/auth.cpp @@ -34,11 +34,13 @@ #pragma comment(lib, "rpcrt4.lib") #pragma comment(lib, "httpapi.lib") +#pragma comment(lib, "psapi.lib") #include #include #include #include +#include #include #include #include @@ -80,6 +82,7 @@ void integrity_watchdog(); std::string extract_host(const std::string& url); bool hosts_override_present(const std::string& host); bool module_paths_ok(); +bool duplicate_system_modules_present(); std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; @@ -1795,6 +1798,42 @@ bool module_paths_ok() return true; } +bool duplicate_system_modules_present() +{ + const wchar_t* kModules[] = { L"ntdll.dll", L"kernel32.dll", L"kernelbase.dll", L"user32.dll" }; + const wchar_t* sysroot_env = _wgetenv(L"SystemRoot"); + std::wstring sysroot = sysroot_env ? sysroot_env : L"C:\\Windows"; + std::wstring sys32 = to_lower_ws(sysroot + L"\\System32\\"); + std::wstring syswow = to_lower_ws(sysroot + L"\\SysWOW64\\"); + + HMODULE mods[1024] = {}; + DWORD needed = 0; + if (!EnumProcessModules(GetCurrentProcess(), mods, sizeof(mods), &needed)) + return false; + + const size_t count = needed / sizeof(HMODULE); + for (size_t i = 0; i < count; ++i) { + wchar_t path[MAX_PATH] = {}; + if (!GetModuleFileNameExW(GetCurrentProcess(), mods[i], path, MAX_PATH)) + continue; + std::wstring p = to_lower_ws(path); + const auto name_pos = p.find_last_of(L"\\/"); + const std::wstring name = (name_pos == std::wstring::npos) ? p : p.substr(name_pos + 1); + bool is_target = false; + for (const auto* modname : kModules) { + if (name == modname) { + is_target = true; + break; + } + } + if (!is_target) + continue; + if (p.rfind(sys32, 0) != 0 && p.rfind(syswow, 0) != 0) + return true; + } + return false; +} + void KeyAuth::api::setDebug(bool value) { KeyAuth::api::debug = value; } @@ -2181,7 +2220,7 @@ void checkInit() { const auto last_mod = last_module_check.load(); if (now - last_mod > 60) { last_module_check.store(now); - if (!module_paths_ok()) { + if (!module_paths_ok() || duplicate_system_modules_present()) { error(XorStr("module path check failed, possible side-load detected.")); } } @@ -2213,7 +2252,7 @@ void integrity_watchdog() { const auto last_mod = last_module_check.load(); if (now - last_mod > 120) { last_module_check.store(now); - if (!module_paths_ok()) { + if (!module_paths_ok() || duplicate_system_modules_present()) { error(XorStr("module path check failed, possible side-load detected.")); } } From 01149afbcf56e2f9331f92cc648703dcd7b35aa0 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 21:11:15 -0500 Subject: [PATCH 009/116] Add module signature and RWX checks --- auth.cpp | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 2 deletions(-) diff --git a/auth.cpp b/auth.cpp index aefd7f7..87ebbdd 100644 --- a/auth.cpp +++ b/auth.cpp @@ -35,12 +35,16 @@ #pragma comment(lib, "rpcrt4.lib") #pragma comment(lib, "httpapi.lib") #pragma comment(lib, "psapi.lib") +#pragma comment(lib, "wintrust.lib") #include #include #include #include #include +#include +#include +#include #include #include #include @@ -83,6 +87,9 @@ std::string extract_host(const std::string& url); bool hosts_override_present(const std::string& host); bool module_paths_ok(); bool duplicate_system_modules_present(); +bool user_writable_module_present(); +bool module_has_rwx_section(HMODULE mod); +bool core_modules_signed(); std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; @@ -1776,6 +1783,72 @@ static std::wstring to_lower_ws(std::wstring value) return value; } +static bool path_has_any(const std::wstring& p, const std::initializer_list& needles) +{ + for (const auto& n : needles) { + if (p.find(n) != std::wstring::npos) + return true; + } + return false; +} + +bool module_has_rwx_section(HMODULE mod) +{ + if (!mod) + return false; + auto base = reinterpret_cast(mod); + auto dos = reinterpret_cast(base); + if (dos->e_magic != IMAGE_DOS_SIGNATURE) + return false; + auto nt = reinterpret_cast(base + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) + return false; + auto section = IMAGE_FIRST_SECTION(nt); + for (unsigned i = 0; i < nt->FileHeader.NumberOfSections; ++i, ++section) { + const auto ch = section->Characteristics; + if ((ch & IMAGE_SCN_MEM_EXECUTE) && (ch & IMAGE_SCN_MEM_WRITE)) + return true; + } + return false; +} + +static bool verify_signature(const std::wstring& path) +{ + WINTRUST_FILE_INFO file_info{}; + file_info.cbStruct = sizeof(file_info); + file_info.pcwszFilePath = path.c_str(); + + WINTRUST_DATA trust_data{}; + trust_data.cbStruct = sizeof(trust_data); + trust_data.dwUIChoice = WTD_UI_NONE; + trust_data.fdwRevocationChecks = WTD_REVOKE_NONE; + trust_data.dwUnionChoice = WTD_CHOICE_FILE; + trust_data.pFile = &file_info; + trust_data.dwStateAction = WTD_STATEACTION_IGNORE; + + GUID policy = WINTRUST_ACTION_GENERIC_VERIFY_V2; + LONG status = WinVerifyTrust(nullptr, &policy, &trust_data); + return status == ERROR_SUCCESS; +} + +bool core_modules_signed() +{ + const wchar_t* kModules[] = { L"ntdll.dll", L"kernel32.dll", L"kernelbase.dll", L"user32.dll" }; + for (const auto* name : kModules) { + HMODULE mod = GetModuleHandleW(name); + if (!mod) + return false; + wchar_t path[MAX_PATH] = {}; + if (!GetModuleFileNameW(mod, path, MAX_PATH)) + return false; + if (!verify_signature(path)) + return false; + if (module_has_rwx_section(mod)) + return false; + } + return true; +} + bool module_paths_ok() { const wchar_t* kModules[] = { L"ntdll.dll", L"kernel32.dll", L"kernelbase.dll", L"user32.dll" }; @@ -1834,6 +1907,36 @@ bool duplicate_system_modules_present() return false; } +bool user_writable_module_present() +{ + HMODULE mods[1024] = {}; + DWORD needed = 0; + if (!EnumProcessModules(GetCurrentProcess(), mods, sizeof(mods), &needed)) + return false; + + wchar_t exe_path[MAX_PATH] = {}; + GetModuleFileNameW(nullptr, exe_path, MAX_PATH); + std::wstring exe_dir = exe_path; + const auto last_slash = exe_dir.find_last_of(L"\\/"); + if (last_slash != std::wstring::npos) + exe_dir = exe_dir.substr(0, last_slash + 1); + exe_dir = to_lower_ws(exe_dir); + + const size_t count = needed / sizeof(HMODULE); + for (size_t i = 0; i < count; ++i) { + wchar_t path[MAX_PATH] = {}; + if (!GetModuleFileNameExW(GetCurrentProcess(), mods[i], path, MAX_PATH)) + continue; + std::wstring p = to_lower_ws(path); + if (p.rfind(exe_dir, 0) == 0) + continue; + + if (path_has_any(p, { L"\\temp\\", L"\\appdata\\local\\temp\\", L"\\downloads\\" })) + return true; + } + return false; +} + void KeyAuth::api::setDebug(bool value) { KeyAuth::api::debug = value; } @@ -2220,7 +2323,7 @@ void checkInit() { const auto last_mod = last_module_check.load(); if (now - last_mod > 60) { last_module_check.store(now); - if (!module_paths_ok() || duplicate_system_modules_present()) { + if (!module_paths_ok() || duplicate_system_modules_present() || user_writable_module_present() || !core_modules_signed()) { error(XorStr("module path check failed, possible side-load detected.")); } } @@ -2252,7 +2355,7 @@ void integrity_watchdog() { const auto last_mod = last_module_check.load(); if (now - last_mod > 120) { last_module_check.store(now); - if (!module_paths_ok() || duplicate_system_modules_present()) { + if (!module_paths_ok() || duplicate_system_modules_present() || user_writable_module_present() || !core_modules_signed()) { error(XorStr("module path check failed, possible side-load detected.")); } } From 6302206843da969b35a9d02569d9c7f0fe3a7290 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 22:04:47 -0500 Subject: [PATCH 010/116] Add hypervisor detection --- auth.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/auth.cpp b/auth.cpp index 87ebbdd..0f6feda 100644 --- a/auth.cpp +++ b/auth.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,7 @@ bool duplicate_system_modules_present(); bool user_writable_module_present(); bool module_has_rwx_section(HMODULE mod); bool core_modules_signed(); +bool hypervisor_present(); std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; @@ -1937,6 +1939,56 @@ bool user_writable_module_present() return false; } +static bool reg_key_exists(HKEY root, const wchar_t* path) +{ + HKEY h = nullptr; + const LONG res = RegOpenKeyExW(root, path, 0, KEY_READ, &h); + if (res == ERROR_SUCCESS) { + RegCloseKey(h); + return true; + } + return false; +} + +static bool file_exists(const std::wstring& path) +{ + const DWORD attr = GetFileAttributesW(path.c_str()); + return (attr != INVALID_FILE_ATTRIBUTES) && !(attr & FILE_ATTRIBUTE_DIRECTORY); +} + +bool hypervisor_present() +{ + int cpu_info[4] = {}; + __cpuid(cpu_info, 1); + const bool hv_bit = (cpu_info[2] & (1 << 31)) != 0; + if (hv_bit) { + return true; + } + + // registry artifacts (conservative) + if (reg_key_exists(HKEY_LOCAL_MACHINE, L"HARDWARE\\ACPI\\DSDT\\VBOX__") || + reg_key_exists(HKEY_LOCAL_MACHINE, L"HARDWARE\\ACPI\\DSDT\\VMWARE") || + reg_key_exists(HKEY_LOCAL_MACHINE, L"HARDWARE\\ACPI\\DSDT\\XEN") || + reg_key_exists(HKEY_LOCAL_MACHINE, L"SOFTWARE\\VMware, Inc.\\VMware Tools") || + reg_key_exists(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Oracle\\VirtualBox Guest Additions")) { + return true; + } + + // file artifacts (drivers/tools) + if (file_exists(L"C:\\Windows\\System32\\drivers\\VBoxGuest.sys") || + file_exists(L"C:\\Windows\\System32\\drivers\\VBoxMouse.sys") || + file_exists(L"C:\\Windows\\System32\\drivers\\VBoxSF.sys") || + file_exists(L"C:\\Windows\\System32\\drivers\\VBoxVideo.sys") || + file_exists(L"C:\\Windows\\System32\\drivers\\vmhgfs.sys") || + file_exists(L"C:\\Windows\\System32\\drivers\\vmmouse.sys") || + file_exists(L"C:\\Windows\\System32\\drivers\\vm3dmp.sys") || + file_exists(L"C:\\Windows\\System32\\drivers\\xen.sys")) { + return true; + } + + return false; +} + void KeyAuth::api::setDebug(bool value) { KeyAuth::api::debug = value; } @@ -2323,7 +2375,7 @@ void checkInit() { const auto last_mod = last_module_check.load(); if (now - last_mod > 60) { last_module_check.store(now); - if (!module_paths_ok() || duplicate_system_modules_present() || user_writable_module_present() || !core_modules_signed()) { + if (!module_paths_ok() || duplicate_system_modules_present() || user_writable_module_present() || !core_modules_signed() || hypervisor_present()) { error(XorStr("module path check failed, possible side-load detected.")); } } @@ -2355,7 +2407,7 @@ void integrity_watchdog() { const auto last_mod = last_module_check.load(); if (now - last_mod > 120) { last_module_check.store(now); - if (!module_paths_ok() || duplicate_system_modules_present() || user_writable_module_present() || !core_modules_signed()) { + if (!module_paths_ok() || duplicate_system_modules_present() || user_writable_module_present() || !core_modules_signed() || hypervisor_present()) { error(XorStr("module path check failed, possible side-load detected.")); } } From f16bfae2964a19a1cfac36bc8eb221796c81f152 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 22:12:39 -0500 Subject: [PATCH 011/116] Add prologue integrity checks --- auth.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/auth.cpp b/auth.cpp index 0f6feda..b1c8e64 100644 --- a/auth.cpp +++ b/auth.cpp @@ -46,6 +46,8 @@ #include #include #include +#include +#include #include #include #include @@ -92,6 +94,8 @@ bool user_writable_module_present(); bool module_has_rwx_section(HMODULE mod); bool core_modules_signed(); bool hypervisor_present(); +void snapshot_prologues(); +bool prologues_ok(); std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; @@ -103,11 +107,16 @@ std::atomic LoggedIn(false); std::atomic last_integrity_check{ 0 }; std::atomic integrity_fail_streak{ 0 }; std::atomic last_module_check{ 0 }; +std::atomic prologues_ready{ false }; +std::array pro_req{}; +std::array pro_verify{}; +std::array pro_checkinit{}; void KeyAuth::api::init() { std::thread(runChecks).detach(); std::thread(integrity_watchdog).detach(); + snapshot_prologues(); seed = generate_random_number(); std::atexit([]() { cleanUpSeedData(seed); }); CreateThread(0, 0, (LPTHREAD_START_ROUTINE)modify, 0, 0, 0); @@ -1989,6 +1998,31 @@ bool hypervisor_present() return false; } +void snapshot_prologues() +{ + if (prologues_ready.load()) + return; + const auto req_ptr = reinterpret_cast(reinterpret_cast(&KeyAuth::api::req)); + const auto verify_ptr = reinterpret_cast(reinterpret_cast(&VerifyPayload)); + const auto check_ptr = reinterpret_cast(reinterpret_cast(&checkInit)); + std::memcpy(pro_req.data(), req_ptr, pro_req.size()); + std::memcpy(pro_verify.data(), verify_ptr, pro_verify.size()); + std::memcpy(pro_checkinit.data(), check_ptr, pro_checkinit.size()); + prologues_ready.store(true); +} + +bool prologues_ok() +{ + if (!prologues_ready.load()) + return true; + const auto req_ptr = reinterpret_cast(reinterpret_cast(&KeyAuth::api::req)); + const auto verify_ptr = reinterpret_cast(reinterpret_cast(&VerifyPayload)); + const auto check_ptr = reinterpret_cast(reinterpret_cast(&checkInit)); + return std::memcmp(pro_req.data(), req_ptr, pro_req.size()) == 0 && + std::memcmp(pro_verify.data(), verify_ptr, pro_verify.size()) == 0 && + std::memcmp(pro_checkinit.data(), check_ptr, pro_checkinit.size()) == 0; +} + void KeyAuth::api::setDebug(bool value) { KeyAuth::api::debug = value; } @@ -2379,6 +2413,9 @@ void checkInit() { error(XorStr("module path check failed, possible side-load detected.")); } } + if (!prologues_ok()) { + error(XorStr("function prologue check failed, possible inline hook detected.")); + } integrity_check(); } @@ -2411,6 +2448,9 @@ void integrity_watchdog() { error(XorStr("module path check failed, possible side-load detected.")); } } + if (!prologues_ok()) { + error(XorStr("function prologue check failed, possible inline hook detected.")); + } if (check_section_integrity(XorStr(".text").c_str(), false)) { const int streak = integrity_fail_streak.fetch_add(1) + 1; if (streak >= 2) { From e5e9dd7c2863867d3127f79b26d633fdc9ed3364 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 22:27:13 -0500 Subject: [PATCH 012/116] Add function region checks --- auth.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/auth.cpp b/auth.cpp index b1c8e64..f6c7d82 100644 --- a/auth.cpp +++ b/auth.cpp @@ -96,6 +96,7 @@ bool core_modules_signed(); bool hypervisor_present(); void snapshot_prologues(); bool prologues_ok(); +bool func_region_ok(const void* addr); std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; @@ -2023,6 +2024,21 @@ bool prologues_ok() std::memcmp(pro_checkinit.data(), check_ptr, pro_checkinit.size()) == 0; } +bool func_region_ok(const void* addr) +{ + MEMORY_BASIC_INFORMATION mbi{}; + if (VirtualQuery(addr, &mbi, sizeof(mbi)) == 0) + return false; + if (mbi.Type != MEM_IMAGE) + return false; + const DWORD prot = mbi.Protect; + const bool exec = (prot & PAGE_EXECUTE) || (prot & PAGE_EXECUTE_READ) || (prot & PAGE_EXECUTE_READWRITE) || (prot & PAGE_EXECUTE_WRITECOPY); + const bool write = (prot & PAGE_READWRITE) || (prot & PAGE_EXECUTE_READWRITE) || (prot & PAGE_WRITECOPY) || (prot & PAGE_EXECUTE_WRITECOPY); + if (!exec || write) + return false; + return true; +} + void KeyAuth::api::setDebug(bool value) { KeyAuth::api::debug = value; } @@ -2416,6 +2432,11 @@ void checkInit() { if (!prologues_ok()) { error(XorStr("function prologue check failed, possible inline hook detected.")); } + if (!func_region_ok(reinterpret_cast(&KeyAuth::api::req)) || + !func_region_ok(reinterpret_cast(&VerifyPayload)) || + !func_region_ok(reinterpret_cast(&checkInit))) { + error(XorStr("function region check failed, possible hook detected.")); + } integrity_check(); } @@ -2451,6 +2472,11 @@ void integrity_watchdog() { if (!prologues_ok()) { error(XorStr("function prologue check failed, possible inline hook detected.")); } + if (!func_region_ok(reinterpret_cast(&KeyAuth::api::req)) || + !func_region_ok(reinterpret_cast(&VerifyPayload)) || + !func_region_ok(reinterpret_cast(&checkInit))) { + error(XorStr("function region check failed, possible hook detected.")); + } if (check_section_integrity(XorStr(".text").c_str(), false)) { const int streak = integrity_fail_streak.fetch_add(1) + 1; if (streak >= 2) { From da1ce7e382f01ed256bc4c231bd30023cc796968 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 22:32:31 -0500 Subject: [PATCH 013/116] Expand prologue and region checks --- auth.cpp | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/auth.cpp b/auth.cpp index f6c7d82..78c551e 100644 --- a/auth.cpp +++ b/auth.cpp @@ -112,6 +112,10 @@ std::atomic prologues_ready{ false }; std::array pro_req{}; std::array pro_verify{}; std::array pro_checkinit{}; +std::array pro_error{}; +std::array pro_integrity{}; +std::array pro_watchdog{}; +std::array pro_section{}; void KeyAuth::api::init() { @@ -2006,9 +2010,17 @@ void snapshot_prologues() const auto req_ptr = reinterpret_cast(reinterpret_cast(&KeyAuth::api::req)); const auto verify_ptr = reinterpret_cast(reinterpret_cast(&VerifyPayload)); const auto check_ptr = reinterpret_cast(reinterpret_cast(&checkInit)); + const auto error_ptr = reinterpret_cast(reinterpret_cast(&error)); + const auto integ_ptr = reinterpret_cast(reinterpret_cast(&integrity_check)); + const auto watch_ptr = reinterpret_cast(reinterpret_cast(&integrity_watchdog)); + const auto section_ptr = reinterpret_cast(reinterpret_cast(&check_section_integrity)); std::memcpy(pro_req.data(), req_ptr, pro_req.size()); std::memcpy(pro_verify.data(), verify_ptr, pro_verify.size()); std::memcpy(pro_checkinit.data(), check_ptr, pro_checkinit.size()); + std::memcpy(pro_error.data(), error_ptr, pro_error.size()); + std::memcpy(pro_integrity.data(), integ_ptr, pro_integrity.size()); + std::memcpy(pro_watchdog.data(), watch_ptr, pro_watchdog.size()); + std::memcpy(pro_section.data(), section_ptr, pro_section.size()); prologues_ready.store(true); } @@ -2019,9 +2031,17 @@ bool prologues_ok() const auto req_ptr = reinterpret_cast(reinterpret_cast(&KeyAuth::api::req)); const auto verify_ptr = reinterpret_cast(reinterpret_cast(&VerifyPayload)); const auto check_ptr = reinterpret_cast(reinterpret_cast(&checkInit)); + const auto error_ptr = reinterpret_cast(reinterpret_cast(&error)); + const auto integ_ptr = reinterpret_cast(reinterpret_cast(&integrity_check)); + const auto watch_ptr = reinterpret_cast(reinterpret_cast(&integrity_watchdog)); + const auto section_ptr = reinterpret_cast(reinterpret_cast(&check_section_integrity)); return std::memcmp(pro_req.data(), req_ptr, pro_req.size()) == 0 && std::memcmp(pro_verify.data(), verify_ptr, pro_verify.size()) == 0 && - std::memcmp(pro_checkinit.data(), check_ptr, pro_checkinit.size()) == 0; + std::memcmp(pro_checkinit.data(), check_ptr, pro_checkinit.size()) == 0 && + std::memcmp(pro_error.data(), error_ptr, pro_error.size()) == 0 && + std::memcmp(pro_integrity.data(), integ_ptr, pro_integrity.size()) == 0 && + std::memcmp(pro_watchdog.data(), watch_ptr, pro_watchdog.size()) == 0 && + std::memcmp(pro_section.data(), section_ptr, pro_section.size()) == 0; } bool func_region_ok(const void* addr) @@ -2434,7 +2454,11 @@ void checkInit() { } if (!func_region_ok(reinterpret_cast(&KeyAuth::api::req)) || !func_region_ok(reinterpret_cast(&VerifyPayload)) || - !func_region_ok(reinterpret_cast(&checkInit))) { + !func_region_ok(reinterpret_cast(&checkInit)) || + !func_region_ok(reinterpret_cast(&error)) || + !func_region_ok(reinterpret_cast(&integrity_check)) || + !func_region_ok(reinterpret_cast(&integrity_watchdog)) || + !func_region_ok(reinterpret_cast(&check_section_integrity))) { error(XorStr("function region check failed, possible hook detected.")); } integrity_check(); @@ -2474,7 +2498,11 @@ void integrity_watchdog() { } if (!func_region_ok(reinterpret_cast(&KeyAuth::api::req)) || !func_region_ok(reinterpret_cast(&VerifyPayload)) || - !func_region_ok(reinterpret_cast(&checkInit))) { + !func_region_ok(reinterpret_cast(&checkInit)) || + !func_region_ok(reinterpret_cast(&error)) || + !func_region_ok(reinterpret_cast(&integrity_check)) || + !func_region_ok(reinterpret_cast(&integrity_watchdog)) || + !func_region_ok(reinterpret_cast(&check_section_integrity))) { error(XorStr("function region check failed, possible hook detected.")); } if (check_section_integrity(XorStr(".text").c_str(), false)) { From 4ca1d301c4ae80969608a33055397220729660f6 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 22:35:34 -0500 Subject: [PATCH 014/116] Remove integrity watchdog --- auth.cpp | 47 ----------------------------------------------- 1 file changed, 47 deletions(-) diff --git a/auth.cpp b/auth.cpp index 78c551e..f16f0ec 100644 --- a/auth.cpp +++ b/auth.cpp @@ -85,7 +85,6 @@ std::string generate_random_number(); std::string curl_escape(CURL* curl, const std::string& input); auto check_section_integrity( const char *section_name, bool fix ) -> bool; void integrity_check(); -void integrity_watchdog(); std::string extract_host(const std::string& url); bool hosts_override_present(const std::string& host); bool module_paths_ok(); @@ -114,13 +113,11 @@ std::array pro_verify{}; std::array pro_checkinit{}; std::array pro_error{}; std::array pro_integrity{}; -std::array pro_watchdog{}; std::array pro_section{}; void KeyAuth::api::init() { std::thread(runChecks).detach(); - std::thread(integrity_watchdog).detach(); snapshot_prologues(); seed = generate_random_number(); std::atexit([]() { cleanUpSeedData(seed); }); @@ -2012,14 +2009,12 @@ void snapshot_prologues() const auto check_ptr = reinterpret_cast(reinterpret_cast(&checkInit)); const auto error_ptr = reinterpret_cast(reinterpret_cast(&error)); const auto integ_ptr = reinterpret_cast(reinterpret_cast(&integrity_check)); - const auto watch_ptr = reinterpret_cast(reinterpret_cast(&integrity_watchdog)); const auto section_ptr = reinterpret_cast(reinterpret_cast(&check_section_integrity)); std::memcpy(pro_req.data(), req_ptr, pro_req.size()); std::memcpy(pro_verify.data(), verify_ptr, pro_verify.size()); std::memcpy(pro_checkinit.data(), check_ptr, pro_checkinit.size()); std::memcpy(pro_error.data(), error_ptr, pro_error.size()); std::memcpy(pro_integrity.data(), integ_ptr, pro_integrity.size()); - std::memcpy(pro_watchdog.data(), watch_ptr, pro_watchdog.size()); std::memcpy(pro_section.data(), section_ptr, pro_section.size()); prologues_ready.store(true); } @@ -2033,14 +2028,12 @@ bool prologues_ok() const auto check_ptr = reinterpret_cast(reinterpret_cast(&checkInit)); const auto error_ptr = reinterpret_cast(reinterpret_cast(&error)); const auto integ_ptr = reinterpret_cast(reinterpret_cast(&integrity_check)); - const auto watch_ptr = reinterpret_cast(reinterpret_cast(&integrity_watchdog)); const auto section_ptr = reinterpret_cast(reinterpret_cast(&check_section_integrity)); return std::memcmp(pro_req.data(), req_ptr, pro_req.size()) == 0 && std::memcmp(pro_verify.data(), verify_ptr, pro_verify.size()) == 0 && std::memcmp(pro_checkinit.data(), check_ptr, pro_checkinit.size()) == 0 && std::memcmp(pro_error.data(), error_ptr, pro_error.size()) == 0 && std::memcmp(pro_integrity.data(), integ_ptr, pro_integrity.size()) == 0 && - std::memcmp(pro_watchdog.data(), watch_ptr, pro_watchdog.size()) == 0 && std::memcmp(pro_section.data(), section_ptr, pro_section.size()) == 0; } @@ -2457,7 +2450,6 @@ void checkInit() { !func_region_ok(reinterpret_cast(&checkInit)) || !func_region_ok(reinterpret_cast(&error)) || !func_region_ok(reinterpret_cast(&integrity_check)) || - !func_region_ok(reinterpret_cast(&integrity_watchdog)) || !func_region_ok(reinterpret_cast(&check_section_integrity))) { error(XorStr("function region check failed, possible hook detected.")); } @@ -2476,45 +2468,6 @@ void integrity_check() { } } -void integrity_watchdog() { - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution sleep_seconds(20, 50); - while (true) { - Sleep(static_cast(sleep_seconds(gen) * 1000)); - if (!initialized || !LoggedIn.load()) - continue; - const auto now = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count(); - const auto last_mod = last_module_check.load(); - if (now - last_mod > 120) { - last_module_check.store(now); - if (!module_paths_ok() || duplicate_system_modules_present() || user_writable_module_present() || !core_modules_signed() || hypervisor_present()) { - error(XorStr("module path check failed, possible side-load detected.")); - } - } - if (!prologues_ok()) { - error(XorStr("function prologue check failed, possible inline hook detected.")); - } - if (!func_region_ok(reinterpret_cast(&KeyAuth::api::req)) || - !func_region_ok(reinterpret_cast(&VerifyPayload)) || - !func_region_ok(reinterpret_cast(&checkInit)) || - !func_region_ok(reinterpret_cast(&error)) || - !func_region_ok(reinterpret_cast(&integrity_check)) || - !func_region_ok(reinterpret_cast(&integrity_watchdog)) || - !func_region_ok(reinterpret_cast(&check_section_integrity))) { - error(XorStr("function region check failed, possible hook detected.")); - } - if (check_section_integrity(XorStr(".text").c_str(), false)) { - const int streak = integrity_fail_streak.fetch_add(1) + 1; - if (streak >= 2) { - error(XorStr("check_section_integrity() failed, don't tamper with the program.")); - } - } else { - integrity_fail_streak.store(0); - } - } -} // code submitted in pull request from https://github.com/BINM7MD BOOL bDataCompare(const BYTE* pData, const BYTE* bMask, const char* szMask) { From ed5ab73c38f2ccf18f82e2bd512cbb9ff58c06ae Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 22:37:42 -0500 Subject: [PATCH 015/116] Add lightweight periodic checks --- auth.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/auth.cpp b/auth.cpp index f16f0ec..e36118a 100644 --- a/auth.cpp +++ b/auth.cpp @@ -107,6 +107,7 @@ std::atomic LoggedIn(false); std::atomic last_integrity_check{ 0 }; std::atomic integrity_fail_streak{ 0 }; std::atomic last_module_check{ 0 }; +std::atomic last_periodic_check{ 0 }; std::atomic prologues_ready{ false }; std::array pro_req{}; std::array pro_verify{}; @@ -2442,6 +2443,29 @@ void checkInit() { error(XorStr("module path check failed, possible side-load detected.")); } } + const auto last_periodic = last_periodic_check.load(); + if (now - last_periodic > 30) { + last_periodic_check.store(now); + if (!prologues_ok()) { + error(XorStr("function prologue check failed, possible inline hook detected.")); + } + if (!func_region_ok(reinterpret_cast(&KeyAuth::api::req)) || + !func_region_ok(reinterpret_cast(&VerifyPayload)) || + !func_region_ok(reinterpret_cast(&checkInit)) || + !func_region_ok(reinterpret_cast(&error)) || + !func_region_ok(reinterpret_cast(&integrity_check)) || + !func_region_ok(reinterpret_cast(&check_section_integrity))) { + error(XorStr("function region check failed, possible hook detected.")); + } + if (check_section_integrity(XorStr(".text").c_str(), false)) { + const int streak = integrity_fail_streak.fetch_add(1) + 1; + if (streak >= 2) { + error(XorStr("check_section_integrity() failed, don't tamper with the program.")); + } + } else { + integrity_fail_streak.store(0); + } + } if (!prologues_ok()) { error(XorStr("function prologue check failed, possible inline hook detected.")); } From 1e6b9e2092ab8b60e5707541adf56cd51a29d0b9 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 22:38:35 -0500 Subject: [PATCH 016/116] Add timing anomaly check --- auth.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/auth.cpp b/auth.cpp index e36118a..58986d7 100644 --- a/auth.cpp +++ b/auth.cpp @@ -96,6 +96,7 @@ bool hypervisor_present(); void snapshot_prologues(); bool prologues_ok(); bool func_region_ok(const void* addr); +bool timing_anomaly_detected(); std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; @@ -2053,6 +2054,23 @@ bool func_region_ok(const void* addr) return true; } +bool timing_anomaly_detected() +{ + const auto wall_now = std::chrono::system_clock::now(); + const auto steady_now = std::chrono::steady_clock::now(); + static auto wall_last = wall_now; + static auto steady_last = steady_now; + const auto wall_delta = std::chrono::duration_cast(wall_now - wall_last).count(); + const auto steady_delta = std::chrono::duration_cast(steady_now - steady_last).count(); + wall_last = wall_now; + steady_last = steady_now; + if (wall_delta < -60 || wall_delta > 300) + return true; + if (std::llabs(wall_delta - steady_delta) > 120) + return true; + return false; +} + void KeyAuth::api::setDebug(bool value) { KeyAuth::api::debug = value; } @@ -2446,6 +2464,9 @@ void checkInit() { const auto last_periodic = last_periodic_check.load(); if (now - last_periodic > 30) { last_periodic_check.store(now); + if (timing_anomaly_detected()) { + error(XorStr("timing anomaly detected, possible time tamper.")); + } if (!prologues_ok()) { error(XorStr("function prologue check failed, possible inline hook detected.")); } From 835e0a2a6761fd6272bce76b7e77091a0a525298 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 22:40:26 -0500 Subject: [PATCH 017/116] Tighten request buffer handling --- auth.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/auth.cpp b/auth.cpp index 58986d7..e69735c 100644 --- a/auth.cpp +++ b/auth.cpp @@ -681,9 +681,9 @@ void KeyAuth::api::web_login() HTTP_SET_NULL_ID(&requestId); int bufferSize = 4096; int requestSize = sizeof(HTTP_REQUEST) + bufferSize; - BYTE* buffer = new BYTE[requestSize]; - PHTTP_REQUEST pRequest = (PHTTP_REQUEST)buffer; - RtlZeroMemory(buffer, requestSize); + auto buffer = std::make_unique(requestSize); + PHTTP_REQUEST pRequest = (PHTTP_REQUEST)buffer.get(); + RtlZeroMemory(buffer.get(), requestSize); ULONG bytesReturned; result = HttpReceiveHttpRequest( requestQueueHandle, @@ -746,7 +746,6 @@ void KeyAuth::api::web_login() NULL ); - delete[]buffer; continue; } @@ -858,8 +857,6 @@ void KeyAuth::api::web_login() going = false; } - delete[]buffer; - if (!success) LI_FN(exit)(0); } @@ -921,9 +918,9 @@ void KeyAuth::api::button(std::string button) HTTP_SET_NULL_ID(&requestId); int bufferSize = 4096; int requestSize = sizeof(HTTP_REQUEST) + bufferSize; - BYTE* buffer = new BYTE[requestSize]; - PHTTP_REQUEST pRequest = (PHTTP_REQUEST)buffer; - RtlZeroMemory(buffer, requestSize); + auto buffer = std::make_unique(requestSize); + PHTTP_REQUEST pRequest = (PHTTP_REQUEST)buffer.get(); + RtlZeroMemory(buffer.get(), requestSize); ULONG bytesReturned; result = HttpReceiveHttpRequest( requestQueueHandle, @@ -986,7 +983,6 @@ void KeyAuth::api::button(std::string button) NULL ); - delete[]buffer; } } From 763a70e0a8678d3099c04018d03c2441d3711b1e Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 08:05:34 -0500 Subject: [PATCH 018/116] Add session heartbeat --- auth.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/auth.cpp b/auth.cpp index e69735c..662662c 100644 --- a/auth.cpp +++ b/auth.cpp @@ -97,6 +97,8 @@ void snapshot_prologues(); bool prologues_ok(); bool func_region_ok(const void* addr); bool timing_anomaly_detected(); +void start_heartbeat(KeyAuth::api* instance); +void heartbeat_thread(KeyAuth::api* instance); std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; @@ -110,6 +112,7 @@ std::atomic integrity_fail_streak{ 0 }; std::atomic last_module_check{ 0 }; std::atomic last_periodic_check{ 0 }; std::atomic prologues_ready{ false }; +std::atomic heartbeat_started{ false }; std::array pro_req{}; std::array pro_verify{}; std::array pro_checkinit{}; @@ -350,6 +353,9 @@ void KeyAuth::api::login(std::string username, std::string password, std::string LI_FN(GlobalAddAtomA)(ownerid.c_str()); LoggedIn.store(true); + start_heartbeat(this); + start_heartbeat(this); + start_heartbeat(this); } else { LI_FN(exit)(12); @@ -798,6 +804,7 @@ void KeyAuth::api::web_login() LI_FN(GlobalAddAtomA)(ownerid.c_str()); LoggedIn.store(true); + start_heartbeat(this); } else { LI_FN(exit)(12); @@ -2067,6 +2074,29 @@ bool timing_anomaly_detected() return false; } +void heartbeat_thread(KeyAuth::api* instance) +{ + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution sleep_seconds(45, 90); + while (true) { + Sleep(static_cast(sleep_seconds(gen) * 1000)); + if (!LoggedIn.load()) + continue; + instance->check(false); + if (!instance->response.success) { + error(XorStr("session check failed.")); + } + } +} + +void start_heartbeat(KeyAuth::api* instance) +{ + if (heartbeat_started.exchange(true)) + return; + std::thread(heartbeat_thread, instance).detach(); +} + void KeyAuth::api::setDebug(bool value) { KeyAuth::api::debug = value; } From 89b90572b23835d64c04e290e2bad2a42c302b9f Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 08:10:03 -0500 Subject: [PATCH 019/116] Add pre-request guard checks --- auth.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/auth.cpp b/auth.cpp index 662662c..7e22f43 100644 --- a/auth.cpp +++ b/auth.cpp @@ -1644,6 +1644,9 @@ void KeyAuth::api::logout() { int VerifyPayload(std::string signature, std::string timestamp, std::string body) { + if (!prologues_ok()) { + error(XorStr("function prologue check failed, possible inline hook detected.")); + } integrity_check(); long long unix_timestamp = 0; try { @@ -2105,6 +2108,17 @@ std::string KeyAuth::api::req(const std::string& data, const std::string& url) { signature.clear(); signatureTimestamp.clear(); integrity_check(); + if (!prologues_ok()) { + error(XorStr("function prologue check failed, possible inline hook detected.")); + } + if (!func_region_ok(reinterpret_cast(&KeyAuth::api::req)) || + !func_region_ok(reinterpret_cast(&VerifyPayload)) || + !func_region_ok(reinterpret_cast(&checkInit)) || + !func_region_ok(reinterpret_cast(&error)) || + !func_region_ok(reinterpret_cast(&integrity_check)) || + !func_region_ok(reinterpret_cast(&check_section_integrity))) { + error(XorStr("function region check failed, possible hook detected.")); + } const auto host = extract_host(url); if (hosts_override_present(host)) { error(XorStr("Hosts file override detected for API host.")); From d0e4c008ca726afaef714522903267b190b8f4a3 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 08:15:48 -0500 Subject: [PATCH 020/116] Add detour and text hash checks --- auth.cpp | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/auth.cpp b/auth.cpp index 7e22f43..d248688 100644 --- a/auth.cpp +++ b/auth.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -99,6 +100,10 @@ bool func_region_ok(const void* addr); bool timing_anomaly_detected(); void start_heartbeat(KeyAuth::api* instance); void heartbeat_thread(KeyAuth::api* instance); +void snapshot_text_hashes(); +bool text_hashes_ok(); +bool detour_suspect(const uint8_t* p); +bool import_addresses_ok(); std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; @@ -119,6 +124,9 @@ std::array pro_checkinit{}; std::array pro_error{}; std::array pro_integrity{}; std::array pro_section{}; +std::atomic text_hashes_ready{ false }; +struct TextHash { size_t offset; size_t len; uint32_t hash; }; +std::vector text_hashes; void KeyAuth::api::init() { @@ -1647,6 +1655,18 @@ int VerifyPayload(std::string signature, std::string timestamp, std::string body if (!prologues_ok()) { error(XorStr("function prologue check failed, possible inline hook detected.")); } + if (!import_addresses_ok()) { + error(XorStr("import address check failed.")); + } + if (!text_hashes_ok()) { + error(XorStr("text section hash check failed.")); + } + if (detour_suspect(reinterpret_cast(&KeyAuth::api::req)) || + detour_suspect(reinterpret_cast(&VerifyPayload)) || + detour_suspect(reinterpret_cast(&checkInit)) || + detour_suspect(reinterpret_cast(&error))) { + error(XorStr("detour pattern detected.")); + } integrity_check(); long long unix_timestamp = 0; try { @@ -2025,6 +2045,7 @@ void snapshot_prologues() std::memcpy(pro_integrity.data(), integ_ptr, pro_integrity.size()); std::memcpy(pro_section.data(), section_ptr, pro_section.size()); prologues_ready.store(true); + snapshot_text_hashes(); } bool prologues_ok() @@ -2077,6 +2098,118 @@ bool timing_anomaly_detected() return false; } +static bool get_text_section_info(std::uintptr_t& base, size_t& size) +{ + const auto hmodule = GetModuleHandle(nullptr); + if (!hmodule) return false; + const auto base_0 = reinterpret_cast(hmodule); + const auto dos = reinterpret_cast(base_0); + if (dos->e_magic != IMAGE_DOS_SIGNATURE) return false; + const auto nt = reinterpret_cast(base_0 + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) return false; + auto section = IMAGE_FIRST_SECTION(nt); + for (auto i = 0; i < nt->FileHeader.NumberOfSections; ++i, ++section) { + if (std::memcmp(section->Name, ".text", 5) == 0) { + base = base_0 + section->VirtualAddress; + size = section->Misc.VirtualSize; + return true; + } + } + return false; +} + +static uint32_t fnv1a(const uint8_t* data, size_t len) +{ + uint32_t hash = 2166136261u; + for (size_t i = 0; i < len; ++i) { + hash ^= data[i]; + hash *= 16777619u; + } + return hash; +} + +void snapshot_text_hashes() +{ + if (text_hashes_ready.load()) + return; + std::uintptr_t base = 0; + size_t size = 0; + if (!get_text_section_info(base, size) || size < 256) + return; + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution dist(0, size - 64); + text_hashes.clear(); + for (int i = 0; i < 8; ++i) { + const size_t offset = dist(gen); + const uint8_t* ptr = reinterpret_cast(base + offset); + text_hashes.push_back({ offset, 64, fnv1a(ptr, 64) }); + } + text_hashes_ready.store(true); +} + +bool text_hashes_ok() +{ + if (!text_hashes_ready.load()) + return true; + std::uintptr_t base = 0; + size_t size = 0; + if (!get_text_section_info(base, size)) + return true; + for (const auto& h : text_hashes) { + if (h.offset + h.len > size) + return false; + const uint8_t* ptr = reinterpret_cast(base + h.offset); + if (fnv1a(ptr, h.len) != h.hash) + return false; + } + return true; +} + +bool detour_suspect(const uint8_t* p) +{ + if (!p) + return true; + // jmp rel32 / call rel32 / jmp rel8 + if (p[0] == 0xE9 || p[0] == 0xE8 || p[0] == 0xEB) + return true; + // jmp/call [rip+imm32] + if (p[0] == 0xFF && (p[1] == 0x25 || p[1] == 0x15)) + return true; + // mov rax, imm64; jmp rax + if (p[0] == 0x48 && p[1] == 0xB8 && p[10] == 0xFF && p[11] == 0xE0) + return true; + return false; +} + +static bool addr_in_module(const void* addr, const wchar_t* module_name) +{ + HMODULE mod = module_name ? GetModuleHandleW(module_name) : GetModuleHandle(nullptr); + if (!mod) + return false; + MODULEINFO mi{}; + if (!GetModuleInformation(GetCurrentProcess(), mod, &mi, sizeof(mi))) + return false; + const auto base = reinterpret_cast(mi.lpBaseOfDll); + const auto end = base + mi.SizeOfImage; + return addr >= base && addr < end; +} + +bool import_addresses_ok() +{ + // wintrust functions should resolve inside wintrust.dll + if (!addr_in_module(reinterpret_cast(&WinVerifyTrust), L"wintrust.dll")) + return false; + // VirtualQuery should be inside kernelbase/kernel32 + if (!addr_in_module(reinterpret_cast(&VirtualQuery), L"kernelbase.dll") && + !addr_in_module(reinterpret_cast(&VirtualQuery), L"kernel32.dll")) + return false; + // curl functions should live in main module when statically linked + if (!addr_in_module(reinterpret_cast(&curl_easy_perform), nullptr)) + return false; + return true; +} + void heartbeat_thread(KeyAuth::api* instance) { std::random_device rd; @@ -2119,6 +2252,18 @@ std::string KeyAuth::api::req(const std::string& data, const std::string& url) { !func_region_ok(reinterpret_cast(&check_section_integrity))) { error(XorStr("function region check failed, possible hook detected.")); } + if (!import_addresses_ok()) { + error(XorStr("import address check failed.")); + } + if (!text_hashes_ok()) { + error(XorStr("text section hash check failed.")); + } + if (detour_suspect(reinterpret_cast(&KeyAuth::api::req)) || + detour_suspect(reinterpret_cast(&VerifyPayload)) || + detour_suspect(reinterpret_cast(&checkInit)) || + detour_suspect(reinterpret_cast(&error))) { + error(XorStr("detour pattern detected.")); + } const auto host = extract_host(url); if (hosts_override_present(host)) { error(XorStr("Hosts file override detected for API host.")); @@ -2507,6 +2652,18 @@ void checkInit() { if (timing_anomaly_detected()) { error(XorStr("timing anomaly detected, possible time tamper.")); } + if (!text_hashes_ok()) { + error(XorStr("text section hash check failed.")); + } + if (!import_addresses_ok()) { + error(XorStr("import address check failed.")); + } + if (detour_suspect(reinterpret_cast(&KeyAuth::api::req)) || + detour_suspect(reinterpret_cast(&VerifyPayload)) || + detour_suspect(reinterpret_cast(&checkInit)) || + detour_suspect(reinterpret_cast(&error))) { + error(XorStr("detour pattern detected.")); + } if (!prologues_ok()) { error(XorStr("function prologue check failed, possible inline hook detected.")); } From b5b151f05181beec7e04d2542c61d42b1cd81c7f Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 08:23:27 -0500 Subject: [PATCH 021/116] Add text protection and PE header checks --- auth.cpp | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/auth.cpp b/auth.cpp index d248688..b97d9c8 100644 --- a/auth.cpp +++ b/auth.cpp @@ -104,6 +104,10 @@ void snapshot_text_hashes(); bool text_hashes_ok(); bool detour_suspect(const uint8_t* p); bool import_addresses_ok(); +void snapshot_text_page_protections(); +bool text_page_protections_ok(); +void snapshot_pe_header(); +bool pe_header_ok(); std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; @@ -127,6 +131,10 @@ std::array pro_section{}; std::atomic text_hashes_ready{ false }; struct TextHash { size_t offset; size_t len; uint32_t hash; }; std::vector text_hashes; +std::atomic text_prot_ready{ false }; +std::vector> text_protections; +std::atomic pe_header_ready{ false }; +uint32_t pe_header_hash = 0; void KeyAuth::api::init() { @@ -1661,6 +1669,12 @@ int VerifyPayload(std::string signature, std::string timestamp, std::string body if (!text_hashes_ok()) { error(XorStr("text section hash check failed.")); } + if (!text_page_protections_ok()) { + error(XorStr("text page protection check failed.")); + } + if (!pe_header_ok()) { + error(XorStr("pe header check failed.")); + } if (detour_suspect(reinterpret_cast(&KeyAuth::api::req)) || detour_suspect(reinterpret_cast(&VerifyPayload)) || detour_suspect(reinterpret_cast(&checkInit)) || @@ -2046,6 +2060,8 @@ void snapshot_prologues() std::memcpy(pro_section.data(), section_ptr, pro_section.size()); prologues_ready.store(true); snapshot_text_hashes(); + snapshot_text_page_protections(); + snapshot_pe_header(); } bool prologues_ok() @@ -2166,6 +2182,85 @@ bool text_hashes_ok() return true; } +void snapshot_text_page_protections() +{ + if (text_prot_ready.load()) + return; + std::uintptr_t base = 0; + size_t size = 0; + if (!get_text_section_info(base, size)) + return; + text_protections.clear(); + const size_t page = 0x1000; + for (size_t off = 0; off < size; off += page) { + MEMORY_BASIC_INFORMATION mbi{}; + if (VirtualQuery(reinterpret_cast(base + off), &mbi, sizeof(mbi)) == 0) + continue; + text_protections.emplace_back(reinterpret_cast(mbi.BaseAddress), mbi.Protect); + } + text_prot_ready.store(true); +} + +bool text_page_protections_ok() +{ + if (!text_prot_ready.load()) + return true; + for (const auto& entry : text_protections) { + MEMORY_BASIC_INFORMATION mbi{}; + if (VirtualQuery(reinterpret_cast(entry.first), &mbi, sizeof(mbi)) == 0) + return false; + const DWORD prot = mbi.Protect; + if (prot != entry.second) + return false; + const bool exec = (prot & PAGE_EXECUTE) || (prot & PAGE_EXECUTE_READ) || (prot & PAGE_EXECUTE_READWRITE) || (prot & PAGE_EXECUTE_WRITECOPY); + const bool write = (prot & PAGE_READWRITE) || (prot & PAGE_EXECUTE_READWRITE) || (prot & PAGE_WRITECOPY) || (prot & PAGE_EXECUTE_WRITECOPY); + if (!exec || write) + return false; + } + return true; +} + +void snapshot_pe_header() +{ + if (pe_header_ready.load()) + return; + const auto hmodule = GetModuleHandle(nullptr); + if (!hmodule) + return; + const auto base = reinterpret_cast(hmodule); + const auto dos = reinterpret_cast(base); + if (dos->e_magic != IMAGE_DOS_SIGNATURE) + return; + const auto nt = reinterpret_cast(base + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) + return; + const size_t header_size = nt->OptionalHeader.SizeOfHeaders; + if (header_size == 0 || header_size > 0x4000) + return; + pe_header_hash = fnv1a(reinterpret_cast(base), header_size); + pe_header_ready.store(true); +} + +bool pe_header_ok() +{ + if (!pe_header_ready.load()) + return true; + const auto hmodule = GetModuleHandle(nullptr); + if (!hmodule) + return true; + const auto base = reinterpret_cast(hmodule); + const auto dos = reinterpret_cast(base); + if (dos->e_magic != IMAGE_DOS_SIGNATURE) + return false; + const auto nt = reinterpret_cast(base + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) + return false; + const size_t header_size = nt->OptionalHeader.SizeOfHeaders; + if (header_size == 0 || header_size > 0x4000) + return false; + return fnv1a(reinterpret_cast(base), header_size) == pe_header_hash; +} + bool detour_suspect(const uint8_t* p) { if (!p) @@ -2258,6 +2353,12 @@ std::string KeyAuth::api::req(const std::string& data, const std::string& url) { if (!text_hashes_ok()) { error(XorStr("text section hash check failed.")); } + if (!text_page_protections_ok()) { + error(XorStr("text page protection check failed.")); + } + if (!pe_header_ok()) { + error(XorStr("pe header check failed.")); + } if (detour_suspect(reinterpret_cast(&KeyAuth::api::req)) || detour_suspect(reinterpret_cast(&VerifyPayload)) || detour_suspect(reinterpret_cast(&checkInit)) || @@ -2655,6 +2756,12 @@ void checkInit() { if (!text_hashes_ok()) { error(XorStr("text section hash check failed.")); } + if (!text_page_protections_ok()) { + error(XorStr("text page protection check failed.")); + } + if (!pe_header_ok()) { + error(XorStr("pe header check failed.")); + } if (!import_addresses_ok()) { error(XorStr("import address check failed.")); } From e5647bf5f99576e589af70bc27ad92306e1c1bd1 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 08:45:39 -0500 Subject: [PATCH 022/116] Add VirtualProtect IAT and module allowlist checks --- auth.cpp | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/auth.cpp b/auth.cpp index b97d9c8..ee3ef09 100644 --- a/auth.cpp +++ b/auth.cpp @@ -108,6 +108,8 @@ void snapshot_text_page_protections(); bool text_page_protections_ok(); void snapshot_pe_header(); bool pe_header_ok(); +bool iat_virtualprotect_ok(); +bool module_allowlist_ok(); std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; @@ -1663,6 +1665,9 @@ int VerifyPayload(std::string signature, std::string timestamp, std::string body if (!prologues_ok()) { error(XorStr("function prologue check failed, possible inline hook detected.")); } + if (!iat_virtualprotect_ok()) { + error(XorStr("VirtualProtect IAT check failed.")); + } if (!import_addresses_ok()) { error(XorStr("import address check failed.")); } @@ -2305,6 +2310,84 @@ bool import_addresses_ok() return true; } +static bool iat_resolves_to(HMODULE module, const char* import_name, const void* target) +{ + if (!module) + return false; + auto base = reinterpret_cast(module); + auto dos = reinterpret_cast(base); + if (dos->e_magic != IMAGE_DOS_SIGNATURE) + return false; + auto nt = reinterpret_cast(base + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) + return false; + const auto& dir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + if (!dir.VirtualAddress) + return false; + auto desc = reinterpret_cast(base + dir.VirtualAddress); + for (; desc->Name; ++desc) { + const char* dll = reinterpret_cast(base + desc->Name); + if (!_stricmp(dll, "KERNEL32.DLL") && !_stricmp(dll, "KERNELBASE.DLL")) + continue; + auto thunk = reinterpret_cast(base + desc->FirstThunk); + auto orig = reinterpret_cast(base + desc->OriginalFirstThunk); + for (; orig->u1.AddressOfData; ++orig, ++thunk) { + if (orig->u1.Ordinal & IMAGE_ORDINAL_FLAG) + continue; + auto import = reinterpret_cast(base + orig->u1.AddressOfData); + if (strcmp(reinterpret_cast(import->Name), import_name) == 0) { + return reinterpret_cast(thunk->u1.Function) == target; + } + } + } + return true; +} + +bool iat_virtualprotect_ok() +{ + HMODULE self = GetModuleHandle(nullptr); + FARPROC vp = GetProcAddress(GetModuleHandleW(L"kernelbase.dll"), "VirtualProtect"); + if (!vp) + vp = GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "VirtualProtect"); + if (!vp) + return false; + if (!addr_in_module(reinterpret_cast(vp), L"kernelbase.dll") && + !addr_in_module(reinterpret_cast(vp), L"kernel32.dll")) + return false; + return iat_resolves_to(self, "VirtualProtect", reinterpret_cast(vp)); +} + +bool module_allowlist_ok() +{ + HMODULE mods[1024] = {}; + DWORD needed = 0; + if (!EnumProcessModules(GetCurrentProcess(), mods, sizeof(mods), &needed)) + return false; + wchar_t exe_path[MAX_PATH] = {}; + GetModuleFileNameW(nullptr, exe_path, MAX_PATH); + std::wstring exe_dir = exe_path; + const auto last_slash = exe_dir.find_last_of(L"\\/"); + if (last_slash != std::wstring::npos) + exe_dir = exe_dir.substr(0, last_slash + 1); + exe_dir = to_lower_ws(exe_dir); + const wchar_t* sysroot_env = _wgetenv(L"SystemRoot"); + std::wstring sysroot = sysroot_env ? sysroot_env : L"C:\\Windows"; + std::wstring sys32 = to_lower_ws(sysroot + L"\\System32\\"); + std::wstring syswow = to_lower_ws(sysroot + L"\\SysWOW64\\"); + + const size_t count = needed / sizeof(HMODULE); + for (size_t i = 0; i < count; ++i) { + wchar_t path[MAX_PATH] = {}; + if (!GetModuleFileNameExW(GetCurrentProcess(), mods[i], path, MAX_PATH)) + continue; + std::wstring p = to_lower_ws(path); + if (p.rfind(sys32, 0) == 0 || p.rfind(syswow, 0) == 0 || p.rfind(exe_dir, 0) == 0) + continue; + return false; + } + return true; +} + void heartbeat_thread(KeyAuth::api* instance) { std::random_device rd; @@ -2347,6 +2430,9 @@ std::string KeyAuth::api::req(const std::string& data, const std::string& url) { !func_region_ok(reinterpret_cast(&check_section_integrity))) { error(XorStr("function region check failed, possible hook detected.")); } + if (!iat_virtualprotect_ok()) { + error(XorStr("VirtualProtect IAT check failed.")); + } if (!import_addresses_ok()) { error(XorStr("import address check failed.")); } @@ -2743,7 +2829,7 @@ void checkInit() { const auto last_mod = last_module_check.load(); if (now - last_mod > 60) { last_module_check.store(now); - if (!module_paths_ok() || duplicate_system_modules_present() || user_writable_module_present() || !core_modules_signed() || hypervisor_present()) { + if (!module_paths_ok() || duplicate_system_modules_present() || user_writable_module_present() || !core_modules_signed() || hypervisor_present() || !module_allowlist_ok()) { error(XorStr("module path check failed, possible side-load detected.")); } } @@ -2753,6 +2839,9 @@ void checkInit() { if (timing_anomaly_detected()) { error(XorStr("timing anomaly detected, possible time tamper.")); } + if (!iat_virtualprotect_ok()) { + error(XorStr("VirtualProtect IAT check failed.")); + } if (!text_hashes_ok()) { error(XorStr("text section hash check failed.")); } From 1390c8c67306d2f4951aa6376769430692e5c612 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 08:49:06 -0500 Subject: [PATCH 023/116] Use system directory APIs --- auth.cpp | 51 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/auth.cpp b/auth.cpp index ee3ef09..85f76d3 100644 --- a/auth.cpp +++ b/auth.cpp @@ -1912,10 +1912,8 @@ bool core_modules_signed() bool module_paths_ok() { const wchar_t* kModules[] = { L"ntdll.dll", L"kernel32.dll", L"kernelbase.dll", L"user32.dll" }; - const wchar_t* sysroot_env = _wgetenv(L"SystemRoot"); - std::wstring sysroot = sysroot_env ? sysroot_env : L"C:\\Windows"; - std::wstring sys32 = to_lower_ws(sysroot + L"\\System32\\"); - std::wstring syswow = to_lower_ws(sysroot + L"\\SysWOW64\\"); + std::wstring sys32 = to_lower_ws(get_system_dir() + L"\\"); + std::wstring syswow = to_lower_ws(get_syswow_dir() + L"\\"); for (const auto* name : kModules) { HMODULE mod = GetModuleHandleW(name); @@ -1934,10 +1932,8 @@ bool module_paths_ok() bool duplicate_system_modules_present() { const wchar_t* kModules[] = { L"ntdll.dll", L"kernel32.dll", L"kernelbase.dll", L"user32.dll" }; - const wchar_t* sysroot_env = _wgetenv(L"SystemRoot"); - std::wstring sysroot = sysroot_env ? sysroot_env : L"C:\\Windows"; - std::wstring sys32 = to_lower_ws(sysroot + L"\\System32\\"); - std::wstring syswow = to_lower_ws(sysroot + L"\\SysWOW64\\"); + std::wstring sys32 = to_lower_ws(get_system_dir() + L"\\"); + std::wstring syswow = to_lower_ws(get_syswow_dir() + L"\\"); HMODULE mods[1024] = {}; DWORD needed = 0; @@ -2014,6 +2010,22 @@ static bool file_exists(const std::wstring& path) return (attr != INVALID_FILE_ATTRIBUTES) && !(attr & FILE_ATTRIBUTE_DIRECTORY); } +static std::wstring get_system_dir() +{ + wchar_t buf[MAX_PATH] = {}; + if (GetSystemDirectoryW(buf, MAX_PATH) == 0) + return L"C:\\Windows\\System32"; + return buf; +} + +static std::wstring get_syswow_dir() +{ + wchar_t buf[MAX_PATH] = {}; + if (GetSystemWow64DirectoryW(buf, MAX_PATH) == 0) + return L"C:\\Windows\\SysWOW64"; + return buf; +} + bool hypervisor_present() { int cpu_info[4] = {}; @@ -2033,14 +2045,15 @@ bool hypervisor_present() } // file artifacts (drivers/tools) - if (file_exists(L"C:\\Windows\\System32\\drivers\\VBoxGuest.sys") || - file_exists(L"C:\\Windows\\System32\\drivers\\VBoxMouse.sys") || - file_exists(L"C:\\Windows\\System32\\drivers\\VBoxSF.sys") || - file_exists(L"C:\\Windows\\System32\\drivers\\VBoxVideo.sys") || - file_exists(L"C:\\Windows\\System32\\drivers\\vmhgfs.sys") || - file_exists(L"C:\\Windows\\System32\\drivers\\vmmouse.sys") || - file_exists(L"C:\\Windows\\System32\\drivers\\vm3dmp.sys") || - file_exists(L"C:\\Windows\\System32\\drivers\\xen.sys")) { + const auto sys32 = get_system_dir(); + if (file_exists(sys32 + L"\\drivers\\VBoxGuest.sys") || + file_exists(sys32 + L"\\drivers\\VBoxMouse.sys") || + file_exists(sys32 + L"\\drivers\\VBoxSF.sys") || + file_exists(sys32 + L"\\drivers\\VBoxVideo.sys") || + file_exists(sys32 + L"\\drivers\\vmhgfs.sys") || + file_exists(sys32 + L"\\drivers\\vmmouse.sys") || + file_exists(sys32 + L"\\drivers\\vm3dmp.sys") || + file_exists(sys32 + L"\\drivers\\xen.sys")) { return true; } @@ -2370,10 +2383,8 @@ bool module_allowlist_ok() if (last_slash != std::wstring::npos) exe_dir = exe_dir.substr(0, last_slash + 1); exe_dir = to_lower_ws(exe_dir); - const wchar_t* sysroot_env = _wgetenv(L"SystemRoot"); - std::wstring sysroot = sysroot_env ? sysroot_env : L"C:\\Windows"; - std::wstring sys32 = to_lower_ws(sysroot + L"\\System32\\"); - std::wstring syswow = to_lower_ws(sysroot + L"\\SysWOW64\\"); + std::wstring sys32 = to_lower_ws(get_system_dir() + L"\\"); + std::wstring syswow = to_lower_ws(get_syswow_dir() + L"\\"); const size_t count = needed / sizeof(HMODULE); for (size_t i = 0; i < count; ++i) { From 35e60e3c221447b8590086d033af8631cbf4beb9 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 08:52:40 -0500 Subject: [PATCH 024/116] Remove hardcoded system paths --- auth.cpp | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/auth.cpp b/auth.cpp index 85f76d3..6d77bcd 100644 --- a/auth.cpp +++ b/auth.cpp @@ -1912,8 +1912,10 @@ bool core_modules_signed() bool module_paths_ok() { const wchar_t* kModules[] = { L"ntdll.dll", L"kernel32.dll", L"kernelbase.dll", L"user32.dll" }; - std::wstring sys32 = to_lower_ws(get_system_dir() + L"\\"); - std::wstring syswow = to_lower_ws(get_syswow_dir() + L"\\"); + std::wstring sys32 = get_system_dir(); + std::wstring syswow = get_syswow_dir(); + if (!sys32.empty()) sys32 = to_lower_ws(sys32 + L"\\"); + if (!syswow.empty()) syswow = to_lower_ws(syswow + L"\\"); for (const auto* name : kModules) { HMODULE mod = GetModuleHandleW(name); @@ -1932,8 +1934,10 @@ bool module_paths_ok() bool duplicate_system_modules_present() { const wchar_t* kModules[] = { L"ntdll.dll", L"kernel32.dll", L"kernelbase.dll", L"user32.dll" }; - std::wstring sys32 = to_lower_ws(get_system_dir() + L"\\"); - std::wstring syswow = to_lower_ws(get_syswow_dir() + L"\\"); + std::wstring sys32 = get_system_dir(); + std::wstring syswow = get_syswow_dir(); + if (!sys32.empty()) sys32 = to_lower_ws(sys32 + L"\\"); + if (!syswow.empty()) syswow = to_lower_ws(syswow + L"\\"); HMODULE mods[1024] = {}; DWORD needed = 0; @@ -2014,16 +2018,16 @@ static std::wstring get_system_dir() { wchar_t buf[MAX_PATH] = {}; if (GetSystemDirectoryW(buf, MAX_PATH) == 0) - return L"C:\\Windows\\System32"; - return buf; + return L""; + return std::wstring(buf); } static std::wstring get_syswow_dir() { wchar_t buf[MAX_PATH] = {}; if (GetSystemWow64DirectoryW(buf, MAX_PATH) == 0) - return L"C:\\Windows\\SysWOW64"; - return buf; + return L""; + return std::wstring(buf); } bool hypervisor_present() @@ -2046,15 +2050,17 @@ bool hypervisor_present() // file artifacts (drivers/tools) const auto sys32 = get_system_dir(); - if (file_exists(sys32 + L"\\drivers\\VBoxGuest.sys") || - file_exists(sys32 + L"\\drivers\\VBoxMouse.sys") || - file_exists(sys32 + L"\\drivers\\VBoxSF.sys") || - file_exists(sys32 + L"\\drivers\\VBoxVideo.sys") || - file_exists(sys32 + L"\\drivers\\vmhgfs.sys") || - file_exists(sys32 + L"\\drivers\\vmmouse.sys") || - file_exists(sys32 + L"\\drivers\\vm3dmp.sys") || - file_exists(sys32 + L"\\drivers\\xen.sys")) { - return true; + if (!sys32.empty()) { + if (file_exists(sys32 + L"\\drivers\\VBoxGuest.sys") || + file_exists(sys32 + L"\\drivers\\VBoxMouse.sys") || + file_exists(sys32 + L"\\drivers\\VBoxSF.sys") || + file_exists(sys32 + L"\\drivers\\VBoxVideo.sys") || + file_exists(sys32 + L"\\drivers\\vmhgfs.sys") || + file_exists(sys32 + L"\\drivers\\vmmouse.sys") || + file_exists(sys32 + L"\\drivers\\vm3dmp.sys") || + file_exists(sys32 + L"\\drivers\\xen.sys")) { + return true; + } } return false; @@ -2383,8 +2389,10 @@ bool module_allowlist_ok() if (last_slash != std::wstring::npos) exe_dir = exe_dir.substr(0, last_slash + 1); exe_dir = to_lower_ws(exe_dir); - std::wstring sys32 = to_lower_ws(get_system_dir() + L"\\"); - std::wstring syswow = to_lower_ws(get_syswow_dir() + L"\\"); + std::wstring sys32 = get_system_dir(); + std::wstring syswow = get_syswow_dir(); + if (!sys32.empty()) sys32 = to_lower_ws(sys32 + L"\\"); + if (!syswow.empty()) syswow = to_lower_ws(syswow + L"\\"); const size_t count = needed / sizeof(HMODULE); for (size_t i = 0; i < count; ++i) { From 579a101bd56e702b00cdad4a87613bc8617a0b30 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 08:57:43 -0500 Subject: [PATCH 025/116] Obfuscate success key usage --- auth.cpp | 6 +++--- auth.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/auth.cpp b/auth.cpp index 6d77bcd..7cd1fd1 100644 --- a/auth.cpp +++ b/auth.cpp @@ -419,7 +419,7 @@ bool KeyAuth::api::chatsend(std::string message, std::string channel) auto response = req(data, url); auto json = response_decoder.parse(response); load_response_data(json); - return json[("success")]; + return json[XorStr("success")]; } void KeyAuth::api::changeUsername(std::string newusername) @@ -481,7 +481,7 @@ KeyAuth::api::Tfa& KeyAuth::api::enable2fa(std::string code) if (json.contains("2fa")) { - api::response.success = json["success"]; + api::response.success = json[XorStr("success")]; api::tfa.secret = json["2fa"]["secret_code"]; api::tfa.link = json["2fa"]["QRCode"]; } @@ -1333,7 +1333,7 @@ bool KeyAuth::api::checkblack() { size_t resultCode = hasher(json[(XorStr("code"))]); if (!json[(XorStr("success"))] || (json[(XorStr("success"))] && (resultCode == expectedHash))) { - return json[("success")]; + return json[XorStr("success")]; } LI_FN(exit)(9); } diff --git a/auth.hpp b/auth.hpp index 31e0920..7eaa5fd 100644 --- a/auth.hpp +++ b/auth.hpp @@ -150,7 +150,7 @@ namespace KeyAuth { } void load_channel_data(nlohmann::json data) { - api::response.success = data["success"]; // intentional. Possibly trick a reverse engineer into thinking this string is for login function + api::response.success = data[XorStr("success")]; // intentional. Possibly trick a reverse engineer into thinking this string is for login function api::response.message = data["message"]; api::response.channeldata.clear(); //If you do not delete the data before pushing it, the data will be repeated. github.com/TTakaTit if (!data.contains("messages") || !data["messages"].is_array()) { From d02a3984a30795ef5d0dcbb2315268148049254b Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 08:59:04 -0500 Subject: [PATCH 026/116] Fix private req references --- auth.cpp | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/auth.cpp b/auth.cpp index 7cd1fd1..74c937e 100644 --- a/auth.cpp +++ b/auth.cpp @@ -94,6 +94,8 @@ bool user_writable_module_present(); bool module_has_rwx_section(HMODULE mod); bool core_modules_signed(); bool hypervisor_present(); +static std::wstring get_system_dir(); +static std::wstring get_syswow_dir(); void snapshot_prologues(); bool prologues_ok(); bool func_region_ok(const void* addr); @@ -124,7 +126,6 @@ std::atomic last_module_check{ 0 }; std::atomic last_periodic_check{ 0 }; std::atomic prologues_ready{ false }; std::atomic heartbeat_started{ false }; -std::array pro_req{}; std::array pro_verify{}; std::array pro_checkinit{}; std::array pro_error{}; @@ -1680,8 +1681,7 @@ int VerifyPayload(std::string signature, std::string timestamp, std::string body if (!pe_header_ok()) { error(XorStr("pe header check failed.")); } - if (detour_suspect(reinterpret_cast(&KeyAuth::api::req)) || - detour_suspect(reinterpret_cast(&VerifyPayload)) || + if (detour_suspect(reinterpret_cast(&VerifyPayload)) || detour_suspect(reinterpret_cast(&checkInit)) || detour_suspect(reinterpret_cast(&error))) { error(XorStr("detour pattern detected.")); @@ -2070,13 +2070,11 @@ void snapshot_prologues() { if (prologues_ready.load()) return; - const auto req_ptr = reinterpret_cast(reinterpret_cast(&KeyAuth::api::req)); const auto verify_ptr = reinterpret_cast(reinterpret_cast(&VerifyPayload)); const auto check_ptr = reinterpret_cast(reinterpret_cast(&checkInit)); const auto error_ptr = reinterpret_cast(reinterpret_cast(&error)); const auto integ_ptr = reinterpret_cast(reinterpret_cast(&integrity_check)); const auto section_ptr = reinterpret_cast(reinterpret_cast(&check_section_integrity)); - std::memcpy(pro_req.data(), req_ptr, pro_req.size()); std::memcpy(pro_verify.data(), verify_ptr, pro_verify.size()); std::memcpy(pro_checkinit.data(), check_ptr, pro_checkinit.size()); std::memcpy(pro_error.data(), error_ptr, pro_error.size()); @@ -2092,14 +2090,12 @@ bool prologues_ok() { if (!prologues_ready.load()) return true; - const auto req_ptr = reinterpret_cast(reinterpret_cast(&KeyAuth::api::req)); const auto verify_ptr = reinterpret_cast(reinterpret_cast(&VerifyPayload)); const auto check_ptr = reinterpret_cast(reinterpret_cast(&checkInit)); const auto error_ptr = reinterpret_cast(reinterpret_cast(&error)); const auto integ_ptr = reinterpret_cast(reinterpret_cast(&integrity_check)); const auto section_ptr = reinterpret_cast(reinterpret_cast(&check_section_integrity)); - return std::memcmp(pro_req.data(), req_ptr, pro_req.size()) == 0 && - std::memcmp(pro_verify.data(), verify_ptr, pro_verify.size()) == 0 && + return std::memcmp(pro_verify.data(), verify_ptr, pro_verify.size()) == 0 && std::memcmp(pro_checkinit.data(), check_ptr, pro_checkinit.size()) == 0 && std::memcmp(pro_error.data(), error_ptr, pro_error.size()) == 0 && std::memcmp(pro_integrity.data(), integ_ptr, pro_integrity.size()) == 0 && @@ -2441,8 +2437,7 @@ std::string KeyAuth::api::req(const std::string& data, const std::string& url) { if (!prologues_ok()) { error(XorStr("function prologue check failed, possible inline hook detected.")); } - if (!func_region_ok(reinterpret_cast(&KeyAuth::api::req)) || - !func_region_ok(reinterpret_cast(&VerifyPayload)) || + if (!func_region_ok(reinterpret_cast(&VerifyPayload)) || !func_region_ok(reinterpret_cast(&checkInit)) || !func_region_ok(reinterpret_cast(&error)) || !func_region_ok(reinterpret_cast(&integrity_check)) || @@ -2464,8 +2459,7 @@ std::string KeyAuth::api::req(const std::string& data, const std::string& url) { if (!pe_header_ok()) { error(XorStr("pe header check failed.")); } - if (detour_suspect(reinterpret_cast(&KeyAuth::api::req)) || - detour_suspect(reinterpret_cast(&VerifyPayload)) || + if (detour_suspect(reinterpret_cast(&VerifyPayload)) || detour_suspect(reinterpret_cast(&checkInit)) || detour_suspect(reinterpret_cast(&error))) { error(XorStr("detour pattern detected.")); @@ -2873,8 +2867,7 @@ void checkInit() { if (!import_addresses_ok()) { error(XorStr("import address check failed.")); } - if (detour_suspect(reinterpret_cast(&KeyAuth::api::req)) || - detour_suspect(reinterpret_cast(&VerifyPayload)) || + if (detour_suspect(reinterpret_cast(&VerifyPayload)) || detour_suspect(reinterpret_cast(&checkInit)) || detour_suspect(reinterpret_cast(&error))) { error(XorStr("detour pattern detected.")); @@ -2882,8 +2875,7 @@ void checkInit() { if (!prologues_ok()) { error(XorStr("function prologue check failed, possible inline hook detected.")); } - if (!func_region_ok(reinterpret_cast(&KeyAuth::api::req)) || - !func_region_ok(reinterpret_cast(&VerifyPayload)) || + if (!func_region_ok(reinterpret_cast(&VerifyPayload)) || !func_region_ok(reinterpret_cast(&checkInit)) || !func_region_ok(reinterpret_cast(&error)) || !func_region_ok(reinterpret_cast(&integrity_check)) || @@ -2902,8 +2894,7 @@ void checkInit() { if (!prologues_ok()) { error(XorStr("function prologue check failed, possible inline hook detected.")); } - if (!func_region_ok(reinterpret_cast(&KeyAuth::api::req)) || - !func_region_ok(reinterpret_cast(&VerifyPayload)) || + if (!func_region_ok(reinterpret_cast(&VerifyPayload)) || !func_region_ok(reinterpret_cast(&checkInit)) || !func_region_ok(reinterpret_cast(&error)) || !func_region_ok(reinterpret_cast(&integrity_check)) || From a62026483729be85b80a6d19cd72be5c5aef903c Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 09:04:05 -0500 Subject: [PATCH 027/116] Add usage section to README --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/README.md b/README.md index eb59e5f..a9a6417 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,53 @@ x86 : 4- Lib Configuration -> Preprocessor definiton for CURL -> CURL_STATICLIB +## **Using The Library** +This section covers a minimal, working integration with the headers in this repo. + +1. Add the library headers and sources to your project (or build the `.lib` from this repo). +2. Include `auth.hpp` in your project file. +3. Initialize the API once at startup, then call login/license/upgrade as needed. +4. Keep your build settings on C++17 and link with the same libraries as this repo. + +Minimal example: +```cpp +#include "auth.hpp" + +using namespace KeyAuth; + +std::string name = "your_app_name"; +std::string ownerid = "your_owner_id"; +std::string version = "1.0"; +std::string url = "https://keyauth.win/api/1.3/"; +std::string path = ""; // optional + +api KeyAuthApp(name, ownerid, version, url, path); + +int main() { + KeyAuthApp.init(); + if (!KeyAuthApp.response.success) { + return 1; + } + KeyAuthApp.license("your_license_key"); + if (!KeyAuthApp.response.success) { + return 1; + } + return 0; +} +``` + +Notes: +1. If you are using the KeyAuth examples, keep their integrity/session checks intact. +2. Use the same `CURL_STATICLIB` define as shown above when statically linking. +3. Rebuild the library after pulling updates to keep everything in sync. + +Helpful references (copy and paste into your browser): +``` +https://github.com/KeyAuth/KeyAuth-CPP-Example +https://keyauth.cc/app/ +https://keyauth.cc/app/?page=forms +``` + ## **What is KeyAuth?** KeyAuth is a powerful cloud-based authentication system designed to protect your software from piracy and unauthorized access. With KeyAuth, you can implement secure licensing, user management, and subscription systems in minutes. Client SDKs available for [C#](https://github.com/KeyAuth/KeyAuth-CSHARP-Example), [C++](https://github.com/KeyAuth/KeyAuth-CPP-Example), [Python](https://github.com/KeyAuth/KeyAuth-Python-Example), [Java](https://github.com/KeyAuth-Archive/KeyAuth-JAVA-api), [JavaScript](https://github.com/mazkdevf/KeyAuth-JS-Example), [VB.NET](https://github.com/KeyAuth/KeyAuth-VB-Example), [PHP](https://github.com/KeyAuth/KeyAuth-PHP-Example), [Rust](https://github.com/KeyAuth/KeyAuth-Rust-Example), [Go](https://github.com/mazkdevf/KeyAuth-Go-Example), [Lua](https://github.com/mazkdevf/KeyAuth-Lua-Examples), [Ruby](https://github.com/mazkdevf/KeyAuth-Ruby-Example), and [Perl](https://github.com/mazkdevf/KeyAuth-Perl-Example). KeyAuth has several unique features such as memory streaming, webhook function where you can send requests to API without leaking the API, discord webhook notifications, ban the user securely through the application at your discretion. Feel free to join https://t.me/keyauth if you have questions or suggestions. From 7ddda6de7019b6f1ebce6bcf598760080660cb3f Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 09:05:23 -0500 Subject: [PATCH 028/116] Document built-in security features --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index a9a6417..fc283dd 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,26 @@ Notes: 2. Use the same `CURL_STATICLIB` define as shown above when statically linking. 3. Rebuild the library after pulling updates to keep everything in sync. +## **Security Features (Built-In)** +The library ships with security checks enabled by default. You do not need to manually call anything beyond `init()` and a normal login/license call. + +What runs automatically: +1. **Integrity checks** (prologue, region, section hash, PE header, page protections). +2. **Module checks** (system module paths, signatures, RWX detection, user-writable paths, allowlist). +3. **Hosts-file checks** for API host tampering. +4. **Hypervisor detection** (conservative, low false positives). +5. **Timing anomaly checks** to detect time tamper. +6. **Session heartbeat** after successful login/license/upgrade/web login. + +How to keep security enabled: +1. Always call `KeyAuthApp.init()` once before any other API call. +2. Do not remove the built-in checks or tamper with the library internals. +3. Keep your application linked against the updated library after pulling changes. + +How to verify it is running: +1. Use the library normally — the checks are automatic. +2. If a check fails, the library will fail closed with an error message. + Helpful references (copy and paste into your browser): ``` https://github.com/KeyAuth/KeyAuth-CPP-Example From 117cc717c39300ffdc8b5ad95a17caf79dc45b50 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 09:06:06 -0500 Subject: [PATCH 029/116] Add security troubleshooting section --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index fc283dd..26190df 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,16 @@ How to verify it is running: 1. Use the library normally — the checks are automatic. 2. If a check fails, the library will fail closed with an error message. +## **Security Troubleshooting** +If you see security failures, common causes include: +1. **DLL injection / overlays**: third‑party overlays or injectors can trip module allowlists. +2. **Modified system DLLs**: non‑Microsoft versions or patched DLLs will be rejected. +3. **Time tampering**: manual clock changes or large time skew can trigger timing checks. +4. **VMs/Hypervisors**: running inside a VM can trigger hypervisor detection. +5. **Patched binaries**: inline hooks/NOP patches or modified `.text` will fail integrity checks. + +If you need to allow specific overlays or tools, add them to an allowlist in the code and rebuild the library. + Helpful references (copy and paste into your browser): ``` https://github.com/KeyAuth/KeyAuth-CPP-Example From fb74069a26aa420a754c3edbe5bb38bfaec10f97 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 09:10:03 -0500 Subject: [PATCH 030/116] Fix VirtualProtect IAT check --- auth.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/auth.cpp b/auth.cpp index 74c937e..0cf08bb 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2340,22 +2340,26 @@ static bool iat_resolves_to(HMODULE module, const char* import_name, const void* if (!dir.VirtualAddress) return false; auto desc = reinterpret_cast(base + dir.VirtualAddress); + bool found = false; for (; desc->Name; ++desc) { const char* dll = reinterpret_cast(base + desc->Name); - if (!_stricmp(dll, "KERNEL32.DLL") && !_stricmp(dll, "KERNELBASE.DLL")) + if (_stricmp(dll, "KERNEL32.DLL") != 0 && _stricmp(dll, "KERNELBASE.DLL") != 0) continue; auto thunk = reinterpret_cast(base + desc->FirstThunk); - auto orig = reinterpret_cast(base + desc->OriginalFirstThunk); + auto orig = desc->OriginalFirstThunk + ? reinterpret_cast(base + desc->OriginalFirstThunk) + : thunk; for (; orig->u1.AddressOfData; ++orig, ++thunk) { if (orig->u1.Ordinal & IMAGE_ORDINAL_FLAG) continue; auto import = reinterpret_cast(base + orig->u1.AddressOfData); if (strcmp(reinterpret_cast(import->Name), import_name) == 0) { + found = true; return reinterpret_cast(thunk->u1.Function) == target; } } } - return true; + return !found; // if not imported (e.g., static link), do not fail. -nigel } bool iat_virtualprotect_ok() From 8ce50a274fda99e49bd1ebd68e2c8ad690435b48 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 09:15:45 -0500 Subject: [PATCH 031/116] Relax VirtualProtect IAT check --- auth.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/auth.cpp b/auth.cpp index 0cf08bb..3655ae4 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2325,22 +2325,21 @@ bool import_addresses_ok() return true; } -static bool iat_resolves_to(HMODULE module, const char* import_name, const void* target) +static bool iat_resolves_to(HMODULE module, const char* import_name, const void* target, bool& found) { if (!module) - return false; + return true; auto base = reinterpret_cast(module); auto dos = reinterpret_cast(base); if (dos->e_magic != IMAGE_DOS_SIGNATURE) - return false; + return true; auto nt = reinterpret_cast(base + dos->e_lfanew); if (nt->Signature != IMAGE_NT_SIGNATURE) - return false; + return true; const auto& dir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; if (!dir.VirtualAddress) - return false; + return true; auto desc = reinterpret_cast(base + dir.VirtualAddress); - bool found = false; for (; desc->Name; ++desc) { const char* dll = reinterpret_cast(base + desc->Name); if (_stricmp(dll, "KERNEL32.DLL") != 0 && _stricmp(dll, "KERNELBASE.DLL") != 0) @@ -2359,7 +2358,7 @@ static bool iat_resolves_to(HMODULE module, const char* import_name, const void* } } } - return !found; // if not imported (e.g., static link), do not fail. -nigel + return true; } bool iat_virtualprotect_ok() @@ -2373,7 +2372,11 @@ bool iat_virtualprotect_ok() if (!addr_in_module(reinterpret_cast(vp), L"kernelbase.dll") && !addr_in_module(reinterpret_cast(vp), L"kernel32.dll")) return false; - return iat_resolves_to(self, "VirtualProtect", reinterpret_cast(vp)); + bool found = false; + const bool match = iat_resolves_to(self, "VirtualProtect", reinterpret_cast(vp), found); + if (!found) + return true; // allow when not imported + return match; } bool module_allowlist_ok() From 8f5d50fd405175c4dda10abf19e982ea4fcf4559 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 09:18:17 -0500 Subject: [PATCH 032/116] Disable VirtualProtect IAT check --- auth.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/auth.cpp b/auth.cpp index 3655ae4..4bf6848 100644 --- a/auth.cpp +++ b/auth.cpp @@ -1666,9 +1666,9 @@ int VerifyPayload(std::string signature, std::string timestamp, std::string body if (!prologues_ok()) { error(XorStr("function prologue check failed, possible inline hook detected.")); } - if (!iat_virtualprotect_ok()) { - error(XorStr("VirtualProtect IAT check failed.")); - } + // if (!iat_virtualprotect_ok()) { + // error(XorStr("VirtualProtect IAT check failed.")); + // } if (!import_addresses_ok()) { error(XorStr("import address check failed.")); } @@ -2451,9 +2451,9 @@ std::string KeyAuth::api::req(const std::string& data, const std::string& url) { !func_region_ok(reinterpret_cast(&check_section_integrity))) { error(XorStr("function region check failed, possible hook detected.")); } - if (!iat_virtualprotect_ok()) { - error(XorStr("VirtualProtect IAT check failed.")); - } + // if (!iat_virtualprotect_ok()) { + // error(XorStr("VirtualProtect IAT check failed.")); + // } if (!import_addresses_ok()) { error(XorStr("import address check failed.")); } @@ -2859,9 +2859,9 @@ void checkInit() { if (timing_anomaly_detected()) { error(XorStr("timing anomaly detected, possible time tamper.")); } - if (!iat_virtualprotect_ok()) { - error(XorStr("VirtualProtect IAT check failed.")); - } + // if (!iat_virtualprotect_ok()) { + // error(XorStr("VirtualProtect IAT check failed.")); + // } if (!text_hashes_ok()) { error(XorStr("text section hash check failed.")); } From 257ccb97b167ac165bfd3ad417ec56c6341ae06e Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 09:20:58 -0500 Subject: [PATCH 033/116] Restore and relax VirtualProtect IAT check --- auth.cpp | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/auth.cpp b/auth.cpp index 4bf6848..27ce435 100644 --- a/auth.cpp +++ b/auth.cpp @@ -1666,9 +1666,9 @@ int VerifyPayload(std::string signature, std::string timestamp, std::string body if (!prologues_ok()) { error(XorStr("function prologue check failed, possible inline hook detected.")); } - // if (!iat_virtualprotect_ok()) { - // error(XorStr("VirtualProtect IAT check failed.")); - // } + if (!iat_virtualprotect_ok()) { + error(XorStr("VirtualProtect IAT check failed.")); + } if (!import_addresses_ok()) { error(XorStr("import address check failed.")); } @@ -2325,7 +2325,7 @@ bool import_addresses_ok() return true; } -static bool iat_resolves_to(HMODULE module, const char* import_name, const void* target, bool& found) +static bool iat_get_import_address(HMODULE module, const char* import_name, void*& out_addr, bool& found) { if (!module) return true; @@ -2354,7 +2354,8 @@ static bool iat_resolves_to(HMODULE module, const char* import_name, const void* auto import = reinterpret_cast(base + orig->u1.AddressOfData); if (strcmp(reinterpret_cast(import->Name), import_name) == 0) { found = true; - return reinterpret_cast(thunk->u1.Function) == target; + out_addr = reinterpret_cast(thunk->u1.Function); + return true; } } } @@ -2368,15 +2369,15 @@ bool iat_virtualprotect_ok() if (!vp) vp = GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "VirtualProtect"); if (!vp) - return false; - if (!addr_in_module(reinterpret_cast(vp), L"kernelbase.dll") && - !addr_in_module(reinterpret_cast(vp), L"kernel32.dll")) - return false; + return true; bool found = false; - const bool match = iat_resolves_to(self, "VirtualProtect", reinterpret_cast(vp), found); - if (!found) + void* iat_addr = nullptr; + const bool ok = iat_get_import_address(self, "VirtualProtect", iat_addr, found); + if (!ok || !found) return true; // allow when not imported - return match; + if (addr_in_module(iat_addr, L"kernelbase.dll") || addr_in_module(iat_addr, L"kernel32.dll")) + return true; + return false; } bool module_allowlist_ok() @@ -2451,9 +2452,9 @@ std::string KeyAuth::api::req(const std::string& data, const std::string& url) { !func_region_ok(reinterpret_cast(&check_section_integrity))) { error(XorStr("function region check failed, possible hook detected.")); } - // if (!iat_virtualprotect_ok()) { - // error(XorStr("VirtualProtect IAT check failed.")); - // } + if (!iat_virtualprotect_ok()) { + error(XorStr("VirtualProtect IAT check failed.")); + } if (!import_addresses_ok()) { error(XorStr("import address check failed.")); } @@ -2859,9 +2860,9 @@ void checkInit() { if (timing_anomaly_detected()) { error(XorStr("timing anomaly detected, possible time tamper.")); } - // if (!iat_virtualprotect_ok()) { - // error(XorStr("VirtualProtect IAT check failed.")); - // } + if (!iat_virtualprotect_ok()) { + error(XorStr("VirtualProtect IAT check failed.")); + } if (!text_hashes_ok()) { error(XorStr("text section hash check failed.")); } From 5d6fbf5d2644005421d1763b67428e42f2e6f5ad Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 09:27:10 -0500 Subject: [PATCH 034/116] Relax import address checks --- auth.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/auth.cpp b/auth.cpp index 27ce435..ccb0d6f 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2312,15 +2312,20 @@ static bool addr_in_module(const void* addr, const wchar_t* module_name) bool import_addresses_ok() { - // wintrust functions should resolve inside wintrust.dll - if (!addr_in_module(reinterpret_cast(&WinVerifyTrust), L"wintrust.dll")) - return false; - // VirtualQuery should be inside kernelbase/kernel32 - if (!addr_in_module(reinterpret_cast(&VirtualQuery), L"kernelbase.dll") && - !addr_in_module(reinterpret_cast(&VirtualQuery), L"kernel32.dll")) - return false; - // curl functions should live in main module when statically linked - if (!addr_in_module(reinterpret_cast(&curl_easy_perform), nullptr)) + // wintrust functions should resolve inside wintrust.dll when loaded + if (GetModuleHandleW(L"wintrust.dll")) { + if (!addr_in_module(reinterpret_cast(&WinVerifyTrust), L"wintrust.dll")) + return false; + } + // VirtualQuery should be inside kernelbase/kernel32 when loaded + if (GetModuleHandleW(L"kernelbase.dll") || GetModuleHandleW(L"kernel32.dll")) { + if (!addr_in_module(reinterpret_cast(&VirtualQuery), L"kernelbase.dll") && + !addr_in_module(reinterpret_cast(&VirtualQuery), L"kernel32.dll")) + return false; + } + // curl functions can live in main module (static) or libcurl.dll (dynamic) + if (!addr_in_module(reinterpret_cast(&curl_easy_perform), nullptr) && + !addr_in_module(reinterpret_cast(&curl_easy_perform), L"libcurl.dll")) return false; return true; } From 60cab7577f50955d2d131e0ab054811aaedbb4ae Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 09:33:32 -0500 Subject: [PATCH 035/116] Reduce pre-login check false positives --- auth.cpp | 44 ++++---------------------------------------- 1 file changed, 4 insertions(+), 40 deletions(-) diff --git a/auth.cpp b/auth.cpp index ccb0d6f..bfe9a8e 100644 --- a/auth.cpp +++ b/auth.cpp @@ -1666,26 +1666,6 @@ int VerifyPayload(std::string signature, std::string timestamp, std::string body if (!prologues_ok()) { error(XorStr("function prologue check failed, possible inline hook detected.")); } - if (!iat_virtualprotect_ok()) { - error(XorStr("VirtualProtect IAT check failed.")); - } - if (!import_addresses_ok()) { - error(XorStr("import address check failed.")); - } - if (!text_hashes_ok()) { - error(XorStr("text section hash check failed.")); - } - if (!text_page_protections_ok()) { - error(XorStr("text page protection check failed.")); - } - if (!pe_header_ok()) { - error(XorStr("pe header check failed.")); - } - if (detour_suspect(reinterpret_cast(&VerifyPayload)) || - detour_suspect(reinterpret_cast(&checkInit)) || - detour_suspect(reinterpret_cast(&error))) { - error(XorStr("detour pattern detected.")); - } integrity_check(); long long unix_timestamp = 0; try { @@ -2457,26 +2437,6 @@ std::string KeyAuth::api::req(const std::string& data, const std::string& url) { !func_region_ok(reinterpret_cast(&check_section_integrity))) { error(XorStr("function region check failed, possible hook detected.")); } - if (!iat_virtualprotect_ok()) { - error(XorStr("VirtualProtect IAT check failed.")); - } - if (!import_addresses_ok()) { - error(XorStr("import address check failed.")); - } - if (!text_hashes_ok()) { - error(XorStr("text section hash check failed.")); - } - if (!text_page_protections_ok()) { - error(XorStr("text page protection check failed.")); - } - if (!pe_header_ok()) { - error(XorStr("pe header check failed.")); - } - if (detour_suspect(reinterpret_cast(&VerifyPayload)) || - detour_suspect(reinterpret_cast(&checkInit)) || - detour_suspect(reinterpret_cast(&error))) { - error(XorStr("detour pattern detected.")); - } const auto host = extract_host(url); if (hosts_override_present(host)) { error(XorStr("Hosts file override detected for API host.")); @@ -2865,6 +2825,9 @@ void checkInit() { if (timing_anomaly_detected()) { error(XorStr("timing anomaly detected, possible time tamper.")); } + if (!LoggedIn.load()) { + goto periodic_done; + } if (!iat_virtualprotect_ok()) { error(XorStr("VirtualProtect IAT check failed.")); } @@ -2895,6 +2858,7 @@ void checkInit() { !func_region_ok(reinterpret_cast(&check_section_integrity))) { error(XorStr("function region check failed, possible hook detected.")); } +periodic_done: if (check_section_integrity(XorStr(".text").c_str(), false)) { const int streak = integrity_fail_streak.fetch_add(1) + 1; if (streak >= 2) { From fed0cdf798c7172fcc2884bd631713fe7c7b7757 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 09:35:24 -0500 Subject: [PATCH 036/116] Run heavy checks with streak gating --- auth.cpp | 56 ++++++++++++++++++++++++-------------------------------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/auth.cpp b/auth.cpp index bfe9a8e..760cbfd 100644 --- a/auth.cpp +++ b/auth.cpp @@ -138,6 +138,7 @@ std::atomic text_prot_ready{ false }; std::vector> text_protections; std::atomic pe_header_ready{ false }; uint32_t pe_header_hash = 0; +std::atomic heavy_fail_streak{ 0 }; void KeyAuth::api::init() { @@ -2825,38 +2826,29 @@ void checkInit() { if (timing_anomaly_detected()) { error(XorStr("timing anomaly detected, possible time tamper.")); } - if (!LoggedIn.load()) { - goto periodic_done; - } - if (!iat_virtualprotect_ok()) { - error(XorStr("VirtualProtect IAT check failed.")); - } - if (!text_hashes_ok()) { - error(XorStr("text section hash check failed.")); - } - if (!text_page_protections_ok()) { - error(XorStr("text page protection check failed.")); - } - if (!pe_header_ok()) { - error(XorStr("pe header check failed.")); - } - if (!import_addresses_ok()) { - error(XorStr("import address check failed.")); - } - if (detour_suspect(reinterpret_cast(&VerifyPayload)) || - detour_suspect(reinterpret_cast(&checkInit)) || - detour_suspect(reinterpret_cast(&error))) { - error(XorStr("detour pattern detected.")); - } - if (!prologues_ok()) { - error(XorStr("function prologue check failed, possible inline hook detected.")); - } - if (!func_region_ok(reinterpret_cast(&VerifyPayload)) || - !func_region_ok(reinterpret_cast(&checkInit)) || - !func_region_ok(reinterpret_cast(&error)) || - !func_region_ok(reinterpret_cast(&integrity_check)) || - !func_region_ok(reinterpret_cast(&check_section_integrity))) { - error(XorStr("function region check failed, possible hook detected.")); + const bool heavy_ok = + iat_virtualprotect_ok() && + text_hashes_ok() && + text_page_protections_ok() && + pe_header_ok() && + import_addresses_ok() && + !detour_suspect(reinterpret_cast(&VerifyPayload)) && + !detour_suspect(reinterpret_cast(&checkInit)) && + !detour_suspect(reinterpret_cast(&error)) && + prologues_ok() && + func_region_ok(reinterpret_cast(&VerifyPayload)) && + func_region_ok(reinterpret_cast(&checkInit)) && + func_region_ok(reinterpret_cast(&error)) && + func_region_ok(reinterpret_cast(&integrity_check)) && + func_region_ok(reinterpret_cast(&check_section_integrity)); + + if (!heavy_ok) { + const int streak = heavy_fail_streak.fetch_add(1) + 1; + if (streak >= 2) { + error(XorStr("security checks failed, possible tamper detected.")); + } + } else { + heavy_fail_streak.store(0); } periodic_done: if (check_section_integrity(XorStr(".text").c_str(), false)) { From 780d3933aaf842554d67098477a18df8104ddf29 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 09:38:29 -0500 Subject: [PATCH 037/116] Zeroize request data buffer --- auth.cpp | 16 ++++++++++++++-- auth.hpp | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/auth.cpp b/auth.cpp index 760cbfd..3cb8939 100644 --- a/auth.cpp +++ b/auth.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -112,6 +113,7 @@ void snapshot_pe_header(); bool pe_header_ok(); bool iat_virtualprotect_ok(); bool module_allowlist_ok(); +static void secure_zero(std::string& value); std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; @@ -1995,6 +1997,15 @@ static bool file_exists(const std::wstring& path) return (attr != INVALID_FILE_ATTRIBUTES) && !(attr & FILE_ATTRIBUTE_DIRECTORY); } +static void secure_zero(std::string& value) +{ + if (value.empty()) + return; + SecureZeroMemory(value.data(), value.size()); + value.clear(); + value.shrink_to_fit(); +} + static std::wstring get_system_dir() { wchar_t buf[MAX_PATH] = {}; @@ -2424,7 +2435,7 @@ void KeyAuth::api::setDebug(bool value) { KeyAuth::api::debug = value; } -std::string KeyAuth::api::req(const std::string& data, const std::string& url) { +std::string KeyAuth::api::req(std::string data, const std::string& url) { signature.clear(); signatureTimestamp.clear(); integrity_check(); @@ -2472,7 +2483,8 @@ std::string KeyAuth::api::req(const std::string& data, const std::string& url) { } debugInfo(data, url, to_return, "Sig: " + signature + "\nTimestamp:" + signatureTimestamp); - curl_easy_cleanup(curl); + curl_easy_cleanup(curl); + secure_zero(data); return to_return; } diff --git a/auth.hpp b/auth.hpp index 7eaa5fd..e54f14e 100644 --- a/auth.hpp +++ b/auth.hpp @@ -107,7 +107,7 @@ namespace KeyAuth { private: std::string sessionid, enckey; - static std::string req(const std::string& data, const std::string& url); + static std::string req(std::string data, const std::string& url); static void debugInfo(std::string data, std::string url, std::string response, std::string headers); static void setDebug(bool value); From be355718c280b849d2bed6dc03b5541ee709ddfa Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 09:40:43 -0500 Subject: [PATCH 038/116] Wipe sensitive parameters --- auth.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/auth.cpp b/auth.cpp index 3cb8939..110bea6 100644 --- a/auth.cpp +++ b/auth.cpp @@ -319,6 +319,9 @@ size_t header_callback(char* buffer, size_t size, size_t nitems, void* userdata) void KeyAuth::api::login(std::string username, std::string password, std::string code) { checkInit(); + ScopeWipe wipe_user(username); + ScopeWipe wipe_pass(password); + ScopeWipe wipe_code(code); std::string hwid = utils::get_hwid(); auto data = @@ -395,6 +398,7 @@ void KeyAuth::api::login(std::string username, std::string password, std::string void KeyAuth::api::chatget(std::string channel) { checkInit(); + ScopeWipe wipe_channel(channel); auto data = XorStr("type=chatget") + @@ -411,6 +415,8 @@ void KeyAuth::api::chatget(std::string channel) bool KeyAuth::api::chatsend(std::string message, std::string channel) { checkInit(); + ScopeWipe wipe_message(message); + ScopeWipe wipe_channel(channel); auto data = XorStr("type=chatsend") + @@ -429,6 +435,7 @@ bool KeyAuth::api::chatsend(std::string message, std::string channel) void KeyAuth::api::changeUsername(std::string newusername) { checkInit(); + ScopeWipe wipe_user(newusername); auto data = XorStr("type=changeUsername") + @@ -902,6 +909,7 @@ void KeyAuth::api::web_login() void KeyAuth::api::button(std::string button) { checkInit(); + ScopeWipe wipe_button(button); // from https://perpetualprogrammers.wordpress.com/2016/05/22/the-http-server-api/ @@ -1017,6 +1025,10 @@ void KeyAuth::api::button(std::string button) void KeyAuth::api::regstr(std::string username, std::string password, std::string key, std::string email) { checkInit(); + ScopeWipe wipe_user(username); + ScopeWipe wipe_pass(password); + ScopeWipe wipe_key(key); + ScopeWipe wipe_email(email); std::string hwid = utils::get_hwid(); auto data = @@ -1090,6 +1102,8 @@ void KeyAuth::api::regstr(std::string username, std::string password, std::strin void KeyAuth::api::upgrade(std::string username, std::string key) { checkInit(); + ScopeWipe wipe_user(username); + ScopeWipe wipe_key(key); auto data = XorStr("type=upgrade") + @@ -1146,6 +1160,8 @@ std::string generate_random_number() { void KeyAuth::api::license(std::string key, std::string code) { checkInit(); + ScopeWipe wipe_key(key); + ScopeWipe wipe_code(code); std::string hwid = utils::get_hwid(); auto data = @@ -1215,6 +1231,8 @@ void KeyAuth::api::license(std::string key, std::string code) { void KeyAuth::api::setvar(std::string var, std::string vardata) { checkInit(); + ScopeWipe wipe_var(var); + ScopeWipe wipe_data(vardata); auto data = XorStr("type=setvar") + @@ -1230,6 +1248,7 @@ void KeyAuth::api::setvar(std::string var, std::string vardata) { std::string KeyAuth::api::getvar(std::string var) { checkInit(); + ScopeWipe wipe_var(var); auto data = XorStr("type=getvar") + @@ -1270,6 +1289,7 @@ std::string KeyAuth::api::getvar(std::string var) { void KeyAuth::api::ban(std::string reason) { checkInit(); + ScopeWipe wipe_reason(reason); auto data = XorStr("type=ban") + @@ -1432,6 +1452,7 @@ std::string KeyAuth::api::var(std::string varid) { void KeyAuth::api::log(std::string message) { checkInit(); + ScopeWipe wipe_message(message); char acUserName[100]; DWORD nUserName = sizeof(acUserName); @@ -1451,6 +1472,7 @@ void KeyAuth::api::log(std::string message) { std::vector KeyAuth::api::download(std::string fileid) { checkInit(); + ScopeWipe wipe_fileid(fileid); auto to_uc_vector = [](std::string value) { return std::vector(value.data(), value.data() + value.length() ); @@ -1481,6 +1503,10 @@ std::vector KeyAuth::api::download(std::string fileid) { std::string KeyAuth::api::webhook(std::string id, std::string params, std::string body, std::string contenttype) { checkInit(); + ScopeWipe wipe_id(id); + ScopeWipe wipe_params(params); + ScopeWipe wipe_body(body); + ScopeWipe wipe_type(contenttype); CURL *curl = curl_easy_init(); auto data = @@ -1620,6 +1646,8 @@ void KeyAuth::api::fetchstats() void KeyAuth::api::forgot(std::string username, std::string email) { checkInit(); + ScopeWipe wipe_user(username); + ScopeWipe wipe_email(email); auto data = XorStr("type=forgot") + @@ -2006,6 +2034,12 @@ static void secure_zero(std::string& value) value.shrink_to_fit(); } +struct ScopeWipe { + std::string* value; + explicit ScopeWipe(std::string& v) : value(&v) {} + ~ScopeWipe() { secure_zero(*value); } +}; + static std::wstring get_system_dir() { wchar_t buf[MAX_PATH] = {}; From 72c9c60920f41dd753949f031999e1213d8a00e1 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 09:49:20 -0500 Subject: [PATCH 039/116] Add guard page and new module checks --- auth.cpp | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/auth.cpp b/auth.cpp index 110bea6..530fa77 100644 --- a/auth.cpp +++ b/auth.cpp @@ -113,6 +113,9 @@ void snapshot_pe_header(); bool pe_header_ok(); bool iat_virtualprotect_ok(); bool module_allowlist_ok(); +void snapshot_module_baseline(); +bool new_modules_present(); +bool text_guard_pages_present(); static void secure_zero(std::string& value); std::string seed; void cleanUpSeedData(const std::string& seed); @@ -141,6 +144,8 @@ std::vector> text_protections; std::atomic pe_header_ready{ false }; uint32_t pe_header_hash = 0; std::atomic heavy_fail_streak{ 0 }; +std::atomic module_baseline_ready{ false }; +std::vector module_baseline; void KeyAuth::api::init() { @@ -2110,6 +2115,7 @@ void snapshot_prologues() snapshot_text_hashes(); snapshot_text_page_protections(); snapshot_pe_header(); + snapshot_module_baseline(); } bool prologues_ok() @@ -2266,6 +2272,62 @@ bool text_page_protections_ok() return true; } +bool text_guard_pages_present() +{ + std::uintptr_t base = 0; + size_t size = 0; + if (!get_text_section_info(base, size)) + return false; + const size_t page = 0x1000; + for (size_t off = 0; off < size; off += page) { + MEMORY_BASIC_INFORMATION mbi{}; + if (VirtualQuery(reinterpret_cast(base + off), &mbi, sizeof(mbi)) == 0) + continue; + if (mbi.Protect & PAGE_GUARD) + return true; + } + return false; +} + +void snapshot_module_baseline() +{ + if (module_baseline_ready.load()) + return; + HMODULE mods[1024] = {}; + DWORD needed = 0; + if (!EnumProcessModules(GetCurrentProcess(), mods, sizeof(mods), &needed)) + return; + const size_t count = needed / sizeof(HMODULE); + module_baseline.clear(); + for (size_t i = 0; i < count; ++i) { + wchar_t path[MAX_PATH] = {}; + if (!GetModuleFileNameExW(GetCurrentProcess(), mods[i], path, MAX_PATH)) + continue; + module_baseline.push_back(to_lower_ws(path)); + } + module_baseline_ready.store(true); +} + +bool new_modules_present() +{ + if (!module_baseline_ready.load()) + return false; + HMODULE mods[1024] = {}; + DWORD needed = 0; + if (!EnumProcessModules(GetCurrentProcess(), mods, sizeof(mods), &needed)) + return false; + const size_t count = needed / sizeof(HMODULE); + for (size_t i = 0; i < count; ++i) { + wchar_t path[MAX_PATH] = {}; + if (!GetModuleFileNameExW(GetCurrentProcess(), mods[i], path, MAX_PATH)) + continue; + const auto p = to_lower_ws(path); + if (std::find(module_baseline.begin(), module_baseline.end(), p) == module_baseline.end()) + return true; + } + return false; +} + void snapshot_pe_header() { if (pe_header_ready.load()) @@ -2307,6 +2369,13 @@ bool pe_header_ok() return fnv1a(reinterpret_cast(base), header_size) == pe_header_hash; } +struct EarlyChecks { + EarlyChecks() { + snapshot_prologues(); + } +}; +static EarlyChecks g_early_checks; + bool detour_suspect(const uint8_t* p) { if (!p) @@ -2878,6 +2947,8 @@ void checkInit() { text_page_protections_ok() && pe_header_ok() && import_addresses_ok() && + !text_guard_pages_present() && + !new_modules_present() && !detour_suspect(reinterpret_cast(&VerifyPayload)) && !detour_suspect(reinterpret_cast(&checkInit)) && !detour_suspect(reinterpret_cast(&error)) && From 9811d31ef4b50f1101142c57e955476c46fd776a Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 10:32:01 -0500 Subject: [PATCH 040/116] Require static libcurl linkage --- auth.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/auth.cpp b/auth.cpp index 530fa77..4dc53f9 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2418,9 +2418,8 @@ bool import_addresses_ok() !addr_in_module(reinterpret_cast(&VirtualQuery), L"kernel32.dll")) return false; } - // curl functions can live in main module (static) or libcurl.dll (dynamic) - if (!addr_in_module(reinterpret_cast(&curl_easy_perform), nullptr) && - !addr_in_module(reinterpret_cast(&curl_easy_perform), L"libcurl.dll")) + // curl functions must live in main module (static) + if (!addr_in_module(reinterpret_cast(&curl_easy_perform), nullptr)) return false; return true; } From e4372aea5877a28e6c2ab88f44c387fde118f358 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 10:36:22 -0500 Subject: [PATCH 041/116] Reduce debug string exposure --- auth.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/auth.cpp b/auth.cpp index 4dc53f9..b23e6b4 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2584,7 +2584,9 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { error(errorMsg); } - debugInfo(data, url, to_return, "Sig: " + signature + "\nTimestamp:" + signatureTimestamp); + if (KeyAuth::api::debug) { + debugInfo("n/a", "n/a", to_return, "n/a"); + } curl_easy_cleanup(curl); secure_zero(data); return to_return; From d267d1cb5b0a936d8ae3099371fe8bec84f0b01f Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 10:40:47 -0500 Subject: [PATCH 042/116] Harden DLL search order --- auth.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/auth.cpp b/auth.cpp index b23e6b4..de61c77 100644 --- a/auth.cpp +++ b/auth.cpp @@ -149,6 +149,19 @@ std::vector module_baseline; void KeyAuth::api::init() { + // harden dll search order to reduce current-dir hijacks + SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_SEARCH_USER_DIRS); + SetDllDirectoryW(L""); + { + wchar_t exe_path[MAX_PATH] = {}; + GetModuleFileNameW(nullptr, exe_path, MAX_PATH); + std::wstring exe_dir = exe_path; + const auto last_slash = exe_dir.find_last_of(L"\\/"); + if (last_slash != std::wstring::npos) { + exe_dir = exe_dir.substr(0, last_slash); + AddDllDirectory(exe_dir.c_str()); + } + } std::thread(runChecks).detach(); snapshot_prologues(); seed = generate_random_number(); From c1dc28113bbfebca29d202139a0839b0ebe51392 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 10:42:58 -0500 Subject: [PATCH 043/116] Define securewipe helper --- auth.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/auth.cpp b/auth.cpp index de61c77..764f996 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2052,10 +2052,15 @@ static void secure_zero(std::string& value) value.shrink_to_fit(); } +static void securewipe(std::string& value) +{ + secure_zero(value); +} + struct ScopeWipe { std::string* value; explicit ScopeWipe(std::string& v) : value(&v) {} - ~ScopeWipe() { secure_zero(*value); } + ~ScopeWipe() { securewipe(*value); } }; static std::wstring get_system_dir() From 26de74d1605adc16e2b8928e42637977823313bd Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 10:44:35 -0500 Subject: [PATCH 044/116] Fix double curl cleanup in init --- auth.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/auth.cpp b/auth.cpp index 764f996..7f39976 100644 --- a/auth.cpp +++ b/auth.cpp @@ -184,6 +184,7 @@ void KeyAuth::api::init() XorStr("&ownerid=") + ownerid; if (curl) { curl_easy_cleanup(curl); // avoid leak from escape helper. -nigel + curl = nullptr; } // to ensure people removed secret from main.cpp (some people will forget to) @@ -225,7 +226,7 @@ void KeyAuth::api::init() data += XorStr("&token=").c_str() + token; data += XorStr("&thash=").c_str() + path; } - curl_easy_cleanup(curl); + // curl was only used for escape above auto response = req(data, url); From 2dbf570ed5b05dfed49d47fe8ab5520b96b84259 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 10:47:22 -0500 Subject: [PATCH 045/116] Move securewipe helpers above init --- auth.cpp | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/auth.cpp b/auth.cpp index 7f39976..317300a 100644 --- a/auth.cpp +++ b/auth.cpp @@ -147,6 +147,26 @@ std::atomic heavy_fail_streak{ 0 }; std::atomic module_baseline_ready{ false }; std::vector module_baseline; +static void secure_zero(std::string& value) +{ + if (value.empty()) + return; + SecureZeroMemory(value.data(), value.size()); + value.clear(); + value.shrink_to_fit(); +} + +static void securewipe(std::string& value) +{ + secure_zero(value); +} + +struct ScopeWipe { + std::string* value; + explicit ScopeWipe(std::string& v) : value(&v) {} + ~ScopeWipe() { securewipe(*value); } +}; + void KeyAuth::api::init() { // harden dll search order to reduce current-dir hijacks @@ -2044,26 +2064,6 @@ static bool file_exists(const std::wstring& path) return (attr != INVALID_FILE_ATTRIBUTES) && !(attr & FILE_ATTRIBUTE_DIRECTORY); } -static void secure_zero(std::string& value) -{ - if (value.empty()) - return; - SecureZeroMemory(value.data(), value.size()); - value.clear(); - value.shrink_to_fit(); -} - -static void securewipe(std::string& value) -{ - secure_zero(value); -} - -struct ScopeWipe { - std::string* value; - explicit ScopeWipe(std::string& v) : value(&v) {} - ~ScopeWipe() { securewipe(*value); } -}; - static std::wstring get_system_dir() { wchar_t buf[MAX_PATH] = {}; From c6f625ec7ad7c9f863e74752f600a9723a02b6bc Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 10:53:46 -0500 Subject: [PATCH 046/116] Update overhaul changelog --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 26190df..4a50527 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,25 @@ If you see security failures, common causes include: If you need to allow specific overlays or tools, add them to an allowlist in the code and rebuild the library. +## **Changelog (Overhaul Summary)** +This list summarizes all changes made in the overhaul: +1. **Security checks**: `.text` integrity, prologue snapshots, function region validation, detour detection. +2. **Hashing & headers**: `.text` slice hashing, PE header hash validation. +3. **Memory protections**: `.text` page protection checks and guard‑page detection. +4. **Module validation**: System32/SysWOW64 path checks, duplicate module detection, allowlist, new‑module detection. +5. **Module trust**: Microsoft signature verification for core DLLs, RWX section detection. +6. **Environment checks**: hypervisor detection and timing anomaly detection. +7. **Import checks**: import address validation; VirtualProtect IAT check (only when imported). +8. **Network hardening**: hosts‑file override detection for API host. +9. **Session hardening**: session heartbeat after successful login/license/upgrade/web login. +10. **DLL search order**: hardened DLL lookup and removed current‑dir hijacking. +11. **String exposure**: request data zeroized after use; sensitive parameters wiped via `ScopeWipe`. +12. **Debug logging**: minimized request/URL logging to reduce in‑memory exposure. +13. **Parsing hardening**: safer JSON parsing and substring handling to avoid crashes. +14. **Curl safety**: fixed cleanup issues; enforced static libcurl linkage. +15. **Module path APIs**: removed hardcoded System32 paths (uses `GetSystemDirectoryW`). +16. **Example/docs**: added usage section, security feature docs, and troubleshooting guidance. + Helpful references (copy and paste into your browser): ``` https://github.com/KeyAuth/KeyAuth-CPP-Example From 221877fc9e5e3982cba927291cf2b2f9327fff1f Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 11:05:25 -0500 Subject: [PATCH 047/116] Stream POST data to reduce copies --- auth.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/auth.cpp b/auth.cpp index 317300a..3280fea 100644 --- a/auth.cpp +++ b/auth.cpp @@ -327,6 +327,24 @@ size_t write_callback(void* contents, size_t size, size_t nmemb, void* userp) { return size * nmemb; } +struct PostData { + const char* data; + size_t len; + size_t pos; +}; + +size_t read_callback(char* buffer, size_t size, size_t nmemb, void* userp) { + auto* pd = static_cast(userp); + const size_t cap = size * nmemb; + const size_t remaining = (pd->pos < pd->len) ? (pd->len - pd->pos) : 0; + const size_t to_copy = remaining < cap ? remaining : cap; + if (to_copy > 0) { + std::memcpy(buffer, pd->data + pd->pos, to_copy); + pd->pos += to_copy; + } + return to_copy; +} + // Callback function to handle headers size_t header_callback(char* buffer, size_t size, size_t nitems, void* userdata) { size_t totalSize = size * nitems; @@ -2583,13 +2601,18 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { std::string to_return; std::string headers; + PostData post{ data.c_str(), data.size(), 0 }; + // Set CURL options curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L); curl_easy_setopt(curl, CURLOPT_NOPROXY, XorStr("keyauth.win").c_str()); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback); + curl_easy_setopt(curl, CURLOPT_READDATA, &post); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast(post.len)); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &to_return); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); From a940b9ecbec8ea2ee554cd3eb4b50c793879d995 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 11:09:10 -0500 Subject: [PATCH 048/116] Fix libcurl read callback usage --- auth.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/auth.cpp b/auth.cpp index 3280fea..3514ce1 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2610,9 +2610,10 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L); curl_easy_setopt(curl, CURLOPT_NOPROXY, XorStr("keyauth.win").c_str()); curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, nullptr); curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback); curl_easy_setopt(curl, CURLOPT_READDATA, &post); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast(post.len)); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, static_cast(post.len)); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &to_return); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); From da6caadb74315a55d0eeb1bc51863ff8c6928169 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 11:11:46 -0500 Subject: [PATCH 049/116] Add content-type header for read callback --- auth.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/auth.cpp b/auth.cpp index 3514ce1..3fd7d03 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2600,6 +2600,8 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { std::string to_return; std::string headers; + struct curl_slist* req_headers = nullptr; + req_headers = curl_slist_append(req_headers, "Content-Type: application/x-www-form-urlencoded"); PostData post{ data.c_str(), data.size(), 0 }; @@ -2618,11 +2620,13 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { curl_easy_setopt(curl, CURLOPT_WRITEDATA, &to_return); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); curl_easy_setopt(curl, CURLOPT_HEADERDATA, &headers); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, req_headers); // Perform the request CURLcode code = curl_easy_perform(curl); if (code != CURLE_OK) { std::string errorMsg = "CURL Error: " + std::string(curl_easy_strerror(code)); + if (req_headers) curl_slist_free_all(req_headers); curl_easy_cleanup(curl); error(errorMsg); } @@ -2630,6 +2634,7 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { if (KeyAuth::api::debug) { debugInfo("n/a", "n/a", to_return, "n/a"); } + if (req_headers) curl_slist_free_all(req_headers); curl_easy_cleanup(curl); secure_zero(data); return to_return; From bbad2346e813a937ea2c31316e909db6d739be0a Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 11:12:57 -0500 Subject: [PATCH 050/116] Improve libcurl POST headers --- auth.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/auth.cpp b/auth.cpp index 3fd7d03..bec4e55 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2602,6 +2602,8 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { std::string headers; struct curl_slist* req_headers = nullptr; req_headers = curl_slist_append(req_headers, "Content-Type: application/x-www-form-urlencoded"); + req_headers = curl_slist_append(req_headers, "Accept: */*"); + req_headers = curl_slist_append(req_headers, "Expect:"); PostData post{ data.c_str(), data.size(), 0 }; @@ -2616,11 +2618,13 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback); curl_easy_setopt(curl, CURLOPT_READDATA, &post); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, static_cast(post.len)); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast(post.len)); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &to_return); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); curl_easy_setopt(curl, CURLOPT_HEADERDATA, &headers); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, req_headers); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "KeyAuth"); // Perform the request CURLcode code = curl_easy_perform(curl); From c9fad4f15e5343f833cfd67fa7c546bb8cd831f5 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 11:15:49 -0500 Subject: [PATCH 051/116] Fallback to POSTFIELDS if headers missing --- auth.cpp | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/auth.cpp b/auth.cpp index bec4e55..08545c5 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2631,10 +2631,40 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { if (code != CURLE_OK) { std::string errorMsg = "CURL Error: " + std::string(curl_easy_strerror(code)); if (req_headers) curl_slist_free_all(req_headers); - curl_easy_cleanup(curl); + curl_easy_cleanup(curl); error(errorMsg); } + // Fallback: if signature headers are missing, retry using POSTFIELDS + if (signature.empty() || signatureTimestamp.empty()) { + curl_easy_cleanup(curl); + curl = curl_easy_init(); + if (!curl) { + if (req_headers) curl_slist_free_all(req_headers); + error(XorStr("CURL Initialization Failed!")); + } + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); + curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L); + curl_easy_setopt(curl, CURLOPT_NOPROXY, XorStr("keyauth.win").c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast(data.size())); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &to_return); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, &headers); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, req_headers); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "KeyAuth"); + code = curl_easy_perform(curl); + if (code != CURLE_OK) { + std::string errorMsg = "CURL Error: " + std::string(curl_easy_strerror(code)); + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(errorMsg); + } + } + if (KeyAuth::api::debug) { debugInfo("n/a", "n/a", to_return, "n/a"); } From 2cf67f84d031f8a1e1b90b08ef098e39710e41a3 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 11:25:52 -0500 Subject: [PATCH 052/116] Format secure wipe helpers --- auth.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/auth.cpp b/auth.cpp index 08545c5..6b7a793 100644 --- a/auth.cpp +++ b/auth.cpp @@ -147,24 +147,24 @@ std::atomic heavy_fail_streak{ 0 }; std::atomic module_baseline_ready{ false }; std::vector module_baseline; -static void secure_zero(std::string& value) +static inline void secure_zero(std::string& value) noexcept { - if (value.empty()) - return; - SecureZeroMemory(value.data(), value.size()); - value.clear(); - value.shrink_to_fit(); + if (!value.empty()) { + SecureZeroMemory(value.data(), value.size()); + value.clear(); + value.shrink_to_fit(); + } } -static void securewipe(std::string& value) +static inline void securewipe(std::string& value) noexcept { secure_zero(value); } -struct ScopeWipe { +struct ScopeWipe final { std::string* value; - explicit ScopeWipe(std::string& v) : value(&v) {} - ~ScopeWipe() { securewipe(*value); } + explicit ScopeWipe(std::string& v) noexcept : value(&v) {} + ~ScopeWipe() noexcept { securewipe(*value); } }; void KeyAuth::api::init() From df69c2bfd19bca21a2ccad7b289c9fcad10abced Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 11:29:02 -0500 Subject: [PATCH 053/116] Revert to POSTFIELDS request body --- auth.cpp | 58 ++------------------------------------------------------ 1 file changed, 2 insertions(+), 56 deletions(-) diff --git a/auth.cpp b/auth.cpp index 6b7a793..2ae2e46 100644 --- a/auth.cpp +++ b/auth.cpp @@ -327,24 +327,6 @@ size_t write_callback(void* contents, size_t size, size_t nmemb, void* userp) { return size * nmemb; } -struct PostData { - const char* data; - size_t len; - size_t pos; -}; - -size_t read_callback(char* buffer, size_t size, size_t nmemb, void* userp) { - auto* pd = static_cast(userp); - const size_t cap = size * nmemb; - const size_t remaining = (pd->pos < pd->len) ? (pd->len - pd->pos) : 0; - const size_t to_copy = remaining < cap ? remaining : cap; - if (to_copy > 0) { - std::memcpy(buffer, pd->data + pd->pos, to_copy); - pd->pos += to_copy; - } - return to_copy; -} - // Callback function to handle headers size_t header_callback(char* buffer, size_t size, size_t nitems, void* userdata) { size_t totalSize = size * nitems; @@ -2605,20 +2587,14 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { req_headers = curl_slist_append(req_headers, "Accept: */*"); req_headers = curl_slist_append(req_headers, "Expect:"); - PostData post{ data.c_str(), data.size(), 0 }; - // Set CURL options curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L); curl_easy_setopt(curl, CURLOPT_NOPROXY, XorStr("keyauth.win").c_str()); - curl_easy_setopt(curl, CURLOPT_POST, 1L); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, nullptr); - curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback); - curl_easy_setopt(curl, CURLOPT_READDATA, &post); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, static_cast(post.len)); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast(post.len)); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast(data.size())); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &to_return); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); @@ -2635,36 +2611,6 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { error(errorMsg); } - // Fallback: if signature headers are missing, retry using POSTFIELDS - if (signature.empty() || signatureTimestamp.empty()) { - curl_easy_cleanup(curl); - curl = curl_easy_init(); - if (!curl) { - if (req_headers) curl_slist_free_all(req_headers); - error(XorStr("CURL Initialization Failed!")); - } - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); - curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L); - curl_easy_setopt(curl, CURLOPT_NOPROXY, XorStr("keyauth.win").c_str()); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast(data.size())); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &to_return); - curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); - curl_easy_setopt(curl, CURLOPT_HEADERDATA, &headers); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, req_headers); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "KeyAuth"); - code = curl_easy_perform(curl); - if (code != CURLE_OK) { - std::string errorMsg = "CURL Error: " + std::string(curl_easy_strerror(code)); - if (req_headers) curl_slist_free_all(req_headers); - curl_easy_cleanup(curl); - error(errorMsg); - } - } - if (KeyAuth::api::debug) { debugInfo("n/a", "n/a", to_return, "n/a"); } From cdfb15f0b66616806195ff33e710d9cddb68e2b9 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 11:31:03 -0500 Subject: [PATCH 054/116] Remove secure_zero redeclaration --- auth.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/auth.cpp b/auth.cpp index 2ae2e46..7f2cfd6 100644 --- a/auth.cpp +++ b/auth.cpp @@ -116,7 +116,6 @@ bool module_allowlist_ok(); void snapshot_module_baseline(); bool new_modules_present(); bool text_guard_pages_present(); -static void secure_zero(std::string& value); std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; From bff03e6904ec67cf33952d84ba9100d541033a27 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 11:32:10 -0500 Subject: [PATCH 055/116] Add inline forward decls for wipe helpers --- auth.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/auth.cpp b/auth.cpp index 7f2cfd6..e8a52fb 100644 --- a/auth.cpp +++ b/auth.cpp @@ -116,6 +116,8 @@ bool module_allowlist_ok(); void snapshot_module_baseline(); bool new_modules_present(); bool text_guard_pages_present(); +inline void secure_zero(std::string& value) noexcept; +inline void securewipe(std::string& value) noexcept; std::string seed; void cleanUpSeedData(const std::string& seed); std::string signature; From ee73713eeae3c03098a4b156a5cab5e97a6e7b9c Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 11:34:19 -0500 Subject: [PATCH 056/116] Harden header parsing for signature --- auth.cpp | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/auth.cpp b/auth.cpp index e8a52fb..ef4cbc7 100644 --- a/auth.cpp +++ b/auth.cpp @@ -333,23 +333,27 @@ size_t header_callback(char* buffer, size_t size, size_t nitems, void* userdata) size_t totalSize = size * nitems; std::string header(buffer, totalSize); - - // Convert to lowercase for comparison - std::string lowercase = header; - std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), ::tolower); - - // Signature - if (lowercase.find("x-signature-ed25519: ") == 0) { - signature = header.substr(header.find(": ") + 2); - signature.erase(signature.find_last_not_of("\r\n") + 1); - //std::cout << "[DEBUG] Captured signature header: " << signature << std::endl; - } - - // Timestamp - if (lowercase.find("x-signature-timestamp: ") == 0) { - signatureTimestamp = header.substr(header.find(": ") + 2); - signatureTimestamp.erase(signatureTimestamp.find_last_not_of("\r\n") + 1); - //std::cout << "[DEBUG] Captured timestamp header: " << signatureTimestamp << std::endl; + if (header.empty()) + return totalSize; + // trim CRLF + while (!header.empty() && (header.back() == '\r' || header.back() == '\n')) { + header.pop_back(); + } + const auto colon = header.find(':'); + if (colon == std::string::npos) + return totalSize; + std::string key = header.substr(0, colon); + std::string value = header.substr(colon + 1); + while (!value.empty() && (value.front() == ' ' || value.front() == '\t')) { + value.erase(value.begin()); + } + std::transform(key.begin(), key.end(), key.begin(), ::tolower); + + if (key == "x-signature-ed25519") { + signature = value; + } + if (key == "x-signature-timestamp") { + signatureTimestamp = value; } return totalSize; From cf5165f2945d810c8fce76b63b839f523e76080d Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 11:40:55 -0500 Subject: [PATCH 057/116] Let libcurl infer POST length --- auth.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/auth.cpp b/auth.cpp index ef4cbc7..ef95792 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2599,7 +2599,6 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L); curl_easy_setopt(curl, CURLOPT_NOPROXY, XorStr("keyauth.win").c_str()); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast(data.size())); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &to_return); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); From ec9729abdc3e5c7c45b74031e7ac29a280bb8504 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 11:52:41 -0500 Subject: [PATCH 058/116] Remove VM/IAT/PE/new-module checks from enforcement --- auth.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/auth.cpp b/auth.cpp index ef95792..0b756c5 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2964,7 +2964,7 @@ void checkInit() { const auto last_mod = last_module_check.load(); if (now - last_mod > 60) { last_module_check.store(now); - if (!module_paths_ok() || duplicate_system_modules_present() || user_writable_module_present() || !core_modules_signed() || hypervisor_present() || !module_allowlist_ok()) { + if (!module_paths_ok() || duplicate_system_modules_present() || user_writable_module_present() || !core_modules_signed() || !module_allowlist_ok()) { error(XorStr("module path check failed, possible side-load detected.")); } } @@ -2975,13 +2975,10 @@ void checkInit() { error(XorStr("timing anomaly detected, possible time tamper.")); } const bool heavy_ok = - iat_virtualprotect_ok() && text_hashes_ok() && text_page_protections_ok() && - pe_header_ok() && import_addresses_ok() && !text_guard_pages_present() && - !new_modules_present() && !detour_suspect(reinterpret_cast(&VerifyPayload)) && !detour_suspect(reinterpret_cast(&checkInit)) && !detour_suspect(reinterpret_cast(&error)) && From 8a7c3bd11697d4c5e0c83e98a04b8acf8593c264 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 11:58:31 -0500 Subject: [PATCH 059/116] Remove legacy VM/PE/IAT/module baseline globals --- auth.cpp | 171 +------------------------------------------------------ 1 file changed, 1 insertion(+), 170 deletions(-) diff --git a/auth.cpp b/auth.cpp index 0b756c5..55b27ab 100644 --- a/auth.cpp +++ b/auth.cpp @@ -94,7 +94,6 @@ bool duplicate_system_modules_present(); bool user_writable_module_present(); bool module_has_rwx_section(HMODULE mod); bool core_modules_signed(); -bool hypervisor_present(); static std::wstring get_system_dir(); static std::wstring get_syswow_dir(); void snapshot_prologues(); @@ -109,13 +108,8 @@ bool detour_suspect(const uint8_t* p); bool import_addresses_ok(); void snapshot_text_page_protections(); bool text_page_protections_ok(); -void snapshot_pe_header(); -bool pe_header_ok(); -bool iat_virtualprotect_ok(); + bool module_allowlist_ok(); -void snapshot_module_baseline(); -bool new_modules_present(); -bool text_guard_pages_present(); inline void secure_zero(std::string& value) noexcept; inline void securewipe(std::string& value) noexcept; std::string seed; @@ -142,11 +136,7 @@ struct TextHash { size_t offset; size_t len; uint32_t hash; }; std::vector text_hashes; std::atomic text_prot_ready{ false }; std::vector> text_protections; -std::atomic pe_header_ready{ false }; -uint32_t pe_header_hash = 0; std::atomic heavy_fail_streak{ 0 }; -std::atomic module_baseline_ready{ false }; -std::vector module_baseline; static inline void secure_zero(std::string& value) noexcept { @@ -2085,41 +2075,6 @@ static std::wstring get_syswow_dir() return std::wstring(buf); } -bool hypervisor_present() -{ - int cpu_info[4] = {}; - __cpuid(cpu_info, 1); - const bool hv_bit = (cpu_info[2] & (1 << 31)) != 0; - if (hv_bit) { - return true; - } - - // registry artifacts (conservative) - if (reg_key_exists(HKEY_LOCAL_MACHINE, L"HARDWARE\\ACPI\\DSDT\\VBOX__") || - reg_key_exists(HKEY_LOCAL_MACHINE, L"HARDWARE\\ACPI\\DSDT\\VMWARE") || - reg_key_exists(HKEY_LOCAL_MACHINE, L"HARDWARE\\ACPI\\DSDT\\XEN") || - reg_key_exists(HKEY_LOCAL_MACHINE, L"SOFTWARE\\VMware, Inc.\\VMware Tools") || - reg_key_exists(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Oracle\\VirtualBox Guest Additions")) { - return true; - } - - // file artifacts (drivers/tools) - const auto sys32 = get_system_dir(); - if (!sys32.empty()) { - if (file_exists(sys32 + L"\\drivers\\VBoxGuest.sys") || - file_exists(sys32 + L"\\drivers\\VBoxMouse.sys") || - file_exists(sys32 + L"\\drivers\\VBoxSF.sys") || - file_exists(sys32 + L"\\drivers\\VBoxVideo.sys") || - file_exists(sys32 + L"\\drivers\\vmhgfs.sys") || - file_exists(sys32 + L"\\drivers\\vmmouse.sys") || - file_exists(sys32 + L"\\drivers\\vm3dmp.sys") || - file_exists(sys32 + L"\\drivers\\xen.sys")) { - return true; - } - } - - return false; -} void snapshot_prologues() { @@ -2138,8 +2093,6 @@ void snapshot_prologues() prologues_ready.store(true); snapshot_text_hashes(); snapshot_text_page_protections(); - snapshot_pe_header(); - snapshot_module_baseline(); } bool prologues_ok() @@ -2296,109 +2249,6 @@ bool text_page_protections_ok() return true; } -bool text_guard_pages_present() -{ - std::uintptr_t base = 0; - size_t size = 0; - if (!get_text_section_info(base, size)) - return false; - const size_t page = 0x1000; - for (size_t off = 0; off < size; off += page) { - MEMORY_BASIC_INFORMATION mbi{}; - if (VirtualQuery(reinterpret_cast(base + off), &mbi, sizeof(mbi)) == 0) - continue; - if (mbi.Protect & PAGE_GUARD) - return true; - } - return false; -} - -void snapshot_module_baseline() -{ - if (module_baseline_ready.load()) - return; - HMODULE mods[1024] = {}; - DWORD needed = 0; - if (!EnumProcessModules(GetCurrentProcess(), mods, sizeof(mods), &needed)) - return; - const size_t count = needed / sizeof(HMODULE); - module_baseline.clear(); - for (size_t i = 0; i < count; ++i) { - wchar_t path[MAX_PATH] = {}; - if (!GetModuleFileNameExW(GetCurrentProcess(), mods[i], path, MAX_PATH)) - continue; - module_baseline.push_back(to_lower_ws(path)); - } - module_baseline_ready.store(true); -} - -bool new_modules_present() -{ - if (!module_baseline_ready.load()) - return false; - HMODULE mods[1024] = {}; - DWORD needed = 0; - if (!EnumProcessModules(GetCurrentProcess(), mods, sizeof(mods), &needed)) - return false; - const size_t count = needed / sizeof(HMODULE); - for (size_t i = 0; i < count; ++i) { - wchar_t path[MAX_PATH] = {}; - if (!GetModuleFileNameExW(GetCurrentProcess(), mods[i], path, MAX_PATH)) - continue; - const auto p = to_lower_ws(path); - if (std::find(module_baseline.begin(), module_baseline.end(), p) == module_baseline.end()) - return true; - } - return false; -} - -void snapshot_pe_header() -{ - if (pe_header_ready.load()) - return; - const auto hmodule = GetModuleHandle(nullptr); - if (!hmodule) - return; - const auto base = reinterpret_cast(hmodule); - const auto dos = reinterpret_cast(base); - if (dos->e_magic != IMAGE_DOS_SIGNATURE) - return; - const auto nt = reinterpret_cast(base + dos->e_lfanew); - if (nt->Signature != IMAGE_NT_SIGNATURE) - return; - const size_t header_size = nt->OptionalHeader.SizeOfHeaders; - if (header_size == 0 || header_size > 0x4000) - return; - pe_header_hash = fnv1a(reinterpret_cast(base), header_size); - pe_header_ready.store(true); -} - -bool pe_header_ok() -{ - if (!pe_header_ready.load()) - return true; - const auto hmodule = GetModuleHandle(nullptr); - if (!hmodule) - return true; - const auto base = reinterpret_cast(hmodule); - const auto dos = reinterpret_cast(base); - if (dos->e_magic != IMAGE_DOS_SIGNATURE) - return false; - const auto nt = reinterpret_cast(base + dos->e_lfanew); - if (nt->Signature != IMAGE_NT_SIGNATURE) - return false; - const size_t header_size = nt->OptionalHeader.SizeOfHeaders; - if (header_size == 0 || header_size > 0x4000) - return false; - return fnv1a(reinterpret_cast(base), header_size) == pe_header_hash; -} - -struct EarlyChecks { - EarlyChecks() { - snapshot_prologues(); - } -}; -static EarlyChecks g_early_checks; bool detour_suspect(const uint8_t* p) { @@ -2485,24 +2335,6 @@ static bool iat_get_import_address(HMODULE module, const char* import_name, void return true; } -bool iat_virtualprotect_ok() -{ - HMODULE self = GetModuleHandle(nullptr); - FARPROC vp = GetProcAddress(GetModuleHandleW(L"kernelbase.dll"), "VirtualProtect"); - if (!vp) - vp = GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "VirtualProtect"); - if (!vp) - return true; - bool found = false; - void* iat_addr = nullptr; - const bool ok = iat_get_import_address(self, "VirtualProtect", iat_addr, found); - if (!ok || !found) - return true; // allow when not imported - if (addr_in_module(iat_addr, L"kernelbase.dll") || addr_in_module(iat_addr, L"kernel32.dll")) - return true; - return false; -} - bool module_allowlist_ok() { HMODULE mods[1024] = {}; @@ -2978,7 +2810,6 @@ void checkInit() { text_hashes_ok() && text_page_protections_ok() && import_addresses_ok() && - !text_guard_pages_present() && !detour_suspect(reinterpret_cast(&VerifyPayload)) && !detour_suspect(reinterpret_cast(&checkInit)) && !detour_suspect(reinterpret_cast(&error)) && From f04ac1bd63ae0d317f50cbc068986a6c9ea038ca Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 12:07:06 -0500 Subject: [PATCH 060/116] Remove module allowlist check --- auth.cpp | 34 +--------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/auth.cpp b/auth.cpp index 55b27ab..f823ab5 100644 --- a/auth.cpp +++ b/auth.cpp @@ -109,7 +109,6 @@ bool import_addresses_ok(); void snapshot_text_page_protections(); bool text_page_protections_ok(); -bool module_allowlist_ok(); inline void secure_zero(std::string& value) noexcept; inline void securewipe(std::string& value) noexcept; std::string seed; @@ -2335,37 +2334,6 @@ static bool iat_get_import_address(HMODULE module, const char* import_name, void return true; } -bool module_allowlist_ok() -{ - HMODULE mods[1024] = {}; - DWORD needed = 0; - if (!EnumProcessModules(GetCurrentProcess(), mods, sizeof(mods), &needed)) - return false; - wchar_t exe_path[MAX_PATH] = {}; - GetModuleFileNameW(nullptr, exe_path, MAX_PATH); - std::wstring exe_dir = exe_path; - const auto last_slash = exe_dir.find_last_of(L"\\/"); - if (last_slash != std::wstring::npos) - exe_dir = exe_dir.substr(0, last_slash + 1); - exe_dir = to_lower_ws(exe_dir); - std::wstring sys32 = get_system_dir(); - std::wstring syswow = get_syswow_dir(); - if (!sys32.empty()) sys32 = to_lower_ws(sys32 + L"\\"); - if (!syswow.empty()) syswow = to_lower_ws(syswow + L"\\"); - - const size_t count = needed / sizeof(HMODULE); - for (size_t i = 0; i < count; ++i) { - wchar_t path[MAX_PATH] = {}; - if (!GetModuleFileNameExW(GetCurrentProcess(), mods[i], path, MAX_PATH)) - continue; - std::wstring p = to_lower_ws(path); - if (p.rfind(sys32, 0) == 0 || p.rfind(syswow, 0) == 0 || p.rfind(exe_dir, 0) == 0) - continue; - return false; - } - return true; -} - void heartbeat_thread(KeyAuth::api* instance) { std::random_device rd; @@ -2796,7 +2764,7 @@ void checkInit() { const auto last_mod = last_module_check.load(); if (now - last_mod > 60) { last_module_check.store(now); - if (!module_paths_ok() || duplicate_system_modules_present() || user_writable_module_present() || !core_modules_signed() || !module_allowlist_ok()) { + if (!module_paths_ok() || duplicate_system_modules_present() || user_writable_module_present() || !core_modules_signed()) { error(XorStr("module path check failed, possible side-load detected.")); } } From 88192975dda974f5e47260b0008f31e9c92c0597 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 12:09:42 -0500 Subject: [PATCH 061/116] Limit module checks to core signatures --- auth.cpp | 93 +------------------------------------------------------- 1 file changed, 1 insertion(+), 92 deletions(-) diff --git a/auth.cpp b/auth.cpp index f823ab5..ef81995 100644 --- a/auth.cpp +++ b/auth.cpp @@ -89,9 +89,6 @@ auto check_section_integrity( const char *section_name, bool fix ) -> bool; void integrity_check(); std::string extract_host(const std::string& url); bool hosts_override_present(const std::string& host); -bool module_paths_ok(); -bool duplicate_system_modules_present(); -bool user_writable_module_present(); bool module_has_rwx_section(HMODULE mod); bool core_modules_signed(); static std::wstring get_system_dir(); @@ -1953,94 +1950,6 @@ bool core_modules_signed() return true; } -bool module_paths_ok() -{ - const wchar_t* kModules[] = { L"ntdll.dll", L"kernel32.dll", L"kernelbase.dll", L"user32.dll" }; - std::wstring sys32 = get_system_dir(); - std::wstring syswow = get_syswow_dir(); - if (!sys32.empty()) sys32 = to_lower_ws(sys32 + L"\\"); - if (!syswow.empty()) syswow = to_lower_ws(syswow + L"\\"); - - for (const auto* name : kModules) { - HMODULE mod = GetModuleHandleW(name); - if (!mod) - continue; - wchar_t path[MAX_PATH] = {}; - if (!GetModuleFileNameW(mod, path, MAX_PATH)) - return false; - std::wstring p = to_lower_ws(path); - if (p.rfind(sys32, 0) != 0 && p.rfind(syswow, 0) != 0) - return false; - } - return true; -} - -bool duplicate_system_modules_present() -{ - const wchar_t* kModules[] = { L"ntdll.dll", L"kernel32.dll", L"kernelbase.dll", L"user32.dll" }; - std::wstring sys32 = get_system_dir(); - std::wstring syswow = get_syswow_dir(); - if (!sys32.empty()) sys32 = to_lower_ws(sys32 + L"\\"); - if (!syswow.empty()) syswow = to_lower_ws(syswow + L"\\"); - - HMODULE mods[1024] = {}; - DWORD needed = 0; - if (!EnumProcessModules(GetCurrentProcess(), mods, sizeof(mods), &needed)) - return false; - - const size_t count = needed / sizeof(HMODULE); - for (size_t i = 0; i < count; ++i) { - wchar_t path[MAX_PATH] = {}; - if (!GetModuleFileNameExW(GetCurrentProcess(), mods[i], path, MAX_PATH)) - continue; - std::wstring p = to_lower_ws(path); - const auto name_pos = p.find_last_of(L"\\/"); - const std::wstring name = (name_pos == std::wstring::npos) ? p : p.substr(name_pos + 1); - bool is_target = false; - for (const auto* modname : kModules) { - if (name == modname) { - is_target = true; - break; - } - } - if (!is_target) - continue; - if (p.rfind(sys32, 0) != 0 && p.rfind(syswow, 0) != 0) - return true; - } - return false; -} - -bool user_writable_module_present() -{ - HMODULE mods[1024] = {}; - DWORD needed = 0; - if (!EnumProcessModules(GetCurrentProcess(), mods, sizeof(mods), &needed)) - return false; - - wchar_t exe_path[MAX_PATH] = {}; - GetModuleFileNameW(nullptr, exe_path, MAX_PATH); - std::wstring exe_dir = exe_path; - const auto last_slash = exe_dir.find_last_of(L"\\/"); - if (last_slash != std::wstring::npos) - exe_dir = exe_dir.substr(0, last_slash + 1); - exe_dir = to_lower_ws(exe_dir); - - const size_t count = needed / sizeof(HMODULE); - for (size_t i = 0; i < count; ++i) { - wchar_t path[MAX_PATH] = {}; - if (!GetModuleFileNameExW(GetCurrentProcess(), mods[i], path, MAX_PATH)) - continue; - std::wstring p = to_lower_ws(path); - if (p.rfind(exe_dir, 0) == 0) - continue; - - if (path_has_any(p, { L"\\temp\\", L"\\appdata\\local\\temp\\", L"\\downloads\\" })) - return true; - } - return false; -} - static bool reg_key_exists(HKEY root, const wchar_t* path) { HKEY h = nullptr; @@ -2764,7 +2673,7 @@ void checkInit() { const auto last_mod = last_module_check.load(); if (now - last_mod > 60) { last_module_check.store(now); - if (!module_paths_ok() || duplicate_system_modules_present() || user_writable_module_present() || !core_modules_signed()) { + if (!core_modules_signed()) { error(XorStr("module path check failed, possible side-load detected.")); } } From f057ff392b2f055db1e7023b3d45b69d0c3dd983 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 12:15:31 -0500 Subject: [PATCH 062/116] Remove duplicate heartbeat starts --- auth.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/auth.cpp b/auth.cpp index ef81995..ce259f1 100644 --- a/auth.cpp +++ b/auth.cpp @@ -409,8 +409,6 @@ void KeyAuth::api::login(std::string username, std::string password, std::string LI_FN(GlobalAddAtomA)(ownerid.c_str()); LoggedIn.store(true); start_heartbeat(this); - start_heartbeat(this); - start_heartbeat(this); } else { LI_FN(exit)(12); From 4abd0a9876ee18c187342ece03b5630dee232414 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 12:17:14 -0500 Subject: [PATCH 063/116] Remove unnecessary curl headers --- auth.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/auth.cpp b/auth.cpp index ce259f1..c667d01 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2295,9 +2295,6 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { std::string to_return; std::string headers; struct curl_slist* req_headers = nullptr; - req_headers = curl_slist_append(req_headers, "Content-Type: application/x-www-form-urlencoded"); - req_headers = curl_slist_append(req_headers, "Accept: */*"); - req_headers = curl_slist_append(req_headers, "Expect:"); // Set CURL options curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); From d7f3f0b744c8da1aa3ceab6541fffb1721c6f4dd Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 12:22:51 -0500 Subject: [PATCH 064/116] Document security checks and optional hardening --- README.md | 57 ++++++++++++++++++++++++++++--------------------------- auth.cpp | 6 ++++++ 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 4a50527..8bac192 100644 --- a/README.md +++ b/README.md @@ -66,12 +66,11 @@ Notes: The library ships with security checks enabled by default. You do not need to manually call anything beyond `init()` and a normal login/license call. What runs automatically: -1. **Integrity checks** (prologue, region, section hash, PE header, page protections). -2. **Module checks** (system module paths, signatures, RWX detection, user-writable paths, allowlist). +1. **Integrity checks** (prologue snapshots, function region validation, `.text` hashing, page protections). +2. **Module checks** (core module signature verification + RWX section detection). 3. **Hosts-file checks** for API host tampering. -4. **Hypervisor detection** (conservative, low false positives). -5. **Timing anomaly checks** to detect time tamper. -6. **Session heartbeat** after successful login/license/upgrade/web login. +4. **Timing anomaly checks** to detect time tamper. +5. **Session heartbeat** after successful login/license/upgrade/web login. How to keep security enabled: 1. Always call `KeyAuthApp.init()` once before any other API call. @@ -82,34 +81,36 @@ How to verify it is running: 1. Use the library normally — the checks are automatic. 2. If a check fails, the library will fail closed with an error message. +## **Optional Hardening Ideas (Not Enabled)** +These are intentionally **not** enabled in the library to avoid false positives, but you can add them if your app needs them. + +1. **PE header erase**: wipe PE header pages after load to make casual dumping harder. This is not a check; it simply reduces dump quality. +2. **Module allowlists**: require a strict set of loaded modules; this breaks overlays and many legitimate plugins. +3. **System module path checks**: enforce System32/SysWOW64-only paths; can fail on custom Windows installs. +4. **Hypervisor detection**: block VMs; useful for niche threat models but unfriendly to legit users. +5. **VirtualProtect IAT validation**: detect IAT hooks; can false-positive in some environments. + ## **Security Troubleshooting** If you see security failures, common causes include: -1. **DLL injection / overlays**: third‑party overlays or injectors can trip module allowlists. -2. **Modified system DLLs**: non‑Microsoft versions or patched DLLs will be rejected. -3. **Time tampering**: manual clock changes or large time skew can trigger timing checks. -4. **VMs/Hypervisors**: running inside a VM can trigger hypervisor detection. -5. **Patched binaries**: inline hooks/NOP patches or modified `.text` will fail integrity checks. - -If you need to allow specific overlays or tools, add them to an allowlist in the code and rebuild the library. +1. **Modified system DLLs**: non‑Microsoft versions or patched DLLs will be rejected. +2. **Time tampering**: manual clock changes or large time skew can trigger timing checks. +3. **Patched binaries**: inline hooks/NOP patches or modified `.text` will fail integrity checks. ## **Changelog (Overhaul Summary)** This list summarizes all changes made in the overhaul: -1. **Security checks**: `.text` integrity, prologue snapshots, function region validation, detour detection. -2. **Hashing & headers**: `.text` slice hashing, PE header hash validation. -3. **Memory protections**: `.text` page protection checks and guard‑page detection. -4. **Module validation**: System32/SysWOW64 path checks, duplicate module detection, allowlist, new‑module detection. -5. **Module trust**: Microsoft signature verification for core DLLs, RWX section detection. -6. **Environment checks**: hypervisor detection and timing anomaly detection. -7. **Import checks**: import address validation; VirtualProtect IAT check (only when imported). -8. **Network hardening**: hosts‑file override detection for API host. -9. **Session hardening**: session heartbeat after successful login/license/upgrade/web login. -10. **DLL search order**: hardened DLL lookup and removed current‑dir hijacking. -11. **String exposure**: request data zeroized after use; sensitive parameters wiped via `ScopeWipe`. -12. **Debug logging**: minimized request/URL logging to reduce in‑memory exposure. -13. **Parsing hardening**: safer JSON parsing and substring handling to avoid crashes. -14. **Curl safety**: fixed cleanup issues; enforced static libcurl linkage. -15. **Module path APIs**: removed hardcoded System32 paths (uses `GetSystemDirectoryW`). -16. **Example/docs**: added usage section, security feature docs, and troubleshooting guidance. +1. **Integrity checks**: prologue snapshots, function region validation, detour detection, `.text` slice hashing, page protections. +2. **Module trust**: Microsoft signature verification for core DLLs, RWX section detection. +3. **Timing checks**: timing anomaly detection to catch clock tamper. +4. **Import checks**: import address validation. +5. **Network hardening**: hosts‑file override detection for API host. +6. **Session hardening**: session heartbeat after successful login/license/upgrade/web login. +7. **DLL search order**: hardened DLL lookup and removed current‑dir hijacking. +8. **String exposure**: request data zeroized after use; sensitive parameters wiped via `ScopeWipe`. +9. **Debug logging**: minimized request/URL logging to reduce in‑memory exposure. +10. **Parsing hardening**: safer JSON parsing and substring handling to avoid crashes. +11. **Curl safety**: fixed cleanup issues; enforced static libcurl linkage. +12. **Module path APIs**: removed hardcoded System32 paths (uses `GetSystemDirectoryW`). +13. **Example/docs**: added usage section, security feature docs, and troubleshooting guidance. Helpful references (copy and paste into your browser): ``` diff --git a/auth.cpp b/auth.cpp index c667d01..f6f9e12 100644 --- a/auth.cpp +++ b/auth.cpp @@ -1932,6 +1932,7 @@ static bool verify_signature(const std::wstring& path) bool core_modules_signed() { + // verify core dll signatures and reject rwx sections -nigel const wchar_t* kModules[] = { L"ntdll.dll", L"kernel32.dll", L"kernelbase.dll", L"user32.dll" }; for (const auto* name : kModules) { HMODULE mod = GetModuleHandleW(name); @@ -2271,6 +2272,7 @@ void KeyAuth::api::setDebug(bool value) { std::string KeyAuth::api::req(std::string data, const std::string& url) { signature.clear(); signatureTimestamp.clear(); + // gate requests on integrity checks to reduce bypasses -nigel integrity_check(); if (!prologues_ok()) { error(XorStr("function prologue check failed, possible inline hook detected.")); @@ -2283,6 +2285,7 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { error(XorStr("function region check failed, possible hook detected.")); } const auto host = extract_host(url); + // block hosts-file redirects for api host -nigel if (hosts_override_present(host)) { error(XorStr("Hosts file override detected for API host.")); } @@ -2668,6 +2671,7 @@ void checkInit() { const auto last_mod = last_module_check.load(); if (now - last_mod > 60) { last_module_check.store(now); + // core module trust check to detect tampered system dlls -nigel if (!core_modules_signed()) { error(XorStr("module path check failed, possible side-load detected.")); } @@ -2675,9 +2679,11 @@ void checkInit() { const auto last_periodic = last_periodic_check.load(); if (now - last_periodic > 30) { last_periodic_check.store(now); + // detect basic clock tampering to block expired key reuse -nigel if (timing_anomaly_detected()) { error(XorStr("timing anomaly detected, possible time tamper.")); } + // periodic integrity sweep across code regions -nigel const bool heavy_ok = text_hashes_ok() && text_page_protections_ok() && From 3d19801c2fc289baf2486fbe30a6dc5472572d49 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 12:24:11 -0500 Subject: [PATCH 065/116] Add security overview documentation --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 8bac192..3903000 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,26 @@ What runs automatically: 4. **Timing anomaly checks** to detect time tamper. 5. **Session heartbeat** after successful login/license/upgrade/web login. +## **Security Overview** +This SDK includes lightweight, client-side defenses that raise the cost of common bypass techniques while keeping normal integrations simple. + +What it protects against: +1. **Inline patching/NOPs**: prologue snapshots and detour heuristics catch modified function entry points. +2. **Code tamper**: `.text` hashing and page‑protection checks detect modified code pages. +3. **API redirection**: hosts‑file checks flag local DNS overrides of the API host. +4. **Time spoofing**: timing anomaly checks reduce abuse of expired keys by system clock changes. +5. **Tampered system DLLs**: core module signature checks reject patched or unsigned system libraries. + +Benefits: +1. **Fail‑closed behavior**: when a check fails, requests are blocked before the API call. +2. **Low integration cost**: no additional calls are required beyond `init()` and a normal login/license flow. +3. **Reduced false positives**: checks are limited to core modules and conservative tamper signals. + +Design notes: +1. These are **client‑side** protections. They complement — not replace — server‑side session validation. +2. If you modify or strip checks, you reduce protection. Keep the SDK updated to inherit fixes. +3. Optional hardening ideas are listed below for advanced users who accept higher false‑positive risk. + How to keep security enabled: 1. Always call `KeyAuthApp.init()` once before any other API call. 2. Do not remove the built-in checks or tamper with the library internals. From 99dba44db5dc11d5e22cd33dbd2bf3d9e00677b4 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 12:28:52 -0500 Subject: [PATCH 066/116] Generalize IAT validation doc --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3903000..2725a60 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ These are intentionally **not** enabled in the library to avoid false positives, 2. **Module allowlists**: require a strict set of loaded modules; this breaks overlays and many legitimate plugins. 3. **System module path checks**: enforce System32/SysWOW64-only paths; can fail on custom Windows installs. 4. **Hypervisor detection**: block VMs; useful for niche threat models but unfriendly to legit users. -5. **VirtualProtect IAT validation**: detect IAT hooks; can false-positive in some environments. +5. **IAT validation**: detect import-table hooks for any imported API; can false-positive in some environments. ## **Security Troubleshooting** If you see security failures, common causes include: From d6eecb95f0f7ef91f978a0c511037470a7a107e5 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 12:36:55 -0500 Subject: [PATCH 067/116] Tidy helpful references formatting --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2725a60..2d21b46 100644 --- a/README.md +++ b/README.md @@ -132,12 +132,10 @@ This list summarizes all changes made in the overhaul: 12. **Module path APIs**: removed hardcoded System32 paths (uses `GetSystemDirectoryW`). 13. **Example/docs**: added usage section, security feature docs, and troubleshooting guidance. -Helpful references (copy and paste into your browser): -``` -https://github.com/KeyAuth/KeyAuth-CPP-Example -https://keyauth.cc/app/ -https://keyauth.cc/app/?page=forms -``` +Helpful references: +- https://github.com/KeyAuth/KeyAuth-CPP-Example +- https://keyauth.cc/app/ +- https://keyauth.cc/app/?page=forms ## **What is KeyAuth?** From 92c8bda956819015fb10915838da8263f14248b2 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 14:52:06 -0500 Subject: [PATCH 068/116] Add usage notes for security checks --- auth.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/auth.cpp b/auth.cpp index f6f9e12..23d435d 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2274,6 +2274,7 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { signatureTimestamp.clear(); // gate requests on integrity checks to reduce bypasses -nigel integrity_check(); + // usage: keep this in req() so every api call is protected -nigel if (!prologues_ok()) { error(XorStr("function prologue check failed, possible inline hook detected.")); } @@ -2666,6 +2667,7 @@ void checkInit() { if (!initialized) { error(XorStr("You need to run the KeyAuthApp.init(); function before any other KeyAuth functions")); } + // usage: call init() once at startup; checks run automatically after that -nigel const auto now = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count(); const auto last_mod = last_module_check.load(); From 2206463abe1478903b008f7e482df7826cd2044c Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 22:21:09 -0500 Subject: [PATCH 069/116] Add GitHub Actions build workflow --- .github/workflows/ci.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ebcbf24 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: + push: + pull_request: + +jobs: + build-windows: + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: setup msvc + uses: ilammy/msvc-dev-cmd@v1 + + - name: build x64 release + shell: powershell + run: | + if (Test-Path "library.sln") { + msbuild "library.sln" /m /p:Configuration=Release /p:Platform=x64 + } elseif (Test-Path "*.sln") { + $sln = Get-ChildItem -Filter *.sln | Select-Object -First 1 + msbuild $sln.FullName /m /p:Configuration=Release /p:Platform=x64 + } else { + Write-Error "no solution (.sln) found to build" + } From 722fc6aaa7fd3a4bfaefb7c455602420d641528f Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 22:25:33 -0500 Subject: [PATCH 070/116] Improve input handling and expiry helpers --- auth.cpp | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- auth.hpp | 38 +++++++++++++++++++++++--------------- 2 files changed, 78 insertions(+), 16 deletions(-) diff --git a/auth.cpp b/auth.cpp index 23d435d..f3a0306 100644 --- a/auth.cpp +++ b/auth.cpp @@ -1720,6 +1720,45 @@ void KeyAuth::api::logout() { load_response_data(json); } +std::string KeyAuth::api::expiry_remaining(const std::string& expiry) +{ + if (expiry.empty()) + return "unknown"; + long long exp = 0; + try { + exp = std::stoll(expiry); + } + catch (...) { + return "unknown"; + } + const long long now = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count(); + long long diff = exp - now; + if (diff <= 0) + return "expired"; + + const long long days = diff / 86400; + const long long weeks = days / 7; + const long long months = days / 30; + const long long hours = (diff % 86400) / 3600; + const long long minutes = (diff % 3600) / 60; + + std::time_t tt = static_cast(exp); + std::tm tm{}; +#ifdef _WIN32 + localtime_s(&tm, &tt); +#else + tm = *std::localtime(&tt); +#endif + char buf[32] = {}; + std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm); + + std::ostringstream out; + out << days << "d " << hours << "h " << minutes << "m remaining" + << " (expires " << buf << ", ~" << weeks << "w / " << months << "mo)"; + return out.str(); +} + int VerifyPayload(std::string signature, std::string timestamp, std::string body) { if (!prologues_ok()) { @@ -2039,14 +2078,26 @@ bool timing_anomaly_detected() const auto steady_now = std::chrono::steady_clock::now(); static auto wall_last = wall_now; static auto steady_last = steady_now; + static ULONGLONG tick_last = GetTickCount64(); + static long long wall_last_sec = std::chrono::duration_cast( + wall_now.time_since_epoch()).count(); const auto wall_delta = std::chrono::duration_cast(wall_now - wall_last).count(); const auto steady_delta = std::chrono::duration_cast(steady_now - steady_last).count(); wall_last = wall_now; steady_last = steady_now; + const ULONGLONG tick_now = GetTickCount64(); + const long long tick_delta = static_cast((tick_now - tick_last) / 1000ULL); + tick_last = tick_now; + const long long wall_now_sec = std::chrono::duration_cast( + wall_now.time_since_epoch()).count(); + const long long wall_tick_delta = wall_now_sec - wall_last_sec; + wall_last_sec = wall_now_sec; if (wall_delta < -60 || wall_delta > 300) return true; if (std::llabs(wall_delta - steady_delta) > 120) return true; + if (std::llabs(wall_tick_delta - tick_delta) > 120) + return true; return false; } @@ -2578,7 +2629,10 @@ void KeyAuth::api::debugInfo(std::string data, std::string url, std::string resp std::istream_iterator()); for (auto const& value : results) { - datas[value.substr(0, value.find('='))] = value.substr(value.find('=') + 1); + const auto pos = value.find('='); + if (pos == std::string::npos) + continue; + datas[value.substr(0, pos)] = value.substr(pos + 1); } RedactField(datas, "sessionid"); diff --git a/auth.hpp b/auth.hpp index e54f14e..1b06ae0 100644 --- a/auth.hpp +++ b/auth.hpp @@ -47,6 +47,7 @@ namespace KeyAuth { void fetchstats(); void forgot(std::string username, std::string email); void logout(); + static std::string expiry_remaining(const std::string& expiry); class subscriptions_class { public: @@ -113,22 +114,27 @@ namespace KeyAuth { void load_user_data(nlohmann::json data) { - api::user_data.username = data[XorStr("username")]; - api::user_data.ip = data[XorStr("ip")]; - if (data[XorStr("hwid")].is_null()) { + api::user_data.username = data.value(XorStr("username"), ""); + api::user_data.ip = data.value(XorStr("ip"), ""); + if (!data.contains(XorStr("hwid")) || data[XorStr("hwid")].is_null()) { api::user_data.hwid = XorStr("none"); } else { api::user_data.hwid = data[XorStr("hwid")]; } - api::user_data.createdate = data[XorStr("createdate")]; - api::user_data.lastlogin = data[XorStr("lastlogin")]; - - for (size_t i = 0; i < data[XorStr("subscriptions")].size(); i++) { // Prompto#7895 & stars#2297 was here - subscriptions_class subscriptions; - subscriptions.name = data[XorStr("subscriptions")][i][XorStr("subscription")]; - subscriptions.expiry = data[XorStr("subscriptions")][i][XorStr("expiry")]; - api::user_data.subscriptions.emplace_back(subscriptions); + api::user_data.createdate = data.value(XorStr("createdate"), ""); + api::user_data.lastlogin = data.value(XorStr("lastlogin"), ""); + + api::user_data.subscriptions.clear(); + if (data.contains(XorStr("subscriptions")) && data[XorStr("subscriptions")].is_array()) { + for (const auto& sub : data[XorStr("subscriptions")]) { + subscriptions_class subscriptions; + if (sub.contains(XorStr("subscription"))) + subscriptions.name = sub.value(XorStr("subscription"), ""); + if (sub.contains(XorStr("expiry"))) + subscriptions.expiry = sub.value(XorStr("expiry"), ""); + api::user_data.subscriptions.emplace_back(subscriptions); + } } } @@ -157,10 +163,12 @@ namespace KeyAuth { return; // avoid invalid server payload crash. -nigel } for (const auto& sub : data["messages"]) { - - std::string authoroutput = sub[XorStr("author")]; - std::string messageoutput = sub["message"]; - int timestamp = sub[XorStr("timestamp")]; std::string timestampoutput = std::to_string(timestamp); + if (!sub.is_object()) + continue; + std::string authoroutput = sub.value(XorStr("author"), ""); + std::string messageoutput = sub.value("message", ""); + const int timestamp = sub.value(XorStr("timestamp"), 0); + std::string timestampoutput = std::to_string(timestamp); authoroutput.erase(remove(authoroutput.begin(), authoroutput.end(), '"'), authoroutput.end()); messageoutput.erase(remove(messageoutput.begin(), messageoutput.end(), '"'), messageoutput.end()); timestampoutput.erase(remove(timestampoutput.begin(), timestampoutput.end(), '"'), timestampoutput.end()); From 03765c35dd56c0380280d0ea2b06983dada0f77a Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 22:28:13 -0500 Subject: [PATCH 071/116] Fix json key access for xorstr --- auth.hpp | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/auth.hpp b/auth.hpp index 1b06ae0..d4a8f0a 100644 --- a/auth.hpp +++ b/auth.hpp @@ -114,25 +114,33 @@ namespace KeyAuth { void load_user_data(nlohmann::json data) { - api::user_data.username = data.value(XorStr("username"), ""); - api::user_data.ip = data.value(XorStr("ip"), ""); - if (!data.contains(XorStr("hwid")) || data[XorStr("hwid")].is_null()) { + const std::string key_username = XorStr("username"); + const std::string key_ip = XorStr("ip"); + const std::string key_hwid = XorStr("hwid"); + const std::string key_created = XorStr("createdate"); + const std::string key_lastlogin = XorStr("lastlogin"); + const std::string key_subs = XorStr("subscriptions"); + const std::string key_sub_name = XorStr("subscription"); + const std::string key_sub_expiry = XorStr("expiry"); + api::user_data.username = data.value(key_username, ""); + api::user_data.ip = data.value(key_ip, ""); + if (!data.contains(key_hwid) || data[key_hwid].is_null()) { api::user_data.hwid = XorStr("none"); } else { - api::user_data.hwid = data[XorStr("hwid")]; + api::user_data.hwid = data[key_hwid]; } - api::user_data.createdate = data.value(XorStr("createdate"), ""); - api::user_data.lastlogin = data.value(XorStr("lastlogin"), ""); + api::user_data.createdate = data.value(key_created, ""); + api::user_data.lastlogin = data.value(key_lastlogin, ""); api::user_data.subscriptions.clear(); - if (data.contains(XorStr("subscriptions")) && data[XorStr("subscriptions")].is_array()) { - for (const auto& sub : data[XorStr("subscriptions")]) { + if (data.contains(key_subs) && data[key_subs].is_array()) { + for (const auto& sub : data[key_subs]) { subscriptions_class subscriptions; - if (sub.contains(XorStr("subscription"))) - subscriptions.name = sub.value(XorStr("subscription"), ""); - if (sub.contains(XorStr("expiry"))) - subscriptions.expiry = sub.value(XorStr("expiry"), ""); + if (sub.contains(key_sub_name)) + subscriptions.name = sub.value(key_sub_name, ""); + if (sub.contains(key_sub_expiry)) + subscriptions.expiry = sub.value(key_sub_expiry, ""); api::user_data.subscriptions.emplace_back(subscriptions); } } @@ -162,12 +170,14 @@ namespace KeyAuth { if (!data.contains("messages") || !data["messages"].is_array()) { return; // avoid invalid server payload crash. -nigel } + const std::string key_author = XorStr("author"); + const std::string key_timestamp = XorStr("timestamp"); for (const auto& sub : data["messages"]) { if (!sub.is_object()) continue; - std::string authoroutput = sub.value(XorStr("author"), ""); + std::string authoroutput = sub.value(key_author, ""); std::string messageoutput = sub.value("message", ""); - const int timestamp = sub.value(XorStr("timestamp"), 0); + const int timestamp = sub.value(key_timestamp, 0); std::string timestampoutput = std::to_string(timestamp); authoroutput.erase(remove(authoroutput.begin(), authoroutput.end(), '"'), authoroutput.end()); messageoutput.erase(remove(messageoutput.begin(), messageoutput.end(), '"'), messageoutput.end()); From 0860267d577ef44c0703dd4c253ebe17ed1eda99 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 22:54:40 -0500 Subject: [PATCH 072/116] Add default timing/save constants --- auth.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/auth.hpp b/auth.hpp index d4a8f0a..1587cc5 100644 --- a/auth.hpp +++ b/auth.hpp @@ -48,6 +48,10 @@ namespace KeyAuth { void forgot(std::string username, std::string email); void logout(); static std::string expiry_remaining(const std::string& expiry); + static constexpr const char* kSavePath = "test.json"; + static constexpr int kInitFailSleepMs = 1500; + static constexpr int kBadInputSleepMs = 3000; + static constexpr int kCloseSleepMs = 5000; class subscriptions_class { public: From 18e77a6b8bb860e7f79a58703a5846dd64b9003d Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 22:57:45 -0500 Subject: [PATCH 073/116] Add lockout helpers and delay utilities --- auth.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ auth.hpp | 12 ++++++++++++ 2 files changed, 60 insertions(+) diff --git a/auth.cpp b/auth.cpp index f3a0306..40932f6 100644 --- a/auth.cpp +++ b/auth.cpp @@ -1759,6 +1759,54 @@ std::string KeyAuth::api::expiry_remaining(const std::string& expiry) return out.str(); } +void KeyAuth::api::init_fail_delay() +{ + Sleep(kInitFailSleepMs); +} + +void KeyAuth::api::bad_input_delay() +{ + Sleep(kBadInputSleepMs); +} + +void KeyAuth::api::close_delay() +{ + Sleep(kCloseSleepMs); +} + +bool KeyAuth::api::lockout_active(const lockout_state& state) +{ + return std::chrono::steady_clock::now() < state.locked_until; +} + +int KeyAuth::api::lockout_remaining_ms(const lockout_state& state) +{ + if (!lockout_active(state)) + return 0; + const auto now = std::chrono::steady_clock::now(); + const auto remaining = std::chrono::duration_cast(state.locked_until - now).count(); + return remaining > 0 ? static_cast(remaining) : 0; +} + +void KeyAuth::api::record_login_fail(lockout_state& state, int max_attempts, int lock_seconds) +{ + if (max_attempts < 1) + max_attempts = 1; + if (lock_seconds < 1) + lock_seconds = 1; + state.fails += 1; + if (state.fails >= max_attempts) { + state.fails = 0; + state.locked_until = std::chrono::steady_clock::now() + std::chrono::seconds(lock_seconds); + } +} + +void KeyAuth::api::reset_lockout(lockout_state& state) +{ + state.fails = 0; + state.locked_until = std::chrono::steady_clock::time_point{}; +} + int VerifyPayload(std::string signature, std::string timestamp, std::string body) { if (!prologues_ok()) { diff --git a/auth.hpp b/auth.hpp index 1587cc5..f9d4393 100644 --- a/auth.hpp +++ b/auth.hpp @@ -1,6 +1,7 @@ #include #include #include +#include #define CURL_STATICLIB @@ -52,6 +53,17 @@ namespace KeyAuth { static constexpr int kInitFailSleepMs = 1500; static constexpr int kBadInputSleepMs = 3000; static constexpr int kCloseSleepMs = 5000; + struct lockout_state { + int fails = 0; + std::chrono::steady_clock::time_point locked_until{}; + }; + static void init_fail_delay(); + static void bad_input_delay(); + static void close_delay(); + static bool lockout_active(const lockout_state& state); + static int lockout_remaining_ms(const lockout_state& state); + static void record_login_fail(lockout_state& state, int max_attempts = 3, int lock_seconds = 30); + static void reset_lockout(lockout_state& state); class subscriptions_class { public: From 1aeb57afdcb32ef9b8fbbd61f95e44eab7142740 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 23:37:16 -0500 Subject: [PATCH 074/116] Harden api host resolution checks --- auth.cpp | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/auth.cpp b/auth.cpp index 40932f6..e81b358 100644 --- a/auth.cpp +++ b/auth.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include @@ -1938,10 +1939,90 @@ std::string extract_host(const std::string& url) return host; } +static bool is_ip_literal(const std::string& host) +{ + sockaddr_in sa4{}; + sockaddr_in6 sa6{}; + return inet_pton(AF_INET, host.c_str(), &sa4.sin_addr) == 1 || + inet_pton(AF_INET6, host.c_str(), &sa6.sin6_addr) == 1; +} + +static bool is_private_or_loopback_ipv4(uint32_t addr_net_order) +{ + const uint32_t a = ntohl(addr_net_order); + const uint8_t b1 = static_cast(a >> 24); + const uint8_t b2 = static_cast((a >> 16) & 0xFF); + if (b1 == 10) return true; + if (b1 == 127) return true; + if (b1 == 0) return true; + if (b1 == 169 && b2 == 254) return true; + if (b1 == 192 && b2 == 168) return true; + if (b1 == 172) { + const uint8_t b3 = static_cast((a >> 8) & 0xFF); + if (b3 >= 16 && b3 <= 31) return true; + } + return false; +} + +static bool is_loopback_ipv6(const in6_addr& addr) +{ + static const in6_addr loopback = IN6ADDR_LOOPBACK_INIT; + return std::memcmp(&addr, &loopback, sizeof(loopback)) == 0; +} + +static bool host_is_keyauth(const std::string& host_lower) +{ + if (host_lower == "keyauth.win" || host_lower == "keyauth.cc" || host_lower == "api-worker.keyauth.win") + return true; + const std::string suffix = ".keyauth.win"; + if (host_lower.size() > suffix.size() && + host_lower.compare(host_lower.size() - suffix.size(), suffix.size(), suffix) == 0) + return true; + return false; +} + +static bool host_resolves_private_only(const std::string& host, bool& has_public) +{ + has_public = false; + addrinfo hints{}; + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; + addrinfo* res = nullptr; + if (getaddrinfo(host.c_str(), nullptr, &hints, &res) != 0) + return false; + bool any = false; + bool all_private = true; + for (addrinfo* p = res; p; p = p->ai_next) { + if (!p->ai_addr) + continue; + any = true; + if (p->ai_family == AF_INET) { + const auto* sa = reinterpret_cast(p->ai_addr); + if (!is_private_or_loopback_ipv4(sa->sin_addr.s_addr)) { + all_private = false; + has_public = true; + } + } else if (p->ai_family == AF_INET6) { + const auto* sa = reinterpret_cast(p->ai_addr); + if (!is_loopback_ipv6(sa->sin6_addr)) { + all_private = false; + has_public = true; + } + } + } + freeaddrinfo(res); + if (!any) + return false; + return all_private; +} + bool hosts_override_present(const std::string& host) { if (host.empty()) return false; + std::string host_lower = host; + std::transform(host_lower.begin(), host_lower.end(), host_lower.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); const char* sysroot = std::getenv("SystemRoot"); std::string hosts_path = sysroot ? std::string(sysroot) : "C:\\Windows"; hosts_path += "\\System32\\drivers\\etc\\hosts"; @@ -1953,10 +2034,12 @@ bool hosts_override_present(const std::string& host) auto hash_pos = line.find('#'); if (hash_pos != std::string::npos) line = line.substr(0, hash_pos); - if (line.find(host) == std::string::npos) + std::transform(line.begin(), line.end(), line.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + if (line.find(host_lower) == std::string::npos) continue; // basic whole-word check - if (line.find(" " + host) != std::string::npos || line.find("\t" + host) != std::string::npos) + if (line.find(" " + host_lower) != std::string::npos || line.find("\t" + host_lower) != std::string::npos) return true; } return false; @@ -2389,6 +2472,21 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { if (hosts_override_present(host)) { error(XorStr("Hosts file override detected for API host.")); } + // block loopback/private redirects for keyauth domains -nigel + { + std::string host_lower = host; + std::transform(host_lower.begin(), host_lower.end(), host_lower.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + if (host_is_keyauth(host_lower)) { + if (is_ip_literal(host_lower)) { + error(XorStr("API host must not be an IP literal.")); + } + bool has_public = false; + if (host_resolves_private_only(host_lower, has_public) && !has_public) { + error(XorStr("API host resolves to private or loopback.")); + } + } + } CURL* curl = curl_easy_init(); if (!curl) { From 0806774dc6af09543eeb0fa31ca1c113b7c67b9d Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 23:37:58 -0500 Subject: [PATCH 075/116] Wipe host strings after checks --- auth.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/auth.cpp b/auth.cpp index e81b358..060f347 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2467,7 +2467,8 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { !func_region_ok(reinterpret_cast(&check_section_integrity))) { error(XorStr("function region check failed, possible hook detected.")); } - const auto host = extract_host(url); + std::string host = extract_host(url); + ScopeWipe host_wipe(host); // block hosts-file redirects for api host -nigel if (hosts_override_present(host)) { error(XorStr("Hosts file override detected for API host.")); @@ -2475,6 +2476,7 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { // block loopback/private redirects for keyauth domains -nigel { std::string host_lower = host; + ScopeWipe host_lower_wipe(host_lower); std::transform(host_lower.begin(), host_lower.end(), host_lower.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); if (host_is_keyauth(host_lower)) { From 76849d31f03f0919c62067010b312f134723e944 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 23:41:06 -0500 Subject: [PATCH 076/116] Add https/proxy guards for api host --- auth.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/auth.cpp b/auth.cpp index 060f347..3fb7955 100644 --- a/auth.cpp +++ b/auth.cpp @@ -1981,6 +1981,45 @@ static bool host_is_keyauth(const std::string& host_lower) return false; } +static bool is_https_url(const std::string& url) +{ + const std::string prefix = "https://"; + if (url.size() < prefix.size()) + return false; + for (size_t i = 0; i < prefix.size(); ++i) { + const char c = static_cast(std::tolower(static_cast(url[i]))); + if (c != prefix[i]) + return false; + } + return true; +} + +static bool proxy_env_set() +{ + const char* keys[] = { "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "http_proxy", "https_proxy", "all_proxy" }; + for (const char* k : keys) { + const char* v = std::getenv(k); + if (v && *v) + return true; + } + return false; +} + +static bool winhttp_proxy_set() +{ + WINHTTP_PROXY_INFO info{}; + if (!WinHttpGetDefaultProxyConfiguration(&info)) + return false; + bool set = false; + if (info.lpszProxy && *info.lpszProxy) + set = true; + if (info.lpszProxyBypass && *info.lpszProxyBypass) + set = true; + if (info.lpszProxy) GlobalFree(info.lpszProxy); + if (info.lpszProxyBypass) GlobalFree(info.lpszProxyBypass); + return set; +} + static bool host_resolves_private_only(const std::string& host, bool& has_public) { has_public = false; @@ -2467,6 +2506,9 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { !func_region_ok(reinterpret_cast(&check_section_integrity))) { error(XorStr("function region check failed, possible hook detected.")); } + if (!is_https_url(url)) { + error(XorStr("API URL must use HTTPS.")); + } std::string host = extract_host(url); ScopeWipe host_wipe(host); // block hosts-file redirects for api host -nigel @@ -2483,6 +2525,9 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { if (is_ip_literal(host_lower)) { error(XorStr("API host must not be an IP literal.")); } + if (proxy_env_set() || winhttp_proxy_set()) { + error(XorStr("Proxy settings detected for API host.")); + } bool has_public = false; if (host_resolves_private_only(host_lower, has_public) && !has_public) { error(XorStr("API host resolves to private or loopback.")); From 1adec9cb50c03c10918aaca2b81b7e84cd068bdd Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sun, 1 Mar 2026 23:43:12 -0500 Subject: [PATCH 077/116] Include winhttp for proxy checks --- auth.cpp | 1 + library.vcxproj | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/auth.cpp b/auth.cpp index 3fb7955..6c13a81 100644 --- a/auth.cpp +++ b/auth.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include diff --git a/library.vcxproj b/library.vcxproj index 884ee9e..17661dc 100644 --- a/library.vcxproj +++ b/library.vcxproj @@ -160,7 +160,7 @@ MachineX64 - libsodium.lib;libcurl.lib%(AdditionalDependencies) + libsodium.lib;libcurl.lib;winhttp.lib;%(AdditionalDependencies) .\curl;.\libsodium;%(AdditionalLibraryDirectories) /NODEFAULTLIB:libcurl.lib %(AdditionalOptions) From cf78f0c89dccd5321dbf8fd243e087e5f84e1165 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Mon, 2 Mar 2026 08:16:21 -0500 Subject: [PATCH 078/116] Trigger example CI on library updates --- .github/workflows/sync-example.yml | 24 ++++++++++++++++++++++++ README.md | 4 ++++ 2 files changed, 28 insertions(+) create mode 100644 .github/workflows/sync-example.yml diff --git a/.github/workflows/sync-example.yml b/.github/workflows/sync-example.yml new file mode 100644 index 0000000..3184cde --- /dev/null +++ b/.github/workflows/sync-example.yml @@ -0,0 +1,24 @@ +name: sync-example + +on: + push: + branches: [ main ] + +jobs: + trigger-example-ci: + runs-on: ubuntu-latest + steps: + - name: dispatch example workflow + env: + EXAMPLE_REPO: ELF-Nigel/KeyAuth-CPP-Example + EXAMPLE_TOKEN: ${{ secrets.EXAMPLE_REPO_TOKEN }} + run: | + if [ -z "$EXAMPLE_TOKEN" ]; then + echo "EXAMPLE_REPO_TOKEN is not set" >&2 + exit 1 + fi + curl -s -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $EXAMPLE_TOKEN" \ + https://api.github.com/repos/$EXAMPLE_REPO/dispatches \ + -d '{"event_type":"sync-library"}' diff --git a/README.md b/README.md index 2d21b46..edb077a 100644 --- a/README.md +++ b/README.md @@ -161,3 +161,7 @@ of the licensor in the software. Any use of the licensor’s trademarks is subje to applicable law. Thank you for your compliance, we work hard on the development of KeyAuth and do not appreciate our copyright being infringed. + +## Example Sync +On push to `main`, this repo can trigger the example repo CI via a repository dispatch event. +Set `EXAMPLE_REPO_TOKEN` in this repo secrets (PAT with access to `ELF-Nigel/KeyAuth-CPP-Example`). From 8e1d6b5a8685d37f2093d783c9d3b6de912962b4 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Mon, 2 Mar 2026 08:19:36 -0500 Subject: [PATCH 079/116] Remove example dispatch workflow --- .github/workflows/sync-example.yml | 24 ------------------------ README.md | 4 ---- 2 files changed, 28 deletions(-) delete mode 100644 .github/workflows/sync-example.yml diff --git a/.github/workflows/sync-example.yml b/.github/workflows/sync-example.yml deleted file mode 100644 index 3184cde..0000000 --- a/.github/workflows/sync-example.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: sync-example - -on: - push: - branches: [ main ] - -jobs: - trigger-example-ci: - runs-on: ubuntu-latest - steps: - - name: dispatch example workflow - env: - EXAMPLE_REPO: ELF-Nigel/KeyAuth-CPP-Example - EXAMPLE_TOKEN: ${{ secrets.EXAMPLE_REPO_TOKEN }} - run: | - if [ -z "$EXAMPLE_TOKEN" ]; then - echo "EXAMPLE_REPO_TOKEN is not set" >&2 - exit 1 - fi - curl -s -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $EXAMPLE_TOKEN" \ - https://api.github.com/repos/$EXAMPLE_REPO/dispatches \ - -d '{"event_type":"sync-library"}' diff --git a/README.md b/README.md index edb077a..2d21b46 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,3 @@ of the licensor in the software. Any use of the licensor’s trademarks is subje to applicable law. Thank you for your compliance, we work hard on the development of KeyAuth and do not appreciate our copyright being infringed. - -## Example Sync -On push to `main`, this repo can trigger the example repo CI via a repository dispatch event. -Set `EXAMPLE_REPO_TOKEN` in this repo secrets (PAT with access to `ELF-Nigel/KeyAuth-CPP-Example`). From 8934712bb1733cb1bd5824b743d0986406427215 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Mon, 2 Mar 2026 08:25:48 -0500 Subject: [PATCH 080/116] Add safe network hardening controls --- auth.cpp | 37 +++++++++++++++++++++++++++++++++++++ auth.hpp | 13 ++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/auth.cpp b/auth.cpp index 6c13a81..48dc53a 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2522,6 +2522,28 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { ScopeWipe host_lower_wipe(host_lower); std::transform(host_lower.begin(), host_lower.end(), host_lower.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); + if (!allowed_hosts.empty()) { + bool allowed = false; + for (const auto& entry : allowed_hosts) { + std::string entry_lower = entry; + std::transform(entry_lower.begin(), entry_lower.end(), entry_lower.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + if (entry_lower.rfind("*.", 0) == 0) { + auto suffix = entry_lower.substr(1); + if (host_lower.size() >= suffix.size() && + host_lower.compare(host_lower.size() - suffix.size(), suffix.size(), suffix) == 0) { + allowed = true; + break; + } + } else if (host_lower == entry_lower) { + allowed = true; + break; + } + } + if (!allowed) { + error(XorStr("API host is not in allowed host list.")); + } + } if (host_is_keyauth(host_lower)) { if (is_ip_literal(host_lower)) { error(XorStr("API host must not be an IP literal.")); @@ -2549,6 +2571,10 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 0L); + curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS); + curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS); curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L); curl_easy_setopt(curl, CURLOPT_NOPROXY, XorStr("keyauth.win").c_str()); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); @@ -2559,6 +2585,17 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { curl_easy_setopt(curl, CURLOPT_HTTPHEADER, req_headers); curl_easy_setopt(curl, CURLOPT_USERAGENT, "KeyAuth"); + if (!pinned_public_keys.empty()) { +#ifdef CURLOPT_PINNEDPUBLICKEY + if (pinned_public_keys.size() > 1) { + error(XorStr("Multiple pinned public keys not supported.")); + } + curl_easy_setopt(curl, CURLOPT_PINNEDPUBLICKEY, pinned_public_keys.at(0).c_str()); +#else + error(XorStr("Pinned public key not supported by this libcurl build.")); +#endif + } + // Perform the request CURLcode code = curl_easy_perform(curl); if (code != CURLE_OK) { diff --git a/auth.hpp b/auth.hpp index f9d4393..b683d4f 100644 --- a/auth.hpp +++ b/auth.hpp @@ -121,10 +121,21 @@ namespace KeyAuth { appdata app_data; responsedata response; Tfa tfa; + + // Optional network hardening controls (do not require backend changes). + void set_allowed_hosts(const std::vector& hosts) { allowed_hosts = hosts; } + void add_allowed_host(const std::string& host) { allowed_hosts.push_back(host); } + void clear_allowed_hosts() { allowed_hosts.clear(); } + + void set_pinned_public_keys(const std::vector& pins) { pinned_public_keys = pins; } + void add_pinned_public_key(const std::string& pin) { pinned_public_keys.push_back(pin); } + void clear_pinned_public_keys() { pinned_public_keys.clear(); } private: std::string sessionid, enckey; - static std::string req(std::string data, const std::string& url); + std::vector allowed_hosts; + std::vector pinned_public_keys; + std::string req(std::string data, const std::string& url); static void debugInfo(std::string data, std::string url, std::string response, std::string headers); static void setDebug(bool value); From 2ba18714183b807fb0e2e5237d9f13040e848607 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Mon, 2 Mar 2026 08:31:18 -0500 Subject: [PATCH 081/116] Check .data/.rdata page protections --- auth.cpp | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/auth.cpp b/auth.cpp index 48dc53a..c8ff5b5 100644 --- a/auth.cpp +++ b/auth.cpp @@ -107,6 +107,8 @@ bool detour_suspect(const uint8_t* p); bool import_addresses_ok(); void snapshot_text_page_protections(); bool text_page_protections_ok(); +void snapshot_data_page_protections(); +bool data_page_protections_ok(); inline void secure_zero(std::string& value) noexcept; inline void securewipe(std::string& value) noexcept; @@ -134,6 +136,8 @@ struct TextHash { size_t offset; size_t len; uint32_t hash; }; std::vector text_hashes; std::atomic text_prot_ready{ false }; std::vector> text_protections; +std::atomic data_prot_ready{ false }; +std::vector> data_protections; std::atomic heavy_fail_streak{ 0 }; static inline void secure_zero(std::string& value) noexcept @@ -2210,6 +2214,7 @@ void snapshot_prologues() prologues_ready.store(true); snapshot_text_hashes(); snapshot_text_page_protections(); + snapshot_data_page_protections(); } bool prologues_ok() @@ -2292,6 +2297,46 @@ static bool get_text_section_info(std::uintptr_t& base, size_t& size) return false; } +static bool get_data_section_info(std::uintptr_t& base, size_t& size) +{ + const auto hmodule = GetModuleHandle(nullptr); + if (!hmodule) return false; + const auto base_0 = reinterpret_cast(hmodule); + const auto dos = reinterpret_cast(base_0); + if (dos->e_magic != IMAGE_DOS_SIGNATURE) return false; + const auto nt = reinterpret_cast(base_0 + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) return false; + auto section = IMAGE_FIRST_SECTION(nt); + for (auto i = 0; i < nt->FileHeader.NumberOfSections; ++i, ++section) { + if (std::memcmp(section->Name, ".data", 5) == 0) { + base = base_0 + section->VirtualAddress; + size = section->Misc.VirtualSize; + return true; + } + } + return false; +} + +static bool get_rdata_section_info(std::uintptr_t& base, size_t& size) +{ + const auto hmodule = GetModuleHandle(nullptr); + if (!hmodule) return false; + const auto base_0 = reinterpret_cast(hmodule); + const auto dos = reinterpret_cast(base_0); + if (dos->e_magic != IMAGE_DOS_SIGNATURE) return false; + const auto nt = reinterpret_cast(base_0 + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) return false; + auto section = IMAGE_FIRST_SECTION(nt); + for (auto i = 0; i < nt->FileHeader.NumberOfSections; ++i, ++section) { + if (std::memcmp(section->Name, ".rdata", 6) == 0) { + base = base_0 + section->VirtualAddress; + size = section->Misc.VirtualSize; + return true; + } + } + return false; +} + static uint32_t fnv1a(const uint8_t* data, size_t len) { uint32_t hash = 2166136261u; @@ -2359,6 +2404,38 @@ void snapshot_text_page_protections() text_prot_ready.store(true); } +void snapshot_data_page_protections() +{ + if (data_prot_ready.load()) + return; + data_protections.clear(); + const size_t page = 0x1000; + + std::uintptr_t base = 0; + size_t size = 0; + if (get_data_section_info(base, size)) { + for (size_t off = 0; off < size; off += page) { + MEMORY_BASIC_INFORMATION mbi{}; + if (VirtualQuery(reinterpret_cast(base + off), &mbi, sizeof(mbi)) == 0) + continue; + data_protections.emplace_back(reinterpret_cast(mbi.BaseAddress), mbi.Protect); + } + } + + base = 0; + size = 0; + if (get_rdata_section_info(base, size)) { + for (size_t off = 0; off < size; off += page) { + MEMORY_BASIC_INFORMATION mbi{}; + if (VirtualQuery(reinterpret_cast(base + off), &mbi, sizeof(mbi)) == 0) + continue; + data_protections.emplace_back(reinterpret_cast(mbi.BaseAddress), mbi.Protect); + } + } + + data_prot_ready.store(true); +} + bool text_page_protections_ok() { if (!text_prot_ready.load()) @@ -2378,6 +2455,24 @@ bool text_page_protections_ok() return true; } +bool data_page_protections_ok() +{ + if (!data_prot_ready.load()) + return true; + for (const auto& entry : data_protections) { + MEMORY_BASIC_INFORMATION mbi{}; + if (VirtualQuery(reinterpret_cast(entry.first), &mbi, sizeof(mbi)) == 0) + return false; + const DWORD prot = mbi.Protect; + if (prot != entry.second) + return false; + const bool exec = (prot & PAGE_EXECUTE) || (prot & PAGE_EXECUTE_READ) || (prot & PAGE_EXECUTE_READWRITE) || (prot & PAGE_EXECUTE_WRITECOPY); + if (exec) + return false; + } + return true; +} + bool detour_suspect(const uint8_t* p) { @@ -2974,6 +3069,7 @@ void checkInit() { const bool heavy_ok = text_hashes_ok() && text_page_protections_ok() && + data_page_protections_ok() && import_addresses_ok() && !detour_suspect(reinterpret_cast(&VerifyPayload)) && !detour_suspect(reinterpret_cast(&checkInit)) && From 50406687f2edc3f7cefbea4e47a9fad7d02748eb Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Mon, 2 Mar 2026 08:32:31 -0500 Subject: [PATCH 082/116] Add changelog for network and integrity hardening --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..23110ef --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## Unreleased +- Network hardening (client-side only): HTTPS-only transport, redirects disabled, and optional host allowlist + public key pinning to reduce the risk of traffic redirection or man-in-the-middle interception. +- Integrity checks: `.text` integrity and page-protection checks, plus non-executable page checks for `.data` and `.rdata` to help detect tampering (transparent, no stealth behavior). + +## Notes +- These protections are defensive and transparent; they do not alter the backend or API and are intended to reduce common redirection and tampering risks. From 412b9996ae12aeac3620b86a8ae614dfccb27b53 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Mon, 2 Mar 2026 18:03:18 -0500 Subject: [PATCH 083/116] Add threaded ban monitor helper --- README.md | 14 ++++++++++++++ auth.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ auth.hpp | 11 +++++++++++ 3 files changed, 75 insertions(+) diff --git a/README.md b/README.md index 2d21b46..aea9940 100644 --- a/README.md +++ b/README.md @@ -161,3 +161,17 @@ of the licensor in the software. Any use of the licensor’s trademarks is subje to applicable law. Thank you for your compliance, we work hard on the development of KeyAuth and do not appreciate our copyright being infringed. + +## Live ban monitor (threaded) + +Optional background check that polls every 45 seconds. Always stop it before exiting. + +```cpp +KeyAuthApp.start_ban_monitor(45, false, [] { + std::cout << "Blacklisted, exiting..." << std::endl; + exit(0); +}); + +// later, before exit +KeyAuthApp.stop_ban_monitor(); +``` diff --git a/auth.cpp b/auth.cpp index c8ff5b5..07bb1cc 100644 --- a/auth.cpp +++ b/auth.cpp @@ -1726,6 +1726,56 @@ void KeyAuth::api::logout() { load_response_data(json); } +void KeyAuth::api::start_ban_monitor(int interval_seconds, bool check_session, std::function on_ban) +{ + if (ban_monitor_running_) { + return; + } + + if (interval_seconds < 1) { + interval_seconds = 1; + } + + ban_monitor_detected_ = false; + ban_monitor_running_ = true; + ban_monitor_thread_ = std::thread([this, interval_seconds, check_session, on_ban]() { + while (ban_monitor_running_) { + if (check_session) { + this->check(false); + } + + if (this->checkblack()) { + ban_monitor_detected_ = true; + ban_monitor_running_ = false; + if (on_ban) { + on_ban(); + } + return; + } + + std::this_thread::sleep_for(std::chrono::seconds(interval_seconds)); + } + }); +} + +void KeyAuth::api::stop_ban_monitor() +{ + ban_monitor_running_ = false; + if (ban_monitor_thread_.joinable()) { + ban_monitor_thread_.join(); + } +} + +bool KeyAuth::api::ban_monitor_running() const +{ + return ban_monitor_running_.load(); +} + +bool KeyAuth::api::ban_monitor_detected() const +{ + return ban_monitor_detected_.load(); +} + std::string KeyAuth::api::expiry_remaining(const std::string& expiry) { if (expiry.empty()) diff --git a/auth.hpp b/auth.hpp index b683d4f..6e40730 100644 --- a/auth.hpp +++ b/auth.hpp @@ -2,6 +2,9 @@ #include #include #include +#include +#include +#include #define CURL_STATICLIB @@ -48,6 +51,10 @@ namespace KeyAuth { void fetchstats(); void forgot(std::string username, std::string email); void logout(); + void start_ban_monitor(int interval_seconds = 45, bool check_session = false, std::function on_ban = {}); + void stop_ban_monitor(); + bool ban_monitor_running() const; + bool ban_monitor_detected() const; static std::string expiry_remaining(const std::string& expiry); static constexpr const char* kSavePath = "test.json"; static constexpr int kInitFailSleepMs = 1500; @@ -214,6 +221,10 @@ namespace KeyAuth { } } + std::atomic ban_monitor_running_{ false }; + std::atomic ban_monitor_detected_{ false }; + std::thread ban_monitor_thread_; + nlohmann::json response_decoder; }; From cd96db53ed43fae72bd4a57d7b106d6261628e08 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 10:19:40 -0500 Subject: [PATCH 084/116] remove github workflows --- .github/workflows/ci.yml | 27 --------------------------- .github/workflows/pr_notification.yml | 12 ------------ 2 files changed, 39 deletions(-) delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/pr_notification.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index ebcbf24..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: ci - -on: - push: - pull_request: - -jobs: - build-windows: - runs-on: windows-latest - steps: - - name: checkout - uses: actions/checkout@v4 - - - name: setup msvc - uses: ilammy/msvc-dev-cmd@v1 - - - name: build x64 release - shell: powershell - run: | - if (Test-Path "library.sln") { - msbuild "library.sln" /m /p:Configuration=Release /p:Platform=x64 - } elseif (Test-Path "*.sln") { - $sln = Get-ChildItem -Filter *.sln | Select-Object -First 1 - msbuild $sln.FullName /m /p:Configuration=Release /p:Platform=x64 - } else { - Write-Error "no solution (.sln) found to build" - } diff --git a/.github/workflows/pr_notification.yml b/.github/workflows/pr_notification.yml deleted file mode 100644 index de44c8b..0000000 --- a/.github/workflows/pr_notification.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Pull Request Notification - -on: - pull_request_target: - types: - - opened - -jobs: - notify: - uses: KeyAuth/.github/.github/workflows/pr_notification_global.yml@main - secrets: - DISCORD_PR: ${{ secrets.DISCORD_PR }} From dd2b9c237d16079aabb5268f49da0f771f4c791f Mon Sep 17 00:00:00 2001 From: "[ELF] Nigel" <254270454+ELF-Nigel@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:26:57 -0500 Subject: [PATCH 085/116] Update auth.hpp add private fields --- auth.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/auth.hpp b/auth.hpp index 6e40730..ef22c0b 100644 --- a/auth.hpp +++ b/auth.hpp @@ -55,6 +55,10 @@ namespace KeyAuth { void stop_ban_monitor(); bool ban_monitor_running() const; bool ban_monitor_detected() const; + bool require_pinning = false; + bool block_proxy = false; + bool block_custom_ca = false; + bool block_private_dns = false; static std::string expiry_remaining(const std::string& expiry); static constexpr const char* kSavePath = "test.json"; static constexpr int kInitFailSleepMs = 1500; From 380c00ea888b017ca38b4f45580059644086901e Mon Sep 17 00:00:00 2001 From: "[ELF] Nigel" <254270454+ELF-Nigel@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:55:07 -0500 Subject: [PATCH 086/116] Update auth.cpp improved symlink/reparse check --- auth.cpp | 61 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/auth.cpp b/auth.cpp index 07bb1cc..89b3f8f 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2112,31 +2112,42 @@ static bool host_resolves_private_only(const std::string& host, bool& has_public bool hosts_override_present(const std::string& host) { - if (host.empty()) - return false; - std::string host_lower = host; - std::transform(host_lower.begin(), host_lower.end(), host_lower.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - const char* sysroot = std::getenv("SystemRoot"); - std::string hosts_path = sysroot ? std::string(sysroot) : "C:\\Windows"; - hosts_path += "\\System32\\drivers\\etc\\hosts"; - std::ifstream file(hosts_path); - if (!file.good()) - return false; - std::string line; - while (std::getline(file, line)) { - auto hash_pos = line.find('#'); - if (hash_pos != std::string::npos) - line = line.substr(0, hash_pos); - std::transform(line.begin(), line.end(), line.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - if (line.find(host_lower) == std::string::npos) - continue; - // basic whole-word check - if (line.find(" " + host_lower) != std::string::npos || line.find("\t" + host_lower) != std::string::npos) - return true; - } - return false; + if (host.empty()) + return false; + + std::string host_lower = host; + std::transform(host_lower.begin(), host_lower.end(), host_lower.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + + const char* sysroot = std::getenv("SystemRoot"); + std::string hosts_path = sysroot ? std::string(sysroot) : "C:\\Windows"; + hosts_path += "\\System32\\drivers\\etc\\hosts"; + + // this will block the symlink/reparse tricks and attrs + const DWORD attr = GetFileAttributesA(hosts_path.c_str()); + if (attr == INVALID_FILE_ATTRIBUTES) + return false; + if (attr & FILE_ATTRIBUTE_REPARSE_POINT) // symlink/junction + return true; + + std::ifstream file(hosts_path); + if (!file.good()) + return false; + + std::string line; + while (std::getline(file, line)) { + auto hash_pos = line.find('#'); + if (hash_pos != std::string::npos) + line = line.substr(0, hash_pos); + + std::transform(line.begin(), line.end(), line.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + + // word check example, this can always be improved + if (line.find(" " + host_lower) != std:string::npos || line.find("\t" + host_lower) != std::string::npos) + return true; + } + return false; } static std::wstring to_lower_ws(std::wstring value) From f05e9711d38b6516ff981b22abee430db722bb7e Mon Sep 17 00:00:00 2001 From: "[ELF] Nigel" <254270454+ELF-Nigel@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:58:27 -0500 Subject: [PATCH 087/116] added new includes --- auth.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/auth.cpp b/auth.cpp index 89b3f8f..7628802 100644 --- a/auth.cpp +++ b/auth.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -36,6 +37,7 @@ #pragma comment(lib, "httpapi.lib") #pragma comment(lib, "psapi.lib") #pragma comment(lib, "wintrust.lib") +#pragma comment(lib, "dnsapi.lib") #include #include From d3bae4348b249ef0c67105492eeba30e354a91bb Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 11:13:07 -0500 Subject: [PATCH 088/116] add dns cache poisoning check --- auth.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/auth.cpp b/auth.cpp index 7628802..d19e5c7 100644 --- a/auth.cpp +++ b/auth.cpp @@ -55,6 +55,7 @@ #include #include #include +#include #include #include @@ -2112,6 +2113,61 @@ static bool host_resolves_private_only(const std::string& host, bool& has_public return all_private; } +static void collect_ips_from_dns(PDNS_RECORD rec, std::vector& out) +{ + for (auto p = rec; p; p = p->pNext) { + if (p->wType == DNS_TYPE_A) { + char buf[INET_ADDRSTRLEN] = {}; + inet_ntop(AF_INET, &p->Data.A.IpAddress, buf, sizeof(buf)); + out.emplace_back(buf); + } else if (p->wType == DNS_TYPE_AAAA) { + char buf[INET6_ADDRSTRLEN] = {}; + inet_ntop(AF_INET6, &p->Data.AAAA.Ip6Address, buf, sizeof(buf)); + out.emplace_back(buf); + } + } +} + +static bool dns_cache_poisoned(const std::string& host) +{ + if (host.empty()) + return false; + + std::vector cached; + std::vector fresh; + + PDNS_RECORD rec_cached = nullptr; + PDNS_RECORD rec_fresh = nullptr; + + if (DnsQuery_A(host.c_str(), DNS_TYPE_A, DNS_QUERY_STANDARD, nullptr, &rec_cached, nullptr) == ERROR_SUCCESS) { + collect_ips_from_dns(rec_cached, cached); + DnsRecordListFree(rec_cached, DnsFreeRecordList); + } + if (DnsQuery_A(host.c_str(), DNS_TYPE_AAAA, DNS_QUERY_STANDARD, nullptr, &rec_cached, nullptr) == ERROR_SUCCESS) { + collect_ips_from_dns(rec_cached, cached); + DnsRecordListFree(rec_cached, DnsFreeRecordList); + } + + if (DnsQuery_A(host.c_str(), DNS_TYPE_A, DNS_QUERY_BYPASS_CACHE, nullptr, &rec_fresh, nullptr) == ERROR_SUCCESS) { + collect_ips_from_dns(rec_fresh, fresh); + DnsRecordListFree(rec_fresh, DnsFreeRecordList); + } + if (DnsQuery_A(host.c_str(), DNS_TYPE_AAAA, DNS_QUERY_BYPASS_CACHE, nullptr, &rec_fresh, nullptr) == ERROR_SUCCESS) { + collect_ips_from_dns(rec_fresh, fresh); + DnsRecordListFree(rec_fresh, DnsFreeRecordList); + } + + if (cached.empty() || fresh.empty()) + return false; + + std::sort(cached.begin(), cached.end()); + cached.erase(std::unique(cached.begin(), cached.end()), cached.end()); + std::sort(fresh.begin(), fresh.end()); + fresh.erase(std::unique(fresh.begin(), fresh.end()), fresh.end()); + + return cached != fresh; +} + bool hosts_override_present(const std::string& host) { if (host.empty()) @@ -2713,6 +2769,9 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { if (host_resolves_private_only(host_lower, has_public) && !has_public) { error(XorStr("API host resolves to private or loopback.")); } + if (dns_cache_poisoned(host_lower)) { + error(XorStr("DNS cache poisoning detected for API host.")); + } } } From efc03db632b9179ea31ade1ec42dc236fce04af1 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 11:29:23 -0500 Subject: [PATCH 089/116] harden api redirect protections --- auth.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/auth.cpp b/auth.cpp index d19e5c7..5058b68 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2028,6 +2028,21 @@ static bool is_loopback_ipv6(const in6_addr& addr) return std::memcmp(&addr, &loopback, sizeof(loopback)) == 0; } +static bool ip_string_private_or_loopback(const std::string& ip) +{ + if (ip.empty()) + return false; + sockaddr_in sa4{}; + if (inet_pton(AF_INET, ip.c_str(), &sa4.sin_addr) == 1) { + return is_private_or_loopback_ipv4(sa4.sin_addr.s_addr); + } + sockaddr_in6 sa6{}; + if (inet_pton(AF_INET6, ip.c_str(), &sa6.sin6_addr) == 1) { + return is_loopback_ipv6(sa6.sin6_addr); + } + return false; +} + static bool host_is_keyauth(const std::string& host_lower) { if (host_lower == "keyauth.win" || host_lower == "keyauth.cc" || host_lower == "api-worker.keyauth.win") @@ -2793,6 +2808,10 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS); curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS); curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L); + curl_easy_setopt(curl, CURLOPT_PROXY, ""); +#ifdef CURL_SSLVERSION_TLSv1_2 + curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); +#endif curl_easy_setopt(curl, CURLOPT_NOPROXY, XorStr("keyauth.win").c_str()); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); @@ -2822,6 +2841,44 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { error(errorMsg); } + long ssl_verify = 0; + if (curl_easy_getinfo(curl, CURLINFO_SSL_VERIFYRESULT, &ssl_verify) == CURLE_OK) { + if (ssl_verify != 0) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("SSL verify result failed.")); + } + } + + char* effective_url = nullptr; + if (curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effective_url) == CURLE_OK && effective_url) { + std::string eff_host = extract_host(effective_url); + std::string host_lower = host; + std::string eff_lower = eff_host; + std::transform(host_lower.begin(), host_lower.end(), host_lower.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + std::transform(eff_lower.begin(), eff_lower.end(), eff_lower.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + if (!eff_lower.empty() && eff_lower != host_lower) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("effective url host mismatch.")); + } + } + + char* primary_ip = nullptr; + if (curl_easy_getinfo(curl, CURLINFO_PRIMARY_IP, &primary_ip) == CURLE_OK && primary_ip) { + std::string ip = primary_ip; + std::string host_lower = host; + std::transform(host_lower.begin(), host_lower.end(), host_lower.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + if (host_is_keyauth(host_lower) && ip_string_private_or_loopback(ip)) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("api host resolved to private or loopback ip.")); + } + } + if (KeyAuth::api::debug) { debugInfo("n/a", "n/a", to_return, "n/a"); } From 3a7ccb57ba6529039f5a07ddcb0f406c1e80ca57 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 11:34:49 -0500 Subject: [PATCH 090/116] add more api redirect protections --- auth.cpp | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 7 deletions(-) diff --git a/auth.cpp b/auth.cpp index 5058b68..2d9e22e 100644 --- a/auth.cpp +++ b/auth.cpp @@ -69,8 +69,19 @@ #include #include +#if __has_include("Security.hpp") #include "Security.hpp" +#define KEYAUTH_HAVE_SECURITY 1 +#else +#define KEYAUTH_HAVE_SECURITY 0 +#endif + +#if __has_include("killEmulator.hpp") #include "killEmulator.hpp" +#define KEYAUTH_HAVE_KILLEMU 1 +#else +#define KEYAUTH_HAVE_KILLEMU 0 +#endif #include #include #include @@ -168,6 +179,9 @@ void KeyAuth::api::init() // harden dll search order to reduce current-dir hijacks SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_SEARCH_USER_DIRS); SetDllDirectoryW(L""); +#if KEYAUTH_HAVE_SECURITY + LockMemAccess(); +#endif { wchar_t exe_path[MAX_PATH] = {}; GetModuleFileNameW(nullptr, exe_path, MAX_PATH); @@ -2093,6 +2107,22 @@ static bool winhttp_proxy_set() return set; } +static bool winhttp_proxy_auto_set() +{ + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG cfg{}; + if (!WinHttpGetIEProxyConfigForCurrentUser(&cfg)) + return false; + bool set = false; + if (cfg.fAutoDetect) + set = true; + if (cfg.lpszAutoConfigUrl && *cfg.lpszAutoConfigUrl) + set = true; + if (cfg.lpszAutoConfigUrl) GlobalFree(cfg.lpszAutoConfigUrl); + if (cfg.lpszProxy) GlobalFree(cfg.lpszProxy); + if (cfg.lpszProxyBypass) GlobalFree(cfg.lpszProxyBypass); + return set; +} + static bool host_resolves_private_only(const std::string& host, bool& has_public) { has_public = false; @@ -2183,6 +2213,29 @@ static bool dns_cache_poisoned(const std::string& host) return cached != fresh; } +static bool dns_fresh_contains_ip(const std::string& host, const std::string& ip) +{ + if (host.empty() || ip.empty()) + return false; + std::vector fresh; + PDNS_RECORD rec_fresh = nullptr; + if (DnsQuery_A(host.c_str(), DNS_TYPE_A, DNS_QUERY_BYPASS_CACHE, nullptr, &rec_fresh, nullptr) == ERROR_SUCCESS) { + collect_ips_from_dns(rec_fresh, fresh); + DnsRecordListFree(rec_fresh, DnsFreeRecordList); + } + if (DnsQuery_A(host.c_str(), DNS_TYPE_AAAA, DNS_QUERY_BYPASS_CACHE, nullptr, &rec_fresh, nullptr) == ERROR_SUCCESS) { + collect_ips_from_dns(rec_fresh, fresh); + DnsRecordListFree(rec_fresh, DnsFreeRecordList); + } + if (fresh.empty()) + return false; + for (const auto& entry : fresh) { + if (_stricmp(entry.c_str(), ip.c_str()) == 0) + return true; + } + return false; +} + bool hosts_override_present(const std::string& host) { if (host.empty()) @@ -2773,13 +2826,13 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { error(XorStr("API host is not in allowed host list.")); } } - if (host_is_keyauth(host_lower)) { - if (is_ip_literal(host_lower)) { - error(XorStr("API host must not be an IP literal.")); - } - if (proxy_env_set() || winhttp_proxy_set()) { - error(XorStr("Proxy settings detected for API host.")); - } + if (host_is_keyauth(host_lower)) { + if (is_ip_literal(host_lower)) { + error(XorStr("API host must not be an IP literal.")); + } + if (proxy_env_set() || winhttp_proxy_set() || winhttp_proxy_auto_set()) { + error(XorStr("Proxy settings detected for API host.")); + } bool has_public = false; if (host_resolves_private_only(host_lower, has_public) && !has_public) { error(XorStr("API host resolves to private or loopback.")); @@ -2877,11 +2930,21 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { curl_easy_cleanup(curl); error(XorStr("api host resolved to private or loopback ip.")); } + if (host_is_keyauth(host_lower) && !dns_fresh_contains_ip(host_lower, ip)) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("api host ip mismatch vs fresh dns.")); + } } if (KeyAuth::api::debug) { debugInfo("n/a", "n/a", to_return, "n/a"); } + if (to_return.size() > (2 * 1024 * 1024)) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("response too large.")); + } if (req_headers) curl_slist_free_all(req_headers); curl_easy_cleanup(curl); secure_zero(data); @@ -3338,7 +3401,9 @@ void modify() while (true) { // new code by https://github.com/LiamG53 + #if KEYAUTH_HAVE_KILLEMU protection::init(); + #endif // ^ check for jumps, break points (maybe useless), return address. if ( check_section_integrity( XorStr( ".text" ).c_str( ), false ) ) From 2efd0a42fad0d8ffd11fe2c5445ebcbfaed0c144 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 12:02:10 -0500 Subject: [PATCH 091/116] tighten api network checks --- auth.cpp | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/auth.cpp b/auth.cpp index 2d9e22e..cb55868 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2028,11 +2028,15 @@ static bool is_private_or_loopback_ipv4(uint32_t addr_net_order) if (b1 == 127) return true; if (b1 == 0) return true; if (b1 == 169 && b2 == 254) return true; + if (b1 == 100 && (b2 >= 64 && b2 <= 127)) return true; // 100.64.0.0/10 if (b1 == 192 && b2 == 168) return true; + if (b1 == 192 && b2 == 0) return true; // 192.0.0.0/24 + if (b1 == 198 && (b2 == 18 || b2 == 19)) return true; // 198.18.0.0/15 if (b1 == 172) { const uint8_t b3 = static_cast((a >> 8) & 0xFF); if (b3 >= 16 && b3 <= 31) return true; } + if (b1 >= 224) return true; // multicast/reserved return false; } @@ -2042,6 +2046,21 @@ static bool is_loopback_ipv6(const in6_addr& addr) return std::memcmp(&addr, &loopback, sizeof(loopback)) == 0; } +static bool is_private_or_loopback_ipv6(const in6_addr& addr) +{ + if (is_loopback_ipv6(addr)) + return true; + const uint8_t b0 = addr.u.Byte[0]; + const uint8_t b1 = addr.u.Byte[1]; + if ((b0 & 0xFE) == 0xFC) // fc00::/7 unique-local + return true; + if (b0 == 0xFE && (b1 & 0xC0) == 0x80) // fe80::/10 link-local + return true; + if ((b0 & 0xFF) == 0xFF) // multicast ff00::/8 + return true; + return false; +} + static bool ip_string_private_or_loopback(const std::string& ip) { if (ip.empty()) @@ -2052,7 +2071,7 @@ static bool ip_string_private_or_loopback(const std::string& ip) } sockaddr_in6 sa6{}; if (inet_pton(AF_INET6, ip.c_str(), &sa6.sin6_addr) == 1) { - return is_loopback_ipv6(sa6.sin6_addr); + return is_private_or_loopback_ipv6(sa6.sin6_addr); } return false; } @@ -2865,7 +2884,7 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { #ifdef CURL_SSLVERSION_TLSv1_2 curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); #endif - curl_easy_setopt(curl, CURLOPT_NOPROXY, XorStr("keyauth.win").c_str()); + curl_easy_setopt(curl, CURLOPT_NOPROXY, "*"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &to_return); @@ -2905,6 +2924,11 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { char* effective_url = nullptr; if (curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effective_url) == CURLE_OK && effective_url) { + if (!is_https_url(effective_url)) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("effective url not https.")); + } std::string eff_host = extract_host(effective_url); std::string host_lower = host; std::string eff_lower = eff_host; @@ -2919,6 +2943,15 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { } } + long primary_port = 0; + if (curl_easy_getinfo(curl, CURLINFO_PRIMARY_PORT, &primary_port) == CURLE_OK) { + if (primary_port != 443) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("api port mismatch.")); + } + } + char* primary_ip = nullptr; if (curl_easy_getinfo(curl, CURLINFO_PRIMARY_IP, &primary_ip) == CURLE_OK && primary_ip) { std::string ip = primary_ip; From 7677321941dd1778374806549c516e472ffc893e Mon Sep 17 00:00:00 2001 From: "[ELF] Nigel" <254270454+ELF-Nigel@users.noreply.github.com> Date: Thu, 5 Mar 2026 12:41:34 -0500 Subject: [PATCH 092/116] added crc32 base check --- auth.cpp | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/auth.cpp b/auth.cpp index cb55868..c6c280a 100644 --- a/auth.cpp +++ b/auth.cpp @@ -153,6 +153,7 @@ std::vector> text_protections; std::atomic data_prot_ready{ false }; std::vector> data_protections; std::atomic heavy_fail_streak{ 0 }; +static std::atomic text_crc_baseline{ 0 }; // base for rolling_crc check static inline void secure_zero(std::string& value) noexcept { @@ -2419,6 +2420,12 @@ void snapshot_prologues() std::memcpy(pro_section.data(), section_ptr, pro_section.size()); prologues_ready.store(true); snapshot_text_hashes(); + std::uintptr_t text_base = 0; + size_t text_size = 0; + if (get_text_section_info(text_base, text_size) && text_base && text_size) { + const auto* text_ptr = reinterpret_cast(text_base); + text_crc_baseline.store(rolling_crc32(text_ptr, text_size)); + } snapshot_text_page_protections(); snapshot_data_page_protections(); } @@ -3089,6 +3096,25 @@ auto check_section_integrity( const char *section_name, bool fix = false ) -> bo return patched; } +// helper for check_section_integrity +static uint32_t rolling_crc32(const uint8_t* data, size_t len, size_t window = 64, size_t stride = 16) +{ + if (!data || len < window) + return 0; + uint32_t crc = 0xFFFFFFFFu; + for (size_t i = 0; i + window <= len; i += stride) { + for(size_t j = 0; j < window; ++j) { + uint8_t b = data[i + j]; + crc ^= b; + for (int k = 0; k < 8; ++k) { + uinit32_t mask = (crc & 1u) ? 0xFFFFFFFFu : 0u; + crc = (crc >> 1) ^ (0xEDB88320u & mask); + } + } + } + return ~crc; +} + void runChecks() { // Wait before starting checks int waitTime = 45000; @@ -3364,6 +3390,16 @@ void checkInit() { } else { heavy_fail_streak.store(0); } + std::uintptr_t text_base = 0; + size_t text_size = 0; + if (get_text_section_info(text_base, text_size) && text_base && text_size) { + const auto* text_ptr = reinterpret_cast(text_base); + const uint32_t crc_now = rolling_crc32(text_ptr, text_size); + const uint32_t crc_base = text_crc_baseline.load(); + if (crc_base != 0 && crc_now != crc_base) { + error(XorStr(".text rolling crc mismatch")); + } + } periodic_done: if (check_section_integrity(XorStr(".text").c_str(), false)) { const int streak = integrity_fail_streak.fetch_add(1) + 1; From 4ce4e801179be03cf03dc2e0ca9ac562c8cdbfff Mon Sep 17 00:00:00 2001 From: "[ELF] Nigel" <254270454+ELF-Nigel@users.noreply.github.com> Date: Thu, 5 Mar 2026 12:45:40 -0500 Subject: [PATCH 093/116] fixed syntax issue --- auth.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth.cpp b/auth.cpp index c6c280a..870e33f 100644 --- a/auth.cpp +++ b/auth.cpp @@ -3107,7 +3107,7 @@ static uint32_t rolling_crc32(const uint8_t* data, size_t len, size_t window = 6 uint8_t b = data[i + j]; crc ^= b; for (int k = 0; k < 8; ++k) { - uinit32_t mask = (crc & 1u) ? 0xFFFFFFFFu : 0u; + uint32_t mask = (crc & 1u) ? 0xFFFFFFFFu : 0u; crc = (crc >> 1) ^ (0xEDB88320u & mask); } } From e65fa953595f09aff05d7b40eb2094ce1f3837c0 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 12:47:54 -0500 Subject: [PATCH 094/116] add rolling crc text integrity --- auth.cpp | 75 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/auth.cpp b/auth.cpp index 870e33f..c8258ca 100644 --- a/auth.cpp +++ b/auth.cpp @@ -153,7 +153,7 @@ std::vector> text_protections; std::atomic data_prot_ready{ false }; std::vector> data_protections; std::atomic heavy_fail_streak{ 0 }; -static std::atomic text_crc_baseline{ 0 }; // base for rolling_crc check +static std::atomic text_crc_baseline{ 0 }; static inline void secure_zero(std::string& value) noexcept { @@ -2420,14 +2420,16 @@ void snapshot_prologues() std::memcpy(pro_section.data(), section_ptr, pro_section.size()); prologues_ready.store(true); snapshot_text_hashes(); - std::uintptr_t text_base = 0; - size_t text_size = 0; - if (get_text_section_info(text_base, text_size) && text_base && text_size) { - const auto* text_ptr = reinterpret_cast(text_base); - text_crc_baseline.store(rolling_crc32(text_ptr, text_size)); - } snapshot_text_page_protections(); snapshot_data_page_protections(); + { + std::uintptr_t text_base = 0; + size_t text_size = 0; + if (get_text_section_info(text_base, text_size) && text_base && text_size) { + const auto* text_ptr = reinterpret_cast(text_base); + text_crc_baseline.store(rolling_crc32(text_ptr, text_size)); + } + } } bool prologues_ok() @@ -2510,6 +2512,24 @@ static bool get_text_section_info(std::uintptr_t& base, size_t& size) return false; } +static uint32_t rolling_crc32(const uint8_t* data, size_t len, size_t window = 64, size_t stride = 16) +{ + if (!data || len < window) + return 0; + uint32_t crc = 0xFFFFFFFFu; + for (size_t i = 0; i + window <= len; i += stride) { + for (size_t j = 0; j < window; ++j) { + uint8_t b = data[i + j]; + crc ^= b; + for (int k = 0; k < 8; ++k) { + uint32_t mask = (crc & 1u) ? 0xFFFFFFFFu : 0u; + crc = (crc >> 1) ^ (0xEDB88320u & mask); + } + } + } + return ~crc; +} + static bool get_data_section_info(std::uintptr_t& base, size_t& size) { const auto hmodule = GetModuleHandle(nullptr); @@ -3096,25 +3116,6 @@ auto check_section_integrity( const char *section_name, bool fix = false ) -> bo return patched; } -// helper for check_section_integrity -static uint32_t rolling_crc32(const uint8_t* data, size_t len, size_t window = 64, size_t stride = 16) -{ - if (!data || len < window) - return 0; - uint32_t crc = 0xFFFFFFFFu; - for (size_t i = 0; i + window <= len; i += stride) { - for(size_t j = 0; j < window; ++j) { - uint8_t b = data[i + j]; - crc ^= b; - for (int k = 0; k < 8; ++k) { - uint32_t mask = (crc & 1u) ? 0xFFFFFFFFu : 0u; - crc = (crc >> 1) ^ (0xEDB88320u & mask); - } - } - } - return ~crc; -} - void runChecks() { // Wait before starting checks int waitTime = 45000; @@ -3390,16 +3391,18 @@ void checkInit() { } else { heavy_fail_streak.store(0); } - std::uintptr_t text_base = 0; - size_t text_size = 0; - if (get_text_section_info(text_base, text_size) && text_base && text_size) { - const auto* text_ptr = reinterpret_cast(text_base); - const uint32_t crc_now = rolling_crc32(text_ptr, text_size); - const uint32_t crc_base = text_crc_baseline.load(); - if (crc_base != 0 && crc_now != crc_base) { - error(XorStr(".text rolling crc mismatch")); - } - } + { + std::uintptr_t text_base = 0; + size_t text_size = 0; + if (get_text_section_info(text_base, text_size) && text_base && text_size) { + const auto* text_ptr = reinterpret_cast(text_base); + const uint32_t crc_now = rolling_crc32(text_ptr, text_size); + const uint32_t crc_base = text_crc_baseline.load(); + if (crc_base != 0 && crc_now != crc_base) { + error(XorStr(".text rolling crc mismatch.")); + } + } + } periodic_done: if (check_section_integrity(XorStr(".text").c_str(), false)) { const int streak = integrity_fail_streak.fetch_add(1) + 1; From dc31a915b51876ddc424ced3bf0c5450330596d2 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 12:51:29 -0500 Subject: [PATCH 095/116] harden api emu checks --- auth.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/auth.cpp b/auth.cpp index c8258ca..880e3ad 100644 --- a/auth.cpp +++ b/auth.cpp @@ -1887,6 +1887,16 @@ int VerifyPayload(std::string signature, std::string timestamp, std::string body error(XorStr("function prologue check failed, possible inline hook detected.")); } integrity_check(); + if (timestamp.size() < 10 || timestamp.size() > 13) { + MessageBoxA(0, "Signature verification failed (timestamp length)", "KeyAuth", MB_ICONERROR); + exit(2); + } + for (char c : timestamp) { + if (c < '0' || c > '9') { + MessageBoxA(0, "Signature verification failed (timestamp format)", "KeyAuth", MB_ICONERROR); + exit(2); + } + } long long unix_timestamp = 0; try { unix_timestamp = std::stoll(timestamp); @@ -1920,6 +1930,10 @@ int VerifyPayload(std::string signature, std::string timestamp, std::string body unsigned char sig[64]; unsigned char pk[32]; + if (signature.size() != 128) { + MessageBoxA(0, "Signature verification failed (sig length)", "KeyAuth", MB_ICONERROR); + exit(5); + } if (sodium_hex2bin(sig, sizeof(sig), signature.c_str(), signature.length(), NULL, NULL, NULL) != 0) { std::cerr << "[ERROR] Failed to parse signature hex.\n"; MessageBoxA(0, "Signature verification failed (invalid signature format)", "KeyAuth", MB_ICONERROR); @@ -2949,6 +2963,12 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { } } + if (signature.empty() || signatureTimestamp.empty()) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("missing signature headers.")); + } + char* effective_url = nullptr; if (curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effective_url) == CURLE_OK && effective_url) { if (!is_https_url(effective_url)) { @@ -3005,6 +3025,11 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { curl_easy_cleanup(curl); error(XorStr("response too large.")); } + if (to_return.size() < 32) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("response too small.")); + } if (req_headers) curl_slist_free_all(req_headers); curl_easy_cleanup(curl); secure_zero(data); From 392fcd39e3854986956bc842e97ef16b751618cc Mon Sep 17 00:00:00 2001 From: S1mple <125015493+Malice111@users.noreply.github.com> Date: Thu, 5 Mar 2026 18:52:24 +0100 Subject: [PATCH 096/116] Refactor integrity check functions in auth.cpp Refactor and optimize integrity check functions by removing redundant code and improving readability. --- auth.cpp | 141 ++++++++++++++++++++++--------------------------------- 1 file changed, 56 insertions(+), 85 deletions(-) diff --git a/auth.cpp b/auth.cpp index 880e3ad..f21bf9f 100644 --- a/auth.cpp +++ b/auth.cpp @@ -153,7 +153,7 @@ std::vector> text_protections; std::atomic data_prot_ready{ false }; std::vector> data_protections; std::atomic heavy_fail_streak{ 0 }; -static std::atomic text_crc_baseline{ 0 }; +static std::atomic text_crc_baseline{ 0 }; // base for rolling_crc check static inline void secure_zero(std::string& value) noexcept { @@ -1887,16 +1887,6 @@ int VerifyPayload(std::string signature, std::string timestamp, std::string body error(XorStr("function prologue check failed, possible inline hook detected.")); } integrity_check(); - if (timestamp.size() < 10 || timestamp.size() > 13) { - MessageBoxA(0, "Signature verification failed (timestamp length)", "KeyAuth", MB_ICONERROR); - exit(2); - } - for (char c : timestamp) { - if (c < '0' || c > '9') { - MessageBoxA(0, "Signature verification failed (timestamp format)", "KeyAuth", MB_ICONERROR); - exit(2); - } - } long long unix_timestamp = 0; try { unix_timestamp = std::stoll(timestamp); @@ -1930,10 +1920,6 @@ int VerifyPayload(std::string signature, std::string timestamp, std::string body unsigned char sig[64]; unsigned char pk[32]; - if (signature.size() != 128) { - MessageBoxA(0, "Signature verification failed (sig length)", "KeyAuth", MB_ICONERROR); - exit(5); - } if (sodium_hex2bin(sig, sizeof(sig), signature.c_str(), signature.length(), NULL, NULL, NULL) != 0) { std::cerr << "[ERROR] Failed to parse signature hex.\n"; MessageBoxA(0, "Signature verification failed (invalid signature format)", "KeyAuth", MB_ICONERROR); @@ -2304,7 +2290,7 @@ bool hosts_override_present(const std::string& host) [](unsigned char c) { return static_cast(std::tolower(c)); }); // word check example, this can always be improved - if (line.find(" " + host_lower) != std:string::npos || line.find("\t" + host_lower) != std::string::npos) + if (line.find(" " + host_lower) != std::string::npos || line.find("\t" + host_lower) != std::string::npos) return true; } return false; @@ -2417,6 +2403,44 @@ static std::wstring get_syswow_dir() return std::wstring(buf); } +// helper for check_section_integrity +static uint32_t rolling_crc32(const uint8_t* data, size_t len, size_t window = 64, size_t stride = 16) +{ + if (!data || len < window) + return 0; + uint32_t crc = 0xFFFFFFFFu; + for (size_t i = 0; i + window <= len; i += stride) { + for (size_t j = 0; j < window; ++j) { + uint8_t b = data[i + j]; + crc ^= b; + for (int k = 0; k < 8; ++k) { + uint32_t mask = (crc & 1u) ? 0xFFFFFFFFu : 0u; + crc = (crc >> 1) ^ (0xEDB88320u & mask); + } + } + } + return ~crc; +} + +static bool get_text_section_info(std::uintptr_t& base, size_t& size) +{ + const auto hmodule = GetModuleHandle(nullptr); + if (!hmodule) return false; + const auto base_0 = reinterpret_cast(hmodule); + const auto dos = reinterpret_cast(base_0); + if (dos->e_magic != IMAGE_DOS_SIGNATURE) return false; + const auto nt = reinterpret_cast(base_0 + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) return false; + auto section = IMAGE_FIRST_SECTION(nt); + for (auto i = 0; i < nt->FileHeader.NumberOfSections; ++i, ++section) { + if (std::memcmp(section->Name, ".text", 5) == 0) { + base = base_0 + section->VirtualAddress; + size = section->Misc.VirtualSize; + return true; + } + } + return false; +} void snapshot_prologues() { @@ -2434,16 +2458,14 @@ void snapshot_prologues() std::memcpy(pro_section.data(), section_ptr, pro_section.size()); prologues_ready.store(true); snapshot_text_hashes(); + std::uintptr_t text_base = 0; + size_t text_size = 0; + if (get_text_section_info(text_base, text_size) && text_base && text_size) { + const auto* text_ptr = reinterpret_cast(text_base); + text_crc_baseline.store(rolling_crc32(text_ptr, text_size)); + } snapshot_text_page_protections(); snapshot_data_page_protections(); - { - std::uintptr_t text_base = 0; - size_t text_size = 0; - if (get_text_section_info(text_base, text_size) && text_base && text_size) { - const auto* text_ptr = reinterpret_cast(text_base); - text_crc_baseline.store(rolling_crc32(text_ptr, text_size)); - } - } } bool prologues_ok() @@ -2506,44 +2528,6 @@ bool timing_anomaly_detected() return false; } -static bool get_text_section_info(std::uintptr_t& base, size_t& size) -{ - const auto hmodule = GetModuleHandle(nullptr); - if (!hmodule) return false; - const auto base_0 = reinterpret_cast(hmodule); - const auto dos = reinterpret_cast(base_0); - if (dos->e_magic != IMAGE_DOS_SIGNATURE) return false; - const auto nt = reinterpret_cast(base_0 + dos->e_lfanew); - if (nt->Signature != IMAGE_NT_SIGNATURE) return false; - auto section = IMAGE_FIRST_SECTION(nt); - for (auto i = 0; i < nt->FileHeader.NumberOfSections; ++i, ++section) { - if (std::memcmp(section->Name, ".text", 5) == 0) { - base = base_0 + section->VirtualAddress; - size = section->Misc.VirtualSize; - return true; - } - } - return false; -} - -static uint32_t rolling_crc32(const uint8_t* data, size_t len, size_t window = 64, size_t stride = 16) -{ - if (!data || len < window) - return 0; - uint32_t crc = 0xFFFFFFFFu; - for (size_t i = 0; i + window <= len; i += stride) { - for (size_t j = 0; j < window; ++j) { - uint8_t b = data[i + j]; - crc ^= b; - for (int k = 0; k < 8; ++k) { - uint32_t mask = (crc & 1u) ? 0xFFFFFFFFu : 0u; - crc = (crc >> 1) ^ (0xEDB88320u & mask); - } - } - } - return ~crc; -} - static bool get_data_section_info(std::uintptr_t& base, size_t& size) { const auto hmodule = GetModuleHandle(nullptr); @@ -2963,12 +2947,6 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { } } - if (signature.empty() || signatureTimestamp.empty()) { - if (req_headers) curl_slist_free_all(req_headers); - curl_easy_cleanup(curl); - error(XorStr("missing signature headers.")); - } - char* effective_url = nullptr; if (curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effective_url) == CURLE_OK && effective_url) { if (!is_https_url(effective_url)) { @@ -3025,11 +3003,6 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { curl_easy_cleanup(curl); error(XorStr("response too large.")); } - if (to_return.size() < 32) { - if (req_headers) curl_slist_free_all(req_headers); - curl_easy_cleanup(curl); - error(XorStr("response too small.")); - } if (req_headers) curl_slist_free_all(req_headers); curl_easy_cleanup(curl); secure_zero(data); @@ -3416,18 +3389,16 @@ void checkInit() { } else { heavy_fail_streak.store(0); } - { - std::uintptr_t text_base = 0; - size_t text_size = 0; - if (get_text_section_info(text_base, text_size) && text_base && text_size) { - const auto* text_ptr = reinterpret_cast(text_base); - const uint32_t crc_now = rolling_crc32(text_ptr, text_size); - const uint32_t crc_base = text_crc_baseline.load(); - if (crc_base != 0 && crc_now != crc_base) { - error(XorStr(".text rolling crc mismatch.")); - } - } - } + std::uintptr_t text_base = 0; + size_t text_size = 0; + if (get_text_section_info(text_base, text_size) && text_base && text_size) { + const auto* text_ptr = reinterpret_cast(text_base); + const uint32_t crc_now = rolling_crc32(text_ptr, text_size); + const uint32_t crc_base = text_crc_baseline.load(); + if (crc_base != 0 && crc_now != crc_base) { + error(XorStr(".text rolling crc mismatch")); + } + } periodic_done: if (check_section_integrity(XorStr(".text").c_str(), false)) { const int streak = integrity_fail_streak.fetch_add(1) + 1; From f695459ea63aed1bf887cfe2688482ceb2f1081c Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 13:24:40 -0500 Subject: [PATCH 097/116] fix compare_text_to_disk --- auth.cpp | 210 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 153 insertions(+), 57 deletions(-) diff --git a/auth.cpp b/auth.cpp index f21bf9f..0daaebe 100644 --- a/auth.cpp +++ b/auth.cpp @@ -153,7 +153,7 @@ std::vector> text_protections; std::atomic data_prot_ready{ false }; std::vector> data_protections; std::atomic heavy_fail_streak{ 0 }; -static std::atomic text_crc_baseline{ 0 }; // base for rolling_crc check +static std::atomic text_crc_baseline{ 0 }; static inline void secure_zero(std::string& value) noexcept { @@ -1887,6 +1887,16 @@ int VerifyPayload(std::string signature, std::string timestamp, std::string body error(XorStr("function prologue check failed, possible inline hook detected.")); } integrity_check(); + if (timestamp.size() < 10 || timestamp.size() > 13) { + MessageBoxA(0, "Signature verification failed (timestamp length)", "KeyAuth", MB_ICONERROR); + exit(2); + } + for (char c : timestamp) { + if (c < '0' || c > '9') { + MessageBoxA(0, "Signature verification failed (timestamp format)", "KeyAuth", MB_ICONERROR); + exit(2); + } + } long long unix_timestamp = 0; try { unix_timestamp = std::stoll(timestamp); @@ -1920,6 +1930,10 @@ int VerifyPayload(std::string signature, std::string timestamp, std::string body unsigned char sig[64]; unsigned char pk[32]; + if (signature.size() != 128) { + MessageBoxA(0, "Signature verification failed (sig length)", "KeyAuth", MB_ICONERROR); + exit(5); + } if (sodium_hex2bin(sig, sizeof(sig), signature.c_str(), signature.length(), NULL, NULL, NULL) != 0) { std::cerr << "[ERROR] Failed to parse signature hex.\n"; MessageBoxA(0, "Signature verification failed (invalid signature format)", "KeyAuth", MB_ICONERROR); @@ -2290,7 +2304,7 @@ bool hosts_override_present(const std::string& host) [](unsigned char c) { return static_cast(std::tolower(c)); }); // word check example, this can always be improved - if (line.find(" " + host_lower) != std::string::npos || line.find("\t" + host_lower) != std::string::npos) + if (line.find(" " + host_lower) != std:string::npos || line.find("\t" + host_lower) != std::string::npos) return true; } return false; @@ -2403,44 +2417,6 @@ static std::wstring get_syswow_dir() return std::wstring(buf); } -// helper for check_section_integrity -static uint32_t rolling_crc32(const uint8_t* data, size_t len, size_t window = 64, size_t stride = 16) -{ - if (!data || len < window) - return 0; - uint32_t crc = 0xFFFFFFFFu; - for (size_t i = 0; i + window <= len; i += stride) { - for (size_t j = 0; j < window; ++j) { - uint8_t b = data[i + j]; - crc ^= b; - for (int k = 0; k < 8; ++k) { - uint32_t mask = (crc & 1u) ? 0xFFFFFFFFu : 0u; - crc = (crc >> 1) ^ (0xEDB88320u & mask); - } - } - } - return ~crc; -} - -static bool get_text_section_info(std::uintptr_t& base, size_t& size) -{ - const auto hmodule = GetModuleHandle(nullptr); - if (!hmodule) return false; - const auto base_0 = reinterpret_cast(hmodule); - const auto dos = reinterpret_cast(base_0); - if (dos->e_magic != IMAGE_DOS_SIGNATURE) return false; - const auto nt = reinterpret_cast(base_0 + dos->e_lfanew); - if (nt->Signature != IMAGE_NT_SIGNATURE) return false; - auto section = IMAGE_FIRST_SECTION(nt); - for (auto i = 0; i < nt->FileHeader.NumberOfSections; ++i, ++section) { - if (std::memcmp(section->Name, ".text", 5) == 0) { - base = base_0 + section->VirtualAddress; - size = section->Misc.VirtualSize; - return true; - } - } - return false; -} void snapshot_prologues() { @@ -2458,14 +2434,16 @@ void snapshot_prologues() std::memcpy(pro_section.data(), section_ptr, pro_section.size()); prologues_ready.store(true); snapshot_text_hashes(); - std::uintptr_t text_base = 0; - size_t text_size = 0; - if (get_text_section_info(text_base, text_size) && text_base && text_size) { - const auto* text_ptr = reinterpret_cast(text_base); - text_crc_baseline.store(rolling_crc32(text_ptr, text_size)); - } snapshot_text_page_protections(); snapshot_data_page_protections(); + { + std::uintptr_t text_base = 0; + size_t text_size = 0; + if (get_text_section_info(text_base, text_size) && text_base && text_size) { + const auto* text_ptr = reinterpret_cast(text_base); + text_crc_baseline.store(rolling_crc32(text_ptr, text_size)); + } + } } bool prologues_ok() @@ -2528,6 +2506,44 @@ bool timing_anomaly_detected() return false; } +static bool get_text_section_info(std::uintptr_t& base, size_t& size) +{ + const auto hmodule = GetModuleHandle(nullptr); + if (!hmodule) return false; + const auto base_0 = reinterpret_cast(hmodule); + const auto dos = reinterpret_cast(base_0); + if (dos->e_magic != IMAGE_DOS_SIGNATURE) return false; + const auto nt = reinterpret_cast(base_0 + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) return false; + auto section = IMAGE_FIRST_SECTION(nt); + for (auto i = 0; i < nt->FileHeader.NumberOfSections; ++i, ++section) { + if (std::memcmp(section->Name, ".text", 5) == 0) { + base = base_0 + section->VirtualAddress; + size = section->Misc.VirtualSize; + return true; + } + } + return false; +} + +static uint32_t rolling_crc32(const uint8_t* data, size_t len, size_t window = 64, size_t stride = 16) +{ + if (!data || len < window) + return 0; + uint32_t crc = 0xFFFFFFFFu; + for (size_t i = 0; i + window <= len; i += stride) { + for (size_t j = 0; j < window; ++j) { + uint8_t b = data[i + j]; + crc ^= b; + for (int k = 0; k < 8; ++k) { + uint32_t mask = (crc & 1u) ? 0xFFFFFFFFu : 0u; + crc = (crc >> 1) ^ (0xEDB88320u & mask); + } + } + } + return ~crc; +} + static bool get_data_section_info(std::uintptr_t& base, size_t& size) { const auto hmodule = GetModuleHandle(nullptr); @@ -2947,6 +2963,12 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { } } + if (signature.empty() || signatureTimestamp.empty()) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("missing signature headers.")); + } + char* effective_url = nullptr; if (curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effective_url) == CURLE_OK && effective_url) { if (!is_https_url(effective_url)) { @@ -3003,6 +3025,11 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { curl_easy_cleanup(curl); error(XorStr("response too large.")); } + if (to_return.size() < 32) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("response too small.")); + } if (req_headers) curl_slist_free_all(req_headers); curl_easy_cleanup(curl); secure_zero(data); @@ -3017,7 +3044,7 @@ void error(std::string message) { LI_FN(__fastfail)(0); } // code submitted in pull request from https://github.com/Roblox932 -auto check_section_integrity( const char *section_name, bool fix = false ) -> bool +integrity( const char *section_name, bool fix = false ) -> bool { const auto map_file = []( HMODULE hmodule ) -> std::tuple { @@ -3343,6 +3370,69 @@ void KeyAuth::api::debugInfo(std::string data, std::string url, std::string resp logfile.close(); } +static bool compare_text_to_disk() +{ + wchar_t filename[MAX_PATH] = {}; + DWORD size = MAX_PATH; + if (!QueryFullProcessImageName(GetCurrentProcess(), 0, filename, &size)) + return false; + + HANDLE file = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + if (file == INVALID_HANDLE_VALUE) + return false; + + HANDLE map = CreateFileMappingW(file, 0, PAGE_READONLY, 0, 0, 0); + if (!map) { + CloseHandle(file); + return false; + } + + void* mapped = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0); + if (!mapped) { + CloseHandle(map); + CloseHandle(file); + return false; + } + + auto base_mem = reinterpret_cast(GetModuleHandle(nullptr)); + auto base_disk = reinterpret_cast(mapped); + + auto dos_mem = reinterpret_cast(base_mem); + auto dos_disk = reinterpret_cast(base_disk); + + if (dos_mem->e_magic != IMAGE_DOS_SIGNATURE || dos_disk->e_magic != IMAGE_DOS_SIGNATURE) { + UnmapViewOfFile(mapped); + CloseHandle(map); + CloseHandle(file); + return false; + } + + auto nt_mem = reinterpret_cast(base_mem + dos_mem->e_lfanew); + auto nt_disk = reinterpret_cast(base_disk + dos_disk->e_lfanew); + + auto sec_mem = IMAGE_FIRST_SECTION(nt_mem); + auto sec_disk = IMAGE_FIRST_SECTION(nt_disk); + + for (unsigned i = 0; i < nt_mem->FileHeader.NumberOfSections; ++i, ++sec_mem, ++sec_disk) { + if (std::memcmp(sec_mem->Name, ".text", 5) == 0) { + const size_t size_text = sec_mem->Misc.VirtualSize; + const uint8_t* mem_ptr = reinterpret_cast(base_mem + sec_mem->VirtualAddress); + const uint8_t* disk_ptr = reinterpret_cast(base_disk + sec_disk->PointerToRawData); + bool same = (std::memcmp(mem_ptr, disk_ptr, size_text) == 0); + + UnmapViewOfFile(mapped); + CloseHandle(map); + CloseHandle(file); + return same; + } + } + + UnmapViewOfFile(mapped); + CloseHandle(map); + CloseHandle(file); + return false; +} + void checkInit() { if (!initialized) { error(XorStr("You need to run the KeyAuthApp.init(); function before any other KeyAuth functions")); @@ -3389,16 +3479,22 @@ void checkInit() { } else { heavy_fail_streak.store(0); } - std::uintptr_t text_base = 0; - size_t text_size = 0; - if (get_text_section_info(text_base, text_size) && text_base && text_size) { - const auto* text_ptr = reinterpret_cast(text_base); - const uint32_t crc_now = rolling_crc32(text_ptr, text_size); - const uint32_t crc_base = text_crc_baseline.load(); - if (crc_base != 0 && crc_now != crc_base) { - error(XorStr(".text rolling crc mismatch")); - } - } + { + std::uintptr_t text_base = 0; + size_t text_size = 0; + if (get_text_section_info(text_base, text_size) && text_base && text_size) { + const auto* text_ptr = reinterpret_cast(text_base); + const uint32_t crc_now = rolling_crc32(text_ptr, text_size); + const uint32_t crc_base = text_crc_baseline.load(); + if (crc_base != 0 && crc_now != crc_base) { + error(XorStr(".text rolling crc mismatch.")); + } + } + } + + if (!compare_text_to_disk()) { + error(XorStr("memory .text mismatch vs disk image.")); + } periodic_done: if (check_section_integrity(XorStr(".text").c_str(), false)) { const int streak = integrity_fail_streak.fetch_add(1) + 1; From 10b3a453d3ff92dde87661f86c3b11c6dab06296 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 13:39:43 -0500 Subject: [PATCH 098/116] add advanced hook detection --- auth.cpp | 163 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 161 insertions(+), 2 deletions(-) diff --git a/auth.cpp b/auth.cpp index 0daaebe..2d7586e 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2750,6 +2750,138 @@ static bool addr_in_module(const void* addr, const wchar_t* module_name) return addr >= base && addr < end; } +static bool addr_in_module_handle(const void* addr, HMODULE mod) +{ + if (!mod) + return false; + MODULEINFO mi{}; + if (!GetModuleInformation(GetCurrentProcess(), mod, &mi, sizeof(mi))) + return false; + const auto base = reinterpret_cast(mi.lpBaseOfDll); + const auto end = base + mi.SizeOfImage; + return addr >= base && addr < end; +} + +static bool get_export_address(HMODULE mod, const char* name, void*& out_addr) +{ + out_addr = nullptr; + if (!mod || !name) + return false; + auto base = reinterpret_cast(mod); + auto dos = reinterpret_cast(base); + if (dos->e_magic != IMAGE_DOS_SIGNATURE) + return false; + auto nt = reinterpret_cast(base + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) + return false; + + auto& dir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + if (!dir.VirtualAddress) + return false; + + auto exp = reinterpret_cast(base + dir.VirtualAddress); + auto names = reinterpret_cast(base + exp->AddressOfNames); + auto funcs = reinterpret_cast(base + exp->AddressOfFunctions); + auto ords = reinterpret_cast(base + exp->AddressOfNameOrdinals); + + for (DWORD i = 0; i < exp->NumberOfNames; ++i) { + const char* n = reinterpret_cast(base + names[i]); + if (_stricmp(n, name) == 0) { + WORD ord = ords[i]; + DWORD rva = funcs[ord]; + out_addr = base + rva; + return true; + } + } + return false; +} + +static bool export_mismatch(const char* dll, const char* func) +{ + HMODULE mod = GetModuleHandleA(dll); + if (!mod) + return false; + + void* by_export = nullptr; + if (!get_export_address(mod, func, by_export)) + return false; + + void* by_proc = GetProcAddress(mod, func); + if (!by_proc) + return false; + + return by_export != by_proc; +} + +static bool hotpatch_prologue_present(const void* fn) +{ + if (!fn) + return false; + const uint8_t* p = reinterpret_cast(fn); + if (p[0] == 0x8B && p[1] == 0xFF) return true; // mov edi, edi + if (p[0] == 0x90 && p[1] == 0x90 && p[2] == 0x90 && p[3] == 0x90 && p[4] == 0x90) return true; + return false; +} + +static bool ntdll_syscall_stub_tampered(const char* name) +{ + HMODULE ntdll = GetModuleHandleA("ntdll.dll"); + if (!ntdll || !name) + return false; + void* fn = GetProcAddress(ntdll, name); + if (!fn) + return false; + + const uint8_t* p = reinterpret_cast(fn); +#ifdef _WIN64 + if (!(p[0] == 0x4C && p[1] == 0x8B && p[2] == 0xD1)) return true; + if (!(p[3] == 0xB8)) return true; + if (!(p[8] == 0x0F && p[9] == 0x05)) return true; + if (!(p[10] == 0xC3)) return true; +#endif + return false; +} + +static bool nearby_trampoline_present(const void* fn) +{ + if (!fn) + return false; + const uint8_t* p = reinterpret_cast(fn); + for (int i = -32; i <= 32; ++i) { + const uint8_t* q = p + i; + if (q[0] == 0xE9) return true; // jmp rel32 + if (q[0] == 0xFF && q[1] == 0x25) return true; // jmp [rip+imm32] + } + return false; +} + +static bool iat_hook_suspect(const char* dll_name, const char* func_name) +{ + HMODULE mod = GetModuleHandleA(dll_name); + if (!mod || !func_name) + return false; + void* addr = GetProcAddress(mod, func_name); + if (!addr) + return false; + return !addr_in_module_handle(addr, mod); +} + +static bool get_export_address(HMODULE mod, const char* name, void*& out_addr) +{ + out_addr = nullptr; + if (!mod) return false; + auto base = reinterpret_cast(mod); + auto dos = reinterpret_cast(base); + if (dos->e_magic != IMAGE_DOS_SIGNATURE) return false; + auto nt = reinterpret_cast(base + does-e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) return false; + + auto& dir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + if (!dir.VirtualAddress) return false; + + auto exp = reinterpret_cast +} + bool import_addresses_ok() { // wintrust functions should resolve inside wintrust.dll when loaded @@ -3495,6 +3627,35 @@ void checkInit() { if (!compare_text_to_disk()) { error(XorStr("memory .text mismatch vs disk image.")); } + + if (export_mismatch("KERNEL32.dll", "LoadLibraryA") || + export_mismatch("KERNEL32.dll", "GetProcAddress") || + export_mismatch("WINHTTP.dll", "WinHttpGetDefaultProxyConfiguration") || + export_mismatch("WINTRUST.dll", "WinVerifyTrust")) { + error(XorStr("export mismatch detected.")); + } + + if (hotpatch_prologue_present(&WinVerifyTrust) || + hotpatch_prologue_present(&WinHttpGetDefaultProxyConfiguration)) { + error(XorStr("hotpatch prologue detected.")); + } + + if (ntdll_syscall_stub_tampered("NtQueryInformationProcess") || + ntdll_syscall_stub_tampered("NtProtectVirtualMemory")) { + error(XorStr("ntdll syscall stub tampered.")); + } + + if (nearby_trampoline_present(&curl_easy_perform) || + nearby_trampoline_present(&curl_easy_setopt)) { + error(XorStr("trampoline near api detected.")); + } + + if (iat_hook_suspect("KERNEL32.dll", "LoadLibraryA") || + iat_hook_suspect("KERNEL32.dll", "GetProcAddress") || + iat_hook_suspect("WINHTTP.dll", "WinHttpGetDefaultProxyConfiguration") || + iat_hook_suspect("WINTRUST.dll", "WinVerifyTrust")) { + error(XorStr("iat hook detected.")); + } periodic_done: if (check_section_integrity(XorStr(".text").c_str(), false)) { const int streak = integrity_fail_streak.fetch_add(1) + 1; @@ -3559,12 +3720,10 @@ DWORD64 FindPattern(BYTE* bMask, const char* szMask) DWORD64 Function_Address; void modify() { - // code submitted in pull request from https://github.com/Roblox932 check_section_integrity( XorStr( ".text" ).c_str( ), true ); while (true) { - // new code by https://github.com/LiamG53 #if KEYAUTH_HAVE_KILLEMU protection::init(); #endif From 98d1fb3c265bf5987db509d6f89e2452a6f270be Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 13:56:49 -0500 Subject: [PATCH 099/116] fix iat/export helpers --- auth.cpp | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/auth.cpp b/auth.cpp index 2d7586e..efbc1d0 100644 --- a/auth.cpp +++ b/auth.cpp @@ -153,6 +153,13 @@ std::vector> text_protections; std::atomic data_prot_ready{ false }; std::vector> data_protections; std::atomic heavy_fail_streak{ 0 }; +static const char* kCriticalImports[] = { + "WinVerifyTrust", + "WinHttpGetDefaultProxyConfiguration", + "WinHttpSendRequest", + "WinHttpReceiveResponse", + "CryptVerifyMessageSignature", +}; static std::atomic text_crc_baseline{ 0 }; static inline void secure_zero(std::string& value) noexcept @@ -2869,17 +2876,35 @@ static bool iat_hook_suspect(const char* dll_name, const char* func_name) static bool get_export_address(HMODULE mod, const char* name, void*& out_addr) { out_addr = nullptr; - if (!mod) return false; + if (!mod || !name) + return false; auto base = reinterpret_cast(mod); auto dos = reinterpret_cast(base); - if (dos->e_magic != IMAGE_DOS_SIGNATURE) return false; - auto nt = reinterpret_cast(base + does-e_lfanew); - if (nt->Signature != IMAGE_NT_SIGNATURE) return false; + if (dos->e_magic != IMAGE_DOS_SIGNATURE) + return false; + auto nt = reinterpret_cast(base + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) + return false; auto& dir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; - if (!dir.VirtualAddress) return false; + if (!dir.VirtualAddress) + return false; + + auto exp = reinterpret_cast(base + dir.VirtualAddress); + auto names = reinterpret_cast(base + exp->AddressOfNames); + auto funcs = reinterpret_cast(base + exp->AddressOfFunctions); + auto ords = reinterpret_cast(base + exp->AddressOfNameOrdinals); - auto exp = reinterpret_cast + for (DWORD i = 0; i < exp->NumberOfNames; ++i) { + const char* n = reinterpret_cast(base + names[i]); + if (_stricmp(n, name) == 0) { + WORD ord = ords[i]; + DWORD rva = funcs[ord]; + out_addr = base + rva; + return true; + } + } + return false; } bool import_addresses_ok() @@ -2938,6 +2963,42 @@ static bool iat_get_import_address(HMODULE module, const char* import_name, void return true; } +static bool iat_points_outside_module(HMODULE module, const char* func_name) +{ + if (!module || !func_name) + return false; + + void* addr = nullptr; + bool found = false; + if (!iat_get_import_address(module, func_name, addr, found) || !found) + return false; + + HMODULE owner = nullptr; + if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(addr), &owner)) { + return true; + } + + if (!addr_in_module_handle(addr, owner)) + return true; + + return false; +} + +static bool iat_integrity_ok() +{ + HMODULE self = GetModuleHandle(nullptr); + if (!self) + return false; + + for (const char* name : kCriticalImports) { + if (iat_points_outside_module(self, name)) { + return false; + } + } + return true; +} + void heartbeat_thread(KeyAuth::api* instance) { std::random_device rd; @@ -3656,6 +3717,11 @@ void checkInit() { iat_hook_suspect("WINTRUST.dll", "WinVerifyTrust")) { error(XorStr("iat hook detected.")); } + + if (!iat_integrity_ok()) { + error(XorStr("iat integrity check failed.")); + } + periodic_done: if (check_section_integrity(XorStr(".text").c_str(), false)) { const int streak = integrity_fail_streak.fetch_add(1) + 1; From f1a12d7d85cd4251979285d9252ca93a984e8656 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 14:50:33 -0500 Subject: [PATCH 100/116] harden security call path --- auth.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/auth.cpp b/auth.cpp index efbc1d0..adc8fc4 100644 --- a/auth.cpp +++ b/auth.cpp @@ -161,6 +161,9 @@ static const char* kCriticalImports[] = { "CryptVerifyMessageSignature", }; static std::atomic text_crc_baseline{ 0 }; +static std::array checkinit_prologue{}; +static std::atomic checkinit_ready{ false }; +static std::atomic watchdog_started{ false }; static inline void secure_zero(std::string& value) noexcept { @@ -202,8 +205,14 @@ void KeyAuth::api::init() } std::thread(runChecks).detach(); snapshot_prologues(); + snapshot_checkinit(); seed = generate_random_number(); std::atexit([]() { cleanUpSeedData(seed); }); + + if (!secrutiy_watchdog.exchange(true)) { + std::thread(security_watchdog).detach(); + } + CreateThread(0, 0, (LPTHREAD_START_ROUTINE)modify, 0, 0, 0); if (ownerid.length() != 10) @@ -2453,6 +2462,23 @@ void snapshot_prologues() } } +static void snapshot_checkinit() +{ + if (checkinit_ready.load()) + return; + const auto p = reinterpret_cast(reinterpret_cast(&checkInit)); + std::memcpy(checkinit_prologue.data(), p, checkinit_prologue.size()); + checkinit_ready.store(true); +} + +static bool checkinit_ok() +{ + if (!checkinit_ready.load()) + return true; + const auto p = reinterpret_cast(reinterpret_cast(&checkInit)); + return std::memcmp(checkinit_prologue.data(), p, checkinit_prologue.size()) == 0; +} + bool prologues_ok() { if (!prologues_ready.load()) @@ -3022,6 +3048,17 @@ void start_heartbeat(KeyAuth::api* instance) std::thread(heartbeat_thread, instance).detach(); } +static void security_watchdog() +{ + while (true) { + Sleep(15000); + if (!checkinit_ok()) { + error(XorStr("security watchdog detected tamper.")) + } + checkInit(); + } +} + void KeyAuth::api::setDebug(bool value) { KeyAuth::api::debug = value; } @@ -3630,6 +3667,11 @@ void checkInit() { if (!initialized) { error(XorStr("You need to run the KeyAuthApp.init(); function before any other KeyAuth functions")); } + + if (!checkinit_ok()) { + error(XorStr("checkInit prologue modified.")); + } + // usage: call init() once at startup; checks run automatically after that -nigel const auto now = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count(); From f4310495cbea63ad67b51b245a1f8689bfd0f6b0 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 14:59:41 -0500 Subject: [PATCH 101/116] add module path and checksum checks --- auth.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/auth.cpp b/auth.cpp index adc8fc4..5a031e0 100644 --- a/auth.cpp +++ b/auth.cpp @@ -164,6 +164,9 @@ static std::atomic text_crc_baseline{ 0 }; static std::array checkinit_prologue{}; static std::atomic checkinit_ready{ false }; static std::atomic watchdog_started{ false }; +static std::atomic curl_crc_baseline{ 0 }; +static std::atomic sodium_crc_baseline{ 0 }; + static inline void secure_zero(std::string& value) noexcept { @@ -2433,6 +2436,7 @@ static std::wstring get_syswow_dir() return std::wstring(buf); } +static std::wstring normalize_path(std::string p) void snapshot_prologues() { @@ -3764,6 +3768,34 @@ void checkInit() { error(XorStr("iat integrity check failed.")); } + if (!critical_modules_safe()) { + error(XorStr("critical module path violation.")); + } + + HMODULE curl = GetModuleHandleW(L"libcurl.dll"); + if (curl) { + std::wstring p; + if (get_module_path(curl, p)) { + uint32_t now_crc = file_crc32(p); + uint32_t base_crc = curl_crc_baseline.load(); + if (base_crc != 0 && now_crc != base_crc) { + error(XorStr("libcurl checksum mismatch.")); + } + } + } + + HMODULE sodium = GetModuleHandleW(L"libsodium.dll"); + if (sodium) { + std::wstring p; + if (get_module_path(sodium, p)) { + uint32_t now_crc = file_crc32(p); + uint32_t base_crc = sodium_crc_baseline.load(); + if (base_crc != 0 && now_crc != base_crc) { + error(XorStr("libsodium checksum mismatch.")); + } + } + } + periodic_done: if (check_section_integrity(XorStr(".text").c_str(), false)) { const int streak = integrity_fail_streak.fetch_add(1) + 1; From fb4337961ef117d7565788a6617959c1b6cc604e Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 16:46:16 -0500 Subject: [PATCH 102/116] add ci workflow --- .github/workflows/ci.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8be579d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,22 @@ +name: ci + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: setup msbuild + uses: microsoft/setup-msbuild@v2 + + - name: build (release x64) + shell: pwsh + run: | + msbuild .\\library.sln /p:Configuration=Release /p:Platform=x64 From 6e8e2027a5a2c092a7f46ecdfbe0195b26474fea Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 16:48:44 -0500 Subject: [PATCH 103/116] fix module checks helpers --- auth.cpp | 120 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 83 insertions(+), 37 deletions(-) diff --git a/auth.cpp b/auth.cpp index 5a031e0..a38e961 100644 --- a/auth.cpp +++ b/auth.cpp @@ -110,6 +110,9 @@ bool core_modules_signed(); static std::wstring get_system_dir(); static std::wstring get_syswow_dir(); void snapshot_prologues(); +static void snapshot_checkinit(); +static bool checkinit_ok(); +static void security_watchdog(); bool prologues_ok(); bool func_region_ok(const void* addr); bool timing_anomaly_detected(); @@ -212,8 +215,8 @@ void KeyAuth::api::init() seed = generate_random_number(); std::atexit([]() { cleanUpSeedData(seed); }); - if (!secrutiy_watchdog.exchange(true)) { - std::thread(security_watchdog).detach(); + if (!watchdog_started.exchange(true)) { + std::thread(security_watchdog).detach(); } CreateThread(0, 0, (LPTHREAD_START_ROUTINE)modify, 0, 0, 0); @@ -2436,7 +2439,84 @@ static std::wstring get_syswow_dir() return std::wstring(buf); } -static std::wstring normalize_path(std::string p) +static std::wstring normalize_path(std::wstring p) +{ + std::transform(p.begin(), p.end(), p.begin(), + [](wchar_t c) { return static_cast(towlower(c)); }); + while (!p.empty() && (p.back() == L'\\' || p.back() == L'/')) { + p.pop_back(); + } + return p; +} + +static bool get_module_path(HMODULE mod, std::wstring& out) +{ + wchar_t path[MAX_PATH] = {}; + if (!mod) + return false; + if (!GetModuleFileNameW(mod, path, MAX_PATH)) + return false; + out.assign(path); + return true; +} + +static bool module_in_system32(HMODULE mod) +{ + std::wstring mod_path; + if (!get_module_path(mod, mod_path)) + return false; + mod_path = normalize_path(mod_path); + + std::wstring sys = normalize_path(get_system_dir()); + if (sys.empty()) + return false; + + return mod_path.rfind(sys + L"\\", 0) == 0; +} + +static uint32_t file_crc32(const std::wstring& path) +{ + std::ifstream f(path, std::ios::binary); + if (!f.good()) return 0; + std::vector buf((std::istreambuf_iterator(f)), std::istreambuf_iterator()); + if (buf.empty()) return 0; + + uint32_t crc = 0xFFFFFFFFu; + for (uint8_t b : buf) { + crc ^= b; + for (int k = 0; k < 8; ++k) { + uint32_t mask = (crc & 1u) ? 0xFFFFFFFFu : 0u; + crc = (crc >> 1) ^ (0xEDB88320u & mask); + } + } + return ~crc; +} + +static bool critical_modules_safe() +{ + const wchar_t* system_mods[] = { L"wintrust.dll", L"crypt32.dll", L"bcrypt.dll" }; + for (const auto* name : system_mods) { + HMODULE mod = GetModuleHandleW(name); + if (!mod) return false; + if (!module_in_system32(mod)) return false; + } + + const wchar_t* app_mods[] = { L"libcurl.dll", L"libsodium.dll" }; + for (const auto* name : app_mods) { + HMODULE mod = GetModuleHandleW(name); + if (!mod) continue; + std::wstring path; + if (!get_module_path(mod, path)) return false; + std::wstring p = normalize_path(path); + + if (p.find(L"\\appdata\\") != std::wstring::npos || + p.find(L"\\temp\\") != std::wstring::npos || + p.find(L"\\downloads\\") != std::wstring::npos) { + return false; + } + } + return true; +} void snapshot_prologues() { @@ -2799,40 +2879,6 @@ static bool addr_in_module_handle(const void* addr, HMODULE mod) return addr >= base && addr < end; } -static bool get_export_address(HMODULE mod, const char* name, void*& out_addr) -{ - out_addr = nullptr; - if (!mod || !name) - return false; - auto base = reinterpret_cast(mod); - auto dos = reinterpret_cast(base); - if (dos->e_magic != IMAGE_DOS_SIGNATURE) - return false; - auto nt = reinterpret_cast(base + dos->e_lfanew); - if (nt->Signature != IMAGE_NT_SIGNATURE) - return false; - - auto& dir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; - if (!dir.VirtualAddress) - return false; - - auto exp = reinterpret_cast(base + dir.VirtualAddress); - auto names = reinterpret_cast(base + exp->AddressOfNames); - auto funcs = reinterpret_cast(base + exp->AddressOfFunctions); - auto ords = reinterpret_cast(base + exp->AddressOfNameOrdinals); - - for (DWORD i = 0; i < exp->NumberOfNames; ++i) { - const char* n = reinterpret_cast(base + names[i]); - if (_stricmp(n, name) == 0) { - WORD ord = ords[i]; - DWORD rva = funcs[ord]; - out_addr = base + rva; - return true; - } - } - return false; -} - static bool export_mismatch(const char* dll, const char* func) { HMODULE mod = GetModuleHandleA(dll); From ce969fd3c339ddf7d126f316912c41e50a13047d Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 16:51:05 -0500 Subject: [PATCH 104/116] fix hosts check typo and watchdog semicolon --- auth.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auth.cpp b/auth.cpp index a38e961..42dbfb6 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2326,7 +2326,7 @@ bool hosts_override_present(const std::string& host) [](unsigned char c) { return static_cast(std::tolower(c)); }); // word check example, this can always be improved - if (line.find(" " + host_lower) != std:string::npos || line.find("\t" + host_lower) != std::string::npos) + if (line.find(" " + host_lower) != std::string::npos || line.find("\t" + host_lower) != std::string::npos) return true; } return false; @@ -3103,7 +3103,7 @@ static void security_watchdog() while (true) { Sleep(15000); if (!checkinit_ok()) { - error(XorStr("security watchdog detected tamper.")) + error(XorStr("security watchdog detected tamper.")); } checkInit(); } From c016d14973d59e89d973f271946516f2932349b1 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 17:10:16 -0500 Subject: [PATCH 105/116] add forward decls and fix check_section_integrity signature --- auth.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/auth.cpp b/auth.cpp index 42dbfb6..4e6c2ad 100644 --- a/auth.cpp +++ b/auth.cpp @@ -126,6 +126,9 @@ void snapshot_text_page_protections(); bool text_page_protections_ok(); void snapshot_data_page_protections(); bool data_page_protections_ok(); +static bool get_text_section_info(std::uintptr_t& base, size_t& size); +static uint32_t rolling_crc32(const uint8_t* data, size_t len, size_t window = 64, size_t stride = 16); +static bool get_export_address(HMODULE mod, const char* name, void*& out_addr); inline void secure_zero(std::string& value) noexcept; inline void securewipe(std::string& value) noexcept; @@ -3324,7 +3327,7 @@ void error(std::string message) { LI_FN(__fastfail)(0); } // code submitted in pull request from https://github.com/Roblox932 -integrity( const char *section_name, bool fix = false ) -> bool +auto check_section_integrity( const char *section_name, bool fix = false ) -> bool { const auto map_file = []( HMODULE hmodule ) -> std::tuple { From aad76fb742817e3ff88cfbb2c08a4fea4548c7c6 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 17:12:03 -0500 Subject: [PATCH 106/116] remove rolling_crc32 default args in definition --- auth.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth.cpp b/auth.cpp index 4e6c2ad..059955f 100644 --- a/auth.cpp +++ b/auth.cpp @@ -2646,7 +2646,7 @@ static bool get_text_section_info(std::uintptr_t& base, size_t& size) return false; } -static uint32_t rolling_crc32(const uint8_t* data, size_t len, size_t window = 64, size_t stride = 16) +static uint32_t rolling_crc32(const uint8_t* data, size_t len, size_t window, size_t stride) { if (!data || len < window) return 0; From 52cbd615f73249c09f91714539aa109e4de6b15c Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 17:15:13 -0500 Subject: [PATCH 107/116] ci: collect build output libs --- .github/workflows/ci.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8be579d..3a17f52 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,3 +20,17 @@ jobs: shell: pwsh run: | msbuild .\\library.sln /p:Configuration=Release /p:Platform=x64 + + - name: collect build output + shell: pwsh + run: | + New-Item -ItemType Directory -Force build-output | Out-Null + Get-ChildItem -Path . -Recurse -Filter *.lib | ForEach-Object { + Copy-Item -Path $_.FullName -Destination build-output -Force + } + + - name: upload build-output + uses: actions/upload-artifact@v4 + with: + name: build-output + path: build-output From cfe6239c522bb3014c54c46cfd8c489c554bc86a Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 18:30:58 -0500 Subject: [PATCH 108/116] harden public key storage and integrity checks --- auth.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/auth.cpp b/auth.cpp index 059955f..e553950 100644 --- a/auth.cpp +++ b/auth.cpp @@ -137,7 +137,22 @@ void cleanUpSeedData(const std::string& seed); std::string signature; std::string signatureTimestamp; bool initialized; -std::string API_PUBLIC_KEY = "5586b4bc69c7a4b487e4563a4cd96afd39140f919bd31cea7d1c6a1e8439422b"; +static constexpr uint8_t k_pubkey_xor1 = 0x5A; +static constexpr uint8_t k_pubkey_xor2 = 0xA5; +static constexpr uint64_t k_pubkey_fnv1a = 0x7553f24ca052d4b1ULL; +static const uint8_t k_pubkey_obf1[64] = { + 0x6f, 0x6f, 0x62, 0x6c, 0x38, 0x6e, 0x38, 0x39, 0x6c, 0x63, 0x39, 0x6d, 0x3b, 0x6e, 0x38, 0x6e, + 0x62, 0x6d, 0x3f, 0x6e, 0x6f, 0x6c, 0x69, 0x3b, 0x6e, 0x39, 0x3e, 0x63, 0x6c, 0x3b, 0x3c, 0x3e, + 0x69, 0x63, 0x6b, 0x6e, 0x6a, 0x3c, 0x63, 0x6b, 0x63, 0x38, 0x3e, 0x69, 0x6b, 0x39, 0x3f, 0x3b, + 0x6d, 0x3e, 0x6b, 0x39, 0x6c, 0x3b, 0x6b, 0x3f, 0x62, 0x6e, 0x69, 0x63, 0x6e, 0x68, 0x68, 0x38 +}; +static const uint8_t k_pubkey_obf2[64] = { + 0x90, 0x90, 0x9d, 0x93, 0xc7, 0x91, 0xc7, 0xc6, 0x93, 0x9c, 0xc6, 0x92, 0xc4, 0x91, 0xc7, 0x91, + 0x9d, 0x92, 0xc0, 0x91, 0x90, 0x93, 0x96, 0xc4, 0x91, 0xc6, 0xc1, 0x9c, 0x93, 0xc4, 0xc3, 0xc1, + 0x96, 0x9c, 0x94, 0x91, 0x95, 0xc3, 0x9c, 0x94, 0x9c, 0xc7, 0xc1, 0x96, 0x94, 0xc6, 0xc0, 0xc4, + 0x92, 0xc1, 0x94, 0xc6, 0x93, 0xc4, 0x94, 0xc0, 0x9d, 0x91, 0x96, 0x9c, 0x91, 0x97, 0x97, 0xc7 +}; +static std::atomic pubkey_hash_seen{ 0 }; bool KeyAuth::api::debug = false; std::atomic LoggedIn(false); std::atomic last_integrity_check{ 0 }; @@ -188,6 +203,59 @@ static inline void securewipe(std::string& value) noexcept secure_zero(value); } +static uint64_t fnv1a64_bytes(const uint8_t* data, size_t len) +{ + uint64_t h = 0xcbf29ce484222325ULL; + for (size_t i = 0; i < len; ++i) { + h ^= static_cast(data[i]); + h *= 0x100000001b3ULL; + } + return h; +} + +static std::string decode_pubkey_hex(const uint8_t* obf, size_t len, uint8_t key) +{ + std::string out; + out.resize(len); + for (size_t i = 0; i < len; ++i) { + out[i] = static_cast(obf[i] ^ key); + } + return out; +} + +static bool pubkey_memory_protect_ok() +{ + MEMORY_BASIC_INFORMATION mbi{}; + if (!VirtualQuery(k_pubkey_obf1, &mbi, sizeof(mbi))) + return false; + const DWORD p = mbi.Protect; + if (p & PAGE_GUARD) + return false; + if ((p & PAGE_READWRITE) || (p & PAGE_WRITECOPY) || + (p & PAGE_EXECUTE_READWRITE) || (p & PAGE_EXECUTE_WRITECOPY)) { + return false; + } + return true; +} + +static std::string get_public_key_hex() +{ + if (!pubkey_memory_protect_ok()) { + error(XorStr("public key memory protection tampered.")); + } + std::string a = decode_pubkey_hex(k_pubkey_obf1, sizeof(k_pubkey_obf1), k_pubkey_xor1); + std::string b = decode_pubkey_hex(k_pubkey_obf2, sizeof(k_pubkey_obf2), k_pubkey_xor2); + if (a != b) { + error(XorStr("public key mismatch detected.")); + } + const uint64_t h = fnv1a64_bytes(reinterpret_cast(a.data()), a.size()); + pubkey_hash_seen.store(h, std::memory_order_relaxed); + if (h != k_pubkey_fnv1a) { + error(XorStr("public key integrity failed.")); + } + return a; +} + struct ScopeWipe final { std::string* value; explicit ScopeWipe(std::string& v) noexcept : value(&v) {} @@ -215,6 +283,7 @@ void KeyAuth::api::init() std::thread(runChecks).detach(); snapshot_prologues(); snapshot_checkinit(); + (void)get_public_key_hex(); seed = generate_random_number(); std::atexit([]() { cleanUpSeedData(seed); }); @@ -1965,7 +2034,8 @@ int VerifyPayload(std::string signature, std::string timestamp, std::string body exit(5); } - if (sodium_hex2bin(pk, sizeof(pk), API_PUBLIC_KEY.c_str(), API_PUBLIC_KEY.length(), NULL, NULL, NULL) != 0) { + const std::string pubkey_hex = get_public_key_hex(); + if (sodium_hex2bin(pk, sizeof(pk), pubkey_hex.c_str(), pubkey_hex.length(), NULL, NULL, NULL) != 0) { std::cerr << "[ERROR] Failed to parse public key hex.\n"; MessageBoxA(0, "Signature verification failed (invalid public key)", "KeyAuth", MB_ICONERROR); exit(6); @@ -1975,7 +2045,7 @@ int VerifyPayload(std::string signature, std::string timestamp, std::string body std::cout << "[DEBUG] Signature: " << signature << std::endl; std::cout << "[DEBUG] Body: " << body << std::endl; std::cout << "[DEBUG] Message (timestamp + body): " << message << std::endl; - std::cout << "[DEBUG] Public Key: " << API_PUBLIC_KEY << std::endl;*/ + std::cout << "[DEBUG] Public Key: " << pubkey_hex << std::endl;*/ if (crypto_sign_ed25519_verify_detached(sig, reinterpret_cast(message.c_str()), From c4dc972950b90de07f761da2076d7b26a2b429ac Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 18:34:35 -0500 Subject: [PATCH 109/116] add client-side anti-emulation checks --- auth.cpp | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 192 insertions(+), 2 deletions(-) diff --git a/auth.cpp b/auth.cpp index e553950..c5019d5 100644 --- a/auth.cpp +++ b/auth.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #include #include @@ -153,6 +154,8 @@ static const uint8_t k_pubkey_obf2[64] = { 0x92, 0xc1, 0x94, 0xc6, 0x93, 0xc4, 0x94, 0xc0, 0x9d, 0x91, 0x96, 0x9c, 0x91, 0x97, 0x97, 0xc7 }; static std::atomic pubkey_hash_seen{ 0 }; +static std::atomic pubkey_protect_ready{ false }; +static DWORD pubkey_protect_baseline = 0; bool KeyAuth::api::debug = false; std::atomic LoggedIn(false); std::atomic last_integrity_check{ 0 }; @@ -223,6 +226,143 @@ static std::string decode_pubkey_hex(const uint8_t* obf, size_t len, uint8_t key return out; } +static std::string to_lower_ascii(std::string v) +{ + std::transform(v.begin(), v.end(), v.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return v; +} + +static bool list_contains_any(const std::string& hay, const std::vector& needles) +{ + for (const auto& n : needles) { + if (hay.find(n) != std::string::npos) + return true; + } + return false; +} + +static bool suspicious_processes_present() +{ + const std::vector bad = { + "fiddler", "mitmproxy", "charles", "httpdebugger", "proxifier", + "burpsuite", "wireshark", "tshark", "x64dbg", "x32dbg", + "ollydbg", "ida", "cheatengine", "processhacker", + "keyauth", "emulator" + }; + HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snap == INVALID_HANDLE_VALUE) + return false; + PROCESSENTRY32 pe{}; + pe.dwSize = sizeof(pe); + if (!Process32First(snap, &pe)) { + CloseHandle(snap); + return false; + } + do { + std::string name = to_lower_ascii(pe.szExeFile); + if (list_contains_any(name, bad)) { + CloseHandle(snap); + return true; + } + } while (Process32Next(snap, &pe)); + CloseHandle(snap); + return false; +} + +static bool suspicious_modules_present() +{ + const std::vector bad = { + "fiddlercore", "mitm", "charles", "httpdebugger", "proxifier", + "keyauth", "emulator", "dbghelp", "symsrv", "detours" + }; + HMODULE mods[1024]; + DWORD needed = 0; + if (!EnumProcessModules(GetCurrentProcess(), mods, sizeof(mods), &needed)) + return false; + const size_t count = needed / sizeof(HMODULE); + char name[MAX_PATH]{}; + for (size_t i = 0; i < count; ++i) { + if (GetModuleFileNameA(mods[i], name, MAX_PATH)) { + std::string lower = to_lower_ascii(name); + if (list_contains_any(lower, bad)) + return true; + } + } + return false; +} + +static bool suspicious_windows_present() +{ + const std::vector bad = { + "fiddler", "mitmproxy", "charles", "burp", "http debugger", + "x64dbg", "x32dbg", "ollydbg", "ida", "cheat engine", + "process hacker", "keyauth", "emulator" + }; + struct Ctx { const std::vector* bad; bool hit; }; + Ctx ctx{ &bad, false }; + auto cb = [](HWND hwnd, LPARAM lparam) -> BOOL { + auto* c = reinterpret_cast(lparam); + if (!IsWindowVisible(hwnd)) + return TRUE; + char title[512]{}; + GetWindowTextA(hwnd, title, sizeof(title)); + if (title[0] == '\0') + return TRUE; + std::string t = to_lower_ascii(title); + if (list_contains_any(t, *c->bad)) { + c->hit = true; + return FALSE; + } + return TRUE; + }; + EnumWindows(cb, reinterpret_cast(&ctx)); + return ctx.hit; +} + +static bool proxy_env_set() +{ + const char* p1 = std::getenv("HTTP_PROXY"); + const char* p2 = std::getenv("HTTPS_PROXY"); + const char* p3 = std::getenv("ALL_PROXY"); + return (p1 && *p1) || (p2 && *p2) || (p3 && *p3); +} + +static bool url_points_to_loopback(const std::string& url) +{ + const std::string host = extract_host(url); + if (host.empty()) + return false; + std::string h = to_lower_ascii(host); + if (h == "localhost" || h == "127.0.0.1" || h == "::1") + return true; + + ADDRINFOA hints{}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + ADDRINFOA* res = nullptr; + if (getaddrinfo(host.c_str(), nullptr, &hints, &res) != 0) + return false; + bool loopback = false; + for (auto* p = res; p; p = p->ai_next) { + if (p->ai_family == AF_INET) { + auto* in = reinterpret_cast(p->ai_addr); + if ((ntohl(in->sin_addr.s_addr) & 0xFF000000u) == 0x7F000000u) { + loopback = true; + break; + } + } else if (p->ai_family == AF_INET6) { + auto* in6 = reinterpret_cast(p->ai_addr); + if (IN6_IS_ADDR_LOOPBACK(&in6->sin6_addr)) { + loopback = true; + break; + } + } + } + freeaddrinfo(res); + return loopback; +} + static bool pubkey_memory_protect_ok() { MEMORY_BASIC_INFORMATION mbi{}; @@ -235,6 +375,10 @@ static bool pubkey_memory_protect_ok() (p & PAGE_EXECUTE_READWRITE) || (p & PAGE_EXECUTE_WRITECOPY)) { return false; } + if (pubkey_protect_ready.load(std::memory_order_relaxed)) { + if (pubkey_protect_baseline != p) + return false; + } return true; } @@ -256,6 +400,40 @@ static std::string get_public_key_hex() return a; } +static bool module_contains_ascii(const std::string& needle) +{ + if (needle.empty()) + return false; + MODULEINFO mi{}; + if (!GetModuleInformation(GetCurrentProcess(), GetModuleHandleA(nullptr), &mi, sizeof(mi))) + return false; + const uint8_t* base = reinterpret_cast(mi.lpBaseOfDll); + const uint8_t* end = base + mi.SizeOfImage; + const uint8_t* p = base; + while (p < end) { + MEMORY_BASIC_INFORMATION mbi{}; + if (!VirtualQuery(p, &mbi, sizeof(mbi))) + break; + const uint8_t* region = reinterpret_cast(mbi.BaseAddress); + const uint8_t* region_end = region + mbi.RegionSize; + if (region_end > end) + region_end = end; + const DWORD protect = mbi.Protect; + const bool readable = (protect & PAGE_READONLY) || (protect & PAGE_READWRITE) || + (protect & PAGE_EXECUTE_READ) || (protect & PAGE_EXECUTE_READWRITE) || + (protect & PAGE_WRITECOPY) || (protect & PAGE_EXECUTE_WRITECOPY); + if (mbi.State == MEM_COMMIT && readable) { + const char* cbegin = reinterpret_cast(region); + const char* cend = reinterpret_cast(region_end); + auto it = std::search(cbegin, cend, needle.begin(), needle.end()); + if (it != cend) + return true; + } + p = region_end; + } + return false; +} + struct ScopeWipe final { std::string* value; explicit ScopeWipe(std::string& v) noexcept : value(&v) {} @@ -3202,6 +3380,12 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { !func_region_ok(reinterpret_cast(&check_section_integrity))) { error(XorStr("function region check failed, possible hook detected.")); } + if (suspicious_processes_present() || suspicious_modules_present() || suspicious_windows_present()) { + error(XorStr("debugger/emulator/proxy detected.")); + } + if (proxy_env_set()) { + error(XorStr("proxy environment detected.")); + } if (!is_https_url(url)) { error(XorStr("API URL must use HTTPS.")); } @@ -3217,7 +3401,7 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { ScopeWipe host_lower_wipe(host_lower); std::transform(host_lower.begin(), host_lower.end(), host_lower.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); - if (!allowed_hosts.empty()) { + if (!allowed_hosts.empty()) { bool allowed = false; for (const auto& entry : allowed_hosts) { std::string entry_lower = entry; @@ -3237,8 +3421,11 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { } if (!allowed) { error(XorStr("API host is not in allowed host list.")); - } } + } + if (url_points_to_loopback(url)) { + error(XorStr("loopback or local host detected for API URL.")); + } if (host_is_keyauth(host_lower)) { if (is_ip_literal(host_lower)) { error(XorStr("API host must not be an IP literal.")); @@ -3944,6 +4131,9 @@ void integrity_check() { const auto last = last_integrity_check.load(); if (now - last > 30) { last_integrity_check.store(now); + if (suspicious_processes_present() || suspicious_modules_present() || suspicious_windows_present()) { + error(XorStr("debugger/emulator/proxy detected.")); + } if (check_section_integrity(XorStr(".text").c_str(), false)) { error(XorStr("check_section_integrity() failed, don't tamper with the program.")); } From 8beef942e6b9070a937fab05c22503427f516c96 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 18:36:32 -0500 Subject: [PATCH 110/116] fix wide process names and remove duplicate proxy_env_set --- auth.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/auth.cpp b/auth.cpp index c5019d5..8d936fb 100644 --- a/auth.cpp +++ b/auth.cpp @@ -233,6 +233,18 @@ static std::string to_lower_ascii(std::string v) return v; } +static std::string wide_to_utf8(const wchar_t* w) +{ + if (!w) + return {}; + int needed = WideCharToMultiByte(CP_UTF8, 0, w, -1, nullptr, 0, nullptr, nullptr); + if (needed <= 0) + return {}; + std::string out(static_cast(needed - 1), '\0'); + WideCharToMultiByte(CP_UTF8, 0, w, -1, out.data(), needed, nullptr, nullptr); + return out; +} + static bool list_contains_any(const std::string& hay, const std::vector& needles) { for (const auto& n : needles) { @@ -260,7 +272,7 @@ static bool suspicious_processes_present() return false; } do { - std::string name = to_lower_ascii(pe.szExeFile); + std::string name = to_lower_ascii(wide_to_utf8(pe.szExeFile)); if (list_contains_any(name, bad)) { CloseHandle(snap); return true; @@ -2388,17 +2400,6 @@ static bool is_https_url(const std::string& url) return true; } -static bool proxy_env_set() -{ - const char* keys[] = { "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "http_proxy", "https_proxy", "all_proxy" }; - for (const char* k : keys) { - const char* v = std::getenv(k); - if (v && *v) - return true; - } - return false; -} - static bool winhttp_proxy_set() { WINHTTP_PROXY_INFO info{}; From 85b255377fcac3f7311433ab77b70c7acc80eaa5 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Thu, 5 Mar 2026 19:07:41 -0500 Subject: [PATCH 111/116] add entry jmp/reg hook checks --- auth.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/auth.cpp b/auth.cpp index 8d936fb..fcfd7f7 100644 --- a/auth.cpp +++ b/auth.cpp @@ -122,6 +122,8 @@ void heartbeat_thread(KeyAuth::api* instance); void snapshot_text_hashes(); bool text_hashes_ok(); bool detour_suspect(const uint8_t* p); +static bool entry_is_jmp_or_call(const void* fn); +static bool entry_is_reg_jump(const void* fn); bool import_addresses_ok(); void snapshot_text_page_protections(); bool text_page_protections_ok(); @@ -3106,6 +3108,26 @@ bool detour_suspect(const uint8_t* p) return false; } +static bool entry_is_jmp_or_call(const void* fn) +{ + if (!fn) return false; + const uint8_t* p = reinterpret_cast(fn); + if (p[0] == 0xE9) return true; // jmp rel32 + if (p[0] == 0xFF && p[1] == 0x25) return true; // jmp [rip+imm32] + if (p[0] == 0xE8) return true; // call rel32 + if (p[0] == 0x68 && p[5] == 0xC3) return true; // push imm32; ret + return false; +} + +static bool entry_is_reg_jump(const void* fn) +{ + if (!fn) return false; + const uint8_t* p = reinterpret_cast(fn); + if (p[0] == 0xFF && (p[1] & 0xF8) == 0xE0) return true; // jmp reg + if (p[0] == 0xFF && (p[1] & 0xF8) == 0xD0) return true; // call reg + return false; +} + static bool addr_in_module(const void* addr, const wchar_t* module_name) { HMODULE mod = module_name ? GetModuleHandleW(module_name) : GetModuleHandle(nullptr); @@ -3381,6 +3403,16 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { !func_region_ok(reinterpret_cast(&check_section_integrity))) { error(XorStr("function region check failed, possible hook detected.")); } + if (entry_is_jmp_or_call(reinterpret_cast(&VerifyPayload)) || + entry_is_jmp_or_call(reinterpret_cast(&checkInit)) || + entry_is_jmp_or_call(reinterpret_cast(&integrity_check)) || + entry_is_jmp_or_call(reinterpret_cast(&check_section_integrity)) || + entry_is_reg_jump(reinterpret_cast(&VerifyPayload)) || + entry_is_reg_jump(reinterpret_cast(&checkInit)) || + entry_is_reg_jump(reinterpret_cast(&integrity_check)) || + entry_is_reg_jump(reinterpret_cast(&check_section_integrity))) { + error(XorStr("entry-point hook detected (jmp/call stub).")); + } if (suspicious_processes_present() || suspicious_modules_present() || suspicious_windows_present()) { error(XorStr("debugger/emulator/proxy detected.")); } @@ -4010,6 +4042,16 @@ void checkInit() { !detour_suspect(reinterpret_cast(&VerifyPayload)) && !detour_suspect(reinterpret_cast(&checkInit)) && !detour_suspect(reinterpret_cast(&error)) && + !entry_is_jmp_or_call(reinterpret_cast(&VerifyPayload)) && + !entry_is_jmp_or_call(reinterpret_cast(&checkInit)) && + !entry_is_jmp_or_call(reinterpret_cast(&error)) && + !entry_is_jmp_or_call(reinterpret_cast(&integrity_check)) && + !entry_is_jmp_or_call(reinterpret_cast(&check_section_integrity)) && + !entry_is_reg_jump(reinterpret_cast(&VerifyPayload)) && + !entry_is_reg_jump(reinterpret_cast(&checkInit)) && + !entry_is_reg_jump(reinterpret_cast(&error)) && + !entry_is_reg_jump(reinterpret_cast(&integrity_check)) && + !entry_is_reg_jump(reinterpret_cast(&check_section_integrity)) && prologues_ok() && func_region_ok(reinterpret_cast(&VerifyPayload)) && func_region_ok(reinterpret_cast(&checkInit)) && From 7343face1513cb8667b3c0c753971457f43f248d Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Fri, 6 Mar 2026 08:06:23 -0500 Subject: [PATCH 112/116] reduce false positives in anti-emu lists --- auth.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/auth.cpp b/auth.cpp index fcfd7f7..e6fb39b 100644 --- a/auth.cpp +++ b/auth.cpp @@ -261,8 +261,7 @@ static bool suspicious_processes_present() const std::vector bad = { "fiddler", "mitmproxy", "charles", "httpdebugger", "proxifier", "burpsuite", "wireshark", "tshark", "x64dbg", "x32dbg", - "ollydbg", "ida", "cheatengine", "processhacker", - "keyauth", "emulator" + "ollydbg", "ida", "cheatengine", "processhacker" }; HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (snap == INVALID_HANDLE_VALUE) @@ -288,7 +287,7 @@ static bool suspicious_modules_present() { const std::vector bad = { "fiddlercore", "mitm", "charles", "httpdebugger", "proxifier", - "keyauth", "emulator", "dbghelp", "symsrv", "detours" + "detours" }; HMODULE mods[1024]; DWORD needed = 0; @@ -311,7 +310,7 @@ static bool suspicious_windows_present() const std::vector bad = { "fiddler", "mitmproxy", "charles", "burp", "http debugger", "x64dbg", "x32dbg", "ollydbg", "ida", "cheat engine", - "process hacker", "keyauth", "emulator" + "process hacker" }; struct Ctx { const std::vector* bad; bool hit; }; Ctx ctx{ &bad, false }; From 57b7c77ff553f50fa6e0084503d40481c79ae7d7 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Fri, 6 Mar 2026 10:49:36 -0500 Subject: [PATCH 113/116] reduce ntdll stub false positives --- auth.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/auth.cpp b/auth.cpp index e6fb39b..a872a68 100644 --- a/auth.cpp +++ b/auth.cpp @@ -3190,10 +3190,20 @@ static bool ntdll_syscall_stub_tampered(const char* name) const uint8_t* p = reinterpret_cast(fn); #ifdef _WIN64 - if (!(p[0] == 0x4C && p[1] == 0x8B && p[2] == 0xD1)) return true; - if (!(p[3] == 0xB8)) return true; - if (!(p[8] == 0x0F && p[9] == 0x05)) return true; - if (!(p[10] == 0xC3)) return true; + // Allow optional ENDBR64 and padding bytes to reduce false positives on CET/hotpatch builds. + const uint8_t* q = p; + // Skip ENDBR64 (f3 0f 1e fa) + if (q[0] == 0xF3 && q[1] == 0x0F && q[2] == 0x1E && q[3] == 0xFA) { + q += 4; + } + // Skip common padding (int3/nop) + for (int i = 0; i < 8 && (*q == 0xCC || *q == 0x90); ++i) { + q++; + } + if (!(q[0] == 0x4C && q[1] == 0x8B && q[2] == 0xD1)) return true; // mov r10, rcx + if (!(q[3] == 0xB8)) return true; // mov eax, imm32 + if (!(q[8] == 0x0F && q[9] == 0x05)) return true; // syscall + if (!(q[10] == 0xC3)) return true; // ret #endif return false; } From 79d0b9c90c28d04c73ca2702e4c620fae2b8308f Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Fri, 6 Mar 2026 10:51:06 -0500 Subject: [PATCH 114/116] relax ntdll stub check --- auth.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/auth.cpp b/auth.cpp index a872a68..69afcb2 100644 --- a/auth.cpp +++ b/auth.cpp @@ -3190,8 +3190,11 @@ static bool ntdll_syscall_stub_tampered(const char* name) const uint8_t* p = reinterpret_cast(fn); #ifdef _WIN64 - // Allow optional ENDBR64 and padding bytes to reduce false positives on CET/hotpatch builds. + // Reduce false positives: only flag obvious hooks/trampolines or missing syscall. const uint8_t* q = p; + if (q[0] == 0xE9 || (q[0] == 0xFF && q[1] == 0x25)) { + return true; // direct jmp / jmp [rip+imm32] + } // Skip ENDBR64 (f3 0f 1e fa) if (q[0] == 0xF3 && q[1] == 0x0F && q[2] == 0x1E && q[3] == 0xFA) { q += 4; @@ -3200,10 +3203,17 @@ static bool ntdll_syscall_stub_tampered(const char* name) for (int i = 0; i < 8 && (*q == 0xCC || *q == 0x90); ++i) { q++; } - if (!(q[0] == 0x4C && q[1] == 0x8B && q[2] == 0xD1)) return true; // mov r10, rcx - if (!(q[3] == 0xB8)) return true; // mov eax, imm32 - if (!(q[8] == 0x0F && q[9] == 0x05)) return true; // syscall - if (!(q[10] == 0xC3)) return true; // ret + // Scan first 32 bytes for syscall; if absent, suspect hook. + bool has_syscall = false; + for (int i = 0; i < 32; ++i) { + if (q[i] == 0x0F && q[i + 1] == 0x05) { + has_syscall = true; + break; + } + } + if (!has_syscall) { + return true; + } #endif return false; } From 0aaac9bb8ec41fcab7a71e56ef04afbffa19f727 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Fri, 6 Mar 2026 14:44:51 -0500 Subject: [PATCH 115/116] security: enforce response signature verification in req() and harden hook detection --- auth.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/auth.cpp b/auth.cpp index 69afcb2..2d4d3ab 100644 --- a/auth.cpp +++ b/auth.cpp @@ -287,7 +287,7 @@ static bool suspicious_modules_present() { const std::vector bad = { "fiddlercore", "mitm", "charles", "httpdebugger", "proxifier", - "detours" + "detours", "minhook", "easyhook", "polyhook", "bypass", "inject", "hook" }; HMODULE mods[1024]; DWORD needed = 0; @@ -3561,6 +3561,14 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { error(XorStr("missing signature headers.")); } + // Enforce cryptographic payload verification on every request path. + const int verify_result = VerifyPayload(signature, signatureTimestamp, to_return); + if ((verify_result & 0xFFFF) != ((42 ^ 0xA5A5) & 0xFFFF)) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("payload verification marker mismatch.")); + } + char* effective_url = nullptr; if (curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effective_url) == CURLE_OK && effective_url) { if (!is_https_url(effective_url)) { From 3d1d4d8f2369ee4d147c00bafeb2781856796e4d Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Fri, 6 Mar 2026 14:49:40 -0500 Subject: [PATCH 116/116] security: add strict signature header validation and secondary req() verification --- auth.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/auth.cpp b/auth.cpp index 2d4d3ab..0af7ed4 100644 --- a/auth.cpp +++ b/auth.cpp @@ -3561,6 +3561,21 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { error(XorStr("missing signature headers.")); } + if (signature.size() != 128 || !std::all_of(signature.begin(), signature.end(), + [](unsigned char c) { return std::isxdigit(c) != 0; })) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("invalid signature header format.")); + } + + if (signatureTimestamp.size() < 10 || signatureTimestamp.size() > 13 || + !std::all_of(signatureTimestamp.begin(), signatureTimestamp.end(), + [](unsigned char c) { return c >= '0' && c <= '9'; })) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("invalid signature timestamp header format.")); + } + // Enforce cryptographic payload verification on every request path. const int verify_result = VerifyPayload(signature, signatureTimestamp, to_return); if ((verify_result & 0xFFFF) != ((42 ^ 0xA5A5) & 0xFFFF)) { @@ -3569,6 +3584,36 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) { error(XorStr("payload verification marker mismatch.")); } + // Independent verification path so hooking one verifier is insufficient. + if (sodium_init() < 0) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("libsodium init failed in request guard.")); + } + + unsigned char sig_guard[64] = { 0 }; + unsigned char pk_guard[32] = { 0 }; + if (sodium_hex2bin(sig_guard, sizeof(sig_guard), signature.c_str(), signature.size(), nullptr, nullptr, nullptr) != 0) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("signature decode failed in request guard.")); + } + const std::string pubkey_hex_guard = get_public_key_hex(); + if (sodium_hex2bin(pk_guard, sizeof(pk_guard), pubkey_hex_guard.c_str(), pubkey_hex_guard.size(), nullptr, nullptr, nullptr) != 0) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("public key decode failed in request guard.")); + } + const std::string signed_message_guard = signatureTimestamp + to_return; + if (crypto_sign_ed25519_verify_detached(sig_guard, + reinterpret_cast(signed_message_guard.data()), + signed_message_guard.size(), + pk_guard) != 0) { + if (req_headers) curl_slist_free_all(req_headers); + curl_easy_cleanup(curl); + error(XorStr("signature verify failed in request guard.")); + } + char* effective_url = nullptr; if (curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effective_url) == CURLE_OK && effective_url) { if (!is_https_url(effective_url)) {