Reversing InfinityHook: Hooking Every System Call via the ETW CpuClock

About the author. I’m Colin Xiao — an independent reverse engineer, mostly working solo on contract jobs. I dig into compiled code of every kind, including but not limited to: Android and iOS apps, x86/x64/ARM native binaries, packers and obfuscators, DRM and license checks, network protocols and APIs, crypto and signature algorithms, malware, game binaries, and embedded/IoT firmware. I also handle large-scale data collection and vulnerability research. If you have any project in mind, I’d be glad to take it on — whatever it is, as long as it’s within my wheelhouse. Just reach out. You can reach me on X at @colinxiao_lab or on Telegram at https://t.me/ColinXiao666.

This research is published for educational and defensive purposes. The target sample is anonymized throughout.


Reversing InfinityHook: Hooking Every System Call via the ETW CpuClock

The core of InfinityHook is swapping WmipLoggerContext[CLCK_INDEX]->GetCpuClock. The original technique comes from everdox/InfinityHook. What follows is a teardown of how a commercial security suite’s kernel driver (guardbase.sys, version 5.12.0.4874) uses that technique to intercept every system call. Only the key parts are shown to keep this readable.

DriverEntry -> InitializeInfinityHook() spawns two threads: one initializes the InfinityHook, the other protects it.

