|
UIC Strainer reversing |
||||||
|
Data |
by "LittleLuk" |
|||||
|
18/10/2005 |
Published by Quequero |
|||||
|
La giustizia è come una ragnatela: gli insetti piccoli rimangono intrappolati quelli grandi la rompono e volano via. Letta su un calendario |
Grazie Lit! Finalmente qualcuno dopo 5 anni ha scritto un tutorial! Ed e' veramente ben fatto, ottimo lavoro! |
Ed ecco mi apparve un cavallo bianco e colui che lo cavalcava aveva un arco, gli fu data una corona e poi egli uscì vittorioso per vincere ancora. Apocalisse |
||||
|
.... |
|
.... |
||||
|
Difficoltà |
|
|||||
|
Introduzione |
|
Tools usati |
|
URL o FTP del programma |
Il crackme è prelevabile da qui: http://www.quequero.org/prj/strainer/strainer.html
|
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, daltronte 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)
|
Quest' ultima call esegue:
|
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 hxxp://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ì:
|
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 hxxp://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'è:
|
Che è la procedura standar per installare un gestore di eccezioni: hxxp://www.quequero.org/uic/SEH.html. Ogni volta che lo strainer 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, opocde non valido, breakpoint, single step e errore di accesso in memoria.
Dopo eseguo:
|
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:
|
Si sa che (vedi hxxp://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:
|
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 hxxp://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 complicarela vita
altrimenti reboot
Poi segue il codice che crea la finestra del programma che è inutile per risolvere il crackme. Andiamo avanti.
|
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:
|
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.
|
Così rimuove l' handler per la gestione delle eccezioni
|
Ho pure sistemato la idt
|
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:
|
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:
|
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:
|
è 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:
|
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:
|
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 hxxp://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
|
Con questi parametri passati alla nuova int1 eseguo un controllo di integrità (crc) sulla zona 40183E - 40185A e lo memorizzo nella variabile 402020 che dora (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.
|
è 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:
|
40144B genera una eccezione perchè leggo da una zona di memoria a cui non ho accesso e quindi finisco a
|
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:
|
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.
|
Proseguiamo
|
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:
|
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.
|
Disclaimer |
Avete capito quanto deve faticare un programmatore per proteggere il proprio software per portare a casa la pagnotta quotidiana?
Questo è solo un crackme ma vorrei ricordare che, in generale, il software va comprato
e non rubato.
Non mi ritengo responsabile per eventuali danni causati dall'uso di questo tutorial.
Reverso al solo scopo informativo e per
migliorare la conoscenza del linguaggio Assembly.