VB Reversing - unleashed! -
(Framework reversing)

Data

by "AndreaGeddon"

 

21/02/2003

UIC's Home Page

Published by Quequero


why do i have a coscience

all it does is fuck with me

Il padre di Ciccio ha tre figli: Qui Quo e... Ciccio? no! Que!!! bwaaaa

why do i have this torment

all i wanna do is fuck it away

....

Home page se presente: http://andreageddon.8m.com
E-mail: andreageddon@homail.com
Irc:  irc.azzurra.net   #crack-it

....

Difficoltà

(x)NewBies (x)Intermedio ( )Avanzato ( )Master

 

Okey stiamo per affrontare il reversing di un semplice crackme scritto in Visual Basic (brrrr). Vediamo di capire un pochino come funziona questo framework del VB e come districarci nel reversing di questo tipo di applicazioni. Ringrazio Ntoskrnl che mi ha scritto il crackme :)


VB Reversing - unleashed! -
(Framework reversing)
Written by AndreaGeddon

Introduzione

Spesso ci capitano davanti programmi scritti in visual basic, e reversarli non è affatto piacevole! Questo tutorial affronta un pochino quelli che sono i problemi nel districarsi nel codice di un programma scritto in vb, ma bene o male come ragionamento è valido per tutti i
framework (mfc, delphi etc etc), spero che questo tutorial vi faccia smettere di scrivere in vb e tutto il resto! 
Il tutorial è diviso in due parti, nella prima analizziamo tutta la struttura dell'exe per capire come sono organizzate le istruzioni che il framework del visualbasic usa per descrivere l'applicazione, nella seconda parte invece vediamo l'algoritmo vero e proprio per ricavarci il serial valido. Volendo se trovate difficile la prima parte potete andare direttamente alla seconda. 
In fondo al tutorial inserisco il disegno dell'albero con le strutture, così mentre seguite la prima parte vi rimane più facile capire dove vi trovate e cosa state facendo! 

Tools usati

 

Un debugger, SoftIce o OllyDebug vanno benissimo, anche se con OllyDebug dovete stare attenti alla gestione di alcuni eventi

Ida, per disassemblare (no no niente Wdasm per piacere)

 

URL o FTP del programma

www.quequero.org :)

Notizie sul programma

Un banale crackme nome/serial con un paio di fronzoli, tipo edit box colorati (ci serviranno per studiare meglio la dinamica della gestione dei messaggi)  

Essay

 

Troppo spesso troviamo applicazioni scritte in questo merdoso VB. Ora non solo il linguaggio fa pena dal punto di vista delle performance, è anche un incubo da reversare! Come al solito l'astrazione del linguaggio si traduce in un groviglio orribile all'interno del programma, ed in effetti il difficile del VB è proprio di sapersi districare nella dll relativa. Partiamo dall'inizio.

 

Backwards Info

Il VB ha avuto una certa evoluzione, all'inizio non era neanche compilato, era solo pseudocodice interpretato (e già questo la dice lunga...), dalla versione 4 ha iniziato ad essere davvero compilato, e tuttora è SOLO compilato. In tutti i casi il VB si appoggia alla sua virtual machine, la sua relativa dll.

precedenti:  vbrun400.dll / vbrun300.dll / vbrun200.dll / vbrun100.dll

VB5:  msvbvm50.dll

VB6:  msvbvm60.dll

ci sono anche alcune piccole variazioni nella differenziazione dei moduli 16 o 32 bit, ma questo non ci interessa. Infatti noi ci concentriamo sugli ultimi due, che sono i più comuni e sono quasi uguali. In particolare per questo tutorial useremo un crackme scritto in VB6, ormai il 90% del sw che trovate scritto in VB è in versione 5 o 6.

Le versioni precedenti non erano compilate, cioè il programma che veniva scritto non veniva compilato in linguaggio macchina, ma in uno pseudocodice che era poi interpretato dalla virtual machine. Per reversare questo tipo di applicazioni non compilate è consigliabile un decompilatore, dopo aver decompilato potete studiare il sorgente basic per capire il programma, ma se dovete fare delle modifiche fisiche siete nella merda! Anche debuggare un programma in pcode è orribile, in quanto vi trovereste a dover steppare continuamente la libreria interprete, auguri! Ma veniamo finalmente a noi. Abbiamo il nostro crackme VB, è compilato per cui possiamo debuggarlo/disassemblarlo/modificarlo a piacimento. Il problema principale di un programma scritto in VB è che usa delle sue API proprietarie per interagire con i controlli etc etc, e queste api sono ovviamente undocumented. Per cui se ci troviamo in un programma VB e vogliamo per esempio breakare come facciamo sempre nel momento in cui prende il nostro Username e Serial non potremo usare GetDlgItemTextA o GetWindowTextA perchè non vengono usate :(( Vedremo ora come funziona il tutto.

Prima di tutto pensate sempre in modo semplice: gli attacchi più facili sono quelli che funzionano, quindi se avete una MessageBox di errore partite da lì. Ovviamente non potete usare l'api MessageBoxA ma dovete usare quella del VB che è rtcMsgBox. Problema comune: il softice non vi accetta i breakpoint sulle api del visual basic, vi dice che non sono simboli definiti. Per risolvere questo problema o aggiungete nel winice.dat la riga contente il path della dll della virtual machine tra le righe dove ci sono tutti gli export, oppure più semplicemente caricate il symbol loader, File->Load Exports e scegliete la libreria della virtual machine. Nota che anche se caricate la libreria quando settate il breakpoint potrebbe dirvi che il simbolo non è istanziato: questo perchè la dll non è ancora stata caricata. In tal caso dovete in qualche modo breakare DENTRO il programma vb usando qualche break qualsiasi, insomma dovete arrivare con il softice in qualche modo dentro il processo del programma vb, a quel punto tutti i break sulle api vb saranno accettati. Se siete su win9x e volete breakare quando il programma prende il testo potete usare l'api di sistema hmemcpy, da lì poi dovete fare un bel pò di backtrace con F12 per tornare al codice del programma. Se siete su nt/2k/xp però non potete usare tale api di sistema, per cui potreste avere qualche difficoltà. Altro attacco: anche qui funziona il discorso delle stringhe, cercate le stringhe del programma per cercare di localizzare il codice che volete. Solito problema: in WinDasm le stringhe non si vedono! Beh io vi dico sempre di usare Ida! Cmq il problema è dovuto al fatto che il visual basic utilizza le stringhe unicode, cioè stringhe in cui ogni carattere occupa due bytes (se non sapete cos'è l'unicode è ora di andare a studiarlo!). Le stringhe unicode per cui ci appariranno come char e zero interposti, ad esempio la stringa "123" sarebbe in esadecimale: 31 00 32 00 33 00, tenetelo bene a mente anche quando cercate stringhe in memoria nel softice!!!! Per poter vedere le stringhe unicode in wdasm dovete scaricare l'apposita patch da protools, oppure prendere una versione superiore alla 8.93.

 

Death and rebirth: prima parte, morte del framework: reversiamolo e capiamolo!

Ok ora che abbiamo dato qualche nozione di base passiamo al crackme in questione. Lanciamolo e vediamo che ci chiede nome e serial. Se inseriamo due valori a caso e premiamo "registra" non ci dà nessun errore, quindi niente message box di errore! Ora se vogliamo fare le cose sbrigative disassembliamo il crackme e cerchiamo qualche indizio tra le stringhe. In Ida andate nella string window, prima di tutto assicuriamoci che siano listate anche le stringhe unicode, basta fare refresh (o setup, dipende dalle versioni), e nel dialog box che si apre aggiungete se non c'è "Unicode". A questo punto abbiamo tutte le stringhe. Quella che attira la nostra attenzione è la seguente:

 

   Congratulazioni, il seriale inserito è quell

 

bene, evidentemente è lì che dobbiamo andare a finire quando inseriamo il seriale giusto! Vediamo la cross reference a questa stringa:

 

   .text:00404884 mov [ebp+var_98], offset aCongratulazion ; "Congratulazioni, il seriale inserito è "...

 

ok, ora abbiamo un indizio! Sotto questa linea viene chiamata la funzione rtcMsgBox

 

   .text:004048BC call ds:rtcMsgBox

 

quindi è chiaro che questo è il punto dove dobbiamo finire quando abbiamo azzeccato. Ora seguendo a ritroso il codice potete trovare i jump che vi fanno arrivare qui, in modo che modificandoli potete arrivare al messaggio di serial corretto anche se il serial inserito è sbagliato. Non siamo qui però per fare una patch, ma vogliamo trovare il seriale! In questo caso cercando tra le stringhe, o al massimo cercando le reference alla messageBox abbiamo trovato il punto che ci interessa, ma a volte nei programmi VB le cose potrebbero andarci peggio, per cui è importante capire come sono gestite le cose e capire come funziona l'engine del VB. Per cui tiratevi su le maniche ed iniziamo un reversing di tutto! Partiamo dall'entry point:

 

   .text:004013EC         public start
   .text:004013EC start:                        ; "VB5!"
   .text:004013EC    push offset RT_GLOBAL
   .text:004013F1    call ThunRTMain

tutto qui? Solo una chiamata a ThunRTMain. Questa chiamata infatti traferisce il controllo alla virtual machine (il Thunder Run Time Engine), che fa il suo porco lavoro di inizializzazione e tutto il resto. E già qui siamo in crisi: se steppate questa call con F10 non tornerete mai al codice, quindi come facciamo a continuare? Di certo non vogliamo stepparci due milioni di righe della msvbvm!!

Per cui iniziamo a ragionare. Se vedete il disasm abbiamo molte righe di codice, evidentemente corrispondono a routines che sono chiamate in base agli eventi che accadono nel crackme, la gestione degli eventi avviene nella virtual machine, ma le routines relative DEVONO risiedere nel codice! E questo già ci è di aiuto, perchè nel codice abbiamo solo le routine che vengono richiamate in base agli eventi. In pratica tutte le routines che troviamo nel codice sono dei callback richiamati dalla DLL (certo ci potrebbero essere delle funzioni interne, ma quelle le potete riconoscere dalle relative xrefs). Uhm spero non abbiate le idee confuse! Continuiamo nel nostro ragionamento: le routine hanno degli indirizzi, giusto? Ora la DLL come fa a sapere l'indirizzo di queste routine da richiamare in base agli eventi? Finora abbiamo incontrato solo la chiamata a ThunRTMain! Se guardate bene prima di quella CALL c'è un PUSH, RT_GLOBAL è il nome che ho usato io, voi vedrete un indirizzo di memoria, potete rinominarlo se vi fa comodo :). Cmq viene passato questo indirizzo come parametro, evidentemente questo indirizzo deve essere un puntatore a una struttura che contiene TUTTE le informazioni necessarie per lo svolgimento del programma, la chiave sta solo nel saper capire questa struttura :). Per prima cosa andate all'indirizzo di RT_GLOBAL e vedrete tanti bytes: dobbiamo organizzarli in dwords (cosa che dovreste fare sempre!) per cui premete il tasto D a volontà e trasformate tutto in dd. Dovremmo vedere questo:

 

   .text:00401AA8 RT_GLOBAL    db    'VB5!' ;      DATA XREF: .text:004013ECo
   .text:00401AAC                    dd    2A1FF0h
   .text:00401AB0                    dd    0
   .text:00401AB4                    dd    0
   .text:00401AB8                    dd    0
   .text:00401ABC                    dd    7Eh
   .text:00401AC0                    dd    0
   .text:00401AC4                    dd    0
   .text:00401AC8                    dd    0A0000h
   .text:00401ACC                    dd    410h
   .text:00401AD0                    dd    0
   .text:00401AD4                    dd    0
   .text:00401AD8                    dd    offset pProjectStruct
   .text:00401ADC                    dd    30F016h
   .text:00401AE0                    dd    0FFFFFF00h
   .text:00401AE4                    dd    8
   .text:00401AE8                    dd    1
   .text:00401AEC                    dd    2
   .text:00401AF0                    dd    0E9h
   .text:00401AF4                    dd    offset pDialogs
   .text:00401AF8                    dd    offset pExtern
   .text:00401AFC                    dd    offset PProjectInfo
   .text:00401B00                    dd    78h
   .text:00401B04                    dd    84h
   .text:00401B08                    dd    8Dh
   .text:00401B0C                    dd    8Eh
   .text:00401B10                    dd    0
   .text:00401B14                    dd    0
   .text:00401B18                    dd    0
   .text:00401B1C                    dd    0

