Zoom Icon

Corso UIC Avanzato 10 LittleLuk

From UIC Archive

UIC Lezione 10 (UIC Strainer)

Contents


Corso UIC Avanzato 10 LittleLuk
Author: LittleLuk
Email: [email protected]
Website:
Date: 18/10/2005 (dd/mm/yyyy)
Level: crittanalisi: Working brain required
reversing:Damn hard, a lot of experience and luck are required
Language: Italian Flag Italian.gif
Comments: Grazie Lit! Finalmente qualcuno dopo 5 anni ha scritto un tutorial! Ed e' veramente ben fatto, ottimo lavoro!



Introduzione

Per caso sono capitato nella e ho visto che Que mi dava del leim perchè non avevo ancora risolto il suo strainer. Ho deciso di ri-affrontare questo crackme che già mi aveva sconfitto nel 2001 :( (dai che ero ironico ;p NdQue)


Tools

Ida
Exescope
peeditor
calc
winhex


Link e Riferimenti

Questo è la soluzione del Corso UIC Avanzato n°10 disponibile alla pagina Corso UIC Avanzato 10


Essay

Questa è solo una rapida ma dettagliata spiegazione di come funziano alcune tecniche antireversing e di come sia possibile trascurarle e risolvere il crackme in modo originale.
Le tradizionali tecniche di reversing non sono efficienti a causa dei trick implementati ma anche per bug presenti nel programma, d'altronde non si può pretendere troppo da un giovane Quequero e da una Alguzza che non ha ancora conquistato la terra ferma! (il vero problema fu che perdemmo entrambi il sorgente originale, io per un crash dell'hdd e Alga perche' cambio' casa e perse alcune cose, quello strainer era privo di questi bug ed includeva anche il chain dell'int1 che sullo strainer attuale non e' presente NdQue).

Tenete presente che questo è un crackme del 2001: ai quei tempi il debugger usato quasi universalmente era il softice e il sistema operativo più diffuso era win98 che permetteva di fare giochetti divertenti con poca fatica, cose non più funzionanti sui moderni sistemi operativi.
Il computer utilizzato per reversare questo crackme non ha win98: non potendo fare girare il programma ho studiato il disassemblato fornito da ida.

Quindi, mano al disassemblatore, bisogna perdere un po' di tempo a "undefinire" e "ridefinire" il codice per renderlo comprensibile. Questo è necessario perchè sono stati inseriti dei byte inutili tra i veri opcode delle istruzioni, questi byte a runtime vengono evitati tramite jump e call a indirizzi opportuni ma rompono le scatole ai disassemblatori perchè non riescono a distinguerli dai veri opcode. Seguendo manualmente il flusso del programma con tutti i jump e chiamate è possibile riconoscere e trascurare i byte spazzatura (imho non si tratta di codice automodificante).

La prima cosa che si vede è un controllo sulla versione del sistema operativo (con ida ho rinominato e inserito delle etichette per facilitare la lettura e comprensione di quello che succede) 401000 call GetVersion 401005 cmp eax, 80000000h 40100A jnb short SO_ok 401071 SO_ok: 401071 call loc_40167F Quest' ultima call esegue: 40167F mov eax, offset loc_401755 401684 mov ds:word_402721, ax 40168A shr eax, 10h 40168D mov word ptr ds:dword_402727, ax 401693 sidt ds:qword_4026C7 40169A mov ebx, dword ptr ds:qword_4026C7+2 4016A0 add ebx, 18h 8*3: ciascun gate occupa 8 byte, mi serve quello dell' int3 4016A3 mov edi, (offset qword_4026C7+6) istruzioni per il salvataggio dell' int3 attuale,

                                       lo riuserà per risistemare alla fine

4016A8 mov esi, ebx 4016AA movsd 4016AB movsd 4016AC mov edi, ebx istruzioni per sovrascrivere il vecchio int 4016AE mov esi, offset word_402721 4016B3 movsd 4016B4 movsd 4016B5 mov ds:dword_402705, ebx 4016BB retn La prima cosa che mi ha colpito è l' utilizzo della istruzione sidt: pone nella qword 4026C7 la base della idt.
La idt è la tabella che contiene tutti gli indirizzi delle interruput del sistema operativo.
Si può rinominare qword_4026C7 con base_idt.