__int64 InitializeInfinityHook()
{
  HANDLE ThreadHandle; // [rsp+40h] [rbp-58h]
  HANDLE Handle; // [rsp+48h] [rbp-50h]
  OBJECT_ATTRIBUTES ObjectAttributes; // [rsp+50h] [rbp-48h]
  unsigned int v4; // [rsp+80h] [rbp-18h]

  v4 = -1073741823;
  ThreadHandle = 0i64;
  ObjectAttributes.Length = 0;
  memset(&ObjectAttributes.RootDirectory, 0, 0x28ui64);
  if ( byte_3648C )
    return 3221225473i64;
  stru_365D0.Count = 1;
  stru_365D0.Owner = 0i64;
  stru_365D0.Contention = 0;
  KeInitializeEvent(&stru_365D0.Event, SynchronizationEvent, 0);
  sub_22980(&unk_36608);
  KeInitializeEvent(&Event, NotificationEvent, 0);
  byte_36938 = 1;
  stru_36568.Count = 1;
  stru_36568.Owner = 0i64;
  stru_36568.Contention = 0;
  KeInitializeEvent(&stru_36568.Event, SynchronizationEvent, 0);
  ObjectAttributes.Length = 48;
  ObjectAttributes.RootDirectory = 0i64;
  ObjectAttributes.Attributes = 512;
  ObjectAttributes.ObjectName = 0i64;
  ObjectAttributes.SecurityDescriptor = 0i64;
  ObjectAttributes.SecurityQualityOfService = 0i64;
  v4 = PsCreateSystemThread(
         &ThreadHandle,
         0x1FFFFFu,
         &ObjectAttributes,
         0i64,
         0i64,
         (PKSTART_ROUTINE)InfinityHookInitThread,
         0i64);
  if ( !v4 )
    ZwClose(ThreadHandle);
  ObjectAttributes.Length = 48;
  ObjectAttributes.RootDirectory = 0i64;
  ObjectAttributes.Attributes = 512;
  ObjectAttributes.ObjectName = 0i64;
  ObjectAttributes.SecurityDescriptor = 0i64;
  ObjectAttributes.SecurityQualityOfService = 0i64;
  v4 = PsCreateSystemThread(
         &Handle,
         0x1FFFFFu,
         &ObjectAttributes,
         0i64,
         0i64,
         (PKSTART_ROUTINE)InitializeInfinityHookProtectThread,
         0i64);
  if ( !v4 )
  {
    ObReferenceObjectByHandle(Handle, 0x1FFFFFu, PsThreadType, 0, &g_ThreadObject, 0i64);
    ZwClose(Handle);
  }
  return v4;
}
void __fastcall InfinityHookInitThread(PVOID StartContext)
{
  __int64 _RAX; // rax
  LARGE_INTEGER (*_RCX)(); // rcx
  __int64 _RCX; // rcx
  LARGE_INTEGER Interval; // [rsp+20h] [rbp-28h]
  _QWORD *getCpuClock; // [rsp+28h] [rbp-20h]
  int v6; // [rsp+30h] [rbp-18h]

  v6 = -1073741823;
  getCpuClock = 0i64;
  Interval.QuadPart = -30000000i64;
  while ( !LoadNtdll() )
    KeDelayExecutionThread(0, 0, &Interval);
  if ( (int)InitializeInfhookCore() >= 0 )
  {
    v6 = EnableEtwLogger();
    if ( v6 >= 0 )
    {
      v6 = GetCkcl(2u, (_QWORD *)infhookCore + 90115);
      if ( v6 >= 0 && *((_QWORD *)infhookCore + 90115) )
      {
        getCpuClock = (_QWORD *)(*((_QWORD *)infhookCore + 90115)
                               + (unsigned int)OFFSET_WMI_LOGGER_CONTEXT_CPU_CYCLE_CLOCK);
        if ( (unsigned __int8)MmIsAddressValid(getCpuClock)
          && (unsigned __int8)MmIsAddressValid((char *)getCpuClock + 7) )
        {
          if ( *getCpuClock >= MmSystemRangeStart )
          {
            _RAX = *((_QWORD *)infhookCore + 90115) + (unsigned int)OFFSET_WMI_LOGGER_CONTEXT_CPU_CYCLE_CLOCK;
            _RCX = InternalGetCpuClock;
            __asm { xchg    rcx, [rax] }
            *((_QWORD *)infhookCore + 90114) = _RCX;
            v6 = 0;
          }
          else
          {
            v6 = 0xC0000001;
          }
        }
        else
        {
          v6 = 0xC0000001;
        }
      }
      else
      {
        v6 = 0xC0000001;
      }
    }
    else
    {
      v6 = 0xC0000001;
    }
  }
  else
  {
    v6 = 0xC0000001;
  }
  PsTerminateSystemThread(0);
}
NTSTATUS __stdcall EnableEtwLogger()
{
  NTSTATUS result; // eax
  NTSTATUS v1; // [rsp+20h] [rbp-18h]
  NTSTATUS v2; // [rsp+20h] [rbp-18h]

  v1 = IfhpModifyTraceSettings(1);
  if ( v1 >= 0 )
    goto LABEL_9;
  v2 = IfhpModifyTraceSettings(0);
  if ( v2 < 0 )
    return v2;
  v1 = IfhpModifyTraceSettings(1);
  if ( v1 >= 0 )
LABEL_9:
    result = v1;
  else
    result = v1;
  return result;
}

IfhpModifyTraceSettings is a straight copy-paste of upstream code, so I’ll skip the listing and just show the comparison:

The main flow: initialize the infhookCore struct (a large struct; I didn’t reverse every member).

sub_2D210 checks two unknowns (irrelevant enough to skip).

Find the PID of explorer.exe.

Load L"\SystemRoot\system32\ntdll.dll", walk its export table, and save every nt* function into a table inside infhookCore.

