- Статус
- Оффлайн
- Регистрация
- 13 Фев 2026
- Сообщения
- 559
- Реакции
- 14
Парни, при очередном забеге по деобфускации драйвера EAC наткнулся на весьма характерный метод. Пока разрабы античитов рассказывают сказки про нейронки, в ядре по-прежнему живет старый добрый захват экрана через Win32k API.
Судя по всему, это их основной способ борьбы с внешними оверлеями, которые рисуются поверх игры. Логика метода завязана на динамическом резолве экспортов win32k.sys и последующем манипулировании GDI-объектами прямо из контекста ядра.
Что именно делает этот кусок кода:
Сам фрагмент кода (Hex-Rays выхлоп после чистки мусора):
Технические нюансы:
Обфускация у EAC сейчас довольно плотная, они активно используют code islands и кастомные джампы (EacJumpThunk), что затрудняет построение чистого графа вызовов. Тем не менее, использование GDI в ядре — это всегда костыль, который можно отследить. Если ваш внешний чит рисует ESP через обычный оверлей, знайте: EAC видит ваш экран почти так же, как его видите вы.
Интересно, как они планируют мерджить эти куски кода в будущем, учитывая, что Microsoft постепенно закручивает гайки для win32k в ядре. Кто-нибудь ковырял последние билды на предмет того, не перешли ли они на использование DirectX или более низкоуровневых интерфейсов захвата?
Судя по всему, это их основной способ борьбы с внешними оверлеями, которые рисуются поверх игры. Логика метода завязана на динамическом резолве экспортов win32k.sys и последующем манипулировании GDI-объектами прямо из контекста ядра.
Что именно делает этот кусок кода:
- Резолвит системные вызовы: NtUserGetDC, NtGdiCreateCompatibleDC, NtGdiBitBlt и NtGdiGetDIBitsInternal.
- Создает совместимый контекст устройства (DC) в памяти.
- Использует BitBlt для копирования содержимого экрана в созданный битмап.
- Вытягивает сырые пиксели через GetDIBits для дальнейшего анализа на стороне сервера или локальных проверок.
Сам фрагмент кода (Hex-Rays выхлоп после чистки мусора):
Код:
*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;
Технические нюансы:
Обфускация у EAC сейчас довольно плотная, они активно используют code islands и кастомные джампы (EacJumpThunk), что затрудняет построение чистого графа вызовов. Тем не менее, использование GDI в ядре — это всегда костыль, который можно отследить. Если ваш внешний чит рисует ESP через обычный оверлей, знайте: EAC видит ваш экран почти так же, как его видите вы.
Если при попытке реверса драйвера ловите BSOD с кодом KMODE_EXCEPTION_NOT_HANDLED, проверяйте IRQL. Как видно из кода, античит делает проверку (KeGetCurrentIrql() & 0xFE) != 0, прежде чем лезть в win32k.
Интересно, как они планируют мерджить эти куски кода в будущем, учитывая, что Microsoft постепенно закручивает гайки для win32k в ядре. Кто-нибудь ковырял последние билды на предмет того, не перешли ли они на использование DirectX или более низкоуровневых интерфейсов захвата?