Yanisto's tiny crackme

Data

by "Cr[]w"

 

15/06/2005

UIC's Home Page

Published by Quequero

People once believed that when someone dies..

Grazie crow!

..a Crow carries their soul to the land of the dead

....

Home page : http://scrows.inscatolati.net/
E-mail: [email protected]
Cr[]w on #cryptorev, #crack-it, irc.azzurra.org

....

Difficoltà

( )NewBies (X)Intermedio ( )Avanzato ( )Master

 

 

Introduzione

Eccoci qua, in questo tutorial spieghero` come risolvere un piccolo
crackme di yanisto e parlero` delle tecniche basilari di
antidisassembling e antidebugging che sono presenti.

Parole dall'autore:

"It has a very small size (<400 bytes of bytecode) but implements a few tricks all the same :

- Elf headers corrupted,
- `Cyphered' binary,
- CRC checking,
- Anti ptrace,
- Anti gdb."

Tools usati

Lida
Unpackers + Bruteforcer

URL o FTP del programma

www.crackmes.de

Essay

Si comincia, per analizzare il crackme usero` solo il disassembler
`lida' (a cui non interessa che gli header siano corrotti). C'e` la
richiesta di un serial number. Apriamolo (e` allegato) con lida e
guardiamo all'entry point.

Seguiamo i jump e guardiamo qua:

 

00200046 call Function_002002F0
    

Ok guardiamo quella funzione:
 

002002F0 nop      ; no operation
002002F1 mov eax, 0x20004B     ; indirizzo successivo alla call
002002F6 mov esi, eax     ; mette l'indirizzo in esi..
002002F8 mov edi, esi     ; ..e in edi
002002FA mov ecx, 0x2A5     ; contatore per il ciclo
002002FF shr ecx, 0x2     ; 0x2a5/0x4 = 0xa9
00200302 lodsd     ; mette in eax una dword letta dall'indirizzo in esi e incrementea esi di 4


Questa funzione decritta l'area che parte da 0x20004B per 0x9a dword e
la va ad eseguire (visto che e` immediatamente succesiva alla call,
quindi quando la funzione ritorna l'eip puntera` ad essa).

Visto che non stiamo debuggando il programma per poter proseguire con
l'analisi dobbiamo decrittare quell'area, quindi hoscritto un unpacker
(e` nell'allegato, si chiama unpacker.c) che in piu` patcha con dei
`nop' la call alla funzione di decrittazione (all'indirizzo 00200046, a
noi ormai e` inutile). Compilare, eseguite e passategli il nome
dell'eseguibile. Ora aprite il file `.upk' con il disassembler e si
continua..

 

00200046 nop
00200047 nop
00200048 nop
00200049 nop
0020004A nop     ; la nostra patch
0020004B mov eax, 0x20019E     ; punta ad un'altra area crittata
00200050 mov ebx, 0xF4
00200055 shr ebx, 0x2     ; ebx = 0xf4/0x4 = 0x3d
00200058 mov edx, [00200292]     ; punta a 0xBEEFC0DA


call..

 

002002BC mov esi, eax     ; carica l'indirizzo dell'area crittata
002002BE mov edi, esi     ; come sopra
002002C0 mov ecx, ebx     ; numero di volte che viene ripetuto il loop
002002C2 lodsd     ; carica in eax una dword
002002C3 xor eax, edx     ; eax = eax ^ 0xBEEFC0DA
002002C5 stosd     ; mette la dword in memoria
002002C6 loop 002002C2     ; decrementa ecx, se non e` 0 torna a `lodsd'
002002C8 ret      ; return


Questa funzione fa lo stesso lavoro della precedente, decritta un area
che andra` ad eseguire, quindi ho scritto un altro unpacker
(unpacker2.c) che deve essere eseguito sull'eseguibile `.upk' (anche
questo patcha la chiamata alla funzione di decrittazione, all'indirizzo
0020005E).


 

00200063 mov ecx, 0x20019E     ; indirizzo della stringa da stampare
00200068 mov edx, 0xF4     ; numero di byte da scrivere


call e jump..

 

002002A0 xor eax, eax
002002A2 mov ebx, eax
002002A4 mov al, 0x4     ; syscall write
002002A6 int 0x80     ; scrive
002002A8 ret      ; return


