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:
GetCkcl(2)gets theclckloggercontextpointer.- If
infhookCore.clckdiffers from the pointer just fetched (i.e. someone else shut down or modified the CLCCK logger), restart the CLCCK logger and setinfhookCore.clckto the new logger. - If the function pointer at
clck + offset_GetCpuClockisn’tInternalGetCpuClock, replace it withInternalGetCpuClock. - If
PerfGlobalGroupMask[2]doesn’t contain the0x40bit (ETW SYSTEM_CALL monitoring was disabled), setinfhookCore.clckto 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