NTSTATUS __stdcall InitializeInfhookCore()
{
  LARGE_INTEGER Interval; // [rsp+20h] [rbp-28h]
  NTSTATUS v2; // [rsp+28h] [rbp-20h]
  UNICODE_STRING DestinationString; // [rsp+30h] [rbp-18h]

  v2 = -1073741823;
  Interval.QuadPart = -30000000i64;
  if ( infhookCore && isZwTraceControlAvailable )
    return 0;
  while ( 1 )
  {
    if ( !infhookCore )
    {
      infhookCore = ExAllocatePoolWithTag(NonPagedPool, 720928ui64, 'hchk');
      if ( !infhookCore )
      {
        v2 = -1073741670;
        goto LABEL_26;
      }
      memset(infhookCore, 0, 0xB0020ui64);
    }
    if ( !*((_QWORD *)infhookCore + 90112) || !*((_QWORD *)infhookCore + 90113) )
    {
      v2 = sub_2D210((char *)infhookCore + 720896, (char *)infhookCore + 720904);
      if ( v2 < 0 || !*((_QWORD *)infhookCore + 90112) || !*((_QWORD *)infhookCore + 90113) )
      {
        v2 = -1073741823;
        goto LABEL_26;
      }
    }
    RtlInitUnicodeString(&DestinationString, L"explorer.exe");
    v2 = FindProcessByProcessName(&DestinationString, &g_ExplorerPID);
    if ( v2 >= 0 )
    {
      if ( g_ExplorerPID )
        break;
    }
    KeDelayExecutionThread(0, 0, &Interval);
  }
  v2 = LoadSSDTFunctionEntry();
  if ( v2 >= 0 )
  {
    v2 = LoadOffsetsVars();
    if ( v2 >= 0 )
    {
      v2 = LoadSSSDTFunctionEntry();
      if ( v2 >= 0 )
      {
        byte_3648D = 1;
        sub_2A640();
        ZwTraceControl = (__int64 (__fastcall *)(_QWORD, _QWORD, _QWORD, _QWORD, _DWORD, _QWORD))MakeZwTraceControl();
        if ( ZwTraceControl )
        {
          isZwTraceControlAvailable = 1;
          v2 = 0;
        }
        else
        {
          v2 = -1073741823;
        }
      }
      else
      {
        v2 = -1073741823;
      }
    }
    else
    {
      v2 = -1073741823;
    }
  }
  else
  {
    v2 = -1073741823;
  }
LABEL_26:
  if ( v2 )
  {
    if ( infhookCore )
    {
      ExFreePoolWithTag(infhookCore, 0x6863686Bu);
      infhookCore = 0i64;
    }
  }
  return v2;
}

Load offsets based on the OS version:

NTSTATUS __stdcall LoadOffsetsVars()
{
  NTSTATUS v1; // [rsp+20h] [rbp-18h]
  unsigned int v2; // [rsp+24h] [rbp-14h]

  v1 = 0;
  v2 = GetBuildNumber();
  if ( v2 > 14393 )
  {
    if ( v2 != 15063 && v2 != 16299 && v2 != 17134 && v2 != 17763 && v2 != 18362 )
      goto LABEL_18;
LABEL_17:
    LODWORD(OFFSET_WMI_LOGGER_CONTEXT_CPU_CYCLE_CLOCK) = 40;
    offset_SystemCallNumber = 0x80i64;
    PerfGlobalGroupMask = GetPerfGlobalGroupMask();
    goto LABEL_18;
  }
  switch ( v2 )
  {
    case 14393u:
      goto LABEL_17;
    case 610u:
      LODWORD(OFFSET_WMI_LOGGER_CONTEXT_CPU_CYCLE_CLOCK) = 24;
      offset_SystemCallNumber = 0x1F8i64;
      PerfGlobalGroupMask = GetPerfGlobalGroupMask2();
      break;
    case 620u:
      LODWORD(OFFSET_WMI_LOGGER_CONTEXT_CPU_CYCLE_CLOCK) = 40;
      offset_SystemCallNumber = 0x80i64;
      PerfGlobalGroupMask = GetPerfGlobalGroupMask3();
      break;
    case 630u:
    case 10240u:
    case 10586u:
      goto LABEL_17;
  }
LABEL_18:
  if ( v2 > 18362 )
    v1 = FindOffsetsFor18383();
  return v1;
}

Note that PerfGlobalGroupMask is located by scanning inside KeReleaseSpinLock:

_int64 GetPerfGlobalGroupMask()
{
  _BYTE *v1; // [rsp+20h] [rbp-38h]
  unsigned int v2; // [rsp+28h] [rbp-30h]
  __int64 v3; // [rsp+30h] [rbp-28h]
  UNICODE_STRING DestinationString; // [rsp+40h] [rbp-18h]

  v2 = 0;
  v3 = 0i64;
  RtlInitUnicodeString(&DestinationString, L"KeReleaseSpinLock");
  v1 = MmGetSystemRoutineAddress(&DestinationString);
  if ( !v1 )
    return 0i64;
  while ( v2 < 0xA )
  {
    if ( v1[v2] == 0x40
      && v1[v2 + 1] == 0x53
      && v1[v2 + 2] == 0x48
      && (unsigned __int8)v1[v2 + 3] == 0x83
      && (unsigned __int8)v1[v2 + 6] == 0xF6
      && v1[v2 + 7] == 5 )
    {
      v3 = (__int64)&v1[*(unsigned int *)&v1[v2 + 8] + 7];
      if ( !(unsigned __int8)MmIsAddressValid(v3) || !(unsigned __int8)MmIsAddressValid(v3 + 15) )
        return 0i64;
      return v3;
    }
    ++v2;
  }
  return v3;
}

Note that PerfGlobalGroupMask[2] & 0x40 controls whether ETW records SYSTEM_CALL events. The security suite monitors this and force-sets the bit so its hook can’t be switched off.

LoadSSSDTFunctionEntry is unremarkable — its only difference from LoadSSDTFunctionEntry is that it attaches to explorer before reading the entry.

Pull ZwTraceControl out of the SSDT entries retrieved above. (The suite appears to use NtTraceControl rather than ZwTraceControl — if so, that’s asking for a blue screen, but I didn’t dig in.)

__int64 MakeZwTraceControl()
{
  __int64 result; // rax
  char *v1; // [rsp+20h] [rbp-18h]

  v1 = FindZwForSSDT("NtTraceControl");
  if ( v1 )
    result = *((_QWORD *)v1 + 42);
  else
    result = 0i64;
  return result;
}

Get the ClckLoggerContext — the method differs by version:

NTSTATUS __fastcall GetCkcl(unsigned int type, _QWORD *clck)
{
  NTSTATUS result; // eax
  unsigned int v3; // [rsp+20h] [rbp-18h]
  unsigned int typee; // [rsp+40h] [rbp+8h]
  _QWORD *clck_1; // [rsp+48h] [rbp+10h]

  clck_1 = clck;
  typee = type;
  v3 = GetBuildNumber();
  if ( v3 <= 14393 )
  {
    if ( v3 != 14393 )
    {
      switch ( v3 )
      {
        case 610u:
          return GetCkclWmiLoggerContext7601(typee, clck_1);
        case 620u:
          return GetCkclWmiLoggerContext9200(typee, clck_1);
        case 630u:
          return GetCkclWmiLoggerContext9600(typee, clck_1);
      }
      if ( v3 != 10240 && v3 != 10586 )
        goto LABEL_19;
    }
    return GetCkclWmiLoggerContext(typee, clck_1);
  }
  if ( v3 == 15063 || v3 == 16299 || v3 == 17134 || v3 == 17763 || v3 == 18362 )
    return GetCkclWmiLoggerContext(typee, clck_1);
LABEL_19:
  if ( v3 <= 18362 )
    result = 0xC0000001;
  else
    result = sub_2A0A0(typee, clck_1);
  return result;
}

On 7600/7601, WmipLoggerContext is located by scanning EtwSendTraceBuffer:

9200 (Win8) and 9600 (Win8.1) use the same pattern, so I won’t repeat it here:

On Windows 10 it’s the classic copy-paste approach:

Not much to add — you can see where this is going.

