Themida 2.0
From UIC
Themida 2.0 Unpack
Contents |
| Infos | |
|---|---|
| Author: | Root |
| Email: | duat66@gmail.com |
| Website: | none |
| Date: | 21/07/2009 (dd/mm/yyyy) |
| Level: |
|
| Language: | Italian |
| Comments: | Formattazione Wiki: Antelox |
Introduzione
Bene iniziamo a vedere questa protezione di cui avevo sentito parlare e letto qualcosa, ma su cui non avevo ancora voluto addentrarmi perché sapevo che non sarebbe stato semplice.
Tools
Questi sono i tool indispensabili, gli altri li troverete nel corso del tutorial.
Primo avvio ( se si avvia… )
Lanciamo il programma con Olly e StrongOD settato in questo modo:
Diamo uno sguardo iniziale al programma qui per esempio:
Come si vede il loader di Themida non carica la IAT per cui non si vedono le normali call[indirizzo iat] o jmp[indirizzo iat] ma vi sostituisce direttamente l’indirizzo dell’api per cui il dump in queste condizioni non sarebbe utilizzabile al prossimo riavvio o su un altro pc poichè la mappatura delle dll e quindi delle api non è su indirizzi fissi.
Ora diamo un’occhiata alla memoria:
Questa è la memoria che Themida carica per i sui scopi, tra cui vi sono tutte le sezioni necessarie per far funzionare la Virtual machine di Themida, che dovremo Dumpare come sezione unica ( io preferisco cosi che il dump di 6 sezioni ) che poi aggiungeremo al dump finale.
Dump Virtual Machine
Iniziamo proprio dalla VM, riavviamo Olly ed inseriamo un bph su VirtualAlloc ( gli deve pur allocare lo spazio necessario alle sezioni ) al primo Break guardiamo lo stack e vediamo:
Alloca 2000 byte, adesso premiamo Alt+F9 e ci troviamo qui:
In Eax abbiamo l’indirizzo della memoria allocata il cui valore viene salvato alla riga 00624c4d, bene diamo Run ed al break Alt+F9 ci troviamo in questa parte interessante:
Viene allocata la memoria per altre 4 sezioni di cui quella alla riga 006f7593 più grande delle altre ( alloca 125000 byte ), se scorriamo il listato vediamo che dopo aver caricato i dati in queste sezione alloca spazio per un’altra sezione:
Bene, adesso abbiamo tutte le sezioni della VirtuAl Machine di Themida, ricapitolando:
006F754F MOV SS:[EBP+7B58C10],EAX(Vm Handler) --- size 1000
006F756F MOV SS:[EBP+7B58C14],EAX(Vm Entry) --- size 1000
006F7593 MOV SS:[EBP+7B58C1C],EAX(vm Code) --- size 125000
006F766C MOV SS:[EBP+7B50FDD],EAX(Vm Context) --- size 1000
00624C4D MOV SS:[EBP+7B549BA],EAX(Vm Stack) --size 2000
Come facciamo a mettere tutto questo in una sezione e poi fare il Dump della regione interessata? Bhè facile scriviamo un piccolo script come questo che ci aiuta:
Var Vm_Handler
var Vm_Entry
var Vm_Code
var Vm_Context
var Vm_Stack
var Vm_Sec_Count
var bpVirtualAlloc
var bpVmSection
mov Vm_Pointer, 02F00000
mov Vm_Handler, 02F01000
mov Vm_Entry, 02F02000
Mov Vm_Code, 02F03000
Mov Vm_Context, 03028000
Mov Vm_Stack, 03029000
Mov Vm_Sec_Count,0
gpa "VirtualAlloc", "Kernel32.dll"
mov bpVirtualAlloc, $RESULT
bphws bpVirtualAlloc
Main_VM:
run
cmp eip,bpVirtualAlloc
jne Main_VM
inc Vm_Sec_Count
rtu
jmp Fix_Vm
Fix_Vm:
Vm Stack:
Cmp Vm_Sec_Count,1
jne Vm Pointer
find eip, #8985????????#
mov pbVmSection,$RESULT
bphws pbVmSection
run
Mov eax, Vm_Stack
bphwc pbVmSection
jmp Main_VM
Vm Pointer:
Cmp Vm_Sec_Count,2
jne Vm Handler
Mov eax, Vm_Pointer
jmp Main_VM
Vm Handler:
Cmp Vm_Sec_Count,3
jne Vm Entry
Mov eax, Vm_Handler
jmp Main_VM
Vm Entry:
Cmp Vm_Sec_Count,4
jne Vm Code
Mov eax, Vm_Entry
jmp Main_VM
Vm Code:
Cmp Vm_Sec_Count,5
jne Vm Context
Mov eax, Vm_Code
jmp Main_VM
Vm Context:
Mov eax, Vm_Context
bphwc bpVirtualAlloc
Lo script è abbastanza semplice l’unica cosa che non ho inserito nello script perchè complicava le cose inutilmente è quello di far allocare la memoria necessaria per le sezioni e poi mostrarmi quale indirizzo aveva utilizzato ( chi vuole può pure farlo… ).
Quindi per farlo funzionare basta utilizzare il Plug-In Memory Manager ed al System Break Point ( poichè dopo potremmo non avere la disponibilità dell’indirizzo di memoria che vogliamo usare ) agire in questo modo:
L’indirizzo è stato scelto a caso invece per la lunghezza è la somma della grandezza delle 6 sezioni + un pochino di spazio libero ( non si sa mai… ).
OEP
Per trovare l’Oep basta mettere un bph su GetProcessHeap ed al secondo break mettere un bpm on access sulla prima sezione e siamo sull’Oep . Come mai si è scelto questa Api? Vi chiederete ( ma anche no…haha ) , basta mettere un bph su GetProcAddress dopo che ha caricato le +6 sezioni della Vm e vedrete quali api carica perche servono al loader. Noterete che carica rtlAllocateHeap ( su Vista ) e questa è quella che ci fa steppare di meno , anzi per niente ( anche se non è sempre cosi ):
Il nostro Oep è 005154e8, adesso però bisogna mettere in ordine la IAT e le call e jmp che non vi fanno riferimento come abbiamo visto all’inizio.
Fix Jmp&Call
In alcuni tutorial troverete che per sistemare la IAT a questo punto molti usano il tools UIF che carica la IAT o in una nuova sezione o dove c’è dello spazio libero sul file e fixa tutte le call e jmp, il problema che ho riscontrato con questo software e che la vm in alcune parti cerca le api all’indirizzo originale e non trovando nulla di valido va in errore, vabbè direte per una facciamo manualmente, e se invece fossero dieci? O se fossero in parti particolari del programma? Quindi la soluzione migliore e sistemare le cose a monte cioè dove Themida scrive quelle call e jmp , vediamo come procedere:
Inseriamo un bph come vedete nell’immagine sulla prima call che troviamo e riavviamo il Olly.
Al primo break se guardiamo a quell’indirizzo vediamo che vi scrive dei nop:
+ un nop prima ( per rispettare il size dell’istruzione originale... e per una sua verifica, ed a noi fa molto comodo… )
Al secondo break ci troviamo in queste condizioni:
L’istruzione a 0079a532 stos byte [edi] inserisce il byte E8 ( call ) in edi, cioè nella nostra locazione di memoria. Adesso steppiamo con F8 e vediamo quando inserisce l’indirizzo della api. Se si trattava di un jmp inseriva E9 invece che E8:
Come vedete in questo punto scrive l’indirizzo o meglio il dispacement ( distanza ) dell’api. Continuiamo a steppare e controlliamo se notiamo qualche riferimento all’indirizzo della IAT che a noi serve per poterlo prelevare ( ci deve essere per forza ) quindi troviamo questo:
Scrive l’indirizzo della funzione nella IAT originale. Bene abbiamo tutto quello che ci serve in questo caso poiché la IAT non viene reindirizzata ( ma le cose non si complicavano di molto, bisognava cambiare qualche altra cosa sempre in questa routine ).
Adesso completiamo il nostro script con le informazioni trovate:
var Vm_Pointer
Var Vm_Handler
var Vm_Entry
var Vm_Code
var Vm_Context
var Vm_Stack
var Vm_Sec_Count
var bpVirtualAlloc
var bpVmSection
// variabili per la IAT
var bpAddr_Api_Iat
var bpFix_Call_Jmp
var _eax
var oep
var iat_api_addrs
mov Vm_Pointer, 02F00000
mov Vm_Handler, 02F01000
mov Vm_Entry, 02F02000
Mov Vm_Code, 02F03000
Mov Vm_Context, 03028000
Mov Vm_Stack, 03029000
Mov Vm_Sec_Count,0
mov bpAddr_Api_Iat, 0079A41B // 0079A41B MOV DS:[EAX],ECX
mov bpFix_Call_Jmp, 0079A532 // 0079A532 STOS BYTE PTR ES:[EDI]
mov oep, 005154e8 // oep
gpa "VirtualAlloc", "Kernel32.dll"
mov bpVirtualAlloc, $RESULT
bphws bpVirtualAlloc
Main_VM:
run
cmp eip,bpVirtualAlloc
jne Main_VM
inc Vm_Sec_Count
rtu
jmp Fix_Vm
Fix_Vm:
Vm Stack:
Cmp Vm_Sec_Count,1
jne Vm Pointer
find eip, #8985????????#
mov pbVmSection,$RESULT
bphws pbVmSection
run
Mov eax, Vm_Stack
bphwc pbVmSection
jmp Main_VM
Vm Pointer:
Cmp Vm_Sec_Count,2
jne Vm Handler
Mov eax, Vm_Pointer
jmp Main_VM
Vm Handler:
Cmp Vm_Sec_Count,3
jne Vm Entry
Mov eax, Vm_Handler
jmp Main_VM
Vm Entry:
Cmp Vm_Sec_Count,4
jne Vm Code
Mov eax, Vm_Entry
jmp Main_VM
Vm Code:
Cmp Vm_Sec_Count,5
jne Vm Context
Mov eax, Vm_Code
jmp Main_VM
Vm Context:
Mov eax, Vm_Context
bphwc bpVirtualAlloc
jmp Fix_Iat
Fix_Iat:
bphws bpAddr_Api_Iat
bphws bpFix_Call_Jmp
bphws oep
eob MainFixIat
run
MainFixIat:
cmp eip, bpAddr_Api_Iat
je h_Addr_Api_Iat
cmp eip, bpFix_Call_Jmp
je h_Fix_Call_Jmp
cmp eip, oep
je Fine
run
h_Addr_Api_Iat:
mov iat_api_addrs, eax
sti
run
h_Fix_Call_Jmp:
mov _eax, eax
and _eax, 000000ff
cmp _eax, 0e8
je Fix_call
cmp _eax, 0e9
je Fix_jmp
run
Fix_call:
sub edi,1
mov [edi], #ff150000#
add edi, 2
mov [edi], iat_api_addrs
mov edi, 61b800
add eip, 1
run
Fix_jmp:
sub edi, 1
mov [edi], #ff250000#
add edi, 2
mov [edi], iat_api_addrs
mov edi, 61b800
add eip, 1
run
Fine:
Bphwc bpAddr_Api_Iat
bphwc bpFix_Call_Jmp
bphwc oep
msg "OEP raggiunto."
ret
Diciamo che l’unica cosa da precisare sullo script è quel ‘mov edi ,61b800’. Questo viene inserito per evitare che le nostre modifiche vengano sovrascritte quindi si sceglie un indirizzo vuoto ed accessibile per far scrivere al loader le sue istruzioni.
Bene adesso possiamo provare il tutto. Togliamo tutti i bp e riavviamo Olly al System BreakPoint inseriamo la nostra sezione con Memory Manager e avviamo lo script; dopo un po’ il risultato è questo:
Tutto ok sembrerebbe, vi è solo un’api non risolta, ma basta andare all’indirizzo segnato per capire che si tratta di wsprintfA
Quindi la IAT inizia a 53900 e finisce a 539cf0, quindi dump del file dump della sezione della vm. Avviamo ImpRec mettiamo a posto la IAT ed aggiungiamo la sezione della vm in questo modo:
Come avrete notato io ho eliminato due sezioni che non mi servono più, ho rinominato qualche sezione ed ho sistemato il VA ( naturalmente ) della sezione della VM. Molto bene, adesso direte, è tutto sistemato…? Io vi dico no, manco per niente il programma va subito in errore all’avvio…uffffff adesso viene tutta la parte dei vari trick AntiDump.
AntiDump
Se adesso facciamo partire il programma dumpato va miseramente in errore ma cominciamo a guardare perché. Guardiamo la differenza che salta subito all’occhio quando siamo sull’OEP:
Come notate lo stack è sfalsato, ma non solo, la dword che si trova a 0012ff88 viene utilizzata come metodo antidump. Se inseriamo quella dword in quella posizione nel nostro file il programma parte ( da altri errori che vedremo dopo ) ma il controllo viene superato. Si potrebbe pensare di inserire quella dword con del codice prima di arrivare all’OEP ma appena riavviamo vedremo che và in errore nuovamente e nell’originale quella dword sarà diversa, allora che facciamo? Bhe io ho fatto il trace fino all’errore e poi mi sono salvato il file per analizzarlo con UltraEdit, ho messo una dword qualsiasi nella posizione 12ff88 e poi l’ho cercata nel trace per vedere di capire ed ho trovato questo, come dword ho inserito FFF5870A:
In pratica fa lo xor tra la dword a 0012ff88 e la dword a 0012ff8c, molto bene allora noi che facciamo ? Noi avviamo l’originale e facciamo lo xor tra la dword a 0012ff88 e quella a 0012ff8c per noi il risultato rappresenta la costante ( anche per Themida ) e prima di arrivare all’OEP lo xoriamo con la dword che si trova a 0012ff8c praticamente inseriamo questo:
Ancora il programma va in errore; un altro fix facile da trovare.
E’ il controllo relativo al Header del PE. A noi basta mettere un bpm on access sulla sezione del PE per vedere cosa modificare . Themida non controlla tutti i campi dell’header del PE ma soltanto il campo AddressOfEntryPoint che sul programma originale quando si trova sull’OEP è settato a 0 e noi faremo lo stesso in questo modo:
La funzione VirtualProtect non è presente nella IAT del programma, noi ci ricorderemo di aggiungerla prima di dumpare il file e prima di avviare ImpRec ( aumentando anche la lunghezza della stessa ) in questo modo:
Proviamo ad avviare il programma ma ancora non và. Prima di procedere una piccola nota per dire che in questi trick in effetti non vi è proprio nulla di complicato ma è stata solo fatta una buona opera di mascheramento niente di più ( il trucco più vecchio del mondo, nascondere le cose ). Anche per i prossimi trick utilizzo il run trace per trovare le parti da sistemare perché, se usato bene, aiuta molto, logicamente si deve cercare di tracciare il meno possibile poichè la VM è ricorsiva si arriva facilmente a trace di 40, 50 Mb ma con un po’ di astuzia si riesce a risolvere questi trick guardando dove l’originale e il dump prendono valori diversi:
Come si vede prende il valore contenuto in 0061cff9 che nel caso del dump è 77099433 invece nel caso del file originale corrisponde a LoadLibraryA quindi noi sistemiamo così:
Poi noteremo che ogni tanto va in errore in questa call:
Che nell’originale corrisponde alla funzione Sleep. Per fixarlo basta vedere da dove preleva l’indirizzo, in questo caso da qui:
Noi sistemiamo in questo modo:
Quindi ricapitolando le modifiche che dobbiamo effettuare prima di arrivare all’entry point sono queste:
Ok siamo quasi alla fine mancano altri due fix, che non credo dipendano propriamente da Themida ma da come è strutturato il programma, anche se Themida fa del suo meglio per incasinare le cose. Si tratta di trovare due costanti:
Un fix è questo, nella locazione 0061dffd si trova un puntatore ad una sezione non presente nel dump ( mica possiamo dumpare tutte le sezioni ) a sua volta questo puntatore punta ad una costante che Themida utilizza quindi noi sistemiamo così:
Come avrete notato nei casi più ostici io inserisco i trace del dump e dell’originale nello stesso file cosi mi riesce più facile capire le cose.
Adesso manca un ultimo fix che io ho sistemato cosi:
Per trovare la costante ho sempre utilizzato il metodo del run trace anche se in questo ultimo caso vi sono degli xor che confondono un pochino le cose.
Ok adesso abbiamo finito ed il file funziona perfettamente:
Logicamente il file è solo unpackato... ( solo questo era il nostro scopo… )
Se in Themida cambieranno alcuni trick noi in ogni caso avremo gli strumenti per provare a risolvere senza stare li ed affidarsi a cose che ti dicono gli altri. Io ho cercato di risolvere le cose senza tener conto di tutto quello scritto in giro perché altrimenti si corre il rischio di rimanere bloccati non appena quelli della Oreans cambiano una virgola.
Nota: Nei trace se vi trovate in difficoltà e non riuscite a capire cercate sempre [Edi+70] Edi è il VM_Context ed a +70 si trova eFlags, se il programma prende una strada diversa lo vedete subito da qui ,quindi andate a ritroso nel trace.
Ciao.
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.