Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Гайд Easy Anti-Cheat — Анализ метода снятия скриншотов из Kernel

Sloppy
Начинающий
Начинающий
Статус
Оффлайн
Регистрация
13 Фев 2026
Сообщения
559
Реакции
14
Парни, при очередном забеге по деобфускации драйвера EAC наткнулся на весьма характерный метод. Пока разрабы античитов рассказывают сказки про нейронки, в ядре по-прежнему живет старый добрый захват экрана через Win32k API.

Судя по всему, это их основной способ борьбы с внешними оверлеями, которые рисуются поверх игры. Логика метода завязана на динамическом резолве экспортов win32k.sys и последующем манипулировании GDI-объектами прямо из контекста ядра.

Что именно делает этот кусок кода:
  1. Резолвит системные вызовы: NtUserGetDC, NtGdiCreateCompatibleDC, NtGdiBitBlt и NtGdiGetDIBitsInternal.
  2. Создает совместимый контекст устройства (DC) в памяти.
  3. Использует BitBlt для копирования содержимого экрана в созданный битмап.
  4. Вытягивает сырые пиксели через GetDIBits для дальнейшего анализа на стороне сервера или локальных проверок.

Сам фрагмент кода (Hex-Rays выхлоп после чистки мусора):

Код:
Expand Collapse Copy
 *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 или более низкоуровневых интерфейсов захвата?
 
Назад
Сверху Снизу