- UNIVERSITA' ITALIANA CRACKING -
4° Corso per Studenti

KEY FILE


Scarica qui il programma per questo corso (164 kb)


INTRODUZIONE:  

Eccoci qui' al quarto appuntamento con la UIC.

Il programma in oggetto non e' molto complicato da crackare, ma presenta una novita' rispetto ai precedenti, e' stato scritto con un linguaggio ad alto livello, e non in ASM come eravate abituati fino ad adesso, in pratica si avvicina di piu' al mondo reale della programmazione, con un sacco di routine aggiunte dal compilatore ed inutili, il programma e' funzionante (solamente crea un piccolo file su disco e lo legge) per controllare se e' stato registrato correttamente premete su Register e successivamente su About, quindi ritornate al programma principale e chiudetelo.

Lo scopo di questo corso e' di creare un key generator, attenzione che i nomi non possono superare i 30 caratteri, quindi tenetene conto. Avrei voluto aggiungerci un piccolo PE cripter, ma per cause estranee non ho avuto il tempo di lavorarci su', arrivera' in un secondo tempo. Consideratelo un relax in attesa del corso 5 di Quequero, che mi sa' non sara passeggiata come questa.

Buon divertimento, per eventuali informazioni o critiche:

tinman@thepentagon.com

Tin Man


Salve a tutti e ben tornati o ben arrivati alla UIC, dunque Tin_Man si è fatto veramente il mazzo per creare questo programmetto dati alcuni gravi problemi che gli sono piombati addosso, quindi mi scuso con voi per questo scarno corso ma data la semplicità del programma potete davvero considerarlo un break prima del 5° corso che credo sarà il riassunto di varie tecniche oltre ad alcune piccole implementazioni :) insomma stavolta cercherò di rendervi la vita difficile e sarò abbastanza cattivello, ci avvicineremo in questo modo ad un target ben più evoluto.

Dunque, torniamo a noi, il programma è scritto come detto prima in alto livello, ha un simpatico trucchetto e dovete creare il vostro bravo keyfile, ma prima di cominciare mi sembra doveroso spiegare come funziona questo tipo di protezione.

Tutti i programmi protetti da key-file devono fare una cosa all'avvio cioè, aprire il keyfile e conseguentemente andare a leggerlo, il key-file non deve necessariamente essere letto all'avvio ma può anche essere decifrato alla pressione di un particolare pulsante come ad esempio il tasto "save", in questo modo se il programma trova il key-file e lo vede come valido allora ci da accesso a questa funziona altrimenti...nada. Per leggere un file possiamo utilizzare svariate funzioni, sotto dos potremmo utilizzare l'int 21h ma visto che siamo sotto W32 possiamo avvalerci di due tecniche principali cioè:

1) L'utilizzo della funzione _lopen e _lread

2) L'utilizzo di una struttura che usi OpenFile, SetFilePointer e poi ReadFile

 

Ora ve le spiego tutte e due altrimenti non sapreste cosa fare.

Prendiamo il come esempio il primo punto, per leggere un file abbiamo bisogno come prima cosa di aprirlo e per questo possiamo usare _lread, ecco la sintassi:

 

HFILE _lopen(

LPCSTR lpPathName,

// pointer to name of file to open

int iReadWrite

// file access mode

);

Come vedete non bisogna far altro che specificare un puntatore al file da aprire e poi scegliere un tipo di modalità di accesso al file stesso, le più usate sono generalmente:  

OF_READ

Opens the file for reading only.

OF_READWRITE

Opens the file for reading and writing.

OF_WRITE

Opens the file for writing only.

Il modo d'uso è molto semplice: 

Extrn _lopen: Proc   

.data         

OF_READ = 0000H
OF_WRITE = 0001H
OF_READWRITE = 0002H         

 

Nome   db 'File.exe',0           

.code         

Start:         

 

push offset Name 

push OF_READ 

Call _lopen
    

Una volta aperto il file con questa funzione lo dobbiamo andare a leggere con _lread che ha questa semplice sintassi:  

 

UINT _lread(

HFILE hFile,

// handle to file

LPVOID lpBuffer,

// pointer to buffer for read data

UINT uBytes

// length, in bytes, of data buffer

);

 