questa è la struttura, immaginiamo ch finisca a 00401B1C perchè subito dopo ci sono delle stringhe. La prima dword è la stringa "VB5!", che in pratica sarebbe la signature della struttura (la stringa dice vb5 anche se in realtà questo programma è stato scritto con il vb6, che lami manco so boni ad aggiornare le strutture interne!). Di seguito vediamo altre dwords con valori vari. Cosa stiamo cercando? In pratica questa struttura dovrebbe avere dei puntatori che in qualche modo descrivono tutto il programma, dialogs e relative routine di controllo. Dal crackme possiamo vedere che c'è un dialog principale più uno richiamabile con il pulsante about, quindi dovremmo trovare informazioni su questi due oggetti. All'address 00401AD8 vedete il puntatore a un qualche dato, Ida non risolve in automatico quel puntatore, ve lo da come "costante sospetta", per cui andate sopra a tutte le costanti sospette (hanno tutte lo stesso colore :)) e premete il tasto "O", questo li trasformerà in puntatori, la cosa è molto importante perchè così verranno generate tutte le xrefs. Come vi ho detto dovete passare al setaccio tutto la sezione .text e convertire in DWORDS tutti i valori, ricordatevi che i valori rispettano sempre l'allineamento per cui dovete sempre allineare a dwords ad indirizzi multipli di 4 (xxx0, xxx4, xxx8, xxxC). In questo caso abbiamo tutte le dwords pronte. Cmq potete fare l'allineamento man mano che leggete il tutorial. Torniamo alla struttura: gli unici dati utili sono i puntatori, per cui dobbiamo concentrare l'attenzione alle righe 00401AD8, 00401AF4, 00401AF8, 00401AFC. Vediamo a quali informazioni puntano. Io ho usato dei nomi, voi vedrete degli indirizzi ovviamente! Ed ovviamente ho usato quei nomi perchè sono andato a vedere a cosa corrispondono quegli address.

Parentesi:

  come notate tutti i dati fissi e le stringhe sono NELLA sezione .text: ora sebbene ciò funzioni, ai fini teorici è un orrore! Infatti da sempre la teoria concepisce i programmi con sezioni separate a seconda delle caratteristiche, cioè R W X (Read Write eXecute) etc etc. In questo caso la sezione data sarà effettivamente usata SOLO per i dati RW, mentre la text per i dati RO X. Questo ci è un pò di aiuto perchè mentre studiamo le strutture magari se troviamo dei campi a zero nella .data sappiamo che quel campo della struttura viene cambiato a runtime, ci basta vedere se il suo valore risiede nella .data o nella .text! Ciò è utile perchè se abbiamo dei puntatori a dati rw è chiaro che dovremo andare a vedere come vengono modificati a runtime per poterli interpretare! Certo di buona norma ci sarebbe dovuta essere una sezione text, una per i dati RO e una per i dati RW, ma possiamo pensare che così abbiamo risparmiato alcuni bytes che avremmo perso per l'alignment usando due sezioni separate, e per stavolta perdoniamo il vb!

Altra cosa, guardiamo le imports, sono risolte come

.text:0040138A jmp ds:rtcMsgBox
.text:00401390 jmp ds:__vbaStrCmp
cioè le import in VB sono risolte sempre nello stile declspec(dllimport), e ciò è male! Infatti quando il codice fa una CALL API voi chiamate in realtà il JUMP all'api (il blocco dei jump api è chiamato "transfer area" o "trampolino"), ok ciò funziona ma dal lato teorico abbiamo un passaggio in più (call -> jmp -> api    invece che   call -> api), ed inoltre se voi voleste fare un controllo di integrità sulle funzioni, il vostro function pointer alla api punterebbe alla transfer area e non al codice della funzione! Inoltre chi scrive crypters si ritrova sempre una rottura di scatole con la transfer area, specie quando vuole implementare pesanti tricks sulla iat!