Finally, the getCpuClock pointer is swapped with InterlockedExchange (well, xchg) and initialization is done:

 getCpuClock = (_QWORD *)(*((_QWORD *)infhookCore + 90115)
                               + (unsigned int)OFFSET_WMI_LOGGER_CONTEXT_CPU_CYCLE_CLOCK);
        if ( (unsigned __int8)MmIsAddressValid(getCpuClock)
          && (unsigned __int8)MmIsAddressValid((char *)getCpuClock + 7) )
        {
          if ( *getCpuClock >= MmSystemRangeStart )
          {
            _RAX = *((_QWORD *)infhookCore + 90115) + (unsigned int)OFFSET_WMI_LOGGER_CONTEXT_CPU_CYCLE_CLOCK;
            _RCX = InternalGetCpuClock;
            __asm { xchg    rcx, [rax] }
            *((_QWORD *)infhookCore + 90114) = _RCX;
            v6 = 0;
          }

Then the protection thread:

void __fastcall InitializeInfinityHookProtectThread(PVOID StartContext)
{
  LARGE_INTEGER Interval; // [rsp+30h] [rbp-28h]
  NTSTATUS v5; // [rsp+38h] [rbp-20h]
  LARGE_INTEGER Timeout; // [rsp+40h] [rbp-18h]
  __int64 ckcl; // [rsp+48h] [rbp-10h]

  Timeout.LowPart = 0;
  memset((char *)&Timeout.QuadPart + 4, 0, 4ui64);
  ckcl = 0i64;
  Interval.QuadPart = -10000000i64;
  if ( g_ThreadObject )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          while ( 1 )
          {
            while ( 1 )
            {
              v5 = KeWaitForSingleObject(&Event, Executive, 0, 0, &Timeout);
              if ( v5 != 0x102 )
              {
                PsTerminateSystemThread(0);
                return;
              }
              if ( infhookCore )
                break;
              KeDelayExecutionThread(0, 0, &Interval);
            }
            if ( *((_QWORD *)infhookCore + 90114) )
              break;
            KeDelayExecutionThread(0, 0, &Interval);
          }
          v5 = GetCkcl(2u, &ckcl);
          if ( v5 >= 0 )
            break;
          KeDelayExecutionThread(0, 0, &Interval);
        }
        if ( *((_QWORD *)infhookCore + 0x16003) != ckcl )
          break;
        if ( !*((_QWORD *)infhookCore + 0x16003) || !(unsigned __int8)MmIsAddressValid(*((_QWORD *)infhookCore + 90115)) )
          goto LABEL_27;
LABEL_16:
        if ( *(LARGE_INTEGER (**)())((unsigned int)OFFSET_WMI_LOGGER_CONTEXT_CPU_CYCLE_CLOCK
                                   + *((_QWORD *)infhookCore + 90115)) != InternalGetCpuClock )
        {
          _RAX = *((_QWORD *)infhookCore + 0x16003) + (unsigned int)OFFSET_WMI_LOGGER_CONTEXT_CPU_CYCLE_CLOCK;
          _RCX = InternalGetCpuClock;
          __asm { xchg    rcx, [rax] }
          *((_QWORD *)infhookCore + 90114) = _RCX;
        }
        if ( PerfGlobalGroupMask && !(*(_DWORD *)(PerfGlobalGroupMask + 8i64) & 0x40) )
          *((_QWORD *)infhookCore + 90115) = 0i64;
        else
LABEL_27:
          KeDelayExecutionThread(0, 0, &Interval);
      }
      v5 = EnableEtwLogger();
      if ( v5 >= 0 )
      {
        v5 = GetCkcl(2u, &ckcl);
        if ( v5 >= 0 )
        {
          *((_QWORD *)infhookCore + 90115) = ckcl;
          if ( *((_QWORD *)infhookCore + 90115) )
            goto LABEL_16;
          KeDelayExecutionThread(0, 0, &Interval);
        }
        else
        {
          KeDelayExecutionThread(0, 0, &Interval);
        }
      }
      else
      {
        KeDelayExecutionThread(0, 0, &Interval);
      }
    }
  }
}

The loop, in plain terms:

  1. GetCkcl(2) gets the clckloggercontext pointer.
  2. If infhookCore.clck differs from the pointer just fetched (i.e. someone else shut down or modified the CLCCK logger), restart the CLCCK logger and set infhookCore.clck to the new logger.
  3. If the function pointer at clck + offset_GetCpuClock isn’t InternalGetCpuClock, replace it with InternalGetCpuClock.
  4. If PerfGlobalGroupMask[2] doesn’t contain the 0x40 bit (ETW SYSTEM_CALL monitoring was disabled), set infhookCore.clck to 0 and loop back to step 2 — auto-restarting the CLCCK logger.

