==============================================================================
------------[ 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 ]---------------------------------
==============================================================================

