
Злоупотребление механизмом обратных вызовов ядра win32k.sys для выполнения произвольного кода
Этот репозиторий предоставлен строго в образовательных и исследовательских целях, связанных с защитой.
В нём демонстрируются внутренние механизмы диспетчеризации обратных вызовов Windows и концепции управления потоком выполнения, связанные с KernelCallbackTable, в контексте proof-of-concept.
Автор не несёт ответственности за неправомерное использование.
Эта техника инъекции злоупотребляет путём диспетчеризации обратных вызовов из ядра в пользовательский режим, используемым графической подсистемой Windows (win32k.sys), для получения выполнения кода в удалённом процессе. Обнаружив KernelCallbackTable через Process Environment Block (PEB) целевого процесса, оператор может перечислить записи обратных вызовов и определить легитимные процедуры пользовательского режима, вызываемые при связанных с GUI переходах из ядра.
Вместо традиционной KernelCallbackTable Injection, когда запись таблицы обратных вызовов перезаписывается напрямую адресом шеллкода, этот вариант перехватывает легитимную цель обратного вызова, на которую ссылается таблица, и перенаправляет выполнение на управляемый атакующим шеллкод при вызове. Поскольку выполнение перехватывается через существующий и ожидаемый путь обратных вызовов, данная техника может служить более скрытной альтернативой более традиционным примитивам, таким как создание удалённого потока или APC-based injection.
Графическая подсистема Windows делегирует часть обработки, связанной с GUI, в пользовательский режим через механизм обратных вызовов, инициируемый из режима ядра. Когда win32k.sys требует выполнения логики в контексте GUI-процесса, он вызывает KeUserModeCallback, чтобы выполнить контролируемый переход из режима ядра в пользовательский режим, сохраняя границу изоляции между обоими контекстами выполнения.
Этот переход устанавливает легитимный путь выполнения, через который ядро диспетчеризует обратные вызовы графической подсистемы в пользовательский режим, — тот же путь, которым впоследствии злоупотребляет представленная техника.
После завершения перехода выполнение попадает в KiUserCallbackDispatcher — процедуру из ntdll.dll, отвечающую за получение индекса обратного вызова, переданного ядром, и передачу управления соответствующему обработчику в пользовательском режиме. Эта процедура служит обязательной точкой входа для всех обратных вызовов, инициированных через KeUserModeCallback.
Поскольку разрешение всех обратных вызовов сходится в этом диспетчере, KiUserCallbackDispatcher выступает центральным связующим звеном между запросами обратных вызовов ядра и их последующим выполнением в пользовательском режиме.
Чтобы определить адрес назначения запрошенного обратного вызова, KiUserCallbackDispatcher обращается к KernelCallbackTable, хранящейся в PEB целевого процесса. Каждая запись таблицы содержит указатель на процедуру обратного вызова пользовательского режима, связанную с конкретной операцией графической подсистемы, как правило реализованную в user32.dll.
Традиционные техники KernelCallbackTable Injection напрямую перезаписывают одну или несколько таких записей, чтобы перенаправить выполнение. Несмотря на эффективность, изменение самой таблицы создаёт структурные аномалии, которые могут быть легко обнаружены при проверке целостности PEB или содержимого таблицы обратных вызовов. Представленная техника избегает этого, сохраняя структуру таблицы и вместо этого перехватывая (детуря) цель обратного вызова, на которую ссылается запись.
Среди доступных записей KernelCallbackTable __fnCOPYDATA предоставляет особенно удобный примитив запуска, поскольку его можно вызвать извне, отправив сообщение WM_COPYDATA через SendMessage(). Это позволяет детерминированно запускать обратный вызов без необходимости в особом состоянии процесса или сложном взаимодействии.
Используя естественно доступную и часто используемую цель обратного вызова, техника получает надёжный примитив выполнения, оставаясь при этом полностью в рамках ожидаемой цепочки диспетчеризации обратных вызовов до момента перенаправления.

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

Для её получения используется следующая логика:
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, для последующего детура.
