Manual Unpacking

for Newbies

Data

by "Jumperplus"

 

Febbraio 2003

UIC's Home Page

Published by Quequero


....

Dire che sei stato esaustivo e' dir poco, grazie mille, il tutorial e' scorrevolissimo oltre che visivamente pulito, bravo jumper

....

....

Home page se presente: http://jumperplus.cjb.net/
E-mail: jumperplus@yahoo.it

....

Difficoltà

(x)NewBies ( )Intermedio ( )Avanzato ( )Master

 

Questo tutorial è rivolto principalmente ai newbies ai quali cercherò di spiegare nel modo più semplice possibile la tecnica del manual unpacking, facendo dei cenni sul formato PE, spiegando alcuni campi modificabili tramite il PEditor e concludendo il tutorial con un esempio pratico di decrypting.


Manual Unpacking for Newbies
Written by Jumperplus

Introduzione

Ultimamente i programmatori utilizzano sempre più frequentemente i vari crypter/packer per proteggere i loro software in modo da impedire che questi possano essere modificati, infatti se aprite con un disassemblatore un eseguibile cryptato noterete che è praticamente illeggibile, quindi per poterlo modificare dobbiamo prima decryptarlo.

INDICE TUTORIAL:

Tools usati

Softice 4.05
Icedump
PEditor 1.7
HexEdit (o qualsiasi altro editor esadecimale)
W32Dasm 8.93 (non indispensabile)
Import REConstructor 1.4.2+

URL o FTP del programma

Lo troverete allegato al tutorial

Notizie sul programma

Il programma che utilizzo come esempio di decrypting non è altro che il notepad del mio Windows Me cryptato con Petite 2.2

Essay

 
STRUTTURA DELL’ ESEGUIBILE CRYPTATO:
 

PE Header

Sezione 1

Sezione 2

Sezione n

