EMACLAB Anticheat Driver, Part 6: Integrity checks
- File Name: EMAC-Driver-x64.sys
- TimeDateStamp: 0x67CAFFCE (Friday, 7 March 2025 14:16:46 GMT)
- Protector: VMProtect 3.8+
Integrity checks
To bypass kernel anticheats, cheaters can simply load their own driver, or even better, manual map it into system memory and try hide itself as much as possible and once the driver is mapped and hidden in memory anticheats have to find it. The anticheat kernel driver itself will not help if there are no integrity checks being actively made on the system, it’s important that anticheats actively scans for traces as well as use patterns and heuristics for detections.
How anticheats find unsigned code
Threads: Any code that actually runs in memory is attached to a thread, there are several threads running on the computer. You can iterate the system threads list and obtain thread stack back trace.
- NMI - It’s possible to register non-maskable interrupts (NMIs) and obtain thread stack back trace.
- APC - It’s possible to send APC to thread and obtain information, or, simply dump it directly.
The stack back trace contains all the informations necessary to find unsigned code being run, that’s great because scanning memory can be tricky and very error prone.
Kernel stack back trace
Sadly the NMI callback is virtualized, but it’s possible to sneak the code which iterate system threads and obtains its informations:
huge EasyAntiCheat thread stack trace code vibes here
void __fastcall EmacGetThreadStartAddressWorkItem(__int64 a1, _EMAC_WORK_ITEM_CONTEXT *context)
{
void (__fastcall *KeSetEventFn)(PKEVENT, _QWORD, _QWORD); // rbp
PIO_WORKITEM WorkItem; // rax
_WORK_QUEUE_ITEM *Flink; // rdi
_EMAC_WORK_ITEM_CONTEXT *Parameter; // rsi
PIO_WORKITEM v7; // rcx
void *ThreadWin32StartAddress; // rax
if ( context )
{
KeSetEventFn = (void (__fastcall *)(PKEVENT, _QWORD, _QWORD))(((unsigned __int64)KeSetEvent ^ qword_FFFFF801BCFACC40) & -(__int64)(qword_FFFFF801BCFACC38 < ((unsigned __int64)KeSetEvent ^ qword_FFFFF801BCFACC40)));
WorkItem = context->WorkItem;
if ( WorkItem )
{
if ( context->Thread )
{
Flink = *(_WORK_QUEUE_ITEM **)WorkItem;
if ( *(_QWORD *)WorkItem )
{
if ( Flink == (_WORK_QUEUE_ITEM *)WorkItem )
{
while ( Flink != (_WORK_QUEUE_ITEM *)context->WorkItem )
{
Parameter = (_EMAC_WORK_ITEM_CONTEXT *)Flink->Parameter;
if ( IsWindows10() && *(PVOID *)&Parameter[1].Event.Header.Lock == context->Thread
|| (v7 = Parameter->WorkItem) != 0i64 && !EmacGetModuleInfoFromAddress((unsigned __int64)v7, 1) )
{
context->Unknown2 = Parameter->WorkItem;
break;
}
Flink = (_WORK_QUEUE_ITEM *)Flink->List.Flink;
if ( !Flink )
break;
}
}
}
}
}
ThreadWin32StartAddress = (void *)EmacGetThreadWin32StartAddress(KeGetCurrentThread());
context->ThreadStartAddress = ThreadWin32StartAddress;
if ( !EmacIsAddressInCodeSectionRange(
(unsigned __int64)ThreadWin32StartAddress,
(_IMAGE_DOS_HEADER *)g_NtoskrnlBase,
0i64) )
context->ThreadStartAddress = 0i64;
KeSetEventFn(&context->Event, 0i64, 0i64);
}
}
char __fastcall EmacGetThreadStartAddress(PETHREAD Thread, void **a2, void **StartAddress)
{
char v3; // bl
void (__fastcall *KeInitializeEventFn)(PKEVENT, _QWORD, _QWORD); // r13
void (__fastcall *IoQueueWorkItemFn)(struct _IO_WORKITEM *, void (__fastcall *)(__int64, _EMAC_WORK_ITEM_CONTEXT *), __int64, _EMAC_WORK_ITEM_CONTEXT *); // r15
void (__fastcall *KeWaitForSingleObjectFn)(void *, _QWORD, _QWORD, _QWORD, _QWORD); // r12
__int64 (__fastcall *ExAllocatePoolWithTagFn)(_QWORD, __int64, _QWORD); // r9
void (__fastcall *ExFreePoolWithTagFn)(_EMAC_WORK_ITEM_CONTEXT *, _QWORD); // rbp
_EMAC_WORK_ITEM_CONTEXT *Context; // rax MAPDST
struct _IO_WORKITEM *workItem; // rax
__int64 (__fastcall *IoAllocateWorkItemFn)(__int64); // [rsp+78h] [rbp+10h]
v3 = 0;
KeInitializeEventFn = (void (__fastcall *)(PKEVENT, _QWORD, _QWORD))(((unsigned __int64)KeInitializeEvent ^ qword_FFFFF801BCFACC40) & -(__int64)(((unsigned __int64)KeInitializeEvent ^ qword_FFFFF801BCFACC40) > qword_FFFFF801BCFACC38));
IoAllocateWorkItemFn = (__int64 (__fastcall *)(__int64))(((unsigned __int64)IoAllocateWorkItem ^ qword_FFFFF801BCFACC40) & -(__int64)(((unsigned __int64)IoAllocateWorkItem ^ qword_FFFFF801BCFACC40) > qword_FFFFF801BCFACC38));
IoQueueWorkItemFn = (void (__fastcall *)(struct _IO_WORKITEM *, void (__fastcall *)(__int64, _EMAC_WORK_ITEM_CONTEXT *), __int64, _EMAC_WORK_ITEM_CONTEXT *))(((unsigned __int64)IoQueueWorkItem ^ qword_FFFFF801BCFACC40) & -(__int64)(((unsigned __int64)IoQueueWorkItem ^ qword_FFFFF801BCFACC40) > qword_FFFFF801BCFACC38));
KeWaitForSingleObjectFn = (void (__fastcall *)(void *, _QWORD, _QWORD, _QWORD, _QWORD))(((unsigned __int64)KeWaitForSingleObject ^ qword_FFFFF801BCFACC40) & -(__int64)(((unsigned __int64)KeWaitForSingleObject ^ qword_FFFFF801BCFACC40) > qword_FFFFF801BCFACC38));
ExAllocatePoolWithTagFn = (__int64 (__fastcall *)(_QWORD, __int64, _QWORD))(((unsigned __int64)ExAllocatePoolWithTag ^ qword_FFFFF801BCFACC40) & -(__int64)(((unsigned __int64)ExAllocatePoolWithTag ^ qword_FFFFF801BCFACC40) > qword_FFFFF801BCFACC38));
ExFreePoolWithTagFn = (void (__fastcall *)(_EMAC_WORK_ITEM_CONTEXT *, _QWORD))(((unsigned __int64)ExFreePoolWithTag ^ qword_FFFFF801BCFACC40) & -(__int64)(((unsigned __int64)ExFreePoolWithTag ^ qword_FFFFF801BCFACC40) > qword_FFFFF801BCFACC38));
if ( a2 )
*a2 = 0i64;
if ( StartAddress )
*StartAddress = 0i64;
Context = (_EMAC_WORK_ITEM_CONTEXT *)ExAllocatePoolWithTagFn(0i64, 0x38i64, 'CAME');
if ( Context )
{
*(_OWORD *)&Context->Event.Header.Lock = 0i64;
*(_OWORD *)&Context->Event.Header.WaitListHead.Blink = 0i64;
*(_OWORD *)&Context->WorkItem = 0i64;
Context->ThreadStartAddress = 0i64;
KeInitializeEventFn(&Context->Event, 0i64, 0i64);
Context->Thread = Thread;
Context->ThreadStartAddress = 0i64;
Context->Unknown2 = 0i64;
workItem = (struct _IO_WORKITEM *)IoAllocateWorkItemFn(g_EmacDeviceObject);
Context->WorkItem = workItem;
if ( workItem )
{
IoQueueWorkItemFn(workItem, EmacGetThreadStartAddressWorkItem, 1i64, Context);
KeWaitForSingleObjectFn(Context, 0i64, 0i64, 0i64, 0i64);
if ( a2 )
*a2 = Context->Unknown2;
if ( StartAddress )
*StartAddress = Context->ThreadStartAddress;
if ( Context->Unknown2 || Context->ThreadStartAddress )
v3 = 1;
}
ExFreePoolWithTagFn(Context, 'CAME');
LOBYTE(Context) = v3;
}
return (char)Context;
}
ULONG_PTR *__fastcall EmacVerifyKernelThreadsStackTrace(unsigned __int64 a1, void *a2, unsigned int a3, ULONG64 a4)
{
ULONG64 v4; // r12
unsigned int status; // ebx MAPDST
unsigned int v7; // esi
int (__fastcall *PsLookupThreadByThreadIdFn)(_QWORD, struct _KTHREAD **); // r13 MAPDST
void (__fastcall *KeStackAttachProcessFn)(PEPROCESS, __int64 *); // rdi
int v10; // eax
void (*ObfDereferenceObjectFn)(void); // r15
__int64 PsGetCurrentThreadIdFn; // r14 MAPDST
size_t v14; // r13
int v16; // ecx
int OffsetKThreadStackLimit; // eax
int OffsetKThreadStackBase; // eax
int OffsetKThreadThreadLock; // eax
unsigned int OffsetKThreadKernelStack; // eax
__int64 OffsetKThreadKernelStack_1; // rdi
unsigned int OffsetKThreadState; // eax
__int64 OffsetKThreadState_1; // rsi
int v24; // r14d
const void **threadInitialStack; // rdi
_BYTE *threadState; // rsi
void *threadStackBase; // r15
__int64 v28; // rdx
void *threadCurrentStack; // rcx
int v31; // r15d
void *CurrentRip; // rdi
void **Rsp; // rsi
size_t i; // r14
__int64 FunctionEntry; // rax
size_t FrameIndex; // rax
size_t v38; // r15
unsigned int StackTraceThreadId; // r13d
size_t y; // rdi
unsigned __int64 currentStackFrame; // r14
EMAC_MODULE_ENTRY *ModuleInfoFromAddress; // rsi
__m128 si128; // xmm0
unsigned __int64 lastStackFrame; // rax
ULONG_PTR v45; // rdi
unsigned int *v47; // r8
ULONG_PTR *result; // rax
bool largePage; // [rsp+40h] [rbp-C0h] BYREF
int ThreadId; // [rsp+44h] [rbp-BCh]
char CurrentIrql; // [rsp+48h] [rbp-B8h]
struct _KTHREAD *Thread; // [rsp+58h] [rbp-A8h] MAPDST BYREF
ULONG64 poolAddress; // [rsp+60h] [rbp-A0h] MAPDST
void (*v55)(void); // [rsp+68h] [rbp-98h]
int v56; // [rsp+70h] [rbp-90h]
__int64 ImageBase; // [rsp+78h] [rbp-88h] BYREF
size_t stackFramesCount; // [rsp+80h] [rbp-80h]
PVOID FrameFileHeader; // [rsp+88h] [rbp-78h] BYREF
void *HandlerData; // [rsp+A0h] [rbp-60h] BYREF
char SubStr[16]; // [rsp+B0h] [rbp-50h] BYREF
wchar_t Str2[8]; // [rsp+C0h] [rbp-40h] BYREF
__m128 v65; // [rsp+D0h] [rbp-30h]
unsigned __int8 (__fastcall *PsIsThreadTerminatingFn)(struct _KTHREAD *); // [rsp+E0h] [rbp-20h]
void *threadStackLimit; // [rsp+E8h] [rbp-18h]
__int64 (__stdcall *MmGetPhysicalAddressFn)(void *); // [rsp+F0h] [rbp-10h]
unsigned __int8 (__fastcall *MmIsAddressValidFn_2)(const void *); // [rsp+F8h] [rbp-8h]
__int64 threadLock; // [rsp+100h] [rbp+0h]
void (__fastcall *KeReleaseSpinLockFn)(__int64, __int64); // [rsp+108h] [rbp+8h]
_IMAGE_NT_HEADERS64 *ntoskrnlHeader; // [rsp+110h] [rbp+10h]
BOOLEAN (__stdcall *MmIsAddressValidFn)(PVOID); // [rsp+118h] [rbp+18h] MAPDST
__int64 (__fastcall *RtlLookupFunctionTableEntryFn)(void *, __int64 *, _QWORD); // [rsp+120h] [rbp+20h]
void (__fastcall *RtlVirtualUnwindFn)(_QWORD, __int64, ULONG64, __int64, CONTEXT *, void **, DWORD64 *, _QWORD); // [rsp+128h] [rbp+28h]
void (__fastcall *ExFreePoolWithTagFn)(ULONG64, _QWORD); // [rsp+130h] [rbp+30h]
unsigned __int8 (__fastcall *MmIsAddressValidFn_1)(__int64); // [rsp+138h] [rbp+38h]
void (__fastcall *RtlPcToFileHeaderFn)(unsigned __int64, PVOID *); // [rsp+140h] [rbp+40h]
void (__fastcall *KeUnstackDetachProcessFn)(__int64 *); // [rsp+148h] [rbp+48h]
struct _KTHREAD *CurrentThread; // [rsp+150h] [rbp+50h]
__int128 EstablisherFrame; // [rsp+158h] [rbp+58h] BYREF
__int64 (__fastcall *KeAcquireSpinLockRaiseToDpcFn)(__int64); // [rsp+168h] [rbp+68h]
__m128i v83; // [rsp+170h] [rbp+70h] BYREF
__m128 v84; // [rsp+180h] [rbp+80h]
__m128i v85; // [rsp+190h] [rbp+90h] BYREF
__int64 v86; // [rsp+1A0h] [rbp+A0h] BYREF
__int128 v87; // [rsp+1A8h] [rbp+A8h]
__int128 v88; // [rsp+1B8h] [rbp+B8h]
unsigned __int64 baseAddress; // [rsp+1C8h] [rbp+C8h]
__int64 stackFrames[32]; // [rsp+1D0h] [rbp+D0h] BYREF
CONTEXT ContextRecord; // [rsp+2D0h] [rbp+1D0h] BYREF
v4 = a4;
status = 0;
if ( KeGetCurrentIrql() > 1u )
{
v47 = (unsigned int *)(a4 + 0x30);
status = 0xC0000148;
result = (ULONG_PTR *)(a4 + 0x38);
v45 = 0i64;
}
else
{
Thread = 0i64;
v7 = 8;
CurrentThread = KeGetCurrentThread();
ThreadId = 8;
baseAddress = 0i64;
v86 = 0i64;
v87 = 0i64;
v88 = 0i64;
memset(stackFrames, 0, sizeof(stackFrames));
FrameFileHeader = 0i64;
PsLookupThreadByThreadIdFn = (int (__fastcall *)(_QWORD, struct _KTHREAD **))(((unsigned __int64)PsLookupThreadByThreadId ^ qword_FFFFF801BCFACC40) & -(__int64)(qword_FFFFF801BCFACC38 < ((unsigned __int64)PsLookupThreadByThreadId ^ qword_FFFFF801BCFACC40)));
KeStackAttachProcessFn = (void (__fastcall *)(PEPROCESS, __int64 *))(((unsigned __int64)KeStackAttachProcess ^ qword_FFFFF801BCFACC40) & -(__int64)(qword_FFFFF801BCFACC38 < ((unsigned __int64)KeStackAttachProcess ^ qword_FFFFF801BCFACC40)));
KeUnstackDetachProcessFn = (void (__fastcall *)(__int64 *))(((unsigned __int64)KeUnstackDetachProcess ^ qword_FFFFF801BCFACC40) & -(__int64)(qword_FFFFF801BCFACC38 < ((unsigned __int64)KeUnstackDetachProcess ^ qword_FFFFF801BCFACC40)));
RtlPcToFileHeaderFn = (void (__fastcall *)(unsigned __int64, PVOID *))((RtlPcToFileHeader ^ qword_FFFFF801BCFACC40) & -(__int64)(qword_FFFFF801BCFACC38 < (RtlPcToFileHeader ^ (unsigned __int64)qword_FFFFF801BCFACC40)));
v10 = *(_DWORD *)(a1 + 0x3F8);
MmIsAddressValidFn_1 = (unsigned __int8 (__fastcall *)(__int64))(((unsigned __int64)MmIsAddressValid ^ qword_FFFFF801BCFACC40) & -(__int64)(qword_FFFFF801BCFACC38 < ((unsigned __int64)MmIsAddressValid ^ qword_FFFFF801BCFACC40)));
v56 = v10;
ObfDereferenceObjectFn = (void (*)(void))(((unsigned __int64)ObfDereferenceObject ^ qword_FFFFF801BCFACC40) & -(__int64)(qword_FFFFF801BCFACC38 < ((unsigned __int64)ObfDereferenceObject ^ qword_FFFFF801BCFACC40)));
v55 = ObfDereferenceObjectFn;
if ( v10 >= 2 )
{
status = 0xC000000D;
v45 = 0i64;
}
else
{
PsGetCurrentThreadIdFn = ((__int64 (*)(void))(((unsigned __int64)PsGetCurrentThreadId_0 ^ qword_FFFFF801BCFACC40) & -(__int64)(qword_FFFFF801BCFACC38 < ((unsigned __int64)PsGetCurrentThreadId_0 ^ qword_FFFFF801BCFACC40))))();
KeStackAttachProcessFn(g_AttachedProcess, &v86);
if ( !g_UnknownThreadStartAddress )
EmacGetThreadStartAddress(KeGetCurrentThread(), 0i64, &g_UnknownThreadStartAddress);
do
{
if ( *(_BYTE *)(a1 + 2000) || g_EmacNotReady )
break;
if ( v7 != (_DWORD)PsGetCurrentThreadIdFn && PsLookupThreadByThreadIdFn(v7, &Thread) >= 0 )
{
if ( Thread != CurrentThread && EmacIsSystemThread(Thread) )
{
ntoskrnlHeader = RtlImageNtHeader((_IMAGE_DOS_HEADER *)g_NtoskrnlBase);
ImageBase = 0i64;
EstablisherFrame = 0ui64;
v14 = 0i64;
HandlerData = 0i64;
memset(&ContextRecord, 0, sizeof(ContextRecord));
largePage = 0;
stackFramesCount = 0i64;
memset(stackFrames, 0, sizeof(stackFrames));
if ( Thread && ntoskrnlHeader )
{
MmIsAddressValidFn = (BOOLEAN (__stdcall *)(PVOID))(((unsigned __int64)MmIsAddressValid ^ qword_FFFFF801BCFACC40) & -(__int64)(qword_FFFFF801BCFACC38 < ((unsigned __int64)MmIsAddressValid ^ qword_FFFFF801BCFACC40)));
RtlLookupFunctionTableEntryFn = (__int64 (__fastcall *)(void *, __int64 *, _QWORD))((qword_FFFFF801BCFACC40 ^ RtlLookupFunctionTableEntry) & -(__int64)(qword_FFFFF801BCFACC38 < (qword_FFFFF801BCFACC40 ^ (unsigned __int64)RtlLookupFunctionTableEntry)));
RtlVirtualUnwindFn = (void (__fastcall *)(_QWORD, __int64, ULONG64, __int64, CONTEXT *, void **, DWORD64 *, _QWORD))((qword_FFFFF801BCFACC40 ^ RtlVirtualUnwind) & -(__int64)(qword_FFFFF801BCFACC38 < (qword_FFFFF801BCFACC40 ^ (unsigned __int64)RtlVirtualUnwind)));
ExFreePoolWithTagFn = (void (__fastcall *)(ULONG64, _QWORD))((qword_FFFFF801BCFACC40 ^ (unsigned __int64)ExFreePoolWithTag) & -(__int64)(qword_FFFFF801BCFACC38 < (qword_FFFFF801BCFACC40 ^ (unsigned __int64)ExFreePoolWithTag)));
poolAddress = ((__int64 (__fastcall *)(_QWORD, __int64, _QWORD))((qword_FFFFF801BCFACC40 ^ (unsigned __int64)ExAllocatePoolWithTag) & -(__int64)(qword_FFFFF801BCFACC38 < (qword_FFFFF801BCFACC40 ^ (unsigned __int64)ExAllocatePoolWithTag))))(
(unsigned int)g_EmacPoolType,
0x2000i64,
'CAME');
if ( poolAddress )
{
v16 = dword_FFFFF801BCFCC174;
if ( (dword_FFFFF801BCFCC174 & 1) == 0 )
{
dword_FFFFF801BCFCC174 |= 1u;
OffsetKThreadStackLimit = GetOffsetKThreadStackLimit();
v16 = dword_FFFFF801BCFCC174;
g_OffsetKThreadStackLimit = OffsetKThreadStackLimit;
}
if ( (v16 & 2) == 0 )
{
dword_FFFFF801BCFCC174 = v16 | 2;
OffsetKThreadStackBase = GetOffsetKThreadStackBase();
v16 = dword_FFFFF801BCFCC174;
g_OffsetKThreadStackBase = OffsetKThreadStackBase;
}
if ( (v16 & 4) == 0 )
{
dword_FFFFF801BCFCC174 = v16 | 4;
OffsetKThreadThreadLock = GetOffsetKThreadThreadLock();
v16 = dword_FFFFF801BCFCC174;
g_OffsetKThreadThreadLock = OffsetKThreadThreadLock;
}
if ( (v16 & 8) != 0 )
{
OffsetKThreadKernelStack_1 = (unsigned int)g_OffsetKThreadKernelStack;
}
else
{
dword_FFFFF801BCFCC174 = v16 | 8;
OffsetKThreadKernelStack = GetOffsetKThreadKernelStack();
v16 = dword_FFFFF801BCFCC174;
OffsetKThreadKernelStack_1 = OffsetKThreadKernelStack;
g_OffsetKThreadKernelStack = OffsetKThreadKernelStack;
}
if ( (v16 & 0x10) != 0 )
{
OffsetKThreadState_1 = (unsigned int)g_OffsetKThreadState;
}
else
{
dword_FFFFF801BCFCC174 = v16 | 0x10;
OffsetKThreadState = GetOffsetKThreadState();
OffsetKThreadKernelStack_1 = (unsigned int)g_OffsetKThreadKernelStack;
OffsetKThreadState_1 = OffsetKThreadState;
g_OffsetKThreadState = OffsetKThreadState;
}
MmIsAddressValidFn_2 = (unsigned __int8 (__fastcall *)(const void *))(((unsigned __int64)MmIsAddressValid ^ qword_FFFFF801BCFACC40) & -(__int64)(qword_FFFFF801BCFACC38 < ((unsigned __int64)MmIsAddressValid ^ qword_FFFFF801BCFACC40)));
MmGetPhysicalAddressFn = (__int64 (__stdcall *)(void *))((qword_FFFFF801BCFACC40 ^ (unsigned __int64)MmGetPhysicalAddress) & -(__int64)(qword_FFFFF801BCFACC38 < (qword_FFFFF801BCFACC40 ^ (unsigned __int64)MmGetPhysicalAddress)));
KeAcquireSpinLockRaiseToDpcFn = (__int64 (__fastcall *)(__int64))((qword_FFFFF801BCFACC40 ^ (unsigned __int64)KeAcquireSpinLockRaiseToDpc) & -(__int64)(qword_FFFFF801BCFACC38 < (qword_FFFFF801BCFACC40 ^ (unsigned __int64)KeAcquireSpinLockRaiseToDpc)));
KeReleaseSpinLockFn = (void (__fastcall *)(__int64, __int64))((qword_FFFFF801BCFACC40 ^ (unsigned __int64)KeReleaseSpinLock) & -(__int64)(qword_FFFFF801BCFACC38 < (qword_FFFFF801BCFACC40 ^ (unsigned __int64)KeReleaseSpinLock)));
PsIsThreadTerminatingFn = (unsigned __int8 (__fastcall *)(struct _KTHREAD *))((qword_FFFFF801BCFACC40 ^ (unsigned __int64)PsIsThreadTerminating) & -(__int64)(qword_FFFFF801BCFACC38 < (qword_FFFFF801BCFACC40 ^ (unsigned __int64)PsIsThreadTerminating)));
KeGetCurrentIrql();
v24 = 0;
memset((void *)poolAddress, 0, 0x2000ui64);
if ( g_OffsetKThreadStackLimit == -1
|| g_OffsetKThreadStackBase == -1
|| g_OffsetKThreadThreadLock == -1
|| (_DWORD)OffsetKThreadKernelStack_1 == -1
|| (_DWORD)OffsetKThreadState_1 == -1 )
{
v31 = 0xC0000138;
}
else
{
threadInitialStack = (const void **)((char *)Thread + OffsetKThreadKernelStack_1);
threadState = (char *)Thread + OffsetKThreadState_1;
threadStackBase = *(void **)((char *)Thread + (unsigned int)g_OffsetKThreadStackBase);
threadStackLimit = *(void **)((char *)Thread + (unsigned int)g_OffsetKThreadStackLimit);
threadLock = (__int64)Thread + (unsigned int)g_OffsetKThreadThreadLock;
CurrentIrql = KeAcquireSpinLockRaiseToDpcFn(threadLock);
if ( PsIsThreadTerminatingFn(Thread) || *threadState != 5 )
{
v24 = 0xC000004B;
}
else
{
threadCurrentStack = (void *)*threadInitialStack;
if ( *threadInitialStack > threadStackLimit
&& threadCurrentStack < threadStackBase
&& MmGetPhysicalAddressFn(threadCurrentStack)
&& MmIsAddressValidFn_2(*threadInitialStack) )
{
v14 = (_BYTE *)threadStackBase - (_BYTE *)*threadInitialStack;
if ( v14 > 0x2000 )
v14 = 0x2000i64;
memmove_2((void *)poolAddress, *threadInitialStack, v14);
}
else
{
v24 = 0xC0000141;
}
}
LOBYTE(v28) = CurrentIrql;
KeReleaseSpinLockFn(threadLock, v28);
v31 = v24;
if ( v24 >= 0
&& v14 - 73 <= 8118
&& EmacVerifyInTextSection(*(_QWORD *)(poolAddress + 0x38), (__int64)g_NtoskrnlBase, ntoskrnlHeader) )
{
memset(&ContextRecord, 0, sizeof(ContextRecord));
CurrentRip = *(void **)(poolAddress + 0x38);
Rsp = (void **)(poolAddress + 0x40);
i = stackFramesCount;
ContextRecord.Rsp = (ULONG64)Rsp;
ContextRecord.Rip = (ULONG64)CurrentRip;
do
{
ImageBase = 0i64;
HandlerData = 0i64;
EstablisherFrame = 0i64;
if ( (unsigned __int64)CurrentRip <= qword_FFFFF801BCFACC38 )
break;
if ( (unsigned __int64)Rsp <= qword_FFFFF801BCFACC38 )
break;
if ( !MmIsAddressValidFn(CurrentRip) )
break;
if ( !MmIsAddressValidFn(Rsp) )
break;
largePage = 0;
if ( !EmacIsPageEntryValid((__int64)CurrentRip, &largePage, 0i64) )
break;
if ( largePage )
break;
stackFrames[i] = (__int64)CurrentRip;
if ( !EmacGetModuleInfoFromAddress((unsigned __int64)CurrentRip, 1) )
break;
FunctionEntry = RtlLookupFunctionTableEntryFn(CurrentRip, &ImageBase, 0i64);
if ( FunctionEntry )
{
RtlVirtualUnwindFn(
0i64,
ImageBase,
ContextRecord.Rip,
FunctionEntry,
&ContextRecord,
&HandlerData,
(DWORD64 *)&EstablisherFrame,
0i64);
CurrentRip = (void *)ContextRecord.Rip;
Rsp = (void **)ContextRecord.Rsp;
}
else
{
CurrentRip = *Rsp;
Rsp = (void **)(ContextRecord.Rsp + 8);
ContextRecord.Rip = (ULONG64)CurrentRip;
ContextRecord.Rsp += 8i64;
}
FrameIndex = i++;
stackFramesCount = i;
if ( FrameIndex >= 32 )
break;
}
while ( (unsigned __int64)CurrentRip >= qword_FFFFF801BCFACC38 && !g_EmacNotReady );
}
}
ExFreePoolWithTagFn(poolAddress, 'CAME');
if ( v31 < 0 || (v38 = stackFramesCount, stackFramesCount - 1 > 31) )
{
v7 = ThreadId;
}
else
{
StackTraceThreadId = ThreadId;
y = 0i64;
do
{
if ( *(_BYTE *)(a1 + 0x7D0) )
break;
if ( g_EmacNotReady )
break;
currentStackFrame = stackFrames[y];
if ( currentStackFrame <= qword_FFFFF801BCFACC38 || !MmIsAddressValidFn_1(stackFrames[y]) )
break;
ModuleInfoFromAddress = EmacGetModuleInfoFromAddress(currentStackFrame, 1);
RtlPcToFileHeaderFn(currentStackFrame, &FrameFileHeader);
if ( ModuleInfoFromAddress )
{
if ( FrameFileHeader != ModuleInfoFromAddress->ImageBase )
break;
if ( ModuleInfoFromAddress->SubjectName[0] )
{
*(_QWORD *)Str2 = 0xB07F113F97FF5772ui64;
*(_QWORD *)&Str2[4] = 0x13112D07CA8DB1F8i64;
si128 = (__m128)_mm_load_si128((const __m128i *)Str2);
v65.m128_u64[0] = 0x77B95CDEC3F425C2i64;
v84.m128_u64[0] = 0xD6106250E59C3E3Fui64;
v65.m128_u64[1] = 0xA8723627E07A05FEui64;
v84.m128_u64[1] = 0x647E4969A3DA918Ci64;
v85.m128i_i64[1] = 0xA8723627E07A05FEui64;
v85.m128i_i64[0] = 0x77B95CDEC3F425B1i64;
v65 = _mm_xor_ps((__m128)_mm_load_si128(&v85), v65);// Decrypted UTF-8: s
*(__m128 *)Str2 = _mm_xor_ps(si128, v84);// Decrypted Raw (unprintable): 44 85 40 94 3f 11 7f b0 74 20 57 69 6e 64 6f 77
if ( strcmp(ModuleInfoFromAddress->SubjectName, (const char *)Str2) )// "Microsoft Corporation"
break;
}
if ( ModuleInfoFromAddress->AdditionalData[0] )
{
*(_QWORD *)SubStr = 0xB364033495E95D52ui64;
v83.m128i_i64[0] = 0xD6106250E59C3E3Fui64;
*(_QWORD *)&SubStr[8] = 0x647E4969A3DA918Ci64;
v83.m128i_i64[1] = 0x647E4969A3DA918Ci64;
*(__m128 *)SubStr = _mm_xor_ps((__m128)_mm_load_si128(&v83), *(__m128 *)SubStr);
if ( strstr((const char *)ModuleInfoFromAddress->AdditionalData, SubStr) )// "mcupdate"
break;
}
}
if ( v56 )
{
if ( v56 == 1
&& ModuleInfoFromAddress
&& !EmacIsAddressInCodeSectionRange(
currentStackFrame,
(_IMAGE_DOS_HEADER *)ModuleInfoFromAddress->ImageBase,
0i64) )
{
EmacReportThreadInvalidStackTrace_2(
Thread,
(__int64)g_UnknownThreadStartAddress,
StackTraceThreadId,
y,
v38,
currentStackFrame,
(__int64)ModuleInfoFromAddress,
a1);
}
}
else if ( !ModuleInfoFromAddress && !FrameFileHeader )
{
if ( y && y < v38 )
lastStackFrame = stackFrames[y - 1];
else
lastStackFrame = 0i64;
EmacReportThreadInvalidStackTrace(
Thread,
g_UnknownThreadStartAddress,
StackTraceThreadId,
y,
v38,
currentStackFrame,
lastStackFrame,
a1);
}
++y;
}
while ( y <= v38 );
v7 = StackTraceThreadId;
}
}
else
{
v7 = ThreadId;
}
ObfDereferenceObjectFn = v55;
}
else
{
v7 = ThreadId;
}
}
ObfDereferenceObjectFn();
}
v7 += 4;
ThreadId = v7;
EmacDelayExecutionThread(1);
}
while ( v7 < 0x10000 );
KeUnstackDetachProcessFn(&v86);
v45 = 2008i64;
if ( a3 < 2008 )
status = 0xC0000004;
else
memmove_2(a2, (const void *)a1, 2008ui64);
v4 = a4;
}
v47 = (unsigned int *)(v4 + 0x30);
result = (ULONG_PTR *)(v4 + 0x38);
}
*result = v45;
*v47 = status;
return result;
}
The routine EmacGetThreadStartAddress
queues an work item which will retrieve the thread start address, it is then verified if that address resides in any ntoskrnl.exe
code section.
Now the NMI callback registration
sadly goes to a vmenter, we cannot reverse any further
Self integrity
I have spotted some procedures that will perform self-integrity checks in the driver.
this function finds driver file path using registry and reads the file contents into a buffer
then relocations are stored in a list
and finally, the buffer contents are xored with a simple key
this function will decrypt the buffer into a copy
this function will perform 1:1 integrity check between the memory and the file sections on disk
Here’s the list of things that the anticheat is doing:
- Checks
MmUnloadedDrivers
andPiDDBCache
, those lists contains informations about kernel drivers loaded by the system - Checks if system images sections have the right PTE flags by iterating
PsLoadedModuleList
and comparing with the PE information from disk - Checks if processes have
\Device\PhysicalMemory
handle - Checks if some functions from
HalPrivateDispatchTable
are being tampered - Self-integrity checks at
.text
(code) and.idata
(IAT), by comparing with the PE file on disk - Checks
gDxgkInterface
table from modulewin32kbase.sys
, this table is oftenly used as what we call.data
pointer hooks. - Enumerate all system handles and strip access mask from any unauthorized process
- Actively walk throught system pages and perform physical memory dumps based on the PTE flags, this can be used to detect manually mapped drivers.
- Iterate BigPool list, searches for known malicious tags.
- Scans usermode process memory for patterns.
That’s all i could find but there must be a lot more scans and checks sitting in virtualized code.