Zoom Icon

Dot Net Framework Reversing(parte 2)

From UIC

.Net Framework Reversing (parte 2)

Contents


Infos
Author: Pbdz
Email: pbdz@libero.it
Website: http://pbdz.cjb.net
Date: 14/06/2004 (dd/mm/yyyy)
Level: Luck and skills are required
Language: Italian Image:Flag_Italian.gif
Comments: Grazie mille pbdz, come al solito hai scritto un ottimo tutorial!



Introduction

Non avete capito niente del tutorial precedente? Mi fa piacere! Vi do un'altra possibilità! ;-)
Che dire...sta arrivando l'estate, le ragazze sono tutte delle .....! L'unico modo per non impazzire è tuffarmi in un mare di codice incomprensibile!.


Tools

Per disassemblare consiglierei IDA, altrimenti basta scrivere sulla nel prompt dei comandi: ildasm.
Per modificare il file basta un qualsiasi Hex Editor.


Link e Riferimenti

Ecco il link al mio programma. Per scaricare il framework andate sul sito di zio Bill! ;-)


Notizie sul Programma

AVVISO: Se avete letto il tutorial precedente potete anche saltare questo pezzo! Altrimenti, vabbè..saltatelo lo stesso, ma rimarrete ignoranti! :-)

Cos'è il Framework.NET?
Questo oscuro oggetto (di casa microsoft) non è altro che una piattaforma su cui possono girare determinati programmi, piattaforma anche chiamata CLR (Common Language Runtime).
Per farvi un esempio basta pensare alle varie dll che servono per far girare un programma in VB.
Il concetto è lo stesso, se fate un programma in C# VB.Net ecc...e non avete il framework installato, il programma non vi funzionerà mai. Il programma non funzionerà perchè conterrà istruzioni differenti dalle solite... Supponiamo che faccia un programma in c#, compilando il codice sorgente ottengo un tipo di file chiamato Assembly, ha l'estensione .exe, ma non contiene al suo interno del codice macchina, bensì I.L. (intermediate language). Questo I.L. sarà tradotto dal CLR in linguaggio macchina vero e proprio, e quindi sarà adatto per l'esecuzione. Il vantaggio di questo meccanismo sta nel fatto che il CLR compila solo il codice che serve, perciò si risparmia tempo, in più una volta che ha compilato il codice questo viene salvato e non sarà più necessario ricompilarlo al momento di una futura esecuzione.
Inutile dire che il framework lo trovate sul sito di ZiA Bill! :-) Potete scaricare quello "normale" che pesa una 20ina di Mb o, se volete divertirvi potete scaricare l'SDK, ma pesa molto di più! (sconsigliato se avete ancora un 56K come me! Consigliato se avete un amico con l'adsl! ;-))
Spero di essere stato abbastanza esaustivo!


Essay

Ho deciso di scrivere una guida un pò più approfondita perchè il primo tutorial forse era un pò troppo sintetico, ma sapete... sono quelle cose che scrivi di getto, senza pensare troppo!!!

Framework.NET

Cercherò di descrivere come interpretare l'intermediate language per risalire alle operazioni più comuni, come dichiarare una variabile, un ciclo for ecc...

Dichiarazione e assegnazione di una variabile

int numero;

disassemblato diviene:

.locals init (int32 V0)

Quando si dichiara una o più variabili, questa viene memorizzata in un'array, qui per esempio abbiamo dichiarato solo una variabile e quindi il suo numero (posizione nell'array) è 0. Se per esempio avevamo tre variabili di cui due di tipo int32 e una di tipo STRING avevamo:

.locals init (int32 V0,
int32 V1,
class System.String V2)

è tutto chiaro? Spero di si!

Assegnare un valore ad una variabile

numero = 5;

Per assegnare un numero occorrono 2 istruzioni:

ldc.i4.5

Questa istruzione serve per pushare nello stack il numero 5 come un int32.

Quando pusha un numero int32 si usa i4, se si usa un numero int64 si usa i8, se si pusha un float32 si usa r4, se si pusha un float64 si usa r8.

La sintassi ldc.i4.(num) è valida con i numeri fino ad 8. E ancora finchè si usano i numeri fino ad 8 le istruzioni vengono considerate un unico blocco, mi spiego meglio, l'istruzione ldc.i4.6 ha un solo opcode che la identifica. Gli opcode per queste istruzioni vanno da 16(ldc.i4.0) a 1E(ldc.i4.8). Ma ritorniamo al discorso di prima, come si fa a pushare un numero maggiore di 8?

