
Abusando do mecanismo de callback do kernel win32k.sys para execução arbitrária de código
Este repositório é fornecido estritamente para fins educacionais e de pesquisa defensiva.
Ele demonstra os mecanismos internos de despacho de callbacks do Windows e conceitos de fluxo de controle relacionados à KernelCallbackTable em um contexto de prova de conceito.
O autor não assume nenhuma responsabilidade por uso indevido.
Esta técnica de injeção abusa do caminho de despacho de callbacks do kernel para o usuário usado pelo subsistema gráfico do Windows (win32k.sys) para obter execução de código dentro de um processo remoto. Ao localizar a KernelCallbackTableatravés doProcess Environment Block (PEB)` do processo alvo, um operador pode enumerar entradas de callback e identificar rotinas legítimas de modo usuário invocadas durante transições de kernel relacionadas à GUI.
Em vez de realizar a tradicional KernelCallbackTable Injection, em que uma entrada de callback é sobrescrita diretamente com o endereço do shellcode, esta variação aplica hook no destino legítimo de callback referenciado pela tabela e redireciona a execução para um shellcode controlado pelo atacante quando invocado. Como a execução é sequestrada por meio de um caminho de callback existente e esperado, a técnica pode fornecer uma alternativa mais furtiva a primitivas mais convencionais, como criação de threads remotas ou APC-based injection.
O subsistema gráfico do Windows delega partes do processamento relacionado à GUI para o modo usuário por meio de um mecanismo de callback iniciado a partir do modo kernel. Quando win32k.sys exige lógica para ser executada no contexto de um processo GUI, ele invoca KeUserModeCallback para realizar uma transição controlada do modo kernel para o modo usuário, preservando o limite de isolamento entre ambos os contextos de execução.
Essa transição estabelece o caminho de execução legítimo por meio do qual o kernel despacha callbacks do subsistema gráfico para o modo usuário — o mesmo caminho posteriormente abusado pela técnica apresentada.
Quando a transição é concluída, a execução entra em KiUserCallbackDispatcher, uma rotina da ntdll.dll responsável por receber o índice de callback fornecido pelo kernel e despachar a execução para o manipulador de callback de modo usuário correspondente. Essa rotina serve como ponto de entrada obrigatório para todos os callbacks iniciados por meio de KeUserModeCallback.
Como toda a resolução de callbacks converge neste dispatcher, KiUserCallbackDispatcher funciona como o pivô central entre as solicitações de callback do kernel e sua eventual execução no modo usuário.
Para resolver o destino de um callback solicitado, KiUserCallbackDispatcher consulta a KernelCallbackTable armazenada no PEB do processo alvo. Cada entrada da tabela contém um ponteiro para uma rotina de callback de modo usuário associada a uma operação específica do subsistema gráfico, normalmente implementada em user32.dll.
Técnicas tradicionais de KernelCallbackTable Injection sobrescrevem diretamente uma ou mais dessas entradas para redirecionar a execução. Embora eficazes, modificar a própria tabela introduz anomalias estruturais que podem ser trivialmente detectadas por meio da validação de integridade do PEB ou do conteúdo da tabela de callbacks. A técnica apresentada evita isso preservando a estrutura da tabela e, em vez disso, aplicando detour no destino de callback referenciado pela entrada.
Entre as entradas disponíveis da KernelCallbackTable, __fnCOPYDATA fornece uma primitiva de gatilho particularmente conveniente porque pode ser invocada externamente ao entregar uma mensagem WM_COPYDATA por meio de SendMessage(). Isso permite que o callback seja acionado deterministicamente sem exigir estado incomum do processo ou interação complexa.
Ao aproveitar um destino de callback naturalmente acessível e frequentemente usado, a técnica obtém uma primitiva de execução confiável, permanecendo totalmente dentro da cadeia de despacho de callbacks esperada antes do redirecionamento.

A técnica começa localizando o processo alvo e lendo seu PEB para recuperar o endereço da KernelCallbackTable, a partir do qual o ponteiro de callback para __fnCOPYDATA é resolvido. Em seguida, memória executável é alocada dentro do processo remoto, e um shellcode controlado pelo atacante é gravado na região alocada. Antes da modificação, os bytes originais da rotina de callback legítima são preservados para permitir restauração posterior.
Um hook inline é subsequentemente instalado no início da rotina __fnCOPYDATA resolvida, substituindo seu prólogo por um salto absoluto para o shellcode injetado. Para acionar a execução, uma mensagem WM_COPYDATA é enviada para a janela alvo, fazendo com que o subsistema gráfico do Windows despache __fnCOPYDATA por meio da cadeia padrão de callback do kernel para o usuário. Quando a execução é concluída, os bytes originais do callback são restaurados para preservar a estabilidade do processo e reduzir artefatos residuais de modificação.
A KernelCallbackTable está localizada no deslocamento 0x58 dentro do PEB:

A seguinte lógica é usada para obtê-la:
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;
}
O código começa chamando NtQueryInformationProcess com a classe de informações ProcessBasicInformation para preencher uma estrutura PROCESS_BASIC_INFORMATION, que expõe o endereço do PEB do processo remoto por meio do campo PebBaseAddress.
Em seguida, NtReadVirtualMemory é usado para ler o PEB remoto em uma estrutura PEB local, permitindo a extração do ponteiro da KernelCallbackTable armazenado no ambiente do processo. Após validar que a tabela de callbacks está presente, uma segunda chamada a NtReadVirtualMemory copia a estrutura remota KERNELCALLBACKTABLE para a memória local, permitindo a resolução direta de destinos de callback, como __fnCOPYDATA, para o detour subsequente.
