PTGui 4.1 | ||
Data |
by "Zero_G" |
|
25/02/2005 |
Published by Quequero | |
"Che gli uccelli dell'ansia e della preoccupazione volino sulla vostra testa, non potete impedirlo; ma potete evitare che vi ci costruiscano un nido." Proverbio Cinese |
Grande ze, e' sformattato e hai lascianto tutti i link relativi, pero' almeno e' un sacco bello :))) |
"Ma chi sei tu che avanzi nel buio della notte ed inciampi nei miei più segreti pensieri?" W. Shakespeare |
|
|
|
Difficoltà: |
( )NewBies ( )Intermedio (X)Avanzato ( )Master |
Introduzione |
Se volete sapere qual'è lo stato dell'arte in questo campo, c'è un tizio di Cambridge che ha sviluppato un software in grado di fare stitching automatico senza bisogno di input da aprte dell'utente: AutoStitch (purtroppo disponibile solo in versione demo, per adesso) ; veramente eccezionale nella qualità e molto interessante da punto di vista teorico, soprattutto l'articolo tecnico che c'ha scritto sopra che vi consiglio di leggere (curiosità: la M$ si è comprata la sua tecnologia ed ha deciso di integrarla nelle prossime versioni di Windows! guardate qui e qui... ;-) )
Ho studiato un po' in qua ed in là ed ho visto che, in quanto a qualità dei risultati, però, il miglior software in circolazione è, sorpresa sorpresa, freeware! :-) Si chiama Panorama Tools, e rappresenta una suite di utilità che eseguono l'allineamento e lo stitching di foto fatte a diverse angolazioni delo stesso panorama (ottime sono anche le utilità di supporto AutoPano ed Enblend, tutte free! :-D).
L'unico problema è che questi programmi lavorano con opzioni a riga di comando e quindi una GUI è fondamentale per poterli usare; se siete interessati, in giro ce ne sono tante, e le più famose sono (non vi riporto i link, tanto sono tutti www.nome-programma.com):
Attenzione! sperimento per la prima volta questa tecnica di socio-reverso-comunicazione (ahahah!): le funzioni importanti sono sottolineate ed i punti fondamentali da seguire sono in neretto così che vi resti più semplice seguire a colpo d'occhio i passi più importanti del tutorial; spero che vi sia d'aiuto, altrimenti me lo dite e la prossima volta scrivo diversamente.
Tools usati |
URL o FTP del programma |
Essay |
Partiamo con il reversing...
Una prima analisi
Tutti pronti? OK! Dopo aver installato PTGui,
recatevi nella sua directory sotto Programmi e date in pasto l'eseguibile
principale (ptgui.exe) a PEiD per vedere con
chi abbiamo a che fare stavolta... riconoscere compilatori e
packers è un passo fondamentale.
c'è andata di lusso: niente packer
astrusi, solo un banalissimo Borland C++ un po' vecchiotto; tutte le
sezioni sembrano a posto ed anche l'analisi sull'entropia sembra confermare
l'assenza di compressioni. :-)
NB: la precedente versione 4.0b di PTGui era packata manualmente ed anche parecchio tosta da ricostruire... eheh, attenti programmatori, mai abbassare la guardia! ;-p
Prima di aprire la nostra cassettina degli attrezzi, osserviamo le mosse della nostra vittima: appena lanciato, si presenta una finestra che ci intima di registrarsi entro 30 giorni ed il bottone OK rimane grigio per una decina di secondi; premiamo su "Register..." e ci appare la consueta mascherina Name/Serial. Se proviamo ad inserire un nome ed un numero a caso vedrete che l'OK non si abilita fino a che il seriale non è composto da almeno 81 caratteri; teniamolo a mente, ci potrebbe servire più avanti...
Premiamo OK ed appare il MessageBox d'errore "The registration code is not valid. Type your name exactly...(blah blah)"; appuntiamoci anche questo.
Beh, come analisi di massima può bastare, abbiamo già diverse informazioni:
Vorrei approfittare di questo tutorial per farvi notare
quanto è utile l'impiego sinergico di un debugger ed un
disassemblatore, OllyDbg ed IDA, in questo caso; per avere più
appoggi possibile, di solito io faccio sempre così: carico l'eseguibile in
IDA e, mentre lavora, apro OllyDbg per iniziare intanto il
debugging così, al momento del bisogno, mi carico il file .MAP appena è pronto
per avere i nomi delle funzioni e possibilmente qualche reference in più.
Se
questo non è il vostro modus operandi standard, potrebbe essere
un'occasione per provare qualcosa di nuovo, no? ;-)
NB: se non riuscite a procurarvi IDA o comunque avete problemi a creare il file .MAP (e che cavolo! quasi 10 minuti di analisi!), da qui potete scaricarvi direttamente il file .UDD per OllyDbg che ho fatto io dal .MAP di IDA.
Facciamoci aiutare da IDA (che in genere ne sa più di noi sull'assembler...)
Appena terminata l'analisi di IDA, scegliete "FILE --> Produce file... --> Create MAP file...", salviamo in ptgui.map e abilitiamo i 3 check box (Segmentation Information, Autogenerated Names, Demangled Names) in modo da avere maggiori info; da dentro OllyDbg, recatevi in "Plugins --> GODUP Plugin --> MAP Loader --> Load Labels/Load Comments" e per entrambi browsate fino al file creato poco fa, così avrete sia le label che i commenti forniti dalla cara IDA. Se date un'occhiata alle call in giro, vedrete che nel disassemblato molte di esse sono passate da criptiche chiamate numeriche tipo "call 004C23B8" a più esplicative "call Advgrid::TCellGraphic::GetPictureSize"! :-D
Un risultato simile l'avremmo ottenuto anche senza
scomodare IDA, ma direttamente con la funzione "load IDA signature" di GODUP,
puntando manualmente ai signature files "bh32rw32.sig"
e "b32vcl.sig" dentro la cartella "SIG" di
IDA.
Nonostante IDA abbia già fatto un buon lavoro, vi consiglio di
lasciarla aperta, così, se vi serve avere qualche dettaglio in più, potete
sempre controllare (dandogli del femminile, può suonare un po' ambiguo... ;-p)
Ricerca per
stringhe
Innanzitutto direi di partire dall'informazione certa:
il messaggio d'errore per il seriale sbagliato.
Premendo con
il tasto destro in OllyDbg, scegliete "Search for... --> All Referenced Text
Strings" per avere la lista delle stringhe; se avete modo di procurarvelo,
potete usare anche il potente plugin Ultra String Reference, che
individua di tutto di più... unico problema, lo trovai un giorno su un sito
giapponese, ma prima possibile lo voglio uploadare sul sito così potete
scaricarlo da lì.
Ancora con il tasto destro (utile, vero?) scegliete "Search fo text" e scrivete "The registration code", tanto è sufficiente; troverete queste righe (ometto per brevità il disassemblato, riporto solo l'offset):
|
Eheh, scometto che i più attenti sentiranno già puzza di GOOD/BAD Boy jump... hey, ma quella stringa piena di strani simboli cos'è?? (se li contate, sono poco più di 81...) ;-)
Facendo doppio click sulla riga sottolineata, vi ritroverete a 0041988C; scorrete un centinaio di righe più in alto e vi accorgerete di essere nel bel mezzo di _TRegistrationForm_OKButtonClick() (grazie IDA!), cioè nella procedura che viene eseguita quando premete sul bottone OK del form di registrazione. La 'T' davanti ci suggerisce inoltre che abbiamo a che fare col caro Delphi, quindi anche il potente DeDe ci poteva essere d'aiuto (a volte non riesci a disassemblare un cavolo, altre volte 4 tools ci riescono contemporaneamente... mah!)
Piazzate un bel breakpoint all'inizio della procedura (la riconoscete bene, perché dopo il RETN precedente c'è sempre qualche NOP di riempimento), più precisamente all'indirizzo 00419704; riavviate il debugger con ALT+F2 e poi premete F9 per lanciare PTGui. Quando appare il Nag Screen entrate in "Register..." e scrivete i soliti nome/seriale farlocchi, ricordandovi di inventarvi almeno 81 caratteri, dopodiché premete OK.
TADAH! :-)
OllyDbg fa la
sua entrata sul palcoscenico pronto a steppare per voi... :-)
Cominciamo con il
debugging
Come potete notare dai nomi delle CALL, molte istruzioni contengono le funzioni Delphi per inizializzare lo stack e roba del genere (di sicuro non ha il mostruoso overhead del compilatore VB, ma poco ci manca... ;-p); steppate fino a 00419768, dove troverete un bel GetText(void): probabilmente adesso vi aspettereste di veder passare da qualche parte nei registri il vostro nome o il seriale, e invece, una premuto F8, non trovate niente... come mai? Semplicemente perché questa è un'ulteriore dimostrazione che il Delphi non è un linguaggio veloce! ;-p
Se si trattava di C++, avreste fatto bene a guardare nei pressi di EAX, perché sicuramente il passaggio di valori sarebbe avvenuto tramite registri, in quanto sono molti più veloci delle altre memorie, ma siccome le chiamate Delphi sono fondamentalemente dei wrappers sulle API di Windows, spesso il compilatore genera codice che si appoggia allo Stack per salvare i risultati in uscita dalle CALL, proprio per mancanza di spazio dovuto alle lunghe pile di chiamate.
Quindi, dove dobbiamo guardare? ve l'ho appena
suggerito! ;-p
In basso a destra potete ammirare l'immagine dello
Stack centrata sullo Stack Pointer (il registro ESP contiene
l'indirizzo della cima dello stack, mentre EBP (Base Pointer) punta alla fine
dello spazio riservato per la funzione corrente); a che serve lo spazio dello
stack con indirizzo maggiore di ESP, direte a voi? a memorizzare le variabili
locali delle funzioni: non per niente, IDA identifica queste variabili con i
nomi progressivi var_4, var_8, var_C, ... , proprio per
ricalcare la loro posizione a partire dalla cima dello stack; ricordatevi che
nello stack si sale facendo decrescere il valore del puntatore, quindi ESP-8 è più in alto di ESP-4.
NB: ho volontariamente omesso i valori degli indirizzi per non confondere troppo le idee, tanto qui non servono.
<< STACK >>
|
Torniamo a noi, anzi, al nostro nome, che per l'appunto si trova nello Stack, 6 "slot" più in alto dell'ESP (esercizio per casa(!): vi consiglio di entrare (F7) nella CALL del GetText() per vedere in che modo opera questa funzione e come fa a scrivere nello Stack (in EAX infatti ci rimane solo la lunghezza della stringa); se invece avete premuto F8, vi ritroverete a 0041976D.
NB: fate attenzione al fatto che ogni step OllyDbg ricentra la visuale dello Stack sull'ESP, quindi dovreste tute le volte riscorrerlo verso l'alto; per quest'evenienza esiste l'opzione "Lock Stack": premete con il destro sullo Stack ed attivatela e ricordatevi di disattivarla quando non ne avete più bisogno. Inoltre, se premete sempre con il destro sull'indirizzo dello Stack dove compare il nome e scegliete "Follow in Dump", in basso a sinistra vedrete il dump.
|
Proseguite con gli step fino a 004197A2, dove il vostro nome sembra essere apparentemente spazzato via dalla "unknown_libname"; infatti subito dopo non lo vedete più nello Stack. Vabbè, facciamo appello ad un po' di Zen e proviamo ad andare avanti lo stesso e vedere che succede...
........
|
Se avete sempre lo stack bloccato, appena premete F8 sull'istruzione 004197B3, vedrete comparire il seriale immesso a ESP-24h (se non lo vedete subito, scorrete un pochino verso l'alto); non è comparsa nuovamente la chiamata esplicita a GetText(void) probabilmente a causa di alcune ottimizzazioni del compilatore... imparate a riconoscere le funzioni che catturano le stringhe non solo per il nome che hanno ma anche per quello che effettivamente fanno! (altro esercizietto: entrate dentro la CALL e seguitene il procedimento)
Le successive istruzioni da 004197B6 a 004197D4 ricalcano lo schema precedente (da 0041976D a 004197CF), mentre subito dopo (a 004197D4) c'è un bel PUSH ed una CALL; fermatevi un secondo su questa funzione... che cavolo ha pushato se nello Stack non è comparso altro che un numeretto? perché non si vedono stringhe? eppure scorrendo in basso il disassemblato basta un colpo d'occhio per capire che di lì a poco verrà controllato l'esito di un check sul seriale (controllate le stringhe che abbiamo visto prima nelle references!)...
Cosa fare in questi casi, allora? :-/
Dove vanno a finire
tutte le stringhe?
Semplice: chi vi ha detto che le stringhe sono gli
unici dati che possono essere passati alle funzioni critiche? nessuno! ;-)
Prima di entrare nella funzione (siete sempre fermi a 004197D7, giusto?),
diamo un altro sguardo allo Stack...
<< Stack >>
|
NB: ricordatevi che i vostri indirizzi non saranno uguali a quelli che vedete qui, perché l'allocazione è dinamica, ma basatevi sempre sul valore di ESP, che fra l'altro Olly si preoccupa di evidenziare nella pila dello Stack stesso. :-)
Dunque, ricapitoliamo:
Andiamo subito a controllare come stanno effettivamente le cose: cliccate con il tasto destro sulla riga del seriale e scegliete "Follow in Dump"; in basso a sinistra vedrete nel Dump una cosa di questo tipo
address |
memory dump |
ASCII |
|
Ecco il nostro bel numerello sepolto fra caratteri ASCII! Notate che le stringhe si indicano con il puntatore al primo carattere che vale fino a che non si incontra il carattere 00 (sono le null-terminated strings del C). Facciamo lo stesso con il numero presente in cima allo stack (io ce l'ho in posizione 0012D7F4), destro + "Follow in Dump" e vedrete delle strane cifre in fila, prendiamo le prime 4: io ho "BC 5F E1 00" = 188 95 225 00 in decimale... mah! strano, eppure in esadecimale mi ricorda qualcosa... scriviamole al contrario: 00E15FBC.
Hey! sembra un indirizzo di memoria tipo quello che abbiamo
seguito prima! Evidenziamo le quattro cifre (compreso lo 00), tasto destro e
"Follow DWORD in Dump" (DWORD = parola di memoria di 8 bytes): eccolo il nostro
nome! :-D (le cifre sono al contrario a causa della codifica
LittleEndian della Intel; per info leggete qui).
Domandina di
teoria: allora che cosa è l'area di memoria che era in cima allo stack che
abbiamo pushato alla CALL? su su, lo so che lo sapete... un
puntatore, vi torna? "Variabile contenente l'indirizzo di
un'altra area di memoria"... i conti tornano!
Purtroppo Olly decodifica soltanto le stringhe con puntatore diretto al primo carattere, quindi in questo caso cercava di interpretare come ASCII il primo puntatore (BC5FE100 nel mio caso); a volte bisogna andare a controllare manualmente per assicurarsi di quello che viene effettivamente passato come argomento alle funzioni.
Quindi ricordiamoci bene dove abbiamo il nostro nome ed il seriale: tutti e due nella area di memoria riservata al programma per contenere le sue variabili (se premete sulla M in alto vedrete che valore ha questo range (nel mio caso da 00D40000 a 000EC000); il seriale è comunque presente anche in EDX.
Grandi
Manovre
Adesso il momento cruciale: dobbiamo entrare nella CALL 00419BF0
all'indirizzo 004197D7 (ricordatevi di sbloccare lo stack sennò vi perdete tra i
context delle varie CALL); premiamo F7 e ci ritroveremo all'inizio
della misteriosa procedura (gli ho messo la label CRYPTO() ;-p).
NB: vi
consiglio di piazzare un breakpoint a 00419BF0, così se dovete riavviare il
debugging ricomincerete da qui.
........ ... |
Allora... fino a 00419C1C nessun problema (inizializzazione + menate varie del Borland (date un'occhiata anche a cosa dice IDA a proposito... hey, mica l'avrete chiusa??); a 00419C21 c'è un PUSH seguito da un incremento del valore puntato dal Base Pointer - 30h cosicché entrambi questi valori vengono passati alla funzione successiva (l'1 ed il puntatore), che non fa parte delle librerie standard (unknown_libname_xxx = funzione di libreria sconosciuta, sub_xxxxxx = funzione definita dal programmatore). Siccome mi sembrava che venisse richiamata un fottio di volte, c'ho messo la label sysFunc() per riconoscerla meglio.
Premessa: io mi sono fatto tutto lo step by step da 0041A03C a 0041A226 e dopo un quarto d'ora ho capito quale divino operato compiesse tale misteriosa funzione... contiene la formula della Kriptonite? la mappa per trovare il Graal? No, TOGLIE GLI SPAZI ED I CARATTERI SPECIALI DA UNA STRINGA!! :-(
Fossi in voi, eviterei di ripetere ulteriormente quest'agghiacciante scoperta: premete F8 e nello Stack vedrete la nuova stringa in corrispondenza dell'indirizzo passato come primo argomento alla CALL (io avevo 0012D7E8).
Bene, ora che abbiamo la stringa ripulita nello Stack (forse cominiciate a capire che con i programmi Delphi e simili è bene dare sempre un'occhio in basso a destra in Olly...) possiamo vedere cosa succede dopo; vi consiglio caldamente di usare i commenti (';' su una riga) e le Labels sia per le funzioni che per le variabili (tasto destro sull'indirizzo --> "Label": io, ad esempio, le ho messe agli indirizzi del seriale e del nome e da ora in avanti mi scrive <name> e <serial>... bello, no? Poi sono anche andato a 0041A03C e con i ":" ho settato la label "removeSpaces()"... eh, una volta sì, ma due no! ;-p
A 00419C36 prende la lunghezza della stringa e
si assicura che sia maggiore di 19h = 25 caratteri; in caso negativo si rimette
tutto a posto e si esce dalla funzione, altrimenti si prosegue avanti a
00419C63.
NB: serial è il seriale senza spazi (mi
raccomando, sempre un occhio allo Stack, perché queste variabili sono abbastanza
"sepolte" in basso...
00419C63 > MOV WORD PTR
SS:[EBP-3C],2C |
Beh, il listato qui sopra è abbastanza eloquente: la stringa di prima doveva essere di almeno 25 caratteri, perché effettivamente al nostro amico gliene servivano 24; subito dopo ci sono 2 PUSH che finalmente mettono nello Stack il nome ed i primi 24 caratteri del seriale (se ci avete messo label, sarà anche più facile accorgersene, altrimenti usate il comando "Follow in Dump" come abbiamo fatto prima).
Cosa succede dentro quella funzione? Non sarà mica lì che avviene il fatidico check Name/Serial? No, fa tutt'altro...
< 8 bytes >
|
< 8
bytes > |
< 8
bytes > | |
SERIALE |
12 34 56 12 |
34 56 12 34 |
56 12 34 56 |
XOR con |
F9 83 6E 1B |
A5 B8 8F C1 |
9C 16 94 A7 |
risultato |
EB B7 38 09 |
91 EE 9D F5 |
CA 04 A0 F1 |
I tre valori con cui fare lo XOR sono costanti, e lo vedete molto bene nel disassemblato che però non riporto qui per ragioni di spazio; se ce l'avete davanti comunque è molto semplice e lineare da seguire:
Fin qui tutto bene, ma se avete avuto l'accortezza di seguire la
procedura passo passo, probabilmente vi sarete accorti che queste conversioni
sembrano sparire dalla circolazione: non li vediamo nè nei registri, nè nello
stack; e allora dove sono?
Beh, non si può pertendere che la Intel o la AMD
ci diano processori da 1000 registri per fare quello che ci pare senza toccare
la memoria! sennò la RAM a che serve? Come abbiamo visto prima per il serialone
da 100 caratteri, questi valori possono essere memorizzati nello spazio di
memoria assegnato al programma e per analizzarlo abbiamo a disposizione il
Memory Dump di OllyDbg in basso a sinistra.
Non esattamente tutti e tre i pezzi xorati finiscono nello heap, quindi vediamo cosa succede ad ognuno di essi spostando la nostra attenzione nei pressi delle istruzioni più importanti, cioè quelle di XOR e di successiva memorizzazione.
Primo segmento di seriale:
0041A2D8 |. CALL <Sysutils::StrToInt(System::AnsiString)>
converte la stringa in numero esadecimale
|
Quindi, il primo valore va a finire in
[LOCAL.17], che è una gentilezza che OllyDbg ci fa per non farci
impazzire con i conti; come abbiamo visto all'inizio, le variabili locali delle
funzioni vengono memorizzate nello spazio compreso tra il Base Pointer (EBP) e
lo Stack Pointer (ESP) (lo "scope" della procedura corrente) ed hanno indirizzi
nella forma [EBP - 4*n], cioè stanno a distanze multiple di 4 bytes a
partire da EBP verso la cima dello Stack; a titolo informativo, gli argomenti
passati alle procedure, a differenza delle variabili locali, hanno invece
indirizzi del tipo [EBP + 4 + 4*n], cioè a distanze multiple di 4 bytes
a partire da EBP verso il fondo dello Stack (il posto EBP + 4 è riservato
all'indirizzo di ritorno della procedura.
Tutte queste potete verificarle
anche da soli andando nelle opzioni di OllyDbg e settando "Analysis 1
---> Show ARGs and LOCALs in procedures".
Allora dov'è finito il valore di EAX? Semplice: [LOCAL.17], per come abbiamo potuto capire poco
fa, rappresenta la 17^ variabile locale, che si troverà perciò all'indirizzo
[EBP - 4*17] = [EBP - 44] = [0012D790 - 44] = [0012D74C] = contenuto dell'area di memoria 0012D74C (Stack Segment) = 0012D794
(non sto
scrivendo eresie aritmetiche, ricordatevi che siamo in esadecimale!
;-p)
Hey, ma mica
possiamo fare tutte le volte questa trafila! Ma infatti ve lo dicevo che
OllyDbg era gentile... quando siete con il debug sull'istruzione a
0041A2E2, date un'occhiata nella status bar subito sotto e vedrete scritto
"Stack SS:[0012D74C] = 0012D794", cioè il risultato delle nostre operazioni,
compreso il valore attuale dell'indirizzo di destinazione; carino, no?
;-)
Il contenuto di EAX va quindi a sovrascrivere l'indirizzo 0012D794, che appunto appartiene allo Stack; appena premete F8, lo vedrete infatti comparire bello bello sempre lì in basso a destra in Olly. Morale della favola, il primo segmento di seriale xorato sta nello Stack all'indirizzo 0012D74C.
Secondo segmento di seriale:
0041A3AC |. CALL <Sysutils::StrToInt(System::AnsiString)>
converte la stringa in numero esadecimale
|
Stavolta è più semplice: il risultato dello XOR va subito in [EDX + 10], cioè nella cella di memoria puntata da EDX + 10; nella status bar potete vedere che in questo caso corrisponde a DS:[00E21670], un indirizzo appartenente al Data Segment... ottimo! Premiamo il tasto destro proprio sulla riga della status bar e scegliamo "Follow Address in Dump", dopodiché premiamo F8 e magicamente vedremo apparire quei famosi 4 bytes xorati di cui sopra.
Terzo segmento di seriale:
0041A42A |. CALL <Sysutils::StrToInt(System::AnsiString)>
converte la stringa in numero
esadecimale |
Esattamente come prima, il risultato finisce nel Data Segment, ma stavolta nell'indirizzo [EDX + 14], che corrisponde a DS:[00E21674].
Stavolta è più semplice: il risultato dello XOR va subito in [EDX + 10], cioè nella cella di memoria puntata da EDX + 10; nella status bar potete vedere che in questo caso corrisponde a DS:[00E21670], un indirizzo appartenente al Data Segment... ottimo! Premiamo il tasto destro proprio sulla riga della status bar e scegliamo "Follow Address in Dump", dopodiché premiamo F8 e magicamente vedremo apparire quei famosi 4 bytes xorati nella memoria davanti a noi.
Riepilogo:
Il primo pezzo del seriale xorato è nello Stack a 0012D74C:
<< Stack >>
|
Il secondo ed il terzo sono adiacenti e vanno rispettivamente da 00E21670 a 00E21673 e da 00E21674 a 00E21677 (ricordatevi del LittleEndian...), mentre in 00E21660 c'è il puntatore alla stringa del nome immesso nel form.
address |
il secondo ed il terzo pezzo |
ASCII |
|
address |
il nome |
ASCII |
|
Bene, direi che per quanto riguarda questa funzione, abbiamo svolto egregiamente il nostro (sporco) lavoro di reversers, quindi posso ritornare dove ci eravamo interroti prima, cioè a 00419C98 (le label "insolite" le ho messe io... Olly non è ancora così sofisticato da dedurle da solo!):
00419C98 . ADD ESP,8
risistema il puntatore allo stack |
Codici proibiti
I commenti che ho scritto lasciano poco spazio
all'immaginazione, comunque vedete bene anche voi che si capisce abbastanza
facilmente quello che fa; quello che non si comprende appieno è PERCHE' il primo
carattere dell'ultimo pezzo xorato NON deve valere né 1 né 2. :-/
mah,
staremo a vedere... intanto proseguiamo con la seconda parte
della funzione:
00419CE0 > MOV ECX,[EBP+8]
ECX = puntatore alla stringa del nome |
'Mbè? E che è tutto 'sto intruglio di JE e CMP? Vediamo vediamo... ;-)
Quando siete fermi su 00419CE0, nella status bar vedrete che [EBP + 8] = 00293EF8 contiene 00DEEE70, cioè il solito puntatore al nome, mentre in EDX vedrete in chiaro i primi 24 caratteri del seriale originale; come abbiamo visto prima nella memory dump, la struttura dati che contiene nome e seriale xorato è molto compatta, ed è racchiusa tutta in circa 8 + 16 = 24 bytes. [ECX + 10] = DS:[00E29408] è a poca distanza dal nome, anzi è proprio il nostro seriale xorato (2° e 3° pezzo), indatti vediamo che vale proprio 91EE9DF5! :-)
"Brancoliamo nel buio, ed a volte ci sembra di esserci smarriti senza speranza; ma improvvisamente uno squarcio di luce disperde il buio della notte e ci illumina la strada indicandoci la via" (niente citazione, è mia; mi è venuta ora e l'ho scritta. l'ora tarda fa brutti scherzi...)
Quindi... cosa diavolo sono tutte quelle costanti che vengono
confrontate con il pezzetto di seriale? Elementare (Dr.) Watson(!)... una
blacklist! I programmatori hanno inserito una serie di codici
non validi che verificano subito se "matchano" con il nostro oppure no; non sarà
mica che ce l'hanno con noi? ;-)
Se avete voglia (io non l'avevo), potete
farvi il procedimento inverso per capire come sono fatti questi 7 seriali che
non funzionano, cioè prendete il numeretto magico del 2° pezzo e lo xorate con
quei 7 valori per vedere i valori proibiti; per fortuna l'imperituro
"123456123456123456123456" non rientra in questa caegoria,
perciò passiamo indenni attraverso il fuoco nemico dei CMP e giungiamo sani e
salvi alla terza parte:
00419D67 > MOV WORD PTR
[EBP-3C],38
\ |
Dai, ormai siete pratici: una volta recuperata la lunghezza,
ricava l'altra parte del seriale, cioè quella che va dal 25° carattere fino
all'(ultimo - 24); potete vedere i tre parametri (stringa, limite sx, limite dx)
rispettivamente in EAX, EDX, ECX. (io avevo 0012F01C, 19, 3B)
La sottostringa
risultante finisce nuovamente nello Stack, all'indirizzo 0012F014 (guardate in
basso a destra nel dump).
Diagnosi: il paziente ha
l'RSA (poraccio...)
Bene, proseguiamo qualche riga più in basso e... ahi ahi! che cosa sono tutte quelle strane funzioni di libreria?
00419D93 . MOV WORD PTR [EBP-3C],14 |
Eheh, ma lo sapete che per questo programmetto sono andati a scomodare addirittura la cifratura RSA? Praticamente un siluro per affondare un galleggiante... :-/
Si tratta di una cifratura a chiave pubblica (è quindi un sistema asimmetrico) di lunghezza solitamente compresa tra un minimo di 512 ed un massimo 1024 bits; nel caso nostro, se premete F8 quando siete alla riga 00419DC0, vedrete comparire nello Stack a (ESP - 14h) in chiaro ASCII la stringa "TRSA using 512 bit key", quindi non dobbiamo nemmeno fare troppa fatica per capire con chi abbiamo a che fare (più chiaro di così ---> TRSA).
Queste tre funzioni (00419DA2, 00419DB3, 00419DC0) inizializzano
la libreria di cifratura impostando anche i KeyBits; subito dopo si passa a
costruire la HashString e questa viene dalla concatenazione dei soliti primi 24
caratteri del seriale e del nome in lower case.
(seriale troncato = "123456123456123456123456", nome = "Zero_G", risultato = "123456123456123456123456zero_g")
Il nome viene recuperato
dalla CALL <sub_5FB434>, lowercasato(!) dalla CALL <unknown_libname_184
e concatenato al seriale dalla String::operator+; eccovi il listato in
dettaglio:
00419DC5 . MOV WORD PTR [EBP-3C],44 |
Arrivati a premere F8 su 00419DFB, nello Stack vi troverete seriale e nome concatenati in 0012D7F4; le successive SetInputString(), HashString() e PutPublicKeyString() servono ovviamente per impostare la chiave pubblica della cifratura, che guarda caso è quello strano biscione di caratteri che avevamo visto all'inizio. ;-)
Continuate ancora qualche istruzione ed arriverete qui:
00419E59 . MOV EDX,[EBP-C] |
Aspettate un secondo prima di premere F8; guardate Stack e Registri: in EDX c'è la seconda parte del seriale (quella dal 25° carattere in poi), mentre nello Stack a 0012D754 c'è la chiave pubblica. La funzione cercherà di creare la vostra signature in base a quella parte di seriale... bene, premiamo F8 e... BAM! Improvvisamente vi ritroverete dentro NTDLL con un bel "Exception 0EEDFADE ecc..." nella status bar! Che è successo? Sono arrivati gli alieni? Ancora no, ma nel frattempo il nostro amico ha generato un'eccezione che è stata catturata dalle DLL Windows, però, molto strano, se premete F8 il debug non è bloccato e riuscite a fare qualche altro passo tra le interiora di Windows, giusto un paio, fino a che una CALL fa apparire il Nag Screen di PTGui ed una volta premuto OK, siete fuori. :-/
Dottore, che cosa consiglia?
2 NOP in supposte!
OK, non perdiamoci d'animo: senza chiudere nulla, mettete direttamente un breakpoint su 00419E5F e ripremete OK da dentro PTGui per ricontrollare il nostro seriale farlocco; dovreste essere proprio lì sulla PutSignatureString(). Questa volta entriamo dentro con F7 e proseguiamo con F8 fino a 005FFB0E, dove c'è un bel JE seguito dalla stringa "Signature has incorrect length" e da un eclatante RaiseExcept(void): beh, ci sarebbe andata troppo bene se avessimo azzeccato anche la lunghezza, che come avrete potuto intuire deve essere lunga 40h = 64 caratteri (si vede se controllate i registri quando siete a 005FFB0C, dove fa il CMP EAX,EDX; EAX lunghezza del seriale, EDX = 40h (lunghezza giusta))
005FFB0C . CMP
EAX,EDX |
Allora che si fa? Beh, io proporrei il metodo violento: lo convinciamo che la signature è lunga giusta, dio bono! (perdonate il toscanismo...) ;-)
I modi per farlo sono due:
fate voi... io per essere più sicuro, dato che dentro c'erano anche dei più espliciti "Signature not valid", ho preferito non rischiare: una spianata di NOP e passa la paura! :-D
Ritornate con il "-" del tastierino numerico indietro a 00419E5F, poi fate doppio click sulla riga in assembly e scrivete NOP assicurandovi che "Fill with NOPs" sia attivo; ecco come apparirà il listato dopo l'intervento:
00419E54 . CALL <System::AnsiString::~AnsiString(void)> |
Togliete il breakpoint dal NOP e mettetelo un'istruzione più in alto, su 00419E5C, premete F9 per far proseguire l'esecuzione del programma e ripremete OK per fargli controllare un altra volta il seriale; vi dovreste trovare proprio sul breakpoint... ci siete? Bene, portiamo a termine la nostra opera: continuate qualche riga più in basso fino alla successiva funzione dell'RSA:
00419E87 . MOV EDX,EAX |
Prima di premere F8 (sì, lo so che siete impazienti... su, ancora poco e l'avremo messo al tappeto!), date un'occhiata in giro, non fuori dalla finestra, ma nei registri: EAX contiene i KeyBits assegnati prima dall'apposita funzione, EDX invece contiene finalmente una stringa... che sia quello il seriale buono? Mi spiace deludervi, ma quello è solo il risultato della decodifica della chiave pubblica che deve corrispondere a quella privata per far sì che il seriale sia buono; a condizione di usare come chiavi prodotti di numeri primi sufficientemente lunghi, non è computazionalmente possibile risalire da una chiave pubblica a quella privata e viceversa; beh, che vi aspettavate? che l'RSA fosse come il cifrario di Cesare? ;-p
Appurato che per lo scopo di questo tutorial è praticamente inutile reversare l'intera implementazione RSA dentro la libreria TRSA (in un giorno del calendario astrale forse lo farò...), premiamo F8 per vedere cosa ne pensa quella funzione del nostro seriale clandestino; ARIBAM! un'altra eccezione! Vabbè, che andasse bene sarebbe stato veramente un caso troppo fortuito! ;-)
Ripremete OK dentro PTGui e premete F7 su 00419E8D; al suo interno vi troverete davanti questo:
005FE643 |. CMP BYTE PTR
[ESI+882],0 |
Il CMP a 005FE643 controlla mediante un flag la presenza della chiave pubblica (il biscione c'è, quindi il controllo lo passa), mentre il CMP a 005FE67B controlla tramite un altro flag che ci sia la signature valida, che per l'appunto non c'è, dato che abbiamo elegantemente taroccato il precedente PutSignatureString(); quindi, c ome prima, anche qui il RaiseExcept() manda tutto all'aria, e abbiamo nuovamente da scegliere tra l'opzione dei patch dei JNZ oppure il Metodo del Boscaiolo™ introdotto poco fa. E siccome stasera c'ho le palle girate, si fa come dico io! Giù NOP come se piovessero sulla CALL a 00419E8D:
00419E89 . MOV EAX,[EBP-58] |
Ottimo! Per fare proprio un lavorino a modo, controlliamo anche un pochino più giù per vedere se ci perdiamo qualcosa; non vi riporto tutto il codice, ma tanto da 00419E92 fino al RETN a 00419F75 non ci sono altro che le onnipresenti AnsiString(void) tanto care al Delphi ed un finale CatchCleanup(void), quindi dovremmo essere a posto così.
Ritorniamo infine a controllare la funzione chiamante, quella che abbiamo soprannominato CRYPTO(); se diamo un occhiata più avanti dopo 00419E8D (dove c'era la TRSA::Verify()), vedremo che il flusso va giù a dritto come un fuso fino RETN, a parte qualche JE che comunque non devia dal percorso principale.
[consiglio importante: imparate a riconoscere quando e quali salti condizionati possono davvero modificare l'andamento principale della funzione in esame; perderete molto meno tempo a capire quali sono i controlli decisivi. anche il flow chart di IDA può essere d'aiuto per questo compito...]
L'ignaro PTGui è da un po' che vi aspetta in animazione sospesa, ma ancora non sa qual'è il suo destino al risveglio, poverino eheh... togliete tutti i breakpoint e ridategli il via con F9; ripremete per l'ennesima volta quel maledetto bottone OK e trattenete il respiro... VAI! E' andata! 8-)
>>> "Thank you for registering PTGui!" <<<
Mai 5 parole suonarono così dolci al nostro orecchio (in particolar modo per me adesso, che sono quasi le quattro di notte...) ;-p
Vi lascio un altro compitino per casa: controllate che anche la funzione al livello più alto (la OKButtonClick() da cui siamo partiti) non presenti problemi di sorta; io l'ho già fatto e vi anticipo che dopo aver controllato che il seriale non sia valido e nemmeno per una versione demo limitata, stampa il lauto messaggino di avvenuta registrazione. :-D
Ultimi
ritocchi
Premete il tasto destro sulla prima istruzione della malefica CRYPTO() a 00419BF0 e scegliete "Find references to ---> Selected command" per trovare tutti i punti in cui si fa riferimento ad essa: nella lista, oltre alla chiamata che abbiamo intercettato noi a 004197D7 sull'OK del bottone, ce ne sono altre 4 sparse per il programma; rimettete il breakpoint su 00419BF0 e riavviate il tutto (ALT+F2).
Come volevasi dimostrare, il seriale viene ricontrollato anche all'avvio ed è per quello che compariva la pallosissima finestra che avevamo visto all'inizio (quella che ci intima la registrazione prima dei 30 giorni e ci fa aspettare un sacco prima di cominciare), ma no problema, amigo! La forzatura che abbiamo operato sul check del seriale è la panacea di tutti i mali (date un'occhiata alla Titlebar e all'About Box...): se lo lasciate proseguire, vedrete che ogni volta che viene tentato il controllo, questo viene supposto essere andato a buon fine, dato che non è saltata fuori l'eccezione... chissà per quale motivo non esce più, vero? :-)
I più curiosi si saranno chiesti dove il pivello va a memorizzare stabilmente i nostri dati una volta immessi; i posti possono essere soltanto due: o su un file o sul registro. Se vi scaricate Filemon e Regmon, vedrete facilmente che hanno optato per la seconda, infatti sono contenuti nella chiave "HKCU\Software\NewHouse\ptGui\Options" e, nonostante siano sbagliati, con la nostra modifica ce li scrive lo stesso e quando li rilegge li prende per buoni! Riguardatevi quello che succedeva da 004199B5 fino al RETN... ;-)
Ah, quasi dimenticavo... premete il destro sul disassemblato e scegliete "Copy to executable ---> All modifications ---> Copy all", poi destro ancora e "Save file" con un nome diverso dall'originale; eccovi il vostro bel eseguibile guarito dall'RSA! E poi non venitemi a dire che Olly non è comodo da usare! ;-9
Hey, a forza di stargli così vicino, non è che me l'avrà attaccata?? io mica posso auto-patcharmi, eh! ;-p
-=Zero_G=-
Note finali |
Ringrazio come al solito tutta la UIC e la splendida gente che ci sta dentro (o intorno)... continuiamo così, ragazzi, che siamo i migliori! ;-)
Un saluto particolare a Que (era bellissimo Neverland, vallo a vedere!), AndreaGeddon (la prossima volta che si organizza una cena voglio conoscere anche te!), pnluck (visto che ce l'ho fatta a finirlo per stasera il tute?), Lonely Wolf (tutto bene lassù?), Ntos, Alfa (ho sostituito tutti gli orologi di casa con.. clessidre! ahah), Shub-Nigurrath, folletto_burlone, Xoanon, massimo_lion, Ox87k, Fego (come procede la tua avventura con il reversing?), Trilogy, SatUrN, X-Spike, e tutti gli altri che non ho nominato qui! (classica scusa pronta per chi non si ricorda mai i nomi delle persone!)
Questa volta includo anche Giovanni (quest'estate in piazza si fa gente!), Emiliano (quando cavolo si va a registrare il votooo?? ;-p), Ernesto ed Augusto (biru!), perché senza di loro questi anni all'università sarebbero stati parecchio più grigi per me; non ci dobbiamo perdere! :-)
Grazie ai Within Temptation ed ai
3 Doors Down che praticamente questa settimana gli ho fuso i cd a forza
di ascoltarli! :-D
Dream Theater, che vi li diamo a fare i soldi ai
concerti se poi non fate più un album da quasi due anni!? io sto aspettando, su,
forza! ;-/
Disclaimer |
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.