Zoom Icon

Corso UIC Avanzato 04

From UIC Archive

KEY FILE

Contents


Corso UIC Avanzato 04
Author: TinMan - Quequero
Email: Email
Website: http://quequero.org
Date: 20/11/1999 (dd/mm/yyyy)
Level: Working brain required
Language: Italian Flag Italian.gif
Comments:



Link e Riferimenti

Questo è il Corsi UIC Avanzati n°04 disponibile alla pagina Corsi per Studenti


Introduzione

Eccoci qui al quarto appuntamento con la UIC.

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

Lo scopo di questo corso è 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', arriverà 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: [email protected] (Tin Man)


Essay

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:

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 

WORD 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

  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 :))))


Disclaimer

I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.

Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevole e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.