Loader

 
Se provate ad aprire con il PEditor un crypter noterete che tra le varie sezioni standard comunemente inserite dal compilatore (.text, .data, .rsrc etc...) alcune avranno dei nomi strani (generalmente si trovano dopo l'ultima sezione), queste sono state aggiunte dal crypter e si chiamano "loader" e servono per decryptare l'eseguibile in memoria in modo tale che possa essere eseguito in modo corretto. Infatti il programma per poter funzionare parte l'esecuzione dalla sezione loader per auto decryptarsi a runtime, una volta che il file è stato decryptato, esce dalla sezione loader e salta all'entry point del programma originale (da ora in poi: original entry point o "OEP"), andando esattamente sulla prima sezione, ora pronto all'esecuzione. A questo punto avremo il programma decryptato su ram e non dobbiamo far altro che farne una copia su hard disk, questo procedimento si chiama "Dumping" e può essere fatto ad esempio da softice utilizzando icedump oppure con il PEditor.
 
Esempio di crypter (petite 1.2):

Section

Virtual Size

Virtual Offset

Raw Size

Raw Offset

Characteristics

.text

00008000

00001000

00005000

00001000

E0000020

.rdata

00001000

00009000

00001000

00006000

40000040

.data

00005000

0000A000

00000000

00000000

C0000040

.rsrc

00009000

0000F000

00009000

00007000

40000040

.madmat

0000D258

00018000

0000160C

00010000

E2000060

 
Nell'esempio che vedete sopra la sezione .madmat è il loader che servirà a decryptare l'eseguibile in memoria.
Dalle varie sezioni e dal nome del loader a volte si può capire quale crypter è stato adoperato, la sezione ".madmat" viene aggiunta da petite 1.2, questo oltre a essere un crypter è anche un packer ecco la differenza:
 
Crypter: crypta le sezioni di un eseguibile ma non ne altera la grandezza
Packer: oltre a cryptare le sezioni ne riduce anche la grandezza
 
 
CENNI SUL FORMATO PE E SU ALCUNI CAMPI MODIFICABILI DAL PEDITOR:
 
Tra i tools necessari per modificare gli eseguibili troviamo il PEditor e poichè dopo il dumping l'eseguibile generalmente non funziona, questo ci tornerà utile per mettere a posto un pò di cose. Per far questo però è fondamentale conoscere la struttura del PE (Portable Executable, formato standard degli eseguibile della piattaforma win32), ed i campi che il Peditor ci consente di modificare, di seguito spiegherò solo i fondamentali prendendo come esempio il W32Dasm 8.93 (vorrei sottolineare che questo disassemblatore non è cryptato, non ho scelto un programma cryptato in quanto al momento mi interessa solo spiegare come leggere i vari campi del PEditor):
 
 
E' importante non far confusione fra offset RVA e VA, in softice infatti tutti gli offset sono scritti in VA mentre nel PEditor in RVA:
 
LE SEZIONI:
 
Sezioni del W32Dasm 8.93
Le sezioni degli eseguibili devono rispettare dei valori di allineamento, sia in memoria che su file, infatti queste iniziano e finiscono sempre ad un indirizzo che è un multiplo del valore di allineamento, questo valore è dato da "Section Alignment" per gli indirizzi in memoria e da "File Alignment" per gli indirizzi su file. Un altro tipo di allineamento, diverso da quello tra le sezioni, è l'allineamento nel file, questo si avrà quando VirtualOffset = RawOffset. Una cosa che spesso faremo nel tutorial sarà quella di cercare un determinato offset nel file partendo da un RVA o VA, se il file è allineato avremo:
VA = Offset Fisico  e  RVA - Image base = Offset Fisico
diversamente dobbiamo convertirli con l' FLC (File Location Calculator) del PEditor.
 
 
LA IMPORT TABLE:
 
Una delle operazione più fastidiose che dobbiamo fare per far funzionare l'eseguibile dumpato è senza dubbio ricostruire la Import Table (IT), di seguito spiegherò i campi relativi alla IT:  
 
IMPORT TABLE del W32Dasm 8.93:
 
Esistono due differenti tipi di struttura della IT, questa varia in funzione del compilatore adoperato:
  1. OriginalFirstThunk     TimeDateStamp     ForwarderChain     Name     FirstThunk
  2. TimeDateStamp     ForwarderChain     Name     FirstThunk
Come potete vedere nella seconda struttura non abbiamo gli OriginalFirstThunk, potrete riconoscere questa struttura dalla prima dword, in questo caso infatti questa sarà settata a 0.
 
        OriginalFirstThunk (OFFSET) ----> OFFSET1 ----> NOME FUNZIONE1
                                                       OFFSET2 ----> NOME FUNZIONE2
                                                       OFFSET3 ----> NOME FUNZIONE3
 
        Se apriamo il W32Dasm 8.93 con un editor esadecimale e guardiamo all'offset 000D408C non troveremo      
        la struttura degli indirizzi che puntano ai nomi delle funzioni importate in quanto il file non è allineato        
        (vi ricordo che per sapere se l'eseguibile è allineamento basta guardare tra le sezioni, solo se
        VirtualOffset = RawOffset si ha l'allineamento), quindi dobbiamo convertire con l'FLC l'offset 000D408C da
        RVA a Offset Fisico: (RVA) 000D408C = (Offset Fisico) 000D1C8C, adesso cercando a questo indirizzo
        troveremo:
                                         
                                                                   48 20 0D 00                                                                 
        5A 20 0D 00   68 20 0D 00   7C 20 0D 00   88 20 0D 00
        9E 20 0D 00   AE 20 0D 00   ECC...........      
                                                         
          tutti questi byte non sono altro che indirizzi che puntano ai nomi delle funzioni importate, quindi solita       
        conversione: (RVA) 000D2048 = (Offset Fisico) 000D0448, a questo indirizzo troveremo:
 
        000D0448  00 00 47 65 74 50 72 6F 63 41 64 64 72 65 73 73 00   ..GetProcAddress.
 
        dove i primi due byte sono la word Hint (0000), dopo avremo il nome della funzione ed infine avremo lo 0 
        per terminare. Come potete notare gli offset trovati sono identici a quelli presenti sul PEditor,
        (li ho colorati in blu) ecco spigata anche l'area  "RVA  OFFSET  HINT  NAME".
 
 
 
 
        l'ultimo byte sarà sempre zero per terminare
 
 
        FirstThunk (OFFSET) ----> ENTRY POINT FUNZIONE 1
                                             ENTRY POINT FUNZIONE 2
                                             ENTRY POINT FUNZIONE 3
 
          la solita conversione: (RVA) 000D45E4 = (Offset Fisico) 000D21E4, a questo indirizzo troveremo:
 
                           48 20 0D 00   5A 20 0D 00   68 20 0D 00
        7C 20 0D 00   88 20 0D 00   9E 20 0D 00   ECC...........
 
        Nella IAT possiamo trovare o gli indirizzi degli entry point delle funzioni importate oppure la copia degli
        OriginalFirstThunk. In questo caso abbiamo la copia degli OriginalFirstThunk infatti:
        (RVA) 000D2048 = (Offset Fisico) 000D0448 che non è altro che l'OriginalFirstThunk trovato prima.
       
 
Si deve a questo punto fare una precisazione importante: non sempre le dll caricano in memoria il loro codice allo stesso indirizzo, nello stesso pc generalmente si, ma in pc diversi non è detto, quindi se il PE Loader trova nella IAT un indirizzo sbagliato se lo ricalcola in base agli OriginalFirstThunk. Stessa cosa accade nel W32dasm dove non abbiamo gli entry point, il PE Loader li calcolerà in base agli OriginalFirstThunk. Naturalmente nella seconda struttura, dove questi sono settati a zero, dobbiamo avere per forza nella IAT la copia degli OriginalFirstThunk in quanto se una dll decidesse di caricare in memoria il suo codice ad un altro indirizzo avremmo gli entry point sbagliati ed il PE Loader non potrebbe ricalcolarli in nessun modo. Un problema analogo nasce sempre in presenza della seconda struttura, dopo il dumping la IAT viene sempre sovrascritta con i valori degli entry point delle funzioni importate, quindi se questi dovessero cambiare il PE Loader non potrebbe più ricalcolarli visto che non abbiamo gli OriginalFirstThunk e non abbiamo neanche una sua copia nella IAT.
 
Seconda struttura:
FirstThunk (copia OriginalFirstThunk) (OFFSET) ----> OFFSET1 ----> NOME FUNZIONE1
                                                                        OFFSET2 ----> NOME FUNZIONE2
                                                                        OFFSET3 ----> NOME FUNZIONE3
 
Ho segnato in rosso gli offset che vengono sempre sovrascritti dopo il dumping. Come potete notare i nomi delle funzioni sono ancora presenti nel file (non sono presenti solo se il crypter li ha cancellati) di conseguenza basterebbe modificare l'array in modo tale da farlo ripuntare sui nomi delle funzioni. Questa modifica potete farla o manualmente con un editor esadecimale (più avanti vedremo come modificare la IT) o più semplicemente utilizzando il rebuilder automatico del Peditor. Quello che vi ho appena spiegato è ciò che avviene se provate a dumpare un file non cryptato dove gli OriginalFirstThunk sono settati a zero (naturalmente questo serve solo a scopo didattico), con i crypter però non è detto che sia così semplice, questi infatti spesso distruggono la IT intenzionalmente, alcuni ad esempio modificano i nomi di alcune funzioni e li sovrascrivono con i nomi di altre, in questo modo la IT sembrerà perfettamente funzionante quando in realtà non lo è, altri cancellano completamente i nomi delle funzioni, altri ancora cancellano il campo "name" che punta al nome della dll importata.... insomma i crypter potrebbero distruggere la IT in mille modi diversi. Per ovviare a questo dobbiamo cercare nel loader le istruzioni che alterano la IT e nopparle oppure dobbiamo farci aiutare da un IAT rebuilder (ad esempio revirgin o Import REConstructor) per ricostruire la IAT, più avanti vedremo come fare.
 
 
COME MODIFICARE L' IMPORT TABLE:
 
Per modificare la IT di un eseguibile dobbiamo prima trovare la sua esatta posizione nel file e successivamente intervenire con un editor esadecimale (il Peditor non ci permette di modificarla). Se ad esempio volessimo modificare la IT del nostro buon vecchio W32Dasm dovremmo operare nel seguente modo: Apriamo il Peditor e guardando in "Directory" e nella seconda riga possiamo leggere subito l'offset della IT e la sua dimensione:
 

RVA

SIZE

000D4000

00000B3C

 
quindi dobbiamo convertirlo da RVA in offset fisico con l' FLC, ottenendo come risultato l'offset 000D1C00.
Se andiamo a cercare con un HexEditor questo indirizzo troveremo i seguenti byte:
 
8C 40 0D 00   00 00 00 00   00 00 00 00   00 20 0D 00
E4 45 0D 00   F0 41 0D 00   00 00 00 00   00 00 00 00
0D 20 0D 00   48 47 0D 00   ETC..........  
00 00 00 00   00 00 00 00   00 00 00 00   00 00 00 00        
00 00 00 00
(I bytes sono colorati alternatamene a gruppi di 5 per distinguere i descrittori)
 
Le ultime 5 dworld sono sempre uguali a 0 ed indicano la fine della import table.
Come potete notare abbiamo trovato gli stessi indirizzi che avevamo trovato prima con il PEditor ma invertiti, quindi se con il PEditor abbiamo OriginalFirstThunk 000D408C nell'editor esadecimale avremo i byte 8C400D00, è importante ricordarsi di questo tutte le volte che dobbiamo modificare la IT scrivendo quindi in ogni dword i singoli byte invertiti.
 
 
COME TROVARE L'ORIGINAL ENTRY POINT:
 
COMANDO BPR:
Sintassi del comando BPR di softice:
BPR StartAddress EndAddress [R|W|RW|T|TW] [IF espressione] [DO Sice-Action]
 
Per trovare l'OEP ci viene in aiuto il comando bpr di softice (nota: non esiste nella versione di softice per NT), questo non fa altro che settare un break point di lettura/scrittura in un blocco di memoria compreso tra "StartAddress" ed "EndAddress" e poiché sappiamo che l'OEP si trova nella prima sezione dovrete settare il bpr proprio in quest'area (nota: non è detto che al primo pop di softice hai trovato l'OEP). In genere i crypter/packer per saltare dal loader all'OEP usano il classico jmp eax, quindi se il crypter non vi dovesse consentire di settare il BPR un alternativa sarebbe steppare il loader e non appena trovate il jmp eax che vi porta sulla prima sezione dumpate (nota: nessuno vieta che possiate finire all'oep anche tramite un ret o qualsiasi altra istruzione).
Per sapere in quale sezione vi trovate basterà guardare la linea sotto la CodeWindow di softice, finché sarete nel loader troverete:
nome processo.nome loader
quando sarete nella sezione del codice avrete:
nome processo.nome prima sezione (.text o .code)
A volte le API "GetModuleHandleA" e "GetVersion" ci vengono in aiuto in quanto si trovano spesso poche istruzioni dopo l'OEP.
 
