- Prima di iniziare ricapitoliamo quello che
abbiamo imparato nel precedente tutorial:
Sui nostri processori sono presenti 4 General Purpose Registers (GPR) che si
chiamano:
- EAX, EBX, ECX, EDX, sono grandi 32-bit (4 byte) e possono contenere tutti
numeri fino a 32-bit di grandezza.
- I GPR possono essere suddivisi in sottoelementi da 16 e 8 bit: EAX -> AX
(16-bit), AH (8-bit), AL (8-bit). EBX -> BX, BH, BL etc...
- Gli altri registri: ESI, EDI, ESP, EBP, sono grandi 32-bit e possono essere
suddivisi solo in sottoelementi da 16-bit: ESI -> SI, ESP -> SP etc...
- Se diciamo che un registro "punta al seriale", vuol dire che contiene
un numero che indica l'indirizzo di memoria al quale si trova il seriale.
- Se vogliamo prelevare un byte dall'indirizzo al quale punta un registro e
metterlo altrove, dobbiamo usare le [], esempio: MOV AL, [EBX].
- MOV copia un valore in memoria o in un registro: MOV EAX, 2 -> Copia 2
in EAX, MOV AL, [ECX] -> Copia in AL il byte puntato da ECX
- TEST EAX, EAX o anche TEST ECX, ECX... Mette a (setta) 1 lo ZeroFlag se il
registro e' uguale a 0.
- CMP EAX, EBX confronta EAX con EBX (potete usarlo su ogni registro) e mette
lo ZeroFlag a 1 se i due operandi sono uguali.
- JNZ/JNE JZ/JE effettuano un salto rispettivamente se: lo ZeroFlag e' 1, lo
ZeroFlag e' 0. JMP salta sempre.
- CALL esegue una sottoroutine (in pratica esegue una funzione).
- ADD EAX, 2 aggiunge 2 a EAX; SUB EDX, 3 sottrae 3 a EDX.
- Il breakpoint indica al Debugger dove fermarsi all'interno del codice del
programma.
- Premendo F8 all'interno di OllyDbg siamo in grado di eseguire una alla volta
le istruzioni che vediamo (Stepping).
A questo punto le fondamenta dell'assembly sono piantate, mancano due istruzioni
fondamentali che avremo modo di vedere piu' tardi, ora crackiamo questa bestiola.
Unzippate WinImage, aprite OllyDbg e caricate WinImage.exe, premete il pulsantino
play una volta, vi apparira' un NagScreen col pulsante OK... Premete anche quello,
clickate su Options e poi Registering, ci viene chiesto un nome e un codice
di registrazione. Mettete il solito nome e codice fittizi (io usero' Quequero
e 666111666) ma non premete ok.
Allora, come si fa ad entrare nel programma? Stavolta utilizzeremo un approccio
differente dal primo tutorial, l'altra volta abbiamo detto al debugger di fermarsi
su MessageBox e siamo tornati indietro, stavolta invece faremo il contrario:
diremo di fermarsi subito e proseguiremo fino alla messagebox :).
Le caselle in cui scrivete il testo si chiamano EditBox e si usano le solite
API per prendere il testo da li, queste API in genere sono: GetWindowText e
GetDlgItemText... Proviamo con la prima, premete Alt+F1 per aprire la CommandLine
di Olly (se non fosse gia' aperta) e scrivete:
bpx GetWindowTextA (ricordate di stare attenti alle maiuscole e di
inserire la A finale), appen fatto clickate su OK... Uhm... Appena clickate
sulla finestra di WinImage il debugger non brekka... Strano vero? Ok, allora
premete Alt+B e con Del (o Canc) cancellate tutti i breakpoint presenti nella
BreakPoints window, premete Play per riavviare l'esecuzione del programma
e tornate alla CommandLine, fate: bpx GetDlgItemTextA, tornate nella finestra
di registrazione e premete OK.
Ottimo il debugger brekka, se si apre la finestra Intermodular Calls, non vi
preoccupate... Chiudetela e tornate alla finestra principale col codice.
Bene ci siamo fermati un attimo prima che il nostro nome venga prelevato, se
tutto e' andato bene dovreste star qui:
00422045 PUSH 101 ; Count = 101 (257.)
0042204A MOV EBX, winimage.00453B90
0042204F PUSH EBX ; Buffer = winimage.00453B90
00422050 PUSH 816 ; ControlID = 816 (2070.)
00422055 PUSH [ARG.1] ; hWnd = 002A0406 ('WinImage Registration',class='#32770',parent=00620070)
00422058 CALL ESI ; GetDlgItemTextA (noi siamo fermi qui)
Andiamo quindi sul
solito msdn.microsoft.com o sulla vostra apireference (win32.hlp) e vediamo
come funziona l'API GetDlgItemText:
UINT GetDlgItemText(
HWND hDlg,
int nIDDlgItem,
LPTSTR lpString,
int nMaxCount
);
Allora, il primo parametro e' l'handle del DialogBox, ovvero il numero che identifica
in memoria la finestra.
Il secondo parametro e' il numero che identifica la nostra EditBox (se abbiamo
due EditBox in una finesta, magari una si chiama 1 e l'altra 2, questo parametro
serve a indicare da quale editbox vogliamo prelevare il testo), il terzo e'
il buffer che conterra' il testo prelevato (fondamentale!) e il quarto e' il
numero massimo di caratteri da prelevare, la nostra chiamata tradotta in C sarebbe:
GetDlgItemText(0x002A0406, 0x816, 00453B90,
0x101);
I numeri preceduti dal '0x' sono intesi in esadecimale, dovete pero' sapere
che gli handle in linea di massima variano di continuo, quindi se ora l'handle
della finestra e' 0x002A0406 al prossimo avvio quasi certamente sara' diverso!
Bene, adesso con F8 andiamo avanti di una riga e facciamo all'interno della
CommandLine: "d ebx", per verificare cosa e' stato messo nel buffer...
Guardiamo in basso e troviamo:
00453B90 51 75 65 71 75 65 72 6F 00
00 65 00 72 00 6F 00 Quequero..e.r.o.
Bene, la chiamata preleva il nome, ho evidenziato un byte per un motivo preciso,
dovete sapere che per convenzione le stringhe vengono terminate dal byte 0,
quindi il primo 0 che incontrate di sicuro termina la stringa (sotto dos si
usava terminarle col carattere $ detto proprio terminatore). Ok, steppiamo con
F8 fino alla seconda GetDlgItemText:
0042205A PUSH 7F ;
Count = 7F (127.)
0042205C MOV EDI, winimage.00453F00
00422061 PUSH EDI ; Buffer = 0012F038
00422062 PUSH 817 ; ControlID = 817 (2071.)
00422067 PUSH [ARG.1] ; hWnd = 002A0406 ('WinImage Registration',class='#32770',parent=00620070)
0042206A CALL ESI ; GetDlgItemTextA (se siete stati bravi...
Vi trovate qui :)
-
-
Ok
stavolta il buffer sta in EDI che punta all'indirizzo: 0012F038 (non vi preoccupate
se da voi e' diverso), quindi steppate una riga dopo l'indirizzo 0042206A e
dumpate EDI con: d edi, ci troverete:
00453F00 36 36 36 31 31 31 36 36 36
00 00 00 36 00 36 00 666111666...6.6.
Come sospettavamo...
Viene prelevato il seriale (nel caso aveste dei dubbi sappiate che il primo
parametro: 00453F00 e' l'indirizzo di memoria al quale si trova il primo byte
del seriale, i numeri 36 36 36 etc sono la rappresentazione esadecimale del
nostro seriale e 666111666 e' il nostro seriale in ASCII standard). Una curiosita':
perche' invece di CALL GetDlgItemTextA c'e' un CALL ESI? Perche' ESI guardacaso
punta proprio all'indirizzo della nostra API quindi se conosciamo l'indirizzo
dell'API possiamo metterlo in un registro e chiamarla cosi invece che col suo
nome esteso, carino vero? Ok a questo punto esaminiamo il codice:
0042206C PUSH winimage.0045386C
; Dovreste trovarvi qui
00422071 PUSH EDI
00422072 PUSH EBX
00422073 CALL winimage.0044208A ; Calcola il Seriale
00422078 MOV ECX, DWORD PTR [45386C]
-
Steppate fino all'indirizzo
00422073 e fate: "d edi" e poi "d ebx" e subito dopo c'e'
una chiamata ad una subroutine... Come potete vede edi contiene il nostro seriale
e ebx il nostro nome... Vuoi vedere che quella CALL calcola il numero che ci
interessa??
E' giunto allora il momento di spiegare le ultime due istruzioni fondamentali:
PUSH e POP :)
La maggior parte delle CALL ha bisogno di "argomenti" per essere eseguita,
nel caso di GetDlgItemText sono 4: l'handle, il buffer etc... Vi faccio un esempio,
devo realizzare una call che faccia la somma di due numeri, la progetto in modo
che abbia bisogno di due parametri, il primo e il secondo addendo, lei poi tornera'
(attenti a questo termine) il risultato, immaginatevi che sia fatta cosi:
Somma(1, 4);
La chiamata ha quindi bisogno di due parametri, nel nostro caso 1 e 4 e nessuno
ci vieta di passarle 6 e 5... Dovete sapere che esistono due modi per passare
parametri ad una sottoroutine, uno e' metterli nei registri... Ma se i registri
non bastano?? Allora mettiamo i dati in memoria e la sottoroutine li prendera'
dalla memoria e li elaborera', quando avra' finito dovra' pur mettere il risultato
da qualche parte e questo risultato finira' o in EAX oppure anch'esso in memoria,
ma non abbiate paura e' facile capire dove finisce.
Nel 99% dei casi i parametri saranno passati tramite lo stack, cioe' la memoria.
Quando eseguite un programma il sistema operativo apre il file, lo copia tutto
nella RAM (se ci entra) e lo esegue.. Questo perche' l'harddisk e' estremamente
lento mentre la ram e' piu' veloce, la memoria si chiama Stack che significa
Pila... Come la pila dei piatti, e questo per un motivo: i dati entrano nell'ordine
inverso in cui escono, pensate ai piatti, se ho 10 piatti e li metto uno sopra
all'altro il primo che potro' prendere sara' l'ultimo che ho messo giusto?
PiattoTerzo
PiattoSecondo
PiattoPrimo
Il "disegno" dovrebbe chiarivi i dubbi :). Questo tipo di "pila"
viene detta LIFO, ovvero: Last In First Out (l'ultimo che entra e' il primo che
esce), ok e per mettere dati dentro la memoria usiamo un'istruzione: PUSH, per
togliere dati invece usiamo POP, facile. Attenzione pero', essendo lo Stack una
Pila, dobbiamo salvare i dati nell'ordine inverso... Immaginate che io debba
salvare: 1, 2 e 3 su una pila, se facessi PUSH 1, PUSH 2 e PUSH 3 in memoria
starebbero cosi:
3
2
1
Perche' lo stack e' LIFO, allora non potrei recuperarli in ordine, se pero' facessi:
PUSH 3, PUSH 2, PUSH 1, allora avrei:
1
2
3
Potrei allora POParli fuori in ordine, mi rendo conto che non e' semplice...
Ma voi pensate ai piatti :)
Ok, finiamo qui la digressione, avrete tempo e modo di capire lo stack piu' in
la, riprendiamo dunque il nostro codice:
0042206C PUSH winimage.0045386C
; Dovreste trovarvi qui
00422071 PUSH EDI ; EDI punta al seriale (sullo stack
viene messo il valore che contiene EDI ovviamente)
00422072 PUSH EBX ; EBX punta al nome
00422073 CALL winimage.0044208A ; Calcola il Seriale
00422078 MOV ECX, DWORD PTR [45386C]
Vengono salvati sullo stack tre argomenti, un indirizzo di memoria che non sappiamo
cos'e' (sono 4 byte a 0, forse serve come appoggio per qualche calcolo), l'indirizzo
contenuto in EDI (che punta al seriale) e quello contenuto in EBX (che punta
al nome). Tanto per complicarvi la vita sappiate che lo stack cresce al contrario,
cioe' cresce verso valori numericamente minori, vuol dire che quando e' totalmente
vuoto lo stack inizia a 0xFFFFFFFF (che e' il valore piu' grande che puo' assumere
un registro a 32-bit), quando e' tutto pieno finira' invece a 0x00000000, non
ve lo dico per confondervi ma devo spiegarvi cosa succede. Immaginate un lampadario
attaccato al soffitto tramite un elastico, la cima dello stack sara' il lampadario,
la base dello stack sara' dove il cavo si infila nel muro :), se tirate il lampadario
(fate crescere lo stack) la CIMA si avvicinera' a terra e il lampadario sara'
cresciuto in altezza verso il basso :)... Ora sappiamo che:
EDI = 00453F00 (ovvero l'indirizzo di memoria dove
si trova il seriale)
EBX = 00453B90 (l'indirizzo di memoria dove si trova
il nome)
Questi numeri non li sto tirando a caso, potete vederli nella finestra di destra
di Olly :))). Un'altra novita', il registro ESP punta alla CIMA dello stack (SP
sta infatti per Stack Pointer), e' per questo che vi ho fatto l'esempio del lampadario
proprio per introdurvi il registro ESP. Quindi steppiamo con F8 fino all'indirizzo
00422073, in questo preciso istante lo stack sara' cosi formato:
ESP+8 ->
0045386C ; Il buffer
ESP+4 -> 00453F00
; Il Serial
ESP -> 00453B90 ;
Il nome
Se non ci credete
dumpate lo stack, "d esp":
0012EFA8 90
3B 45 00 00 3F 45 00 6C
38 45 00 38 F0 12 00 ;E..?E.l8E.8ð.
Uhm non li vedete?
Beh in realta' ci sono... Ma sono scritti ribaltati (si tanto per complicarvi
la vita, ma non date la colpa a me datela alla Intel :), questa e' detta notazione
LittleEndian, cioe' il byte meno significativo e' il primo :) percio' se salviamo
sullo stack:
12 34 56 78 -> diventera': 78 56 34 12
Farete l'abitudine anche a questo col passar del tempo. Ok quindi abbiamo sullo
stack tre parametri, non ci importa che sono messi ribaltati tanto quando andremo
a riprenderli dallo stack saranno dritti, ora dobbiamo entrare nella subroutine
chiamata all'indirizzo 00422078, e questo e' quantomai semplice, se ancora non
ci siete portatevi con F8 fin sopra la CALL e quindi premete F7, zap, tramite
un warp siamo arrivati qui:
0044208A PUSH EBP ;
Voi siete qui
0044208B MOV EBP, ESP
0044208D SUB ESP, 200
00442093 PUSH ESI ; USER32.GetDlgItemTextA
00442094 MOV ESI, [ARG.3]
00442097 TEST ESI, ESI ; USER32.GetDlgItemTextA
00442099 PUSH EDI ; winimage.00453F00
Finiamo quindi di spiegare la call, immaginate questo pezzo di codice:
00400000 CALL 00400006
00400004 MOV EBX, EAX
00400006 MOV EAX, EBX
00400008 RET
Arrivati all'indirizzo 00400000 il processore salva sullo stack il registro
EIP (che ' un registro non modificabile dagli utenti e che contiene sempre l'indirizzo
dell'istruzione che si sta eseguendo, provate a guardarlo dentro OllyDbg mentre
steppate), quindi salta all'indirizzo 00400006, esegue l'istruzione e appena
arriva sull'istruzione RET preleva dallo stack l'EIP che avevamo salvato prima,
lo incrementa e fa un jump sul nuovo EIP (ovviamente EIP viene incrementato altrimenti
il processore salterebbe di nuovo sulla call e la eseguirebbe all'infinito),
il percorso sarebbe quindi: 00400000 -> 00400006 -> 00400008 -> 00400004,
e poi ipotizziamo che si fermi. La CALL quindi e' un biglietto di andata e ritorno
(perche' sa sempre dove tornare) mentre il JMP e' di sola andata... Perche' salta
e non torna.
Come vedete trovare la fine di una subroutine e' facile in quanto basta trovare
il RET. Ok, a questo punto, senza steppare, guardate un pochetto il codice, dovreste
trovare delle call a strcmp, questa funzione (che non e' un'API ma una funzione
standard del C) serve a confrontare due stringhe tra loro, e mette EAX = 1 se
le stringhe sono diverse, altrimenti mette EAX = 0 se le stringhe sono uguali,
ora che sappiamo come funziona una call andiamo a vedere cosa viene confrontato,
steppate con F8 (F7 si usa solo quando dovete ENTRARE in una call) fino all'indirizzo
004420DE:
004420DB POP ECX
004420DC POP ECX
004420DD PUSH EAX
004420DE CALL <JMP.&CRTDLL.strcmp> ; strcmp,
voi siete qui
Uhm, qualcosa
non quadra, vi avevo detto che strcmp prendeva due parametri, la prima e la
seconda stringa... Qui vediamo un push solo prima della chiamata... Ci sta fregando?
Verifichiamo, EAX viene salvato sullo stack, vediamo cosa contiene: "d
eax":
0012EDA0 45 31
38 46 33 31 00 00 40 10 01 A5 00 00 00 00 E18F31..@¥...
Un numerello... Non ci interessa molto, per quello che e' scritto in verdino
sbiadito ;ppp, e l'altro parametro? Beh a rigor di logica se non c'e' un push
stara' gia' sullo stack giusto? Verifichiamo: "d esp+4":
0012ED94 A0 EE
12 00 00 3F 45 00 8D D7 CF 77 45 31 38 46 î..?E.×ÏwE18F
Vi dice niente quel numerello?? Provate a ribaltarlo e dumpatelo: "d 0012EEA0":
0012EEA0 36 36 36 31 31 31 36 36 36
00 CD 77 00 00 00 00 666111666.Íw....
Eh eh massi e' un nostro caro amico... Il serial fittizio (speriamo non se la
prenda a male se lo chiamiamo cosi :)), bene, il nostro serialone viene confrontato
con un numerino verde-sbiadito... E poi che succede?
004420DE CALL <JMP.&CRTDLL.strcmp>
; strcmp, voi siete qui
004420E3 TEST EAX, EAX
; Se EAX = 0, cioe' se le due stringhe sono uguali...
004420E5 POP ECX ; 0012EDA0
004420E6 POP ECX ; 0012EDA0
004420E7 JE winimage.0044218D
; ... Salta all'indirizzo 0044218D
C'e' un jump-uzzo, quindi se strcmp ritorna 0 in eax (ovvero le due stringhe
sono uguali), salta... Vuoi vedere che se cambiamo quel JE in JMP abbiamo una
registrazione bella e pronta??? Facciamo una prova, steppiamo fino a 004420E7
(o selezionate quella riga col mouse), premiamo la barra spazio e scriviamo:
JMP 0044218D, assicuratevi che la casella "Fill with NOP's" sia checkata
(NOP non lo conoscete? Argh ci vorrano almeno 500 pagine per spiegarvi cosa
fa... Dunque... Avete presente i politici in italia? Ecco, cosa fanno quelli?
Nulla? Esatto! NOP e' come loro non fa assolutamente nulla, significa infatti:
NO oPeration, non fa niente quindi ;p) e come per magia il codice da:
004420E7 0F84
A0000000 JE winimage.0044218D
Diventera':
004420E7 E9 A1000000
JMP winimage.0044218D
004420EC 90 NOP
Bene, premete Play e vedete cosa succede.. Ihihih siamo registrati :)))...
Ma fermi un secondo... Se disabilitate i breakpoints e riavviate il programma..
Siamo di nuovo in modalita' UNregistered, perche'? Beh il programma all'avvio
ha ricontrollato il seriale, ha visto che l'avevamo fregato e ci ha beccati...
E poi la modifica che abbiamo fatto era in memoria non sul file vero percio',
sperando che la routine di controllo sia solo una (ma tanto volte non e' cosi),
andiamo a modificarla per davvero, come si fa? Presto detto, aprite winimage.exe
con il vostro HexEditor preferito, dobbiamo trovare gli opcode che abbiamo cambiato
ovvero: 0F84 A0000000,
ma se cercate nell'hexeditor ce ne saranno a palate, percio' facciamo una cosa,
prendiamo un po di opcodes prima e un po' dopo :), quindi all'indirizzo 004420E7
clickate col tasto destro del mouse e fate "Follow in Dump | Selection",
in basso vedrete i byte che si trovano a quell'indirizzo:
004420E7 0F 84
A0 00 00 00 8D 85 00 FF FF FF 50 8D 87 48
„ ...….ÿÿÿP‡H
004420F7 19 05 14 50 8D 85 00 FE FF FF 50 E8 37 FF FF FF P….þÿÿPè7ÿÿÿ
00442107 59 59 50 E8 6B 02 00 00 85 C0 59 59 74 78 8D 85 YYPèk..…ÀYYtx.
Copiate quelli che ho evidenziato... Dovrebbero essere abbastanza per essere
unici all'interno del file, pastateli nel search (levate gli spazi mi raccomando)
e premete Search (se usate HexWorkshop assicuratevi che facciate la ricerca
in "Hex Values" e quando vi chiede di fare il backup rispondete di
si), chiudete OllyDbg altrimenti non potrete salvare il file, e cambiate quel:
0F84A0000000 in E9A100000090,
raga mi raccomando... Dovete cambiare i byte non INSERIRNE di nuovi :) altrimenti
non funzionera' piu' nulla :))).
A questo punto se avete fatto tutto bene... Il programma e' crackato, potete
utilizzarlo come volete... Ma questo seriale davvero non vogliamo trovarlo???
Eddai non fate i pigri :)... Se riflettete vengono fatti un sacco di strcmp
con vari numeri, vuoi vedere che alla fine di tutto verra' confrontato il nostro
seriale con quello giusto? Boh proviamo, arrivate fin qui:
004421AC POP ECX
004421AD POP ECX
004421AE PUSH EAX ; s1 = "2E7DBCB"
004421AF CALL <JMP.&CRTDLL.strcmp> ; strcmp,
voi siete qui!
Guardate, non dobbiamo neanche scomodarci a cercarlo, ce lo dice direttamente
Olly, fate: "d eax":
0012EDA0 32 45
37 44 42 43 42 00 00 10 01 A5 00 00 00 00 2E7DBCB..¥....
Eh eh :) levate tutti i breakpoint, rimettete il file originale e mettete questo
seriale (ovviamente col vostro nick sara' diverso!)... Wow registrati senza
neanche crackare il programma... Che storia 'sto Reversing :). Ora siete pronti
per guardare altri tutorial, buona fortuna!
-
-
Quequero
Prima di tutto grazie ad evilcry per avermi
suggerito in MailingList questo programma come target del tutorial, un saluto
a tutte le MailingLists della UIC, a AndreaGay, Pincopall, Fobbo, Iro, Mary
:*, Artemis :***, Spider, korn, DJK, s1mo e gli altri che non faccio a tempo
a salutare perche' devo letteralmente fuggire ciaooooooooo....
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 che ogni sviluppatore
ha dovuto portare avanti per fornire ai rispettivi consumatori i migliori prodotti
possibili.
Reversiamo al solo scopo informativo e per
migliorare la nostra conoscenza del linguaggio Assembly.