E se questo non bastasse vedremo altre idiozie del compilatore, tipo istruzioni inutili, istruzioni ridondanti etc

Vi sto convincendo che il vb è la fonte del male nel mondo (informatico) ????

Beh basta guerre personali, torniamo al nostro reversing. Abbiamo dato un senso alla struttura RT_GLOBAL, ora ovviamente vorrete sapere come ho giustificato quei nomi e a che cosa servono. Partiamo dall'ultima struttura, pProjectInfo, ecco dove ci porta

 

.text:004013F8 pProjectInfo dd 0                 ; field_0 DATA XREF: .text:00401AA8o
.text:004013F8                  dd 30h              ; field_4
.text:004013F8                  dd 40h              ; field_8
.text:004013F8                  dd 0                 ; field_C
.text:004013F8                  dd 0B659B23Fh  ; field_10
.text:004013F8                  dd 4379F64Dh    ; field_14
.text:004013F8                  dd 0DB15E5BEh  ; field_18
.text:004013F8                  dd 0ACB73717h  ; field_1C
.text:004013F8                  dd 0                 ; field_20
.text:004013F8                  dd 10000h         ; field_24
.text:004013F8                  dd 0                 ; field_28
.text:004013F8                  dd 0                 ; field_2C
.text:004013F8                  db 'Progetto1',0,0,0,0,0,0,0,0,0,0,0; Project_Name


anche qui il limite alla struttura lo diamo in base al fatto che dopo quest'ultima riga ce n'è una che è referenceata da altro codice, il che vuol dire che è un altra struttura (ecco perchè vi dicevo di convertire tutti i suspicious data in offset! con xrefs si vedono molte più cose!). Vabbè cmq qui abbiamo diversi numerelli, che non so a cosa servano ma possiamo immaginare che siano menate tipo timedatestamp e flags vari. In ultimo troviamo la stringa con il nome del progetto. Questa è la struttura che ho chiamato _PROJECT_INFO, non ve la scrivo perchè tanto come vedete è noto solo l'ultimo membro! Cmq qui ci fermiamo, non ci sono altre sottostrutture nè puntatori a dati utili. Tornando a RT_GLOBAL stavolta andiamo a vedere il puntatore a pExtern. Questo punta a 

 

.text:00401A5C pExtern dd 7 ; DATA XREF: .text:00401AA8o
.text:00401A60            dd offset ImportData
.text:00401A64            dd 6
.text:00401A68            dd offset InsideImport


questo codice. Anche qui ho messo dei nomi, andiamo a vedere perchè li ho chiamati così. Seguiamo il primo offset, ImportData, e vediamo il codice seguente:

 

.text:00402DA4 ImportData dd offset aShell32 ; DATA XREF: .text:00401A60o    "shell32"
.text:00402DA8                 dd offset aShellexecutea ; "ShellExecuteA"
.text:00402DAC align 8
.text:00402DB0                 dd offset IAT_Data
.text:00402DB4                 dd 2 dup(0)


ed IAT_DATA punta a 

 

.data:004063A4 IAT_Data                     dd 0 ; DATA XREF: .text:00402DB0o
.data:004063A8 ShellExecute_Instance   dd 0
.data:004063AC Address_ShellExecute    dd 0 ; DATA XREF: DLL_Importr

spiegazione:ImportData punta ai nomi delle funzioni "extra" importate dal progetto, e IAT_DATA punta (se li vedete a runtime) all'image base della dll Shell32 e all'address dell'api ShellExecuteA. Sappiamo che il programma ha fatto un uso forzato di questa api (senza cioè passare per il framework), in pratica usa ShellExecuteA dall'about box per lanciare il browser dai due url presenti. Allo stesso modo InsideImport (non vi pasto tutti i bytes) punta a un array con due puntatori, il primo punta a qualche sorta di descrittore, il secondo punta ad una dword che prende l'offset di una funzione della msvbvm, in pratica questo ImportData fornisce il thunking a delle funzioni interne alla virtual machine. E con questo anche pExtern è sistemata.

Torniamo a RT_GLOBAL e stavolta vediamo a cosa punta pDialogs:

 

.text:00401B40 pDialogs dd 50h               ; SizeOfStruct
.text:00401B40             dd 0C0730B8Ch   ; field_4
.text:00401B40             dd 48BA1AF8h     ; field_8
.text:00401B40             dd 19855DAAh     ; field_C
.text:00401B40             dd 850239DCh     ; field_10
.text:00401B40             dd 0                  ; field_14
.text:00401B40             dd 0                  ; field_18
.text:00401B40             dd 0                  ; field_1C
.text:00401B40             dd 0                  ; field_20
.text:00401B40             dd 0                  ; field_24
.text:00401B40             dd 310h             ; field_28
.text:00401B40             dd 0                  ; field_2C
.text:00401B40             dd 0                  ; field_30
.text:00401B40             dd 0                  ; field_34
.text:00401B40             dd 0                  ; field_38
.text:00401B40             dd 0                  ; field_3C
.text:00401B40             dd 61Eh             ; field_40
.text:00401B40             dd 0                  ; field_44
.text:00401B40             dd offset MainDialogInfo            ; DialogData
.text:00401B40             dd 4Ch             ; terminatore

questa è la prima struttura, corrisponde al dialog principale, subito dopo ce n'è un'altra che corrisponde al dialog di about. Come avete capito questa lista di struttura ha delle descrizioni sui controlli, stile, aspetto dei dialogs presenti nell'applicazione. L'ultimo campo, terminatore, serve ad indicare la terminazione della lista di strutture (4Ch = continua, 7Ch = fine). Il penultimo campo punta ad altre informazioni (MainDialogInfo, nell'altra struttura il puntatore punta a AboutDialogInfo :) ), se ci andate vedrete una sfilza di valori che non sto a listare, cose tra cui

 

.text:004014A0 aMain                 db 'main',0
.text:004014A5                         dd 1A010Dh
.text:004014A9 aSimpleCrackmeB db 'Simple Crackme by Ntoskrnl',0


il che era utile per capire di quale dialog si trattasse :), idem per il secondo, ci troviamo testi relativi all'about box. Altre informazioni utili in questa struttura non ce ne sono, abbiamo esaurito i rami. Per cui di nuovo torniamo a RT_GLOBAL. Come vedete sappiamo già un sacco di cose solo dal disassembly, ma a livello pratico, sul codice e sulla sua gestione non sappiamo ancora nulla. Per questo ritorniamo a vedere l'ultimo campo che ancora non abbiamo esaminato di RT_GLOBAL, cioè pProjectStruct. Questo punta a una struttura che contiene un tree con tutte le informazioni del progetto:

 

.text:00401BE0 pProjectStruct dd 1F4h                        ; unk0 ; DATA XREF: .text:00401AA8o
.text:00401BE0                      dd offset pTree             ; Tree_Modules
.text:00401BE0                      dd 0                            ; unk8
.text:00401BE0                      dd offset Start_OfCode   ; StartOfCode
.text:00401BE0                      dd offset End_OfCode     ; EndOfCode
.text:00401BE0                      dd 1200h                      ; unk14
.text:00401BE0                      dd offset Proj_Var_1       ; Unk_Var1
.text:00401BE0                      dd offset pVbaEH           ; BaseExceptionHandler
.text:00401BE0                      dd offset Proj_Var_2       ; Unk_Var2
.text:00401BE0                      db '*', 0, '\', 0, 'A', 0, 'C', 0, ':', 0, '\', 0, 'W'; ProjectPath
.text:00401BE0                      db 0, 'I', 0, 'N', 0, 'D', 0, 'O', 0, 'W', 0, 'S', 0, '\'; ProjectPath
.text:00401BE0                      db 0, 'D', 0, 'e', 0, 's', 0, 'k', 0, 't', 0, 'o', 0, 'p'; ProjectPath
.text:00401BE0                      db 0, '\', 0, 'S', 0, 'c', 0, 'r', 0, 'a', 0, 'c', 0, 'k'; ProjectPath
.text:00401BE0                      db 0, 'm', 0, 'e', 0, '\', 0, 's', 0, 'c', 0, 'r', 0, 'a'; ProjectPath
.text:00401BE0                      db 0, 'c', 0, 'k', 0, 'm', 0, 'e', 0, '\', 0, 's', 0, 'i'; ProjectPath
.text:00401BE0                      db 0, 'm', 0, 'p', 0, 'l', 0, 'e', 0, 'c', 0, 'r', 0, 'a'; ProjectPath
.text:00401BE0                      db 0, 'c', 0, 'k', 0, '.', 0, 'v', 0, 'b', 0, 'p', 1A3h dup(0); ProjectPath
.text:00401BE0                      dd offset pExtern           ; Extern_Import
.text:00401E18                      dd 2