Con le istruzioni successive va a modificare il contenuto di base_idt+18h cioè riscrive l 'indirizzo dell' int3, se il funzionamento non vi è troppo chiaro andate a vedere http://www.fuckinworld.org/active85k/assembly/tut07.htm. Il codice è esattamente lo stesso, cambiano solo i nomi delle variabili.
In conclusione il compito della call 40167F è quello di rimappare la vera int3 con una new_int3 che sono istruzioni presenti nello strainer a loc_401755. Questo codice appartiene ad un programma che gira in user mode ma qualsiasi applicazione che, da adesso, chiama una int3 esegue new_int3 come fosse a ring0. Questo bel giochino non funziona su winNT o successivi, è così è giustificato il controllo della versione del sistema operativo visto in precedenza.

Ogni volta che con un debugger si piazza un breakpoint si inserisce un' int3 che, semplificando, quando viene eseguita fa poppare il debugger. Questo è quello che fa la vera int3 ma c'è da aspettare che quella nuova contenga dei trick per complicare la vita ai reverser.
Andiamo a vedere se è così: 401755 new_int_3: 401755 cmp eax, 7C2h 40175A jz short eax_7c2 40175C cmp eax, 7C1h 401761 jz short eax_7c1 401763 cmp eax, 7D0h 401768 jz short eax_7d0 40176A mov eax, cr0 reboot pc 40176D and eax, 0 401770 mov cr0, eax 401773 iret 401774 eax_7c1: 401774 mov eax, ds:dword_402138 401779 xor eax, ds:dword_402134 40177F jmp eax 401781 iret 401782 eax_7c2: 401782 xor [esi+ecx], edi 401785 sub ecx, 4 401788 test ecx, ecx 40178A jnz short eax_7c2 40178C iret 40178D eax_7d0: 40178D mov eax, dr7 401790 cmp eax, 42Ah 401795 jg short setta_edi_415 401797 xor eax, eax 401799 xor edi, edi 40179B iret 40179C setta_edi_415: 40179C mov edi, 415h 4017A1 iret La nuova int3 è usata come una normale funzione di un qualsiasi programma che accetta parametri in ingresso. Termina con IRET perchè è un RET da un Int: è analogo ad un ret classico ma in più fa passare da r0 a r3. Il passaggio parametri avviene tramite i valori dei registri.
Se eax = 7C2 esegue una operazione di decrypt: edi è la key, ecx è la lunghezza della zona da decryptare e esi punta alla zona da decryptare.
Se eax = 7C1 esegue un jmp eax.
Se eax = 7D0 va a leggere il debug register DR7 (lo può fare perchè questo codice è eseguito a ring0) e ritorna
eax = edi = 0 se DR7 <= 42A altrimenti ritorna edi = 415
In tutti gli altri casi azzera il control register CR0 (sono sempre a r0) provocando un reboot.

Leggendo http://www.woodmann.com/crackz/Tutorials/Protect.htm, per esempio, si vede che il controllo sul DR7 è un antisice; pure il reboot è usato per impedire l' utilizzo dei breakpoint: infatti se l' int3 non viene eseguita con i parametri esatti vado in crash :(

Riprendo l'analisi, dopo aver hookato l' int3 c'è: 40107A push offset sub_401AA5 401083 push dword ptr fs:0 40108D mov fs:0, esp Che è la procedura standar per installare un gestore di eccezioni: Structured Exception Handling SEH. Ogni volta che lo strainerd genererà una eccezione questa sarà gestita da sub_401AA5. Con una rapida occhiata a quest'offset si scopre che le eccezioni gestite esplicitamente sono la divisione per 0, opcode non valido, breakpoint, single step e errore di accesso in memoria.

Dopo eseguo: 401097 call sub_4019C4 40109C call sub_4017FD 4010A1 call sub_401B4D 4010A6 call sub_401BE6 4010AB call sub_40170C che hanno tutte quante una struttura analoga alla call vista prima per l' int3 quindi pure queste hookano la idt, rispettivamente gli int 9, 18, 2, 5, 4

4019C4 aggiorna l' int9 con 401A30

