|
Niente Prog per oggi
(Di tutto un pò) |
|
Data |
by "GuZuRa" |
|
|
UIC's Home Page |
Published by Quequero |
Chi sa soffre... |
Guzura ci spiega il funzionamento dello stack senza
entrare troppo nei particolari, il tutorial è adattissimo ai NewBies per capire cosa sia
e come funzioni lo stack! |
Chi non sa soffre di più... |
UIC's form |
- E-mail: [email protected]
- Su #crack-it come GuZuRa
|
UIC's form |
Difficoltà
|
(X)NewBies ( )Intermedio ( )Avanzato ( )Master |
|
- Questo tutorial è rivolto ai totally newbies ed ai newbies che hanno voglia di di
ripassare i primi concetti (quelli da tenere sempre presenti...Come : le donne dei
reverser sono le più belle; il mio nick incute terrore al solo pronunciarlo e cose del
genere...)
- Scherzi a parte ho scritto questo tutorial proprio mentre io stesso andavo a rivedermi
le prime cose basilari tra cui il concetto di stack (questo strano oggetto del desiderio)
- Ultima nota introduttiva : spero che con tutte le cose che ,al momento, ho intenzione di
infilarci dentro non diventi una tut pallosissimo...
-
- Chissà se servirà
(Di tutto un pò)
Written by Tuo GuZuRa
- Lo stack chi lo ama e chi lo frega...
- Il cevello può bastare
- Alla UIC ovvio ;))))
- Ma che programma
-
- Ora vado ad aprire una lunghissima parentesi sul significato delle push, pop e dello
stack (Que dai una controllata che è meglio :)))))
- Nota: le cose che cerco di spiegare non sono per nulla generali ma sono suscettibili a
cambiamenti e manipolazioni diverse proprio perchè l'assembler ci permette di controllare
a bassissimo livello anche memoria e affini quindi non consideratele come ASSOLUTE
- STACK
- Lo stack è un' area di memoria gestita con tecnica LIFO (Last In, First Out) che viene
usata "solitamente" utilizzata per contenere i record di attivazione delle
procedure o (vedetela così anche se non è la stessa cosa) i parametri in ingresso alle
procedure del programma attualmente in esecuzione
- E' caratterizzato dal registro SS (stack segment) che definisce il segmento che punta
allo stack corrente (ma questo è poco significativo nel reversing di programmi a 32bit
cioè la maggior parte di quelli che reversiamo, sui 16bit c'era il seguente
problema: considerate che in memoria potevano coesistere più stack (uno per ogni
programma in esecuzione grossomodo) e ognuno poteva indirizzare al massimo 64kbyte di
stack quindi se un programma superava tale limite si rischiava di sovrascrivere lo stack
di un'altro programma con ovvi casini per questo era necessario sapere sempre il SS del
programma che si voleva reversare mentre per altri motivi questo non è più un problema
per i 32bit; il messaggio che deve rimanere è che, per un newbie, questo registro
"non è fondamentale") e dal registro esp (dove per sp si intende stack pointer)
che chiariremo tra un attimo
- Vediamo di spiegare cosa intendiamo per LIFO, cioè l'ultimo elemento infilato nello
stack è anche il primo ad essere estratto
-
- Prima cosa lo stack cresce dagli indirizzi alti a quelli bassi e che l'indirizzo
di partenza dello stack contenuto in (SS) non è il bottom
dello stack
-
SS ----->|--------------------- |
-
| Spazio nello stack |
-
ESP | ancora disponibile |
-
|
|
- TOP dello stack ----->|--------------------- |ESP+00 Indirizzo più basso
- | Spazio
occupato |ESP+04
- |dalla roba nello stack|ESP+08
- BOTTOM dello stack ----->|--------------------- | Indirizzo più alto
-
- Fate un piccolo sforzo e cercate di seguirmi AD OGNI BARRA VERTICALE ( | )FAREMO CORRISPONDERE 4 BYTE (cioè una double-word) quindi nel
nostro modello, potremo indirizzare fino a 8 locazioni ,da 4 BYTE ognuna, senza correre il
rischio di sovrascrivere gli stack di altri programmi andando oltre lo spazio ancora
disponibile (questa non è nella realtà la dimensione vera dello spazio indirizzabile,
ovvio).
- Ho detto prima che lo stack serve per contenere le variabili in igresso alle procedure,
voi dovete immaginare che in ogni locazione (che è grande 4 byte cioè 32 bit ci sia un
valore, una variabile, un indirizzo che mi possa servire)
- Nel nostro modello abbiamo già occupato 4 locazioni e altre 4 invece ne abbiamo
disponibili
- Ancora un po di notazione OGNI BARRA VERTICALE ( | )
corrisponderà a una locazione da 4 byte libera e ad OGNI BARRA VERTICALE ( | ) una locazione occupata
- Chiariamo subito che cos'è ESP: è l' OFFSET (la distanza) tra SS e il TOP sello stack;
noi non sappiamo esattamente quantificarlo però sappiamo che se cerchiamo un valore che
sappiamo essere nella prima locazione dello stack faremo riferimento as SS : ESP (metto
anche SS anche se ho già detto che per noi è meno significativo anche perchè è ovvio
che un debugger ci mostra lo stack che ci interessa) ; analogamente se vogliamo leggere un
valore che sappiamo trovarsi nella terza locazione occupata nello stack faremo riferimento
a SS : ESP + 8 (8 byte dopo ESP cioè due locazioni da 4 byte dopo; il PC ragiona in byte
e non in locazioni) guardate in figura (in blu) dove sta ESP
e ESP + 8
-
- COME SI MANEGGIA LO STACK (LIFO)
- Si utilizzano due istruzioni PUSH che mette un valore nello stak nella locazione sopra
il TOP e l'istruzione POP che toglie l'attuale TOP e trasforma in TOP i 4 byte che erano
sotto (spero di chiarire con l'esempio sotto)
-
-
- Situazione prima di una
push
Situazione dopo una push
-
- SS -->|---------------------
| SS--> |--------------------- |
-
| Spazio nello stack |
| Spazio nello stack |
- ESP |
ancora disponibile |
ESP
| ancora disponibile |
-
|
| TOP -->| Nuovo
TOP |ESP+00
- TOP -->|--------------------- |ESP+00
|--------------------- |ESP+04
-
| Spazio occupato |ESP+04 | Spazio occupato |ESP+08
-
|dalla roba nello
stack|ESP+08
|dalla roba nello stack|ESP+0C
- BOTTOM-->|--------------------- |
BOTTOM-->|--------------------- |
-
- Come potete vedere è diminuito l'offset da SS, osservate bene come si sono
modificate le cose e saremo a bolla; per capire come funziona una pop basta invertire le
situazioni (si parte da quella a destra e si arriva a quella a sinistra); ora dovrebbe
essere chiaro che io posso fare 3 push a fila ma se ne faccio un'altra supero SS e vado a
scrivere dove non mi compete (era questa la limitazione che c'era nei programmi a 16 bit
che avevano stack limitato a 64Kbyte e non è molto)
- A livello di teoria della programmazione potrei accedere al bottom solo dopo aver fatto
tante pop quante sono le locazioni precedenti, occupate, (in realtà io posso accedere a
tutto quello che voglio conoscendo ESP e la distanza in byte da ESP della locazione che mi
serve; come ho detto sopra) Qui si spiega perchè si dice che lo stack è gestito LIFO
-
- Ora cerchiamo di capire perchè lo stack è così importante analizzando le relazioni
che ha con una procedura assembler che più generale non si può ;))))))) Mi scusino i
maghi della programmazione in ASM...Altra cosa, come al solito lo schema a cui faccio
riferimento e generico e suscettibile di infinite varianti ma mi serve solo per far capire
alcuni concetti
-
- In un linguaggio pseudo C l'invocazione del programma chiamante sarebbe di questo tipo :
- funzione(arg1,arg2,arg3,...,argN)
-
- Mentre in ASM sarebbe di questo tipo
- push argN
- push ...
| arg1 |ESP+00 Se supponiamo che lo
stack
- push arg2
| arg2 |ESP+04 sia "vuoto"
abbiamo una
- push arg1
| ... |ESP+08 situazione di questo
tipo
- call_funzione
| argN |ESP+0C prima
della call
- add esp,num_byte_arg
-
- Succede questo, il chiamante :
- 1)passa sullo stack gli argomenti alla funzione(dall'ultimo al primo)
- 2)trasferisce il controllo con la call_funzione
- 3)al ritorno dalla procedura aggiusta il puntatore allo stack di un valore pari al
totale in byte degli argomenti passati, in modo da "scaricarli" virtualmente
(cioè se accedete a ESP - 04 o altro li ribeccate) dallo stack
-
- Adesso però dobbiamo vedere come è fatta la call_funzione al suo interno
- Lo standard è quasi sempre questo
- funzione proc near (o far)
- Si salva sullo stack l'attuale valore del registro EBP e poi si sovrascrive EBP con il
contenuto di ESP. Per tutto il tempo di vita della procedura, EBP non viene più
modificato, e viene utilizzato come record di attivazione della funzione (cioè se mi
serve un valore lo pesco da EBP nella stessa maniera che avevo spiegato sopra ;se mi serve
il quarto valore andrò a leggere EBP + 12...)
- Tutto questo per dire che si incomincia il codice della call con
- Push EBP
- Mov EBP, ESP
-
- Se la funzioncella fa uso di variabili locali queste vengono allocate a seguire con
l'istruzione
- Sub ESP,XXX (numero di locazioni che ci servono * 4 byte)
-
- Per "convenzione" dopo essere uscito dalla call i registri EBP, ESP, CS, DS,
SS, EDI, ESI devono avere il vecchio valore che avevano prima della chiamata quindi se ho
intenzione di utilizzarli e modificarli nella funzione dovrò fare così (la convenzione
di riavere EDI e ESI, dopo le chiamate, uguali al valore che avevano prima delle chiamate
stesse viene mantenuta anche se sono chiamate API di windows, per gli altri registri non
sò)
- Push EDI se utilizzato cioè ne salvo il valore nello stack
- Push ESI se utilizzato,etc...
-
- In uscita possono avere valori diversi per EAX, EBX, ECX, EDX, ES, e i FLAG (e noi come
reverser siamo interessati soprattutto a queste modifiche che avvengono dentro le call)
-
- Anche l'epilogo della procedura è standard
- Pop ESI
- Pop EDI etc...
-
- Pop EBP ripristino di EBP al valore che avevo prima della chiamata
-
- Adesso ci occupiamo della parte fondamentale e anche chiarificatrice dei deliri di
qualche riga sopra
- ACCESSO AI PARAMETRI PASSATI SULLO STACK
- Supponiamo che il chiamante abbia passato 3 parametri sullo stack (le variabili L e K e
il valore 22) e abbia invocato la procedura, lo stack che osserviamo è questo
-
-
SS ----->|--------------------- |
-
| Spazio nello stack |
-
ESP | ancora disponibile |
-
|
|
- TOP dello stack ----->| Offset di ritorno |ESP+00
- |
L
|ESP+04
- |
K
|ESP+08
- BOTTOM dello stack ----->|
22
|ESP+0C
-
- L'offset di ritorno definisce in pratica a quale punto del codice si ritorna una volta
eseguita la call (generalmente è l'istruzione successiva alla call stessa)
- Siamo ora nella nostra bella funzioncina e supponiamo di voler riservare due locazioni
(8 byte in tutto) per due variabili locali, la nostra funzione sarà così
- funzione PROC NEAR
- Push EBP
- Mov EBP, ESP
-
- SUB ESP, 08
-
- Lo stack a questo punto risulta questo
-
-
-
SS ----->
| DISP0NIBILE |
- TOP dello stack -----> ESP+00 | 2° variabile locale |EBP-08
-
ESP+04 |
1° variabile locale |EBP+04
-
ESP+00 |Vecchio valore di EBP |EBP+0C
-
ESP+0C | Offset di ritorno |EBP+04
- ESP+10 |
L
|EBP+08
- ESP+14 |
K
|EBP+0C
- BOTTOM dello stack -----> ESP+18
|
22
|EBP+10
-
- Adesso pensiamo di voler fare in modo che il nostro programmino arrivi ad avere in EAX
il valore L + K - 22 come facciamo??
- Il codice che dovremo inserire è questo (vi farà capire come si accede ai vari valori
nello stack)
- Mov EAX, [EBP+08] muovo in eax L (Notate che uso EBP)
- Add EAX, [EBP+0C] sommo ad L il valore K
- Sub EAX, [EBP+10] e ho in pratica realizzato quello che volevo
-
- Supponiamo di voler inizializzare a zero la seconda variabile locale: semplice
- Mov DWORD PTR [EBP-08], 0
-
- Prima di uscire devo rimettere a posto lo stack quindi farò
- Mov ESP, EBP
- Devo togliere il vecchio valore di EBP da ESP (guardate la figura sopra e vi sara chiaro
come tutto si modifica)
- Pop EBP
- Ret Perchè in questa call non ho usato nessono dei registri EDI, ESI ...
(se li avessi usati avrei dovuto fare prima delle push e poi delle pop)
Add ESP,0C questa mi serve per togliere dallo stack le tre locazioni che facevano
riferimento a L, K, 22 e tornare effettivamente alla situazione di stack iniziale
Spero che alla luce di questo tut lo stack non vi si più tanto oscuro...
ALLA PROSSIMA Byez GuZuRa
Ringrazio Que che con la UIC mi ha fatto imparare cose di cui non conoscevo nemmeno
l'esistenza, e il mio PROf di info2 che mi ha spiegato così bene lo stack (chissa se
anche lui è membro????) a si anche tutti quelli che mi conoscono ;)))))))))
- Vorrei ricordare che il software va comprato e non rubato, dovete registrare il
vostro prodotto dopo il periodo di valutazione. Non mi ritengo responsabile per eventuali
danni causati al vostro computer determinati dall'uso improprio di questo tutorial. Questo
documento è stato scritto per invogliare il consumatore a registrare legalmente i propri
programmi, e non a fargli fare uso dei tantissimi file crack presenti in rete, infatti
tale documento aiuta a comprendere lo sforzo immane che ogni singolo programmatore ha
dovuto portare avanti per fornire ai rispettivi consumatori i migliori prodotti possibili.
- Noi reversiamo al solo scopo informativo e di miglioramento del linguaggio Assembly.
- Capitoooooooo????? Bhè credo di si ;))))