EMACLAB Anticheat Driver, Part 4: Hooks
- File Name: EMAC-Driver-x64.sys
- TimeDateStamp: 0x67CAFFCE (Friday, 7 March 2025 14:16:46 GMT)
- Protector: VMProtect 3.8+
Syscall hooks
Recently i have seen some people saying they where unable to read the game memory for some reason, and now you’ll understand why that’s a thing: they’re intercepting syscalls.
Infinityhook
A very long time ago Microsoft implemented something called PatchGuard (a.k.a KPP), that’s a mechanism to protect the system integrity by actively verifying critical system structures and code. Today, it’s not possible to directly hook system routines or modify the SSDT tables without triggering a BSOD, but some people found a way (and pretty more ways) to defeat PatchGuard or simply get around it. If your goal is simply to intercept system calls then everdox/infinityhook is your go to. But wait, wasn’t infinityhook patched like 6 years ago? Yes it was, but it was quickly found a way to revive it, this repo: wsnbbhehao/infinity-hook-pro_win7_win11 for example, is a solution that works from Win7 up to latested Windows 11 version.
In all honesty i wasn’t expecting such a thing from EMACLAB since there’s no other anticheat (besides some controversial chinese anticheats) that actually uses infinityhook AFAIK.
InfinityHook abuses ETW (Event Tracing for Windows) infrastructure. The HalpCollectPmcCounters function is called during syscall dispatch and leaves a predictable stack layout with magic marker values. EMAC scans the stack for these markers and replaces the syscall function pointer.
InfinityHook Infrastructure
| Function | Address | Purpose |
|---|---|---|
EmacGetCpuClock | 0xBCF4C150 | Main callback invoked by HAL PMC collection |
EmacInitializeInfinityHook | 0xBCF4C2B4 | Initialization — hooks HalpCollectPmcCounters |
EmacInfinityHookSetup | 0xBCF4A794 | Registers syscall interceptions into the hook list |
EmacInfinityHookHandler | 0xBCF4A8F0 | Stack scanner that performs the actual pointer replacement |
EmacCheckStackIntegrity | 0xBCF4A3B0 | Validates stack integrity during dispatch |
Magic Markers
#define INFINITY_HOOK_MARKER_START 0xEAADDEEAADDEADDE
#define INFINITY_HOOK_MARKER_END 0xAEADDEEADAEAADDE
// Syscall pointer at marker + 9 QWORDs
It’s very easy to identify this as infinityhook due to the magic constants values…
char EmacGetCpuClock()
{
int NtKiSystemCall64Offset; // edx
void **v1; // rbx
unsigned __int64 i; // rax
int v3; // r9d
int v4; // ecx
__int64 v5; // rsi
__int64 v6; // rdi
unsigned __int64 *j; // rbx
void *retaddr; // [rsp+28h] [rbp+0h] BYREF
unsigned __int64 v10; // [rsp+30h] [rbp+8h] BYREF
__int64 v11; // [rsp+38h] [rbp+10h] BYREF
dword_FFFFF801BD006AFC = 1;
NtKiSystemCall64Offset = FindNtKiSystemCall64Offset();
dword_FFFFF801BD006AF8 = NtKiSystemCall64Offset;
v1 = &retaddr;
LOBYTE(i) = -(((unsigned __int64)IoGetStackLimits ^ qword_FFFFF801BCFACC40) > qword_FFFFF801BCFACC38);
v11 = 0i64;
v10 = 0i64;
if ( !byte_FFFFF801BCFACDA0 && NtKiSystemCall64Offset != -1 )
{
v3 = *(_DWORD *)KeGetCurrentThread();
for ( i = g_EmacInfinityHookList; i != g_EmacInfinityHookListEnd; i += 24i64 )
{
v4 = *(_DWORD *)(i + 4);
if ( v4 != -1 && v4 == v3 )
{
v5 = *(_QWORD *)(i + 16);
v6 = *(_QWORD *)(i + 8);
if ( v5 )
{
LOBYTE(i) = ((__int64 (__fastcall *)(__int64 *, unsigned __int64 *))(((unsigned __int64)IoGetStackLimits ^ qword_FFFFF801BCFACC40) & -(__int64)(((unsigned __int64)IoGetStackLimits ^ qword_FFFFF801BCFACC40) > qword_FFFFF801BCFACC38)))(
&v11,
&v10);
if ( (unsigned __int64)&retaddr < v10 )
{
while ( 1 )
{
i = (unsigned __int64)(v1 + 1);
if ( *(_WORD *)v1 == 0xF33 && (*(_DWORD *)i == 0x501802 || *(_DWORD *)i == 0x601802) )
break;
++v1;
if ( i >= v10 )
return i;
}
for ( j = (unsigned __int64 *)(v1 + 2); (unsigned __int64)j < v10; ++j )
{
i = *j;
if ( *j >= 0xEAADDEEAADDEADDEui64 && i < 0xAEADDEEADAEAADDEui64 )
{
i = (unsigned __int64)(j + 9);
if ( (unsigned __int64)(j + 9) >= v10 )
return i;
if ( *(_QWORD *)i == v5 && v6 )
{
*(_QWORD *)i = v6;
return i;
}
}
}
}
}
return i;
}
}
}
return i;
}
Iterate throught syscall hook handlers lists, checks if syscall index and syscall routine matches, proceeds to infinityhook the stack. If you’re unfamiliar with this code i recommend taking a look at their respective repo’s.
Handlers
Good, now we wan’t to know which syscalls are being intercepted and what’s actually being checked and done for each handler.
Here’s the list of 14 intercepted system routines, their handler functions, and respective actions:
| Syscall | Handler Function | Behavior |
|---|---|---|
| NtCreateThreadEx | EmacNtCreateThreadExHandler | Checks if target is game process. Verifies StartRoutine against DbgBreakPoint, DbgUiRemoteBreakin, DbgUserBreakPoint. Sets flag for report. |
| NtCreateThread | EmacNtCreateThreadHandler | Same checks as NtCreateThreadEx. |
| NtQueueApcThread | EmacNtQueueApcThreadHandler | Checks if target is game process. Verifies APC routine against debug break functions AND LoadLibraryA/W/ExA/ExW, LdrLoadDll. Reports code injection attempts. |
| NtQueueApcThreadEx | EmacNtQueueApcThreadExHandler | Same as NtQueueApcThread. |
| NtSetContextThread | EmacNtSetContextThreadHandler | Checks if target is game process. Examines Context->Rip for suspicious values. |
| NtAllocateVirtualMemory | EmacNtAllocateVirtualMemoryHandler | Whitelists x86launcher.exe and x64launcher.exe (Steam). Blocks and reports other callers. This is a Steam handle fix. |
| NtFreeVirtualMemory | EmacNtFreeVirtualMemoryHandler | Fixes edge cases for Steam launcher and csrss. No reports generated. |
| NtProtectVirtualMemory | EmacNtProtectVirtualMemoryHandler | Fixes edge cases for Steam launcher and csrss. No reports generated. |
| NtWriteVirtualMemory | EmacNtWriteVirtualMemoryHandler | Reports if current process is NOT Steam or csrss AND target is game process. |
| NtReadVirtualMemory | EmacNtReadVirtualMemoryHandler | Reports if any non-game process reads within game’s region size or engine2.dll region. |
| NtMapViewOfSection | EmacNtMapViewOfSectionHandler | Fixes edge cases for Steam/csrss. No reports. |
| NtUnmapViewOfSection | EmacNtUnmapViewOfSectionHandler | Fixes edge cases for Steam/csrss. No reports. |
| NtUserFindWindowEx | EmacNtUserFindWindowExHandler | Reports if non-Steam/non-game/non-EMAC process queries for window name containing L"Counter-". |
| NtUserSendInput | EmacNtUserSendInputHandler | Blocks ALL simulated input from any process and generates a report. |
Report Functions
Each handler has a corresponding report function that logs the event for later retrieval via IOCTL:
| Report Function | Tracks |
|---|---|
EmacReportNtCreateThread | Thread creation in game |
EmacReportNtCreateThreadEx | Extended thread creation |
EmacReportCreateThread | Generic thread creation |
EmacReportNtQueueApcThreadEx | APC injection attempts |
EmacReportNtReadVirtualMemory | Memory reads from game |
EmacReportNtUserFindWindowEx | Window enumeration of game |
EmacReportNtSendUserInput | Simulated input blocking |
EmacNtWriteVirtualMemoryReport | Memory writes to game |
EmacReportNtAllocateVirtualMemory | Memory allocation in game |
Good sneakie
That’s surely an extra layer of security, but in reality they had to infinityhook to fix some holes that Windows and Steam itself leaves in the system, because of HANDLE’s.
The only downside is that all the handlers are calling a virtualized function (sub_FFFFF801BCF4A358), i am not sure if that’s appropriate when hooking system calls…
the function called obtains the appropriate original procedure for the hook 
uhh… i can see that’s a vmenter 
It doesn’t really ends here
There’s another interesting aspect of the anticheat, i have discovered that they will also try to inline patch the system when some conditions are met.
bool EmacCanHookSystem()
{
int retLength; // [rsp+30h] [rbp+8h] BYREF
__int64 v2; // [rsp+38h] [rbp+10h] BYREF
v2 = 8i64;
retLength = 0;
return ((int (__fastcall *)(__int64, __int64 *, __int64, int *))((ZwQuerySystemInformation ^ qword_FFFFF801BCFACC40) & -(__int64)((ZwQuerySystemInformation ^ (unsigned __int64)qword_FFFFF801BCFACC40) > qword_FFFFF801BCFACC38)))(
103i64, // SystemCodeIntegrityInformation
&v2,
8i64,
&retLength) < 0
|| (v2 & 0x140000000000i64) != 0; // Check if test signing is enabled and if code integrity is disabled
}
They query system code integrity information via ZwQuerySystemInformation with class 103 (SystemCodeIntegrityInformation). If system currently has test-signing on and nointegritychecks on (v2 & 0x140000000000) it will install inline hooks in the following 6 kernel procedures:
| Kernel Function | Hook Handler | Purpose |
|---|---|---|
| KeAttachProcess | EmacKeAttachProcessHook | Monitor/block process context switching |
| KeStackAttachProcess | EmacKeStackAttachProcessHook | Monitor/block stack-based process attach |
| MmCopyVirtualMemory | EmacMmCopyVirtualMemoryHook | Intercept cross-process memory copies |
| RtlFindExportedRoutineByName | EmacRtlFindExportedRoutineByNameHook | Redirect export resolution to EMAC hooks |
| KeIsAttachedProcess | EmacKeIsAttachedProcessHook | Monitor attach status queries |
| MmGetSystemRoutineAddress | EmacMmGetSystemRoutineAddressHook | Redirect routine resolution to EMAC hooks |
Hook Infrastructure
| Function | Address | Purpose |
|---|---|---|
EmacInstallHooks | 0xBCF27788 (0xEDF bytes) | Master hook installation — resolves targets, writes inline patches |
EmacUninstallHooks | — | Removes all inline hooks (cleanup) |
EmacRestoreHookedPointers | — | Restores original function bytes |
EmacVerifyRoutineAddress | — | Validates hook target is in expected module |
EmacPhysicalExchangePointer | — | Atomic pointer exchange via physical memory mapping |
That’s very confusing to me because i have tried running the anticheat at those conditions and i was unable to run the game, they keep telling me to disable test-signing and enable integrity checks…
For that reason i am unsure if getting into this subject is really necessary, i provide the .IDB so anyone curios can take a closer look.
But generally speaking those hooks will verify the return address, the most interesting part is that hooks in RtlFindExportedRoutineByName and MmGetSystemRoutineAddress will ensure that any newly loaded driver will resolve functions through EMAC’s hooks, effectively extending protection to drivers loaded after EMAC. This is particularly clever because it means even if a cheat driver loads after the anticheat initializes, it will still be unable to get direct pointers to the hooked functions.
Note: In practice, the game refuses to launch with test-signing enabled, so these hooks may only activate in specific development/testing scenarios.
IDA decompiled snippets
Conclusion
Very interesting, that’s the kind of stuff that’s not really seen in other products.