int 9 chiamata con:
esi = 400 modifica dr7, pone dr0= ebx e ritorna eax= DR7 || 55h
esi = 869 pone eax= dr7
esi = 873 modifica key2[0], key2[1] (vedi più avanti per capire cosa sono)
esi = 855 esegue new_int3
altrimenti reboot

4017FD aggiorna l' int18 con 4019AB che si occupa di rilevare il debugger e torna eax = 0 se non ho sice 312h in caso contrario: 4019AB xor ax, 18h 4019AF int 41h 4019B1 xor ax, 5566h 4019B5 cmp ax, 0A6E0h controllo se int 41h torna ax = F386 4019B9 jz 4019BE 4019BB xor eax, eax 4019BD iret 4019BE mov eax, 312h 4019C3 iret Si sa che (vedi http://www.ctyme.com/intr/int-41.htm la mitica guida di Ralph Brown) che l' int41h chiamata con ax=4Fh rileva un debugger perxhè ritorna eax= F386 se lo trova. Guarda caso 5566^ F386 fa proprio A6E0. Per avere ax=4F al momento della chiamata bisogna chiamare new_int18 con ax= 4F^18 = 57 e infatti l' int18 è chiamato solo una volta a: 4015AE mov ax, 57h come previsto! 4015B2 int 18h 401B4D aggiorna l' int2 con 401C31 che chiamata con
eax = 430 decrypta: esi= key di decrypt, ecx= len decrypt, edi= punta alla zona da decryptare
altrimenti reboot

401BE6 aggiorna l' int5 (un classico vedi per esempio il k2, ciao Yado, e un sacco di altri programmi) con 401BB0 che chiamata con
eax = 700 modifica dr7 e pone eax= DR7 && FFFFFF2A
eax = 70D decrypta: edi= key di decrypt esi= len decrypt ecx= punta alla zona da decryptare
altrimenti rimango freezato a r0 che equivale e spegnere il pc manualmente, sigh

40170C aggiorna l' int4 con 401884 che chiamata con
eax = 360 ritorna
-esi= 715 se dr7 > 418
-esi= 714 se dr7 <= 418
-eax= dr7
Ha tutta l' aria di essere un antisice

eax = 350 ritorna
-esi= 718 se ho breakpoint memory (quando setto un bpmb il debugger va a modificare DR0-DR3: è per questo motivo che è possibile settarne solo 4)
-esi= 714 se non ho bpmb

eax = 340 elimina i bpmb e modifica DR7: DR7= DR7 && FFFFFF2Ah

altrimenti reboot: siamo a r0 e va a leggere all' indirizzo 0 provocando così un errore fatale.

Per comprendere il perchè DR7 venga modificato in quei modi consiglio di leggere il paragrafo 12.2.2 a http://www.online.ee/~andre/i80386/Chap12.html

Un' altra int molto usata, oltre all' int3, è l' int1 che serve per effettuare lo stepping e come previsto viene rimappata a 4018FA così si è impossibilitati a tracciare il programma, anche per via del gestore di eccezioni che controlla il single step.
Non riesco a trovare nessun riferimento a 4018FA quindi non sono sicuro che il programma usi effettivamente l' int1 rimappata anche se poi eseguo 40164F call risistema_int1 cosa risistema? sostituisce la vera int1 con un old_int1 pseudocasuale?!? Così a memoria mi sembra di ricordare che Que aveva accontentato noi poveri leim semplificandoci il lavoro, non vorrei che avesse evitato la rimappatura dell' int1, se così fosse doveva eliminare pure la sua risistemazione.

Ad ogni modo la nuova int1 chiamata con
edx = 780 calcola il crc su una zona passata tramite eax e ebx che rappresentano rispettivamente l'inizio e la fine della zona sottoposta a crc. Ritorna cx = ax = crc
edx = 663 chiama la nuova int18
edx = 7C0 fa un decrypt sospetto, modifica cr0 e pone eax = tr3, probabilmente è un codice che non verrà mai eseguito ma serve a complicare la vita
altrimenti reboot

Poi segue il codice che crea la finestra del programma che è inutile per risolvere il crackme. Andiamo avanti. 40120E cmp [ebp+wParam], 67h 401212 jnz short loc_401219 401214 call loc_40125F 67h = 103 e utilizzando exescope si vede che 103 è l' id del pulsante register, quindi la call subito sotto è responsabile di tutte le cose malefiche, dei controlli e del decrypt. Siamo nel cuore del crackme.

