Zoom Icon

Corso UIC Avanzato 10 LittleLuk

From UIC

UIC Lezione 10 (UIC Strainer)

Contents


Infos
Author: LittleLuk
Email: littleluk@libero.it
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 Image: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
<nowiki>**</nowiki> 47 25 40 **
6A 00
E8 ** 0A 00 00
<nowiki>**</nowiki> 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.