- Status
- Offline
- Joined
- Mar 3, 2026
- Messages
- 546
- Reaction score
- 7
Anyone still thinking their external overlay is 100% safe just because it's "external" needs to look at what Easy Anti-Cheat (EAC) is actually doing inside their kernel driver. While digging through their latest mess of obfuscated code islands, it looks like they are still heavily relying on win32k exports to handle screen captures from ring 0.
Technical Breakdown
The logic below shows how the driver resolves critical GDI functions manually rather than relying on standard imports—likely to avoid simple import hooks and to maintain a low profile. They are targeting everything needed for a full pixel-data grab: NtUserGetDC, NtGdiBitBlt, and NtGdiGetDIBitsInternal.
Core Logic Flow:
Anti-Cheat Context
This isn't just for checking if you have a colorful menu open. EAC uses these screenshots to catch ESP box boundaries, chams that aren't properly hidden from GDI calls, and external overlays that don't use more advanced hijacking techniques to stay invisible. If your overlay is just a top-most transparent window, they already have you.
Has anyone else been tracking their win32k traversal in the latest builds? It seems they are getting more aggressive with how often they trigger these captures during Premier matches in CS2 and high-tier Tarkov raids.
Drop your findings or crash logs if you're hitting BSODs while trying to hook these resolvers.
Technical Breakdown
The logic below shows how the driver resolves critical GDI functions manually rather than relying on standard imports—likely to avoid simple import hooks and to maintain a low profile. They are targeting everything needed for a full pixel-data grab: NtUserGetDC, NtGdiBitBlt, and NtGdiGetDIBitsInternal.
Core Logic Flow:
- Resolves PsGetThreadWin32Thread to ensure they are in a context that can interact with the GUI subsystem.
- Dynamically resolves win32k.sys exports using a custom resolver (EacPeLocateLowCmpSpan).
- Grabs the DC, creates a compatible bitmap in memory, and triggers a BitBlt to copy the screen contents.
- Uses GetDIBits to dump the buffer, which is likely then phoning home or being analyzed for known ESP/overlay patterns.
Code:
*a1 = 0LL;
*a2 = 0;
CurrentThread = KeGetCurrentThread();
v9 = EacDecodeNativeResolverPair(g_NativeImport_PsGetThreadWin32Thread_mod, g_NativeImport_PsGetThreadWin32Thread_enc);
if ( !((__int64 (__fastcall *)(struct _KTHREAD *))((0x348CD615FA024567LL * v9) ^ 0xE2DFA06E1F229229uLL))(CurrentThread) )
goto LABEL_13;
if ( g_Win32kCaptureExportsReady )
goto LABEL_3;
if ( (KeGetCurrentIrql() & 0xFE) != 0 )
{
LABEL_13:
LODWORD(v10) = 0;
return (unsigned int)v10;
}
LODWORD(v10) = 0;
v22 = (_DWORD *)EacJumpThunk_1400F4074(11LL, 0LL);
if ( !v22 )
return (unsigned int)v10;
v23 = v22;
v58[0] = (__m128i)xmmword_1401D62F0;
v58[1].m128i_i32[0] = 228626287;
v24 = (char **)EacVmLowCmpTransform_1400FBE21(v58, v22);
v58[0] = _mm_load_si128(xmmword_1401D6300);
v58[1].m128i_i32[0] = -205954010;
v25 = (char **)EacVmLowCmpTransform_1400FBE21(v58, v23);
if ( v25 == 0LL || v24 == 0LL )
{
EacFreeTrackedBlock(v23);
goto LABEL_13;
}
v58[0].m128i_i32[2] = 0;
v58[0].m128i_i64[0] = 0LL;
v26 = -2109794682;
for ( i = 0LL; i != 3; ++i )
{
v26 = __ROL4__(1140671485 * v26 + 12820163, 1);
v58[0].m128i_i32[i] = v26 ^ dword_1401D86DC[i];
}
g_Win32k_NtUserGetDC = (__int64 (__fastcall *)(_QWORD))EacPeLocateLowCmpSpan(
v24[2],
*((unsigned int *)v24 + 6),
(unsigned __int64)v58);
v28 = 0LL;
memset(v58, 0, 24);
v29 = -604945222;
do
{
v29 = __ROL4__(1140671485 * v29 + 12820163, 30);
v58[0].m128i_i32[v28] = v29 ^ dword_1401D86E8[v28];
++v28;
}
while ( v28 != 6 );
g_Win32k_NtGdiCreateCompatibleDC = (__int64 (__fastcall *)(_QWORD))EacPeLocateLowCmpSpan(
v25[2],
*((unsigned int *)v25 + 6),
(unsigned __int64)v58);
memset(v58, 0, 24);
v30 = 570971763;
for ( j = 0LL; j != 4; ++j )
{
v58[0].m128i_i32[j] = *((_DWORD *)&unk_1401D8700 + j) ^ v30;
v30 = __ROL4__(1140671485 * v30 + 12820163, 29);
}
v32 = 1622400489;
for ( k = 0LL; k != 3; ++k )
{
v58[1].m128i_i8[k] = v32 ^ *((_BYTE *)&unk_1401D8700 + k + 16);
v32 >>= 8;
}
g_Win32k_NtGdiGetDeviceCaps = (__int64 (__fastcall *)(_QWORD, _QWORD))EacPeLocateLowCmpSpan(
v24[2],
*((unsigned int *)v24 + 6),
(unsigned __int64)v58);
memset(v58, 0, 28);
v34 = 1207376218;
for ( m = 0LL; m != 7; ++m )
{
v34 = -1140671485 * v34 - 12820164;
v58[0].m128i_i32[m] = v34 ^ dword_1401D8713[m];
}
g_Win32k_NtGdiCreateCompatibleBitmap = (__int64 (__fastcall *)(_QWORD, _QWORD, _QWORD))EacPeLocateLowCmpSpan(
v25[2],
*((unsigned int *)v25 + 6),
(unsigned __int64)v58);
memset(v58, 0, 28);
v36 = -701610193;
for ( n = 0LL; n != 4; ++n )
{
v58[0].m128i_i32[n] = *((_DWORD *)&unk_1401D872F + n) ^ v36;
v36 = -1140671485 * v36 - 12820163;
}
v38 = 1482325863;
for ( ii = 0LL; ii != 2; ++ii )
{
v58[1].m128i_i8[ii] = v38 ^ *((_BYTE *)&unk_1401D872F + ii + 16);
v38 >>= 8;
}
g_Win32k_NtGdiSelectBitmap = (__int64 (__fastcall *)(_QWORD, _QWORD))EacPeLocateLowCmpSpan(
v25[2],
*((unsigned int *)v25 + 6),
(unsigned __int64)v58);
memset(v58, 0, 18);
v58[0].m128i_i32[2] = 0;
v58[0].m128i_i64[0] = 0LL;
v40 = 58455018;
for ( jj = 0LL; jj != 3; ++jj )
{
v40 = _byteswap_ulong(214013 * v40 + 2531011);
v58[0].m128i_i32[jj] = v40 ^ dword_1401D8741[jj];
}
g_Win32k_NtGdiBitBlt = (__int64 (__fastcall *)(_QWORD, _QWORD, _QWORD, _QWORD, _DWORD, _QWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD))EacPeLocateLowCmpSpan(v25[2], *((unsigned int *)v25 + 6), (unsigned __int64)v58);
v42 = 0LL;
memset(v58, 0, 21);
v43 = -1646841888;
do
{
v58[0].m128i_i32[v42] = *((_DWORD *)&unk_1401D874D + v42) ^ v43;
++v42;
v43 = __ROL4__(214013 * v43 + 2531011, 28);
}
while ( v42 != 5 );
v58[1].m128i_i8[4] = *((_BYTE *)&unk_1401D874D + 20) ^ 0xEA;
g_Win32k_NtGdiDeleteObjectApp = (__int64 (__fastcall *)(_QWORD))EacPeLocateLowCmpSpan(
v24[2],
*((unsigned int *)v24 + 6),
(unsigned __int64)v58);
memset(v58, 0, 21);
v44 = -1627889142;
for ( kk = 0LL; kk != 4; ++kk )
{
v44 = __ROL4__(214013 * v44 + 2531011, 29);
v58[0].m128i_i32[kk] = v44 ^ dword_1401D8762[kk];
}
g_Win32k_NtUserReleaseDC = (__int64 (__fastcall *)(_QWORD))EacPeLocateLowCmpSpan(
v24[2],
*((unsigned int *)v24 + 6),
(unsigned __int64)v58);
memset(v58, 0, 23);
v46 = 510780929;
for ( mm = 0LL; mm != 5; ++mm )
{
v58[0].m128i_i32[mm] = *((_DWORD *)&unk_1401D8772 + mm) ^ v46;
v46 = __ROL4__(214013 * v46 + 2531011, 2);
}
v48 = 525754037;
for ( nn = 0LL; nn != 3; ++nn )
{
v58[1].m128i_i8[nn + 4] = v48 ^ *((_BYTE *)&unk_1401D8772 + nn + 20);
v48 >>= 8;
}
g_Win32k_NtGdiGetDIBitsInternal = (__int64 (__fastcall *)(_QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _DWORD, _DWORD, _DWORD))EacPeLocateLowCmpSpan(v25[2], *((unsigned int *)v25 + 6), (unsigned __int64)v58);
memset(v58, 0, 23);
v50 = g_Win32k_NtGdiGetDIBitsInternal != 0LL;
v51 = _mm_cmpeq_epi32(
_mm_unpacklo_epi64(
_mm_loadl_epi64((const __m128i *)&g_Win32k_NtGdiDeleteObjectApp),
_mm_loadl_epi64((const __m128i *)&g_Win32k_NtUserReleaseDC)),
(__m128i)0LL);
v52 = _mm_cmpeq_epi32(
_mm_unpacklo_epi64(
_mm_loadl_epi64((const __m128i *)&g_Win32k_NtGdiSelectBitmap),
_mm_loadl_epi64((const __m128i *)&g_Win32k_NtGdiBitBlt)),
(__m128i)0LL);
v53 = _mm_cmpeq_epi32(
_mm_unpacklo_epi64(
_mm_loadl_epi64((const __m128i *)&g_Win32k_NtGdiGetDeviceCaps),
_mm_loadl_epi64((const __m128i *)&g_Win32k_NtGdiCreateCompatibleBitmap)),
(__m128i)0LL);
v54 = _mm_cmpeq_epi32(
_mm_unpacklo_epi64(
_mm_loadl_epi64((const __m128i *)&g_Win32k_NtUserGetDC),
_mm_loadl_epi64((const __m128i *)&g_Win32k_NtGdiCreateCompatibleDC)),
(__m128i)0LL);
v55 = (_mm_movemask_epi8(
_mm_packs_epi32(
_mm_packs_epi32(
_mm_and_si128(_mm_shuffle_epi32(v54, 177), v54),
_mm_and_si128(_mm_shuffle_epi32(v53, 177), v53)),
_mm_packs_epi32(
_mm_and_si128(_mm_shuffle_epi32(v52, 177), v52),
_mm_and_si128(_mm_shuffle_epi32(v51, 177), v51)))) & 0xAAAA) == 0;
g_Win32kCaptureExportsReady = g_Win32k_NtGdiGetDIBitsInternal != 0LL && v55;
EacFreeTrackedBlock(v23);
if ( !v50 || !v55 )
goto LABEL_13;
LABEL_3:
LODWORD(v10) = 0;
v11 = g_Win32k_NtUserGetDC(0LL);
if ( v11 )
{
v12 = v11;
v13 = g_Win32k_NtGdiCreateCompatibleDC(v11);
if ( v13 )
{
v14 = v13;
*a3 = g_Win32k_NtGdiGetDeviceCaps(v12, 8LL);
v15 = g_Win32k_NtGdiGetDeviceCaps(v12, 10LL);
*a4 = v15;
v16 = g_Win32k_NtGdiCreateCompatibleBitmap(v12, (unsigned int)*a3, v15);
if ( v16 )
{
v17 = v16;
LODWORD(v10) = 0;
if ( g_Win32k_NtGdiSelectBitmap(v14, v16)
&& (unsigned __int8)g_Win32k_NtGdiBitBlt(v14, 0LL, 0LL, (unsigned int)*a3, *a4, v12, 0, 0, 13369376, -1, 0) )
{
v18 = EacJumpThunk_1400B4883(0LL, 40LL, 4LL);
if ( v18 )
{
v10 = v18;
v19 = 4 * *a4 * *a3;
*a2 = v19;
v20 = EacJumpThunk_1400B4883(0LL, v19, 4LL);
if ( v20 )
{
*(_DWORD *)v10 = 40;
*(_DWORD *)(v10 + 4) = *a3;
*(_DWORD *)(v10 + 8) = -*a4;
*(_DWORD *)(v10 + 12) = 2097153;
*(_QWORD *)(v10 + 16) = 0LL;
v57 = *a2;
v21 = v20;
if ( (unsigned int)g_Win32k_NtGdiGetDIBitsInternal(v14, v17, 0LL, *a4, v20, v10, 0, v57, 0) == *a4 )
*a1 = v21;
else
EacJumpThunk_1400B4A15(v21);
}
EacJumpThunk_1400B4A15(v10);
}
LOBYTE(v10) = *a1 != 0;
}
g_Win32k_NtGdiDeleteObjectApp(v17);
}
else
{
LODWORD(v10) = 0;
}
g_Win32k_NtGdiDeleteObjectApp(v14);
}
else
{
LODWORD(v10) = 0;
}
g_Win32k_NtUserReleaseDC(v12);
}
return (unsigned int)v10;
Anti-Cheat Context
This isn't just for checking if you have a colorful menu open. EAC uses these screenshots to catch ESP box boundaries, chams that aren't properly hidden from GDI calls, and external overlays that don't use more advanced hijacking techniques to stay invisible. If your overlay is just a top-most transparent window, they already have you.
The code islands are heavily split. EAC uses mutated resolvers and displacement-heavy jumps to make static analysis a nightmare. If you are reversing the driver, look for the EacVmLowCmpTransform calls—that is where the real magic happens for their internal protection.
Has anyone else been tracking their win32k traversal in the latest builds? It seems they are getting more aggressive with how often they trigger these captures during Premier matches in CS2 and high-tier Tarkov raids.
Drop your findings or crash logs if you're hitting BSODs while trying to hook these resolvers.