VB
Reversing - unleashed! - |
||
Data |
by "AndreaGeddon" |
|
21/02/2003 |
Published by Quequero | |
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 |
.... |
|
.... |
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 :)
Introduzione |
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 |
Notizie sul programma |
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 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
|
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 ;))))