come vedete anche qui è ripetuto il puntatore alle import che abbiamo visto prima! A parte questo vediamo che c'è il pVbaEH che è l'exception handler di default per il progetto, poi in ProjectPath abbiamo il percorso del progetto (non si vede molto bene ma la stringa è C:\Windows\Desktop\Scrackme\Scrackme\simplecrackme.vbp) il che ci fa anche pensare che chi ha scritto questa roba l'ha fatto su windows 9x! Start_OfCode e End_OfCode identificano rispettivamente la signature che sta immediatamente prima e immediatamente dopo di tutto il codice! (Tale signature è 0xE9E9E9E9, i CC di contorno sono padding) Quindi questi due valori ci dicono DOVE si trova tutto il codice di preciso. Rimane in tutto solo pTree (che è tutt'altro che facile!):

 

.text:004027A4 pTree  dd 0                        ; Unk0 ; DATA XREF: .text:00401A70o
.text:004027A4           dd offset pVBFunc     ; SVar1 ;
.text:004027A4           dd offset pTreeData   ; pData
.text:004027A4           dd 0FFFFFFFFh          ; UnkC
.text:004027A4           dd 0                        ; Unk10
.text:004027A4           dd offset PTree_Unk_var1; SVar2
.text:004027A4           dd 3858EC10h           ; SData1
.text:004027A4           dd 4D694AEAh          ; SData2
.text:004027A4           dd 0A6D4C5BFh         ; SData3
.text:004027A4           dd 36F89EDAh          ; SData4
.text:004027A4           dd 3000Ah               ; SData5
.text:004027A4           dd 30003h               ; SData6
.text:004027A4           dd offset pModules1     ; pModulesList
.text:004027A4           dd 0                       ; Unk34
.text:004027A4           dd 0                       ; Unk38
.text:004027A4           dd 0                       ; Unk3C
.text:004027A4           dd offset aProgetto1_1     ; pProjectName
.text:004027A4           dd 409h                  ; Unk44
.text:004027A4           dd 410h                  ; Unk48
.text:004027A4           dd 0                       ; Unk4C
.text:004027A4           dd 2                       ; Unk50

pVBFunc punta a qualche funzione interna della msvbvm, potrebbe essere tipo un costruttore o qualche inizializzatore (boh). Ci sono un altro paio di variabili che non ho capito cosa fanno, poi come vedete c'è il puntatore al nome del progetto.

Altra cosa, va bene l'astrazione ma che senso ha lasciare informazioni sul nome dei moduli e del progetto nel compilato finale?? E questo è un problema di molti compilatori non solo del vb, boh!

Ora di campi interessanti abbiamo pTreeData e pModules1. Rispettivamente puntano ai dati del project tree e all'array di descrittori dei moduli. Ci stiamo avvicinando sempre più a quello che ci interessa! Partiamo da pTreeData:

 

.text:00403B30 pTreeData  dd 0                ; DATA XREF: .text:004027A4o
.text:00403B34                 dd offset pTree
.text:00403B38                 dd 0FFFFFFFFh
.text:00403B3C                 dd 0
.text:00403B40                 dd offset pFormList
.text:00403B44                 dd 0
.text:00403B48                 dd 0
.text:00403B4C                 dd 0
.text:00403B50                 dd 0FFFFFFFFh
.text:00403B54                 dd 0
.text:00403B58                 dd offset ProjInfo2
.text:00403B5C                 dd offset PjRaw1
.text:00403B60                 dd offset unkVar1
.text:00403B64                 dd offset ProjInfo2
.text:00403B68                 dd offset PjRaw2
.text:00403B6C                 dd offset unkVar2
.text:00403B70                 dd offset ProjInfo2
.text:00403B74                 dd offset PjRaw3
.text:00403B78                 dd offset unkVar3
.text:00403B7C                 dd offset ProjInfo2
.text:00403B80                 dd offset PjRaw4
.text:00403B84                 dd offset unkVar4

all'inizio abbiamo un pTree, cioè un pointer alla struttura da cui siamo arrivati, poi vediamo quei ProjInfo2 che puntano, se andate a vedere, ad una zona dove c'è la stringa

 

db 'C:\Programmi\Microsoft Visual Studio\VB98\VB6.OLB',0

 

e altri valori e offset di variabili, quindi queste sono ancora informazioni sul progetto nel framework. I vari PjRawx sono anche questi puntatori a numerelli vari, e le varie UnkVar sono puntatori a variabili che a runtime sembrano rimanere sempre a zero. Boh cmq non ci frega, sono informazioni che non servono a nulla per i nostri fini. Quello che ci interessa è pFormList. Andiamoci e troviamo

 

.text:00403A70 pFormList   dd offset pForm1     ; DATA XREF: .text:00403B40o
.text:00403A74                 dd offset pForm2
.text:00403A78                 dd 620318h

cioè la lista di puntatori a descrittori dei Form. I form in VB sarebbero i vari dialog. Ora vediamo che questi puntatori puntano a due strutture identiche (ovviamente riempite con dati diversi per i due form), la struttura è la seguente

 

.text:00403AB0  pForm1   dd 0      ; DATA XREF: .text:0040223Co
.text:00403AB4              dd offset pMod1Descriptor
.text:00403AB8              dd 0FFFFFFFFh
.text:00403ABC              dd 0
.text:00403AC0              dd 0
.text:00403AC4              dd 0
.text:00403AC8              dd offset unkFormListVar1
.text:00403ACC              dd 0
.text:00403AD0              dd offset unkFormListVar2
.text:00403AD4              dd offset unkFormListVar2
.text:00403AD8              dd offset unkFormListVar2
.text:00403ADC              dd 0
.text:00403AE0              dd 0
.text:00403AE4              dd 0
.text:00403AE8              dd 58h
.text:00403AEC              dd 4

vediamo dei puntatori a variabili di cui non sappiamo il significato, ma ciò che importa è il pMod1Descriptor! Adesso ci dobbiamo fermare qui: infatti questo puntatore lo ritroveremo nella lista dei moduli (pModules1 in pTree, ricordate?) quindi andiamo a ricercarli da lì! Tornate in pTree e vediamo che c'era questa riga

 

.text:004027A4           dd offset pModules1     ; pModulesList

cioè il puntatore alla lista dei moduli (MODULI, non FORM!! Ogni modulo è un file-src separato, e in questo progetto ce ne sono 3, l'abbiamo visto prima, ricordate?). Okey tranquilli che abbiamo quasi finito! Anche qui la struttura dei tre moduli è la stessa, cambiano solo i valori:

 

.text:004027F8   pModules1 dd offset pMod1Descriptor  ; field_0
.text:004027F8                  dd 0FFFFFFFFh       ; field_4 

.text:004027F8                  dd offset pFlags1    ; Flags
.text:004027F8                  dd 0                     ; field_C
.text:004027F8                  dd 0                     ; field_10
.text:004027F8                  dd 0                     ; field_14
.text:004027F8                  dd offset aMain_0   ; ModuleName
.text:004027F8                  dd 0Dh                  ; field_1C
.text:004027F8                  dd offset unkModVar    ; AdditionalData
.text:004027F8                  dd 0FFFFh             ; field_24
.text:004027F8                  dd 18083h             ; field_28
.text:004027F8                  dd 0                     ; field_2C

le tre strutture sono consecutive, per cui subito dopo di questa c'è la seconda e poi la terza. Vediamo qui un puntatore alle Flags per il modulo (o per lo meno ciò che PENSO siano flags :)), il puntatore al nome del modulo (vediamo nelle tre strutture i nomi "main", "aboutfrm", "mod"), un pointer ad additional data (che punta ad un'area vuota in cui a runtime vengono storati alcuni valori, boh!). Ed infine ciò che più serve, il primo campo di questa struttura è pModxDescriptor, cioè il puntatore al descrittore del modulo stesso. In caso abbiate problemi i tre puntatore ai tre descrittori sono:

 

