Copy Link
Add to Bookmark
Report
BFi numero 11 anno 6 file 15 di 15
==============================================================================
------------[ BFi numero 11, anno 6 - 23/12/2003 - file 15 di 15 ]------------
==============================================================================
-[ MiSCELLANE0US ]------------------------------------------------------------
---[ DRiVERS E SEH iN WiN NT/2000
---[ dev-04, 17/01/2002
-----[ valv`0 <valvoline@s0ftpj.org>
---[ un okkio ai drivers ed il SEH in Win NT/2000 ]---
---[ valv`0 <valvoline@s0ftpj.org> ]---
.Introduzione.
<---------------------------------------------------------------------------->
I drivers di windows NT, che manipolano direttamente indirizzi "modo-utente"
oppure che chiamano funzioni che possono causare un exception, devono poter
gestire le eccezioni possibili. Win NT, insieme a Visual C/C++, fornisce un
meccanismo conosciuto come SEH o Structured Exception Handling per farlo.
In quest'articolo, provero` a dare le basi e ad analizzare quello che si deve
e NON DEVE! fare nella costruzione di un buon driver per Windows NT/2000.
Molti di voi staranno pensando: "che palle, il solito articolo prettamente
tecnico, dove non si capira` niente." Beh, forse sara` pure vero, ma vi
ricordo che con l'avvento di win2000/XP oramai tutto il grosso viene
svolto a livello Kernel/Driver; quindi una buona conoscenza del sistema e`
alla base di una buona analisi e gestione coscenziosa di una macchina Win di
livello avanzato. Quindi rimbocchiamoci le mani e cominciamo subito.
.I Drivers e le Exceptions.
<---------------------------------------------------------------------------->
I drivers che non gestiscono le exceptions ( o che le gestiscono male)
causano un arresto del sistema operativo, con il familiare messaggio di
errore: KMODE_EXCEPTION_NOT_HANDLED. Normalmente, questo e' indicativo di un
driver che sta` accedendo un indirizzo utente invalido. Ovviamente, se il
driver utilizza un sistema Structured Exception Handling (da qui in poi: SEH),
l'exception dovrebbe essere gestita ed il sistema non generera` il messaggio
di errore.
Visual C utilizza le keywords __try ed __except per implementare SEH. Qquando
si costruiscono drivers per win NT, l'ambiente di sviluppo fornisce le
definizioni per "try" ed "except". Queste keywords abbreviate possono essere
usate in programmi C,ma non possono essere usate in programmi C++: dal momento
che il meccanismo di gestione delle eccezioni di C++ non e` compatibile con
il gestore di eccezioni del sistema operativo, non e' possibile usare le
keyword "try" ed "except" con drivers modo kernel.
E` da notare che il modello SEH che descriviamo qui, non deve essere confuso
con il modello di terminazione dei processi. Sfortunatamente, e` facile
confoderli. Il gestore di terminazione usa la keyword "__try" per indicare
l'inizio di un blocco di terminazione e la keyword "__finally" per indicare
la terminazione del blocco. Il gestore di terminazione e` utile quando si
prova ad assicurare che le risorse siano rilasciate correttamente, eseguendo
il codice nel blocco di terminazione. Ma poiche` non c'entra un'emerita cippa
con quello di cui parliamo in questo articolo, rimandiamo ad altro luogo ed
altra data questa simpatica chiaccherata. Qui sotto c'e` un codice di
esempio che usa un gestore di exception. Nell'esempio analizziamo un
buffer modo-utente per assicurarci che sia valido:
<-| drvseh/seh_ex0.c |->
/*
leggiamo dati da un indirizzo modo-utente. Dobbiamo, inoltre assicurarci
che sono dati validi.
*/
BOOLEAN OsrProbeForRead(Buffer, Length)
{
ULONG idx;
UCHAR dummyArg;
PUCHAR effectiveAddress;
/* controlliamo il buffer di input leggendo un byte da ogni pagina
nel range specificato.
*/
__try {
for (idx = ADDRESS_AND_SIZE_TO_SPAN_PAGES(Buffer, Length);idx;idx--) {
effectiveAddress = (PUCHAR) Buffer;
effectiveAddress += ((idx-1) * PAGE_SIZE);
dummyArg = *effectiveAddress;
}
} __except (EXCEPTION_EXECUTE_HANDLER) {
DbgPrint("Exception is 0x%x\n", GetExceptionCode());
return FALSE;
}
/*
se siamo arrivati sin qui, il buffer di input è valido!
*/
return TRUE;
}
<-X->
Il codice e` molto semplice: consiste di un blocco protetto (il codice tra
__try ed __except) e di un gestore di exception (il codice che segue
__except). Guardiamolo piu` in dettaglio:
+ l'espressione dopo la clausola __except indica le azioni che devono essere
prese quando si verifica un exception. Il blocco di codice che segue __except
e` eseguito solo se l'espressione valutata ha un valore non zero.
+ ci sono due funzioni, che possono essere usate solo dal blocco di __except:
- GetExceptionInformation (invoca la funzione __exception_info)
- GetExceptionCode (invoca la funzione __exception_code)
Queste due funzioni forniscono informazioni addizionali riguardo
l'exception appena verificatasi.
+ Non tutte le exceptions possono essere catturate. In alcuni casi il sistema
operativo, continuera` a killarsi con un KMODE_EXCEPTION_NOT_HANDLED.
+ La logica dell'exception non e` invocata se il blocco protetto non causa una
exception.
Come abbiamo mostrato in questo esempio, l'uso primario di SEH nei drivers e`
di proteggere l'accesso ai buffers modo-utente. Questo perche` il gestore di
page-fault insieme al sistema operativo genera un exception
(STATUS_ACCESS_VIOLATION) se si accede ad un indirizzo invalido. Ovviamente
esistono altre exceptions, diverse da quella appena vista. Per questo motivo,
WinNT usa gli "status code" come valori di riconoscimento per le exceptions.
La gestione delle eccezioni e` basata su uno stack di gestori di eccezioni
individuali. In questo modo, il codice generato dal compilatore per un __try,
costruisce un nuovo blocco per l'eccezione, che viene messo nello stack.
Se si verifica un exception, quindi, il sistema operativo inizia ad esaminare
il primo blocco. Questo implica valutare l'espressione che segue il blocco
__except ed agire di conseguenza; ci sono tre valori validi che possono
verificarsi / essere valutati:
+ EXCEPTION_EXECUTE_HANDLER (1): indica che il blocco di exception puo`
essere eseguito.
+ EXCEPTION_CONTINUE_SEARCH (0): indica che il gestore non puo` gestire questa
particolare exception. Il blocco per l'eccezione e` rimosso dallo stack e
viene esaminato il prossimo. Se il sistema operativo rimane senza blocchi
da esaminare, si killa con un KMODE_EXCEPTION_NOT_HANDLED.
+ EXCEPTION_CONTINUE_EXECUTION (-1): indica che l'exception dovrebbe essere
ignorata e l'esecuzione del codice deve continuare dal punto in cui si e`
verificata l'eccezione.
Tipicamente si usano EXCEPTION_EXECUTE_HANDLER o EXCEPTION_CONTINUE_SEARCH.
Teoricamente, se un driver e` stato in grado di risolvere la causa
dell'exception, potrebbe essere possibile usare EXCEPTION_CONTINUE_EXECUTION,
ma questo in pratica si usa raramente (per non dire mai). Tornando al nostro
codice, notiamo che il gestore dell' exception e` sempre eseguito se
l'exception occorre dentro questo blocco. Spesso, si preferisce usare un
filtro che ci permetta di ottenere piu` informazioni riguardo le exceptions.
di seguito c'e` una routine che chiameremo da __except per salvare le
informazioni sull'exception e generare, quindi, un bug-check:
<-| drvseh/seh_ex1.c |->
ULONG BackgroundExceptionFilter( ULONG Code, PEXCEPTION_POINTERS pointers)
{
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT Context;
ExceptionRecord = pointers->ExceptionRecord;
Context = pointers->ContextRecord;
DbgBreakPoint();
return EXCEPTION_EXECUTE_HANDLER;
}
<-X->
vediamo come usarla, dentro un semplice blocco __try / __except:
...
...
<-| drvseh/seh_ex2.c |->
runAgain = FALSE;
__try {
runAgain=(*backgroundTask->BackgroundTaskProc)(backgroundTask->Context);
} __except ( BackgroundExceptionFilter(GetExceptionCode(),
GetExceptionInformation()) ) {
DbgPrint(("\nunexpected exception when calling background routine\n"));
}
<-X->
In realta`, uno degli obiettivi nell'usare gestori di exception strutturati e`
che la vera azione del processare l'exception causa la perdita delle
informazioni nello stack. Quindi, quando esaminiamo il problema da dentro un
debugger, frequentemente troviamo che lo stack e` stato svuotato, e le
informazioni riguardo cosa il sistema stesse facendo al momento dell'exception
sono andate perdute. Un filtro di exceptions, allora, ci permette di esaminare
i dati e ripristinare il tutto in maniera efficente. In generale, SEH e` molto
utile quando si chiamano funzioni che potrebbero degenerare in una exception.
Vediamone un esempio, con la funzione MmProbeAndLockPages. Essa di solito
ritorna STATUS_ACCESS_VIOLATION (indicando che l'indirizzo dell'utente non e`
valido), ma puo` anche generare un STATUS_QUOTA_EXCEEDED (indicando che la
memoria non puo` essere bloccata a causa di restrizioni di risorse). Vediamo
allora come applicare la nostra funzione di filtro a quanto detto. Proteggiamo
ogni chiamata fuori dal nostro file system con un blocco __try/__except. Se si
verifica una exception, la routine e` chiamata nel filtro.
<-| drvseh/seh_es3.c |->
static BOOLEAN FsdSupExceptionFilter(PEXCEPTION_POINTERS ExceptionInformation,
ULONG ExceptionCode)
{
DbgPrint("********************************************************\n");
DbgPrint("*** FSDK DEBUGGING: E` stata catturata un exception ***\n");
DbgPrint("*** nella routine FSD; L'FSDK avviera` un breakpoint.***\n");
DbgPrint("*** Se e` stato settato un debugger, partira` ***\n");
DbgPrint("*** immediatamente. ***\n");
DbgPrint("*** ***\n");
DbgPrint("*** windbg commands: ***\n");
DbgPrint("\n\n !exr %x ; !cxr %x ; !kb\n\n",
ExceptionInformation->ExceptionRecord,
ExceptionInformation->ContextRecord);
DbgPrint("*** ***\n");
DbgPrint("*** ***\n");
DbgPrint("********************************************************\n");
/*
attakkiamo un breakpoint qui nel caso ci sia un debugger attivo.
Usiamo un blocco try/except, per assicurarci che se non c'e` nessun
debugger attivo non perderemo informazioni rilevanti sull'errore
generato.
*/
__try {
DbgBreakPoint();
} __except (EXCEPTION_EXECUTE_HANDLER) {
/*
L'obiettivo e` quello di non crashare la macchina
se nessun debugger risulta attivo.
*/
}
DbgPrint("********************************************************\n");
DbgPrint("*** FSDK DEBUGGING: e` stata chiamata KeBugCheckEx. ***\n");
DbgPrint("*** Il sistema, si arrestera` immediatamente. ***\n");
DbgPrint("********************************************************\n");
return EXCEPTION_EXECUTE_HANDLER;
}
<-X->
Nel codice sopra, viene usata un'altra tecnica molto utile, chiamata
"DbgBreakPoint". Se un debugger di kernel e` attivo, catturera` l'exception
(STATUS_BREAKPOINT - 0x80000003) ed il nostro gestore non sara` coinvolto.
Ovviamente, se non c'e` un debugger di kernel, verra` invocato il nostro
gestore che procedera` ad ignorare l'exception e a continuare come se il
breakpoint non fosse avvenuto. Viene usata questa tecnica perche` assicura
che anche se viene lasciato un breakpoint in un driver, esso non causera` un
crash del sistema; ovviamente ne rallentera` l'esecuzione notevolmente (motivo
per cui e` buona regola rimuovere i breakpoints da zone di codice usate
frequentemente).
Usare SEH e` un requisito fondamentale per ogni driver che manipola buffers a
livello utente; anche se non difficoltoso, e` essenziale per assicurare che il
sistema non vada in crash se il buffer utente risulta invalido.
.I BlueScreens - Questi Sconosciuti.
<---------------------------------------------------------------------------->
Ma vediamo, adesso, piu` da vicino i fatidici nemici che nella precedente
discussione abbiamo cercato a tutti i costi di evitare. L'obiettivo e` quello
di riuscire a capire sino in fondo perche` sono pericolosi e perche`
andrebbero evitati. Facciamo un salto indietro e proviamo ad immaginare una
situazione tipica:
Hai appena aggiunto alcune funzionalita` al tuo favoloso programma di
cifratura on-the-fly del tuo disco fisso (scusate la deviazione mentale, ma e`
quello a cui sto lavorando N.P). Resetti la macchina. Il tuo driver viene
caricato ed inizi a testarlo e... whaablaaaaaaamaaaaaam! Il tuo monitor
scricchiola come se fosse stato divelto in due (vero, qu3st?), compare uno
sfondo blue in modo testo 80x50 - a segnalare l'inizio di altre tre ore di
ricerca di un motivo e di un colpevole tra le radici e le migliaia di linee di
codice del nostro programma (possibilmente ASM.. ehehe). Niente di nuovo vero?
Tutti noi oramai abbiamo un rapporto di amore/odio nei confronti dei BSOD (in
gergo: Blue Screen Of Death). I messaggi di errore "cryptici" ed i dump HEX
sono, oramai, parte della nostra giornata tipo. Persino i piu` esperti del
settore (ciao, m0libdeno!) raramente capiscono cosa sta` dietro quegli strani
messaggi che il kernel ci restituisce a seguito di un errore sconosciuto.
.Da dove vengono i BlueScreens?.
<---------------------------------------------------------------------------->
I BluScreens sono un modo di NT per dire che qualcosa e` andato terribilmente
male e che il sistema e` stato fermato a causa di NT stesso (completamente
divelto); continuare potrebbe portare alla perdita di dati o alla loro
corruzione totale. Lo schermo e` mostrato con una chiamata ad una delle due
funzioni KeBugCheck(...) o KeBugCheckEx(...). Entrambe sono esportate per
l'utilizzo in drivers di dispositivi e/o filesystems:
<-| drvseh/seh_ex4.c |->
VOID KeBugCheck(
IN ULONG BugCheckCode
);
VOID KeBugCheckEx(
IN ULONG BugCheckCode,
IN ULONG BugCheckParameter1,
IN ULONG BugCheckParameter2,
IN ULONG BugCheckParameter3,
IN ULONG BugCheckParameter4
);
<-X->
Entrambe le chiamate prendono un parametro (BugCheckCode). Questo parametro e`
conosciuto anche come: "codice di STOP" e generalmente categorizza le ragioni
del blocco del sistema. KeBugCheckEx(...) prende quattro parametri addizionali
che sono semplicementi stampati sul BluScreen, insieme con il codice di stop.
Questi parametri hanno un significato predefinito per alcuni codici di stop
standard (alcuni dei quali, verranno descritti dopo), ma e` anche possibile
usare dei propri codici personali ridefinendo la tabella standard.
KeBugCheck(...) non fa` niente di piu` che chiamare KeBugCheckEx(...) con
i quattro parametri settati a 0. La prima cosa che fa` KeBugCheckEx(...) e`
disabilitare tutti gli interrupts chiamando KiDisableInterrupts(...). Quindi
porta la macchina nel modo BlueScreen e dumpa il messaggio di stop.
Le operazioni sono realizzate con una chiamata a HalDisplayString(...).
Essa prende un parametro, che e` una stringa da stampare sul BOFD, controlla
inoltre per vedere se il sistema e` gia nel modo BlueScreen e se non
lo e` viene switchato in tale stato. Quindi dumpa la stringa in memoria-video
/modo-testo alla posizione corrente del cursore. HalDisplayString(...) puo`
essere usato per lanciare e produrre dei BlueScreen personalizzati, oppure per
stampare messaggi di informazioni per il BlueScreen mostrato. Sfortunatamente,
se si chiama HalDisplayString(...) dopo che il sistema ha passato il BOFD
iniziale, non c'e` modo per ripristinare lo schermo al suo modo precedente!
KeBugCheckEx(...), successivamente, chiama KeGetBugMessageText(...), una
funzione che converte un codice di stop nel suo equivalente in testo usando
una tabella interna di nomi di codici di stop. E` possibile vedere il set
completo dei codici predefiniti del sistema ed i loro testi associati nel file
bugcodes.h nella DDK microsoft.
A questo punto KeBugCheckEx(...) chiama un qualunque gestore di Bugs
che i drivers hanno registrato ( un gestore e` registrato chiamando
KeRegisterBugCheckCallback(...)). Il suo scopo e` quello di riempire un buffer
(allocato dal chiamante della routine di registro) con lo stato del
device che sara` esaminato da dentro WinDbg.
Le chiamate ai BugCheck sono anche utili quando il dispositivo che il nostro
driver sta` controllando deve essere spento quando si verifica un crash
di sistema: verranno ritornati, infatti, i puntatori necessari per
disabilitare il DEV ( Molti nuovi sistemi di protezione hardware utilizzano
un sistema di questo tipo, che disabilita i drivers necessari all'utilizzo del
software se non viene riscontrata la presenza di una key nel sistema .NDA)
Il sistema, quindi, chiama KeDumpMachineState(...) che dumpa il resto del
testo sullo schermo. KeDumpMachineState(...) prova ad interpretare ognuno dei
quattro parametri che erano stati passati a KeBugCheckEx(...) come indirizzo
valido internamente ad un modulo caricato e si ferma quando puo` risolverne
uno; viene usata la funzione interna KiPcToFileHeader(...) per svolgere tutto.
L'informazione ritornata da KiPcToFileHeader(...) riguarda il primo parametro
che e` stato risolto con successo; viene stampato immediatamente seguendo il
form di testo del codice di stop ed include l'indirizzo base del modulo ed
il nome del modulo. In questo modo, un parametro di indirizzo puo` essere uno
qualunque dei 4 parametri KeBugCheckEx(...).
Il resto dello schermo e` diviso in tre aree. La prima e` l'area CPUID, sotto
c'e` l'area dei driver caricati ed infine c'e` un trace dello stack.
L'area CPUID include il CPUID, i settaggi dell'IRQL al tempo di scrittura del
bluescreen (su macchine x86 questo sara` sempre 0x1F - SYNCH_LEVEL - poiche`
HAL disabilita tutti gli interrupts quando switcha il modo video) ed il numero
della build. Il numero della build (accessibile via 'NtBuildNumber'), e` una
variabile a 32-bit esportata dal kernel; la parte alta e` C (checked build) o
F (free build), il resto e` il numero della build attuale (es: 2600 per XP).
Sotto l'area CPUID c'e` l'area dei drivers caricati. Ogni driver nel sistema
tiene traccia dei moduli caricati e del suo time stamp, mostrato nella parte
centrale del bluscreen. In realta` queste informazioni sono di scarsa utilita`
ma e` possibile usarle, ad esempio, per essere sicuri che la versione del
driver che sta girando sul sistema e` quella che pensavamo. Il numero stampato
esprime il numero di secondi dalle 4P.M. del 31/12/69, fino a quando il driver
e` stato compilato e viene estratto dall'header del PE (portable executable)
del driver. KeDumpMachineState(...) lo ottiene con una chiamata a
RtlImageNtHeader(...).
La regione sotto i drivers caricati fornisce alcuni dettagli su cosa accade.
In pratica e` un trace di stack che parte da KeBugCheckEx e procede in avanti.
KeDumpMachineState(...) stampa tanti frammenti quanti ne entrano nello schermo
a meno che non incontri un puntatore a stack invalido. Lo stack e` letto
usando la funzione KiReadStackValues(...); ogni frammento mostrato consiste
di:
+ indirizzo
+ indirizzo di ritorno
+ le prime 4 DWORDS nel frammento (che potrebbero essere parametri passati)
+ il nome del modulo a cui l'indirizzo di ritorno del frame sta puntando
Se il nostro driver sta nel trace, con buona probabilita` l'errore sara` nel
dump e guardando agli indirizzi di ritorno che puntano al driver sara`
possibile vedere dove esso e` chiamato da altre funzioni che conducono
all'errore.
Ovviamente, e` possibile che il driver abbia causato alcune alterazioni, da
qualche parte, che non sono state individuate e quindi non sono listate nello
stack (.NDA.)
KeBugCheckEx(...) quindi prova a connettere un debugger; tuttavia, non chiama
il debugger a questo punto. Scrive, invece, un crash-dump (se i crash-dumps
sono stati abilitati), quindi come ultima azione invoca qualunque debugger
attivo con un breakpoint.
.Interpretare i codici di arresto.
<---------------------------------------------------------------------------->
In molti casi la piu` importante informazione fornita da un bluescreen e` il
codice di stop e i 4 parametri stampati con essa. Questi parametri devono
essere interpretati con le informazioni sui codici di stop. Adesso proveremo a
fornire una mini-referenza, coprendo i piu` importanti e comuni codici di
arresto. Proveremo, inoltre, ad interpretare i parametri listati con essi.
+ IRQL_NOT_LESS_OR_EQUAL (0xA)
Questo e` il piu` conosciuto (ed odiato), dal momento che lo si incontra con la
maggior frequenza. Viene ritornato quando il kernel (oppure un driver)
determina che il corrente IRQL e` piu` grande del previsto. L'epicentro per
molti di questi errori e` in MmAccessFault(...), il gestore di errori della
Memoria.
MmAccessFault e` responsabile per la gestione degli errori di pagina e
tipicamente lo fa in silenzio. Quando IRQL e` in DISPATCH_LEVEL (o un livello
piu` alto), essa ritorna un 'STATUS_IN_PAGE_ERROR' al gestore degli errori
di pagine del sistema; quest'ultimo puo` quindi chiamare KeBugCheckEx(...) con
un 'IRQL_NOT_LESS_OR_EQUAL'. Un altro punto dove questi errori possono essere
generati e` nella funzione di gestione thread del kernel: ExpWorkerThread(..);
dopo essere ritornata da una routine di lavoro, controlla l'IRQL per
assicurarsi del suo PASSIVE_LEVEL (il livello in cui era prima che l'elemento
di lavoro fosse chiamato). Se non e` in PASSIVE_LEVEL, ritorna un
IRQL_NOT_LESS_OR_EQUAL.
I parametri per questo errore sono mostrati sotto:
--------
IRQL_NOT_LESS_OR_EQUAL (0xA) (dal thread attivo)
Param1 indirizzo della routine attiva chiamata
Param2 IRQL invalido
Param3 copia di Param1
Param4 puntatore alla struttura dati di lavoro
IRQL_NOT_LESS_OR_EQUAL (0xA) (da MmAccessFault)
Param1 indirizzo che e` stato referenziato
Param2 IRQL invalido
Param3 tipo di accesso (0 == lettura, 1 == scrittura)
Param4 indirizzo dove e` stata incontrata la referenza
--------
+ KMODE_EXCEPTION_NOT_HANDLED (0x1E)
Questo errrore e` generato da piu` posti nel kernel, incluso il gestore di
exception del sistema. Si verifica quando un exception scatta senza che il
sistema abbia modo di prevederla e/o gestirla. Un esempio di questo tipo
avviene quando MmAccessFault(...) ritorna un errore a causa di una referenza
di memoria invalida ad una pagina protetta. Ad esempio, un driver che scrive
su una pagina di sola-lettura generera` questo tipo di errore. I parametri
sono mostrati sotto:
--------
KMODE_EXCEPTION_NOT_HANDLED (0x1E)
Param1 il codice dell'exception (NTSTATUS.H per dettagli)
Param2 indirizzo del codice dove si e` verficata l'exception
Param3 primo parametro exception
Param4 secondo parametro exception
0x800000003 Breakpoint hit with no debugger active
0xC00000005 Access violation (in questo caso Param4 e` l'indirizzo che e`
stato referenziato).
--------
+ UNEXPECTED_KERNEL_MODE_TRAP (0x7F)
Questo codice e` molto simile a quello di KMODE_EXCEPTION_NOT_HANDLED, ma
questo e` il risultato di una trap di sistema per il quale non ci sono gestori
adatti.
Per esempio, se avviene un exception per il calcolo in virgola mobile, ed il
sistema non e` preparato a gestirlo (ad esempio, se avviene nel codice del
kernel), viene generata questa espressione. Per questo tipo di errore, il
primo parametro mostra il tipo di exception della CPU e gli altri parametri
sono praticamente inutili.
--------
UNEXPECTED_KERNEL_MODE_TRAP (0x7F)
Param1 il codice di exception della CPU
--------
+ PAGE_FAULT_IN_NON_PAGED_AREA (0x50)
Questo tipo si colloca con IRQL_NOT_LESS_OR_EQUAL in termini di frequenza con
cui si incontra. E` generato quando un componente del kernel accede ad un
indirizzo che e` fuori dalla memoria paginata (vi ricorda niente ? :-) ), ma
non c'è nessun valido mapping per la memoria. Ancora una volta,
MmAccessFault(...) e` la sorgente di tutto. Un driver puo` avviarlo sia
eseguendo un riferimento a dati, oppure saltando fuori (ad esempio, tornando
da una funzione che ha sfondato lo stack .NDA.)
---------
PAGE_FAULT_IN_NON_PAGED_AREA (0x50)
Param1 indirizzo referenziato
---------
.Analisi di un Sistema (nshare di miralink: http://www.miralink.com).
<---------------------------------------------------------------------------->
La chiave di ogni buona analisi e` ovviamente assicurarsi di usare i giusti
attrezzi per il lavoro da svolgere! Nell'analizzare il crash-dump di esempio,
useremo WinDB (build 2127.1) e i386kd.
Il crash di cui parliamo e` stato ottenuto da un sistema con Win NT 5.0 (SP2),
mentre girava l'ultima versione di nshare della miralink (www.miralink.com).
La piattaforma aveva lavorato perfettamente con tutte le versioni precedenti
del programma, ma in questa mostrava uno stranissimo:
PAGE_FAULT_IN_NONPAGED_AREA
mentre la macchina in questione aveva traffico di rete su porte firewallate
dal programma di cui sopra.
Ovviamente il primo pensiero e` andato a qualche bug dell'architettura di rete
(sempre possibile quando si usa un programma esterno che si appoggia sul
livello di rete per mapparlo/controllarlo/redirigerlo). Solo successivamente,
mi sono accorto che il vero responsabile era nshare.
Quando ho iniziato a controllarlo, ho notato che il sistema crashava sul so
(nessun driver era coinvolto nel crash). Doveva, allora, essere un errore del
programma: nessuna applicazione di norma deve essere in grado di crashare il
sistema operativo (ho detto "dovrebbe") :-)
Un codice di errore di 0x50 (PAGE_FAULT_IN_NONPAGED_AREA), come detto sopra,
e` uno dei piu` comuni che si puo` osservare su macchine con kernel NT. Come
dovremmo oramai sapere, esso si verifica come risultato di un errore di pagina
su di un indirizzo dentro lo spazio di indirizzamento di sistema (normalmente
tra 0x80000000 e 0xFFFFFFFF) che non supporta paginazione in quell'area. In
conclusione, qualcuno nel sistema ha provato ad accedere ad una locazione di
memoria invalida (puntatore ad una struttura dati non inizializzata o ad una
regione di memoria rilasciata). Senza grossi problemi il gestore della memoria
(Memory Manager) lo considera un errore critico e blocca il sistema. In questo
caso i quattro parametri ci dicono tutto riguardo il perche` il sistema e`
andato in crash. Il primo parametro indica l'indirizzo virtuale che e` stato
toccato ed il secono parametro indica se o no l'indirizzo e` stato letto
(zero) o scritto (uno). Il senso degli altri due parametri non e` molto utile
in NT. In questo caso, il codice di arresto e` stato:
STOP: PAGE_FAULT_IN_NONPAGED_AREA (af3defc4, 0, 0, 0)
cioe' un tentativo di accedere all'indirizzo af3defc4. L'indirizzo e` un
indirizzo "permesso", ma non e` uno di quelli che si vedono normalmente in
uso, probabilmente perche` la macchina in questione ha un 512Mb di memoria
fisica; questo a conferma del fatto che l'errore e` un problema di
programmazione.
Una volta determinata la sorgente del problema, iniziamo a guardare lo stack
che ha dichiarato l'halt. Su di un sistema multi-processore non e` molto
semplice, dal momento che l'arresto potrebbe non essere avvenuto sulla CPU 0,
anche se, ovviamente, il debugger iniziera` a controllare la CPU 0.
A questo punto, si inizia con il guardare nello stack ogni processore nel
tentativo di identificare quale processore ha chiamato KeBugCheckEx. In questo
caso, otteniamo le informazioni mostrate sotto:
-----------
0: kd> kv
cannot get version packet on a crash dumpcannot get version packet on a crash
dumpChildEBP RetAddr Args to Child
f766ce14 80003e47 80153f7c 00000000 00000000 ntkrnlmp!KeWaitForSingleObject+0x9a(FPO: [Non-Fpo]
f766ce34 8019ace2 7ffde000 77fa5560 00000000 halmps!ExAcquireFastMutex+0x2b (FPO: [0,2,0])
f766ce4c 8019aba1 00000001 b980ae08 b980ae58 ntkrnlmp!PspExitProcess+0x8c(FPO: [Non-Fpo]
f766ced0 8019a53c 00000000 f766cf04 0006fea4 ntkrnlmp!PspExitThread+0x447(FPO: [Non-Fpo]
f766cef4 80140da9 ffffffff 00000000 00000000 ntkrnlmp!NtTerminateProcess+0x13c(FPO: [Non-Fpo]
f766cef4 77f681ff ffffffff 00000000 00000000 ntkrnlmp!KiSystemService+0xc9 (FPO: [0,0] TrapFrame @ f766cf04)
f766cdf4 80153f70 b980aea4 00000000 00000000 0x77f681ff [Stdcall: 257]
0006ff5c 00000000 00000000 00000000 00000000 ntkrnlmp!PspActiveProcessMutex(FPO: [Non-Fpo]
0: kd> ~1
1: kd> kv
dumpChildEBP RetAddr Args to Child
f7b9ab24 80143e8f 00000000 af3defc4 00000000 ntkrnlmp!MmAccessFault+0x29a(FPO: [Non-Fpo]
f7b9ab24 8015c925 00000000 af3defc4 00000000 ntkrnlmp!KiTrap0E+0xc7 (FPO: [0,0] TrapFrame @ f7b9ab3c) <---- !!!!!
f7b9abb8 8015481a f7abeca0 b980ae08 00010000 ntkrnlmp!ExpCopyProcessInfo+0x11 (FPO: [2,0,3])
f7b9ac38 8015b811 00b40000 00010000 f7b9aec8 ntkrnlmp!ExpGetProcessInformation+0x156(FPO: [Non-Fpo]
f7b9aeec 80140da9 00000005 00b40000 00010000 ntkrnlmp!NtQuerySystemInformation+0x725(FPO: [Non-Fpo]
f7b9aeec 77f67e27 00000005 00b40000 00010000 ntkrnlmp!KiSystemService+0xc9 (FPO: [0,0] TrapFrame @ f7b9af04)
f7b9abac b2ec4ff0 f7abeca0 b2ec4e58 8015481a 0x77f67e27 [Stdcall: 257]
00b3fabc 00000000 00000000 00000000 00000000 0xffffffff`b2ec4ff0 [Stdcall: 257]
-----------
Da quanto appena visto, non possiamo capire quale CPU ha causato l'arresto.
A questo punto, chiediamo aiuto al OEM Support Tools KD extension. Troviamo
che lo stack per la CPU-1 ha chiamato KeBugCheckEx:
> !b.stack
T. Address RetAddr Called Procedure
*1 F7B9AAD0 8012E67A _KeBugCheckEx@20(00000050, AF3DEFC4, 00000000,...);
*0 F7B9AAFC 80118AE8 @KiFlushSingleTb@8(F7B9AB38, 801450C1, 80118AE8,...);
*0 F7B9AB04 801450C1 @FxsrSwapContextNotify@8(80118AE8, 80118AE8, 8011BB44,...);
*0 F7B9AB08 80118AE8 @KiFlushSingleTb@8(80118AE8, 8011BB44, 00000000,...);
*0 F7B9AB0C 80118AE8 @KiFlushSingleTb@8(8011BB44, 00000000, BC442E08,...);
*0 F7B9AB10 8011BB44 dword ptr EAX(00000000, BC442E08, FFFFF000,...);
*1 F7B9AB28 80143E8F _MmAccessFault@16(00000000, AF3DEFC4, 00000000,...);
*1 F7B9AB40 800031DA _KiIpiServiceRoutine@8(F7B9AB54, 800031E0, 0001001C,...);
*0 F7B9AB48 800031E0 _HalEndSystemInterrupt@8(0001001C, 000000E1, 00000010,...);
*0 F7B9AB64 80120DEB _MmMapLockedPagesSpecifyCache@24(00006C8E, 00000000, AC900023,...);
*1 F7B9ABBC 8015481A _ExpCopyProcessInfo@8(F7ABECA0, B980AE08, 00010000,...); <--------------
*1 F7B9AC3C 8015B811 _ExpGetProcessInformation@12(00B40000, 00010000, F7B9AEC8,...);
*0 F7B9AC58 F7C363A8 _NbtDereferenceDevice@4(B70D2E78, 80E6964C, 80E69528,...);
*1 F7B9AC74 801128AF dword ptr [ECX+EAX*4+38](B70D2E78, 80E69528, 0000004A,...);
*1 F7B9AC88 F7B49BBB @IofCallDriver@8(F7B9000E, 80E01279, 80E69400,...);
*1 F7B9ACAC 8012DF3E @KfReleaseSpinLock@8(F7B9ACDC, ABC8C008, C02AF230,...);
*1 F7B9ACC0 8012D140 @MiChargeCommitmentCantExpand@8(BCA7EFBC, 80150F30, 00000100,...);
*1 F7B9ACE0 8010A8BC _MmAllocateSpecialPool@12(00000100, 7366704E, 00000000,...);
*1 F7B9AD10 801134E1 @KfReleaseSpinLock@8(EBC40937, 00000000, EBC40938,...);
*1 F7B9AD14 EBC40937 _IoReleaseCancelSpinLock@4(00000000, EBC40938, A5332F00,...);
*1 F7B9AD5C EBC4624B _NpAddDataQueueEntry@24(801096D9, F7B9ADC0, A5332F00,...);
*0 F7B9AD60 801096D9 @KfReleaseSpinLock@8(F7B9ADC0, A5332F00, F7B9ADE8,...);
*0 F7B9ADA8 8012DDE8 @KfReleaseSpinLock@8(00000000, A8E9EFFC, C4000010,...);
*0 F7B9ADD0 8012DC15 @KfReleaseSpinLock@8(F7B9AE34, F7B9AE34, 00000000,...);
*1 F7B9ADE8 80131164 @MiInsertNode@8(00B4FFFF, 00B40000, C4000010,...);
*1 F7B9AE38 80181E56 _MiInsertVad@4(80181E9B, F7B9AF04, 00B3FA3C,...);
*0 F7B9AE3C 80181E9B @ExReleaseFastMutex@4(F7B9AF04, 00B3FA3C, 801813DE,...);
*0 F7B9AE84 80139804 @KfReleaseSpinLock@8(00000004, BC8EEFD4, 00010000,...);
*1 F7B9AEF0 80140DA9 dword ptr EBX(00000005, 00B40000, 00010000,...);
Nota che possiamo osservare la chiamata KeBugCheckEx: se e` presente sullo
stack, anche se in un frammento di stack "ghost", deve essere stata chiamata.
A questo punto, abbiamo l'errore di pagina che ha causato l'operazione di
terminazione del sistema. KiTrap0E sullo stack, che e` l'errore di pagina del
gestore del kernel, poiche` trap14 (0x0E) e` l'errore di pagina sulla CPU.
Esso e` avvenuto nella funzione ExpCopyProcessInfo. Questa funzione era stata
invocata da ExpGetProcessInformation. Sfortunatamente, non abbiamo nessuna
sorgente di informazione riguardo le due funzioni. Alcune fonti non ufficiali
ci dicono che: le funzioni provano a copiare un gruppo di dati da una
struttura EPROCESS in un buffer temporaneo (.NDA.). Cosi` abbiamo controllato
gli argomenti per determinare se uno era in effetti una struttura EPROCESS.
Ed effettivamente era cosi`: il secondo parametro e` una struttura EPROCESS:
1: kd> !process b980ae08
!process b980ae08 <----- secondo parametro
PROCESS b980ae08 Cid: 0120 Peb: 7ffdf000 ParentCid: 007c
DirBase: 08c6f000 ObjectTable: 00000000 TableSize: 0.
Image: cgiapp.exe
VadRoot a856cfc8 Clone 0 Private 30. Modified 0. Locked 0.
B980AFC4 MutantState Signalled OwningThread 0
Process Lock Owned by Thread bf6b6dc0
Token b0834eb0
ElapsedTime 0:00:00.0500
UserTime 0:00:00.0015
KernelTime 0:00:00.0015
QuotaPoolUsage[PagedPool] 3713
QuotaPoolUsage[NonPagedPool] 832
Working Set Sizes (now,min,max) (145, 50, 345) (580KB, 200KB, 1380KB)
PeakWorkingSetSize 167
VirtualSize 4 Mb
PeakVirtualSize 9 Mb
PageFaultCount 164
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 36
THREAD bf6b6dc0 Cid 120.78 Teb: 00000000 Win32Thread: 00000000 RUNNING
Not impersonating
Owning Process b980ae08
WaitTime (seconds) 338550
Context Switch Count 53
UserTime 0:00:00.0000
KernelTime 0:00:00.0015
Start Address 0x77f0528c
Win32 Start Address 0x01001150
Stack Init f766d000 Current f766cc80 Base f766d000 Limit f766a000 Call 0
Priority 16 BasePriority 8 PriorityDecrement 0 DecrementCount 0
ChildEBP RetAddr Args to Child
f766ce14 80003e47 80153f7c 00000000 00000000 ntkrnlmp!KeWaitForSingleObject+0x9a
f766ce34 8019ace2 7ffde000 77fa5560 00000000 halmps!ExAcquireFastMutex+0x2b
f766ce4c 8019aba1 00000001 b980ae08 b980ae58 ntkrnlmp!PspExitProcess+0x8c
f766ced0 8019a53c 00000000 f766cf04 0006fea4 ntkrnlmp!PspExitThread+0x447
f766cef4 80140da9 ffffffff 00000000 00000000 ntkrnlmp!NtTerminaeProcess+0x13c
f766cef4 77f681ff ffffffff 00000000 00000000 ntkrnlmp!KiSystemService+0xc9
f766cdf4 80153f70 b980aea4 00000000 00000000 +0x77f681ff
0006ff5c 00000000 00000000 00000000 00000000 ntkrnlmp!PspActiveProcessMutex
Il trace dello stack sopra e` molto interessante: il processo tracciato e` in
uscita. Da questo, iniziamo a sospettare che stiamo osservando un interessante
bug: un processo sta raccogliendo informazioni riguardo un secondo processo,
ed il secondo processo sta terminando. I due threads sono avviati
simultaneamente, uno sulla CPU0 ed uno sulla CPU1.
Il thread sulla CPU0 ( il thread che sta terminando) sta entrando in una
condizione di wait. Esso non e` stato ancora segato (quindi sta ancora
girando), ma ha incontrato una mutex considerata leggittima e sta aspettando
questa mutex (lo determiniamo dalla chiamata a ExAcquireFastMutex).
Questo non mostra un bug, ma certamente accresce i nostri sospetti. Decidiamo
che e` tempo di porre la nostra attenzione sul thread che va in errore (gira
sulla CPU 1).
Facciamo un po' di backtracing sul codice; per farlo, usiamo le informazioni
sul frame nello stack. In questo caso, esso e` stato semplice da trovare,
perche` il debugger ha trovato e riportato la locazione del trap (notate le
frecce: <---- che ho disseminato nei dumps). Se non lo avesse fatto, avremmo
cercato manualmente sullo stack. Sulle piattaforme IA32 che girano su WinNT, i
valori dei registri DS ed ES contengono il valore 0x23 e quindi possiamo
identificare la locazione della trap cercando questi valori (il registro DS,
e` registrato 0x34bytes dall'inizio della trap). Questa tecnica e` spiegata in
dettaglio in un articolo della microsoft (Q159672) (.NDA.)
La trap ci dice cosa contenevano i registri al tempo dell'errore. Da questa
informazione possiamo lavorare all'indietro per provare a tracciare com'era
il codice al momento del crash. In questo caso la funzione che abbiamo bisogno
di analizzare e` stata appena chiamata e questo ci rende facile sapere cosa
cercare; quindi usando il debugger generiamo una porzione di codice assembler
per questa funzione:
> !trap f7b9ab3c
eax=af3defb0 ebx=b2ec4e58 ecx=00005d28 edx=00000481 esi=f7abeca0 edi=b980ae08
eip=8015c925 esp=f7b9abb0 ebp=f7b9ac38 iopl=0 nv up ei ng nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010282
ErrCode = 00000000
8015C925 8B4014 mov eax,dword ptr [eax+14h]
La trap e` avvenuta all'indirizzo 0xf7b9ab3c, mentre provava ad accedere
all'indirizzo 0xaf3defb0 (un valore nel registro EAX di questo esempio).
Quindi l'istruzione:
8015C925 8B4014 mov eax,dword ptr [eax+14h]
sta provando a ricevere alcuni valori dalla memoria. Lavorando di reversing
da questo, proviamo a determinare dove arriva il codice con questo particolare
valore. Sotto, mostriamo il reversing dall'inizio della funzione corrente
(ExpCopyProcessInfo).
> u 8015c914 <---------------------
NT!_ExpCopyProcessInfo@8+0x0:
8015C914 53 push ebx
8015C915 56 push esi
8015C916 57 push edi
8015C917 8B7C2414 mov edi,dword ptr [esp+14h]
8015C91B 8B8704010000 mov eax,dword ptr [edi+104h]
8015C921 85C0 test eax,eax
8015C923 740C je _ExpCopyProcessInfo@8+1Dh
8015C925 8B4014 mov eax,dword ptr [eax+14h]
Con un crash di questo tipo, di solito si lavora all'indietro seguendo le
tracce delle informazioni in nostro possesso per vedere se possiamo
determinare dove si e` verificato il problema.
In questo caso, il contenuto del registro EAX arriva usando l'indirizzo
0x104bytes dall'indirizzo contenuto nel registro EDI. Questo suona tanto come
una de-referenza di alcuni campi interni ad una struttura dati. L'indirizzo
della struttura dati e` stato estratto dallo stack (il puntatore dello stack
e` ESP), 0x14bytes dal punto corrente dello stack-pointer.
Dal momento che le tre istruzioni precedenti avevano pushato tre valori nello
stack e la funzione di ritorno e` anch'essa salvata qui dentro, notiamo che
questo sembra stia referenziando il secondo parametro (con il parametro 1 a
0x10 dallo stack-pointer). Non dimentichiamo che lo stack cresce in giu`
quindi gli argomenti sopra il corrente stack-pointer sono valori sullo stack.
Dal momento che notiamo che il parametro e` EPROCESS, crediamo che questo e`
consistente -stiamo provando a caricare informazioni dalla struttura EPROCESS.
Quindi, la nostra prossima domanda diventa: cosa sta dentro la struttura
EPROCESS all'offset 0x104? Usiamo un "!strct" per mostrare il formato della
struttura EPROCESS:
> !strct eprocess
Structure EPROCESS (Size:0x1f8) member offsets:
+0000 Pcb(KPROCESS struct)
+0000 Header(DISPATCHER_HEADER struct)
+0010 ProfileListHead(LIST_ENTRY struct)
+0018 DirectoryTableBase
+0020 LdtDescriptor(KGDTENTRY struct)
+0028 Int21Descriptor(KIDTENTRY struct)
+0030 IopmOffset
+0032 Iopl
+0033 VdmFlag
+0034 ActiveProcessors
+0038 KernelTime
+003c UserTime
+0040 ReadyListHead(LIST_ENTRY struct)
+0048 SwapListEntry(LIST_ENTRY struct)
+0050 ThreadListHead(LIST_ENTRY struct)
+0058 ProcessLock
+005c Affinity
+0060 StackCount
+0062 BasePriority
+0063 ThreadQuantum
+0064 AutoAlignment
+0065 State
+0066 ThreadSeed
+0067 DisableBoost
+0068 ExitStatus
+006c LockEvent(KEVENT struct)
+006c Header(DISPATCHER_HEADER struct)
+007c LockCount
+0080 CreateTime
+0088 ExitTime
+0090 LockOwner
+0094 UniqueProcessId
+0098 ActiveProcessLinks(LIST_ENTRY struct)
+0098 Flink
+009c Blink
+00a0 QuotaPeakPoolUsage
+00a8 QuotaPoolUsage
+00b0 PagefileUsage
+00b4 CommitCharge
+00b8 PeakPagefileUsage
+00bc PeakVirtualSize
+00c0 VirtualSize
+00c8 Vm(MMSUPPORT struct)
+00c8 LastTrimTime
+00d0 LastTrimFaultCount
+00d4 PageFaultCount
+00d8 PeakWorkingSetSize
+00dc WorkingSetSize
+00e0 MinimumWorkingSetSize
+00e4 MaximumWorkingSetSize
+00e8 VmWorkingSetList
+00ec WorkingSetExpansionLinks(LIST_ENTRY struct)
+00f4 AllowWorkingSetAdjustment
+00f5 AddressSpaceBeingDeleted
+00f6 ForegroundSwitchCount
+00f7 MemoryPriority
+00f8 LastProtoPteFault
+00fc DebugPort
+0100 ExceptionPort
+0104 ObjectTable
+0108 Token
+010c WorkingSetLock(FAST_MUTEX struct)
+010c Count
+0110 Owner
+0114 Contention
+0118 Event(KEVENT struct)
+0128 OldIrql
+012c WorkingSetPage
+0130 ProcessOutswapEnabled
+0131 ProcessOutswapped
+0132 AddressSpaceInitialized
+0133 AddressSpaceDeleted
+0134 AddressCreationLock(FAST_MUTEX struct)
+0134 Count
+0138 Owner
+013c Contention
+0140 Event(KEVENT struct)
+0150 OldIrql
+0154 HyperSpaceLock
+0158 ForkInProgress
+015c VmOperation
+015e ForkWasSuccessful
+015f MmAgressiveWsTrimMask
+0160 VmOperationEvent
+0164 PageDirectoryPte(HARDWARE_PTE struct)
+0164 Valid
+0164 Write
+0164 Owner
+0164 WriteThrough
+0164 CacheDisable
+0164 Accessed
+0164 Dirty
+0164 LargePage
+0164 Global
+0164 CopyOnWrite
+0164 Prototype
+0164 reserved
+0164 PageFrameNumber
+0168 LastFaultCount
+016c ModifiedPageCount
+0170 VadRoot
+0174 VadHint
+0178 CloneRoot
+017c NumberOfPrivatePages
+0180 NumberOfLockedPages
+0184 NextPageColor
+0186 ExitProcessCalled
+0187 CreateProcessReported
+0188 SectionHandle
+018c Peb
+0190 SectionBaseAddress
+0194 QuotaBlock
+0198 LastThreadExitStatus
+019c WorkingSetWatch
+01a0 Win32WindowStation
+01a4 InheritedFromUniqueProcessId
+01a8 GrantedAccess
+01ac DefaultHardErrorProcessing
+01b0 LdtInformation
+01b4 VadFreeHint
+01b8 VdmObjects
+01bc ProcessMutant(KMUTANT struct)
+01bc Header(DISPATCHER_HEADER struct)
+01cc MutantListEntry(LIST_ENTRY struct)
+01d4 OwnerThread
+01d8 Abandoned
+01d9 ApcDisable
+01dc ImageFileName
+01ec VmTrimFaultValue
+01f0 SetTimerResolution
+01f1 PriorityClass
+01f2 SubSystemMinorVersion
+01f3 SubSystemMajorVersion
+01f2 SubSystemVersion
+01f4 Win32Process
> * esp+14 looks like Param2
> * eax is (esp+14)->(104)
> * Test for null
> * eax = *(eax+14)
Nota l'offset 0x104 - ObjectTabke. Tornando indietro al codice disassemblato,
notiamo che dopo aver caricato questo valore in memoria, esso e` testato per
assicurare che non e` un puntatore a NULL:
8015C921 85C0 test eax,eax
8015C923 740C je _ExpCopyProcessInfo@8+1Dh
Dal momento che stiamo eseguento l'istruzione che segue il "je", sappiamo che
il test avviene con successo ed abbiamo un valore non-NULL. Proviamo a
comparare questo risultato con il contenuto corrente dei dati in memoria.
Facciamo questo dumpando il contenuto della struttura EPROCESS, usando
kdex2x86:
0: kd> !strct eprocess B980Ae08
Structure EPROCESS (Size:0x1f8) at 0xb980ae08:
+0000 Pcb(KPROCESS struct)
+0000 Header(DISPATCHER_HEADER struct)
+0010 ProfileListHead(LIST_ENTRY struct)
+0018 DirectoryTableBase = 08c6f000 21570000
+0020 LdtDescriptor(KGDTENTRY struct)
+0028 Int21Descriptor(KIDTENTRY struct)
+0030 IopmOffset = 20ad
+0032 Iopl = 00
+0033 VdmFlag = 00
+0034 ActiveProcessors = 00000001
+0038 KernelTime = 00000001
+003c UserTime = 00000001
+0040 ReadyListHead(LIST_ENTRY struct)
+0048 SwapListEntry(LIST_ENTRY struct)
+0050 ThreadListHead(LIST_ENTRY struct)
+0058 ProcessLock = 00000000
+005c Affinity = 0000000f
+0060 StackCount = 0001
+0062 BasePriority = 08
+0063 ThreadQuantum = 24
+0064 AutoAlignment = 00
+0065 State = 00
+0066 ThreadSeed = 54
+0067 DisableBoost = 00
+0068 ExitStatus(NTSTATUS) = 0(STATUS_SUCCESS)
+006c LockEvent(KEVENT struct)
+006c Header(DISPATCHER_HEADER struct)
+007c LockCount = 00000000
+0080 CreateTime(LARGE_INTEGER/ULARGE_INTEGER union) = following
+0080 None(Anonymous struct) = following
+0088 ExitTime(LARGE_INTEGER/ULARGE_INTEGER union) = following
+0088 None(Anonymous struct) = following
+0090 LockOwner = BF6B6DC0 (-> PKTHREAD)
+0094 UniqueProcessId = 00000120 (-> HANDLE)
+0098 ActiveProcessLinks(LIST_ENTRY struct)
+0098 Flink = BF2E4EA0 (-> PLIST_ENTRY)
+009c Blink = B2EC4EA0 (-> PLIST_ENTRY)
+00a0 QuotaPeakPoolUsage = 00000460 00002938
+00a8 QuotaPoolUsage = 00000340 00000e81
+00b0 PagefileUsage = 00000024
+00b4 CommitCharge = 00000024
+00b8 PeakPagefileUsage = 0000003a
+00bc PeakVirtualSize = 00905000
+00c0 VirtualSize = 004e5000
+00c8 Vm(MMSUPPORT struct)
+00c8 LastTrimTime(LARGE_INTEGER/ULARGE_INTEGER union) = following
+00d0 LastTrimFaultCount = 000000a2
+00d4 PageFaultCount = 000000a4
+00d8 PeakWorkingSetSize = 000000a7
+00dc WorkingSetSize = 00000091
+00e0 MinimumWorkingSetSize = 00000032
+00e4 MaximumWorkingSetSize = 00000159
+00e8 VmWorkingSetList = C0502000 (-> PMMWSL)
+00ec WorkingSetExpansionLinks(LIST_ENTRY struct)
+00f4 AllowWorkingSetAdjustment = 01
+00f5 AddressSpaceBeingDeleted = 00
+00f6 ForegroundSwitchCount = 00
+00f7 MemoryPriority = 00
+00f8 LastProtoPteFault = 00000000
+00fc DebugPort = 00000000
+0100 ExceptionPort = b3030f68
+0104 ObjectTable = 00000000 (-> PHANDLE_TABLE) <------------ !!!!
+0108 Token = B0834EB0 (-> PACCESS_TOKEN)
+010c WorkingSetLock(FAST_MUTEX struct)
+010c Count = 00000001
+0110 Owner = 00000000 (-> PKTHREAD)
+0114 Contention = 00000000
+0118 Event(KEVENT struct)
+0128 OldIrql = 0000003d
+012c WorkingSetPage = 0002ec71
+0130 ProcessOutswapEnabled = 00
+0131 ProcessOutswapped = 00
+0132 AddressSpaceInitialized = 01
+0133 AddressSpaceDeleted = 00
+0134 AddressCreationLock(FAST_MUTEX struct)
+0134 Count = 00000001
+0138 Owner = 00000000 (-> PKTHREAD)
+013c Contention = 00000000
+0140 Event(KEVENT struct)
+0150 OldIrql = 00000000
+0154 HyperSpaceLock = 00000000
+0158 ForkInProgress = 00000000 (-> PETHREAD)
+015c VmOperation = 0000
+015e ForkWasSuccessful = 00
+015f MmAgressiveWsTrimMask = 00
+0160 VmOperationEvent = 00000000 (-> PKEVENT)
+0164 PageDirectoryPte(HARDWARE_PTE struct)
+0168 LastFaultCount = 00000000
+016c ModifiedPageCount = 00000000
+0170 VadRoot = a856cfc8
+0174 VadHint = a856cfc8
+0178 CloneRoot = 00000000
+017c NumberOfPrivatePages = 0000001e
+0180 NumberOfLockedPages = 00000000
+0184 NextPageColor = 5d24
+0186 ExitProcessCalled = 01
+0187 CreateProcessReported = 00
+0188 SectionHandle = 00000004 (-> HANDLE)
+018c Peb = 7FFDF000 (-> PPEB)
+0190 SectionBaseAddress = 01000000
+0194 QuotaBlock = BDCEEFD0 (-> PEPROCESS_QUOTA_BLOCK)
+0198 LastThreadExitStatus(NTSTATUS) = 0(STATUS_SUCCESS)
+019c WorkingSetWatch = 00000000 (-> PPAGEFAULT_HISTORY)
+01a0 Win32WindowStation = 00000000 (-> HANDLE)
+01a4 InheritedFromUniqueProcessId = 0000007C (-> HANDLE)
+01a8 GrantedAccess(ACCESS_MASK) = 1f0fff( STANDARD_RIGHTS_ALL )
+01ac DefaultHardErrorProcessing = 00008000
+01b0 LdtInformation = 00000000
+01b4 VadFreeHint = ba2cafc8
+01b8 VdmObjects = 00000000
+01bc ProcessMutant(KMUTANT struct)
+01bc Header(DISPATCHER_HEADER struct)
+01cc MutantListEntry(LIST_ENTRY struct)
+01d4 OwnerThread = 00000000 (-> PKTHREAD)
+01d8 Abandoned = 00
+01d9 ApcDisable = 00
+01dc ImageFileName = cgiapp.exe......
+01ec VmTrimFaultValue = 00000000
+01f0 SetTimerResolution = 00
+01f1 PriorityClass = 02
+01f2 SubSystemMinorVersion = 00
+01f3 SubSystemMajorVersion = 04
+01f2 SubSystemVersion = 0400
+01f4 Win32Process = 00000000
*Nota che il valore in 0x104 e` null!
Abbiamo terminato la nostra analisi credendo di aver trovato una
racecondition multiprocessore all'interno di NT. Specificatamente il gestore
della objecttable e` stato cancellato e deallocato allo stesso tempo, un
thread separato ha tentato di dereferenziarlo. Il tutto e` stato riportato a
chi di dovere, blablablablablablalblalblalblalblalbla.
.Conclusioni.
<---------------------------------------------------------------------------->
Queste sono le magie dietro i bluescreen. Nella mia esperienza le informazioni
presentate sui bluescreen servono molto come un "cenno". Tracciare realmente
un problema richiede di giocare sugli errori da dentro "SoftIce/NT" o WinDbg.
Entrambi i debuggers ottengono un controllo completo al punto di
KeBugCheckEx(...), quindi e` possibile controllare intorno per altre tracce.
Ovviamente, la maggior parte delle volte, bisognera` guardare l'errore
verificatosi prima di riuscire a capirlo realmente.
.Credits & Resources.
<---------------------------------------------------------------------------->
greetz, fly out to the following:
CmCSynTH, Hi0, mirc4ll4, smaster, Cavallo, quest, vecna
DreadN, Berry, FuSyS, Kobaiashi, naif, nail, spirit.
...and all the other, that my broken mind'd broken.
Resources:
[A] Inside Windows NT - A. Solomon
[B] Undocumented Windows NT - P. Dabak, S. Phadke, M. Borate
[C] Windows NT Device Driver Development - P. Viscarola, W. Mason
==============================================================================
--------------------------------[ EOF 15/15 ]---------------------------------
==============================================================================