Procedendo mi perdo nel codice complicatissimo ma trovo una zona interessante: 401607 xor ecx, ecx 401609 mov eax, dword ptr ds:40216F 40160E mov ebx, offset 401026 401613 decrypta_1: 401613 xor [ebx+ecx], eax 401616 add ecx, 4 401619 cmp ecx, 44h 40161C jnz short decrypta_1 40161E xor ecx, ecx 401620 mov eax, dword ptr ds:40216B 401625 mov ebx, offset 401026 40162A decrypta_2: 40162A xor [ebx+ecx], eax 40162D add ecx, 4 401630 cmp ecx, 44h 401633 jnz short decrypta_2 401635 jmp 401026 Spiego cosa fa questa porzione del programma. Si tratta di una funzione che decrypta i 44h byte posizionati a partire da 401026. Ci sono 2 layer di decrypt identici ciascuno dei quali carica in eax la chiave (40216F contiene la key1 mentre 40216B la key2), in ebx viene scritto l' inizio della zona da decryptare. La seconda volta è inutile dato che ebx non viene modificato ma Que vuole farci sprecare un po' di cicli macchina non sapendo che così facendo ha introdotto una debolezza che mi ha permesso di decifrare la zona criptata senza conoscere le chiavi. Ecx è l' analogo della variabile i usata come contatore nei cicli for.

Una volta decifrato il codice lo vado ad eseguire tramite un jmp 401026 e se tutto è corretto il programma deve mostrare la msgbox di congratulazioni, deve rimuovere l' handler per la gestione delle eccezioni, rimettere a posto la idt e terminarsi. 40163A mov eax, fs:0 40163F mov esp, [eax] 401641 pop dword ptr fs:0 401647 add esp, 4 Così rimuove l' handler per la gestione delle eccezioni 40164A call risistema_int_3 40164F call risistema_int_1 401654 call risistema_int_9 401659 call risistema_int_2 40165E call risistema_int_4 401663 call risistema_int_18 401668 call risistema_int_5 Ho pure sistemato la idt 40166D push ds:dword_402741 401673 call _lclose 401678 push 0 40167A call ExitProcess e così ho terminato il processo.
Manca il codice che mostra l' avvenuta registrazione quindi è nascosto nel codice cryptato. Con le istruzioni eseguite poco sopra decrypto 401026 - 401069. Subito dopo ho 40106A jmp near ptr unk_401261 che mi porta ad un' altra porzione di codice criptato di lunghezza pari a 18h byte. Non capisco perchè questa istruzione sia in chiaro infatti permette di fare alcune supposizioni.

Ipotizzo che per far apparire la messagebox di congratulazioni siano state usate operazioni standard: push uType push offset Caption push offset Text push hWnd call MessageBoxA che sono 19 = 13h byte. Poi ci vuole un jmp 40163A per far proseguire il programma correttamente con la rimozione del gestore eccezioni e tutto quello già detto in precedenza. Suppongo che anche per questo jump Que e Alga abbiano usato un jump del tipo E9 XX XX XX XX proprio come quello che si trova a 40106A (Notate quante cose si ripetono!) quindi altri 5 byte per un totale di 24 = 18h byte esattamente tanti quanti sono i byte cryptati a 401261. Sembra che tutto quanto si incastrarsi senza problemi! Per completare il puzzle bisogna che a 401026 ci siano le istro per il decrypt dei 24 byte successivi e rimanendo sempre nel campo delle ipotesi suppongo che le istro poste a 401026 siano identiche a quelle che si trovano a 401607.

Riassumo per chi si è perso:

401607 decrypta ed esegue 401026 - 401069
401026 - 401069 decrypta ed esegue 401261 - 401278
401261 mostra la msgbox di congratulazioni e salta a 40163A
40163A rimette tutto a posto e esce

Partiamo dall' inizio e semplifichiamo un po' le cose usando le proprietà dello xor (considero una dword come un vettore di 4 byte): 401026_decriptato[i]= (401026_criptato[i] ^ key1[i%4]) ^ key2[i%4] che equivale a 401026_decriptato[i]= 401026_criptato[i] ^ key3[i%4] dove key3[i%4]= key1[i%4] ^ key2[i%4] (Attenzione alla notazione intel che inverte i byte delle dword)