COMANDO TRACEX:
Tracex è un comando di icedump simile al bpr di softice utile anche questo per trovare l'OEP, ecco la sua sintassi:
/TRACEX <low EIP> <high EIP>
ad esempio:
/TRACEX 401000 409000
 
 
DUMPING CON ICEDUMP:
 
Di seguito spiegherò passo passo tutte le operazioni da seguire per decryptare un eseguibile:
 
Esempio di crypter (petite 1.2)

Section

Virtual Size

Virtual Offset

Raw Size

Raw Offset

Characteristics

.text

00008000

00001000

00005000

00001000

E0000020

.rdata

00001000

00009000

00001000

00006000

40000040

.data

00005000

0000A000

00000000

00000000

C0000040

.rsrc

00009000

0000F000

00009000

00007000

40000040

.madmat

0000D258

00018000

0000160C

00010000

E2000060

  1. Caricate IceDump (per visualizzare i comandi digitate in softice /)
  2. Aprite con il PEditor l’eseguibile e su “section” appuntate il primo, il secondo e l’ultimo “Virtual offset” cioè 1000, 9000 e 18000 e l’ultimo “Virtual size” cioè D258 (è possibile visualizzarle anche in softice scrivendo “map32 nome file”)
  3. Per trovare l’OEP scrivete in softice:
    bpr 401000 409000 r if eip < 409000   (la sua sintassi: bpr limit1 limit2 r if eip < limit2)
    (non è detto che al primo pop di softice avete trovato l'OEP)
  4. Arrivati all’OEP sommate l’ultimo “virtual offset” con l’ultimo “Virtual size” tenendo presente che le sezioni hanno quasi sempre l'allineamento a 1000 (questo valore è dato da Section Alignment), quindi dovete considerare il multiplo di 1000 superiore più vicino a  “Virtualsize”. Il multiplo di 1000 superiore più vicino a D258 è E000 quindi 18000 + E000 = 26000 (che è il valore di "Size of Image") in softice scrivete:
    /dump 400000 26000 c:\dump.exe   (la sua sintassi: /dump indirizzo lunghezza file_disco)
    dovete dumpare da 400000 (image base) perché si deve includere anche il “PE header”
  5. Se l’eseguibile non ha l’icona devete “riallinearlo”, per far questo con il PEditor andate nella “Section Table”, cliccate con il tasto destro del mouse e scegliete “DumpFixer”
  6. Con il PEditor correggete l’entry point con quello trovato (va scritto in RVA)
  7. Aprite con il peditor il file e andando in "rebuilder" selezionate l'opzione "rebuild Import Table" e cliccate su "Do", se l'eseguibile funziona è bene controllare se i valori di TimeDateStamp e ForwarderChain siano stati azzerati dal rebuilder in modo da permettere al Pe Loader di ricalcolare gli entry point delle funzioni nel caso in cui questi non siano più gli stessi. Un altra cosa che dovete controllare è se compaiono i nomi delle funzioni importate, se così non fosse l'eseguibile potrebbe funzionare solo sul vostro PC e poiché il rebuilder non è riuscito a far ripuntare la IAT sui nomi delle funzioni importate probabilmente il crypter ha alterato intenzionalmente la IT, più avanti vedremo come risolvere questo inconveniente.
 
DUMPING CON IL PEDITOR:
 
In alternativa al dumping con icedump potete dumpare con il Peditor:
  1. Arrivati all'OEP appuntate l'opcode della prima istruzione, in particolare ci interessano i primi 2 byte, per visualizzare gli opcode scrivete in softice: code on
  1. Modificate la prima istruzione con jmp eax, per far questo scrivete in softice:
        a              (invio)
        jmp eax     (invio 2 volte) (il suo opcode è FFE0)
  1. A questo punto uscite da softice con F5 e andate a cercare nel tasks viewer del PEditor il processo, quindi cliccandogli con tasto destro del mouse sopra scegliete dump (full). Abbiamo sostituito l'istruzione all'OEP con jmp eax in modo tale che il programma non potesse continuare l'esecuzione rimanendo alluppato in memoria, se non avessimo fatto in questo modo l'eseguibile continuando l'esecuzione avrebbe modificato la sezione .data e dumpando adesso avremmo ottenuto un file inutilizzabile.
  2. Adesso dovete rimettere al suo posto con un editor esadecimale l'opcode originale all'OEP.
 
ESEMPIO DI DECRYPTING/UNPACKING (PETITE 2.2):
 
In questa parte del tutorial spiegherò come decryptare petite 2.2 (il notepad cryptato è allegato al tutorial).
 
Notepad petite_2.2

Section

Virtual Size

Virtual Offset

Raw Size

Raw Offset

Characteristics

 

00006000

00001000

00002C00

00000800

E0000060

.petite

00006000

00007000

00001E71

00003400

C0000040

 

00001000

0000D000

00000000

00000000

C2000040

 

000003CA

0000E000

00000400

00000400

E2000060

 
Come potete notare le sezioni sono tutte senza nome tranne una. L'entry point è all'offset 40E042 quindi il loader che decrypterà l'eseguibile è nell'ultima sezione. Vorrei farvi notare una cosa, petite non è solo un crypter ma è anche un packer (compressore), tutte le volte infatti che Raw Size < Virtual Size e questo non è dovuto ad un normale allineamento delle sezioni allora si tratta di un packer, nel nostro caso il valore di Virtual Size della prima sezione è grande più del doppio del corrispondente Raw Size. Fatta questa brevissima analisi, dobbiamo trovare l'OEP, scrivete in softice:
bpr 400000 407000 r if eip < 407000
Se adesso premete F5 si bloccherà tutto e sarete costretti a resettare il pc, questo accade perché petite si accorge che è stato settato il bpr e manda tutto in crash. Un'altra alternativa sarebbe steppare tutto il loader in cerca del jmp eax ma con petite vi perdereste sicuramente nei meandri del codice. L'unica soluzione valida è trovare un'api sulla quale agganciarvi in modo da superare una buona parte del codice, così facendo non sarete costretti a steppare tutto il loader, io vi consiglio LoadLibratyA (va bene anche per molti altri crypter). Su quest'api softice poppa 6 volte quindi settategli un break point e dopo il 6° pop iniziate a steppare e non molte istruzioni dopo troverete 40E042 jmp 4010CC, dove 4010cc sarà l'OEP in quanto si trova sulla prima sezione. Bene, arrivati a questo punto potete dumpare scrivendo in softice:
/dump 400000 F000 c:\dump.exe
Adesso riallineate l'eseguibile e correggere l'entry point con quello appena trovato. Se andate a guardare la IT noterete subito che non ci sono i nomi delle funzioni importate, la causa è senza dubbio il crypter che altera intenzionalmente la IT (in questo caso il rebuilder automatico del PEditor non ci verrà in aiuto), potete a questo punto operare in due modi:
  1. Cercate nel loader le istruzioni che alterano la IT e le dumpate
  2. Vi fate aiutare da un IAT rebuilder per ricostruire la IAT
Io spiegherò solo il secondo metodo utilizzando Import REConstructor:
 
 
  1. Eseguite il notepad cryptato e cercate nel tasks viewer il suo processo
  2. Scrivete nel campo "OEP" l'original entry point in RVA
  3. Cliccate su "IAT AutoSearch" in modo tale da consentire al programma di inserire nell'apposito campo l'indirizzo della IAT e la sua grandezza
  4. Cliccate su "Get Imports"
  5. Import REConstructor non riuscirà a risolvere alcune funzioni, se cliccate però su "Auto Trace" nel log comparirà la scritta: "Congratulations! There is no more invalid pointer, now the questions is: Will it work? :-)", bene questo vuol dire che il tool ha risolto tutte le funzioni
  6. Cliccate su "Fix Dump" e selezionate il file dumpato, adesso l'eseguibile funziona!!!
 
Jumperplus
 
 

Note finali

Un saluto a Quequero, a tutta la UIC e alla ML e ... alla prossima!!!

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 immane che ogni singolo programmatore ha dovuto portare avanti per fornire ai rispettivi consumatori i migliori prodotti possibili.

Noi reversiamo al solo scopo informativo e di miglioramento del linguaggio Assembly.

Capitoooooooo????? Bhè credo di si ;))))