.text:00402230  pMod1Descriptor   dd 1

.text:00401E1C  pMod2Descriptor   dd 10001h

.text:00401A6C  pMod3Descriptor   dd 20001h 

 

puff puff non si finisce più con queste dannate struct eh?? Partiamo dal primo modulo, quello corrispondende al "main", quindi dovrebbe corrispondere al main dialog. Ora questi puntatori puntano a una struttura che cambia in base al fatto che il src-module a cui si riferiscono sia un .frm o un .bas, ed infatti i primi due moduli sono frm (e hanno la stessa struct), mentre il terzo è un bas e ha una struct diversa. Vediamo questo descrittore del primo form:

 

.text:00402230  pMod1Descriptor dd 1                    ; field_0
.text:00402230                         dd offset pTree     ; BackToTree
.text:00402230                         dd 0                    ; field_8
.text:00402230                         dd offset pForm1   ; FormInfo
.text:00402230                         dd 0FFFFFFFFh      ; field_10
.text:00402230                         dd 0                    ; field_14
.text:00402230                         dd offset pModules1    ; BackToModuleList
.text:00402230                         dd offset Proj_Var_1    ; unkVar
.text:00402230                         dd 0                     ; field_20
.text:00402230                         dd 6066CCh           ; field_24
.text:00402230                         dd 0                     ; field_28
.text:00402230                         dd 0                     ; field_2C
.text:00402230                         dd 0                     ; BoolDoublePtrIndex1
.text:00402230                         dd offset ppRaw     ; RawData1
.text:00402230                         dd 1                     ; BoolDoublePtrIndex2
.text:00402230                         dd offset pRaw2     ; RawData2
.text:00402230                         dd 0                     ; BoolDoublePtrIndex3
.text:00402230                         dd offset ppRaw     ; RawData3
.text:00402230                         dd 1                     ; BoolDblPtr
.text:00402230                         dd offset Main_Controls_List     ; ControlsListx
.text:00402230                         dd 0                                     ; BoolDoublePtrIndex4
.text:00402230                         dd offset ppRaw2                    ; RawData4
.text:00402230                         dd 7                                     ; NumberOfControls
.text:00402230                         dd offset Main_Controls_List     ; ControlsList
.text:00402230                         dd 1B7000Dh                          ; field_60
.text:00402230                         dd 6C0068h                            ; field_64
.text:00402230                         dd offset pMainDispatcher         ; GlobalDispatchTable
.text:00402230                         dd offset UnkMod1Var              ; UnkVar2
.text:00402230                         dd 0                                     ; field_70
.text:00402230                         dd 625B54h                            ; field_74

Vedete che all'inizio i primi quattro puntatori sono cose già incontrate (pTree, pForm1, pModules1, sono tutti back pointers, in più c'è un ptr a variabile globale del progetto). I BoolDoublePtrIndex sono dei bool che indicano se il puntatore successivo è un puntatore diretto o un double pointer (0 = dbl-ptr, 1 = direct-ptr), danno indicazioni sui vari p/ppRaw, che sono puntatori a dei dati numerici per il modulo, poco interessano ai nostri fini. La cosa più importante è il campo ControlsList, che punta al Main_Controls_List, cioè alla lista dei controlli del dialog. Il campo precedente, NumberOfControls, indica quanti sono i controlli nella lista (oddio, è in realtà un array di strutture e non una lista nel vero senso della parola, come le altre!), cmq ci torniamo fra un attimo alla lista dei controlli. Vediamo finalmente quello che davvero stavamo cercando: la GlobalDispatchTable! Questo valore punta all'event dispatcher di tutto il dialog (o form che dir si voglia), andiamo a vedere:

 

.text:004023C8  pMainDispatcher  dd offset Null_Event1   ; DATA XREF: .text:00402230o
.text:004023CC                         dd offset Null_Event2
.text:004023D0                         dd offset Null_Event3
.text:004023D4                         dd offset pAbout_Action2
.text:004023D8                         dd offset pAbout_Set_Color2
.text:004023DC                         dd offset pAbout_Unset_Color2
.text:004023E0                         dd offset pRegister_Action2
.text:004023E4                         dd offset pRegister_Set_Color2
.text:004023E8                         dd offset pRegister_Unset_Color2
.text:004023EC                         dd offset pSerial_Set_Color2
.text:004023F0                         dd offset pSerial_Unset_Color2
.text:004023F4                         dd offset pName_Set_Color2
.text:004023F8                         dd offset pName_Unset_Color2

queste sono tutte le azioni user defined che avvengono nel programma. Ogni puntatore di questi punta ad un'entry del tipo:

 

.text:0040271F pAbout_Action: ; DATA XREF: .text:004026B4o
.text:0040271F sub dword ptr [esp+4], 4Bh
.text:00402727 pAbout_Action2: ; DATA XREF: .text:004023D4o
.text:00402727 jmp About_ACTION


dove il JUMP vi porta alla routine di codice vera e proria (è una transfer area usata internamente). Come ho fatto a capire a cosa corrispondevano le routine? Beh senza andare a studiarvi il codice delle routine basta fare una prova semplice semplice, ora che avete tutti i jump di tutti gli eventi mettete un bpx sopra ogni jump, lasciate runnare l'applicazione, fate una qualche azione (tipo premete un pulsante) e vedete quali eventi vengono spawnati, alla fine due più due fa quattro e vi ritrovate la mappa di eventi risolta!

Oppure ancora meglio, se avete tanti eventi può diventare un casino breakare su tutti i jump, per cui vediamo come tramite le strutture di descrizione possiamo svangarcela meglio. Torniamo a pModDescriptor1, avevamo un bel puntatore a Main_Controls_List, che punta ad un array di strutture che descrivono il controllo. Vediamo per esempio la prima di queste strutture:

 

.text:004022B0 Main_Controls_List  dd 110040h    ; Style
.text:004022B0                           dd 34h           ; Control_ID
.text:004022B0                           dd offset PRjaw1    ; RawData
.text:004022B0                           dd 30002h             ; field_C
.text:004022B0                           dd 0                     ; field_10
.text:004022B0                           dd 0                     ; field_14
.text:004022B0                           dd offset Register_Dispatcher       ; PrivateDispatchTable
.text:004022B0                           dd 625F54h                        ; field_1C
.text:004022B0                           dd offset aRegister_0           ; ControlName
.text:004022B0                           dd 30002h                          ; field_24

come vede dal control name qui si riferisce al pulsante "register", quello che più ci interessa in questa struttura è la PrivateDispatchTable! Se ci andiamo infatti troviamo:

 

.text:004023FC Register_Dispatcher  dd 0   ; DATA XREF: .text:004022B0o
.text:00402400                             dd offset Main_Controls_List
.text:00402404                             dd offset pMod1Descriptor
.text:00402408                             dd offset pEVENT_SINK_QueryInterface
.text:0040240C                             dd offset pEVENT_SINK_Addref
.text:00402410                             dd offset pEVENT_SINK_Release
.text:00402414                             dd offset pRegister_Action
.text:00402418                             dd 0
.text:0040241C                             dd 0
.text:00402420                             dd offset pRegister_Set_Color
.text:00402424                             dd 0
.text:00402428                             dd 0
.text:0040242C                             dd 0
.text:00402430                             dd offset pRegister_Unset_Color
.text:00402434                             dd 0
.text:00402438                             db 20h dup( 0)

et voilà! Qui troviamo vabbè innanzitutto il backpointer a Main_Controls_List, e poi tutto l'array di puntatori a eventi, in questo caso sono SOLO gli eventi riguardanti il pulsante, se tornate alla lista dei controlli vedete che per ogni controllo c'è una PrivateDispatchTable, in tal caso potete avere tutte le info che volete su un controllo! Beh per esempio in questo crackme a noi potrebbe interessare particolarmente la routine richiamata dal pulsante "register", visto che sarà quella che calcola il seriale. La routine è quella puntata da pRegisterAction :).

E con questo direi che abbiamo finito, se tornate indietro alla ModuleList potrete constatare che per pMod2Descriptor il discorso è lo stesso, troverete una GlobalDispatchTable, la lista dei controlli ognuno con la sua PrivateDispatchTable.

