PTGui 4.1
(RSA? no, grazie, ho smesso!)

Data

by "Zero_G"

 

25/02/2005

UIC's Home Page

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

 

homepage: http://xxxxxxx.xxx.xxx/
email e contatto MSN: mailto:xxxxxx@xxxxxxx.xxx
nick su IRC [Azzurra (#crack-it)]: "Zero_G"

 

Difficoltà:

( )NewBies ( )Intermedio (X)Avanzato ( )Master

 

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):


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

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 o FTP del programma

Il programma "vittima" è reperibile direttamente dal sito proprietario h**p://www.ptgui.com; ('t' al posto degli asterischi)
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:

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):

........: ..... "..............."
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-sDIVWHjRC0c1xQYHXZx13o-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 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 >>

0012EFD8: <ptgui.loc_594E0B>
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...

<< Stack >>

0012D7D0: 00593DCF ptgui.00593DCF
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:

  1. 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
  2. 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
00E2AD84   2E 00 37 38 76 00 00 00 01 00 00 00 64 00 00 00   ..78v.......d...
00E2AD94   31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36   1234567890123456
00E2ADA4   37 38 39 30 31 32 33 34 35 36 37 38 39 30 31 32   7890123456789012
00E2ADB4   33 34 35 36 37 38 39 30 31 32 33 34 35 36 37 38   3456789012345678
00E2ADC4   39 30 31 32 33 34 35 36 37 38 39 30 31 32 33 34   9012345678901234
00E2ADD4   35 36 37 38 39 30 31 32 33 34 35 36 37 38 39 30   5678901234567890
00E2ADE4   31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36   1234567890123456
00E2ADF4   37 38 39 30 00 73 6F 20 B0 3E 71 00 B0 3E 71 00   7890.so .>q..>q.
00E2AE04   00 12 00 00 70 20 66 69 6C 65 73 20 66 72 6F 6D   ....p files from
00E2AE14   20 45 78 70 6C 6F 72 65 72 20 69 6E 74 6F 20 74   Explorer into t

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]               EDX contiene 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 dove scrivere il risultato della funzione
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          |
00419C42 . PUSH EAX                                and 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...

00419C63 > MOV WORD PTR SS:[EBP-3C],2C
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                               /-------|- secondo argomento (indice sinistro della sottostringa)
00419C7D . MOV EDX,1                            /---|-------|- primo argomento (indice destro della sottostringa)
00419C82 . CALL <System::AnsiString::SubString(int,int)>    \ ptgui.005FB588
 ----> 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>                                \ ptgui.0041A228
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...

  1. Prende il seriale troncato (esattamente quei primi 24 caratteri) e lo divide ulteriormente in 3 parti di 8 caratteri ciascuna;
  2. 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)
  3. 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:

  1. da 0041A228 a 0041A233 = inizializzazione
  2. da 0041A238 a 0041A295 = recupera il primo pezzo di seriale
  3. da 0041A29A a 0041A2D5 = concatenazione con "0x"
  4. da 0041A2D8 a 0041A2E2 = conversione in esadecimale e XOR con F9 83 6E 1B
  5. da 0041A2E5 a 0041A344 = strane manipolazioni sulla data e sull'ora correnti [???]
  6. da 0041A347 a 0041A370 = recupera il secondo pezzo di seriale
  7. da 0041A373 a 0041A3A9 = concatenazione con "0x"
  8. da 0041A3AC a 0041A3C2 = conversione in esadecimale e XOR con A5 B8 8F C1
  9. da 0041A3C5 a 0041A3EE = recupera il terzo pezzo di seriale
  10. da 0041A3F1 a 0041A427 = concatenazione con "0x"
  11. da 0041A42A a 0041A440 = conversione in esadecimale e XOR con 9C 16 94 A7
  12. 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:

0041A2D8 |. CALL <Sysutils::StrToInt(System::AnsiString)>       converte la stringa in numero esadecimale
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:

0041A3AC |. CALL <Sysutils::StrToInt(System::AnsiString)>       converte la stringa in numero esadecimale
0041A3B1 |. XOR EAX,A5B88FC1                                    la XORa con il 2° numeretto magico
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:

0041A42A |. CALL <Sysutils::StrToInt(System::AnsiString)>       converte la stringa in numero esadecimale
0041A42F |. XOR EAX,9C1694A7                                    la XORa con il 3 ° numeretto magico
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].

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

0012D738: 00000000 <= CIMA DELLO STACK (ESP)
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   <.Ô.,.........ó@
00E21670   F5 9D EE 91 F1 A0 04 CA 00 00 00 00 40 9E F3 40   õî.ñ..Ê....@žó@
00E21680   78 A7 E2 00 2C 00 00 00 2A 00 00 00 E4 1B 57 00   x§â.,...*...ä.W.

address
il nome
ASCII
00D4972C   6C 31 00 00 16 00 00 00 01 00 00 00 06 00 00 00   l1..............
00D4973C   5A 65 72 6F 5F 47 00 00 6A 00 00 00 34 F6 58 00   Zero_G..j...4öX.
00D4974C   00 00 00 00 00 00 00 00 DC DE D5 00 B0 97 D4 00   ........ÜÞÕ...Ô.

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
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 è 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
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:

00419D67 > MOV WORD PTR [EBP-3C],38                        \
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
00419D86 . LEA EAX,[EBP-4]                                 EAX = seriale originale
00419D89 . MOV EDX,19                                      EDX = 19h
00419D8E . CALL <System::AnsiString::SubString(int,int)>   prende la sottostringa da 19h (25) a length - 18h (24)

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

00419DC5 . MOV WORD PTR [EBP-3C],44
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-sDIVWHjRC0c1xQYHXZx13o-
                                               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:

00419E59 . MOV EDX,[EBP-C]
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))

005FFB0C . CMP EAX,EDX
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:

  1. Patchare il JE a JMP per forzare il salto a 005FFB40
  2. 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:

00419E54 . CALL <System::AnsiString::~AnsiString(void)>
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:

00419E87 . MOV EDX,EAX
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:

005FE643 |. CMP BYTE PTR [ESI+882],0
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, 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]
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! 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.