Zoom Icon

PTGui 4.1

From UIC

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

Contents


Infos
Author: Zero_G
Email: zero.g80@gmail.com
Website: http://zerog.altervista.org
Date: 25/02/2005 (dd/mm/yyyy)
Level: Quite hard
Language: Italian Image:Flag_Italian.gif
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.

<< 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
00E2AD84
00E2AD94
00E2ADA4
00E2ADB4
00E2ADC4
00E2ADD4
00E2ADE4
00E2ADF4
00E2AE04
00E2AE14

73 20 63 61 73 65 20 73 65 6E 73 69 74 69 76 65
2E 00 37 38 76 00 00 00 01 00 00 00 64 00 00 00
31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36
37 38 39 30 31 32 33 34 35 36 37 38 39 30 31 32
33 34 35 36 37 38 39 30 31 32 33 34 35 36 37 38
39 30 31 32 33 34 35 36 37 38 39 30 31 32 33 34
35 36 37 38 39 30 31 32 33 34 35 36 37 38 39 30
31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36
37 38 39 30 00 73 6F 20 B0 3E 71 00 B0 3E 71 00
00 12 00 00 70 20 66 69 6C 65 73 20 66 72 6F 6D
20 45 78 70 6C 6F 72 65 72 20 69 6E 74 6F 20 74

s case sensitive
..78v.......d...
1234567890123456
7890123456789012
3456789012345678
9012345678901234
5678901234567890
1234567890123456
7890.so .>q..>q.
....p files from
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]                           ; 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...

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                          ;     /-----|- 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...

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

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

<< 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
00E21670
00E21680

3C 97 D4 00 2C 00 00 00 00 00 00 00 90 80 F3 40
F5 9D EE 91 F1 A0 04 CA 00 00 00 00 40 9E F3 40
78 A7 E2 00 2C 00 00 00 2A 00 00 00 E4 1B 57 00

<.Ô.,.........ó@
õ?î.ñ..Ê....@žó@
x§â.,...*...ä.W.


address il nome ASCII

00D4972C
00D4973C
00D4974C

6C 31 00 00 16 00 00 00 01 00 00 00 06 00 00 00
5A 65 72 6F 5F 47 00 00 6A 00 00 00 34 F6 58 00
00 00 00 00 00 00 00 00 DC DE D5 00 B0 97 D4 00

l1..............
Zero_G..j...4öX.
........ÜÞÕ...Ô.

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

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

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

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

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

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.