Semplice, si usa questa funzione: ldc.i4.s num, es. ldc.i4.s 0xA in questo caso l’opcode sarà da 1F0A dove 1F rappresenta l'istruzione per pushare e 0A il numero pushato. Può darsi che qualche volta invece di ldc.i4.s num troverete ldc.i4 num.

stloc.0

Questa funzione non fa altro che caricare nella variabile numero 0 il valore precedentemente pushato.

Operazioni matematiche semplici

Facciamo finta di aver già dichiarato 3 variabili di cui la terza deve contenere il risultato tra la somma/sottrazione/divisione/moltiplicazione tra le prime due.

int numero = 10;
int numero1 = 23;
int risultato;

risultato = numero+numero1;

ecco come ci viene tradotto:

.locals init (int32 V0,
int32 V1,
int32 V2)
ldc.i4.s 0xA
stloc.0
ldc.i4.s 0x17
stloc.1

Fino a qui sappiamo cosa succede, giusto? Abbiamo le prime 2 variabili assegnate. Ora ecco cosa succede:

ldloc.0
ldloc.1
add
stloc.2

Si capisce già cosa combina in questo caso, pusha nello stack la prima e la seconda variabile con l'istruzione ldloc, li somma con l’istruzione add e poi poppa il loro valore nella terza variabile tramite l’istruzione stloc.2 (2 rappresenta la posizione nell'array delle variabili...).

Il procedimento è identico sia per la sottrazione/divisione/moltiplicazione...le istruzioni sono:

mul //moltiplicazione
sub //sottrazione
div //divisione

Queste istruzioni prelevano dallo stack gli ultimi 2 valori pushati e ripusha il risultato che poi verrà inserito nell'altra variabile.

IF-ELSE

Vediamo cosa succede quando in un programma sono usate queste 2 condizioni!

Supponiamo che in un programma ci sia questo codice:

int numero = 1;

if(numero == 1)
{
MessageBox.Show("Condizione soddisfatta","Messaggio");
}
else
{
MessageBox.Show("Condizione non soddisfatta","Messaggio");
}

Scusate, non è il massimo che si possa avere! Però è giusto per rendere la cosa abbastanza semplice. Disassembliamo e avremo:

//Fin qui dichiariamo la solita variabile e le assegnamo come valore 1.
.locals init (int32 V0)
ldc.i4.1
stloc.0
//Qui pushamo nello stack il valore della variabile e il numero 1
ldloc.0 ---> posizione che la variabile occupa nell’array
ldc.i4.1
bne.un.s loc_158
/*Questa sopra è la funzione chiave, in pratica salta se i due valori che
abbiamo pushato sono diversi. È l'equivalente di un jne! Quella s alla
fine significa che il salto è corto.
In sintesi possiamo dire
if(var_pushata == 1)
[Esegui codice] */


//Quello che segue sono le istruzioni che servono a visualizzare una MessageBox.
//Ve le commento brevemente
ldstr "Condizione soddisfatta" //carica nello stack il testo della MsgBox
ldstr "Messaggio" //carica nello stack il titolo della MsgBox
//Infine chiama la funzione vera e propria
call value class [System.Windows.Forms]System.Windows.Forms.DialogResult \
[System.Windows.Forms]System.Windows.Forms.MessageBox::Show(class \
System.String, class System.String)
pop
br.s loc_168 //questo è un salto incodizionato e serve per terminare tutta
//la funzione, la s sta ad indicare che il salto è di
//tipo short

loc_158:
ldstr "Condizione non soddisfatta"
ldstr "Messaggio"
call value class [System.Windows.Forms]System.Windows.Forms.DialogResult \
[System.Windows.Forms]System.Windows.Forms.MessageBox::Show(class \
System.String, class System.String)
pop

loc_168:
ret

Ciclo While

l’equivalente di

int numero = 1;
while(numero < 10)
{
numero++;
}

?

Eccolo:

.locals init (int32 V0)
ldc.i4.1
stloc.0
//fin qui tutto normale, sempre la solita variabile con valore 1.
br.s loc_148 //Qui salta alla loc_148, perciò andiamo pure noi!

loc_144:
ldloc.0
ldc.i4.1
add
stloc.0
/*Non notate niente di familiare? Dai...date uno sguardo al paragrafo
che riguarda le operazioni matematiche! In sintesi tutto sto casino per
fare "numero++".

Credo sia chiarissimo! Da notare che finita questa operazione va di nuovo
a controllare se la nostra variabile è minore di 10, come ogni
ciclo while che si rispetti!*/


