Nono Corso Newbies
Manual Unpacking for NewBies!

Data

by "AndreaGeddon"

 

23/03/2001

UIC's Home Page

Published by Quequero



Andreuzzo, stavolta ti sei impegnato :) E bbbravo, hai fatto davvero un lavoro degno di lode :) complimenti

 

....

Home page se presente: www.andreageddon.com
E-mail: andreageddon@hotmail.com
Nick, UIN, canale IRC/EFnet frequentato   irc.azzurra.it   #crack-it
 

....

Difficoltà

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

 

Ebbene si! Me lo avete chiesto non di rado in chat, ed ora eccovelo qua: un piccolo crypter! E' davvero molto semplice, ma spero che questo vi sproni (anzi, vi costringa) a mettere le vostre manacce sul PE. Più che altro vediamo un approccio generale all'attacco di un packer/crypter.


Nono Corso NewBies
Manual Unpacking for Newbies
Written by AndreaGeddon

Allegato

Introduzione

Che palle! Ieri mattina mi son svegliato per andare all'univ, e il mio prof malato ci ha dato buca... azz, vabbè, in fin dei conti ci ho guadagnato, visto che mi sono ritrovato un pò più di tempo per finire il mio LeimCrypt :-P

Tools usati

-SoftIce
-PEditor
-Un HexEditor
-IceDump

URL o FTP del programma

quequero.cjb.net

Notizie sul programma

Il programma è una semplice finestra con un semplice pulsante "Register" che vi dice (semplicemente) che non siete registrati. Per ottenere il messaggio di registrazione avvenuta basta modificare un jump, ma se volete parchare il crackme ve lo dovete decriptare :-). Ho lasciato anche la possbilità di scrivere una memory patch, anche se tale soluzione richiederebbe un pò di lavoro in più (ehi ehi non riciclate i process patcher!!).

Essay

UN PO' DI TEORIA

Okei, il programma è in apparenza un normale programma. Lo eseguiamo, ci registriamo e tutto sembra a posto. Per registrarci basterà bypassare un jump prima della messagebox. Bene! Ora andiamo a patchare l'eseguibile... DOH! Non trovo il byte da patchare!! Apriamo il WDasm e... DOH! Cazz il wdasm crasha! Questo non è intenzionale nel mio LeimCryptm. Vabbè, possiamo sempre disassemblarlo con IDA, cmq visto che è criptato... Per prima cosa prendo il mio caro PEditor e diamo uno sguardo al PE di questo eseguibile. In particolare per prima cosa guardiamo le sezioni:

Section VirtualSize Virtual Offset Raw Size Raw Offset Characteristics
.text 00000B12 00001000 00000C00 00000400 E0000020
.rdata 00000448 00002000 00000600 00001000 E0000020
.data 00000194 00003000 00000200 00001600 E0000020
.idata 0000060C 00004000 00000800 00001800 E0000020
.rsrc 000016AC 00005000 00001800 00002000 E0000020
Geddone! 00003000 00007000 00003000 00003800 E0000020

dalle sezioni si può capire molto. Spesso il packer lo potete individuare proprio guardando le sezioni: oltre alle sezioni standard (.text, .data, .rsrc etc..) trovate delle sezioni strane, tipo .Petite, ASPR etc. che vi permettono di individuare il packer. Nel nostro caso è tutto a posto tranne l'ultima sezione "Geddone!". Dubito che un compilatore inserisca spontaneamente una sezione con tale nome, quindi questa sezione identifica il mio LeimCrypt. Ovviamente non troverete il decrypter specifico su protools :-P, quindi ci toccherà fare il decrypt manualmente. Non preoccupatevi se non ne sapete niente, il crypter è davvero semplice e vedrete che il decrypting consiste in 3 semplici passi. Vabbè, abbiamo capito che il file originario è stato ritoccato e criptato con il LeimCrypt. A maggior conferma diamo uno sguardo all'entry point:

RVA:     000070001

VA:        00407001

l'address dell'entry point cade proprio nella sezione Geddone!. Infatti questa sezione ha come RVA il valore 00007000 (VA 00407000). Prima di fare confusione con i VA, RVA e ImageBase andatevi a leggere il mio tutorial sul PEditor. Cmq diamo un riassunto sommario:

ImageBase: per gli eseguibili è quasi sempre 00400000

RVA: indirizzo relativo all'ImageBase (relative virtual address)

VA: indirizzo    completo di ImageBase (Virtual address)

