*** Reverse Engeneering di Babylon Translator ***
IDA 3.8 |
Babylon Translator è una utility abbastanza utile se si ha a che fare con lingue straniere, in quanto permette con un semplice click la traduzione di una parola qualsiasi in un qualsiasi documento. La versione shareware scade dopo una trentina di giorni, e siamo molto curiosi di vedere come fa il programma a capire la propria scadenza.
-# Considerazioni Generali, ovvero Pre-cracking #-
Una caratteristica dei programmi a tempo è quella di essere, generalmente, legati all'orologio di sistema. I primi programmi a tempo erano facili da bypassare, perchè bastava cambiare la data e tutto filava liscio. Poi le cose sono andate complicandosi. Infatti in alcune applicazioni cambiando la data non si faceva altro che causare la scadenza immediata (la prima applicazione di questo genere che ho incontrato personalmente era Turbo Browser '98, Dicembre '97). Ciò accade anche in Babylon Translator.
Se si cambia la data in avanti nel tempo non succcede niente, non appena la si mette un giorno indietro l'applicazione è scaduta e bisogna registrarla per forza via rete. Però metti che, come nel mio caso, uno non c'ha il modem, c'ha un bordello di traduzioni da fare per il fine settimana ed ha appena buttato il dizionario di inglese nel caminetto perchè siamo in inverno?
-# Reversing Babylon Protection - Parte I #-
Visto che Babylon è una applicazione a tempo ci sarà qualche chiamata API a riguardo. Personalmente quando mi appresto a fare ricerche del genere uso un disassemblatore, perchè ci potrebbero essere diverse funzioni API non-Windows, ma proprietarie del programma, che risiedono in file .dll. Nel nostro caso Babylon ha nella propria directory il file captlib.dll. Facendo 'Anteprima' sul file vediamo che esporta varie funzioni tra cui una un po' sospetta, OpenBabylonDLL. Ed è ora la volta di IDA. Dissassemblando il file nella funzione esportata OpenBabylonDLL c'è l'API GetsystemTime e RegOpenKeyExA come segue:
CODE:004020BA 8D 85 D4 FE FF FF lea eax, [ebp+var_12C] ; eax = indirizzo in cui vengono CODE:004020C0 50 push eax ; salvati i dati dell'orologio CODE:004020C1 E8 7A CC 00 00 call j_GetSystemTime CODE:004020C6 8D 55 E8 lea edx, [ebp+var_18] CODE:004020C9 52 push edx CODE:004020CA 6A 01 push 1 CODE:004020CC 6A 00 push 0 CODE:004020CE 68 8D 25 42 00 push offset str->SoftwareBa CODE:004020D3 68 02 00 00 80 push 80000002h CODE:004020D8 E8 BF CD 00 00 call j_RegOpenKeyExA CODE:004020DD 85 C0 test eax, eax CODE:004020DF 0F 85 0C 02 00 00 jnz loc_0_4022F1
Per GetsystemTime la sintassi Φ:
VOID GetSystemTime( LPSYSTEMTIME lpSystemTime // indirizzo della struttura 'Tempo');quindi eax Φ quell'indirizzo. La struttura 'Tempo' Φ questa:
typedef struct _SYSTEMTIME { // st WORD wYear; <- Anno WORD wMonth; <- Mese WORD wDayOfWeek;<- Giorno della settimana WORD wDay; <- Giorno WORD wHour; <- Ora WORD wMinute; <- Minuto WORD wSecond; <- Secondo WORD wMilliseconds; } SYSTEMTIME;
Di questa struttura nel nostro caso i dati più rilevanti saranno al massimo i primi 4. Nell'indirizzo ebp-12c nel mio caso i valori restituiti sono:
CF 07 01 00 03 00 1B 00 ...... |--| | | | 1999 1 Mercol. 27
... appunto la data odierna. Per comodità, in IDA, si può rinominare la variabile var_12c in TimeStructure.
Segue in seguito l'apertura della chiave di registro 'LOCAL\SOFTWARE\BABYLON\Babylon Translator\b1', il cui esito è eax=0, quindi la funzione ha avuto successo. Subito dopo c'è un bel RegQueryValueExA...
CODE:004020E5 C7 45 EC 08 00 00+mov [ebp+buffer_size], 8 CODE:004020EC 8D 4D EC lea ecx, [ebp+buffer_size] CODE:004020EF 51 push ecx CODE:004020F0 8D 85 64 FF FF FF lea eax, [ebp+buffer_address] CODE:004020F6 50 push eax CODE:004020F7 6A 00 push 0 CODE:004020F9 6A 00 push 0 CODE:004020FB 68 B4 25 42 00 push offset str->Indexbydat CODE:00402100 FF 75 E8 push [ebp+Bab_Handle_of_open_HKey_data] CODE:00402103 E8 88 CD 00 00 call j_RegQueryValueExA CODE:00402108 85 C0 test eax, eax CODE:0040210A 0F 85 D9 01 00 00 jnz loc_0_4022E9 CODE:00402110 33 C0 xor eax, eax CODE:00402112 EB 12 jmp short loc_0_402126
In questo stralcio di codice ho già cambiato i nomi delle varie var_xx per comodità. Si evince quindi facilmente che l'app ha preparato un buffer di 8 byte, da riempire con le informazioni recuperate da 'HKEY_LOCAL_MACHINE\Software\Babylon\Babylon Translator\b1\IndexByData'. Sono dei valori esadecimali che per ora non ci dicono niente...
-# Fine Reversing Babylon Protection - Parte I #-
-# Considerazioni generali - I #-
L'analisi di un'applicazione da sproteggere non va fatta tutta d'un fiato... Una protezione va studiata ed analizzata a fondo, oltre che per il gusto di bypassarla, anche per capire dove il programmatore sbaglia, e, come dicono a Roma, errando discitur ;). Con uno strumenti potenti come IDA e Soft-Ice è davvero difficile ostacolare un possibile reverser.
Comunque, come dicevo, l'analisi di parti di codice va fatta in più passate, per capire meglio 'chi è cosa'. Nel listato del programma disassemblato ci sono una marea di variabili, che possono avere un significato, DEVONO avere un significato, tutto sta nel capire quale.
-# Fine considerazioni generali - I #-
-# Reversing Babylon Protection - Parte II #-
Dopo aver eseguito l'applicazione diverse volte, ho iniziato a dare i nomi alle variabili del programma, comprendendo qual è la 'filosofia' della protezione. Questa può esser suddivisa per comodità in due parti, una che controlla che non sia stata cambiata la data di sistema, e l'altra che calcola quanti giorni rimangono alla scadenza. I dati sulla scadenza sono contenuti nel registro di win sotto la chiave 'HKEY_LOCAL_MACHINE\Software\Babylon\Babylon Translator\b1\' e sono criptati. Non a caso l'applicazione utilizza due maschere XOR per leggerli, che vengono inizializzate all'inizio:
CODE:00402094 8B 05 38 23 42 00 mov eax, ds:dword_0_422338 ; CODE:0040209A 89 45 F8 mov [ebp+Xor_value1], eax ;<---\ Le due maschere XOR CODE:0040209D 8B 05 3C 23 42 00 mov eax, ds:dword_0_42233C ; | \ La prima Φ utilizzata CODE:004020A3 89 45 FC mov [ebp+var_4], eax ; | > con la data corrente, la CODE:004020A6 8B 15 40 23 42 00 mov edx, ds:dword_0_422340 ; | / seconda con i dati sulla CODE:004020AC 89 55 F4 mov [ebp+Xor_value2], edx ;<---/ scadenza
e sono usate prima per decriptare i byte e poi per criptarli nuovamente, a chiasmo (12|21). Così, IndexByData contiene le informazioni sulla data odierna e IndexByExport sui giorni rimanenti.
Con la seguente routine viene decriptata IndexByExport, e controllato che Day_Remaining sia >=0.
CODE:004021C0 EB 12 jmp short Start_Xor2 CODE:004021C2 Xor2: ; Routine che decripta CODE:004021C2 0F BF D0 movsx edx, ax ; IndexByExport usando CODE:004021C5 8A 4C 15 F4 mov cl, byte ptr [ebp+edx+Xor_value2]; la maschera XOR #2 CODE:004021C9 0F BF D0 movsx edx, ax ; (Xor_value2) CODE:004021CC 30 8C 15 64 FF FF+xor [ebp+edx+buffer_address], cl ; buffer_addr = dati da CODE:004021D3 40 inc eax ; decriptare CODE:004021D4 Start_Xor2: ; buffer_size = dimens. CODE:004021D4 0F BF C8 movsx ecx, ax ; buffer (4 bytes) CODE:004021D7 3B 4D EC cmp ecx, [ebp+buffer_size] ; CODE:004021DA 72 E6 jb short Xor2 ; CODE:004021DC 66 C7 45 F2 00 00 mov [ebp+Day_Remaining], 0 ; *** memcpy *** CODE:004021E2 6A 01 push 1 ; num. bytes da copiare CODE:004021E4 8D 85 64 FF FF FF lea eax, [ebp+buffer_address] ; origine CODE:004021EA 50 push eax ; CODE:004021EB 8D 45 F2 lea eax, [ebp+Day_Remaining] ; destinazione CODE:004021EE 50 push eax ; CODE:004021EF E8 D4 31 00 00 call _memcpy ; COPIA CODE:004021F4 83 C4 0C add esp, 0Ch CODE:004021F7 66 29 75 F2 sub [ebp+Day_Remaining], si CODE:004021FB 66 83 7D F2 00 cmp [ebp+Day_Remaining], 0 ; Se Day_Remaining Φ CODE:00402200 7D 06 jge short Still_some_days ; >= 0, vai avanti
Si capisce che in IndexByExport solo il primo byte è quello significativo, quello che una volta decriptato dà il numero effettivo di giorni rimamenti, il resto dei bytes sono chiacchiere...
Subito dopo il salto, viene 'assemblata' una parola di 4 bytes, di cui il primo valore è quello dei giorni rimanenti. Poi viene salvata nuovamente nel registro...
CODE:00402208 Still_some_days: CODE:00402208 0F B7 95 E2 FE FF+movzx edx, [ebp+Junk_value1] CODE:0040220F C1 E2 10 shl edx, 10h CODE:00402212 0F B7 8D E0 FE FF+movzx ecx, [ebp+Junk_value2] CODE:00402219 C1 E1 08 shl ecx, 8 CODE:0040221C 0B D1 or edx, ecx CODE:0040221E 0F BF 45 F2 movsx eax, [ebp+Day_Remaining] CODE:00402222 0B D0 or edx, eax CODE:00402224 89 55 E4 mov [ebp+Jkval1_Jkval2_Day_Remaining], edx CODE:00402227 FF 75 EC push [ebp+buffer_size] ; buffer_size = 4 bytes CODE:0040222A 8D 55 E4 lea edx, [ebp+Jkval1_Jkval2_Day_Remaining] CODE:0040222D 52 push edx CODE:0040222E 8D 8D E4 FE FF FF lea ecx, [ebp+XORed_Final_Value] CODE:00402234 51 push ecx CODE:00402235 E8 8E 31 00 00 call _memcpy CODE:0040223A 83 C4 0C add esp, 0Ch CODE:0040223D 33 C0 xor eax, eax CODE:0040223F EB 12 jmp short Start_Xor3 CODE:00402241 Xor3: ; Routine di encriptazione CODE:00402241 0F BF D0 movsx edx, ax CODE:00402244 8A 4C 15 F4 mov cl, byte ptr [ebp+edx+Xor_value2] CODE:00402248 0F BF D0 movsx edx, ax CODE:0040224B 30 8C 15 E4 FE FF+xor [ebp+edx+XORed_Final_Value], cl CODE:00402252 40 inc eax CODE:00402253 Start_Xor3: CODE:00402253 0F BF C8 movsx ecx, ax CODE:00402256 3B 4D EC cmp ecx, [ebp+buffer_size] CODE:00402259 72 E6 jb short Xor3 CODE:0040225B FF 75 EC push [ebp+buffer_size] CODE:0040225E 8D 85 E4 FE FF FF lea eax, [ebp+XORed_Final_Value] ; Valore finale criptato CODE:00402264 50 push eax CODE:00402265 6A 03 push 3 CODE:00402267 6A 00 push 0 CODE:00402269 68 CE 25 42 00 push offset str->Indexbye_0 CODE:0040226E FF 75 E8 push [ebp+Bab_Handle_of_open_HKey_data] CODE:00402271 E8 20 CC 00 00 call j_RegSetValueExA ; Salva valore finale
Il resto della protezione non fa più parte effettivamente della protezione, in quanto la parte critica è appena stata eseguita. La parte finale è:
CODE:004022F1 0F BF 45 F2 movsx eax, [ebp+Day_Remaining] CODE:004022F5 8B 55 08 mov edx, [ebp+Addr_Day_Remaining] CODE:004022F8 89 02 mov [edx], eax CODE:004022FA 85 DB test ebx, ebx ; Se ebx=1, app scaduta CODE:004022FC 74 04 jz short loc_0_402302 CODE:004022FE 33 C0 xor eax, eax CODE:00402300 EB 04 jmp short loc_0_402306 CODE:00402302 loc_0_402302: CODE:00402302 0F BF 45 F2 movsx eax, [ebp+Day_Remaining] CODE:00402306 loc_0_402306: CODE:00402306 5E pop esi CODE:00402307 5B pop ebx CODE:00402308 8B E5 mov esp, ebp CODE:0040230A 5D pop ebp CODE:0040230B C2 04 00 retn 4
, e non fa altro che spostare i giorni rimanenti in un indirizzo che viene utilizzato per visualizzarli nel programma, nella dialog box di informazioni.
Ora che la protezione è stata spiegata del tutto sta a voi modificare il flusso del programma o cambiare i valori nel registro per avere il massimo dei giorni possibili. Questo numero non può superare i 255 giorni, poichè Day_Remaining è di 1 byte (FF=255).
Se mi è stato possibile analizzare nei particolari questa routine di protezione è stato grazie ad IDA. E' possibile scaricarlo dalla pagina di Iczelion (iczelion.cjb.net) e per utilizzarlo al meglio sarebbe bene leggere la essay di +mammon su fravia o sul suo sito stesso. Se riceverò abbastanza richieste farò la traduzione del 'manuale di Ida' di +mammon.
Includo anche l'intera procedura totalmente disassemblata e reversed qui.
Copyright (c) Little-John 1999
mail: little_john80(at)hotmail(dot)com