Corso UIC Newbies 09
From UIC
Corso UIC Newbies 09
Contents |
| Infos | |
|---|---|
| Author: | AndreaGeddon |
| Email: | andreageddon@hotmail.com |
| Website: | http://andreageddon.8m.com |
| Date: | 23/03/2001 (dd/mm/yyyy) |
| Level: |
|
| Language: | Italian |
| Comments: | |
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 po' più di tempo per finire il mio LeimCrypt :-P
Ebbene si! Me lo avete chiesto non di rado in chat, ed ora eccovelo qua: un piccolo crypter! È 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.
Tools
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 po' 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: 00007001
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. È 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 po', è 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 po'. Guardate un po' 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 po' 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 | TimeDateStamp | 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ì:
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:
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 po' 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 3affinché 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
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 malevoli 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.