Ecco fatto, abbiamo finalmente terminato! Come vedete ci è bastata IDA e un pò di zen e già dominiamo l'applicazione! Ora sapete come districarvi nel codice di un exe vb, il problema rimane sempre che il vb usa funzioni proprietarie, che spesso non sono intuitive, per cui venire a capo di un algoritmo cmq è una rogna.

 

Death and rebirth: seconda parte, rinascita del reverser: reversiamo l'algo e troviamo il seriale!

Dopo tutto questo bordello siamo in grado di dominare completamente il programma, ora ci resta solo da reversare quello che ci interessa. Abbiamo trovato con un Ida e un pò di zen la routine chiamata quando si preme il pulsante "register", quindi andiamo ad esaminarla. Nel caso vi siate persi l'indrizzo la routine la trovate qui:

 

.text:00403EA0  Register_ACTION   proc near ; CODE XREF: .text:0040274Ej

 

Register_ACTION è il nome simbolico che ho dato io per indicare l'azione legata al pulsante (ci sono anche le azioni di cambio colore). Diamo uno sguardo:

 

.text:00403EA0  push ebp                    ; creazione stackframe locale
.text:00403EA1  mov ebp, esp
.text:00403EA3  sub esp, 18h               ; alloca spazio per le variabili
.text:00403EA6  push offset pVbaEH
.text:00403EAB  mov eax, large fs:0      ; installa il SEH handler per la routine...
.text:00403EB1  push eax                    ; ...questo seh punta all'api del vb  __vbaExceptHandler
.text:00403EB2  mov large fs:0, esp
.text:00403EB9  mov eax, 104h
.text:00403EBE  call __vbaChkstk         ; controlla la validità dello stack e alloca altri 104h bytes
.text:00403EC3  push ebx                   ; save registers area
.text:00403EC4  push esi
.text:00403EC5  push edi

...