Questo vuol dire che a byte cryptati uguali corrispondono byte decryptati uguali se si trovano a distanza multipla di 4 byte tale cioè da essere xorati con lo stesso byte della key3

Tutto questo è simile a ciò che un crittoanalista chiamerebbe "codice a sostituzione monoalfabetica" che si rompe facilmente andando a cercare le frequenze di ripetizione dei simboli. Ecco perchè ho evidenziato tutte le parti uguali presenti nel programma.

Andiamo a cercare i simboli ripetuti nel codice cryptato che si trovano a distanza multipla di 4:

[40103C] = [401048] = 12; sono a distaza 0Ch --> lo decrypto con key3[2]
[40103E] = [40104A] = D0; sono a distaza 0Ch --> lo decrypto con key3[0]
[40103F] = [40104B] = F7; sono a distaza 0Ch --> lo decrypto con key3[1]
[401040] = [ 40104C] = A8; sono a distaza 0Ch --> lo decrypto con key3[2]

Per tutti questi byte l' uguaglianza sarà mantenuta anche dopo il decrypt e colpisce il fatto che siano indirizzi quasi contigui, potrebbe trattarsi di qualche operazione, per esempio mov, che coinvolge offset simili. Tenendo conto della notazione intel, dei virtual address e della lunghezza del programma ipotizzo si tratti di un mov registro, 004XYyZz dove XYyZz sono incognite ---> opcode: Pp Zz Yy 4X 00 dove Pp dipende dal registro.
Zz nei 2 casi è differente dato che i rispettivi byte criptati sono differenti: si tratta dei byte meno significativi dell' indirizzo e quindi i due indirizzi messi nel registro puntano a zone di memoria vicine ma non coincidono. Il fatto è ragionevole dato che il programma è piccolino e usa poche variabili. Aver trovato un possibile mov è molto importante perchè è una istruzione di 5 byte di cui il primo può assumere solo un insieme limitato di valori mentre per l' ultimo è possibile fare le ipotesi sempificative indicate poco sopra e soprattutto, il primo e l' ultimo byte sono a distanza pari a 4 perciò vengono decriptati con lo stesso byte della chiave.

Per ricavare la key di decrypt uso le solite proprietà dello xor key= byte in chiaro XOR byte cryptati ---> (uso Qq perchè nei due casi, 40103D e 401049, questo codice cambia) Pp Zz Yy 4X 00 XOR 12 Qq D0 F7 A8 =


Gg Hh Mm Bn A8 <--- Gg Hh Mm n sono valori da determinare

Per quanto detto poco sopra e sapendo che la chiave ha lunghezza 4 si ricava che key3[0] == key3[4] da cui
Gg == A8

L' istruzione a 40103C ora è diventata ([40103c]^Gg == 12^A8 == BA)
BA aa bb 4c 00 = mov edx, 00 4X Yy Zz che come previsto è un mov.
A8 (che è l'unico byte della chiave che conosco esattamente) oltre a decryptare 40103C decrypta pure 401040,401044... e in particolare

401060 che decryptato diventa 04
401064 che decryptato diventa 04
401068 che decryptato diventa 75, è un possibile jnz

Cerco questi byte nel disassemblato e ne trovo di simili a:

401613 31 04 0B xor [ebx+ecx], eax
401616 83 C1 04 add ecx, 4
401619 83 F9 44 cmp ecx, 44h ; in tutto devo decryptare 44h byte
40161C 75 F5 jnz short decrypta_1

Come si vede i byte '04' e '75' sono tutti a distanza= 4 come nel caso di 401060, suppongo quindi che 401060 e dintorni siano il loop di decrypt che va a decryptare 401261 - 401278, ipotizzo pure che ci siano 2 layer di decrypt simili a 401613 solo che qui devo decryptare 18h byte e non 44h.
Questo dovrebbe farvi capire perchè lo studio delle ripetizioni è alla base della rottura dei cifrari, dai più semplici ai più complessi passando per enigma :)

Per verifica

