Giochi di prestigio con SMARTCHECK
PREMESSA
LE INFORMAZIONI CHE TROVATE ALL'INTERNO DI QUESTO FILE SONO PER PURO SCOPO DIDATTICO.
L'AUTORE NON INCORAGGIA CHI VOLESSE UTILIZZARLO PER SCOPI ILLEGALI.
DIFFICOLTA'
scala : *=Novizio, **=Apprendista, ***=Esperto, ****=Guru
target: **1/2
TOOLS USATI
* SmartCheck 6.0 (Numega)
* SoftIce 3.23 (Numega)
* IDA ver 3.80b (Ilflak / DataRescue)
* HIEW 6.01
INTRODUZIONE
Per iniziare direi che e' necessaria un breve digressione sul funzionamento e soprattutto sull' utilizzo di SmartCheck. SmartCheck e' fondamentalmente un avanzato ApiHooker, cioe' un programma che si aggancia (hook) alle chiamate alle API di sistema o alle funzioni di una DLL cercando di carpire tutte le informazioni possibili e sui parametri passati e sul programma chiamante: questo gli permette di analizzare runtime la presenza di errori generati dal passaggio di parametri non validi (ad exp. perche' non inizializzati), di trovare memory leak, ecc. o semplicemente di verificare cosa avviene dietro le quinte del nostro programma VB una volta "compilato" ed eseguito. Inoltre, in presenza dei sorgenti, SMCHK ci consente di trovare direttamente nel nostro codice dove sta l'istruzione colpevole, il parametro sbagliato, ecc..Indubbiamente un tool molto potente! "A cosa puo' servirci?" Beh a moltissimo direi :)
Chi ha esperienza di reverse con programmi VB sa che il problema piu' grosso con questi target e' l'origine stessa di VB, che nasce come linguaggio interpretato: anche senza arrivare al caso estremo di programmi interamente in PCODE (VB3/4 e VB5/6 non "compilati") il codice "nativo" dell'eseguibile e' una sorta di collante intorno a chiamate alle DLL runtime di VB. Questo si rivela un problema sia nel caso intendessimo debuggare il codice con Sice, sia nel caso di un approccio dead listing, in quanto siamo costretti a seguire un dedalo di chiamate ad API che costituiscono i vari layer con cui il VB incapsula i costrutti ad alto livello del linguaggio. SmartCheck entra in gioco proprio a questo punto: ci permette di avare una sorta di "fotografia" dell'esecuzione di un programma, consentendoci di seguire il workflow del programma stesso comprese API invocate, messaggi, hook, chiamate alle interfacce OLE,ecc. A questo punto studiando i "log" possiamo trovare i punti "d'attacco" con cui iniziare il debug, le api migliori su cui mettere i breakpoint, i file e le entry del registro coinvolte e molto altro.. nel caso estremo potremmo addirittura trovarci il "serial number" bello in chiaro :)
CONFIGURAZIONE DI SMARTCHECK
La configurazione di SmartCheck non e' un'operazione molto complessa ma e' di vitale importanza in quanto coinvolge il modo stesso di operare di SMTCHK, e quindi le informazioni che poi possiamo ottenere: innanzitutto caricate il programma da debuggare con FILE->OPEN.. in questo modo avrete la possibilita' di impostare anche i settaggi relatili al target corrente:
TAB "ERROR DETECTION"
* listbox "type of errors": permette di selezionare i tipi di errori che volgiamo vengano tracciati: una configurazione tipica e' Memory ,Pointer, Api errors ad ON e leaks ad OFF
* checkbox "report errors immediatly" : impostatelo ad off o vi verra' un crampo alla mano a suon di confermare tutti gli errori che si presentano :)
* button "advanced": da qui accedete ad un dialog in cui potete impostare alcune fondamentali opzioni del "motore di debug" di SMTCHK:
* "report error caused by other errors" : quwsta opzione vi consente di tracciare anche gli errori in modo "ricorsivo".. mettetela ad ON in quanto potrete loggare anche cosa accade nel caso il programma utilizzi informazioni errate che generano errori (ad exp.con i keyfile potrebbe essere la lunghezza errata, gli attributi, la data, ecc... insomma avete capito :)
* "report error even if no source..": beh mi pare scontanto l'ON ;)
* "report each error once" : lasciatelo ad OFF; noi vogliamo piu' informazioni possibili
* "check all heap block..": settatelo ad OFF, rallenta solo le prestazioni senza che nella maggioranza dei casi possiamo trarne vantaggio (a meno che no stiate debaggando un vostro programma ;)
* "guard byte" : non ci interessa in quanto legato al check dello heap
* "cache program events" : scontato l'ON a meno che non abbiate un pentium3000 nel qual caso non ve ne puo' fregare di piu' ;)
* "suppress system and api call" : settatelo ad ON; questo vi consente di tracciare anche le API di sistema (RegQueryValueExA,ReadFile,ecc..) indispensabile!
* "defer program results..": settatelo ad ON, in questo modo il log avvera' in background e potrete utilizzare il programma in modo trasparente
TAB "REPORTING"
questa opzione permette di impostare quali eventi devono essere riportati nei log, e quando deve iniziare il log: quindi la configurazione tipica sara'impostare tutto ad ON tranne che "MouseMove event over OCX controls" = OFF, questo perche' vogliamo che SMTCHK riporti ogni possibile errore genarato da VB per avere, lo ripeto, piu' informazioni utili possibili.
TAB "FILES TO CHECK"
questo tab permette di impostare quali EXE,DLL debbano essere coinvolti nel logging (e quindi nell'APIhook) in modo da filtrare le informazioni non necessarie. In genere vanno attivati tutti gli EXE/DLL che sono utilizzati dal programma e che non fanno parte di DLL/OCX di runtime a noi ben note e/o che sappiamo per certo non possono essere coinvolti nella protezione (vedi COMDLG32.OCX,DAO35.DLL,NETBIOS.DLL,ecc.)
TAB "ERROR SUPPRESSION"
qui e' possibile specifiare le API o le DLL escluse dall'hooking e quindi dal report.Si tratta di tarare il "motore" di SMTCHK a seconda del programma debuggato in modo che ignori specifiche chiamate a funzioni od ad intere DLL, che sappiamo per certo irrilevanti nello schema di protezione. E' ovvio che tipicamente, escluderemo i vari OCX standard, le DLL del DAO, ecc. mentre e' meglio lasciare le varie DLL MFC spesso utilizzate x funzioni di supporto. In ogni caso SMTCHK crea un file di contenente le vari API escluse x cui non e' necessario specificarle ad ogni esecuzione.
UN ESEMPIO PRATICO
Bene, dopo la lunga (pure pallosa ;) ma devorosa premessa vediamo di testare la vera potenza di SMTCHK su un target "reale". Per questo scopo ho scelto un programma commerciale di cui non faro' il nome eheh ;).. xche' presenta una schema abbastanza elaborato di serial che a mio giudizio costituisce un classico caso "da manuale" di protezione VB.
Questo target utilizza un unlock code in cui sono contenute le informazioni sul tipo e numero di licenze, la data di validita', ecc.. nel caso installiate la trial questa sara' valida per i classici 60gg, con una sola licenza user e nessuna licenza server. All'avvio si presenta uno splash screen che ci illustra le operazioni in corso: browsing data, updating database, e ofcoz "checking license" ;).. nel menu help e'presente la dialog x inserire il serial con verifica immediata. Ora mandiamo avanti la data e vediamo cosa succede: ok ok, il programma ci informa gentilmente che e' scaduto il periodo trial e ci presenta la dialog per inserire il serial.. direi che abbiamo abbastanza materiale per stendere un piano d'attacco ..splash form,license form.. lets go :)
Runniamo il prog da SmartCheck.. lasciamo che il prog visualizzi il messaggio.. clikkiamo per registrarci.. modifichiamo con solito sistema "a cazzo" (TM) il serial.. serial invalido! :) bene ora dovremmo aver un bel log da analizzare:
SMRCHK vi presenta una window divisa in due panel.. a SX il log vero e proprio in forma concisa, mentre a DX le informazioni in forma estesa: leggi parametri e cosa fondamentale l'address della call nella forma MODULENAME!RVA
newbie corner
questa forma ci mostra il Relative Virtual Address della call.. cioe' offset relativo all'address in cui viene mappata l'immagine di MODULENAME che contiene il codice (=IMAGEBASE; per gli .exe solitamente corrispondente alla preferred IMAGEBASE presente nel PE Header (=40000), per le DLL spesso e' diversa in quanto sono piu' soggette a collisioni e quindi a rilocazione, specie se hanno sezioni shared). Questa forma assomiglia a quella usata in sICE, MODULENAME!SECTION+OFFSET, solo che non e' esplicitato il nome della section. Questo formato consente di indicare un address in un modulo a prescindere dalla eventuale rilocazione. Se vogliamo ottenere l'address effettivo in memoria dobbiamo semplicemente sommare la IMAGEBASE. Prendiamo ad exp. SPIINIT.ED82: dobbiamo fare:
IMAGEBASE + RVA
34040000 + F7B5 = 3404F7B7
se invece volessimo calcolare l'offset su file, ad esempio per usare la pratica scorciatoia
dell'INT3 hardcoded, possiamo ottenerlo facendo questo semplice calcolo:
RAWOFFSET section + (ADDRESS - IMAGEBASE) - RVA section
400 + (3404F7B5 - 34040000 ) - 1000 = EBB5
dove section e' la section che contiene quell'address (tipicamente .TEXT o CODE) e RAWOFFSET l'offset fisico nel file in cui inizia detta section. Tutte queste informazioni si possono essere ricavate usando un PE dumper o in modo piu' pratico usando PEBROWSER e leggendo le info nel PE header. Se non ci avete capito un cz cominciate studiare il formato PE.. vi fara' molto comodo :)
GIOCHI DI PRESTIGIO
Quando iniziate ad analizzare i log si SMTCHK vi conviene usare un apprioccio topdow, mi spiego: nel menu VIEW sciegliete "Show error and specific events", poi la voce Specific Events.. questo vi consente di analizzare i singoli aspetti del programma: la creazione di form, controlli, gli eventi come i click, le call alle api,ecc.. L'idea e' quella di dare uno sguardo d'insieme per trovare velocemente un punto che ci puo' interessare e quindi passare ad una visione piu' dettagliata. Ad exp in questo caso noi cerchiamo la splash form, o la form x inserire la licenza, quindi una buona idea e quella di attivare per ora solo "Object events" + "Methods, properties.." + "Form Creation": in questo modo vedremo solo i log relativi alla creazione di form, settaggi di proprieta' (ad exp. una caption bar), o le invocazioni in risposta ad un click nel menu, nei button, ecc... Di fatti in un attimo troviamo:
3341 FSPISplash (Form) created
7515 FSPISplash.Show
20996 LoadStatus.Caption < -- "Verifying License..." (String)
bene abbiamo scovato un buon punto di inizio.. ora possiamo attivare le altr voci per analizzare in dettaglio i log: quindi attiviamo le opzioni di visualizzazione
"Visual Basic and.." -> (visualizza le a VB e WIN di alto livello (MID,LEFT,REAFILE,ecc.)
"Value Coercion" -> (visualizza le conversioni implicite che VB fa'.. eheh so parecchie ;)
"API Calls form.." -> (visualizza anche le api di basso livello di VB e WIN (ex. vbaSubVarVal)
A questo punto avremo un bel po da leggere :)
Appena sotto a LoadStatus.Caption troviamo due strane call:
22413 MethCallEngine()
22417 MethCallEngine()
questo non e' altro che un entrypoint x l'engine di VB, quindi dobbiamo seguire le call espandendo i rami di queste entry per vedere quello che avviene realmente quando viene eseguito il codice della splash form. Scrollando da questo ci salta subito all'occhio questo blocco:
24623 __vbaStrToAnsi(String:"Software...", LPSTR *:0073F470) returns DWORD:514078
24633 RegOpenKeyExA(HKEY:80000001, LPSTR:00514078, DWORD:00000000,
.....
24686 __vbaStrToAnsi(String:"LicUser", LPSTR *:0073F470) returns DWORD:514C90
24696 RegQueryValueExA(HKEY:C728D988, LPSTR:00514C90, DWORD:00000000,
.....
25146 __vbaStrCopy(String:"Register...", LPBSTR:0073F3A4) returns DWORD:512F10
25151 __vbaStrCat(String:"5", String:"Software...") returns DWORD:610F54
25156 __vbaStrMove(String:"Software...", LPBSTR:0073F3A8) returns DWORD:610F54
25160 __vbaStrToAnsi(String:"Software...", LPSTR *:0073EF78) returns DWORD:514078
25170 RegOpenKeyExA(HKEY:80000002, LPSTR:00514078, DWORD:00000000,
Ehhe, e' chiaro cosa sta facendo.. legge i dati della licenza dal registro: esaminando i parametri vediamo subito dove si trovano le informazioni..dall'SDK:
80000001 = HKEY_LOCAL_USER;
80000002 = HKEY_LOCAL_MACHINE
una rapida occhiata al regisytro ci conferma il tutto.. Ora sappiamo anche dove e' contenuta la protezione nella dll SPIINIT.DLL.. andiamo sempre meglio ;) continuando a scorrere i log incontriamo questo blocco che manipola il trial serial :
25333 Len(String:"191041-2...") returns LONG:31
25334 Len(String:"191041-2...") returns LONG:31
25335 Long (31) --> Integer (31)
25336 Mid$(String:"191041-2...", long:1, VARIANT:Integer:1)
..... ... stesso pattern x 6 ...
25488 Mid$(String:"191041-2...", long:7, VARIANT:Integer:1)
25513 Mid$(String:"191041-2...", long:1, VARIANT:Integer:6)
25521 Mid$(String:"191041-2...", long:8, VARIANT:Missing)
25533 Left$(String:"191041", long:1)
25571 Len(String:"191041") returns LONG:6
25572 IsNumeric(VARIANT:ByRef String:"191041") returns Boolean:True
25580 String ("191041") --> Long (191041)
26386 Len(String:"00698") returns LONG:5
26387 Len(String:"00698") returns LONG:5
26388 Long (5) --> Integer (5)
26389 Mid$(String:"00698", long:1, VARIANT:Integer:1)
..... ... stesso pattern x 3 ...
26488 Mid$(String:"00698", long:5, VARIANT:Integer:1)
26514 Mid$(String:"00698", long:1, VARIANT:Integer:5)
26521 Mid$(String:"00698", long:7, VARIANT:Missing)
26533 Left$(String:"00698", long:1)
26575 Len(String:"00698") returns LONG:5
26576 IsNumeric(VARIANT:ByRef String:"00698") returns Boolean:True
26584 String ("00698") --> Long (698)
Anche qui possimo fare alcune interessanti considerazioni: innanzitutto possiamo dire che si tratta di un loop che:
1) chiama una proc contenente un'altro loop che decodifica i blocchi del serial uno alla volta
2) verifica che il blocco decodificato sia un numero e quindi lo converte come LONG perche' un loop direte voi.. semplice dico io :) : SMTCHK riporta questi caller:
25333 SPIINIT!1C8FB |
25336 SPIINIT!1C92B | find_block loop
25533 SPIINIT!1CA36 |
25571 SPIINIT!ED5F | decode_loop
25572 SPIINIT!ED82 |
26386 SPIINIT!1C8FB |
26389 SPIINIT!1C92B |
26533 SPIINIT!1CA36 |
26575 SPIINIT!ED5F |
26576 SPIINIT!ED82 |
ed quindi facile riconoscere i pattern e dedurre la presenza di un loop. Come vedete SMTCHK ci consente una visione molto precisa di quello che sta accandendo durante l'esecuzione, basta solo saper decifrare le informazioni contenute nei log. Cmq siccome siamo dei tipi precisi andiamo a verificare il dissassemblato in IDA e.. sorpresa:
.text:3404ED38 call j___vbaStrCopy
.... more ....
.text:3404ED45 call sub_0_3405C843 ; < -- decode loop
.... more ....
.text:3404ED5F call j___vbaLenBstr
.text:3404ED64 test eax, eax
.text:3404ED66 jz loc_0_34050559
.... more ....
.text:3404ED81 push eax
.text:3404ED82 call j_rtcIsNumeric
.text:3404ED87 test ax, ax
.text:3404ED8A jz loc_0_34050559
.text:3404ED90 push dword ptr [ebp-5Ch]
.text:3404ED93 call j___vbaI4Str
.text:3404ED98 movsx ecx, bx
.text:3404ED9B mov edx, [ebp-34h]
le nostre supposizioni erano giuste (no dico dubitavate di me?!?.. vi stronco in caso affermativo!:))
Appena sotto questo snippet ne troviamo un'altro interessante:
26592 __vbaVarDup(VARIANT:String:"000000", VARIANT:Empty) returns DWORD:73F374
26597 Format(VARIANT:ByRef Long:191041, VARIANT:String:"000000", Integer:1, Integer:1)
..... ... stesso pattern x 3 ...
26673 __vbaVarDup(VARIANT:String:"00000", VARIANT:Empty) returns
26679 Format(VARIANT:ByRef Long:698, VARIANT:String:"00000", Integer:1, Integer:1)
seguito da:
26694 __vbaVarCat(VARIANT:String:"191041", VARIANT:String:"-") returns DWORD:73F354
26700 __vbaVarCat(VARIANT:String:"191041-", VARIANT:String:"28251")
26728 __vbaVarCat(VARIANT:String:"191041-2...", VARIANT:String:"-") returns DWORD:73F294
26734 __vbaVarCat(VARIANT:String:"191041-2...", VARIANT:String:"00698") returns DWORD:73F264
autoesplicativo.. con i valori decodificati il programma si costruisce una versione correttamente formattata del serial (questo puo' sembrare assurdo ma se guardate i log anche per la dialog di licenza, vi accorgerete che il codice del check e' come e' logico aspettarsi lo stesso.. quindi i programmatori hanno codificato la procedura a prescindere dalla fonte dei dati.. registro o editboxs. Benissimo ora sappiamo anche la struttura del serial NNNNNN-NNNNN-NNNNNN-NNNNN-NNNNN
Continuiamo xche' ora viene il bello pero':
26826 Format(VARIANT:ByRef Long:191041, VARIANT:String:"000000",
..... ... stesso pattern x 3 ...
26907 Format(VARIANT:ByRef Long:698, VARIANT:String:"00000", Integer:1
.....
26922 __vbaVarCat(VARIANT:String:"191041", VARIANT:String:"28251") returns DWORD:73F334
..... ... stesso pattern x 2 ...
26938 __vbaVarCat(VARIANT:String:"19104128...", VARIANT:String:"00698")
un'altra volta la formattazione ma questa volta senza "-" ?!?!... gia' ma guardate ora cosa fa il programma:
27008 Mid(VARIANT:ByRef String:"19104128...", long:6, VARIANT:Integer:2)
27013 Mid(VARIANT:ByRef String:"19104128...", long:15, VARIANT:Integer:2)
27018 Mid(VARIANT:ByRef String:"19104128...", long:1, VARIANT:Integer:2)
27023 Mid(VARIANT:ByRef String:"19104128...", long:10, VARIANT:Integer:5)
27028 Mid(VARIANT:ByRef String:"19104128...", long:17, VARIANT:Integer:2)
27033 Mid(VARIANT:ByRef String:"19104128...", long:21, VARIANT:Integer:3)
27038 Mid(VARIANT:ByRef String:"19104128...", long:8, VARIANT:Integer:2)
27044 Mid(VARIANT:ByRef String:"19104128...", long:24, VARIANT:Integer:2)
27050 Mid(VARIANT:ByRef String:"19104128...", long:3, VARIANT:Integer:3)
27056 Mid(VARIANT:ByRef String:"19104128...", long:26, VARIANT:Integer:2)
27062 Mid(VARIANT:ByRef String:"19104128...", long:19, VARIANT:Integer:2)
.....
27074 __vbaVarCat(VARIANT:String:"1210", VARIANT:String:"19") returns DWORD:73F304
.....
27119 __vbaVarCat(VARIANT:String:"12101951...", VARIANT:String:"34") returns DWORD:73F184
eheh un bel giochetto di prestigio... questo blocco lo potremmo chiarare BuildInternalSerial: il programma rimappa dei sottoblocchi del serial e ne costruisce una versione nuova che e' poi quella che viene usata x leggere le informazioni della licenza a loro volta contenuti in sottoblocchi di questo InternalSerial, in pratica abbiamo questa tabella di corrispondenza fra le posizioni:
internal 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
original 06 07 15 16 01 02 10 11 12 13 14 17 18 21 22 23 08 09 24 25 03 04 05 26 27 19 20
Ora abbiamo piu' o meno le idee chiare sul tipo di protezione che hanno addottato i programmatori si tratta di vedere in dettaglio i singoli blocchi.
Siccome 'sto benedetto tutorial e' gia' bello lungo.. ne analizzeremo solo alcuni, in particolare il controllo sul checksum e quello sulla expiration date... per gli altri vi daro' solo una breve spiegazione del procedimento usato.
IL CHECKSUM
Dopo aver scoperto i giochi di prestigio sul serial, vediamo in dettaglio la routine del checksum e soprattutto come da SMTCHK possiamo averne una chiara visione senza ricorre a IDA o sIce:
di seguito abbiamo:
27223 Mid(VARIANT:ByRef String:"12101951...", long:20, VARIANT:Integer:2)
27228 String ("61") --> Integer (61)
questo e' una sorta di magic che come vedremo viene usato x calcolare i parametri finali della licenza.
27248 Mid(VARIANT:ByRef String:"12101951...", long:25, VARIANT:Integer:2)
27253 String ("83") --> Integer (83)
.....
27273 Mid(VARIANT:ByRef String:"12101951...", long:27, VARIANT:Integer:1)
27278 String ("4") --> Integer (4)
come vediamo vengono letti separatamente i blocchi 25..26 e 27 che vedremo sono i valori di raffronto per il checksum.. poi segue un bel papirozzo:
27296 Len(String:"12101951...") returns LONG:27
27297 Long (24) --> Integer (24)
27298 Mid(VARIANT:ByRef String:"12101951...", long:1, VARIANT:Integer:1)
27303 __vbaI2ErrVar(VARIANT:String:"1") returns DWORD:610001
..... ... stesso pattern x 22 ...
27821 Mid(VARIANT:ByRef String:"12101951...", long:24, VARIANT:Integer:1)
27826 __vbaI2ErrVar(VARIANT:String:"9") returns DWORD:610009
spiegazione: chiaramente un loop che legge i caratteri uno alla volta da 1 a 24 (=len - 3) e li trasforma in int: non ci vuole molta fantasia a capire che sotto c'e' una qualche somma o cmq operazione aritmetica che calcola il checksum.. purtroppo SMTCHK non puo' aiutarci oltre ma almeno ci ha detto dove cercare.. ora IDA fara' il resto ;)
.text:3404F802 push 19h
.text:3404F804 lea eax, [ebp-2A0h]
.text:3404F80A push eax
.text:3404F80B lea eax, [ebp-0B0h]
.text:3404F811 push eax
.text:3404F812 call j_rtcMidCharVar
.text:3404F817 lea eax, [ebp-0B0h]
.text:3404F81D push eax
.text:3404F81E call j___vbaI2Var
.text:3404F823 mov [ebp-20h], eax ; < -- check1 = 83
.... more ....
.text:3404F863 push 1Bh
.... more ....
.text:3404F873 call j_rtcMidCharVar
.text:3404F878 lea eax, [ebp-0B0h]
.text:3404F87E push eax
.text:3404F87F call j___vbaI2Var
.text:3404F823 mov [ebp-24h], eax ; < -- check2 = 4
.... more ....
.text:3404F8A1 call j___vbaLenBstr
.text:3404F8A6 mov ecx, eax
.text:3404F8A8 sub ecx, 3
.text:3404F8AB jo loc_0_34050978
.text:3404F8B1 call j___vbaI2I4
.text:3404F8B6 mov [ebp-40Ch], eax
.text:3404F8BC mov dword ptr [ebp-44h], 1 ; |
.text:3404F8C3 mov eax, [ebp-44h] ; | for count=1 -> len(ISer)-3
.text:3404F8C6 cmp ax, [ebp-40Ch] ; |
.text:3404F8CD jg loc_0_3404F96A ; |
.... more ....
.text:3404F904 lea eax, [ebp-0B0h]
.text:3404F90A push eax
.text:3404F90B call j_rtcMidCharVar
.text:3404F910 lea eax, [ebp-0B0h]
.text:3404F916 push eax
.text:3404F917 call j___vbaI2ErrVar
.text:3404F91C imul ax, [ebp-44h]
.text:3404F921 jo loc_0_34050978
.text:3404F927 movsx eax, ax
.text:3404F92A add eax, [ebp-64h]
.... more ....
.text:3404F96A mov eax, [ebp-64h]
.text:3404F96D cdq
.text:3404F96E push 0Bh
.text:3404F970 pop ecx
.text:3404F971 idiv ecx
.text:3404F973 cdq
.text:3404F974 push 64h
.text:3404F976 pop ecx
.text:3404F977 idiv ecx
.text:3404F979 movsx eax, word ptr [ebp-20h]
.text:3404F97D cmp edx, eax ; check1
.text:3404F97F jnz loc_0_3405055C
.text:3404F985 mov eax, [ebp-64h]
.text:3404F988 cdq
.text:3404F989 push 0Ah
.text:3404F98B pop ecx
.text:3404F98C idiv ecx
.text:3404F98E movsx eax, word ptr [ebp-24h]
.text:3404F992 cmp edx, eax ; check2
.text:3404F994 jnz loc_0_3405055C
ora sappiamo che :
CHECKSUM = sum[MID(x,1)*x] con x=1 to 24
VAL(MID(25,2) = (CHECKSUM IDIV 0B) MOD 64
VAL(MID(27,1) = CHECKSUM MOD 0A
L'EXPIRATION DATE
Nel check che controlla se la licenza e' scaduta incontriamo un'altro giochetto di prestigio che ovviamente SMTCHK ci svela senza troppa fatica: riassumendo il programma legge dal registro il valore nella entry "Register5" e ne decodifica il contenuto..in pratica contiene i valori in HEX, tradotti in ASCII, che compongono la stringa con la data di installazione.. per e 04-FEB-99 e la confronta con la data di scadenza codificata nel serial. Ovviamente condisce la cosa con un bello scramble della stringa basato su una key hardcoded.
Ma guardiamo l'output di SMTCHK:
intanto vediamo che la key di "encryption" compare assolutamente in chiaro in SMTCHK :) 28264 Len(String:":u89h4)h...") returns LONG:14
28505 Mid(VARIANT:ByRef String:"000A0041...", long:1, VARIANT:Integer:4)
28510 __vbaVarCat(VARIANT:String:"H", VARIANT:String:"000A") returns DWORD:73EF30
28515 __vbaStrVarVal(VARIANT:String:"&H000A") returns DWORD:5279F4
28516 Val(String:"H000A") returns double:10 (displayed as single-precision floating point)
28528 Double (10) --> Long (10)
..... ... stesso pattern x tante volte ;) ...
Mid(VARIANT:ByRef String:"000A0041...", long:33, VARIANT:Integer:4)
28774 __vbaVarCat(VARIANT:String:"H", VARIANT:String:"0058") returns DWORD:73EF30
28779 __vbaStrVarVal(VARIANT:String:"&H0058") returns DWORD:527974
28780 Val(String:"H0058") returns double:88 (displayed as single-precision floating point)
28792 Double (88) --> Long (88)
questo in pratica decodifica da ASCII a HEX passando per la notazione coll'"&H" del VB..purtroppo in SMTCHK non e' visibile la parte dell'encryption, che si basa in sostanza su uno xor, ma non e' molto importante perche' lo scopo di capire come si svolge il check lo raggiungiamo lo stesso:
28807 Chr(Integer:57)
28813 __vbaVarCat(VARIANT:String:"9", VARIANT:String:"") returns
..... ... more ...
28983 Chr(Integer:48)
28989 __vbaVarCat(VARIANT:String:"0", VARIANT:String:"4-Feb-99") returns DWORD:73EF40
seguito da un'inequivocabile sequenza:
29045 IsDate(VARIANT:ByRef String:"04-Feb-9...") returns Boolean:True
29242 String ("04-Feb-99") --> Date (2/4/99)
29343 Double (36225) --> Date (3/6/99)
resta da capire quel 36225 da dove viene.. cioe' lo sappiamo dal serial decode:), ma da dove: osserviamo questo blocco che segue poco dopo il controllo del checksum:
27897 Mid(VARIANT:ByRef String:"12101951...", long:1, VARIANT:Integer:1)
27902 Mid(VARIANT:ByRef String:"12101951...", long:6, VARIANT:Integer:6)
27907 __vbaVarCat(VARIANT:String:"1", VARIANT:String:"951222") returns DWORD:73F334
27912 __vbaI4ErrVar(VARIANT:String:"1951222") returns DWORD:1DC5F6
e appena sotto
28003 Mid(VARIANT:ByRef String:"12101951...", long:15, VARIANT:Integer:5)
28008 __vbaVarSub(VARIANT:String:"00820", VARIANT:Integer:257) returns DWORD:73F354
28028 __vbaVarSub(VARIANT:Double:563, VARIANT:Integer:531) returns DWORD:73F344
28029 Double (32) --> Long (32)
28030 SysFreeString(BSTR:005279F4)
28034 Long (32) --> Integer (32)
28035 Double (2000.03) --> Integer (2000)
28036 DateSerial(Integer:2000, Integer:1, Integer:32)
28039 __vbaDateVar(VARIANT:Date:2/1/00) returns DWORD:6106DC
Riassumendo, come ci mostra SMTCHK, il pragramma usa i blocci 1 + 6..11 e 15..19 per qualche strano calcolo.. passando in ida risulta chiaro che in sostanza sono rispettivamente la data iniziale e la expiration date: in pratica tra 28003 e 28028 il programma esegue: license date = VAL(MID(1,1)+MID(6,6))-120746 expiration date = VAL(MID(15,5))-((Magic^2 IDIV 7)-257)).. e e poi non ditemi che SMTCHK non e' utile :)
Cmq il check avviene con 36255 perche' io ho alterato solo i blocchi 15..19 ma ovviamente i checksum non e' valido e quindi il programma passa in versione trail.. quindi i canonici 60gg dopo la data di installazione, poco male visto che sappiamo come correggere il controllo sul checksum.
NOTE FINALI
Il resto dei parametri del serial sono calcolati nello stesso modo ad exp. le user license:
27975 Mid(VARIANT:ByRef String:"12101951...", long:12, VARIANT:Integer:3)
27980 __vbaVarSub(VARIANT:String:"036", VARIANT:Integer:5) returns
che corrisponde in IDA a : VAL(MID(12,3) IDIV 2) - 5
e cosi': server lic= VAL(MID(4,2)-(Magic MOD 0A), lic type = VAL(MID(22,3)-(Magic MOD 1f), ecc... Ovviamente il significato di questi valori lo potete scoprire solo da sIce.. ma sicuramente SmartCheck vi ha ridotto di moltissimo il lavoro di stepping e di comprensione del codice. Ora potete ricostruire l'InternalSerial e da questo applicando il mapping alla rovescia il serial finale senza aver passato ore in sICE a pigiare il tasto F8, con buona pace dei vostri occhi :) Come vedete SMTCHK e' un tool davvero eccezzionale: ci ha permesso di districarci all'interno di una protezione che, se avessimo usato solo i tool "normali", ci avrebbe portato via molto piu' tempo e fatica; inoltre ci ha consententito non solo di capire come VB lavora sottobanco (ottima cosa per imparare quali BreakPoint sono piu' indicati a seconda del tipo di protezione) ma ci ha fornito una visione dettagliata dell'esecuzione del programma dandoci la possibilta' di fare una sorta di pre-reverse che alla fine non era molto distante dal codice effettivamente eseguito. Chiudo 'sto pacco di tut (azz e' proprio lungo :P ) con la speranza di aver chiarito a molti newbie le idee sull'uso di SmartCheck e soprattutto su come usarlo in modo proficuo.. se non ci sono riuscito pazienza :) .. anche perche' non tornero' sull'argomento per parecchio tempo dopo sto romanzo :)))