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 img

uhh… i can see that’s a vmenter img

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

Hooks Snippets

Conclusion

Very interesting, that’s the kind of stuff that’s not really seen in other products.