Manual Unpacking
From UIC
Manual Unpacking
Contents |
| Infos | |
|---|---|
| Author: | phobos |
| Email: | phobos333 -at- email -dot- it |
| Website: | P.H.R.E.W (Actually Offline) |
| Date: | 01/06/2006 (dd/mm/yyyy) |
| Level: |
|
| Language: | Italian |
| Comments: | Un tutorial generico sul manual unpacking |
Introduzione
Adesso mi impegno... ;-)
Il mio 'target' per dirlo all'americana e' lo spiegarvi in modo quanto piu' generale possibile la tecnica del Manual Unpacking...
Innanzitutto una breve premessa...
Da un po di tempo a questa parte, i programmatori commerciali e le Software House, seguono la 'moda' di crittare e packare i loro eseguibili per rendere complicata la vita di noi poveri reverser. Non solo... dal punto di vista meramente 'filosofico', l'utilizzo di un packer permette la distribuzione di eseguibili su internet, visto che le dimensioni di questi ultimi vengono ridotte, in alcuni casi di un buon settanta per cento ed oltre.
Il vero motivo e' pero' la protezione dei loro prodotti...
Infatti, provate a disassemblare un file packato o crittato... vedrete che ne viene fuori...
Il problema, e' facilmente risolvibile, visti i centinaia du unpacker e decripter presenti sulla scena... ma volete mettere la soddisfazione di fare 'a mano' una cosa che molti devono affidare ad un programma? ;-)
A questo punto, una domanda sorge spontanea... 'che cosa fa un packer??'
Ora vediamo di capirlo...
Tools
Numega SoftICE
GetTyp
Dumper per la memoria (Adump andra' benissimo)
ProcDump
Iczelion's Virtual Address to File Offset Utility
Editor Esadecimale di vostro gusto
Un bel po di pazienza e un bel po di tempo... Se fumate...le vostre sigarette preferite (vi ricordo, che ai sensi dell'articolo 46 della legge 428/90, il fumo nuoce gravemente alla salute, e che, se siete minorenni, non dovete fumare, se no, vi si secca il 'pistolino' e poi vi cade! :-))
Notizie sul Programma
Ogni programma che noi eseguiamo sulla nostra macchina, 'risiede' su una memoria di massa (Hard Disk, CD-Rom, Floppy
Disk (he he he! una volta! ;-)...) esso in realta' e' una sequenza di bit, che vengono letti dal computer e trasferiti in memoria,
da una particolare parte del sistema operativo chiamata 'loader' (appunto colui che si occupa di caricare in memoria un
programma ed avviarne l'esecuzione).
Ogni programma, e' costituito da un insieme di istruzioni, dati, variabili, eccetera, quindi, deve occupare 'fisicamente' dello spazio in memoria, e questo spazio deve essere ordinato, in base a quelle che sono le necessita' (esistono programmi che usano piu' dati e che quindi richiedono piu' spazio in memoria di altri).
Questo 'ameno' compito e' affidato al loader, che non fa altro che andare a leggere nella parte iniziale del programma e vedere quanto spazio gli serve in memoria per funzionare e come questo spazio debba essere ripartito.
Ogni programma e' costituito da varie sezioni, fisicamente sequenziali, che identificano i vari oggetti (con un abuso di linguaggio) che lo costituiscono: dati, codice, e cosi' via. Queste vengono lette dal loader che si occupa di trasferire tutto in memoria. A questo punto, si potrebbe scrivere un vero e proprio trattato a riguardo (io non ne sarei in grado, Kill3xx lo ha fatto (CIAUZ Killo!!!) spiegando byte per byte il formato PE (Portable Executable) sui suoi tre tutes sull'argomento (trovate tutto su RingZ3r0)... ora chiudo le parentesi che 'sta frase inizia a sembrare un'equazione!!).
I packer o cripters, non fanno altro che (in termini che piu' seplici non si puo') crittare o comprimere (o entrambe) una o piu' sezioni del file eseguibile, aggiungendo al file originale, una porzione di codice (solitamente alla fine del file) che si occupa di effettuare l'unpack o il decript prima che il programma venga eseguito e subito dopo averlo 'letto' dalla memoria di massa su cui e' situato.
Quindi, il programma, nell'attimo prima di essere eseguito DEVE essere presente in memoria nella condizione 'originale' e cioe' esattamente come era prima di essere stato compresso o crittato... la 'filosofia' del manual unpacking consiste proprio in questo: 'rubare' il programma dalla memoria del computer e 'ricostruirne' una copia, decrittata e decompressa, da poter poi utilizzare per qualunque scopo sia necessario (cr@cking tra gli altri ;-)).
Essay
Vediamo come intervenire...
Innanzitutto, una volta stabilito che il programma in questione e' packato (ed a questo scopo potete utilizzare il GetType) per effettuare il dumping abbiamo bisogno di alcune informazioni, la prima da 'prelevare' e' l'entry point del programma (in parole povere la prima riga di istruzioni che viene eseguita dal programma) per reperire questa informazione utilizzeremo il ProcDump: andiamo nella sezione che tratta il PE editor, selezioniamo il programma in esame, e nella prima casella della box che ci appare, abbiamo il nostro Entry Point in formato RVA (Real Virtual Address) segnamoci il numero, ed a questo punto, ci serve convertirlo nell'offset del file.
A questo scopo, o lo fate 'a mano' (e se siete in grado di farlo, potete risparmiarvi il resto del tute!!!), oppure, vi affidate all'utility preposta (Virtual Address to File Offset), aprite l'utility, e nella casella 'Memory Address' inseriamo l'indirizzo fornitoci dal Pdump, selezioniamo la check box relativa ad RVA, apriamo il programma cliccando su 'file' ed andandolo a selezionare (se a questo punto vi viene comunicato un errore dal RVA to File Offset, e' perche' il programma in esame e' gia aperto, quindi per risolvere, sara' sufficiente chiuderlo e rieseguire la procedura nell'utility) e clicchiamo su 'Do It'... otteniamo l'offset, in formato esadecimale e decimale, a noi interessa quello esadecimale... segnatevelo e chiudete tutti i programmi aperti.
La seconda fase consiste nell'aprire l'utility ADump (e lasciarla aperta per tutte le rimanenti fasi). L'ADump e' una utility che 'gira' sotto consolle DOS, quindi, una volta avviata, la potrete utilizzare dalla finestra MS-DOS di Windows.
All'interno della consolle del programma digitate il comando r seguito da invio, vi apparira' una lista del tipo
---------------------
STARTOFFS: 0x83AA1000 --(1)
ENDOFFS: 0x83B95240 --(1bis)
LIMIT: 0xF4240 <1000000> --(2)
CUROFFS: 0x83AA1000
MAPFN: C:\WINDOWS\TEMP\Adump.log --(3)
MAPFSIZE: 0xF4240 --(4)
ANFILTER: A..Z,a..z,0..9
Dove, STARTOFFS indica l'indirizzo di partenza dello spazio di ambiente mappato per il programma ADump (1), LIMIT indica la dimensione in byte (formato esedecimale e decimale, tra parentesi) (2), MAPFN e' il nome del file che sara' usato per salvare il dump della memoria (3), e MAPFSIZE e' la dimensione del file suddetto (4).
Chiaramente, i valori esadecimali in (1) ed (1bis) potranno differire da quelli indicati (dipende da cosa avete attivo in memoria) e il percorso del file Adump.log (3) sara' quello dove avrete installato il programma. Ma cio' e' irrilevante,
Segnatevi il valore di STARTOFFS e andiamo avanti.
Ora dobbiamo intercettare l'entry point del programma con il SoftICE (e per questa simpatica procedura thanx to Andreuzzo & Quequero): come gia' detto prima, l'entry point e' la prima istruzione che viene eseguita dal programma durante la sua esecuzione, questa non coincide mai con la prima istruzione del file disassemblato (infatti, se avete notato, nel windasm, tra i menu' e' presente una voce che permette di raggiungere l'entry point del programma). A questo scopo abbiamo bisogno dell'editor esadecimale: apriamolo, portiamoci sul byte indicato dall'offset fornitoci dall'utility di Iczelion e sostituiamo il byte (avendo cura di segarci quello originale) con il codice esadecimale CC che e' l'equivalente dell'istruzione INT 03.
Salviamo il programma, avendo la cura di farne una copia di backup (sempre... se qualcosa va storto dobbiamo poter tornare indietro) attiviamo il SoftICE e settiamo un break point sull'int 03 (BPINT 3 <invio>) usciamo dall'ice e avviamo il programma che abbiamo modificato, a questo punto l'ice dovrebbe intervenire e femarsi sull'istruzione immediatamente successiva a quella da noi cambiata in INT 03 che altro non e' che il nostro Entry Point. Il passo successivo consiste nel disabilitare il break settato, segnarsi l'indirizzo della riga di codice che contiene l'istruzione int 03 (il nostro entry point, e a questo punto, potete anche mettere un break su questa locazione di codice, oppure aspettare tanto dovremo farlo dopo) uscire dall'ice e ripristinare il programma, togliendo il codice CC dall'offset dell'entry point e rimettendo quello originale.
Fatto? Ok, andiamo avanti! ;-)
Il passo successivo consiste nell'eseguire lo stepping all'interno del SoftICE, partendo dall'entry point del programma, fino a raggiungere la sezione denominata '.code', la individuate facilmente perche' e' referenziata all'interno dell'ice sulla riga della finestra del codice. (per intenderci, apparira' una stringa del tipo NOME_PROGRAMMA .CODE + XXXX) Quando saremo arrivati nella sezione code del nostro programma, dobbiamo andare a vedere quali sono le pagine di memoria che esso sta utilizzando (in gergo tecnico: che sono state mappate).
Per fare cio', dobbiamo digitare nel SoftICE il comando MAP32 NOME_PROGRAMMA <invio> dove al posto di NOME_PROGRAMMA dobbiamo inserire il nome che identifica il nostro programma in memoria (se non lo conoscete, utilizzate il comando TASK, vi apparira' un elenco con tutti i processi attivi nel sistema in quell'istante, compreso il nome del programma in esame).
Fatto cio', vi apparira' una lista del tipo:
NOME_PROG .CODE 1 0XXX:00XXXXXX 00XXXXXX CODE RO
NOME_PROG .DATA 2 0XXX:00XXXXXX 000XXXXX IDATA RW
NOME_PROG .BSS 3 0XXX:00XXXXXX 00XXXXXX XXXX RW
NOME_PROG .idata 4 0XXX:00XXXXXX 0000XXXX IDATA RW
NOME_PROG .tls 5 0XXX:00XXXXXX 0000XXXX XXXX XX
NOME_PROG .rdata 6 0XXX:00XXXXXX 0000XXXX IDATA XX
NOME_PROG .reloc 7 0XXX:00XXXXXX 000XXXXX XXXX XX
NOME_PROG .rsrc 8 0XXX:00XXXXXX 000XXXXX XXXX XX
NOME_PROG .rsrc 9 0XXX:00KKKKKK 0000XXXX XXXX XX <--ultima sezione
Dove: Owner, indica il programma in esame; Obj Name, e' il nome della sezione; Obj#, indica il numero sequenziale della sezione; Address, e' costituito dagli indirizzi in memoria da cui partono le varie sezioni (<- IMPORTANTE); Size, indica la dimensione della sezione (<- IMPORTANTE); Type, indica la tipologia della sezione e la due lettere che seguono la tipologia, sono i 'flags' della sezione (RW: Readable/Writable; RO: Read Only; e cosi' via).
Una nota: tutti i dati contenuti nella tabella risultante dal comando Map32 sono in formato esadecimale.
A questo punto, ci serve conoscere le dimensioni del file, e a questo proposito, ci possiamo riallacciare al discorso che facevamo all'inizio, circa la 'sequenzialita delle varie sezioni che costituiscono un programma, da quando vengono lette dalla memoria di massa a quando questo risiede in memoria. Praticamente, dobbiamo utilizzare la formula:
Praticamente, RVA Ultima sezione e' quel numero evidenziato in rosso nell'esempio (KKKKKK), Image Base e' sempre uguale a 00400000h (cio' e' vero solo per gli eseguibili, per altri files, tipo le dll cambia, se volete sapere perche' vi do' due parole chave: rilocazione e collisioni ;-)) PE Size, e' solitamente uguale al valore 1000h.
Si'... Solitamente... poiche' non eseste una formula per conoscere al PE Size, e, almeno secondo quanto ne so io, nemmeno un tool che lo faccia, il metodo che si puo' utilizzare a questo proposito e' il seguente: si apre l'eseguibile con un editor esadecimale, si controlla la prima parte del file (quella dove sono presenti tutti i bytes 00 e le voci relativa alle varie sezioni: .code, .idata., .reloc, ecc.), ci si porta alla fine di tutti i byte con valore 00, subito dopo le suddette stringhe, e immediatamente prima dell'inizio dela sequenza di caratteri 'illegibili', all'offset corrispondente all'ultimo byte pari a 00, corrisponde la PE Size. Facciamo un esempio 'grafico'
00000010h B8 00 00 00 00 00 00 00 40 00 1A 00 00 00 00 00 ¸.......@......
Omissis...
000003E0h 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000003F0h 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 <b>00</b> ...............<b>.</b> <-Ultimo byte PE
00000400h 00 A3 00 20 40 00 8B 58 3C 03 D8 0F B7 43 14 0F .£. @.‹X<.Ø.·C..
Nel nostro caso, il byte evidenziato in grassetto, e' l'ultimo del PE, (mi sono risparmiato tutti i precedenti, ma non potete sbagliare, ce ne sono all'incirca 256, tutti uguali a 00h), l'offset corrispondente al byte in questione corrisponde alla PE Size, nel nostro caso: 3FFh.
E' possibile anche fare un'altra procedura: semplicemente sommare tutti i valori esadecimali che troviamo sotto la voce Size, stesso dentro il SoftICE (il comando e' ? seguito dalla somma) e otteniamo piu' o meno lo stesso risultato.
Una piccola nota, le dimensioni che otteniamo con queste due procedure, differiscono, oltre che tra loro stesse, anche da quelle effettive del programma, di qualche kbyte, quindi non vi preoccupate...
Fatta questa procedura, lo 'step' successivo consiste ne calcolare il numero di pagine di memoria che il loader ha allocato per il programma (la memoria del computer e' suddivisa, per costruzione, in pagine, ognuna delle quali ha una dimenzione di 65535 byte cioe' di 64 kbyte, se vi interessa l'argomento, vi rimando allo studio delle tecniche e dei metodi di indirizzamento).
Quindi dobbiamo eseguire una semplice divisione: VALORE_OTTENUTO_DA_(*) / FFF
Dove, VALORE_OTTENUTO_DA_(*), e' la dimensione del file ottenuta con una delle due procedure viste sopra (mi raccomando: in esadecimale!!!), e FFF non e' altro che il valore 65535 in esadecimale.
A questo punto, abbiamo ottenuto un numero (sempre esadecimale) che indica la quantita' di pagine della memoria che sono state mappate per il nostro programma.
Ora dobbiamo vedere quali pagine non sono 'fisicamente' presenti: infatti, la mappatura, e' un processo che 'riserva' una serie di pagine per l'uso da parte del programma, ma non e' detto che tutte queste siano effettivamente 'riempite' dei dati o del codice del programma stesso. Questa procedura, ci permettera' di caricare tutto il programma in memoria, onde evitare che nella fase successiva (quella del 'furto' vero e proprio del programma) ci ritroviamo con 'pezzi' di codice mancanti.
Utilizzeremo a questo scopo il comando:
Dove, 0400000 e' il nostro image base (vedi sopra), mentre al posto delle XXX dobbiamo inserire il risultato della divisione tra dimensione del file ed FFF, che abbiamo fatto pocanzi. Il SoftICE (non vi ho detto che tutti questi comandi vanno inseriti nell'ice?? ;-)) ci fornira' una lista di indirizzi del tipo
00400000 00XXXXXX X X X X X XX XXXXXXXX
00401000 NP 00XXXXX
00402000 00XXXXXX X X X X X XX XXXXXXXX
Chiaramente la lista sara' un bel po' piu' lunga!
Le pagine precedute da NP sono quelle Non Presenti le altre sono quelle presenti. Scorrete tutta la lista e segnatevi l'indirizzo di tutte le pagine non presenti.
Quindi, ora bisogna caricare in memoria le pagine che non sono presenti, per farlo utilizzaremo il comando PAGEIN seguito dagli indirizzi delle pagine non presenti (la procedura va effettuata per ogni pagina non presente, lo so... e' una rottura di palle... ma il reversing ha bisogno di molta pazienza... quindi coraggio!)
Finito questo 'snervante' compito (hehehehehe se sapeste quante altre volte dovremo farlo!!! hihihihihi!), abbiamo in memoria tutto il nostro eseguibile, pronto per essere 'sniffato' ;-)
Adesso intraprendiamo una fase delicata: dobbiamo trasferire il programma, dall'area in cui risiede in memoria, all'area di memoria allocata per l'ADump. Pero' qui' bisogna fare una precisazione: l'ADump 'mappa' in memoria un'area di 'soli' un milione di byte (hehehehehehe ce ne fossero stati 5/6 di milioni di byte sul mio 'defunto' 386!! ;-)) quindi, se la dimensione risultante del nostro eseguibile e' superiore a questo milione, dobbiamo deguire questa procedura:
Digitiamo in SoftICE il comando
Avendo l'accortezza di sostituire all'indirizzo che io ho evidenziato in rosso il valore di STARTOFFS che abbiamo segnato prima. Il valore 00400000 e' la nostra image base, F4240 e' la lunghezza del blocco di memoria che andiamo a trasferire (in esadecimale, coincide proprio con quel milione di byte che abbiamo detto sopra) nell'area riservata all'ADump.
La procedura va eseguita tante volte, quanti sono i segmenti da un milione di byte contenuti nel nostro programma 'target'.
Cerco di essere piu' chiaro: se dobbiamo 'dumpare' un programma grande 2.5 megabyte avremo in bytes una lunghezza di 2621440 byte (2.5 * 1024 * 1024) che in esadecimale corrisponde a 280000. Se effettuiamo una divisione intera (non sapete cos'e'? Semplicemente quella divisione che ci facevano fare alle scuole elementari, quella che fornisce un risultato intero ed un resto che non e' piu' divisibile per il divisore) tra la dimensione del nostro programma e lo spazio che abbiamo a disposizione nell'ADump, otteniamo quante volte dobbiamo ripetere questa procedura, poi, l'ultimo passaggio dovremo sostituire ad F4240 il valore del resto della nostra divisione... mi sa che invece di chiarirvi le idee ve le ho incasinate ancora di piu'!!! :-D
Facciamo un esempio pratico, ipotizzando che il nostro programma sia di 2.5 mega e utilizzando gli indirizzi che abbiamo visto sopra per l'ADump:
1) Apriamo l'ice, steppiamo dall'entry point del programma alla sezione .code
2) Mappiamo le pagine presenti e quelle non presenti, e carichiamo in memoria quelle non presenti (la parte che utilizza il comando PAGEIN).
3) Digitiamo il comando m 00400000 L F4240 83AA1000 nel SoftICE
4) Usciamo dal softice e andiamo nell'ADump, e salviamo il dumping della memoria su un file, con il comando: w c:\percorso\nome_programma_1.log (al posto di 'percorso' e di 'nome_programma', chiaramente, mettiamo il percorso in cui vogliamo salvare il file dump e il nome dello stesso)
5) Rientriamo nel softice, risteppiamo dal program entry point alla nostra sezione .code
6) Ricarichiamo in memoria le pagine della destinate al programma che non sono presenti
7) Mappiamo la parte di memoria successiva nell'ADump con il comando: m 0040F4240 L F4240 83AA1000 (la parte successiva 'parte' dalla fine di quella che abbiamo appena mappato, cioe': 00400000 + F4240)
8) Usciamo dall'ice e andiamo in ADump a salvare quest'altra parte di codice: w c:\percorso\nome_programma_2.log
9) Rientriamo in SoftICE, steppiamo dal program entry point alla sezione code e carichiamo le pagine non presenti
10) Trasferiamo l'ultima parte del nostro programma nell'ADump: m 41E8480 L 97B80 83AA1000
Dove: 41E8480 non e' altro che l'ultimo indirizzo (quello al passaggio (7)) addizionato di un altro milione di byte (F4240); 97B80 e' la parte rimanente di programma (ottenibile, sottraendo F4240 dalla dimensione del file tante volte fino a che non otteniamo che il risultato della sottrazione e' minore di F4240)
11) Usciamo dall'ice e salviamo l'ultima parte del nostro 'futuro' programma: w c:\percorso\nome_programma_3.log
A questo punto abbiamo su disco dei files che rappresntano il dumping del programma in memoria, per segmenti di un milione di bytes, quindi dobbiamo fare ancora due operazioni prima di avere finito.
La prima consiste nel collegare tra loro i vari files, fino ad ottenerne uno solo, che poi rinomineremo in nome_programma.exe. Per fare cio' sara' sufficiente utilizzare un editor esadecimale, aprire i files uno alla volta, copiare ed incollare tutto il loro contenuto sequenzialmente a partire dal primo, dopo di che' 'salvare con nome...'.
La procedura finale (e quella piu' importante) e' denominata 'riallineamento del PE'. Ed a questo scopo utilizzeremo il ProcDump.
Apriamo il PDump e da questo apriamo il nostro programma dalla sezione del procDump detta 'PE Editor', e controlliamo le sezioni del file (cliccando sul pulsante 'sections') dopo di che' dobbiamo fare click con il pulsante destro su ognuna delle sezioni che ci appaiono in elenco e selzionare la voce di menu' 'Edit Section', a questo punto ci apparira' una finestra con delle dialog contenti dei numeri esadecimali (gli addres fisici e virtuali delle varie sezioni del programma), e noi le dobbiamo 'riallineare'... mi spiego: supponiamo di avere nella section .data i valori
PSize: 00019A00 Offset: 000B6200
Noi le dobbiamo cambiare in
PSize: 0001AF84 Offset: 000B8000
A questo punto, dopo aver eseguito la procedura descritta per tutte le sezioni presenti nel programma, e, dopo aver confermato tutte le modifiche nel PDump (cliccando su 'OK') ,il nostro eseguibile dovrebbe essere pronto per 'girare' (una conferma dovrebbe essere data dal fatto che viene ripristinata l'icona interna del programma, sempre che ne sia prevista una)
Ora il programma e' pronto per essere... mah'... fatene cio' che volete! ;-)
E' tutto. Spero di esservi stato di aiuto :-)
Ciao.
phobos (aka D4rKSP4rr0W)
Note Finali
I MIEI PIU' SENTITI RINGRAZIAMENTI E SALUTI VANNO A:
- Yado, per avermi fatto incuriosire su questa tecnica
- Quequero per il suo continuo impegno e anche perche' e' un Bonzo
- Kill3xx, per aver contribuito ad 'aprire il vaso di Pandora'
- AndreaGeddon perche' ha scritto un tute sul manual unpacking per cerebrolesi come me (e che io ho generalizzato)!
- Tutti i membri o semplici partecipanti di RingZ3r0, della UIC, di #crack-it
- Tutti coloro che hanno la pazienza di ascoltare un pazzo scatenato come me! :-)
A PRESTO!!
Disclaimer
Come sempre, voglio ricordarvi che la 'registrazione' dei programmi mediante seriali 'sniffati', il loro cracking, ed in alcuni casi il loro semplice 'reverse engeneering' sono procedure vietate dalla legge. Una volta terminato il periodo shareware bisogna registrare legalmente il programma o disfarsene. Questo tute e' scritto per soli fini didattici, l'autore non vuole in alcun modo incoraggiare attivita' quali hacking, cracking o phreacking. Non mi assumo inoltre nessuna responsabilita' se vi spappolate i programmi. Lo scopo di questo e degli altri tute presenti su questo sito, e' di analizzare le tecniche di protezione usate dai programmatori commerciali ed individuare eventuali 'falle' al fine di rendere i loro prodotti piu' efficienti e sicuri. Se avete suggerimenti, critiche costruttive o domande, contattatemi. Se invece volete reclamare diritti sui marchi o prodotti citati all'interno del presente... andate pure al diavolo; io non ci guadagno un centesimo, rivolgetevi a chi ci specula sopra. Tutti i nomi citati sono marchi registrati o copyright dei rispettivi autori.
Categories: Phobos | 2006