Come Craccare Certus
Dicevo che è scritto in Delphi, che è un ottimo sistema di sviluppo RAD, in questo caso piu' che adatto ad un programma che prevede l'inserimento e l'elaborazione di strutture di dati.
La versione target è la time-limited demo del programma, perfettamente funzionante in tutte le sue funzionalità (ottimo strumento di valutazione, continuate cosÏ :-) fino al 30mo giorno dopo l'installazione. Da quella data in poi il programma non parte nemmeno.
Procuratevi come al solito un debugger per Win32, meglio se quello integrato nel Visual Studio (VisualC++) poichè faro' riferimento a quello nel resto della trattazione; serve anche il WDasm32, questa volta è decisamente piu' utile che in altri casi per cui tenetelo a portata di mouse. Il fixup finale come di consueto puo' essere fatto con un hexeditor (HexWorkshop p.e.) ma per i soliti scopi didattici non è affatto necessario.
Ah dimenticavo, vorrei lasciare in sospeso alcuni passaggi finali, fondamentalmente alcune finezze possibili a crack finito, questo perchè so che solitamente chi legge un tutorial per l'appunto lo LEGGE. Io vorrei che chi ricevesse questo tutorial ESEGUISSE i passaggi che descrivo, altrimenti tanto varrebbe raccontarvi cosa ho mangiato ieri sera e quali suoni ho emesso dopo la digestione. Scherzi a parte, leggere un tutorial SENZA APPLICARLO è quasi come non leggerlo affatto, per cui pensateci prima di proseguire ed al limite tornateci sopra quando avrete la copia del programma a portata di mano per fare esercizio. Le cose lasciate in sospeso non sono necessarie per il crack fine a se stesso ma sono il miglior esercizio per imparare qcosa di Reverse Engeneering.
Orbene, carichiamo l'eseguibile (CerTuS.exe) in VC++ e diamo un Trace Into (F11) per ritrovarci nel codicillo asm. Ecco cosa si vede :
005488DC 55 push ebp
005488DD 8B EC mov ebp,esp
005488DF 83 C4 EC add esp,0ECh
005488E2 B8 04 84 54 00 mov eax,548404h
005488E7 E8 34 DF EB FF call 00406820 <- Chiamata sospetta
005488EC A1 8C B5 54 00 mov eax,[0054B58C]
005488F1 8B 00 mov eax,dword ptr [eax]
005488F3 E8 80 FF EB FF call 00408878
005488F8 84 C0 test al,al
005488FA 74 07 je 00548903
005488FC 33 C0 xor eax,eax
005488FE E8 11 B3 EB FF call 00403C14
00548903 E8 7C F8 EF FF call 00448184
.
.
Carichiamo CerTuS.exe anche nel WDasm, cosÏ intanto che debugghiamo il disassembler fa il suo lavoro (piuttosto lunghetto in questo caso). Siccome il programma non è ancora scaduto, conviene farsi un'idea del suo funzionamento prima di alterare il clock di sistema per vedere cosa succede. Si parte con uno Step Over (F10), e la prima cosa che salta all' occhio è che la chiamata 'CALL 00406820' è decisamente pesante, e tra altre cose si preoccupa di visualizzare la splash form di apertura. Visto che il controllo rimane in questa prima parte di programma è conveniente continuare il trace. E' abbastanza facile intuire che l'istruzione che lancia il programma vero e proprio è compresa nelle :
.
.
005489BA 8B 00 mov eax,dword ptr [eax]
005489BC E8 2B 1C EE FF call 0042A5EC <- programma principale
005489C1 E8 F2 5D F5 FF call 0049E7B8 <-|
005489C6 E8 31 B1 EB FF call 00403AFC <--Routines di uscita
005489CB 00 FF add bh,bh
005489CD FF ???
.
.
Il programma vero e proprio viene lanciato da 'CALL 0042A5EC', il quale ci presenta una form con i soliti disclaimer e soprattutto i giorni rimanenti. Premendo 'Esci' (non era meglio 'Entra'? bahh!) il programma viene effettivamente lanciato.
Per ora abbiamo già visto qcosa; intanto se il fido WDasm è stato lanciato con sufficiente anticipo dovrebbe aver prodotto il disassemblato del programma, pronto per le solite ispezioni. Lasciamo tutto cosÏ e premuriamoci di portare l'orologio di sistema avanti di 2 mesi, tanto per gradire.
Questa volta eseguendo normalmente CerTus veniamo subito bloccati da una dialog che ci informa gentilmente che il periodo di prova è terminato. Ma pensa te... Torniamo al debugger e ricominciamo dall'inizio per vedere questa volta dove si cela la trappola, ripartendo con F11; la porzione di codice iniziale è sempre la stessa, ma quando eseguiamo passo passo (F10) le istruzioni scopriamo che già all' indirizzo 005488E7 contenente la 'CALL 00406820' viene effettuato il check. Bene, se non altro sappiamo dove andare a parare. Restartiamo il programma e tracciamo la 'CALL 00406820' con F11, il codice è riportato qui sotto :
.
.
00406820 50 push eax
00406821 6A 00 push 0
00406823 E8 F8 FE FF FF call 00406720
00406828 BA 00 91 54 00 mov edx,549100h
0040682D 52 push edx
0040682E 89 05 B8 C4 54 00 mov dword ptr ds:[54C4B8h],eax
00406834 89 42 04 mov dword ptr [edx+4],eax
00406837 E8 98 FF FF FF call 004067D4
0040683C 5A pop edx
0040683D 58 pop eax
0040683E E8 AD D1 FF FF call 004039F0 <- Qui chekka i giorni
00406843 C3 ret
.
.
come si nota subito la routine non contiene istruzioni di salto, quindi la chiamata al check della scadenza del termine deve essere necessariamente in una delle chiamate, e non ci resta che scoprirlo eseguendole una ad una. Arrivati al 'POP EAX' non è ancora successo nulla, infatti basta eseguire (F10) la 'CALL 004039F0' che ecco come per magilla la nostra carissima messagebox che nuovamente di fa notare che dobbiamo cacciare fuori i soldi :-) per cui è il caso di tracciare ulteriormente. A questo proposito potrebbe essere il caso di settare un breakpoint alla locazione 0040683E per evitare la tiritera di trace step, ad libitum.
Supponiamo di essere nuovamente in procinto di eseguire 'CALL 004039F0' ed analizziamola kon F11; ecco la nuova porzione di codice risultante :
.
.
004039F0 89 05 88 C4 54 00 mov dword ptr ds:[54C488h],eax
004039F6 31 C0 xor eax,eax
004039F8 89 05 8C C4 54 00 mov dword ptr ds:[54C48Ch],eax
004039FE 89 15 90 C4 54 00 mov dword ptr ds:[54C490h],edx
00403A04 8B 42 04 mov eax,dword ptr [edx+4]
00403A07 89 05 20 C0 54 00 mov dword ptr ds:[54C020h],eax
00403A0D E8 D6 FE FF FF call 004038E8
00403A12 C6 05 24 C0 54 00 00 mov byte ptr ds:[54C024h],0
00403A19 E8 72 FF FF FF call 00403990
00403A1E C3 ret
.
.
con la stessa deduzione logica fatta in precedenza siamo portati a pensare che il codice del check sia in una delle due call. Vi risparmio la fatica e vi dico subito che il controllo viene effettuato eseguendo 'CALL 00403990'. Restartiamo ancora (manca poco, coraggio!) e riportiamoci subito prima dell' esecuzione della chiamata, tracciamola con F11 ed ecco :
.
.
00403990 55 push ebp
00403991 8B EC mov ebp,esp
00403993 53 push ebx
00403994 56 push esi
00403995 57 push edi
00403996 A1 88 C4 54 00 mov eax,[0054C488]
0040399B 85 C0 test eax,eax
0040399D 74 4B je 004039EA
0040399F 8B 30 mov esi,dword ptr [eax]
004039A1 33 DB xor ebx,ebx
004039A3 8B 78 04 mov edi,dword ptr [eax+4]
004039A6 33 D2 xor edx,edx
.
.
mmh... Qui il gioco si fa duro, e noi che siamo dei duri dobbiamo per definizione cominciare a giocare. Innanzitutto eliminiamo tutti i breakpoints eventualmente impostati in precedenza e settiamone uno solo in esecuzione dell' indirizzo 00403990. Fatto cio' cominciamo la nostra esplorazione del codice. Eseguendo con F10 istruzione per istruzione si arriva a questa parte di codice che è abbastanza critica :
.
.
004039B6 7E 14 jle 004039CC
004039B8 8B 04 DF mov eax,dword ptr [edi+ebx*8]
004039BB 43 inc ebx
004039BC 89 1D 8C C4 54 00 mov dword ptr ds:[54C48Ch],ebx
004039C2 85 C0 test eax,eax
004039C4 74 02 je 004039C8
004039C6 FF D0 call eax
004039C8 3B F3 cmp esi,ebx
004039CA 7F EC jg 004039B8
004039CC 33 C0 xor eax,eax
.
.
eseguendo il codice passo passo si entra in un loop, mentre se lasciassimo proseguire il programma (ve lo dico io ;-) fino 004039CC con un Run To... vedremmo immancabilmente spuntare la solita messagebox. Che fare? Innanzitutto bevetevi un Martini Cocktail o un Invisibile, perchè fa bene (anche se col crackin' c'entrano sega) dopodichè concentratevi sul codice. E' evidente che una volta entrati nel loop la sola condizione di uscita è rappresentata dalla istruzione 'CMP ESI,EBX' seguita da un 'JG 004039B8' (per chi non lo sapesse JG = Jump if Greater) che riporta l'esecuzione del programma indietro. Una occhiata ai registri della CPU ci fa vedere ESI = 0000009A mentre EBX = 000000xx, con xx un valore che dipende dalle volte che avete eseguito il ciclo. Infatti inizialmente EBX vale zero e viene incrementato ('INC EBX') presumibilmente fino a raggiungere e superare il valore di ESI (9A). All' indirizzo 004039C6 c'è una strana 'CALL EAX', che potrebbe spiegarci il senso di tanto ciclare : evidentemente EAX viene caricato (MOV EAX,DWORD PTR [EDI+EBX*8]) con il valore di una routine presa da una tabella di procedure (o di inizializzazione di oggetti) tra cui evidentemente anche la parte di codice che controlla se siamo ancora in trial period. Come potrete ben intuire se avessimo semplicemente saltato tutto cio' il programma si sarebbe miseramente piantato (eheheh ve lo giuro ;-) dato che manca tutta questa inizializzazione.
Visto che per come la vedo io siamo qui per imparare, vorrei che quello che ho scritto qui sopra non venisse letto frettolosamente. Non capita spessissimo di trovare un check nidificato in questa maniera, ma proprio per questo fate bagaglio di questa esperienza per evitare tentativi di suicidio davanti a qcosa di simile in futuro...
Questo purtroppo non ci dice nulla della soluzione del nostro problema, se non che saremo costretti ad eseguire le istruzioni del ciclo fino a che 'CALL EAX' non farà comparire il messagebox, in modo da prendere nota del valore di EAX al fine di piazzarci un nuovo breakpoint.
Io ovviamente ho fatto cosÏ, ma se volete potete impostare un breakpoint sul valore di EBX = 18h (esadecimale!) in quanto la chiamata al check viene fatta proprio per EBX = 00000018 con il registro EAX = 00447FD4. Questo ci da molte utili informazioni, poiche ora sappiamo con esattezza *dove* mettere il prossimo breakpoint, cioè appunto a 00447FD4. Quittiamo e restartiamo il programma fino ad incontrare il breakpoint, dove troveremo :
.
.
00447FD4 55 push ebp
00447FD5 8B EC mov ebp,esp
00447FD7 33 C0 xor eax,eax
00447FD9 55 push ebp
00447FDA 68 22 80 44 00 push 448022h
00447FDF 64 FF 30 push dword ptr fs:[eax]
00447FE2 64 89 20 mov dword ptr fs:[eax],esp
00447FE5 83 2D 7C C7 54 00 01 sub dword ptr ds:[54C77Ch],1 <--Giorni
rimasti
00447FEC 73 26 jae 00448014 <------- Meno di zero ?
cattivello...
00447FEE E8 AD 1D FC FF call 00409DA0
00447FF3 D8 25 2C 80 44 00 fsub dword ptr ds:[44802Ch]
00447FF9 DD 1D 68 C7 54 00 fstp qword ptr ds:[54C768h]
00447FFF 9B wait
00448000 C7 05 78 C7 54 00 FF mov dword ptr ds:[54C778h],0FFFFFFFFh
0044800A E8 7D FA FF FF call 00447A8C
0044800F E8 88 F4 FF FF call 0044749C
00448014 33 C0 xor eax,eax <----- Piu' di zero ? Ok
proseguiamo!
00448016 5A pop edx
00448017 59 pop ecx
00448018 59 pop ecx
00448019 64 89 10 mov dword ptr fs:[eax],edx
0044801C 68 29 80 44 00 push 448029h
00448021 C3 ret
.
.
mi scuso se ho riportato questa valanga di codice macchina, ma siamo arrivati al cuore della routine di check. Il succo di tutto il check sta in quel 'SUB DWORD PTR DS:[54C77Ch],1', ovvero nel decremento di una variabile. l'istruzione successiva 'JAE 00448014' fa in modo che il programma salti a 00448014 se la variabile vale zero o piu' di zero (JAE = Jump if Above or Equal). Che mi venga un accidente se quelli non sono i giorni rimasti! Se sono rimasti 0 giorni di prova e ne tolgo uno la variabile contiene un numero negativo e la procedura non salta. Tutto qui. La tentazione è davvero forte e visto che comunque il breakpoint ce lo siamo già marcato, tantovale fare la prova. Eseguiamo fino al salto condizionato e simuliamolo settando l' EIP appunto a 00448014. Se la nostra teoria fosse esatta il programma non dovrebbe + mostrare la messagebox ma proseguire indisturbato.
Se proviamo a lasciar andare il programma (F5 sotto VC++) in realtà le cose non sono cosÏ semplici. La messagebox NON viene piu' visualizzata, viene visualizzata la splashform di presentazione giusta, ma nonostante cio' il programma esce. Evidentemente ci sono altri checks :-( A questo punto la disperazione potrebbe avervi colto. "Ma come, un onesto Reverse Engineer si da' cosÏ tanto da fare e poi il risultato è tutto qui? BWaaaaah" (segue pianto disperato).
In realta' questo crack presenta altre insidie, ed inoltre ha diverse possibili 'soluzioni' a seconda del livello di eleganza che si è in grado di raggiungere. Visto che in fondo questo è un tutorial e che se foste in grado di fare tutto da soli molto ma molto probabilmente non stareste perdendo tempo con il mio scritto, vi dico brevemente come si arriva ad una soluzione (la piu' immediata e meno elegante, quella di cui un purista non dovrebbe mai accontentarsi). Riprendete in mano il debugger e settate un nuovo breakpoint proprio sotto la prima chiamata in questo punto esatto :
.
.
005488DD 8B EC mov ebp,esp
005488DF 83 C4 EC add esp,0ECh
005488E2 B8 04 84 54 00 mov eax,548404h
005488E7 E8 34 DF EB FF call 00406820
005488EC A1 8C B5 54 00 mov eax,[0054B58C] <-- Breakpoint qui
005488F1 8B 00 mov eax,dword ptr [eax]
.
.
ora attivate entrambi i breakpoint trovati, questo appena inserito e quello che interrompe il programma all' inizio della prima routine di check, ovvero all'indirizzo 00447FD4. Con questi due BP impostati lanciate il programma (F5), eseguite la routine di check fino al salto condizionato visto prima (il 'JAE 00448014'), saltate direttamente a 00448014 e date nuovamente F5. A questo punto vi troverete proprio sotto la chiamata iniziale, nel corpo principale del programma.
Se stepperete il programma (F10) da qui troverete che il programma stesso si chiuderà eseguendo una malefica 'CALL 00403C14', e che la stessa chiamata è ripetuta con lo stesso schema per ben quattro volte prima dell'inizio della routine del programma :
.
.
005488F8 84 C0 test al,al
005488FA 74 07 je 00548903
005488FC 33 C0 xor eax,eax
005488FE E8 11 B3 EB FF call 00403C14 <--<<< Call Malefica
00548903 E8 7C F8 EF FF call 00448184
.
.
00548942 9E sahf
00548943 76 07 jbe 0054894C
00548945 33 C0 xor eax,eax
00548947 E8 C8 B2 EB FF call 00403C14 <--<<< Anche qui !
0054894C A1 78 B9 54 00 mov eax,[0054B978]
.
.
00548969 84 C0 test al,al
0054896B 75 07 jne 00548974
0054896D 33 C0 xor eax,eax
0054896F E8 A0 B2 EB FF call 00403C14 <--<<< E qui !
00548974 8B 0D 18 BA 54 00 mov ecx,dword ptr ds:[54BA18h]
.
.
005489AB 9E sahf
005489AC 76 07 jbe 005489B5
005489AE 33 C0 xor eax,eax
005489B0 E8 5F B2 EB FF call 00403C14 <--<<< E pure qui !
005489B5 A1 78 B9 54 00 mov eax,[0054B978]
.
.
Ok, riassumendo il lavoro fatto fino ad ora (uff...) per sperare di usare 'sto benedetto programma oltre i 30 giorni del trial period dobbiamo modificare *cinque* istruzioni di salto condizionato in altrettanti salti incondizionati ovvero Jxx -> JMP.
Se volete provare con un hexeditor a cercare nel file eseguibile la sequenza di bytes < 83 2D 7C C7 54 00 01 [73] 26 > e sostituire '73' con 'EB' (JAE con JMP in poche parole) e poi tutte le varie sequenze dei 4 salti condizionati e sostituite l'istruzione di salto incondizionato JMP (per inciso : < 84 C0 [74] 07 33 C0 E8 11 > con 'EB' al posto di '74' , < 9E [76] 07 33 C0 E8 C8 > 'EB' al posto di '76', < 84 C0 [75] 07 33 C0 E8 A0 > 'EB' al posto di '75' e infine < 9E [76] 07 33 C0 E8 5F > 'EB' al posto di '76' ) e salvare il tutto, potrete vedere che con somma gioia il programma FUNZIONA! La splash form e la prima schermata riportano 0 giorni rimasti, ma uno potrebbe dire 'Ecchissenefrega' e tanti saluti. Noi NO.
Questa come dicevo è una soluzione perfettamente funzionante, relativamente poco invasiva (5 bytes modificati su un file di oltre 2Mb) ma decisamente poco elegante. Dato che siamo/siete qui per IMPARARE, cerchiamo di andare oltre questa prima modifica.
Prima di tutto sarebbe il caso di esaminare cosa fa la chiamata 'CALL 00403C14' che fa terminare il programma. Vi riporto il codice :
.
.
00403C14 89 05 30 C0 54 00 mov dword ptr ds:[54C030h],eax
00403C1A E9 DD FE FF FF jmp 00403AFC
00403C1F C3 ret
.
.
beh, che dire... invece che modificare le istruzioni di salto per fare in modo che non venga eseguita questa chiamata si potrebbe + elegantemente (ed anche + utilmente, vedi il mio tutorial sul dongle) inserire come prima istruzione un bel 'RET', visto che comunque le istruzioni di salto condizionato viste in precedenza puntano tutte all'istruzione successiva a 'CALL 00403C14'. Piazzando un 'RET' all' indirizzo 00403C14 non bisogna dimenticarsi di noppare i bytes in modo da riempire la lunghezza dell' istruzione fino a quella successiva (il NOP è rappresentato dal valore 90h).
Anche questa soluzione è perfettamente funzionale, e ci evita di modificare i 4 salti condizionati modificando solamente una istruzione nella stessa CALL da loro eseguita.
Puo' bastare? Per usare il programma si, certo. Per voi NO.
Ecco allora la parte del crack lasciata aperta :
LA PARTE DEL CRACK LASCIATA APERTA
(bel titolino vero ?:-)
Ok, si diceva che se lo scopo fosse stato un crack fine a se stesso ce ne sarebbe stato già d' avanzo. Siccome noi siamo gente ONESTISSIMA e reversiamo i programmi al solo scopo di imparare a debuggare i nostri (cazzo quasi quasi ci credo pure io tanto che lo dico bene...) è d' obbligo estendere i nostri orizzonti oltre le barriere del conosciuto.
Voglio infatti lasciare in sospeso alcune migliorie, parte delle quali le ho sperimentate personalmente, mentre altre mi sono venute in mente quando oramai avevo chiuso l' R-FILE (Reverse File :-) e non avevo voglia di rifare tutto daccapo. Di conseguenza alcune modifiche sono semplici da fare, altre mediamente difficili, altre di difficoltà sconosciuta a priori. Non vi dico niente in anticipo altrimenti sareste tentati di provare solo quelle già sperimentate da me. Sappiatemi dire ovviamente...
- UNO) Eliminare completamente la form di apertura che ci annoia con quel suo assurdo tasto ESCI (bahh!!);
- UNObis) Eliminare di conseguenza la stessa form all'uscita del prg.
- DUE) Modificare il programma in modo da continuare a sembrare shareware con un tot di giorni mancanti alla scadenza anche dopo che il prg ha cessato di funzionare;
- TRE) Modificare il programma per fare in modo che il programma appaia registrato a nome di qkuno (che ne so, RingZer0 Inc. :-) e si comporti di conseguenza (il codice lo prevede già, basta attivarlo);
- QUATTRO) In realtà la chiamata che noi eliminiamo con il RET ('CALL 00403C14') ha anche altre funzioni e non viene eseguita sempre tutte e 4 le volte che compare : tracciatela e modificate il programma in modo che essa non faccia terminare il programma (ovviamente in maniera piu' elegante che con il RET iniziale!.
Alcune tracce : Prima di tutto non vi ho fatto aprire WDasm32 tanto per giocare, di conseguenza utilizzatelo perchè puo' tornare molto utile per questa parte; Il ciclo 'CALL EAX' che vi ho detto di analizzare con cura non finisce dopo il check ma fa altre cose;
Non sempre c'è una sola variabile per tenere traccia di un dato.
Comments