401614 corrisponde a 401060 --> 04 deve corrispondere a AC^A8 = 04 OK
401618 corrisponde a 401064 --> 04 deve corrispondere a AC^A8 = 04 OK
40161C corrisponde a 401068 --> 75 deve corrispondere a DD^A8 = 75 OK

se il loop di decrypt è identico a quello a 401613 si ha pure che a 401069 dopo il decrypt deve contenere F5 e non 5D come nel codice criptato ma F5 = 5D ^ A8.
Analogamente ricavo:

[401619] = 83 corrisponde a [401065] 2B^A8 = 83
[40161A] = F9 corrisponde a [401066]; lo decrypto con 3B^F9 = C2
[40161B] = 44 corrisponde a [401067] che sono il numero dei byte da decryptare;
  in questo caso ne ho 18h e non 44h quindi decrypto con AF^18 = B7

Finalmente ora ho la chiave di decrypt:

key3[0]= C2
key3[1]= B7
key3[2]= A8
key3[3]= A8
key3 = A8A8B7C2h

Non rimane che buttare giù un po' di righe in C e finalmete ottengo il codice decryptato: 401026 mov eax, ds:402133 40102B xor eax, 3A4C1F23h 401030 mov ebx, ds:402137 401036 xor ebx, 86793D9Ah 40103C mov edx, 401270h 401041 mov word ptr [edx], 9090h 401046 xor ecx, ecx 401048 mov edx, 401275h 40104D decrypt_401275: 40104D xor [edx+ecx], ebx 401050 add ecx, 4 401053 cmp ecx, 18h 401056 jnz short decrypt_401275 401058 xor ecx, ecx 40105A mov edx, 401275h 40105F decrypt_2_401275: 40105F xor [edx+ecx], eax 401062 add ecx, 4 401065 cmp ecx, 18h 401068 jnz short decrypt_2_401275 40106A jmp loc_401261 Come previsto ho i 2 loop di decrypt simili a quelli in chiaro e il jmp alla loc_401261 dove ho il codice per la messageboxa. Ho solo un mov [edx], 9090h che non mi aspettavo, a cosa servirà?
Si nota quello che secondo me è un bug: i 2 loop decryptano 401275 - 40128D invece che 401261 - 401279. Probabilmente è dovuto a qualche byte errato disseminato qua e la.

Non rimane che capire come decryptare 401261 - 401279, anche qui ho due layer di xor con key4 (402133) e key5 (402137) che posso ricondurre ad uno solo che usa una key6 opportuna.
Da quanto detto prima si sa che ci sono i vari push, call, jmp e quindi il codice potrebbe avere una struttura simile a: 6A 20 68 xx xx 4x 00 68 xx xx 4x 00 6A 00 E9 xx xx 00 00 per ora ho: 11 B5 F5 20 5E D5 9D 7C 3C B0 DD 14 11 95 75 A3 71 95 9D FD BF 96 9D 14 il 1° byte e il 13° sono uguali e sono a distanza 12, 12%4=0 quindi vuol dire che li decrypto con key6[0]:

codice_crypt[00]^key6[0] = 11^key6[0] = 6A
codice_crypt[12]^key6[0] = 11^key6[0] = 6A ---> key6[0] =  11^6A = 7B

Utilizzando questa info trasformo il codice in 6A B5 F5 20 25 D5 9D 7C 47 B0 DD 14 6A 95 75 A3 0A 95 9D FD C4 96 9D 14 F5 20 25 D5 9D deve corrispondere a push caption della messageboxa di registrazione.
Una possibile caption è quella che si trova a: 4025C4 "Gooooooood!!!!!!!!" che tradotta in codice asm diventa push 4025C4h --> 68 C4 25 40 00 per far corrispondere anche gli altri byte deve essere:

codice_crypt[02]^key6[2]= F5^key6[2] = 68 ---> key6[2]= 9D
codice_crypt[03]^key6[3]= 20^key6[3] = C4 ---> key6[3]= E4
codice_crypt[05]^key6[1]= D5^key6[1] = 40 ---> key6[1]= 95

Vado a decryptare il resto e ottengo: 6A 20 68 ** 25 40 00 ** 47 25 40 ** 6A 00 E8 ** 0A 00 00 ** C4 03 00 ** Va tutto bene ma ho ancora dei problemi con i byte contrassegnati con ** perchè ottengo indirizzi senza senso, ma so che il secondo ** deve corrispondere a 68 quindi (ne sono certo perchè è l' opcode del push e non l'offset di qualche variabile)
ottengo:

