EMACLAB Anticheat Driver, Part 5: Filter and Callbacks
- File Name: EMAC-Driver-x64.sys
- TimeDateStamp: 0x67CAFFCE (Friday, 7 March 2025 14:16:46 GMT)
- Protector: VMProtect 3.8+
Callbacks
Callbacks for threads, processes and images are registered, the only handler i could find without virtualization was image callback.
Image Load Callback
Function: EmacImageCallback (0xBCEF09D4, 0x1A79 bytes — one of the largest non-virtualized functions in the driver)
Registered via PsSetLoadImageNotifyRoutineEx. It will insert the newly module into a internal list via EmacCreateModuleEntry (0xBCEF719C), then several actions are performed:
- Resolve ntdll.dll exports:
DbgBreakPoint,DbgUiRemoteBreakin,DbgUserBreakPoint,LdrLoadDll - Resolve kernel32.dll exports:
LoadLibraryA,LoadLibraryW,LoadLibraryExA,LoadLibraryExW - Resolve kernelbase.dll exports: Same LoadLibrary variants (for WOW64 processes)
- Track game modules:
cs2.exebase/size andengine2.dllbase/size - Track GamersClub launcher image base
- Inject EMAC client DLL into the game process via
NtCreateThreadExtargetingKERNEL32.DLL!LoadLibraryW
Injected DLL Paths
| DLL Pattern | Target |
|---|---|
\EMAC-CSGO-x64.dll | CS2 64-bit game process |
\EMAC-CSGO-x86.dll | CS:GO 32-bit game process (legacy) |
\EMAC-CS-Client-x64.dll | Fallback CS2 64-bit |
\EMAC-CS-Client-x86.dll | Fallback CS:GO 32-bit |
\EMAC-Client-x64.dll | Non-game 64-bit process |
\EMAC-Client-x86.dll | Non-game 32-bit (WOW64) process |
Related Functions
| Function | Address | Purpose |
|---|---|---|
EmacCreateModuleEntry | 0xBCEF719C | Creates internal module tracking entry |
EmacVerifyModuleWorkItem | 0xBCEF7338 | Queues module verification as work item |
EmacVerifyModuleEntry | 0xBCF20390 (0x738E — massive) | Full module verification (VMProtect-heavy) |
EmacVerifyLoadedModulesList | 0xBCEF761C | Iterates PsLoadedModuleList for validation |
EmacGetModuleInfoFromAddress | 0xBCEF74F0 | Looks up module info from internal list by address |
Other Kernel Callbacks
- PsSetCreateProcessNotifyRoutine → Virtualized (tracks process creation/termination)
- PsSetCreateThreadNotifyRoutine → Virtualized (tracks thread creation)
Note: Thread and process callbacks are VMProtect-virtualized and cannot be fully reversed.
You must think, if the driver loads the .DLL then i can simply overwrite it on disk so it loads my own, but remember there’s a minifilter with file signature checks:
Minifilter
EMACLAB Anticheat registers a FSFilter Activity Monitor with altitude 363570. Then a pre-operation callback is set on memory section creation, to intercept system image loading.
Filter Callback Flow
Function: EmacFltCallback (0xBCEF27A4) — Pre-operation callback:
- Checks if operation is
IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATIONwithSectionPageProtection = PAGE_EXECUTE (0x10) - Only processes section creates from PID 4 (System) or the game process
- Gets file name information via
FltGetFileNameInformation - Calls
EmacFltVerifyFileNameto validate the file - If verification fails → blocks the load with
STATUS_INSUFFICIENT_RESOURCES (0xC000009A)
The whole file signature verification is done in kernel using CI.dll APIs.
__int64 __fastcall EmacFltCallback(PFLT_CALLBACK_DATA fltCallbackData)
{
unsigned int v2; // ebp
ULONG (__fastcall *FltGetRequestorProcessIdFn)(PFLT_CALLBACK_DATA); // rsi
NTSTATUS (__fastcall *FltGetFileNameInformationFn)(PFLT_CALLBACK_DATA, FLT_FILE_NAME_OPTIONS, PFLT_FILE_NAME_INFORMATION *); // r12
NTSTATUS (__fastcall *FltParseFileNameInformationFn)(PFLT_FILE_NAME_INFORMATION); // r15
NTSTATUS (__stdcall *FltReleaseFileNameInformationFn)(PFLT_FILE_NAME_INFORMATION); // r14
PFLT_IO_PARAMETER_BLOCK Iopb; // rdi
ULONG processId; // esi
struct _FLT_FILE_NAME_INFORMATION *v9; // rcx
PFLT_FILE_NAME_INFORMATION fileNameInformation; // [rsp+50h] [rbp+8h] BYREF
v2 = 1;
_InterlockedAdd(&g_EmacReferenceCount, 1u);
fileNameInformation = 0i64;
FltGetRequestorProcessIdFn = (ULONG (__fastcall *)(PFLT_CALLBACK_DATA))(((unsigned __int64)FltGetRequestorProcessId ^ qword_FFFFF801BCFACC40) & -(__int64)(((unsigned __int64)FltGetRequestorProcessId ^ qword_FFFFF801BCFACC40) > qword_FFFFF801BCFACC38));
FltGetFileNameInformationFn = (NTSTATUS (__fastcall *)(PFLT_CALLBACK_DATA, FLT_FILE_NAME_OPTIONS, PFLT_FILE_NAME_INFORMATION *))(((unsigned __int64)FltGetFileNameInformation ^ qword_FFFFF801BCFACC40) & -(__int64)(((unsigned __int64)FltGetFileNameInformation ^ qword_FFFFF801BCFACC40) > qword_FFFFF801BCFACC38));
FltParseFileNameInformationFn = (NTSTATUS (__fastcall *)(PFLT_FILE_NAME_INFORMATION))(((unsigned __int64)FltParseFileNameInformation ^ qword_FFFFF801BCFACC40) & -(__int64)(((unsigned __int64)FltParseFileNameInformation ^ qword_FFFFF801BCFACC40) > qword_FFFFF801BCFACC38));
FltReleaseFileNameInformationFn = (NTSTATUS (__stdcall *)(PFLT_FILE_NAME_INFORMATION))(((unsigned __int64)FltReleaseFileNameInformation ^ qword_FFFFF801BCFACC40) & -(__int64)(((unsigned __int64)FltReleaseFileNameInformation ^ qword_FFFFF801BCFACC40) > qword_FFFFF801BCFACC38));
if ( fltCallbackData )
{
Iopb = fltCallbackData->Iopb;
if ( Iopb )
{
if ( Iopb->Parameters.Read.Length == 1 && Iopb->Parameters.AcquireForSectionSynchronization.PageProtection == 0x10 )
{
((void (__fastcall *)(PFLT_CALLBACK_DATA))(((unsigned __int64)FltGetRequestorProcess ^ qword_FFFFF801BCFACC40) & -(__int64)(((unsigned __int64)FltGetRequestorProcess ^ qword_FFFFF801BCFACC40) > qword_FFFFF801BCFACC38)))(fltCallbackData);
processId = FltGetRequestorProcessIdFn(fltCallbackData);
if ( (processId == 4 || g_GameProcessId && processId == (_DWORD)g_GameProcessId)
&& (FltGetFileNameInformationFn(fltCallbackData, 0x101i64, &fileNameInformation) & 0xC0000000) != 0xC0000000
&& (FltParseFileNameInformationFn(fileNameInformation) & 0xC0000000) != 0xC0000000
&& processId == 4
&& EmacFltVerifyFileName(Iopb, fileNameInformation) )
{
fltCallbackData->IoStatus.Information = 0i64;
v2 = 4;
v9 = fileNameInformation;
fltCallbackData->IoStatus.Status = 0xC000009A;// Block from loading
if ( !v9 )
goto LABEL_17;
}
else
{
v9 = fileNameInformation;
}
if ( v9 )
FltReleaseFileNameInformationFn(v9);
}
}
}
LABEL_17:
_InterlockedDecrement(&g_EmacReferenceCount);
return v2;
}
They proceed to verify the file, based on name and code signature informations.
bool __fastcall EmacFltVerifyFileName(PFLT_IO_PARAMETER_BLOCK Iopb, PFLT_FILE_NAME_INFORMATION fileNameInformation)
{
char v3; // di
void (__fastcall *ExFreePoolWithTagFn)(_IMAGE_DOS_HEADER *, _QWORD); // r14
_IMAGE_DOS_HEADER *fileBuffer; // rbx
__int64 e_lfanew; // rax
_EMAC_IMAGE_SIGN_INFO a3; // [rsp+20h] [rbp-E0h] BYREF
unsigned __int64 fileSize; // [rsp+310h] [rbp+210h] BYREF
LODWORD(fileSize) = 0;
*(_QWORD *)&a3.VerificationStatus = 0i64;
a3.PolicyInfoSize = 0;
v3 = 0;
a3.IsVerified = 0;
memset(&a3.SigningTime, 0, 681);
ExFreePoolWithTagFn = (void (__fastcall *)(_IMAGE_DOS_HEADER *, _QWORD))(((unsigned __int64)ExFreePoolWithTag ^ qword_FFFFF801BCFACC40) & -(__int64)(((unsigned __int64)ExFreePoolWithTag ^ qword_FFFFF801BCFACC40) > qword_FFFFF801BCFACC38));
if ( !byte_FFFFF801BCFAC6D8 ) // Probably a master switch for file verification
{
if ( fileNameInformation && EmacCheckDbkProcessHackerByFileName(&fileNameInformation->Name) )
{
return 1;
}
else
{
fileBuffer = (_IMAGE_DOS_HEADER *)EmacFltReadFileToBuffer(Iopb->TargetInstance, Iopb->TargetFileObject, &fileSize);
if ( fileBuffer )
{
if ( fileBuffer->e_magic == 0x5A4D )
{
e_lfanew = fileBuffer->e_lfanew;
if ( (int)e_lfanew < 4096
&& (unsigned int)fileSize > 0x1000
&& *(_DWORD *)((char *)&fileBuffer->e_magic + e_lfanew) == 0x4550
&& *(USHORT *)((char *)&fileBuffer->e_lfarlc + e_lfanew) == 0x20B
&& *(USHORT *)((char *)fileBuffer[1].e_res + e_lfanew) == 1 )// Validate PE
{
if ( EmacVerifyFileSigned(fileBuffer, (unsigned int)fileSize, &a3) >= 0
&& LOBYTE(a3.Unknown1)
&& EmacVerifyFileCertificateName(a3.SubjectName) )
{
v3 = 1;
}
else
{
v3 = EmacVerifyFileUnknown((__int64)fileBuffer);
}
}
}
ExFreePoolWithTagFn(fileBuffer, 'CAME');
}
}
}
return v3;
}
EmacCheckSystemImageByName will verify if image name is dbk64.sys/dbk32.sys (Cheat Engine) or kprocesshacker2.sys (Process Hacker):
this is kinda of meme since it checks purely by the file name, you can try renaming a legitimate non-malicious driver and try load it to see what happens
bool __fastcall EmacCheckDbkProcessHacker(const wchar_t *imageName)
{
unsigned __int64 v1; // rbx
__m128 v2; // rdi
unsigned __int64 v4; // r8
unsigned __int64 v5; // r8
__m128 si128; // xmm0
__m128 v7; // xmm1
__int64 v8; // rbx
__int64 *v9; // rsi
bool v10; // di
__int64 i; // rbx
wchar_t *v12; // rdx
__m128 v14; // [rsp+20h] [rbp-69h] BYREF
__m128 v15; // [rsp+30h] [rbp-59h] BYREF
__m128 v16; // [rsp+40h] [rbp-49h] BYREF
__m128 v17; // [rsp+50h] [rbp-39h]
__int128 v18[2]; // [rsp+60h] [rbp-29h] BYREF
__int128 v19[2]; // [rsp+80h] [rbp-9h] BYREF
__int128 v20[2]; // [rsp+A0h] [rbp+17h] BYREF
char v21[32]; // [rsp+C0h] [rbp+37h] BYREF
char *v22; // [rsp+F8h] [rbp+6Fh] BYREF
__int128 *v23; // [rsp+100h] [rbp+77h] BYREF
v1 = -1i64;
v16.m128_u64[0] = 0xA5AE4945064F9i64;
v2.m128_u64[0] = 0x3C5A8F9432649Di64;
v14.m128_u64[0] = 0x3C5A8F9432649Di64;
v16.m128_u64[1] = 0x3AD429636D08232i64;
v2.m128_u64[1] = 0x3AD429636D08206i64;
v14.m128_u64[1] = 0x3AD429636D08206i64;
v16 = _mm_xor_ps((__m128)_mm_load_si128((const __m128i *)&v14), v16);// Decrypted UTF-16: dbk64
v4 = -1i64;
memset(v18, 0, sizeof(v18));
do
++v4;
while ( v16.m128_i16[v4] );
std_vector_push_back(v18, &v16, v4);
v14 = v2;
v16.m128_u64[0] = 0xF5AE4945064F9i64;
v16.m128_u64[1] = 0x3AD429636D08234i64;
v5 = -1i64;
v16 = _mm_xor_ps((__m128)_mm_load_si128((const __m128i *)&v16), v2);// Decrypted UTF-16: dbk34
memset(v19, 0, sizeof(v19));
do
++v5;
while ( v16.m128_i16[v5] );
std_vector_push_back(v19, &v16, v5);
v16 = v2;
v14.m128_u64[0] = 0x535AFD944264F6i64;
v14.m128_u64[1] = 0x3DE42E536B58265i64;
si128 = (__m128)_mm_load_si128((const __m128i *)&v14);
v15.m128_u64[0] = 0x454DEBE66E3527A7i64;
v15.m128_u64[1] = 0x3C2843EC1561C2DDi64;
v7 = (__m128)_mm_load_si128((const __m128i *)&v15);
v17.m128_u64[0] = 0x4526EB856E5427CFi64;
v17.m128_u64[1] = 0x3C2843EC1513C2B8i64;
v15 = _mm_xor_ps(v7, v17); // Decrypted Raw (unprintable): 51 43 77 fa 1b b1 1e 45 65 00 72 00 00 00 00 00
v14 = _mm_xor_ps(si128, v2); // Decrypted UTF-16: kpro2
memset(v20, 0, sizeof(v20)); // L"kprocesshacker"
do
++v1;
while ( v14.m128_i16[v1] );
std_vector_push_back(v20, &v14, v1);
v15.m128_u64[0] = 0i64;
v22 = v21;
v8 = 3i64;
v23 = v18;
v14 = 0i64;
std_vector_alloc_3((unsigned __int64 *)&v14, 3ui64, &v23, &v22);
v9 = (__int64 *)v21;
v10 = 1;
do
{
v9 -= 4;
sub_FFFFF801BCEF4B20(v9);
--v8;
}
while ( v8 );
for ( i = v14.m128_u64[0]; i != v14.m128_u64[1]; i += 32i64 )// // this is basically a range based loop to traverse vector elements
{
v12 = (wchar_t *)i;
if ( *(_QWORD *)(i + 24) > 7ui64 )
v12 = *(wchar_t **)i;
if ( wcsistr((wchar_t *)imageName, v12) )
goto LABEL_16;
}
v10 = 0;
LABEL_16:
sub_FFFFF801BCEF4A94((__int64)&v14);
return v10;
}
The next function goes beyong and verifies the file signature with known malicious subject names:
I am sorry, the static xor string decipher oftenly puts the words out of order
bool __fastcall EmacVerifyFileCertificateName(char *subjectName)
{
__int64 v1; // rdi
__int64 v3; // r8
bool v4; // bl
__int64 v5; // r8
__int64 v6; // r8
__int64 v7; // r8
__int64 v8; // r8
__int64 v9; // r8
__m128 v10; // xmm0
__int64 v11; // r8
__m128 si128; // xmm1
__int64 v13; // r8
__m128 v14; // xmm0
__m128 v15; // xmm1
__m128 v16; // xmm0
__int64 v17; // r8
__int64 v18; // r8
__m128 v19; // xmm0
__m128 v20; // xmm1
__m128 v21; // xmm0
__int64 v22; // r8
__m128 v23; // xmm0
__m128 v24; // xmm1
__m128 v25; // xmm0
__int64 v26; // r8
__m128 v27; // xmm0
__m128 v28; // xmm1
__m128 v29; // xmm0
__int64 v30; // r8
__m128 v31; // xmm0
__m128 v32; // xmm1
__int64 v33; // r8
__m128 v34; // xmm0
__m128 v35; // xmm1
__m128 v36; // xmm0
__int64 v37; // r14
__int64 *v38; // rdi
char v39; // si
unsigned __int64 v40; // rdx
__int64 v41; // rcx
__int64 v42; // r8
const char *v43; // rcx
__int64 i; // rdi
char *v45; // rdx
__m128 v47; // [rsp+20h] [rbp-E0h] BYREF
__m128 v48; // [rsp+30h] [rbp-D0h] BYREF
__m128 v49; // [rsp+40h] [rbp-C0h] BYREF
__m128 v50; // [rsp+50h] [rbp-B0h] BYREF
__m128 v51; // [rsp+60h] [rbp-A0h] BYREF
__m128 v52; // [rsp+70h] [rbp-90h]
__m128 v53; // [rsp+80h] [rbp-80h]
__m128 v54; // [rsp+90h] [rbp-70h]
__int128 v55[2]; // [rsp+A0h] [rbp-60h] BYREF
__int128 v56[2]; // [rsp+C0h] [rbp-40h] BYREF
__int128 v57[2]; // [rsp+E0h] [rbp-20h] BYREF
__int128 v58[2]; // [rsp+100h] [rbp+0h] BYREF
__int128 v59[2]; // [rsp+120h] [rbp+20h] BYREF
__int128 v60[2]; // [rsp+140h] [rbp+40h] BYREF
__int128 v61[2]; // [rsp+160h] [rbp+60h] BYREF
__int128 v62[2]; // [rsp+180h] [rbp+80h] BYREF
__int128 v63[2]; // [rsp+1A0h] [rbp+A0h] BYREF
__int128 v64[2]; // [rsp+1C0h] [rbp+C0h] BYREF
__int128 v65[2]; // [rsp+1E0h] [rbp+E0h] BYREF
__int128 v66[2]; // [rsp+200h] [rbp+100h] BYREF
__int128 v67[2]; // [rsp+220h] [rbp+120h] BYREF
__int128 v68[2]; // [rsp+240h] [rbp+140h] BYREF
__int128 v69[2]; // [rsp+260h] [rbp+160h] BYREF
char v70[48]; // [rsp+280h] [rbp+180h] BYREF
char *v71; // [rsp+2C8h] [rbp+1C8h] BYREF
__int128 *v72; // [rsp+2D0h] [rbp+1D0h] BYREF
v1 = -1i64;
v51.m128_u64[0] = 0x6E797AFBF5570CDEi64;
v47.m128_u64[0] = 0x3C5A8F9432649Di64;
v51.m128_u64[1] = 0x3AD429653BEEB61i64;
v47.m128_u64[1] = 0x3AD429636D08206i64;
v51 = _mm_xor_ps((__m128)_mm_load_si128((const __m128i *)&v51), v47);// Decrypted UTF-8: Cheat Engine
v3 = -1i64;
memset(v55, 0, sizeof(v55));
v4 = 0;
do
++v3;
while ( v51.m128_i8[v3] );
sub_FFFFF801BCEF2B2C(v55, &v51);
v47.m128_u64[0] = 0x3C5A8F9432649Di64;
v51.m128_u64[0] = 0x6E5537EEFE5C01DFi64;
v47.m128_u64[1] = 0x3AD429636D08206i64;
v51.m128_u64[1] = 0x3AD3BE65AB5C626i64;
v5 = -1i64;
v51 = _mm_xor_ps((__m128)_mm_load_si128((const __m128i *)&v51), v47);// Decrypted UTF-8: Benjamingine
memset(v56, 0, sizeof(v56));
do
++v5;
while ( v51.m128_i8[v5] );
sub_FFFFF801BCEF2B2C(v56, &v51);
v47.m128_u64[0] = 0x3C5A8F9432649Di64;
v51.m128_u64[0] = 0x205D33C5B45C01CAi64;
v47.m128_u64[1] = 0x3AD429636D08206i64;
v51.m128_u64[1] = 0x3AD429636A5EB4Ai64;
v6 = -1i64;
v51 = _mm_xor_ps((__m128)_mm_load_si128((const __m128i *)&v51), v47);// Decrypted UTF-8: Wen Jia Delpy
memset(v57, 0, sizeof(v57));
do
++v6;
while ( v51.m128_i8[v6] );
sub_FFFFF801BCEF2B2C(v57, &v51);
v47.m128_u64[0] = 0x3C5A8F9432649Di64;
v51.m128_u64[0] = 0x6D5511E8FA5D0CDEi64;
v47.m128_u64[1] = 0x3AD429636D08206i64;
v51.m128_u64[1] = 0x3AD42F857B8C126i64;
v7 = -1i64;
v51 = _mm_xor_ps((__m128)_mm_load_si128((const __m128i *)&v51), v47);// Decrypted UTF-8: ChongKimLiu
memset(v58, 0, sizeof(v58));
do
++v7;
while ( v51.m128_i8[v7] );
sub_FFFFF801BCEF2B2C(v58, &v51);
v47.m128_u64[0] = 0x3C5A8F9432649Di64;
v51.m128_u64[0] = 0x4B6E1BC7C76621D3i64;
v47.m128_u64[1] = 0x3AD429636D08206i64;
v51.m128_u64[1] = 0x3AD42FA51B1D126i64;
v8 = -1i64;
v51 = _mm_xor_ps((__m128)_mm_load_si128((const __m128i *)&v51), v47);// Decrypted UTF-8: NETSHARK Chan
memset(v59, 0, sizeof(v59));
do
++v8;
while ( v51.m128_i8[v8] );
sub_FFFFF801BCEF2B2C(v59, &v51);
v47.m128_u64[0] = 0x3C5A8F9432649Di64;
v51.m128_u64[0] = 0x547D1FC7D76134DBi64;
v47.m128_u64[1] = 0x3AD429636D08206i64;
v51.m128_u64[1] = 0x3AD429636D08255i64;
v9 = -1i64;
v51 = _mm_xor_ps((__m128)_mm_load_si128((const __m128i *)&v51), v47);// Decrypted UTF-8: FPSCHEAT Sagl
memset(v60, 0, sizeof(v60));
do
++v9;
while ( v51.m128_i8[v9] );
sub_FFFFF801BCEF2B2C(v60, &v51);
v51.m128_u64[0] = 0x3C5A8F9432649Di64;
v47.m128_u64[0] = 0x6D4934E6E05308CDi64;
v51.m128_u64[1] = 0x3AD429636D08206i64;
v47.m128_u64[1] = 0x6FCC36FF51B9C626i64;
v10 = _mm_xor_ps((__m128)_mm_load_si128((const __m128i *)&v47), v51);
v11 = -1i64;
v48.m128_u64[0] = 0x9069BF0012660EFi64;
v48.m128_u64[1] = 0x3C2843EC151381F4i64;
si128 = (__m128)_mm_load_si128((const __m128i *)&v48);
v52.m128_u64[0] = 0x4526EB856E5427CFi64;
v52.m128_u64[1] = 0x3C2843EC1513C2B8i64;
v48 = _mm_xor_ps(si128, v52); // Decrypted Raw (unprintable): 72 04 14 95 7f c1 3a 09 4c 43 00 00 00 00 00 00
v47 = v10; // Decrypted UTF-8: PlatinumS
memset(v61, 0, sizeof(v61));
do
++v11;
while ( v47.m128_i8[v11] );
sub_FFFFF801BCEF2B2C(v61, &v47);
v48.m128_u64[1] = 0x3C2843EC1513C2B8i64;
v47.m128_u64[0] = 0x531C12CCD16636D7i64;
v13 = -1i64;
v51.m128_u64[0] = 0x3C5A8F9432649Di64;
v47.m128_u64[1] = 0x23E30DDF6285CE49i64;
v14 = (__m128)_mm_load_si128((const __m128i *)&v47);
v48.m128_u64[0] = 0x4526EB856E546282i64;
v15 = (__m128)_mm_load_si128((const __m128i *)&v48);
v51.m128_u64[1] = 0x3AD429636D08206i64;
v52.m128_u64[0] = 0x4526EB856E5427CFi64;
v52.m128_u64[1] = 0x3C2843EC1513C2B8i64;
v48 = _mm_xor_ps(v15, v52); // Decrypted UTF-8: ME
v47 = _mm_xor_ps(v14, v51); // Decrypted UTF-8: JRTECH S Digital
memset(v62, 0, sizeof(v62));
do
++v13;
while ( v47.m128_i8[v13] );
sub_FFFFF801BCEF2B2C(v62, &v47);
v51.m128_u64[1] = 0x3AD429636D08206i64;
v51.m128_u64[0] = 0x3C36FDC71230D5i64;
v16 = (__m128)_mm_load_si128((const __m128i *)&v51);
v47.m128_u64[0] = 0x3C5A8F9432649Di64;
v17 = -1i64;
v47.m128_u64[1] = 0x3AD429636D08206i64;
v51 = _mm_xor_ps(v16, v47); // Decrypted Raw (unprintable): 02 06 74 16 31 24 20 53 20 44 69 67 69 74 61 6c
memset(v63, 0, sizeof(v63));
do
++v17;
while ( v51.m128_i8[v17] );
sub_FFFFF801BCEF2B2C(v63, &v51);
v51.m128_u64[0] = 0x3C5A8F9432649Di64;
v47.m128_u64[0] = 0x431C34EEF05C05D5i64;
v18 = -1i64;
v51.m128_u64[1] = 0x3AD429636D08206i64;
v47.m128_u64[1] = 0x64C32DD516A9F66Fi64;
v19 = _mm_xor_ps((__m128)_mm_load_si128((const __m128i *)&v47), v51);
v48.m128_u64[0] = 0x315582C14E3D46BBi64;
v48.m128_u64[1] = 0x77410FCC6170ABCAi64;
v20 = (__m128)_mm_load_si128((const __m128i *)&v48);
v49.m128_u64[0] = 0x4AC1249BE823DE60i64;
v49.m128_u64[1] = 0x5DC27D2DFBEB5206i64;
v50.m128_u64[0] = 0x21789A509F80B113i64;
v50.m128_u64[1] = 0x8BBE4558BC680C79ui64;
v53.m128_u64[0] = 0x23A060BBC844B001i64;
v53.m128_u64[1] = 0x2EA61242BCCB2B6Ai64;
v54.m128_u64[0] = 0x4C0CE831EFE5F533i64;
v47 = v19; // Decrypted UTF-8: Handan C
v21 = _mm_xor_ps((__m128)_mm_load_si128((const __m128i *)&v49), v53);
v54.m128_u64[1] = 0x8BBE4558BC1C621Cui64;
v52.m128_u64[0] = 0x4526EB856E5427CFi64;
v52.m128_u64[1] = 0x3C2843EC1513C2B8i64;
v48 = _mm_xor_ps(v20, v52); // Decrypted UTF-8: tai Dist
v50 = _mm_xor_ps((__m128)_mm_load_si128((const __m128i *)&v50), v54);// Decrypted UTF-8: Department
v49 = v21; // Decrypted UTF-8: ang Daily Goods
memset(v64, 0, sizeof(v64));
do
++v18;
while ( v47.m128_i8[v18] );
sub_FFFFF801BCEF2B2C(v64, &v47);
v51.m128_u64[0] = 0x3C5A8F9432649Di64;
v47.m128_u64[0] = 0x205B34E6FE5C05D3i64;
v22 = -1i64;
v51.m128_u64[1] = 0x3AD429636D08206i64;
v47.m128_u64[1] = 0x23C223FF4EB9EA5Ci64;
v23 = _mm_xor_ps((__m128)_mm_load_si128((const __m128i *)&v47), v51);
v48.m128_u64[0] = 0x314786F701324986i64;
v48.m128_u64[1] = 0x544B26B8357DADD1i64;
v24 = (__m128)_mm_load_si128((const __m128i *)&v48);
v49.m128_u64[0] = 0x608019DCA728DF6Fi64;
v49.m128_u64[1] = 0x2EA67636F0E70505i64;
v53.m128_u64[0] = 0x23A060BBC844B001i64;
v47 = v23; // Decrypted UTF-8: Nanjing ity Cong
v25 = (__m128)_mm_load_si128((const __m128i *)&v49);
v53.m128_u64[1] = 0x2EA61242BCCB2B6Ai64;
v52.m128_u64[0] = 0x4526EB856E5427CFi64;
v52.m128_u64[1] = 0x3C2843EC1513C2B8i64;
v49 = _mm_xor_ps(v25, v53); // Decrypted UTF-8: nology Cly Goods
v48 = _mm_xor_ps(v24, v52); // Decrypted UTF-8: Informatrict LiK
memset(v65, 0, sizeof(v65));
do
++v22;
while ( v47.m128_i8[v22] );
sub_FFFFF801BCEF2B2C(v65, &v47);
v51.m128_u64[0] = 0x3C5A8F9432649Di64;
v47.m128_u64[0] = 0x591C3DE1FD4311DBi64;
v51.m128_u64[1] = 0x3AD429636D08206i64;
v47.m128_u64[1] = 0x66E362F857A4EC73i64;
v26 = -1i64;
v27 = _mm_xor_ps((__m128)_mm_load_si128((const __m128i *)&v47), v51);
v48.m128_u64[0] = 0x2072CBEE1C3B50BBi64;
v48.m128_u64[1] = 0x70046D835633AADBi64;
v28 = (__m128)_mm_load_si128((const __m128i *)&v48);
v49.m128_u64[0] = 0x23A060BBC86AD475i64;
v49.m128_u64[1] = 0x2EA61242BCCB2B6Ai64;
v47 = v27; // Decrypted UTF-8: Fuqing YZhixiao
v29 = (__m128)_mm_load_si128((const __m128i *)&v49);
v53.m128_u64[1] = 0x2EA61242BCCB2B6Ai64;
v52.m128_u64[0] = 0x4526EB856E5427CFi64;
v52.m128_u64[1] = 0x3C2843EC1513C2B8i64;
v53.m128_u64[0] = 0x23A060BBC844B001i64;
v49 = _mm_xor_ps(v29, v53); // Decrypted Raw (unprintable): 74 64 2e 00 00 00 00 00 6f 2e 2c 4c 74 64 00 00
v48 = _mm_xor_ps(v28, v52); // Decrypted UTF-8: twork Teion Tech
memset(v66, 0, sizeof(v66));
do
++v26;
while ( v47.m128_i8[v26] );
sub_FFFFF801BCEF2B2C(v66, &v47);
v48.m128_u64[1] = 0x3C2843EC1513C2B8i64;
v47.m128_u64[0] = 0x205233E2FB4001D7i64;
v30 = -1i64;
v51.m128_u64[0] = 0x3C5A8F9432649Di64;
v47.m128_u64[1] = 0x6ADF07B64FB4ED45i64;
v31 = (__m128)_mm_load_si128((const __m128i *)&v47);
v48.m128_u64[0] = 0x4526EB856E5427ACi64;
v32 = (__m128)_mm_load_si128((const __m128i *)&v48);
v51.m128_u64[1] = 0x3AD429636D08206i64;
v52.m128_u64[0] = 0x4526EB856E5427CFi64;
v52.m128_u64[1] = 0x3C2843EC1513C2B8i64;
v48 = _mm_xor_ps(v32, v52); // Decrypted UTF-8: c
v47 = _mm_xor_ps(v31, v51); // Decrypted UTF-8: Jeromin untan Ne
memset(v67, 0, sizeof(v67));
do
++v30;
while ( v47.m128_i8[v30] );
sub_FFFFF801BCEF2B2C(v67, &v47);
v47.m128_u64[0] = 0x3C5A8F9432649Di64;
v51.m128_u64[0] = 0x6B5309AFFD590DD3i64;
v47.m128_u64[1] = 0x3AD429636D08206i64;
v51.m128_u64[1] = 0x3AD429640BFEE69i64;
v33 = -1i64;
v51 = _mm_xor_ps((__m128)_mm_load_si128((const __m128i *)&v51), v47);// Decrypted UTF-8: Niki SokCody Eri
memset(v68, 0, sizeof(v68));
do
++v33;
while ( v51.m128_i8[v33] );
sub_FFFFF801BCEF2B2C(v68, &v51);
v51.m128_u64[0] = 0x3C5A8F9432649Di64;
v47.m128_u64[0] = 0x205B34E6FE5B01DFi64;
v51.m128_u64[1] = 0x3AD429636D08206i64;
v47.m128_u64[1] = 0x66DD2DDE58B9ED4Ci64;
v34 = _mm_xor_ps((__m128)_mm_load_si128((const __m128i *)&v47), v51);
v48.m128_u64[0] = 0x11068EE20F396EEFi64;
v48.m128_u64[1] = 0x5B472F837B7BA1DDi64;
v35 = (__m128)_mm_load_si128((const __m128i *)&v48);
v49.m128_u64[0] = 0x23A04EDFBC089078i64;
v49.m128_u64[1] = 0x2EA61242BCCB2B6Ai64;
v47 = v34; // Decrypted UTF-8: Beijing olov
v36 = (__m128)_mm_load_si128((const __m128i *)&v49);
v53.m128_u64[1] = 0x2EA61242BCCB2B6Ai64;
v52.m128_u64[0] = 0x4526EB856E5427CFi64;
v52.m128_u64[1] = 0x3C2843EC1513C2B8i64;
v53.m128_u64[0] = 0x23A060BBC844B001i64;
v49 = _mm_xor_ps(v36, v53); // Decrypted UTF-8: y Ltd.
v48 = _mm_xor_ps(v35, v52); // Decrypted UTF-8: Image T
memset(v69, 0, sizeof(v69));
do
++v1;
while ( v47.m128_i8[v1] );
sub_FFFFF801BCEF2B2C(v69, &v47);
v48.m128_u64[0] = 0i64;
v71 = v70;
v72 = v55;
v47 = 0i64;
sub_FFFFF801BCEF2EC4(&v47, 15i64, &v72, &v71);
v37 = 15i64;
v38 = (__int64 *)v70;
v39 = 1;
do
{
v38 -= 4;
--v37;
v40 = v38[3];
if ( v40 > 15 )
{
v41 = *v38;
if ( v40 + 1 >= 0x1000 )
{
v42 = *(_QWORD *)(v41 - 8);
v43 = (const char *)(v41 - v42);
if ( (unsigned __int64)(v43 - 8) > 0x1F )
{
Xlength_error(v43);
JUMPOUT(0xFFFFF801BCEF3E7Eui64);
}
v41 = v42;
}
operator_delete(v41);
}
v38[2] = 0i64;
v38[3] = 15i64;
*(_BYTE *)v38 = 0;
}
while ( v37 );
if ( subjectName && *subjectName )
{
for ( i = v47.m128_u64[0]; i != v47.m128_u64[1]; i += 32i64 )// range based loop for vector possibly
{
v45 = (char *)i;
if ( *(_QWORD *)(i + 24) > 15ui64 )
v45 = *(char **)i;
if ( stristr(subjectName, v45) )
goto LABEL_48;
}
}
else
{
v39 = 0;
LABEL_48:
v4 = v39;
}
sub_FFFFF801BCEF4A08(&v47);
return v4;
}
EmacVerifyFileUnknown is virtualized so i have no idea what’s being verified there.
Certificate Blacklist (14 entries)
Function: EmacVerifyFileCertificateName (0xBCEF3404) — Checks PE signature subject name against known malicious signers:
| # | Subject Name | Association |
|---|---|---|
| 1 | Cheat Engine | Cheat Engine tool |
| 2 | Benjamingine | Cheat Engine variant signer |
| 3 | Wen Jia Delpy | Benjamin Delpy alias (Mimikatz author) |
| 4 | ChongKimLiu | Known cheat certificate signer |
| 5 | NETSHARK Chan | Known cheat certificate signer |
| 6 | FPSCHEAT Sagl | FPS cheat company |
| 7 | PlatinumS | Cheat provider |
| 8 | JRTECH S Digital | Known cheat signer |
| 9 | Handan City District… | Chinese company (cheat front) |
| 10 | Nanjing Information Technology… | Chinese company |
| 11 | Fuqing Yuntao Network Tech | Chinese company |
| 12 | Jeromin Yuntan Network | Company certificate |
| 13 | Niki Sok / Cody Eri | Known cheat signer |
| 14 | Beijing Image Technology Ltd. | Chinese company |
File I/O and Signing Functions
| Function | Address | Purpose |
|---|---|---|
EmacFltReadFileToBuffer | 0xBCF15DB0 | Reads file contents through minifilter |
EmacVerifyFileSigned | 0xBCF14290 | CI.dll-based Authenticode verification |
EmacVerifyFileSignedByFileName | 0xBCF14940 | Verifies signature by file path |
EmacVerifyFilePolicyInfo | 0xBCF13E04 | Checks file against policy |
EmacCalculateAuthenticodeHash | 0xBCF149F8 | Computes PE Authenticode hash |
EmacIsInVerifiedImagesList | 0xBCF14168 | Checks cached verification results |
ObRegisterCallbacks & Handle Protection
Handle Stripping
Function: EmacObPostHandleOperation (0xBCEF4B98) — Post-operation callback for handle creation/duplication on PsProcessType.
Function: EmacStripProcessHandles (0xBCF2FF74) — Uses ExEnumHandleTable to iterate all system handles and strip dangerous access rights from handles to protected processes:
void HandleEnumCallback(HANDLE_TABLE_ENTRY* entry, PVOID context) {
// XOR lower 25 bits to remove dangerous access rights
entry->GrantedAccess = (entry->GrantedAccess & 0xFE000000) |
(entry->GrantedAccess ^ stripMask) & 0x01FFFFFF;
}
Access masks stripped:
| Object Type | Strip Mask | Removed Rights |
|---|---|---|
| Process (standard) | 0xFFFFF7C5 | VM_READ, VM_WRITE, VM_OPERATION |
| Process (PPL) | 0xFFFFF7D5 | More permissive for Protected Process Light |
| Thread | 0xFFFFFFED | SUSPEND_RESUME, TERMINATE |
Handle Management Functions
| Function | Address | Purpose |
|---|---|---|
EmacEnumHandleTableWin7 | 0xBCF1615C | Legacy handle table enumeration (dead code) |
EmacEnumHandleTable | 0xBCF16194 | Modern handle table walker |
EmacGetProcessHandlesInfo | 0xBCF1620C | Gets handle info for a process |
IDA decompiled snippets
Conclusion
Very solid approaches, minifilter with signature verification on kernel is the today standard. The certificate blacklist of 14 known cheat signers and the handle stripping via ExEnumHandleTable add significant protection layers.
Weak points surely are the global variables, anyone can manipulate the game process id, as well as the variables storing addresses from ntdll, kernel32, etc. The Process Hacker detection by filename only (dbk64, kprocesshacker) is also trivially bypassable by renaming.