
Abusare del meccanismo di callback del kernel win32k.sys per l'esecuzione di codice arbitrario
Questo repository è fornito esclusivamente per scopi educativi e di ricerca difensiva.
Dimostra i meccanismi interni di dispatch dei callback di Windows e i concetti di controllo del flusso relativi alla KernelCallbackTable in un contesto proof-of-concept.
L'autore non si assume alcuna responsabilità per un uso improprio.
Questa tecnica di injection abusa del percorso di dispatch dei callback da kernel a utente utilizzato dal sottosistema grafico di Windows (win32k.sys) per ottenere l'esecuzione di codice all'interno di un processo remoto. Individuando la KernelCallbackTableattraverso ilProcess Environment Block (PEB)`, un operatore può enumerare le voci di callback e identificare le routine legittime in modalità utente invocate durante le transizioni di kernel correlate alla GUI.
Invece di eseguire la tradizionale KernelCallbackTable Injection, in cui una voce di callback viene sovrascritta direttamente con l'indirizzo dello shellcode, questa variante aggancia la destinazione di callback legittima referenziata dalla tabella e reindirizza l'esecuzione a uno shellcode controllato dall'attaccante al momento dell'invocazione. Poiché l'esecuzione viene dirottata attraverso un percorso di callback esistente e atteso, la tecnica può rappresentare un'alternativa più stealth a primitive più convenzionali come la creazione di thread remoti o l'iniezione basata su APC.
Il sottosistema grafico di Windows delega parti dell'elaborazione correlata alla GUI alla modalità utente tramite un meccanismo di callback avviato dalla modalità kernel. Quando win32k.sys richiede l'esecuzione di logica nel contesto di un processo GUI, invoca KeUserModeCallback per eseguire una transizione controllata dalla modalità kernel alla modalità utente, preservando il confine di isolamento tra i due contesti di esecuzione.
Questa transizione stabilisce il percorso di esecuzione legittimo attraverso il quale il kernel distribuisce i callback del sottosistema grafico in modalità utente—lo stesso percorso successivamente abusato dalla tecnica presentata.
Una volta completata la transizione, l'esecuzione entra in KiUserCallbackDispatcher, una routine di ntdll.dll responsabile di ricevere l'indice di callback fornito dal kernel e di distribuire l'esecuzione al corrispondente gestore di callback in modalità utente. Questa routine funge da punto di ingresso obbligatorio per tutti i callback avviati tramite KeUserModeCallback.
Poiché tutta la risoluzione dei callback converge su questo dispatcher, KiUserCallbackDispatcher funge da punto di snodo centrale tra le richieste di callback del kernel e la loro successiva esecuzione in modalità utente.
Per risolvere la destinazione di un callback richiesto, KiUserCallbackDispatcher consulta la KernelCallbackTable memorizzata nel PEB del processo di destinazione. Ogni voce della tabella contiene un puntatore a una routine di callback in modalità utente associata a una specifica operazione del sottosistema grafico, tipicamente implementata in user32.dll.
Le tecniche tradizionali di KernelCallbackTable Injection sovrascrivono direttamente una o più di queste voci per reindirizzare l'esecuzione. Sebbene efficaci, la modifica della tabella stessa introduce anomalie strutturali che possono essere banalmente rilevate tramite la convalida dell'integrità del PEB o dei contenuti della tabella di callback. La tecnica presentata evita questo problema preservando la struttura della tabella e, invece, applicando un detour alla destinazione di callback referenziata dalla voce.
Tra le voci disponibili della KernelCallbackTable, __fnCOPYDATA fornisce una primitiva di attivazione particolarmente comoda perché può essere invocata esternamente consegnando un messaggio WM_COPYDATA tramite SendMessage(). Ciò consente di attivare il callback in modo deterministico senza richiedere uno stato di processo insolito o interazioni complesse.
Sfruttando una destinazione di callback naturalmente accessibile e di uso frequente, la tecnica ottiene una primitiva di esecuzione affidabile rimanendo completamente all'interno della catena di dispatch dei callback prevista prima del reindirizzamento.

La tecnica inizia individuando il processo di destinazione e leggendo il suo PEB per recuperare l'indirizzo della KernelCallbackTable, da cui viene risolto il puntatore di callback per __fnCOPYDATA. Successivamente viene allocata memoria eseguibile all'interno del processo remoto e lo shellcode controllato dall'attaccante viene scritto nella regione allocata. Prima della modifica, i byte originali della routine di callback legittima vengono preservati per consentire un successivo ripristino.
Un hook inline viene successivamente installato all'inizio della routine __fnCOPYDATA risolta, sostituendo il suo prologo con un salto assoluto allo shellcode iniettato. Per attivare l'esecuzione, un messaggio WM_COPYDATA viene inviato alla finestra di destinazione, facendo sì che il sottosistema grafico di Windows distribuisca __fnCOPYDATA attraverso la catena standard di callback da kernel a utente. Una volta completata l'esecuzione, i byte originali del callback vengono ripristinati per preservare la stabilità del processo e ridurre gli artefatti di modifica residui.
La KernelCallbackTable si trova all'offset 0x58 all'interno del PEB:

La seguente logica viene utilizzata per ottenerla:
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;
}
Il codice inizia chiamando NtQueryInformationProcess con la classe di informazioni ProcessBasicInformation per popolare una struttura PROCESS_BASIC_INFORMATION, che espone l'indirizzo del PEB del processo remoto tramite il campo PebBaseAddress.