5656#include < ws2tcpip.h>
5757#include < winhttp.h>
5858#include < windns.h>
59+ #include < tlhelp32.h>
5960#include < string>
6061#include < array>
6162
@@ -153,6 +154,8 @@ static const uint8_t k_pubkey_obf2[64] = {
153154 0x92 , 0xc1 , 0x94 , 0xc6 , 0x93 , 0xc4 , 0x94 , 0xc0 , 0x9d , 0x91 , 0x96 , 0x9c , 0x91 , 0x97 , 0x97 , 0xc7
154155};
155156static std::atomic<uint64_t > pubkey_hash_seen{ 0 };
157+ static std::atomic<bool > pubkey_protect_ready{ false };
158+ static DWORD pubkey_protect_baseline = 0 ;
156159bool KeyAuth::api::debug = false ;
157160std::atomic<bool > LoggedIn (false );
158161std::atomic<long long > last_integrity_check{ 0 };
@@ -223,6 +226,143 @@ static std::string decode_pubkey_hex(const uint8_t* obf, size_t len, uint8_t key
223226 return out;
224227}
225228
229+ static std::string to_lower_ascii (std::string v)
230+ {
231+ std::transform (v.begin (), v.end (), v.begin (),
232+ [](unsigned char c) { return static_cast <char >(std::tolower (c)); });
233+ return v;
234+ }
235+
236+ static bool list_contains_any (const std::string& hay, const std::vector<std::string>& needles)
237+ {
238+ for (const auto & n : needles) {
239+ if (hay.find (n) != std::string::npos)
240+ return true ;
241+ }
242+ return false ;
243+ }
244+
245+ static bool suspicious_processes_present ()
246+ {
247+ const std::vector<std::string> bad = {
248+ " fiddler" , " mitmproxy" , " charles" , " httpdebugger" , " proxifier" ,
249+ " burpsuite" , " wireshark" , " tshark" , " x64dbg" , " x32dbg" ,
250+ " ollydbg" , " ida" , " cheatengine" , " processhacker" ,
251+ " keyauth" , " emulator"
252+ };
253+ HANDLE snap = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0 );
254+ if (snap == INVALID_HANDLE_VALUE)
255+ return false ;
256+ PROCESSENTRY32 pe{};
257+ pe.dwSize = sizeof (pe);
258+ if (!Process32First (snap, &pe)) {
259+ CloseHandle (snap);
260+ return false ;
261+ }
262+ do {
263+ std::string name = to_lower_ascii (pe.szExeFile );
264+ if (list_contains_any (name, bad)) {
265+ CloseHandle (snap);
266+ return true ;
267+ }
268+ } while (Process32Next (snap, &pe));
269+ CloseHandle (snap);
270+ return false ;
271+ }
272+
273+ static bool suspicious_modules_present ()
274+ {
275+ const std::vector<std::string> bad = {
276+ " fiddlercore" , " mitm" , " charles" , " httpdebugger" , " proxifier" ,
277+ " keyauth" , " emulator" , " dbghelp" , " symsrv" , " detours"
278+ };
279+ HMODULE mods[1024 ];
280+ DWORD needed = 0 ;
281+ if (!EnumProcessModules (GetCurrentProcess (), mods, sizeof (mods), &needed))
282+ return false ;
283+ const size_t count = needed / sizeof (HMODULE);
284+ char name[MAX_PATH]{};
285+ for (size_t i = 0 ; i < count; ++i) {
286+ if (GetModuleFileNameA (mods[i], name, MAX_PATH)) {
287+ std::string lower = to_lower_ascii (name);
288+ if (list_contains_any (lower, bad))
289+ return true ;
290+ }
291+ }
292+ return false ;
293+ }
294+
295+ static bool suspicious_windows_present ()
296+ {
297+ const std::vector<std::string> bad = {
298+ " fiddler" , " mitmproxy" , " charles" , " burp" , " http debugger" ,
299+ " x64dbg" , " x32dbg" , " ollydbg" , " ida" , " cheat engine" ,
300+ " process hacker" , " keyauth" , " emulator"
301+ };
302+ struct Ctx { const std::vector<std::string>* bad; bool hit; };
303+ Ctx ctx{ &bad, false };
304+ auto cb = [](HWND hwnd, LPARAM lparam) -> BOOL {
305+ auto * c = reinterpret_cast <Ctx*>(lparam);
306+ if (!IsWindowVisible (hwnd))
307+ return TRUE ;
308+ char title[512 ]{};
309+ GetWindowTextA (hwnd, title, sizeof (title));
310+ if (title[0 ] == ' \0 ' )
311+ return TRUE ;
312+ std::string t = to_lower_ascii (title);
313+ if (list_contains_any (t, *c->bad )) {
314+ c->hit = true ;
315+ return FALSE ;
316+ }
317+ return TRUE ;
318+ };
319+ EnumWindows (cb, reinterpret_cast <LPARAM>(&ctx));
320+ return ctx.hit ;
321+ }
322+
323+ static bool proxy_env_set ()
324+ {
325+ const char * p1 = std::getenv (" HTTP_PROXY" );
326+ const char * p2 = std::getenv (" HTTPS_PROXY" );
327+ const char * p3 = std::getenv (" ALL_PROXY" );
328+ return (p1 && *p1) || (p2 && *p2) || (p3 && *p3);
329+ }
330+
331+ static bool url_points_to_loopback (const std::string& url)
332+ {
333+ const std::string host = extract_host (url);
334+ if (host.empty ())
335+ return false ;
336+ std::string h = to_lower_ascii (host);
337+ if (h == " localhost" || h == " 127.0.0.1" || h == " ::1" )
338+ return true ;
339+
340+ ADDRINFOA hints{};
341+ hints.ai_family = AF_UNSPEC;
342+ hints.ai_socktype = SOCK_STREAM;
343+ ADDRINFOA* res = nullptr ;
344+ if (getaddrinfo (host.c_str (), nullptr , &hints, &res) != 0 )
345+ return false ;
346+ bool loopback = false ;
347+ for (auto * p = res; p; p = p->ai_next ) {
348+ if (p->ai_family == AF_INET) {
349+ auto * in = reinterpret_cast <sockaddr_in*>(p->ai_addr );
350+ if ((ntohl (in->sin_addr .s_addr ) & 0xFF000000u ) == 0x7F000000u ) {
351+ loopback = true ;
352+ break ;
353+ }
354+ } else if (p->ai_family == AF_INET6) {
355+ auto * in6 = reinterpret_cast <sockaddr_in6*>(p->ai_addr );
356+ if (IN6_IS_ADDR_LOOPBACK (&in6->sin6_addr )) {
357+ loopback = true ;
358+ break ;
359+ }
360+ }
361+ }
362+ freeaddrinfo (res);
363+ return loopback;
364+ }
365+
226366static bool pubkey_memory_protect_ok ()
227367{
228368 MEMORY_BASIC_INFORMATION mbi{};
@@ -235,6 +375,10 @@ static bool pubkey_memory_protect_ok()
235375 (p & PAGE_EXECUTE_READWRITE) || (p & PAGE_EXECUTE_WRITECOPY)) {
236376 return false ;
237377 }
378+ if (pubkey_protect_ready.load (std::memory_order_relaxed)) {
379+ if (pubkey_protect_baseline != p)
380+ return false ;
381+ }
238382 return true ;
239383}
240384
@@ -256,6 +400,40 @@ static std::string get_public_key_hex()
256400 return a;
257401}
258402
403+ static bool module_contains_ascii (const std::string& needle)
404+ {
405+ if (needle.empty ())
406+ return false ;
407+ MODULEINFO mi{};
408+ if (!GetModuleInformation (GetCurrentProcess (), GetModuleHandleA (nullptr ), &mi, sizeof (mi)))
409+ return false ;
410+ const uint8_t * base = reinterpret_cast <const uint8_t *>(mi.lpBaseOfDll );
411+ const uint8_t * end = base + mi.SizeOfImage ;
412+ const uint8_t * p = base;
413+ while (p < end) {
414+ MEMORY_BASIC_INFORMATION mbi{};
415+ if (!VirtualQuery (p, &mbi, sizeof (mbi)))
416+ break ;
417+ const uint8_t * region = reinterpret_cast <const uint8_t *>(mbi.BaseAddress );
418+ const uint8_t * region_end = region + mbi.RegionSize ;
419+ if (region_end > end)
420+ region_end = end;
421+ const DWORD protect = mbi.Protect ;
422+ const bool readable = (protect & PAGE_READONLY) || (protect & PAGE_READWRITE) ||
423+ (protect & PAGE_EXECUTE_READ) || (protect & PAGE_EXECUTE_READWRITE) ||
424+ (protect & PAGE_WRITECOPY) || (protect & PAGE_EXECUTE_WRITECOPY);
425+ if (mbi.State == MEM_COMMIT && readable) {
426+ const char * cbegin = reinterpret_cast <const char *>(region);
427+ const char * cend = reinterpret_cast <const char *>(region_end);
428+ auto it = std::search (cbegin, cend, needle.begin (), needle.end ());
429+ if (it != cend)
430+ return true ;
431+ }
432+ p = region_end;
433+ }
434+ return false ;
435+ }
436+
259437struct ScopeWipe final {
260438 std::string* value;
261439 explicit ScopeWipe (std::string& v) noexcept : value(&v) {}
@@ -3202,6 +3380,12 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) {
32023380 !func_region_ok (reinterpret_cast <const void *>(&check_section_integrity))) {
32033381 error (XorStr (" function region check failed, possible hook detected." ));
32043382 }
3383+ if (suspicious_processes_present () || suspicious_modules_present () || suspicious_windows_present ()) {
3384+ error (XorStr (" debugger/emulator/proxy detected." ));
3385+ }
3386+ if (proxy_env_set ()) {
3387+ error (XorStr (" proxy environment detected." ));
3388+ }
32053389 if (!is_https_url (url)) {
32063390 error (XorStr (" API URL must use HTTPS." ));
32073391 }
@@ -3217,7 +3401,7 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) {
32173401 ScopeWipe host_lower_wipe (host_lower);
32183402 std::transform (host_lower.begin (), host_lower.end (), host_lower.begin (),
32193403 [](unsigned char c) { return static_cast <char >(std::tolower (c)); });
3220- if (!allowed_hosts.empty ()) {
3404+ if (!allowed_hosts.empty ()) {
32213405 bool allowed = false ;
32223406 for (const auto & entry : allowed_hosts) {
32233407 std::string entry_lower = entry;
@@ -3237,8 +3421,11 @@ std::string KeyAuth::api::req(std::string data, const std::string& url) {
32373421 }
32383422 if (!allowed) {
32393423 error (XorStr (" API host is not in allowed host list." ));
3240- }
32413424 }
3425+ }
3426+ if (url_points_to_loopback (url)) {
3427+ error (XorStr (" loopback or local host detected for API URL." ));
3428+ }
32423429 if (host_is_keyauth (host_lower)) {
32433430 if (is_ip_literal (host_lower)) {
32443431 error (XorStr (" API host must not be an IP literal." ));
@@ -3944,6 +4131,9 @@ void integrity_check() {
39444131 const auto last = last_integrity_check.load ();
39454132 if (now - last > 30 ) {
39464133 last_integrity_check.store (now);
4134+ if (suspicious_processes_present () || suspicious_modules_present () || suspicious_windows_present ()) {
4135+ error (XorStr (" debugger/emulator/proxy detected." ));
4136+ }
39474137 if (check_section_integrity (XorStr (" .text" ).c_str (), false )) {
39484138 error (XorStr (" check_section_integrity() failed, don't tamper with the program." ));
39494139 }
0 commit comments