dobbiamo conoscere per prima cosa l'handle del file che però viene riportato in eax dopo la chiamata a _lopen, ci serve un puntatore ad un buffer che conterrà i dati letti dal file e poi dobbiamo sapere quanti byte vogliamo leggere da questo file, in sostanza il nostro mini programma diventerebbe così:     

Extrn _lopen:Proc   

.data         

OF_READ = 0000H
OF_WRITE = 0001H
OF_READWRITE = 0002H         

Nome   db 'File.exe',0         

Handle dd ?         

Buffer dd ?           

.code         

Start:         

push offset Name 

push OF_READ 

Call _lopen 

mov handle, eax         

push handle 

push offset Buffer         

push 20 

call _lread

Come vedete prendiamo il file di nome File.exe, lo apriamo, pushamo l'handle nello stack, pushiamo l'offset di un buffer dove finiranno i dati letti e decidiamo di leggere 20 bytes, quindi basterebbe guardare queste poche istruzioni per vedere cosa viene letto dal file, basta ricordare solo che le istruzioni non vengono pushate nello stesso ordine in cui le mettiamo noi.

 

Bene, adesso esaminiamo il secondo modo di aprire e leggere un file, se non vogliamo usare i vecchi _lopen e _lread possiamo utilizzare la struttura OpenFile, SetFilePointer, ReadFile, dunque iniziamo vedendo la sintassi del comando OpenFile:  

HFILE OpenFile(

LPCSTR lpFileName,

// pointer to filename

LPOFSTRUCT lpReOpenBuff,

// pointer to buffer for file information

UINT uStyle

// action and attributes

); 

 

Bene, bene, per prima cosa abbiamo bisogno di un puntatore al nome del file, poi di un puntatore ad un buffer che conterrà le informazioni e si deve infine specificare la modalità di lettura, come vedete tranne che per l'ultimo parametro la funzione presenta le stesse caratteristiche di _lopen, quindi iniziamo:

 

Extrn OpenFile:Proc

 

.data

OF_READ = 0000H
OF_WRITE = 0001H
OF_READWRITE = 0002H

Nome   db 'File.exe',0

Buffer dd ?

Handle dd ?

.code

Start:

push offset Nome
push offset Buffer
push OF_READWRITE
call OpenFile
mov Handle, eax
cmp eax, -1 jnz Okkei

Come vedete pushiamo il file nello stack, pushiamo poi l'offset dek buffer che conterrà i nostri cari dati e quindi la modalità di apertura cioè, OF_READWRITE, in pratica apriamo il file in modalità read & write, slaviamo l'handle e poi facciamo un cmp eax, -1, perchè? È presto detto, perchè se eax riporta come valore dopo la chiamata -1 (in hex FFFFFFFF) vuol dire che c'è stato un errore, se non ci sono allora saltiamo ad una nuova routine. Adesso sappiamo come si apre, ma dobbiamo settare un puntatore a questo file, a questo scopo sfrutteremo l'API SetFilePointer che presenta questa sintassi:

 

DWORD SetFilePointer(

HANDLE hFile,

// handle of file

LONG lDistanceToMove,

// number of bytes to move file pointer

PLONG lpDistanceToMoveHigh,

// address of high-order word of distance to move

DWORD dwMoveMethod

// how to move

);

lo so che può sembrarvi un tantino ostica ma in realtà è molto semplice, innanzitutto abbiamo la necessità di settare un puntatore per far sapere alla successiva API ReadFile cosa e dove andare a leggere, questa struttura è molto comoda perchè se in un programma abbiamo bisogno di leggere più volte un file in diverse posizioni, non dobbiamo far altro che specificare l'offset che ci interessa, così velocizziamo enormemente il nostro programma perchè basterebbe mettere in un buffer di volta in volta l'offset che ci serve e richiamare sempre la stessa routine di lettura, cosa che invece non possiamo fare con _lopen e _lread pechè non ci danno la possibilità di specificare offsets. Allora vediamo come funziona questa API, come prima cosa abbiamo bisogno dell'handle del file ma come già detto questo viene riportato in eax dopo la chiamata a OpenFile, poi abbiamo bisogno di inserire il numero di byte che vogliamo muovere nel puntatore, ma questo valore è spesso settato a NULL. Adesso si presenta uno strano parametro, a cosa serve? Dunque se il parametro è NULL allora la funzione SetFilePointer opererà solo su file grandi al massimo 2^32-2 bytes, se invece specifichiamo qualche parametro allora il SetFilePointer può operare al massimo su file di ben 2^64-2 bytes, e poi dobbiamo specificare una Dword nella quale andremo a salvare l'offset, l'offset serve ad indicare al puntatore a quanti byte dall'inizio dobbiamo iniziare la lettura del file, bene bene, il nostro programma diventerà allora così:

Extrn OpenFile:Proc

Extrn ExitProcess:Proc

Extrn SetFilePointer:Proc  

OF_READ = 0000H

OF_WRITE = 0001H

OF_READWRITE = 0002H

Nome   db 'File.exe',0

Buffer dd ?

Handle dd ?

NULL equ 0

Read_Point dd 00001234H

.code

Start:

push offset Nome
push offset Buffer
push OF_READWRITE
call OpenFile
mov Handle, eax
cmp eax, -1 jnz Okkei 

call ExitProcess

okkei:

push Handle

push NULL

push NULL

push Dword Ptr[Read_Point]

call SetFilePointer

 

Come vedete in questo caso apriamo il file e saltiamo alla label "okkei" se non ci sono errori, quindi pushiamo l'handle del file, settiamo il secondo e terzo parametro a NULL e quindi pushiamo l'offset al quale vogliamo leggere (che è 1234h) e quindi chiamiamo la nostra agognata API SetFilePointer. Adesso possiamo andare a leggere il filuzzo, la sintassi di ReadFile è:

 

BOOL ReadFile(

HANDLE hFile,

// handle of file to read

LPVOID lpBuffer,

// address of buffer that receives data

DWORD nNumberOfBytesToRead,

// number of bytes to read

LPDWORD lpNumberOfBytesRead,

// address of number of bytes read

LPOVERLAPPED lpOverlapped

// address of structure for data

);

 

Di cosa abbiamo bisogno stavolta? Presto detto, ci serve come sempre l'Handle del file che a questo punto è già stato salvato, poi dobbiamo avere l'indirizzo di un buffer che rivecerà i dati letti , il numero di bytes che vogliamo leggere, un altro buffer nel quale finirà il numero di bytes letti ed infine l'indirizzo di una struttura per i dati, quest'ultimo parametro sarà settato a NULL perchè per il momento non vogliamo avvalerci di strutture, ecco quindi        come si trasforma il nostro programma ormai finito:

         

Extrn OpenFile:Proc

Extrn ExitProcess:Proc

Extrn SetFilePointer:Proc

Extrn ReadFile:Proc

 

.data

OF_READ = 0000H
OF_WRITE = 0001H
OF_READWRITE = 0002H

Nome   db 'File.exe',0

Buffer dd ?

Handle dd ?

NULL equ 0

Read_Point dd 00001234H

Read_Buffer dd ?

Bytes_Letti   dd ?

Numero_bytes dd 00000012H

.code

Start:

push offset Nome
push offset Buffer
push OF_READWRITE
call OpenFile
mov Handle, eax
cmp eax, -1 jnz Okkei 

call ExitProcess

okkei:

push Handle

push NULL

push NULL

push Dword Ptr[Read_Point]

call SetFilePointer

push Handle
push offset Read_Buffer
push dword ptr [Numero_bytes]
push offset Bytes_Letti
push NULL

call ReadFile

 

Se vi capitasse una routine del genere non dovreste far altro che sgamare l'offset ed andare a vedere cosa trovate, oppure potreste semplicemente spulciare nel Read_Buffer, dopo questa routine i dati inizieranno a venire manipolati. Ma non è finito, infatti spesso i linguaggi ad alto livello utilizzano un altro modo per leggere i file cioè tramite l'API CreateFile, che ha una sintassi piuttosto complicata:  

 

HANDLE CreateFile(

LPCTSTR lpFileName,

// pointer to name of the file

DWORD dwDesiredAccess,

// access (read-write) mode

DWORD dwShareMode,

// share mode

LPSECURITY_ATTRIBUTES lpSecurityAttributes,

// pointer to security attributes

DWORD dwCreationDistribution,

// how to create

DWORD dwFlagsAndAttributes,

// file attributes

HANDLE hTemplateFile

// handle to file with attributes to copy

);

 