Now what InternalGetCpuClock actually does:

LARGE_INTEGER InternalGetCpuClock()
{
  unsigned int walkTimes; // ecx
  char *hookEntry; // [rsp+20h] [rbp-58h]
  unsigned int j; // [rsp+30h] [rbp-48h]
  __int64 syscallFunction; // [rsp+48h] [rbp-30h]
  void **StackCurrent; // [rsp+50h] [rbp-28h]
  unsigned int walkStackOffset; // [rsp+60h] [rbp-18h]
  unsigned int syscallNumber; // [rsp+64h] [rbp-14h]
  void *retaddr; // [rsp+78h] [rbp+0h]

  j = 0;
  syscallNumber = GetSystemCallNumber();
  if ( (int)KeGetCurrentIrql() < 2 )
  {
    if ( ExGetPreviousMode() )
    {
      syscallFunction = GetSSDTEntryRoutine(syscallNumber);
      if ( syscallFunction )
      {
        hookEntry = GetSSDTHookEntry(syscallNumber);
        if ( hookEntry )
        {
          if ( *((_DWORD *)hookEntry + 65) || *((_DWORD *)hookEntry + 64) )
          {
            if ( (int)(unsigned __int8)hookEntry[332] <= 0 )
              walkStackOffset = 0;
            else
              walkStackOffset = 0x70;
            for ( StackCurrent = (void **)(__readgsqword(0x1A8u) - (walkStackOffset + 400i64));
                  StackCurrent > &retaddr;
                  --StackCurrent )
            {
              walkTimes = j++;
              if ( walkTimes >= 10 )
                break;
              if ( (unsigned __int8)MmIsAddressValid(StackCurrent) == 1
                && (unsigned __int8)MmIsAddressValid((char *)StackCurrent + 7) == 1
                && (void *)syscallFunction == *StackCurrent )
              {
                _RCX = NewSSDTRoutine;
                _RAX = StackCurrent;
                __asm { xchg    rcx, [rax] }
                return KeQueryPerformanceCounter(0i64);
              }
            }
          }
        }
      }
    }
  }
  return KeQueryPerformanceCounter(0i64);
}

__int64 GetSystemCallNumber()
{
  return *(_DWORD *)(offset_SystemCallNumber + PsGetCurrentThread()) & 0xFFFF;
}

__int64 __fastcall GetSSDTEntryRoutine(unsigned int syscallNumber)
{
  __int64 result; // rax
  char v2; // [rsp+20h] [rbp-18h]

  if ( syscallNumber < 0x1000 )
  {
    if ( syscallNumber < 0x400 )
      result = GetSSDTEntry(*((__int64 **)infhookCore + 90112), syscallNumber, &v2);
    else
      result = 0i64;
  }
  else if ( syscallNumber - 4096 < 0x400 )
  {
    result = GetSSDTEntry((__int64 *)(*((_QWORD *)infhookCore + 90113) + 32i64), syscallNumber - 4096, &v2);
  }
  else
  {
    result = 0i64;
  }
  return result;
}

First: the call must be a user-mode system call, and IRQL must be below DISPATCH_LEVEL.

It reads CurrentThread->SystemCallNumber, then uses the syscall number to get the SSDT function entry and the hook entry (the hook entry may not exist).

