C# Framework.NET Reversing - Episodio 2
(I.L. Craking)

Data

by "Pbdz"

 

14/giugno/2004

UIC's Home Page

Published by Quequero

Forza Azzurri!!

Grazie mille pbdz, come al solito hai scritto un ottimo tutorial!

Trullallerò trullallà!

....

Home page: http://pbdz.cjb.net

E-mail: [email protected]

....

Difficolt�

( )NewBies (x)Intermedio ( )Avanzato ( )Master

 

Non avete capito niente del tutorial precedente? Mi fa piacere! Vi do un'altra possibilità! ;-)


C# Framework.NET Reversing - Episodio 2

I.L. Cracking
Written by Pbdz

Introduzione

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 usati

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

 

URL o FTP del programma

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 po� pi� approfondita perch� il primo tutorial forse era un po� troppo sintetico, ma sapete�sono quelle cose che scrivi di getto, senza pensare troppo!!!

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 soma/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. E� 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 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

�

Ok, ora che sappiamo qualcosina in più possiamo cimentarci nell'interpretazione del codice di un mio proto-crackme che lo 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)

��������������������������������������� // DATA XREF: InitializeComponent+1F1r

� {

��� .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 ache 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:������������������������������� // CODE XREF: button1_Click+6Fj

��� 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:������������������������������� // CODE XREF: button1_Click+81j

��� 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

Qui inserirete con questo carattere il vostro piccolo disclaimer, non � obbligatorio per� � meglio per voi se c'�. Dovete scrivere qualcosa di simile a: 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