codice_crypt[08]^key6[3]= 7C^key6[3] = 68 ---> key6[3]= 14

6A 20 68 34 25 40 00 68 47 25 40 00 6A 00 E8 B7 0A 00 00 E9 C4 03 00 00 Ci siamo quasi: ci sono ancora un po' di errori ma forse sono dovuti ai soliti byte errati (il programma è corretto, c'è solo un check antidebug che è implemenatato in modo errato, il crackme si può risolvere patchando solo un byte, vero Que&Alga?).

Con ida si vede che è meglio avere 6A 20 68 C4 25 40 00 68 D7 25 40 00 6A 00 E8 48 0A 00 00 E9 C1 03 00 00 che corrisponde a: 401261 push 20h 401263 push offset Text "Gooooooood!!!!!!!!" 401268 push offset aOhYeahIMRegist "Oh Yeah! I'm registered now!!!\r\nNice 40126D push 0 40126F call MessageBoxA 401274 jmp loc_40163A rimuove gestore eccezioni e sistema IDT è da tenere presente che ho 401041 mov word ptr [edx], 9090h che modifica i byte in modo non previsto quindi la si può noppare.

In conclusione:

key3= key1^key2 = C2 B7 A8 A8
key6= key4^key5 = 7B 95 9D 14

Con ida guardo quali sono i valori costanti che posso assumere le varie key (trascurando completamente gli altri):

viene settata a all' indirizzo viene settata a all' indirizzo viene settata a all' indirizzo
key1[0] 63h 4014C4 8Fh 40185C
key1[1] 5Fh 40152E C8h 4015A7 4015B8 46h
key1[2]
key1[3] F8h 401749
/
key2[0] 2Fh 4016F5 1Ch 401BA4
key2[1] 16h 40155D 7Fh 401A81
key2[2] C0h 401A88
key2[3] 50h 401B35 53h 401B41

e, mano alla calcolatrice, si vede che:

key3[1]= B7 = C8 ^ 7F
key3[3]= A8 = F8 ^ 50

da cui si ricava che

key1[1]= C8h   key1[3]= F8h
key2[1]= 7Fh   key2[3]= 50h

Abbiamo così ricavato altre info riguardanti il flusso del programma per arrivare al "Good work, Man".
Se volete provare a trovare quei pochi byte che Quequero dice che bisogna patchare per far partire il crackme vi do qualche altro indizio. Penso che gli autori abbiano seguito questa strada perchè altrimenti sarebbe stato banale trovare le varie key o avere il codice decryptato in chiaro e dumparlo.
Dopo la malefica call 40125F che esegue un po' di xor tra registri fino ad arrivare a 4012D6 jmp eax che manda a 40131E. Qui decrypta la stringa posta a 402028 che poi passa come parametro a CreateFileA: 401381 push 0 401383 push 80h 401388 push 3 40138A push 0 40138C push 3 40138E push 0C0000000h 401393 push offset nome_file 401398 call CreateFileA 4013A3 cmp eax, 0FFFFFFFFh 4013A6 jz short loc_4013D4 La struttura è quella classica di un controllo meltice per detectare il sice ma il decrypt del nome del file è errato (come segnalato da Que) e quindi il controllo fallisce. Per sicurezza si può ebbare 4013A6 in modo dal far fallire comunque il check. Attenzione a eventuali crc. Riprendiamo.
Il jump porta a: 4013D4 push eax 4013D5 call CloseHandle 4013DA jmp 4014FD Ma se CreateFileA ritorna -1 vuol dire che non c'è nessun handle, perchè lo devo chiudere? Non penso serva per detectare un debbuger usermode come olly con la tecnica segnalata da Pnluck e indicata a http://www.woodmann.com/forum/showthread.php?t=6303&page=2&pp=15&highlight=fs%3A%5B18%5D in modo da generare uno STATUS_INVALID_HANDLE.
Daltronde se rileva il sice esegue 2 cicli di decrypt che iniziano a 4013AF utilizzando come chiave il registro AL che è inizializzato dal valore ritornato da rdtsc che è praticamente un valore pseudocasuale.