Questo pezzo scrive solo qualce info sul crackme. Ora guardiamo piu`
attentamente quel `call e jump..':


 

0020006D call Function_0020029A

0020029A      E9 01 00 00 00      jmp Label_002002A0 ; (near + 0x1)
0020029F      (DB) B0
002002A0      31 C0      xor eax, eax
002002A2      89 C3      mov ebx, eax
002002A4      B0 04      mov al, 0x4
002002A6      CD 80      int 0x80
...


Questo era l'output di lida, ora vediamo quello di `objdump':

 

0:      e9 01 00 00 00      jmp 0x6 ; (near + 1)
5:      b0 31      mov $0x31,%al
7:      c0 89 c3 b0 04 cd 80      rorb $0x80,0xcd04b0c3(%ecx)
...


Cos'e` successo? (Tralasciando la differenza di indirizzi) Come si puo`
vedere gli opcode sono identici, allora perche` le istruzioni sono
interpretate diversamente? Perche` c'e` un piccolo trick che confonde i
disassembler meno furbi (che non tengon traccia dell'esecuzione del
programma), la presenza dell'opcode 0xB0 non associato ad altri.

Mentre lida interpreta l'istruzione all'indirizzo 0020029A, vede che e`
un jump, e continua a disassemblare da dove il jump porterebbe, objdump
dopo aver decodificato quel jump guarda il byte successivo (0xb0), che
sta ad indicare un `muovi un byte immediato in al' e che ha bisogno di
un altro byte per essere completato, quindi prende il byte successivo
(0x31) e codifica l'istruzione come `mov $0x31,%al'. In questo modo
l'opcode 0x31 non e` piu` interpretato come xor ma come un operando del
mov, quindi anche le istruzioni successive non saranno interpretate
correttamente. Per ovviare a questo problema conviene usare un
disassembler come ida, che appunto tiene traccia di quali istruzioni
potrebbe andare ad eseguire il programma una volta avviato. Da notare
che anche altri opcode possono essere usati al posto di 0x31 per
disturbare l'interpretazione da parte del disassembler. Questo trick e`
piu` volte usato nel programma ma non lo evidenziero` nuovamente.

Dopo questa piccola digressione continuiamo.


 

00200072 mov eax, 0x1A     ; syscall ptrace
00200077 xor ecx, ecx     ; controlla se stiamo debuggando
00200079 mov esi, ecx     ; ma noi abbiamo solo disassemblato
0020007B mov edx, 0x1     ; quindi non ci interessa molto :)
00200080 int 0x80     ; chiama ptrace
00200082 sub ebx, eax     ; `salva' il risultato
00200084 test eax, eax     ; se non e` presente un debugger
00200086 jz Label_00200099     ; leggi il serial number inserito


ptrace e` una system call, in questo punto il programma controlla se e`
debuggato grazie ad essa. `gdb' e` un debugger basato suptrace, quindi
se lo si usasse per debuggare il crackme la funzione restituirebbe
errore e il programma uscrirebbe (non ho pastato questo pezzo di
codice). Debuggandolo si puo` comunque bypassare questo trick mettendo
un breakpoint sul jump e cambiare il valore dello zero-flag, oppure sul
test e cambiare il valore di eax e continuare, o anche sostituire il
`jz' in un `jmp', quello che preferite :) Il perche` dell'istruzione
all'indirizzo 00200082 lo spiegheremo dopo, ora continuamo.

un altro jump..


 

0020009C push ebx      ; salva il valore di ebxr
0020009D mov ecx, 0x200296     ; dove l'input verra` salvator
002000A2 mov edx, 0x4     ; numero di byte da leggere
002000A7 call Function_002002AA


jump..

 

002002B0 xor eax, eax
002002B2 mov ebx, eax
002002B4 inc bl     ; file descriptor
002002B6 mov al, 0x3     ; syscall read
002002B8 int 0x80     ; leggiamo
002002BA ret      ; return


Il codice precedente legge 4 byte della nostra password e li mette in
0x200296.


 

002000AC call Function_002002C9


jump..

 