loc_148:
ldloc.0
ldc.i4.s 0xA
//fin qui ha pushato nello stack il valore della nostra variabile e il
//numero 0x0A, che come tutti sanno equivale a 10 decimale.
blt.s loc_144 //qui c'è un salto condizionato, salta se il primo
//valore è minore del secondo! Mi sa che è ora
//di saltare! :-) Andiamo alla loc_144!

Ciclo Do-While

int numero = 1;
do
{
numero++;
}
while(numero < 10);

Disassemblato avremo:

.locals init (int32 V0)
ldc.i4.1
stloc.0

loc_142:
ldloc.0
ldc.i4.1
add
stloc.0
ldloc.0
ldc.i4.s 0xA
blt.s loc_142
ret

/*Se notate bene le istruzioni cambiano solo nell'ordine...infatti il ciclo
do-while esegue prima l'istruzione e poi verifica la condizione. Non commento
il codice in quanto è identico al precedente.*/

Ciclo For

Ecco un ciclo for ultra standard!

for(int i=0; i < 10;i++)
{
MessageBox.Show("Messaggio numero "+i,"Titolo MessageBox");
}

Ed ecco la “traduzione”:

.locals init (int32 V0)
ldc.i4.0
stloc.0
br.s loc_163 //dopo aver assegnato la variabile salta a loc_163
loc_144:
ldstr "Messaggio numero"
ldloc.0
box [mscorlib]System.Int32
call class System.String [mscorlib]System.String::Concat(class \
System.Object, class System.Object)
ldstr "Titolo MessageBox"
call value class [System.Windows.Forms]System.Windows.Forms.DialogResult \
[System.Windows.Forms]System.Windows.Forms.MessageBox::Show(class \
System.String, class System.String)
pop //Fin qui visualizza la messagebox
ldloc.0
ldc.i4.1
add //Incrementa il valore di i di 1...
stloc.0
loc_163:
ldloc.0
ldc.i4.s 0xA
blt.s loc_144 //Confronta la variabile (i) con il numero 10 e salta a
//loc_144 se i è minore.
Ret

Prendere il testo da una TextBox

Considerate questa routine:

private void button1_Click(object sender, System.EventArgs e)
{
string nome = nome_txt.Text;
string serial = serial_txt.Text;
MessageBox.Show(zz, prova);
}

Come potete osservare dichiaro 2 stringhe e le assegno 2 valori che provengono da 2 textBox quali, nome_txt e serial_txt. Il programma in fine visualizza le stringhe con una MessageBox.

Ecco come si presenta con IDA:

.locals init (class System.String V0, //dichiara le
class System.String V1) //due stringhe
ldarg.0
ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox \
my_app.Form1::serial //carica nello stack il controllo da usare,
//in questo caso la TextBox
callvirt class System.String \
[System.Windows.Forms]System.Windows.Forms.Control::get_Text()
//chiama la funzione per prendere il testo dalla textBox.
stloc.0 //inserisce il valore ottenuto nella prima variabile (posizione 0).
ldarg.0
ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox \
my_app.Form1::nome
callvirt class System.String \
[System.Windows.Forms]System.Windows.Forms.Control::get_Text()
stloc.1 //fin qui ha fatto la stessa cosa ma ha usato l'altra textbox
//e l'altra variabile per immagazzinare il valore ottenuto.
ldloc.0 //carica nello stack la prima variabile.
ldloc.1 //carica nello stack la seconda variabile.
call value class [System.Windows.Forms]System.Windows.Forms.DialogResult \
[System.Windows.Forms]System.Windows.Forms.MessageBox::Show(class \
System.String, class System.String) //chiama la funzione MessageBox.
pop
ret

Crackme

Ok, ora che sappiamo qualcosina in più possiamo cimentarci nell'interpretazione del codice di un mio proto-crackme che trovate come allegato a questo tutorial...

Il crackme chiede un nome e un serial per essere registrato, sicuramente (ma va?) il serial viene generato in base al nome inserito, disassembliamo con IDA o con qualche altro programma che supporti il framework andiamo e portiamoci nella routine button1_Click.

Ecco cosa ci appare:

.method private hidebysig void button1_Click(class \
System.Object sender, class [mscorlib]System.EventArgs e)
{
.locals init (class System.String V0,
class System.String V1,
class [mscorlib]System.Text.StringBuilder V2,
int32[] V3,
int32 V4 //Questa variabile sarebbe il contatore usato nel
//ciclo for per generare il codice)
ldarg.0
ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox ForUIC2.Form1::textBox1
callvirt class System.String [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
stloc.0 //Arrivati a questo punto abbiamo il nome
//nella nostra variabile di tipo stringa
ldarg.0
ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox \
ForUIC2.Form1::textBox2
callvirt class System.String \
[System.Windows.Forms]System.Windows.Forms.Control::get_Text()
stloc.1 // Dopo queste altre operazioni abbiamo
// il codice da noi inserito nell'altra
// variabile
newobj void [mscorlib]System.Text.StringBuilder::.ctor()
stloc.2 // Qui crea l'oggetto StringBuilder, in
// c# sarebbe StringBuilder myString =
// new StringBuilder();
ldloc.0
callvirt int32 [mscorlib]System.String::get_Length()
conv.ovf.u2.un // Questa istruzione è molto particolare...
// in pratica come potete osservare
// prima di questa istruzione c'è una
// chiamata ad una funzione get_Legth()
// che si occupa di determinare
// lunghezza di una stringa,
// in questo caso della
// stringa che contiene il nostro nome.
// La funzione conv.ovf.u2.un converte
// il numero dei caratteri della stringa
// in un int16 (u2), ed in più ha la
// funzione di prevenire un overflow (ovf).
newarr [mscorlib]System.Int32 // Qui inizializza l'array che conterrà
// il codice...questo array
// come potete vedere è di tipo int32.
stloc.3 // Ora l'array è inizializzato
ldc.i4.0 // Carica la variabile col nostro nome
stloc.s byte_4
br.s loc_33F // Salta al ciclo for

loc_320:
ldloc.3 // Carica un elemento dell'array
ldloc.s 4 // carica la posizione l'indice
ldloc.0 // carica la stringa
ldloc.s 4 // carica la posizione dell'indice
callvirt char [mscorlib]System.String::get_Chars(int32) //trova il codice
// del carattere contenuto nella string che si
// trova nella posizione indicate dall'indice.
ldc.i4.2 // Carica una costante numerica,
// in questo caso 2
xor // xora il codice del carattere corrente con 2
stelem.i4 // inserisce il risultato nell'array
ldloc.2
ldloc.3 // carica l'array
ldloc.s 4 // carica l'indice
ldelem.i4 // carica il valore dell'array
// indicato dall'indice.
// Questa funzione ha bisogno di 2
// parametri per funzionare,
// perciò prima vengono pushati l'array e l'indice.
// Il valore restituito dalla funzione
// viene inserito nello stack
callvirt class [mscorlib]System.Text.StringBuilder \
[mscorlib]System.Text.StringBuilder::Append(int32) // Questa funzione
// serve per costruire una stringa,
// in questo caso abbiamo aggiunto
// il primo carattere...
pop // non c'è molto da dire...in sintesi
// rimuove l'ultimo elemento
// inserito nello stack
ldloc.s 4 // Push indice
ldc.i4.1 // push del numero 1
add // Incrementa di 1 l'indice
stloc.s 4 // Aggiorna l'indice col nuovo valore

loc_33F:
ldloc.s 4 // Carica il valore corrente dell'indice
ldloc.0 // Carica la stringa...
callvirt int32 [mscorlib]System.String::get_Length()
blt.s loc_320 // Se l'indice è più piccolo della
// lunghezza della stringa salta
ldarg.0
ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox \
ForUIC2.Form1::textBox2
callvirt class System.String \
[System.Windows.Forms]System.Windows.Forms.Control::get_Text()
ldloc.2 // legge il serial da noi inserito
callvirt class System.String [mscorlib]System.Text.StringBuilder::ToString()
call bool [mscorlib]System.String::op_Equality(class \
System.String, class System.String)
brfalse.s loc_373 // Salta se la chiamata alla funzione
// precedente restituisce un
// valore di tipo booleano FALSE.
ldstr "Registrato! Bravo!"
ldstr "Pbdz Crackme"
call value class [System.Windows.Forms]System.Windows.Forms.DialogResult \
[System.Windows.Forms]System.Windows.Forms.MessageBox::Show(class \
System.String, class System.String)
pop
br.s loc_383

loc_373:
ldstr "Codice sbagliato!!"
ldstr "Pbdz Crackme"
call value class [System.Windows.Forms]System.Windows.Forms.DialogResult \
[System.Windows.Forms]System.Windows.Forms.MessageBox::Show(class \
System.String, class System.String)
pop

loc_383:
ret
}

Questo è quanto, spero di essere stato abbastanza chiaro! Se avete qualche difficoltà non esitate a contattarmi!


Note Finali

Ringrazio Quequero perchè mi da la possibilità di esprimermi grazie al suo sito! Un saluto a tutti quelli che credono che programmare significa saper usare "word". Un saluto a tutti quelli che il 29 giugno saranno a Padova a vedere i Metallica! Ah, dimenticavo, ringrazio mia cugina per avermi prestato il portatile!


Disclaimer

I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.

Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevoli e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.