PTGui 4.1
From UIC
PTGui 4.1 (RSA? no, grazie, ho smesso!)
| Infos | |
|---|---|
| Author: | Zero_G |
| Email: | zero.g80@gmail.com |
| Website: | http://zerog.altervista.org |
| Date: | 25/02/2005 (dd/mm/yyyy) |
| Level: |
|
| Language: | Italian |
| Comments: | Grande ze, è sformattato e hai lasciato tutti i link relativi, però almeno è un sacco bello :))) |
Introduzione
Ultimamente mi sono dedicato abbastanza approfonditamente alle foto panoramiche, teoria e pratica insieme (ahah, basta crackmes e trabiccoli vari!); questo tipo di "collage digitale" che si fa con più foto dello stesso posto scattate ad angolazioni diverse permette di andare ben oltre il limite dei gigapixel della nostra cara macchinetta: date un'occhiata a questo sito per avere un'idea dei risultati ottenibili anche solo con 2 o 3 foto della stessa location! :-)
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):
- PTAssembler (è di Max Lyons, il tizio delle foto che vi dicevo prima; per altre informazioni consultate il suo sito)
- Hugin (tentativo di GUI multipiattaforma, ma ancora in fase (parecchio) beta)
- PTGUI (secondo me il migliore, con un sacco di opzioni carine, tra cui la comoda funzione Panorama Editor)
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
- PEiD (ormai la sua diagnosi è sottintesa...)
- OllyDbg 1.10 + GODUP Plugin (non temete, presto sul mio sito metterò online parecchi plugins di Olly che non si trovano più in giro!)
- IDA 4.7 (indispensabile se volete ricavare i nomi reali delle funzioni Delphi; alternativamente, potete anche usare DeDe)
- Filemon e Regmon (opzionali, solo per un controllo finale)
URL del programma
Il programma "vittima" è reperibile direttamente dal sito proprietario http://www.ptgui.com
Nella sezione dowloads troverete anche l'engine di Panorama Tools aggiornato all'ultima versione (senza quello, ovviamente, PTGui non funziona...)
Essay
Bene, ho scritto anche troppo in quest'introduzione... ancora un po' e finivo col parlarvi solamente di fotografia panoramica! ;-p
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:
- sappiamo qual èil messaggio d'errore e ci può servire come base per una ricerca di stringhe;
- il controllo sul seriale viene sicuramente ripetuto all'avvio, altrimenti non comparirebbe il nag screen;
- il seriale deve essere lungo almeno 81 caratteri;
- è presente anche un check sul numero di giorni di valutazione rimanenti.
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 for text" e scrivete "The registration code", tanto è sufficiente; troverete queste righe (ometto per brevità il disassemblato, riporto solo l'offset):
004194D1: ASCII "RegName"
00419537: ASCII "RegKey"
00419592: ASCII "(registration key is hidden)"
0041988C: ASCII "The registration code is not valid. Type your name exactly as you did when you
registered PTGui. The registration code is case sensitive."
004198F6: ASCII "dddddd"
00419941: ASCII "dddddd"
0041997D: ASCII "The registration code you entered is for a temporary license, expiring at %s.
The license is not valid anymore."
004199DA: ASCII "RegName"
00419A52: ASCII "RegKey"
00419ABD: ASCII "Thank you for registering PTGui!"
00419E2F: ASCII "++11IE:pGQKLK-NACiKpFfdc-PxcKOZAGeL0HPFm97h-sDIVWHj
RC0c1xQYHXZx13o-oXQ2j2xaBjBL-SodBMOSXzIjo+c833jjhg"
........: ..... "................."
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 fosse trattato 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.
0012EFDC:
0012EFE0:
0012EFE4:
0012EFE8: ASCII "Zero_G" (ESP-28h)
0012EFEC:
0012EFF0:
0012EFF4: ptgui.00593DCF
0012EFF8: ptgui.005FB129
0012EFFC:
0012F000:
0012F004:
0012F008:
0012F00C: ptgui.00419788
-----------------------------------
0012F010: <= CIMA DELLO STACK (ESP)
0012F014:
0012F018:
0012F01C: USER32.77D1B373
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.
00419768 CALL <TControl::GetText(void)> ; qui viene letto il nome immesso
0041976D LEA EDX,[LOCAL.2]
00419770 MOV EAX,[LOCAL.30]
00419773 CALL <System::AnsiString::operator=(System::AnsiString &)>
00419778 DEC [LOCAL.20]
0041977B LEA EAX,[LOCAL.2]
0041977E MOV EDX,2
00419783 CALL <System::AnsiString::~AnsiString(void)>
00419788 MOV WORD PTR SS:[EBP-5C],2C
0041978E MOV ECX,[LOCAL.28]
00419791 MOV EAX,DS:[ECX+2DC]
00419797 ADD EAX,208
0041979C MOV [LOCAL.31],EAX
0041979F LEA EAX,[LOCAL.3]
004197A2 CALL <unknown_libname_184>
........
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...
004197A7 MOV EDX,EAX
004197A9 INC [LOCAL.20]
004197AC MOV ECX,[LOCAL.31]
004197AF MOV EAX,DS:[ECX]
004197B1 MOV ECX,DS:[EAX]
004197B3 CALL DS:[ECX+1C] <ptgui.sub_557E08> ; qui invece si carica il seriale
........
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...
0012D7D4 00E2ACFC ASCII "123456789012(...)" ; <= seriale
0012D7D8 00593E6B <ptgui.loc_593E6B>
0012D7DC 0012DA0C
0012D7E0 005FB129 ptgui.005FB129
0012D7E4 0012DA0C
0012D7E8 00DEEE5C
0012D7EC 0012D888
0012D7F0 0012D894
0012D7F4 00E28904 ; <= CIMA DELLO STACK (ESP) [nome?]
0012D7F8 00000000
0012D7FC 00000000
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:
- l'indirizzo della cella contenente il seriale immesso sta più in alto nello Stack (hey, la sapete differenza tra puntatori e variabili, vero?); io l'ho trovato dentro 00E2ACFC, e questo è indicato in 0012D7D4 = ESP-20h nello Stack
- il numero pushato per ultimo probabilmente ha qualcosa a che fare con il nostro nome che sembrava scomparso...
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 |
|
00E2AD74 |
73 20 63 61 73 65 20 73 65 6E 73 69 74 69 76 65 |
s case sensitive |
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.
00419BEE NOP
00419BEF NOP
00419BF0 PUSH EBP ; \
00419BF1 MOV EBP,ESP ; |
00419BF3 ADD ESP,-58 ; |
00419BF6 PUSH EBX ; |======> Blocco di Inizializzazione
00419BF7 PUSH ESI ; |
00419BF8 PUSH EDI ; |
00419BF9 MOV EAX,006AC7C4 ; |
00419BFE CALL <@__InitExceptBlockLDTC> ; /
00419C03 MOV BYTE PTR SS:[EBP-4D],0
00419C07 MOV WORD PTR SS:[EBP-3C],8
00419C0D MOV WORD PTR SS:[EBP-3C],20 ; ma non valeva già 8!? stupido compilatore! ;-p
00419C13 MOV EDX,SS:[EBP+8] ; in EDX il nostro nome (controllate nel Dump)
00419C16 PUSH DWORD PTR DS:[EDX+20]
00419C19 LEA EAX,SS:[EBP-4]
00419C1C CALL <unknown_libname_184> ; IDA ci dice che questa fa parte
; della libreria Borland (ipse dixit)
00419C21 PUSH EAX ; indirizzo per scrivere il risultato della funz.
00419C22 INC DWORD PTR SS:[EBP-30] ; dove prendere l'input della funzione
00419C25 CALL <sub_41A03C> ; questa non è della Borland... cosa farà?
; newserial = removeSpaces(serial)
00419C2A ADD ESP,8 ; riposizionamento in cima allo stack
00419C2D MOV WORD PTR SS:[EBP-3C],14
00419C33 LEA EAX,SS:[EBP-4] ; EAX contiene l'indirizzo di newserial
00419C36 CALL <System::AnsiString::Length(void)> ; EAX = length(newserial)
00419C3B CMP EAX,19 ; if(EAX >= 25)
00419C3E JGE SHORT <loc_419C63> ; go on ---------------\
00419C40 XOR EAX,EAX ; else reset all and |
00419C42 PUSH EAX ; go away from here |
00419C43 DEC DWORD PTR SS:[EBP-30] ; |
00419C46 LEA EAX,SS:[EBP-4] ; |
00419C49 MOV EDX,2 ; |
00419C4E CALL <System::AnsiString::~AnsiString(void)> ; |
00419C53 POP EAX ; |
00419C54 MOV EDX,SS:[EBP-4C] ; |
00419C57 MOV FS:[0],EDX ; |
00419C5E JMP <loc_419F69> ; jump to end_proc |
00419C63 MOV WORD PTR SS:[EBP-3C],2C ; <--------------------/
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...
00419C69 LEA EAX,SS:[EBP-8]
00419C6C CALL <sysFunc()> ; sono stato prudente a dargli un nome! ;-)
00419C71 PUSH EAX ; / Arg1
00419C72 INC DWORD PTR SS:[EBP-30] ; |
00419C75 LEA EAX,SS:[EBP-4] ; |
00419C78 MOV ECX,18 ; /-----|- II argomento (indice sx della sottostringa)
00419C7D MOV EDX,1 ; /---|-----|- I argomento (indice dx della sottostringa)
00419C82 CALL <System::AnsiString::SubString(int,int)> ; \ EAX=substring(serial,1,24)
00419C87 MOV WORD PTR SS:[EBP-3C],14
00419C8D PUSH DWORD PTR SS:[EBP-8] ; / Arg2 ah, finalmente si vedono passare
00419C90 PUSH DWORD PTR SS:[EBP+8] ; | Arg1 seriale e nome; mi stavo cominciando
00419C93 CALL <sub_41A228> ; \ a preoccupare... (il PTR è per il nome)
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...
- Prende il seriale troncato (esattamente quei primi 24 caratteri) e lo divide ulteriormente in 3 parti di 8 caratteri ciascuna;
- Mette la stringa "0x" davanti a ciascuna parte per passare il tutto alla funzione StrToInt(), che le converte in DWORD numeriche; (es: "123456" ---> "0x123456" ---> 123456h)
- XORa le parti ciascuna con un diverso valore constante di 4 bytes, secondo questo schema (prendo per esempio il seriale che ho immesso io):
| < 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:
- da 0041A228 a 0041A233 = inizializzazione
- da 0041A238 a 0041A295 = recupera il primo pezzo di seriale
- da 0041A29A a 0041A2D5 = concatenazione con "0x"
- da 0041A2D8 a 0041A2E2 = conversione in esadecimale e XOR con F9 83 6E 1B
- da 0041A2E5 a 0041A344 = strane manipolazioni sulla data e sull'ora correnti [???]
- da 0041A347 a 0041A370 = recupera il secondo pezzo di seriale
- da 0041A373 a 0041A3A9 = concatenazione con "0x"
- da 0041A3AC a 0041A3C2 = conversione in esadecimale e XOR con A5 B8 8F C1
- da 0041A3C5 a 0041A3EE = recupera il terzo pezzo di seriale
- da 0041A3F1 a 0041A427 = concatenazione con "0x"
- da 0041A42A a 0041A440 = conversione in esadecimale e XOR con 9C 16 94 A7
- da 0041A445 a 0041A467 = titoli di coda... ;-)
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:
0041A2DD XOR EAX,F9836E1B ; la XORa con il 1° numeretto magico
0041A2E2 MOV [LOCAL.17],EAX ; copia il risultato in EAX
0041A2E5 MOV WORD PTR [EBP-30],8 ; e poi
0041A2EB MOV EDX,[LOCAL.17] ; lo copia anche in EDX
0041A2EE AND EDX,1FFFF ; e lo moltiplica per 1FFF (gli serve dopo con la data)
0041A2F4 LEA EAX,[LOCAL.19] ; EAX = puntatore al seriale originale (quello lungo)
0041A2F7 CALL <sub_41A4C4> ; uno dei gestore delle eccezioni del Delphi
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:
0041A3B1 XOR EAX,A5B88FC1 ; la XORa con il 2° <em>numeretto magico</em>
0041A3B6 MOV EDX,[ARG.1] ; [ARG.1] conteneva il nostro nome, che ora va in EDX
0041A3B9 MOV [EDX+10],EAX ; il risultato dello XOR finisce in [EDX+10]
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:
0041A42F XOR EAX,9C1694A7 ; la XORa con il 3° <em>numeretto magico</em>
0041A434 MOV EDX,[ARG.1] ; [ARG.1] conteneva il nostro nome, che ora va in EDX
0041A437 MOV [EDX+14],EAX ; il risultato dello XOR finisce in [EDX+14]
Esattamente come prima, il risultato finisce nel Data Segment, ma stavolta nell'indirizzo [EDX + 14], che corrisponde a DS:[00E21674].
Riepilogo:
Il primo pezzo del seriale xorato è nello Stack a 0012D74C:
0012D73C:40F39E40
0012D740:000001DB
0012D744:00000000
0012D748:40F38090
0012D74C:EBB73809 ; il primo pezzo del seriale xorato
0012D750:0012D7B8 Pointer to next SEH record
0012D754:005D976F SE handler
0012D758:006AC9FC ptgui.006AC9FC
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 |
|
00E21660 |
3C 97 D4 00 2C 00 00 00 00 00 00 00 90 80 F3 40 |
<.Ô.,.........ó@ |
| address | il nome | ASCII |
|
00D4972C |
6C 31 00 00 16 00 00 00 01 00 00 00 06 00 00 00 |
l1.............. |
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!):
00419C9B MOV EAX,[EBP+8] ; EAX = il solito doppio puntatore al nome
00419C9E TEST BYTE PTR [EAX+14],1 ; [EAX + 14] è il 1° carattere XORato dell'ultimo pezzo
00419CA2 JNZ SHORT <2ndPhase> ; se non è uguale ad 1 prosegui con la seconda fase
00419CA4 MOV EDX,[EBP+8] ; altrimenti
00419CA7 CMP DWORD PTR [EDX+14],2 ; controlla se è uguale a 2
00419CAB JE SHORT <2ndPhase> ; se è diverso, prosegui con la seconda fase
00419CAD XOR EAX,EAX ; altrimenti, rimetti tutto a posto
00419CAF PUSH EAX ; .
00419CB0 DEC DWORD PTR [EBP-30] ; .
00419CB3 LEA EAX,[EBP-8] ; .
00419CB6 MOV EDX,2 ; .
00419CBB CALL <System::AnsiString::~AnsiString(void)> ; .
00419CC0 DEC DWORD PTR [EBP-30] ; .
00419CC3 LEA EAX,[EBP-4] ; .
00419CC6 MOV EDX,2 ; .
00419CCB CALL <System::AnsiString::~AnsiString(void)> ; .
00419CD0 POP EAX ; .
00419CD1 MOV EDX,[EBP-4C] ; .
00419CD4 MOV FS:[0],EDX ; .
00419CDB JMP <exit_proc> ; ed esci dalla funzione
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 è PERCHÈ 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:
00419CE3 CMP DWORD PTR [ECX+10],32FE01B ; [ECX + 10] contiene il secondo pezzo di seriale XORato
00419CEA JE SHORT <EndProc>
00419CEC MOV EAX,[EBP+8]
00419CEF CMP DWORD PTR [EAX+10],259E1E1
00419CF6 JE SHORT <EndProc>
00419CF8 MOV EDX,[EBP+8]
00419CFB CMP DWORD PTR [EDX+10],259CA2B
00419D02 JE SHORT <EndProc>
00419D04 MOV ECX,[EBP+8]
00419D07 CMP DWORD PTR [EDX+10],1F77DA3
00419D0E JE SHORT <EndProc>
00419D10 MOV EAX,[EBP+8]
00419D13 CMP DWORD PTR [EDX+10],1DF3C7F
00419D1A JE SHORT <EndProc>
00419D1C MOV EDX,[EBP+8]
00419D1F CMP DWORD PTR [EDX+10],1B4D5BB
00419D26 JE SHORT <EndProc>
00419D28 MOV ECX,[EBP+8]
00419D2B CMP DWORD PTR [EDX+10],0A0A56F
00419D32 JNZ SHORT <3rdPhase>
00419D34 XOR EAX,EAX ; <EndProc>
'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:
00419D6D LEA EAX,[EBP-C] ; |
00419D70 CALL <unknown_libname_184> ; |==== > affari suoi... ;-p
00419D75 PUSH EAX ; |
00419D76 INC DWORD PTR [EBP-30] ; /
00419D79 LEA EAX,[EBP-4] ; EAX = seriale originale
00419D7C CALL <System::AnsiString::Length(void)> ; EAX = length(name)
00419D81 MOV ECX,EAX ; ECX = EAX
00419D83 ADD ECX,-18 ; ECX = ECX - 18h (24)
00419D86 LEA EAX,[EBP-4] ; EAX = seriale originale
00419D89 MOV EDX,19 ; EDX = 19h (25)
00419D8E CALL <System::AnsiString::SubString(int,int)> ; prende la sottostringa da 19h a length -18h
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?
00419D99 XOR ECX,ECX
00419D9B MOV DL,1
00419D9D MOV EAX,[6DAFC0]
00419DA2 CALL <TMD5::TMD5(Classes::TComponent *)>
00419DA7 MOV [EBP-54],EAX
00419DAA XOR ECX,ECX
00419DAC MOV DL,1
00419DAE MOV EAX,[<off_5FDD3C>]
00419DB3 CALL <Rsa::TRSA::TRSA(Classes::TComponent *)>
00419DB8 MOV [EBP-58],EAX
00419DBB XOR EDX,EDX
00419DBD MOV EAX,[EBP-58]
00419DC0 CALL <Rsa::TRSA::SetKeyBits(Rsa::TKeyBits)>
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:
00419DCB MOV WORD PTR [EBP-3C],5C
00419DD1 LEA EAX,[EBP-14]
00419DD4 CALL <unknown_libname_184>
00419DD9 MOV EDX,EAX
00419DDB INC DWORD PTR [EBP-30]
00419DDE MOV EAX,[EBP+8]
00419DE1 CALL <sub_5FB434>
00419DE6 LEA EDX,[EBP-14]
00419DE9 PUSH EDX
00419DEA LEA EAX,[EBP-10]
00419DED CALL <unknown_libname_184>
00419DF2 MOV ECX,EAX
00419DF4 INC DWORD PTR [EBP-30]
00419DF7 LEA EAX,[EBP-8]
00419DFA POP EDX
00419DFB CALL <System::AnsiString::operator+(System::AnsiString &)>
00419E00 DEC DWORD PTR [EBP-30]
00419E03 LEA EAX,[EBP-14]
00419E06 MOV EDX,2
00419E0B CALL <System::AnsiString::~AnsiString(void)>
00419E10 MOV WORD PTR [EBP-3C],50
00419E16 MOV EDX,[EBP-10]
00419E19 MOV EAX,[EBP-54]
00419E1C CALL <TMD5::SetInputString(System::AnsiString)>
00419E21 MOV EAX,[EBP-54]
00419E24 CALL <TMD5::HashString(void)>
00419E29 MOV WORD PTR [EBP-3C],68
00419E2F MOV EDX,006AC3AC ; ASCII "++11IE:pGQKLK-NACiKpFfdc-PxcKOZAGeL0HPFm97h-sDI
; VWHjRC0c1xQYHXZx13o-oXQ2j2xaBjBL-SodBMOSXzIjo+"
00419E34 LEA EAX,[EBP-18]
00419E37 CALL <sub_5FAF4C>
00419E3C INC DWORD PTR [EBP-30]
00419E3F MOV EDX,[EAX]
00419E41 MOV EAX,[EBP-58]
00419E44 CALL <Rsa::TRSA::PutPublicKeyString(System::AnsiString &)>
00419E49 DEC DWORD PTR [EBP-30]
00419E4C LEA EAX,[EBP-18]
00419E4F MOV EDX,2
00419E54 CALL <System::AnsiString::~AnsiString(void)>
00419E59 MOV EDX,[EBP-C]
00419E5C MOV EAX,[EBP-58]
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:
00419E5C MOV EAX,[EBP-58]
00419E5F CALL <Rsa::TRSA::PutSignatureString(System::AnsiString)>
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))
005FFB0E JE SHORT <loc_5FFB40>
005FFB10 MOV EAX,[EBP-4]
005FFB13 ADD EAX,32
005FFB16 MOV EDX,005FFBD0 ; ASCII "Signature has incorrect length"
005FFB1B CALL <System::__linkproc__ LStrAsg(void)>
005FFB20 MOV EAX,[EBP-4]
005FFB23 CALL <Rsa::TRSA::DoStatusUpdate(void)>
005FFB28 MOV ECX,005FFBD0 ; ASCII "Signature has incorrect length"
005FFB2D MOV DL,1
005FFB2F MOV EAX,[<off_5CBCAC>]
005FFB34 CALL <unknown_libname_1105>
005FFB39 CALL <System::__linkproc__ RaiseExcept(void)>
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:
- Patchare il JE a JMP per forzare il salto a 005FFB40
- NOPpare di brutto la chiamata dall'esterno (a 00419E5F, dove eravamo prima di entrare)
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:
00419E59 MOV EDX,[EBP-C]
00419E5C MOV EAX,[EBP-58]
00419E5F NOP
00419E60 NOP
00419E61 NOP
00419E62 NOP
00419E63 NOP
00419E64 MOV EAX,[EBP-54]
00419E67 ADD EAX,28
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:
00419E89 MOV EAX,[EBP-58]
00419E8C POP ECX
00419E8D CALL <Rsa::TRSA::Verify(uchar *,int,int)>
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:
005FE64A JNZ SHORT <loc_5FE67B>
005FE64C LEA EAX,[ESI+32]
005FE64F MOV EDX,005FE718 ; ASCII "Public key not loaded"
005FE654 CALL <System::__linkproc__ LStrAsg(void)>
005FE659 MOV EAX,ESI
005FE65B CALL <Rsa::TRSA::DoStatusUpdate(void)>
005FE660 MOV ECX,005FE718 ; ASCII "Public key not loaded"
005FE665 MOV DL,1
005FE667 MOV EAX,[<off_5FDC40>]
005FE66C CALL <unknown_libname_1105>
005FE671 CALL <System::__linkproc__ RaiseExcept(void)>
005FE676 JMP <loc_5FE705>
005FE67B CMP BYTE PTR [ESI+884],0
005FE682 JNZ SHORT <loc_5FE6AE>
005FE684 LEA EAX,[ESI+32]
005FE687 MOV EDX,005FE738 ; ASCII "Signature not loaded"
005FE68C CALL <System::__linkproc__ LStrAsg(void)>
005FE691 MOV EAX,ESI
005FE693 CALL <Rsa::TRSA::DoStatusUpdate(void)>
005FE698 MOV ECX,005FE738 ; ASCII "Signature not loaded"
005FE69D MOV DL,1
005FE69F MOV EAX,[<off_5FDC40>]
005FE6A4 CALL <unknown_libname_1105>
005FE6A9 CALL <System::__linkproc__ RaiseExcept(void)>
005FE6AE MOV EDX,84 ; loc_5FE6AE
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, come 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:
00419E8C POP ECX
00419E8D NOP
00419E8E NOP
00419E8F NOP
00419E90 NOP
00419E91 NOP
00419E92 MOV [EBP-4D],AL
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! È andata! 8-)
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
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
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.