002002D1 xor eax, eax
002002D3 mov ebx, eax
002002D5 mov ecx, 0x2DF
002002DA shr ecx, 0x2 ; ecx = 0x2DF/0x4 = 0xb7
002002DD mov esi, 0x200008     ; inizio dell'area del crc
002002E2 lodsd     ; mette in eax una dword
002002E3 add ebx, eax     ; ebx = ebx + eax
002002E5 loop 002002E2     ; se ecx != 0 torna a `lodsd'
002002E7 xor ebx, 0x5508046B     ; xor tra la somma e un valore fisso
002002ED ret     ; return



Bene, siamo al cuore del programma :)
Viene calcolata la somma di tutte le dword a partire dall'indirizzo
0x200008 e viene messa in ebx. Visto che il blocco che compone la somma
contiene anche la nostra password, sara` questa che determinera` il
risultato. questa somma e` poi xorata con un valore fisso.


 

002000B1 xor ebx, [PTR_00200296]     ; dove e` stata salvata la nostra password
002000B7 jz Label_002000CC     ; se lo xor da come risultato 0 seguiamo il jump
002000CC pop ebx     ; risistema il volte di ebx
002000CD test ebx, ebx     ; se e` presente un debugger
002000CF jnz Label_002000B9     ; vai al wrong serial..


Spieghiamo queste ultime istrunzioni. La prima rimette in ebx il valore
che prima era stato pushato (all'indirizzo 0020009C), ma perche` fa il
test? Quel valore e` 0 - ritorno_di_ptrace come sipuo` vedere:


 

002002A0 xor eax, eax
002002A2 mov ebx, eax
..call ptrace..
00200082 sub ebx, eax
..poi push ebx..


Quindi se il programma non e` tracciato da un debugger ebx vale zero,
altrimenti cosi non sarebbe e il programma uscrirebbe. E` solo un altro
test basato sulla precedente chiamata a ptrace, nulla di piu`.


 

002000D1 mov ecx, 0x200136     ; altrimenti good serial :)
002000D6 mov edx, 0x68     ; numero di byte da scrivere
002000DB call Function_0020029A


jump..

 

002002A0 xor eax, eax
002002A2 mov ebx, eax
002002A4 mov al, 0x4     ; sys write
002002A6 int 0x80     ; chiama la sys write
002002A8 ret     ; return

00200094 jmp Label_0020030C     ; exit


jump..

 

00200312 xor eax, eax     ; eax = 0
00200314 mov ebx, eax     ; return status
00200316 inc al     ; syscall exit
00200318 int 0x80     ; exit!!


Abbiam finito.. :) come possiamo vedere la somma, dopo esser stata
xorata con un valore fisso, viene ulteriormente xorata con il nostro
serial. Se il risultato di queste operazioni non e` 0 il serial non
verra` accettato. Visto che il serial e` lungo solo 4 byte ho scritto un
bruteforcer che trova tutte le password stampabili corrette
(bruteforcer.c, ci mette un paio di minuti a finire).

Nota: l'area che compone la somma finale include anche le nostre patch
(i nop sulle call per le funzioni che decrittano l'eseguibile), quindi
devono essere risistemate. Inoltre il serial generato dal bruteforcer
deve essere usato con il programma packato, perche` gli altri sono
patchati.

E` tutto gente.

                                                                                                                Cr[]w


Note finali

Il crackme non era difficile, infatti non abbiamo nemmeno dovuto metter
mano al debugger.. :) Comunque sia ho approfittato dell'occasione per
parlare riguardo ai trick usati per una protezione del software
_veramente basilare_, sperando possa esser stato utile a qualcuno.

Ps: Non so voi ma per me la cosa piu` faticosa del tutorial e` stata infilare
il testo nell'uicform.. :) (non guardate i sorgenti di questa pagina! brrr..)

Disclaimer

Vorrei ricordare che il software va comprato e  non rubato, dovete registrare il vostro prodotto dopo il periodo di valutazione. Non mi ritengo responsabile per eventuali danni causati al vostro computer determinati dall'uso improprio di questo tutorial. Questo documento è stato scritto per invogliare il consumatore a registrare legalmente i propri programmi, e non a fargli fare uso dei tantissimi file crack presenti in rete, infatti tale documento aiuta a comprendere lo sforzo che ogni sviluppatore ha dovuto portare avanti per fornire ai rispettivi consumatori i migliori prodotti possibili.

Reversiamo al solo scopo informativo e per migliorare la nostra conoscenza del linguaggio Assembly.