for ( StackCurrent = (void **)(__readgsqword(0x1A8u) - (walkStackOffset + 400i64));
      StackCurrent > &retaddr;
      --StackCurrent )
{

It walks the stack from RspBase (gs:0x1A8) - 0x70 down to _AddressOfReturnAddress. When it finds (void*)syscallFunction == *StackCurrent, it replaces *StackCurrent with NewSSDTRoutine.

NewSSDTRoutine:

.text:0000000000030169 55                                                  push    rbp
.text:000000000003016A 48 8B EC                                            mov     rbp, rsp
.text:000000000003016D 9C                                                  pushfq
.text:000000000003016E 41 52                                               push    r10
.text:0000000000030170 48 81 EC C0 00 00 00                                sub     rsp, 0C0h
.text:0000000000030177 48 89 4D 10                                         mov     [rbp+arg_0], rcx
.text:000000000003017B 48 89 55 18                                         mov     [rbp+arg_8], rdx
.text:000000000003017F 4C 89 45 20                                         mov     [rbp+arg_10], r8
.text:0000000000030183 4C 89 4D 28                                         mov     [rbp+arg_18], r9
.text:0000000000030187 E8 C4 FF FF FF                                      call    GetSystemCallNumber
.text:000000000003018C 89 84 24 B8 00 00 00                                mov     [rsp+0D0h+var_18], eax
.text:0000000000030193 8B 8C 24 B8 00 00 00                                mov     ecx, [rsp+0D0h+var_18]
.text:000000000003019A 48 8D 55 10                                         lea     rdx, [rbp+arg_0]
.text:000000000003019E 4C 8D 84 24 B0 00 00 00                             lea     r8, [rsp+0D0h+var_20]
.text:00000000000301A6 E8 F5 95 FF FF                                      call    CallHookEntryRoutine
.text:00000000000301AB 83 F8 02                                            cmp     eax, 2
.text:00000000000301AE 48 8B 84 24 B0 00 00 00                             mov     rax, [rsp+0D0h+var_20]
.text:00000000000301B6 74 5B                                               jz      short loc_30213
.text:00000000000301B8 8B 8C 24 B8 00 00 00                                mov     ecx, [rsp+0D0h+var_18]
.text:00000000000301BF E8 CC 94 FF FF                                      call    GetSSDTEntryRoutine
.text:00000000000301C4 48 85 C0                                            test    rax, rax
.text:00000000000301C7 74 4A                                               jz      short loc_30213
.text:00000000000301C9 4C 8B D0                                            mov     r10, rax
.text:00000000000301CC 48 8D 75 30                                         lea     rsi, [rbp+arg_20]
.text:00000000000301D0 48 8D 7C 24 20                                      lea     rdi, [rsp+0D0h+var_B0]
.text:00000000000301D5 FC                                                  cld
.text:00000000000301D6 48 C7 C1 0E 00 00 00                                mov     rcx, 0Eh
.text:00000000000301DD F3 48 A5                                            rep movsq
.text:00000000000301E0 48 8B 4D 10                                         mov     rcx, [rbp+arg_0]
.text:00000000000301E4 48 8B 55 18                                         mov     rdx, [rbp+arg_8]
.text:00000000000301E8 4C 8B 45 20                                         mov     r8, [rbp+arg_10]
.text:00000000000301EC 4C 8B 4D 28                                         mov     r9, [rbp+arg_18]
.text:00000000000301F0 41 FF D2                                            call    r10
.text:00000000000301F3 48 89 84 24 B0 00 00 00                             mov     [rsp+0D0h+var_20], rax
.text:00000000000301FB 8B 8C 24 B8 00 00 00                                mov     ecx, [rsp+0D0h+var_18]
.text:0000000000030202 48 8D 55 10                                         lea     rdx, [rbp+arg_0]
.text:0000000000030206 4C 8B 84 24 B0 00 00 00                             mov     r8, [rsp+0D0h+var_20]
.text:000000000003020E E8 4D 96 FF FF                                      call    sub_29860
.text:0000000000030213
.text:0000000000030213                                     loc_30213:                              ; CODE XREF: NewSSDTRoutine+4D↑j
.text:0000000000030213                                                                             ; NewSSDTRoutine+5E↑j
.text:0000000000030213 48 8B 84 24 B0 00 00 00                             mov     rax, [rsp+0D0h+var_20]
.text:000000000003021B 48 81 C4 C0 00 00 00                                add     rsp, 0C0h
.text:0000000000030222 41 5A                                               pop     r10
.text:0000000000030224 9D                                                  popfq
.text:0000000000030225 5D                                                  pop     rbp
.text:0000000000030226 C3                                                  retn
.text:0000000000030226                                     NewSSDTRoutine  endp

The hook entry holds eight function pointers (*(QWORD*)&hookEntry[16 * i + 8]). When the SSDT entry is invoked, the suite calls these eight entries (the non-zero ones) for us:

__int64 __fastcall CallHookEntryRoutine(unsigned int a1, __int64 a2, __int64 a3)
{
  unsigned int i; // [rsp+20h] [rbp-38h]
  char *hookEntry; // [rsp+30h] [rbp-28h]
  _QWORD *a3a; // [rsp+68h] [rbp+10h]
  __int64 a1a; // [rsp+70h] [rbp+18h]

  a1a = a3;
  a3a = (_QWORD *)a2;
  hookEntry = GetSSDTHookEntry(a1);
  if ( !hookEntry || !*((_DWORD *)hookEntry + 65) )
    return 1i64;
  for ( i = 0; i < 8; ++i )
  {
    if ( *(_QWORD *)&hookEntry[16 * i + 8]
      && (unsigned int)CallHookEntryRoutineInternal(
                         a1a,
                         *(__int64 (__fastcall **)(__int64, _QWORD, _QWORD, _QWORD))&hookEntry[16 * i + 8],
                         a3a) == 2 )
    {
      return 2i64;
    }
  }
  return 1i64;
}

__int64 __fastcall CallHookEntryRoutineInternal(__int64 a1, __int64 (__fastcall *fn)(__int64, _QWORD, _QWORD, _QWORD), _QWORD *a3)
{
  ULONG_PTR stackParameters[15]; // [rsp+20h] [rbp-A0h]

  qmemcpy(stackParameters, a3 + 3, sizeof(stackParameters));
  return fn(a1, *a3, a3[1], a3[2]);
}

If CallHookEntryRoutine returns 2, the system call is intercepted and the original function is not called. Otherwise, the original SSDT function is fetched from the infhookCore table, the register and stack arguments are restored, and call r10 invokes the original SSDT function:

.text:00000000000301C9 4C 8B D0                                            mov     r10, rax
.text:00000000000301CC 48 8D 75 30                                         lea     rsi, [rbp+arg_20]
.text:00000000000301D0 48 8D 7C 24 20                                      lea     rdi, [rsp+0D0h+var_B0]
.text:00000000000301D5 FC                                                  cld
.text:00000000000301D6 48 C7 C1 0E 00 00 00                                mov     rcx, 0Eh
.text:00000000000301DD F3 48 A5                                            rep movsq//memcpy restore stack args
.text:00000000000301E0 48 8B 4D 10                                         mov     rcx, [rbp+arg_0]//restore register args
.text:00000000000301E4 48 8B 55 18                                         mov     rdx, [rbp+arg_8]
.text:00000000000301E8 4C 8B 45 20                                         mov     r8, [rbp+arg_10]
.text:00000000000301EC 4C 8B 4D 28                                         mov     r9, [rbp+arg_18]
.text:00000000000301F0 41 FF D2                                            call    r10 //original SSDT function

To summarize: this code has blue-screen landmines all over it (see everdox/InfinityHook issue #18), and the driver can’t be dynamically unloaded on uninstall (unless you simply don’t let users uninstall, in which case, fine).

The good news: build 18950 has already shipped InfinityHook off to the great beyond.

The security suite only saw the second layer, and mistook Microsoft for the first layer — when in fact Microsoft was on the fifth.


Colin Xiao — I take on contract reverse-engineering work, and writeups like this are how I show what I can do. If you need help with anything in this space — including but not limited to the areas I cover above — I’m available. No project is too big or too small — if it’s reverse engineering and I can do it, I’ll take it. DM me on X (@colinxiao_lab) or Telegram (@ColinXiao666).

Get in touch