ok questo è l'inizio della routine, ovviamente lo stile di compilazione è quello di una funzione (così come le vedreste in C), chiaramente qui il VB aggiunge il suo codice, cioè il __vbaChkstk in caso di richiesta di molto spazio per l'allocazione delle variabili locali, altrimenti vedreste due  "sub  esp, xx". Qui già ne vedete uno che alloca 18h bytes nello stack (ma che bisogno c'è di allocare due volte??? Stupido vb!), in caso non lo sapeste le variabili locali di una funzione vengono messe sullo stackframe privato della funzione, in quanto appunto il loro arco di vita riguarda solo la funzione. A parte lavoretti sullo stack poi il VB installa per OGNI funzione il suo exception handler privato, che corrisponde all'api che ho scritto nei commenti alla riga 00403EB1. Cmq, l'importate è che sappiate riconoscere una funzione, IDA vi è certamente di aiuto in questo, ma a volte dovete riconoscerla ad occhio e, come vedete, il pattern è quasi sempre lo stesso per cui nema problema. Cmq andiamo poco più avanti:

 

.text:00403F74  call ds:__vbaHresultCheckObj
.text:00403F7A  mov [ebp+var_120], eax
.text:00403F80  jmp short loc_403F8C

.text:00403F82  mov [ebp+var_120], 0
.text:00403F8C  mov eax, [ebp+Name]
.text:00403F8F  mov [ebp+Name2], eax
.text:00403F95  mov [ebp+Name], 0
.text:00403F9C  mov edx, [ebp+Name2]
.text:00403FA2  lea ecx, [ebp+pName2]
.text:00403FA5  call ds:__vbaStrMove
.text:00403FAB  lea ecx, [ebp+var_50]
.text:00403FAE  call ds:__vbaFreeObj

.text:00403FB4  mov [ebp+INUTILE], 4
.text:00403FBB  mov ecx, [ebp+pName2]
.text:00403FBE  push ecx
.text:00403FBF  push offset NullString
.text:00403FC4  call ds:__vbaStrCmp
.text:00403FCA  test eax, eax
.text:00403FCC  jnz NomeNonVuoto

ora __vbaHresultCHeckObj dovrebbe essere la funzione che si occupa di prendere il testo dal controllo (insieme a __VbaObjSet che sta prima), però questa funzione la si trova sempre nei controlli, quindi credo sia una funzione generica che fornisce una interfaccia di accesso all'oggetto (il controllo in questo caso) e che permette di fare una query sui dati dell'oggetto stesso tramite strutture. Cmq poco importa, qui riusciamo facilmente a capire cosa succede: viene preso il nome, il puntatore al nome è messo nella variabile che ho chiamato Name (variabile locale, ora si parla sempre di locals!). Come vedete c'è uno StrMove che copia la stringa in un'altra locazione (evviva! sprechiamo la memoria! ne troverete tanti di strmov!), poi c'è il vbaStrCmp, che non ci vuole un genio a capire che fa il confronto tra stringhe! In questo caso fa un confronto tra il nome inserito e una stringa vuota, quindi controlla se il Nome è vuoto. Se lo è ci dà un messaggio di errore. Allo stesso modo troverete la stessa identica procedura poco dopo per il controllo del serial:

 

.text:00404133  push offset NullString
.text:00404138  call ds:__vbaStrCmp
.text:0040413E  test eax, eax
.text:00404140  jnz SerialNonVuoto

ok procediamo, oltre nell'analisi dell'algoritmo ed ecco un altro simpatico controllo:

 

.text:004041F3  mov [ebp+INUTILE], 0Dh
.text:004041FA  mov eax, [ebp+SerialString]
.text:004041FD  push eax
.text:004041FE  call ds:__vbaLenBstr
.text:00404204  mov [ebp+Serial_Name_StrLen], eax
.text:00404207  mov [ebp+INUTILE], 0Eh
.text:0040420E  cmp [ebp+Serial_Name_StrLen], 8    ; la lunghezza del serial deve essere 8 char!
.text:00404212  jz short SerialLungo8CharOk
.text:00404214  jmp ToQuitRoutine

e qui vediamo che la lunghezza del serial deve essere di 8 caratteri. Tutto ok finora. Ah la variabile che ho chiamato INUTILE sembra essere una sorta di counter interno, forse il VB ne tiene traccia in caso di eccezione per sapere dove si trovava l'esecuzione in quel momento, boh! Non fateci caso, quando la vedete è una riga che potete ignorare :). Proseguiamo oltre ed arriviamo a questo ciclo:

 

.text:00404241  Begin_Serial_For: ; CODE XREF: Register_ACTION+47Aj
.text:00404241  mov cx, [ebp+ActualCounter]
.text:00404245  add cx, [ebp+IterationStep]
.text:0040424C  jo ErrorOverflow
.text:00404252  mov [ebp+ActualCounter], cx
.text:00404256  mov dx, [ebp-100100b]
.text:0040425A  cmp dx, [ebp+CounterTop_StrLen]
.text:00404261  jg End_Serial_For
.text:00404267  mov [ebp+INUTILE], 12h
.text:0040426E  mov [ebp+var_58], 1
.text:00404275  mov [ebp+var_60], 2
.text:0040427C  lea eax, [ebp+SerialString]
.text:0040427F  mov [ebp+SerialString2], eax
.text:00404285  mov [ebp+var_A0], 4008h
.text:0040428F  lea ecx, [ebp+var_60]
.text:00404292  push ecx
.text:00404293  movsx edx, [ebp+ActualCounter]
.text:00404297  push edx
.text:00404298  lea eax, [ebp+var_A0]
.text:0040429E  push eax
.text:0040429F  lea ecx, [ebp+var_70]
.text:004042A2  push ecx
.text:004042A3  call ds:rtcMidCharVar
.text:004042A9  lea edx, [ebp+var_70]
.text:004042AC  push edx
.text:004042AD  lea eax, [ebp+Name]
.text:004042B0  push eax
.text:004042B1  call ds:__vbaStrVarVal ; restituisce un puntatore all' i-esimo char
.text:004042B7  push eax
.text:004042B8  call ds:rtcAnsiValueBstr ; mette l'i-esimo char in eax
.text:004042BE  mov ecx, eax
.text:004042C0  call ds:__vbaUI1I2       ; conversione da Intero 1 byte  a  Intero 2 bytes
.text:004042C6  mov [ebp+ActualCharConvertito], al
.text:004042C9  lea ecx, [ebp+Name]
.text:004042CC  call ds:__vbaFreeStr
.text:004042D2  lea ecx, [ebp+var_70]
.text:004042D5  push ecx
.text:004042D6  lea edx, [ebp+var_60]
.text:004042D9  push edx
.text:004042DA  push 2
.text:004042DC  call ds:__vbaFreeVarList
.text:004042E2  add esp, 0Ch
.text:004042E5  CheckCharIsNumber: ; DATA XREF: .text:00401174o
.text:004042E5  mov [ebp+INUTILE], 13h
.text:004042EC  movzx ax, [ebp+ActualCharConvertito]
.text:004042F1  cmp ax, 30h
.text:004042F5  jge short Major30ok
.text:004042F7  Minor30: ; DATA XREF: .text:00401178o
.text:004042F7  jmp ToQuitRoutine
.text:004042FC  Major30ok: ; CODE XREF: Register_ACTION+455j
.text:004042FC  mov [ebp+INUTILE], 16h
.text:00404303  movzx cx, [ebp+ActualCharConvertito]
.text:00404308  cmp cx, 39h
.text:0040430C  jle short Minor39ok
.text:0040430E  Major39: ; DATA XREF: .text:00401184o
.text:0040430E  jmp ToQuitRoutine
.text:00404313  Minor39ok: ; CODE XREF: Register_ACTION+46Cj
.text:00404313  mov [ebp+INUTILE], 19h
.text:0040431A  jmp Begin_Serial_For

ok in pratica tutto questo ciclo serve a controllare char per char che il serial inserito sia un numero, cioè che i char ascii che lo compongono siano compresi tra 30h e 39h (ovvero cifre da 0 a 9). In pratica è un for tipo:

 

for(i=0; i<8; i++)

{

    if [(serial[i] < 0x30h)  OR  (serial[i] > 0x39h)] goto ErrorQuit;

}

 

(scrivo in pseudocodice, non in VB o C!). Ok il nostro seriale deve essere di 8 char e deve essere numerico. Andiamo avanti.

 

.text:00404369  mov ax, [ebp+ActualCounter]
.text:0040436D  add ax, [ebp+var_E4]
.text:00404374  jo ErrorOverflow
.text:0040437A  mov [ebp+ActualCounter], ax
.text:0040437E  mov cx, [ebp+ActualCounter]
.text:00404382  cmp cx, [ebp+TOP_Name_Len]
.text:00404389  jg EndNameFor
.text:0040438F  Begin_Name_For: ; DATA XREF: .text:0040119Co
.text:0040438F  mov [ebp+INUTILE], 1Dh ; Somma tutti i chars del nome
.text:00404396  mov [ebp+var_58], 1
.text:0040439D  mov [ebp+var_60], 2
.text:004043A4  lea edx, [ebp+pName2]
.text:004043A7  mov [ebp+SerialString2], edx
.text:004043AD  mov [ebp+var_A0], 4008h
.text:004043B7  lea eax, [ebp+var_60]
.text:004043BA  push eax
.text:004043BB  movsx ecx, [ebp+ActualCounter]
.text:004043BF  push ecx
.text:004043C0  lea edx, [ebp+var_A0]
.text:004043C6  push edx
.text:004043C7  lea eax, [ebp+var_70]
.text:004043CA  push eax
.text:004043CB  call ds:rtcMidCharVar
.text:004043D1  lea ecx, [ebp+var_70]
.text:004043D4  push ecx
.text:004043D5  lea edx, [ebp+Name]
.text:004043D8  push edx
.text:004043D9  call ds:__vbaStrVarVal
.text:004043DF  push eax
.text:004043E0  call ds:rtcAnsiValueBstr
.text:004043E6  movsx eax, ax
.text:004043E9  mov ecx, [ebp+SommaCharsNome]   ; importante!
.text:004043EC  add ecx, eax                                ; somma char a char del nome
.text:004043EE  jo ErrorOverflow
.text:004043F4  mov [ebp+SommaCharsNome], ecx
.text:004043F7  lea ecx, [ebp+Name]
.text:004043FA  call ds:__vbaFreeStr
.text:00404400  lea edx, [ebp+var_70]
.text:00404403  push edx
.text:00404404  lea eax, [ebp+var_60]
.text:00404407  push eax
.text:00404408  push 2
.text:0040440A  call ds:__vbaFreeVarList
.text:00404410  add esp, 0Ch
.text:00404413  mov [ebp+INUTILE], 1Eh
.text:0040441A  jmp loc_404369      ; ripeti ciclo (for)

beh che dire, niente di astruso, un altro FOR che non fa altro che sommare tutti i char del nome. Anche qui una cosa banale tipo:

 

SommaCharsNome=0;

for(i=0; i<strlen(nome); i++)

{

    SommaCharsNome += nome[i];

}

 

ora seguite il pezzo che arriva subito dopo il for:

 

.text:0040441F  mov [ebp+INUTILE], 1Fh
.text:00404426  mov ecx, [ebp+SommaCharsNome]
.text:00404429  xor ecx, [ebp+Serial_Name_StrLen] ; xora la somma dei char con la lunghezza del nome
.text:0040442C  mov [ebp+SommaCharsNome], ecx

il valore che tiene la somma di tutti i chars viene xorato con il valore della lunghezza del nome. Ora proseguiamo e troviamo i calcoli sul seriale:

 

.text:0040445E  mov dx, [ebp+ActualCounter]
.text:00404462  add dx, [ebp+var_EC]
.text:00404469  jo ErrorOverflow
.text:0040446F  mov [ebp+ActualCounter], dx
.text:00404473  mov ax, [ebp+ActualCounter]
.text:00404477  cmp ax, [ebp+var_F0]
.text:0040447E  jg End_For_4
.text:00404484  Begin_For_4: ; DATA XREF: .text:004011B0o
.text:00404484  mov [ebp+INUTILE], 22h ; il ciclo crea una stringa di 4 chars dai primi 4 char del serial
.text:0040448B  mov [ebp+var_58], 1
.text:00404492  mov [ebp+var_60], 2
.text:00404499  lea ecx, [ebp+SerialString]
.text:0040449C  mov [ebp+SerialString2], ecx
.text:004044A2  mov [ebp+var_A0], 4008h
.text:004044AC  lea edx, [ebp+var_60]
.text:004044AF  push edx
.text:004044B0  movsx eax, [ebp+ActualCounter]
.text:004044B4  push eax
.text:004044B5  lea ecx, [ebp+var_A0]
.text:004044BB  push ecx
.text:004044BC  lea edx, [ebp+var_70]
.text:004044BF  push edx
.text:004044C0  call ds:rtcMidCharVar
.text:004044C6  mov eax, [ebp+Numeric4chars]
.text:004044C9  push eax
.text:004044CA  call ds:__vbaStrI4
.text:004044D0  mov edx, eax
.text:004044D2  lea ecx, [ebp+StringNumber]
.text:004044D5  call ds:__vbaStrMove
.text:004044DB  push eax
.text:004044DC  lea ecx, [ebp+var_70]
.text:004044DF  push ecx
.text:004044E0  lea edx, [ebp+Name]
.text:004044E3  push edx
.text:004044E4  call ds:__vbaStrVarVal   ; prende il ptr al char del serial
.text:004044EA  push eax
.text:004044EB  call ds:rtcAnsiValueBstr ; prende il char del serial dal ptr e lo mette in eax
.text:004044F1  sub ax, 30h
.text:004044F5  jo ErrorOverflow
.text:004044FB  push eax
.text:004044FC  call ds:__vbaStrI2     ; converte una stringa a un intero 2 bytes

.text:00404502  mov edx, eax
.text:00404504  lea ecx, [ebp+var_48]
.text:00404507  call ds:__vbaStrMove
.text:0040450D  push eax
.text:0040450E  call ds:__vbaStrCat  ; concatena le stringhe
.text:00404514  mov edx, eax
.text:00404516  lea ecx, [ebp+var_4C]
.text:00404519  call ds:__vbaStrMove
.text:0040451F  push eax
.text:00404520  call ds:__vbaI4Str    ; converte un intero 4 bytes  ad una stringa
.text:00404526  mov [ebp+Numeric4chars], eax
.text:00404529  lea eax, [ebp+var_4C]
.text:0040452C  push eax
.text:0040452D  lea ecx, [ebp+var_48]
.text:00404530  push ecx
.text:00404531  lea edx, [ebp+StringNumber]
.text:00404534  push edx
.text:00404535  lea eax, [ebp+Name]
.text:00404538  push eax
.text:00404539  push 4
.text:0040453B  call ds:__vbaFreeStrList
.text:00404541  add esp, 14h
.text:00404544  lea ecx, [ebp+var_70]
.text:00404547  push ecx
.text:00404548  lea edx, [ebp+var_60]
.text:0040454B  push edx
.text:0040454C  push 2
.text:0040454E  call ds:__vbaFreeVarList
.text:00404554  add esp, 0Ch
.text:00404557  mov [ebp+INUTILE], 23h
.text:0040455E  jmp loc_40445E    ; ripeti ciclo

questo ciclo itera solo 4 volte (la prima metà del serial), e praticamente copia i primi 4 caratteri del serial verso un'altra stringa, in modo che alla fine avremo una stringa risultante che rappresenta le prime 4 cifre del serial (se abbiamo inserito "12345678" come serial, dopo questo ciclo avremo un valore numerico che equivale alla stringa "1234"). Ed infatti vediamo che subito dopo abbiamo:

 

.text:00404563  mov [ebp+INUTILE], 24h
.text:0040456A  mov eax, [ebp+Numeric4chars]     ; eax = numero corrispondende ai primi 4 char del serial
.text:0040456D  xor eax, 29Ah                            ; eax xorato con 29A
.text:00404572  mov [ebp+Numeric4chars], eax
.text:00404575  mov [ebp+INUTILE], 25h
.text:0040457C  mov ecx, [ebp+Numeric4chars]
.text:0040457F  cmp ecx, [ebp+SommaCharsNome] ; IMPORTANTE! PRIMO CHECK
.text:00404582  jz short PrimoCheckOk
.text:00404584  jmp ToQuitRoutine

et voilà, eccoci al primo check! In pratica dopo aver preso il valore numerico corrispondente alla stringa della prima metà del seriale lo xora con il valore 29Ah (666 decimale, quale fantasia mio caro ntoskrnl :PPPP) e poi lo confronta con il valore della somma dei char del nome. Se i due valori sono uguali si procede al prossimo check, altrimenti si esce dalla routine!

Quindi vediamo di calcolarci questo serial: io inserisco come nome AndreaGeddon, quindi ecco i calcoli che mi ritrovo a fare:

 

stringa:  AndreaGeddon

somma dei char ascii:     41 + 6E + 64 + 72 + 65 + 61 + 47 + 65 + 64 + 64 + 6F + 6E = 49C

somma xorata con lunghezza nome:     49C  xor  0C  = 490h

secondo xor:       490h  xor  29A  =  60Ah  =>  1546 decimale

 

ecco calcolato il primo pezzo del seriale, i primi 4 bytes. Ora dobbiamo proseguire con l'analisi per vedere i secondi quattro bytes come saranno calcolati! Proseguendo troviamo un altro FOR che non pasto tutto, va da 004045CD a 004046F9, i punti importanti di questo ciclo sono:

 

.text:00404686  call ds:__vbaStrVarVal
.text:0040468C  push eax
.text:0040468D  call ds:rtcAnsiValueBstr   ; mette il char i-esimo in eax
.text:00404693  movsx edx, ax
.text:00404696  mov esi, [ebp+SommaCharsNome]
.text:00404699  add esi, edx                  ; somma char a char
.text:0040469B  jo ErrorOverflow
.text:004046A1  lea eax, [ebp+var_90]
.text:004046A7  push eax
.text:004046A8  lea ecx, [ebp+StringNumber]
.text:004046AB  push ecx
.text:004046AC  call ds:__vbaStrVarVal
.text:004046B2  push eax
.text:004046B3  call ds:rtcAnsiValueBstr    ;mette il char all'opposto dell' (i-esimo -1) in eax
.text:004046B9  movsx edx, ax
.text:004046BC  xor esi, edx                    ;xora di volta in volta il char opposto alla somma
.text:004046BE  mov [ebp+SommaCharsNome], esi

ecco qui un attimo due paroline per spiegare questo passaggio, in pratica fa un for per tutti i char del nome, e ad ogni iterazione scorre in due versi, un verso dal primo char verso l'ultimo, l'altro verso dal penultimo char verso il primo!

Ora succede che nello scorrere in avanti il char i-esimo viene aggiunto alla variabile che tiene la somma, mentre dall'altro verso il char i-esimo - 1 viene xorato con la somma ottenuta. In pratica per il mio nome ecco cosa succede:

 

stringa:  AndreaGeddon

ascii:     41 6E 64 72 65 61 47 65 64 64 6F 6E 

1° iterazione: 

somma = somma + 41

somma = somma xor 6F

somma ora vale 2E

 

2° iterazione: 

somma = somma + 6E

somma = somma xor 64

somma ora vale F8

 

... etc etc

 

alla fine del ciclo per il mio nome la somma varrà 344h. Attenzione! Avete notato che il secondo verso di questo for scorre dal penultimo carattere verso il primo, il che vuol dire che alla ultima iterazione non prenderà il primo char del Nome, ma il byte precedente! Questo genera una eccezione interna a rtcMidCharVar, quindi se lo debuggate in usermode potreste avere problemi! L'importante è che avete capito il ciclo, poi il crackme funziona lo stesso. Se per caso avete problemi con l'eccezione vi basta saltare tutto il ciclo (tanto avete capito come funziona, non serve fare tutte le iterazioni!) ed andare direttamente alla linea 004046FE. Da qui proseguendo troviamo l'ultimo ciclo (che va da 0040471F a 0040481F) che non vi pasto perchè è banale, in pratica serve solo data la stringa del serial ad ottenere il valore numerico corrispondente ai secondi 4 char della stringa. Quindi se io avevo inserito "12345678" come serial ora questo for ottiene il valore numerico 5678. Superato questo ciclo abbiamo finito:

 

.text:00404824  mov [ebp+INUTILE], 30h
.text:0040482B  mov ecx, [ebp+SommaCharsNome]
.text:0040482E  cmp ecx, [ebp+Numeric4chars]        ; IMPORTANTE! SECONDO CHECK!
.text:00404831  jz short SerialOk
.text:00404833  jmp ToQuitRoutine

ed eccoci qui!!! Confronta il valore 344h appena ottenuto con la seconda parte del serial. 344h = 836 dec, quindi andandolo ad unire alla prima parte il serial per il mio nome varrà  15460836.

Facile no?

Ora avete compreso l'algoritmo, potete anche decidere di scriverci un keymaker!

 


ALBERO DELLE STRUTTURE:

 

RT_GLOBAL -> pProjectStruct -> pTree -> pTreeData -> pTree

                                                  -> pFormList -> pForm1 -> pMod1Descriptor

                                                                 -> pForm2 -> pMod2Descriptor

                                     -> pModules1 -> pModules1 -> pMod1Descriptor -> pTree 

                                                                                  -> pForm

                                                                                  -> pModules1

                                                                                  -> Main_Controls_List

                                                                                  -> pMainDispatcher

                                                  -> pModules2 -> pMod2Descriptor -> (idem)

                                                  -> pModules3 -> pMod3Descriptor -> (idem)

                            -> pExtern

          -> pDialogs -> MainDialogInfo

          -> pExtern  -> ImportData

                      -> InsideImport

          -> pProjectInfo

 

Bye All!

AndreaGeddon

 

Note finali

Saluto tutto crack-it e tutta la UIC, un thanx a Ntoskrnl per il crackme (mi ha evitato di dover scrivere in vb hihi) che cmq ucciderò per colpa delle sue battutacce huaz huaz >:-)) un saluto a Ironspark che mi fa da betareader!

Disclaimer

Fate quello che volete ma non fatelo con il vb!. Ogni volta che usate VB Dio uccide un gattino. Ogni licenza del vb vale come posto prenotato all'inferno. 

Noi reversiamo al solo scopo informativo e di miglioramento del linguaggio Assembly.

Capitoooooooo????? Bhè credo di si ;))))