Trovo pure un probabile crc 4013DF mov edx, 780h 4013E4 mov eax, offset loc_40183E 4013E9 mov ebx, offset loc_40185A 4013EE int 1 4013F0 mov ds:dword_402020, ecx ...... 401544 mov edx, 780h 401549 mov eax, offset loc_40183E 40154E mov ebx, offset loc_40185A 401553 int 1 401555 cmp ecx, ds:dword_402020 40155B jz short loc_401564 Con questi parametri passati alla nuova int1 eseguo un controllo di integrità (crc) sulla zona 40183E - 40185A e lo memorizzo nella variabile 402020 che d'ora (piccola easter egg) in poi chiamerò crc. In seguito ricontrollo il crc per vedere se sono state fatte patch magari noppando un po' di cose direttamente dal sice. 401422 mov ebx, ds:dword_402152 401428 div ebx è molto interessante dato che 402152 vale 0. La divisione per 0 genera un' eccezione gestita da 401AA5, che verifica il tipo di eccezione occorsa e manda a: 401BA4 mov ds:key2_0, 1Ch 401443 xor eax, eax 401445 add eax, ds:dword_402156 vale 0 40144B mov ebx, [eax] 40144B genera una eccezione perchè leggo da una zona di memoria a cui non ho accesso e quindi finisco a 401A81 mov ds:key2_1, 7Fh valore atteso 401A88 mov ds:key2_2, 0C0h valore atteso 401A8F mov eax, offset loc_401A99 401A94 xor word ptr [eax], 7777h 401A99 mov eax, 340h 401A94 va a decryptare l' istruzione 401A99 che poi vado ad eseguire, ma genera un' altra eccezione del tipo accesso in memoria perchè la sezione di codice non è writeable (verificate con un peeditor) quindi rieseguo 401A81 e seguenti. Per le proprietà ormai arcinote dello xor la seconda volta che eseguo 401A94 risistemo 401A99 con mov eax, 340h quindi per semplificare le cose si può noppare 401A94 e aggiungere l' attributo writeable.
(Imho la sezione di codice deve avere pure l' attributo writeable perchè altrimenti ogni volta che ricorre all' smc sigenera un errore).

Ma ogni volta che finisco di gestire una eccezione non devo terminare con: mov eax, X eax = 1 ---> Passa al prossimo Handler della catena ret eax = 0 ---> Ricarica il contesto e continua l'esecuzione proprio come dice Que nel suo tute? Altrimenti si supera la massima dimensione dello stack che genera una eccezione il cui codice è C00000FDh, codice che infatti ho ottenuto numerose volte. 4014B9 pushf 4014BA or dword ptr [esp], 100h 4014C1 popf L' or con 100h va a settare il Trap Flag che equivale a generare una eccezione analoga a un single point, una eccezione gestita a 401B2D. A 401564 elaboro le 2 chiavi di decrypt.

Proseguiamo 4015C4 call 4017AD 4015C9 xor al, ds:key1_0 4015CF sub al, 13h 4015D1 mov ds:key1_2, al ... 4015F9 mov byte_401605, bl 4015FF mov byte_401606, bh Ho delle istruzione che influenzano il valore delle key tramite AL che è il valore ritornato da 4017AD: si tratta di un numero che dipende dai byte del sorgente e da eventuali patch. Poco dopo ho del codice automodificante che probabilmente genera un invalid_opcode gestito da: 401749 mov ds:key1_3, 0F8h valore atteso 401750 jmp decrypta_zona_interessante che completa le key di decrypt e poi va a decifrare il codice.


Note Finali

Dopo tutta questa fatica spero finalmente di entrare a far parte della "MailingList Riservata della UIC dove si parlerà di tecniche di reversing un po' più avanzate, si decideranno man mano i corsi da fare, le cose da approvare" hihihi

Grazie a Evilcry che mi ha dato molti consigli e ha risolto il crackme prima di me ma non ha mai pubblicato il tute.
Saluto anche The_enigma, Active85k, Pnluck, Quequero, Alga, AndreaGeddon, LonelyWolf, Xoa, Nt, PincoPall e tutti gli altri di cui ora non mi sovviene il nome.

Questo tutorial è dedicato a Cristian e Ilaria.