Allora il programma stavolta non lo faccio perchè tanto è come il ReadFile, la stessa cosa in pratica, abbiamo bisogno di un puntatore al nome del file, ci serve sapere il tipo di accesso che vogliamo (rea, write o tutti e due) il tipo di condivisione che vogliamo avere, in pratica qua specifichiamo se il file può venir letto anche da programmi esterni a quello che l'ha aperto (FILE_SHARE_READ), oppure se può essere scritto anche da programmi esterni (FILE_SHARE_WRITE). Il 4° parametro serve per determinare se l'handle può essere utilizzato anche da processi figli o meno, poi dobbiamo sapere la modalità con la quale creare il filuzzo, qui si possono usare cinque parametri, uno per la creazione di un nuovo file (CREATE_NEW) uno per sovrascrivere un file (CREATE_ALWAYS) ed un altro per aprire solamente il file specificato (OPEN_EXISTING), c'è poi OPEN_ALWAYS che serve ad aprire il file specificato e se non lo trova lo crea e poi TRUNCATE_EXISTING che una volta aperto il file lo tronca fino a farlo diventare di 0 bytes ecc....Dobbiamo poi specificare gli attributi che dovrebbe avere il file, di questi i principali sono: 

 

FILE_ATTRIBUTE_HIDDEN 

FILE_ATTRIBUTE_NORMAL 

FILE_ATTRIBUTE_READONLY

FILE_ATTRIBUTE_SYSTEM

FILE_ATTRIBUTE_TEMPORARY

 

Se non volete assegnare nessun attributo particolare utilizzare FILE_ATTRIBUTE_NORMAL, per spiegare il penultimo parametro ci vorrebbe un libro, vi dico che solo che serve per specificare il tipo di scrittura da attuare sul file, noi non useremo nulla perchè non dobbiamo scrivere nulla ma se dovessimo scrivere qualcosa potremmo usare: FILE_FLAG_WRITE_THROUGH, che in pratica scrive i dati attraverso una cache e da questa poi sul disco duro. L'ultimo param invece specifica un tipo di lettura generica su un file template quindi non ci serve. Un esempio forse è meglio che ve lo faccio, ecco come si potrebbe aprire un file:

 

Extrn CreateFileA:Proc

.data

OF_READ = 0000H
Nome   db 'File.exe',0

NULL equ 0

.code

Start: 

push offset Nome

push OF_READ

push FILE_SHARE_READ

push NULL

push OPEN_EXISTING

push NULL

push NULL

call CreateFileA

 

Allora, in questo caso vediamo che apriamo un file di nome: File.exe, in modalità di sola lettura e condividiamo la lettura anche da programmi esterni, lo apriamo con OPEN_EXISTING, in questo modo se il programma non trova il file invece di crearlo ci segnala l'errore. Okkei adesso siete diventati degli apritori di file :) quindi divertitevi con questo programma che è abbastanza semplice ma ricordate sempre che il key-file viene aperto, letto e poi i dati vengono confrontati con dei dati che il programma ha in se, oppure con dei dati creti sul momento, come? Bhè, ad esempio il programmatore potrebbe creare un key-file personalizzato in base al vostro nome, in pratica metto il nick da qualche parte nel key-file, in plain-text o in forma crittata, lo legge, lo decritta se necessario, lo manipola e dalla manipolazione va a creare dei dati che verrano poi confrontati con quelli presi dal file, ricordate che qualunque metodo di crittazione venisse usato (in genere si tratta si semplici maschere Xor) il programma dovrebbe comunque confrontare da qualche parte i suoi bytes con i vostri, quindi occhio a tutti i cmp che trovate e spulciate per benino tutti i buffer ed i valori pushati dalle varie API perchè saranno loro a condurvi sulla retta via. Raga scusatemi ancora per questo scarno tutorial ma l'ho scritto in sole tre ore quindi abbiate pazienza che è tardissimo, devo andare a letto e devo uppare domani mattina il sitozzo scusatemi di nuovo anche se la lezione sembra più di programmazione che di reversing ma proprio a questo proposito ho messo tutti gli esempi in asm perchè è così che li troverete nei programmi, quindi raga buon divertimento ciauzzzzzzzzzz


OBIETTIVI:  
  1. Spiegare dettagliatamente il funzionamento della routine che genera e checka il key-file
  2. Creare un generatore di Key-File adattabile a tutti i nomi
  3. Spiegate come funziona la routine che vi fa un certo dispetto :) e spiegate come fa ad ottenere quell'effetto (capirete cosa intendo crackando il programma dhe hi hi hi :))))

Quequero


[Back]