per intenderci, un file eseguibile viene mappato in memoria a partire dalla sua ImageBase, quindi tutti gli indirizzi RVA danno l'indirizzo in memoria escluso di ImageBase. Ad esempio:

il relative virtual address della sezione Geddone! : 7000

invece il suo Virtual Address vero e proprio è: 00407000 (aggiunta l'imagebase)

Mentre debuggiamo ci troviamo a lavorare sui VA (l'entry point lo debuggerete alla linea 00407001, non alla linea 7001!), mentre quando esaminiamo un file con un PEditor ci troviamo di fronte agli RVA. Tenete sempre a mente questa differenza. Tenete anche conto che il FileOffset corrispondente a un RVA o VA va sempre calcolato, ma per questo esistono vari offset calculator (ad esempio il mio!! :-)). Okei, viste queste piccole nozioni continuiamo.

Quando affrontiamo un packer/crypter ci troviamo di fronte ad un eseguibile criptato (grande scoperta...). Beh se è criptato come fa a funzionare? E qui viene in gioco la sezione Geddone!, o in genere quella del packer. Infatti il programma parte dalla sezione Geddone! per auto decriptarsi a runtime    e quindi per potersi eseguire senza problemi. Una volta che il file oringinario è stato decriptato, allora si esce dalla sezione Geddone! e si torna al programma originale, ora pronto all'esecuzione. E già qui si accende una lampadina. Noi dobbiamo decriptarci il programma, ma se lui si decripta da solo tanto vale sfruttare il suo lavoro! Il programma in memoria dovrà per forza comparire decriptato per poter essere eseguito (se lo eseguiamo criptato crasha), quindi una volta che sarà lì bello decriptato e funzionante noi non faremo altro che prenderlo dalla memoria e salvarlo su disco. Il programma aggiunto che si occupa di decriptare il programma originale si chiama Loader. Mi sa che non mi sono spiegato molto bene... meglio vedere il tutto con uno schemetto:

 

PE Header
Sezione 1
Sezione 2
Sezione n
Loader

questo è il nostro eseguibile. Quando lo eseguiamo l'entry point si trova nella sezione del Loader, ed in genere il Loader si trova sempre alla fine. Il loader quindi decripterà secondo il suo algoritmo le sezioni criptate: ad esempio prende la sezione 1 e la decripta, poi passa alla 2 e la decripta, e così via. In genere vengono criptate solo le sezioni di codice e di dati. Una volta che il loader ha finito il suo lavoro salta all'Entry Point originale del programma, che si trova sempre nella sezione 1 (la sezione di codice è praticamente sempre la prima). Infatti originariamente il programma non aveva la sezione loader, ed il programma cominciava la sua esecuzione da un punto definito nella sezione di codice (entry point). Comqunque abbiamo capito come funziona a grandi linee un Loader. E' chiaro che se noi iniziamo lo stepping dall'entry point che sta sul loader ci stepperemo tutto il loader e vedremo tutto quello che combina.

Ora possiamo pensare all'attacco. Come detto poco fa, noi aspetteremo che il programma sia tutto bello decriptato per salvarcelo su disco. Ma come facciamo a sapere se il programma è decriptato o no?? Beh è facile, basta che iniziamo a steppare il loader finchè il loader non ci manda dalla sezione Geddone! alla sezione .text. A quel punto vuol dire che siamo arrivati all'Entry point del programma originale, e che il programma è pronto all'esecuzione. In genere troviamo il classico

JMP   EAX

dove in eax c'è l'original Entry Point. Questo jump ci spedirà nella sezione .code. Per vedere in che sezione vi trovate basterà guardare la linea sotto la CodeWindow del softice: finchè siete nel loader troverete scritto

nono.Geddone!

dopo il jump eax la linea sarà

nono.text

ecco che siamo dove vogliamo! In genere i crypter/packer usano il classico jump eax perchè lo usa anche il PE loader del Windozz, ma ciò non toglie che potete trovare anche altri modi di saltare all'original Entry Point, ad esempio con una call, un ret o qualsiasi cosa permetta di gestire l'eip. A proposito, se non fosse chiara la distinzione tra packer e crypter:

crypter:  cripta le sezioni di un eseguibile ma non ne altera la grandezza

packer:  oltre a criptare le sezioni le riduce anche di grandezza

è indifferente per i nostri scopi parlare di crypter o packer, il problema è sempre lo stesso. Come avrete notato il mio LeimCrypt è un semplice crypter. Di teoria ne abbiamo buttata giù un bel pò, è ora di effettuare l'attacco.

 

IL DUMPING GREZZO

 

In genere se il crypter non è complesso (come nel mio caso) possiamo eseguire il file, e mentre il file è in esecuzione apriamo il PEditor, andiamo nella lista dei Task, scegliamo il processo del nono corso newbies, tasto destro e avete un bel... DUMP FULL. Salvate il programma su disco col nome che volete. Questo è un approccio non molto corretto, in quanto il programma potrebbe avere già modificato la sezione data in modo irrimediabile. Il che vuol dire che ora il vostra programma DUMPED.EXE (il prog dumpato insomma) ha una sezione dati non corrispondente all'originale. In questo caso ciò non comporta nessun problema, ma io preferisco la via del dumping grezzo, cioè ci steppiamo il loader, arriviamo all'original Entry Point e ci dumpiamo il programma. Come facciamo a Dumpare da SoftIce?? Esiste un ottimo programma, si chiama IceDump, lo trovate come al solito su protools. IceDump va caricato nel softice prima di poter essere usato. Nella directory di icedump avete la cartella W9x che indica la versione per sistemi win95-98. Nella cartella W9x trovate varie cartelle ognuna con un numero, ad esempio 322, 323 etc.. queste corrispondono alle diverse versioni di softice. Ora potete provare a vedere la versione del vostro softice e caricare l'icedump.exe dalla relativa cartella, ma di solito le versioni non coincidono, quindi provate tutti gli icedump.exe finchè ne trovate uno che funziona. Bene ora che avete icedump caricato siamo pronti. In sofftice battete /n e avrete la lista dei comandi aggiunti da IceDump. Come vedete non avete solo il comando DUMP, ma anche altri, quali il tetris (!), e le opzioni per sentire MP3 o CDaudio dal debugger. Strafico! Vabbè non divaghiamo. Vogliamo dumparci il nostro bel file. Carichiamo il SoftIce Loader (sta nella cartella del SoftIce), selezioniamo il nono.exe e premiamo il tasto run (quello con gli ingranaggi). Il softice popperà sull'entry point del programma, nel nostro caso alla linea 00407001, che è quello del loader. In effetti abbiamo usato il loader del softice perchè ti poppa subito sull'entry point :-) (Attenti a non fare c0nfusione tra il loader del crypter e quello del softice!). Se il SoftIceLoader non poppasse sull'entry point allora dovete ricorrere al solito espediente: con un HexEditor andate nel file offset corrispondente all'entry point e sostituite il byte con un bel CCh. Questo è l'opcode per INT 3. Ora in softice battete un bel

BPX int 3

e quando lanciate il programma il softice popperà sulla prima linea. Ricordatevi prima di sostituire il primo byte con CCh di segnarvi il byte originale, perchè quando il softice sarà poppato dovrete rimetterlo al posto del CC tramite

r eip byte_originale

Okei qualunque metodo abbiate usato ora siete nell'Entry Point del loader. Siamo alla riga 00407001. Per ora lasciamo stare il loader, lo esamineremo più tardi. Pensiamo ad arrivare all'original entry point. Iniziamo a steppare, passiamo su alcune call, proseguiamo. Come vedete il codice è abbastanza lineare, niente magheggi strani o garbugli di call, al contrario di quanto troverete nei crypter più bastardi. Steppiamo fino ad arrivare a

00407136        JUMP   EAX

arrivate a questa linea, e vedrete in eax l'indirizzo dell'entry point, cioè 004017D0. Eseguite il jump eax e arrivate alla linea 004017D0, nella sezione .text. Okei, questo è l'original entry point. Scrivetevelo che vi servirà dopo. Ora che siete alla riga dell'original entry point il programma è pronto all'esecuzione, perciò mano ad icedump. Prima cosa vediamo le informazioni sulla mappa del programma. In softice battete

map32  nono

ed otterrete la seguente tabella:

 

OWNER OBJ NAME OBJ # ADDRESS SIZE

TYPE

NONO .text 0001 00401000 B12 CODE    RW
NONO .rdata 0002 00402000 448 CODE    RW
NONO .data 0003 00403000 194 CODE    RW
NONO .idata 0004 00404000 60C CODE    RW
NONO .rsrc 0005 00405000 16AC CODE    RW
NONO Geddone! 0006 00407000 3000 CODE    RW

da qui possiamo ricavarci le informazioni sulle sezioni mappate in memoria. Ora vediamo che in address abbiamo dei VA e non RVA. Comunque, ora che il file è decriptato (siamo ancora sull'original entry point ricordate?) ce lo dobbiamo salvare. Quindi vogliamo salvare tutte le sezioni. Potremmo escludere la sezione Geddone!, ma di solito è meglio lasciare tutto come sta. Quindi il nostro eseguibile si estende da 00401000 a 00407000. NO! Attenti! Prima di 00401000 c'è il PE header che ci serve! Quindi dobbiamo sempre calcolare che dobbiamo dumpare a partire da 00400000. Arriviamo fino a 00407000, che è l'inizio della sezione Geddone!, e aggiungiamo il suo size (3000h). Quindi in totale dovremo dumpare 00400000 + 7000 + 3000. Ricordate che le sezioni hanno un loro allineamento, quindi se il size della sezione Geddone! fosse stato ad esempio 2800 avremmo dovuto dumpare sempre fino a 00407000+3000. Le sezioni hanno l'allineamento a 1000, quindi dovete considerare il multiplo di 1000 superiore più vicino al size. Ad esempio, per la sezione .rsrc:

VA: 00405000          SIZE: 16AC

il multiplo di 1000 superiore e più vicino a 16AC è 2000, quindi 00405000+2000 = 00407000. Vabbè, cmq ora sappiamo cosa dumpare, dumpiamolo! In softice con IceDump caricato battete:

/dump 00401000 A000 c:\dump.exe

dove A000 = 7000 + 3000 e dove la sintassi del comando DUMP è:

/dump indirizzo lunghezza file_disco

Evviva! Abbiamo dumpato il nostro crackme. Andiamo a vedere il nostro file dumpato. Umm... Già vediamo che il file non ha la sua icona che aveva prima... Brutta cosa. Poi se eseguiamo il programma crasha. Vediamo di capire quello che è successo. Avendo fatto un dump della mappa in memoria il programma risulta disallienato. Mi spiego. Come vi dicevo prima gli RVA e i FileOffset non coincidono DI SOLITO. Questi coincidono SOLO se il FileAlignment è uguale al SectionAlignment. Di norma il SectionAlignment è a 1000h, mentre il FileAlignment è a 200h. Quando noi dumpiamo il programma dalla memoria, questo viene salvato su disco secondo il SectionAlignment, che è di 1000h, mentre originariamente il FileAlignment del nono.exe era di 200h. Che vuol dire? Che ora il file dump.exe è allineato a 1000h, e ciò vuol dire che anche gli offset delle sezioni sono stati spostati. Ecco quindi che la sezione .rsrc non è più dove indica la section table di dump.exe, quindi l'icona non appare. A questo problema si può ovviare facilmente. Prendiamo il PEditor, carichiamo il dump.exe, andiamo nella Section Table. Col tasto destro fate apparire il menù e scegliete DUMPFIXER. Questo porta automaticamente il VirtualSize = RawSize e il VirtualOffset = RawOffset. Questa procedura si chiama riallineamento, e non è necessaria se avete effettuato il dump con il PEditor. Ora che il file è riallineato ecco che è tornata l'icona. Andiamo ad eseguire il file... e di nuovo crash! Beh ora basta ragionare un pò. Guardate un pò l'entry point: 7001. Azz. Questo è l'entry point che ci porta nel Loader, ma se noi lo lasciamo così il programma riparte dal loader e ridecripta le sezioni scombussolandole! Quindi dobbiamo cambiarlo. L'entry point originale lo abbiamo incontrato quando siamo arrivati al jump eax. Vi avevo detto di scriverlo. Se non ve lo ricordate ve lo rimembro io: 004017D0. Quindi il relativo RVA sarà 17D0. Con il PEditor scrivete il 17D0 nel campo EntryPoint, quindi cliccate su Apply Changes. Okei, ora abbiamo salvato il nostro original entry point. Bene. Fatto questo, torniamo ad eseguire il nostro programma... CRASH! Ancoraaa?? Eh si. Notate che ora il crash è diverso, ci mette un pò più tempo il pc a crashare. Quindi i nostri cambiamenti sono serviti a qualcosa. Ma se abbiamo aggiustato le sezioni e l'entry point perchè crasha? E qui arriva il bello quando cercate di unpackare un programma! Quasi sempre viene manomessa la IT (import table) in modo che dopo un dump risulti compromessa, così quando andiamo a rieseguire il file sono cazzi! Certo ormai le IT le distruggono in una maniera incredibile, ma come annunciato dal crackme ho inserito solo un piccolo trick sulla IT, quindi cerchiamo di capire cosa non va. Di nuovo mano al PEditor, riaprite il vostro dump.exe. Ora andate nella sezione DIRECTORY, e da lì andate a EXPORT. "Export not found". Cazzo sto facendo! Andiamo alla sezione IMPORT,   e li vediamo la tabella con i moduli importati e la lista delle funzioni importate.

 

Dll Name OrigFirstThunk TimeDate Stamp Forwarder Chain Name FirstThunk
MZ 00000000 36F80F55 00000000 00000000 4258
MSVCRT 000041D8 36B69D5D 78000000 00004462 43BC
KERNEL32 00004064 371FC2B3 BFF70000 00004560 4248
USER32 00004220 37456CEB BFF50000 000045F4 4404

Andiamo ad occhio. Cosa c'è di strano?? Il primo modulo! In DLL name troviamo MZ. Ma MZ non è una dll! Perlomeno non era una DLL importata dal nono.exe. Infatti nel crackme non decriptato troviamo nel primo modulo il nome MFC42, che è la dll delle MFC che tanto uso io :-). Poi vediamo anche che sono stati azzerati i campi OriginalFirstThunk, Forwarder Chain e Name. Per una spiegazione dettagliata di tali campi andate a leggervi il mio tutorial sul PEditor. Cmq abbiamo il nono.exe con i giusti valori, quindi basterà copiarceli per tornare al file funzionante. E qui il mio crypter è stato previdente! Dal PEditor  (o da qualsiasi altro editor di PE) non potete modificare i valori dei moduli importati, quindi siete costretti a farlo manualmente. Per fare ciò invece di cercare leimisticamente i bytes del TimeDateStamp per cercare un riscontro dall'hex editor (avrei potuto azzerare pure quelli, quindi non avreste avuto nessun pattern da cercare :-P), vediamo come fare il lavoro nel migliore modo. Prima di tutto ci occorre salvare i corretti valori dal file nono.exe, quindi prendiamoli:

OriginalFirstThunk:  00004074

Name:   0000442C

notate che nel nono.exe i forwarder chain e i timedatestamp sono tutti azzerati, quindi li azzereremo pure nel nostro dump.exe. Okei, ora ci occorre sapere come è fatta e dove si trova la IMPORT TABLE (si si già lo sapete, nel mio tute sul PE :-P). Innanzitutto l'indirizzo RVA della IMPORT TABLE ci è dato dal PEditor, nella sezione directory: la seconda linea contiene i campi:

 

RVA SIZE
00004000 00000064

ora col FLC del PEditor mettiamo nel campo RVA il valore 4000, cliccate su DO, ed otteniamo nel campo offset il valore... 4000! E già, perchè dopo il dump il file ha una allineamento perfetto (FileAlignment = SectionAlignment). Apriamo il nostro HexEditor, cerchiamo l'appena citato offset 4000h. Ecco quello che troviamo.

 

00 00 00 00   55 0F F8 36   00 00 00 00    00 00 00 00
58 42 00 00  
D8 41 00 00    5D 9D B6 36   00 00 00 78
62 44 00 00   BC 43 00 00
   64 40 00 00   B3 C2 1F 37
00 00 F7 BF   60 45 00 00   48 42 00 00  
20 42 00 00
EB 6C 45 37   00 00 F5 BF   F4 45 00 00   04 44 00 00

00 00 00 00   00 00 00 00   00 00 00 00   00 00 00 00
00 00 00 00

mmm non molto chiaro eh? Diamo un senso a queste dwords. Il valore 4000 punta alla import table. La import table è un array di strutture, ogni struttura è un descrittore per un modulo importato. L'ultima struttura è la null ed indica la fine della import table. In particolare la struttura di un descrittore è definita così:

 

IMAGE_IMPORT_DESCRIPTOR STRUC
   OrigFirstThunk       DD ?            puntatore all'array di puntatori ai nomi delle funzioni
   TimeDateStamp      DD ?           data e ora
   ForwarderChain      DD ?           forwader
   NameRva                   DD ?         puntatore al nome della DLL
   FirstThunk                 DD ?          puntatore all'array di Entry Point delle funzioni importate (IAT)
IMAGE_IMPORT_DESCRIPTOR ENDS
IMAGE_IMPORT_DESCRIPTOR_ EQU 5*4

 

quindi 5 dwords ognuna con il suo significato. I bytes della IT scritti prima li ho colorati alternatamente a gruppi di 5 per distinguere i descrittori. L'ultime 5 dwords sono tutte NULL e indicano la fine della IT. Quindi ormai è fatta. Nel primo descrittore prendiamo la prima dword e la sostituiamo con 00004074 e la quarta dword con 0000442C. MI RACCOMANDO LA NOTAZIONE INTEL! Nella prima dword dovete scrivere 04 74 00 00, cioè i byte vanno invertiti, e così per tutte le modifiche che farete. Inoltre per ogni descrittore prendiamo la seconda e terza dword, cioè quelle corrispondenti a TimeDateStamp e Forwarder e le azzeriamo. Questo è importante, perchè dopo un dump la IT è stata cambiata: di solito il PEloader del win assegna automaticamente un valore al Forwarder e riempie la IAT di indirizzi di funzioni. Tutto okei. Il file funziona anche così. Però funziona solo sul vostro computer :-). Infatti se il Forwarder non è NULL il PEloader del win non si ricalcola gli indirizzi di tutte le funzioni importate, ma usa gli indirizzi già presenti nella IAT e calcolati in precedenza. Per il vostro stesso pc va bene, perchè in un pc le api vengono mappate praticamente sempre agli stessi indirizzi, ma se portate il programma dumpato in questo modo su un altro pc il prog non funzionerà perchè gli indirizzi delle api cambiano da pc a pc, quindi la IAT per un altro pc contiene tutti indirizzi sbagliati! Se invece noi azzeriamo il Forwarder, il PELoader prende gli OriginalFirstThunk che sono i puntatori alle funzioni importate, se ne ricava gli indirizzi, e li mette nei FirstThunk (cioè nella IAT). In questo modo qualunque siano gli indirizzi delle funzioni il programma funziona sempre, visto che gli indirizzi vengono ricalcolati ad ogni esecuzione. Ora che abbiamo fatto tutte le modifiche alla import table siamo a posto. Eseguite il vostro DUMP.EXE e il file partirà senza problemi.

Ora che il crackme è un normalissimo programma, non avrete problemi a scovare il jump da modificare per farvi registrare.

Io qui ho descritto la via più grezza, quella dell'IceDump e della ricostruzione. Visto che la IT viene trickata dal loader, è meglio se seguiamo il loader e saltiamo il trickaggio :-). In particolare possiamo anche fare il dump con PEditor, perchè quando dumpa si preoccupa di ripulirci la IT e di allinearci correttamente il file in automatico. Quindi adesso vediamo questo metodo più "elegante".

 

IL DUMPING ELEGANTE

 

Con il dumping elegante noi cerchiamo di evitarci la ricostruzione manuale delle strutture del PE alterate intenzionalmente dal crypter. L'idea è: invece di romperci le chiappe a ricostruire un PE tanto vale che steppiamo il loader e gli facciamo evitare l'alterazione del PE :-) Così al momento del dumping avremo un PE corretto. Bene, iniziamo lo stepping del loader:

 

00407001    pusha
00407002   call 00407007
00407007   pop ebp
00407008   sub ebp, 7
0040700B   mov eax, [ebp+148h]  
prende un rva
00407011   mov ebx, [ebp+158h]  
prende l'imagebase
00407017   add eax, ebx         
li somma e ottiene un VA
00407019   push 0                
parametro per la call
0040701B   call dword ptr [eax] 
chiama GetModuleHandle
0040701D   mov [ebp+1B0h], eax  
salva l'handle del processo del crackme
00407023   mov eax, [ebp+15Ch]  
prende il puntatore alla sezione di codice RVA
00407029   mov ecx, [ebp+160h]  
prende il size della sezione di codice
0040702F   mov ebx, [ebp+158h]  
mette in ebx l'image base
00407035   add eax, ebx         
eax = VA della sezione di codice
00407037   dec ecx               
qui inizia il ciclo per gli ECX bytes della sezione
00407038   mov bl, [eax+ecx]    
prendi l'n-esimo byte criptato
0040703B   not bl                
fagli il NOT
0040703D   xor bl, 37h           
xoralo con 37h
00407040   rol bl, 3             
e quindi ROL 3
00407043   mov [eax+ecx], bl    
metti il byte decriptato al suo posto
00407046   cmp ecx, 0            
sono finiti tutti i byte?
00407049   jnz short loc_407037 
se no, loop
0040704B   mov eax, [ebp+164h]  
prende il puntatore rva alla sezione data
00407051   mov ecx, [ebp+168h]  
prende il size della sezione data
00407057   mov ebx, [ebp+158h]  
prede l'imagebase
0040705D   add eax, ebx         
eax = VA della sezione data
0040705F   dec ecx               
e qui il ciclo per decriptare la sezione dei dati
00407060   mov bl, [eax+ecx]    
prendi l'n-esimo char criptato
00407063   not bl                
fagli un bel NOT
00407065   xor bl, 58h           
xoralo con 58h
00407068   rol bl, 5             
rol di 5
0040706B   mov [eax+ecx], bl    
ripristina il byte decriptato
0040706E   cmp ecx, 0            
i bytes sono finiti?
00407071   jnz short loc_40705F 
se no, continua a decriptare
00407073   mov eax, [ebp+14Ch]  
prende un address RVA
00407079   add eax, [ebp+158h]  
gli somma l'image base
0040707F   lea ebx, [ebp+1A3h]  
ebx punta a KERNEL32.dll
00407085   push ebx              
passiamo il puntatore alla stringa alla funzione
00407086   call dword ptr [eax] 
call LoadLibraryA
00407088   mov edi, eax         
salva l'handle del modulo kernel in edi
0040708A   mov ebx, [ebp+150h]  
prende ancora un rva
00407090   add ebx, [ebp+158h]  
gli somma l'image base
00407096   mov ecx, [ebp+13Ch]  
puntatore al nome di una funzione (GetVersion)
0040709C   add ecx, [ebp+158h]  
e gli aggiungiamo l'image base
004070A2   add ecx, 2            
salta l'HINT della funzione
004070A5   push ecx              
salva il puntatore alla funzione
004070A6   push edi              
salva l'handle del Kernel
004070A7   mov esi, ebx         
salva il VA di GetProcAddress in esi
004070A9   call dword ptr [ebx] 
call a GetProcAddress
004070AB   mov ebx, [ebp+148h]  
mette in edx il puntatore all'rva che è l'indirizzo di GetModuleHandle
004070B1   add ebx, [ebp+158h]  
gli aggiunge l'image base
004070B7   mov [ebx], eax       
salva l'address di GetVersion ottenuto con GetProcAddress al posto dell'address di GetModuleHandle
004070B9   mov ecx, [ebp+140h]  
Prende il puntatore al nome della funzione GetModuleHandleA
004070BF   add ecx, [ebp+158h]  
gli aggiunge l'image base
004070C5   add ecx, 2            
salta l'hint
004070C8   push ecx              
salva il puntatore
004070C9   push edi              
salva l'handle del kernel
004070CA   call dword ptr [esi] 
call a GetProcAddress
004070CC   mov ebx, [ebp+14Ch]  
prende l'rva di LoadLibraryA
004070D2   add ebx, [ebp+158h]  
gli somma l'imagebase
004070D8   mov [ebx], eax       
e salva l'address di GetModuleHandleA al posto di LoadLibraryA
004070DA   mov ecx, [ebp+144h]  
Prende il puntatore al nome della funzione GetStartUpInfoA
004070E0   add ecx, [ebp+158h]  
gli aggiunge l'image base
004070E6   add ecx, 2            
salta l'hint
004070E9   push ecx              
salva il puntatore
004070EA   push edi              
salva l'handle del kernel
004070EB   call dword ptr [esi] 
call a GetProcAddress
004070ED   mov ebx, [ebp+150h]  
prende l'rva di GetProcAddress
004070F3   add ebx, [ebp+158h]  
gli somma l'imagebase
004070F9   mov [ebx], eax       
e salva l'address di GetModuleHandleA al posto di LoadLibraryA
004070FB   mov ebx, [ebp+1B0h]  
prende l'handle del processo del crackme
00407101   mov eax, [ebx+3Ch]   
prende il delta del PE
00407104   add ebx, eax         
ora ebx punta al PE del processo del crackme
00407106   mov eax, [ebx+80h]   
prende l'RVA della IT
0040710C   add eax, [ebp+158h]  
eax = VA della IT
00407112   mov ebx, eax         
copia il VA in ebx
00407114   mov dword ptr [ebx], 0 
   nel primo descrittore azzera il valore OriginalFirstThunk
0040711A   mov dword ptr [ebx+0Ch], 0   
nel primo descrittore azzera il valore NameRva (nome del modulo importato)
00407121   mov dword ptr [ebx+8], 0
   nel primo descrittore azzera il campo Forwarder
00407128   mov eax, [ebp+154h]  
metti in eax l'rva dell' original entry point
0040712E   mov ebx, [ebp+158h]  
ebx = imagebase
00407134   add eax, ebx         
ottieni il VA
00407136   jmp eax               
salta all'original entry point

eccolo qui! Questo è tutto il loader commentato riga per riga. Vedete i due cicli di decrypt, uno per la sezione codice e uno per la sezione data. La cosa interessante è che dopo questi due cicli di decrypt vengono salvati gli indirizzi di tre api (GetVersion, GetModuleHandleA e GetStartupInfoA) al posto dei 3 indirizzi di api usate dal crypter (GetModuleHandle, LoadLibraryA e GetProcAddress). Questo cosa vuol dire? Che il file che abbiamo dumpato non era ancora corretto! A funzionare funzionava, perchè le tre api del kernel hanno un ruolo marginale nel programma, ma rimane il fatto che le api dumpate sono sbagliate. Cioè, mi spiego. Il programma dumpato ha le funzioni importate per il kernel che sono le seguenti:

GetModuleHandle

LoadLibraryA

GetProcAddress

che però sono sbagliate, in quanto abbiamo visto che al posto dei loro address il loader ci va a mettere gli address delle api

GetVersion

GetModuleHandleA

GetStartupInfoA

che vuol dire? Che quando carichiamo il crackme, il PEloader ottiene gli indirizzi delle PRIME TRE API, così il loader del crypter può usarle. Però poi il loader del crypter deve ricordarsi che il programma originale non aveva le prime tre api, ma le SECONDE TRE API, quindi il crypter invece di ripristinare sia i nomi delle funzioni che i loro indirizzi, ripristina solo i loro indirizzi: tanto a runtime i nomi delle funzioni non servono più. Capite?? Il crackme funziona perchè le tre api che gli abbiamo lasciato non gli provocano problemi, ma se un programma usa quelle tre api in modo fondamentale (come quasi tutti i programmi) allora il programma avrebbe crashato nonostante il suo PE fosse tecnicamente perfetto. Beh è un pò bastardello come trick. Quindi quando vi trovate di fronte al leimcrypter se volete risolverlo completamente dovete prendere anche la lista degli OriginalFirstThunk e modificare i primi 3 affinchè puntino alle seconde 3 api invece che alle prime 3. Bene, abbiamo esaminato il loader, abbiamo scoperto questa nuova feature del LeimCrypter, ora vediamo il modo elegante per dumpare il file. Seguite il loader, dopo i cicli di decrypt e dopo il ripristino delle 3 api arrivate al JUMP EAX. Eseguitelo e siete all'entry point del programma originale. Ora noi vogliamo dumparlo col PEditor (o con un dumper qualsiasi), quindi per fare in modo che il programma non prosegua l'esecuzione dobbiamo mettere un jump sull'entry point in modo che risulti:

004017D0  JUMP 004017D0

in questo modo il programma rimane in animazione sospesa, e non altera la sezione di data. Ora potete uscire dal softice (il programma al win apparirà ovviamente bloccato... stupidi timeout), quindi ora col PEditor ci dumpiamo il programma. Ricordate di sostituire all'entry  point del file dumpato l'istruzione originale che c'era prima che metteste il JMP. Ora dovete solo aggiustare l'entry point. E la IT? Avete visto il loader? Vedete i punti in cui alla fine azzera i 3 valori del descrittore della IT? BENE! Noppateli! Così la IT non verrà alterata, e avrete subito un bel dump pronto e funzionante. Beh, direi che è tutto.

Byeeee

AndreaGeddon

 

Note finali

Un saluto a tutta la ML, un grazie a Killexx per il suo ottimo tutorial e per le sue sempre esaurienti spiegazioni :-). Un saluto a Risk e Eaglez che ci siamo incontrati da poco, e pure a ZeroByte e al suo serverino. Il solito saluto all'eccelso Xoa. Ciauz gente.

Disclaimer

Vi ricordo che il LeimCrypt ®© è un marchio registrato ed è protetto da copyleft. Se volete usare la favolosa potenza di questo crypter per più di 5 giorni dovete mandarme li sordi!

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

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