Skip to content

Commit c4dc972

Browse files
committed
add client-side anti-emulation checks
1 parent cfe6239 commit c4dc972

1 file changed

Lines changed: 192 additions & 2 deletions

File tree

auth.cpp

Lines changed: 192 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
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
};
155156
static std::atomic<uint64_t> pubkey_hash_seen{ 0 };
157+
static std::atomic<bool> pubkey_protect_ready{ false };
158+
static DWORD pubkey_protect_baseline = 0;
156159
bool KeyAuth::api::debug = false;
157160
std::atomic<bool> LoggedIn(false);
158161
std::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+
226366
static 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+
259437
struct 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

Comments
 (0)