Easy
CD-DA Exctractor 7.5.0 build 1
(vestirsi
della corteccia di Armadillo)
|
Data
|
by
Zarathustra
|
|
14/11/2004
|
UIC's
Home Page
|
Published by
Quequero
|
Ci sono 10 tipi
di persone, quelli che capiscono i numeri binari...
|
Grazie
tante zara, ottimo tute
|
e
quelli che non li capiscono.
|
....
|
- Per
qualunque chiarimento contattatemi sul forum della UIC
|
....
|
Difficoltà
|
( )NewBies (X)Intermedio
(X)Avanzato ( )Master
|
|
-
- Questo e' il primo tutorial della mia vita, spero che
lo troviate interessante e soprattutto ne possiate trarre spunti per
approfondire la tecnica che ho utilizzato per squoiare armadillo da questo
target. Non a caso il titolo da me scelto indica proprio come sia possibile
integrare parte del codice di armadillo per l'abbattimento di una delle piu'
ostiche trappole tese al reverser da questo animaletto fastidioso: le nanomites
(battezzate alla UIC da Saturn con il nome di nanometti). Io personalmente
mi sono divertito tanto in questo mio primo 'orgasmo informatico'.
-
Ma bando alle ciancie ed iniziamo con le cose serie.
Un punto sicuramente importante e' da dove parte questo tutorial. Dato che mi
sembra fuori luogo oltreche' ripetitivo illustrare quanto fatto da altri in
altra sede a proposito delle n-mila protezioni introdotte nelle ultime versioni
di armadillo, non spieghero' nei minimi dettagli queste ultime ma mi limitero'
a dire come bypassarle. Piuttosto mi soffermero' sul mio metodo di fissare una
di queste protezioni: i nanometti. - Il
tutorial e' volutamente non per newbies. Illustrare per filo e per segno tutti
gli schemi di protezione contro l'unpacking di armadillo richiederebbe molto
tempo. L'ho scritto principalmente per tenere traccia del lavoro che ho svolto
in questo periodo e perche' possa servire come base di partenza per studi piu'
approfonditi sull'argomento. In ogni caso se qualcosa non fosse chiaro allora
contattatemi sul forum della UIC cerchero' di rispondervi prima possibile.
Ancora, vi consiglio di scaricare subito il programma dal sito perche' il
tutorial e' legato alla versione corrente in maniera molto pesante.
-
-
-
-
OllyDebug
1.10 + plugins -
ImpRec
-
LordPE Deluxe
- WinHex
11.xx
- The brain
(non mi risulta possibile inserire qui il collegamento a questo tool, l'ho
perso subito dopo aver scritto il tutozzo...)
- Lettore CD con su 'CENTURY CHILD'
dei Nightwish
Il tutorial preve una certa dimistichezza con l'uso
dei suddetti tools. Daro' per scontate certe cose, come cosa e' una IAT, come e'
strutturato un PE, come si puo' aggiungere una entry alla IAT o una sezione ad
un eseguibile o, ancora, come riallineare le sezioni di un eseguibile dumpato
(tutte operazioni che comunque sono rese molto piu' semplici con i tools a nostra disposizione).
- www.poikosoft.com
- Il target in esame e' un programma che serve per
effettuare il grab delle tracce audio di un cd, ma permette anche di effettuare
conversioni di file audio nei formati piu' disparati. In realtà non l'ho
provato in tutte le sue funzionalità, il punto importante e' che il programma
e' stato protetto con una delle piu' recenti versioni di armadillo.
- La procedura seguita normalmente quando
si ha a che fare con un programma compattato/criptato e' quella di identificarne
il packer e vedere se esiste già una cura. Usando PeID ci si accorge subito
che il target fa uso di armadillo (cosa deducibile da tanti altri indizi come
il nag screen iniziale ed il reminder in pieno stile armazio). Ad ogni modo
PeID fallisce nel determinarne la versione esatta, ed io personalmente non
mi sono impegnato piu' di tanto nel cercarla (anche se devo ammettere che
la tecnica utilizzata da Mephisto per la ricerca della versione di armadillo,
in questo caso non funziona).
Prima di iniziare va ribadito un punto importante: armazio introduce
parecchio overhead nei programmi che protegge. Tale overhead si traduce nella
compilazione di tabelle e nella scompattazione di sezioni di dati/codice.
L'approccio che ho seguito non e' ottimo, nel senso che il dump ottenuto prescinde
dal cercare di riciclare il codice di armadillo per fargli creare tali sezioni
automaticamente anche nel programma dumpato. Il dump ottenuto mira ad essere
prima funzionante e poi ottimo. Ecco perche' le tabelle ed il codice di cui
sopra sono praticamente incluse nel dump stesso in nuove sezioni dell'eseguibile.
Tale scelta e' stata motivata dal fatto che mi premeva tirare fuori al piu'
presto un tutorial sui nanometti. L'approccio seguito e' questo: dumpare e
fissare il dumpato dandogli cio' di cui ha bisogno. Cio' richiede una attenta
opera di debug e di confronto del tracing del programma orginale e del
dumpato.
I passi che seguiremo nell'ottenimento del nostro dump funzionante sono i
seguenti:
1) Trovare l'OEP del programma.
2) Aggiustare la IAT del programma che armazio ha vilmente ridotto ad un
colabrodo.
3) Fissare i salti ad una sezione del packer generata dinamicamente dal fetido.
4) Fissare i nanometti tramite l'installazione di un
SEH ad esso dedicato che tuttavia fa uso del codice di arma stesso.
Per capire cosa sta dietro al punto 1) un must sono sicuramente i tutes di
ricardo narvaja & c. Se non avete mai sentito parlare di come trovare
l'entry point effettivo di un programma protetto con armadillo allora dovete
necessariamente leggere uno dei suddetti tutorials.
-
Per una comprensione del meccanismo di protezione Debug Blocker di armadillo
e di come i due processi creati quando viene usato questo metodo di
protezione interagiscono si rimanda ai due tutorials di Faina sulla UIC.
Da qui in avanti ci si riferira' con il nome di processo padre al processo
debugger, e con il nome di processo figlio al processo debuggato di cui nei
suddetti tutorial. Prima degli studi di Faina (esimio collega), si
riteneva a torto che non si potesse effettuare il detach dal padre (debugger)
del processo figlio (debugged), se non lavorando su WinXP ed utilizzando la API
DebugAcriveProcessStop. In realtà Faina ha scoperto il metodo della
OpenMutex/CreateMutex secondo cui il processo figlio puo' partire senza
necessariamente avere a che fare con il padre (almeno per quanto riguarda la
parte iniziale). Questo apre interessanti e rapide prospettive per trovare
l'entry point del programma anche se il programma utizza la protezione Debug
Blocker. In effetti il nostro target utilizza tale protezione e di cio' ce se
ne puo' rendere conto considerato che nel Task Manager ci sono due processi
attivi con lo stesso nome, pur avendo lanciato una sola copia del programma.
In questo tutorial si
utilizza OllyDebug come debugger. E' importante assicurarsi di avere installati
almeno i seguenti plugins: OllyDump,HideDebuggerm CommandLine. Questi plugins,
che potete trovare facilmente in rete nelle loro ultime versioni sono indispensabili
soltanto per seguire questo tutorial passo passo, ma non sono strettamente
necessari alla fine della risoluzione del nostro problema.
Dopo
avere installato i precedenti plugins apriamo il programma target con Olly.
Assicurarsi che tutte le eccezzioni vengano passate al programma da Olly.
Se nel corso del run se ne presenteranno altre aggiungerle alla lista, almeno
in questa prima fase.
-
Con una Search for ->
intermodular calls (tasto destro del mouse sul codice assembly) cerchiamo
le chiamate alla OpenMutexA di kernel32.dll.
Due sono i punti in
cui tale procedura viene chiamata : 0x74ec9a e 0x74f09c
Per bypassare la protezione
DebugBlocker e' sufficiente invertire i salti dopo la test eax,eax che si
trovano immediatamente a tali call. In questo caso basta fare :
-
-
0074ECA2 74 04 JE SHORT ezcddax.0074ECA8
diventa -> JNE SHORT 74ECA8
0074F0A4 0F85 98020000 JNZ ezcddax.0074F342 diventa
-> JZ 74f342
|
Dopo cio' se il programma (non fatelo) venisse fatto partire il processo che
verrebbe avviato sarebbe il processo figlio e non il padre. Per trovare l'entry
point del processo figlio basta mettere un breakpoint sulla API CreateThread e
trovare quando dopo che un po' di sezioni di codice vengono scompattate il
punto in cui si ha il salto alla sezione di codice effettiva del programma.
Pertanto mettiamo un bp sulla call a CreateThread (bp CreateThread sulla
commandLine di Olly). Runniamo con F9 (se si prensentano delle eccezzioni
passiamole al programma con Shift+F9) e la terza volta che breakkiamo
sulla CreateThread, siamo in prossimità dell'entry point. Ritornando al codice
del main thread (CTRL+F9) un po' piu' giu' c'e' una CALL EDI, tale
chiamata porta dritto dritto all'entry point del programma. Nel nostro caso la
abbiamo :
-
-
020BA42F FFD7 CALL EDI ; ezcddax.00401780
|
Quindi l'entry point del programma e' proprio questo:
0x401780
Siamo
pronti per effettuare il dump del programma. Useremo il plug-in di Olly
(ollydump) per effettuare il dump a partire dall'entry point dove dovremmo
trovarci a questo punto(ricordo che si trova all'indirizzo 0x401780). Prima di effettuare
il dump e' possibile mettere a posto un paio di cosette
che sono state messe male. In particolare il PE Header del
programma (che intuitivamente si trova all'indiirizzo 0x400000) presenta qualche problema. Di cio' ci si puo'
rendere conto andando all'indirizzo 0x400000 nella finestra di dump
e selezionando l'opzione Special-> PE Header. Tale opzione ci fa vedere
i bytes del dump con un layout di tipo PE
Header, quindi se qualcosa va storto ce ne accorgiamo subito. In effetti l'offset alla PE Signature
e' sbagliato (0x3DF142). Per trovare l'offset corretto facciamo Search For -> Binary
String nella finestra di dump e cerchiamo la stringa 'PE' ovvero la
sequenza esadecimale 50 45 . L'offset giusto e' 0x200. Sostituiamo tale valore al 0x3DF142
che troviamo all'indirizzo 0x40003C. Adesso tutta la PE Signature viene
effettivamente visualizzata da Olly in maniera corretta. Bene ora possiamo fare
il dump con ollydump. Assicuriamoci che la casella di spunta "rebuild import"
sia deselezionata (la IAT la fisseremo con imprec) e che la "Fix Raw Offset ...
" sia selezionata (quest'ultima opzione consente di avere un dumpato
con le sezioni già allineate, deselezionando questa opzione l'operazione di
riallineamento va fatta manualmente). Otteniamo cosi' il dumpato (che salveremo
come dumped.exe) che ovviamente per forza di cose non funzionerà. Lasciamo
aperto Olly con il programma a 0x401780
-
- Le sezioni del dumpato devono essere opportunamente
allineate. Apriamo il dumpato con il PeEditor di LordPE ed assicuriamoci che
tutti gli header delle sezioni siano tali che VSize = RSize e Voffset
= ROffset (queste dovrebbero gia' essere state allineate da Olly). Oltre a
cio' per evitare sgradevoli messagebox da parte di Olly nel prosieguo, mettiamo
a posto la BaseOfCode (0x1000). Se abbiamo fatto le cose per bene windows
dovrebbe mostrare correttamente l'icona del programma. Adesso fisseremo la
IAT. Infatti runnando il dumpato non parte manco a mazzate. Il fixing della
IAT prevede tre passi:
- 1) Localizzazione della IAT nel programma.
- 2) Patching del programma originale per l'ottenimento
di una IAT papabile.
- 3) Sostituzione o aggiunta della IAT papabile
al programma dumpato.
-
- Per questi punti lo strumento principe e'
imprec. Lanciamo imprec e selezioniamo il programma attualmente debuggato
con olly. Per far cio' selezioniamo tale programma in imprec in "attach
to active process". Sostituiamo l'OEP con 0x1780 e tramite IAT autosearch
abbiamo trovato la IAT del programma. Ora e' evidente che la IAT trovata e'
completamente bucherellata dato che ci sono un sacco di funzioni unresolved
(le entries della IAT le si ottengono tramite il pulsante GetImports) tutte
le altre appartengono solo a kernel32.dll (strano no!). Bisogna patchare il
programma originale per ottenere una IAT decente. E' cio' che faremo. Segnamoci
l'offset della IAT trovata con ImpRec (0x0026A560).
- Da questo momento riavvieremo un paio di
volte il programma ed ogni volta bisogna patchare la OpenMutex di cui prima.
Olly fortunatamente mantiene traccia delle patches applicate pero' vanno di
nuovo applicate. Quindi riavviamo il programma con Olly (debug->restrart)
e con Ctrl-p applichiamo di nuovo le patch (assicuratevi che siano "active").
Mettiamo anche un HW Breakpoint on write sull'indirizzo (0x0026A560+ 0x400000
= 0x66A560). Otteniamo cosi' il punto dove viene scritta la IAT.
- Il punto dove avviene la scrittura della IAT
e che si ottiene la seconda volta che il programma brekka e' il seguente:
-
-
-
020B722E MOV EAX,DWORD PTR SS:[EBP-37FC] ; ezcddax.0066A564 020B7234
83C0 04 ADD EAX,4
- 020B7237 MOV DWORD
PTR SS:[EBP-37FC],EAX
- 020B723D JMP 020B6F10
- 020B7242 FF15 9C020C02
CALL DWORD PTR DS:[20C029C] ; KERNEL32.GetTickCount
|
-
-
Come suggerito da SaturN sul forum per ottenere una IAT corretta bisogna guardare
un po' prima e patchare un jnz
-
-
-
020B70A5 FFB5 54C2FFFF PUSH DWORD PTR SS:[EBP-3DAC]
- 020B70AB FF15 5C030C02
CALL DWORD PTR DS:[20C035C] ; MSVCRT._stricmp
- 020B70B1 59 POP ECX
- 020B70B2 59 POP ECX
- 020B70B3 85C0 TEST EAX,EAX
- 020B70B5 75 11 JNZ SHORT
020B70C8 --> diventa Jmp
|
-
La patch indicata tuttavia non potra' essere applicata subito ma e' necessario
far ripartire il programma perche' le prime entry della tabella sono ormai
state scritte. Quindi mettiamo un hw breakpoint alla locazione 0x020B70B3.
Inoltre bisogna mettere un hw breakpoint un po' piu sotto
- dopo che tutta la IAT e' stata scritta, cio'
ci serve per evitare che il programma vada a nanna a causa di un CRC
che viene fatto subito dopo. Il punto dove mettere il secondo hw breakpoint
e' all'indirizzo 0x20B7337.
- Adesso possiamo riavviare il programma con
Olly. Quando il programma si ferma su 0x20B70B3 patchiamo come su indicato,
togliamo questo hw breakpoint e continuiamo. Il programma si ferma sul secondo
breakpoint : 0x20B7337.
- Se andiamo a guardare all'indirizzo della
IAT essa e' corretta, o quasi!! Ci sono ancora delle fake entries che separano
le entries buone. Pero' non e' un problema. Con imprec apriamo il programma
attualmente debuggato, inseriamo 1780 nell'OEP, premiamo IAT autosearch. Seguiamo
il consiglio di ImpRec -> cioe' mettiamo RVA = 0x0026A000 e size 0x4000
- Otteniamo cosi' la IAT corretta.
- Fissiamo il programma dumped.exe con FixDump
(aggiungendo una nuova sezione all'eseguibile).
- Proviamo ad eseguire il programma dumpato
e fissato e vediamo cosa succede: dannazione, non si avvia! Il motivo?
- Un'altra delle protezione di armadillo! I
salti ad una sezione del packer generata dinamicamente.
- Illustrero' di seguito un modo per fissarli
che e' consistente con quanto detto all'inizio a proposito della metodologia
utilizzata in questo tute.
- Ci sono tuttavia altri modi per risolvere
il problema (v. Tutorial di Eggi).
- A questo punto dovreste avere ancora la copia
del programma aperta con olly che si trova all'oep. Non chiudetela!
- Avviamo il programma dumpato con olly, e
deselezioniamo le access violation tra le eccezzioni passate al progamma.
Dopo avere avviato il programma
troveremo una violazione di accesso ad un indirizzo che nel mio caso e' 0x3000000,
dico nel mio caso perche' in realta' la sezione dove avvengono i fantomatici
salti viene generata dinamicamente. Bene', il programma vuole tale sezione?
Allora diamogliela. Dumpiamo nella prima istanza di olly tale sezione. Con
Alt+M vediamo la mappa di memoria, selezioniamo la sezione che ci interessa
e dumpiamola (tasto destro sulla sezione e dump). Salviamola su un file (backup->
save data to file).
- Chiudiamo l'instanza di olly con cui abbiamo
aperto il dumped.exe. E apriamo dumped.exe con il peeditor di lordpe aggiungendo
la sezione caricata da disco. Assicuriamoci inoltre che tutto sia a posto
giusto. In particolare, la sezione viene aggiunta alla fine del dumpato, subito
dopo la iat che ha creato imprec. E' importante cambiare la virtualsize della
sezione creata da imprec al valore corretto (allineata) e inoltre e' importante
che il virtual offset della nuova sezione sia pari a 0x3000000 (nel mio caso)
- 0x00400000.
- Se abbiamo fatto le cose per bene, provando
ad avviare il programma non partira' ancora, ma apparirà una graziosa MessageBox
che ci informa del fatto che si e' incontrata una eccezzione di tipo int3.
- Eccoci finalmente al piatto forte di questo
tutorial: le nanomites o nanometti volgarmente detti. I nanometti sono delle
istruzioni int3 piazzate nel codice del programma orginale al posto di salti
condizionali e non, che vengono risolte run time dal processo padre. Praticamente
quello che fa il processo padre e' di debuggare il processo figlio e ogni
volta che si presenta un evento di debug che e' un int3, ricava il contesto
del processo figlio da cui risale a come alterarne il Program Counter per
farlo riprendere dal punto corretto. Nel corso del tempo tale protezione e'
andata via via migliorandosi, all'inizio tali int3 erano risolti direttamente
attraverso una tabella ed addirittura corrispondevano a salti non condizionati.
Poi sono stati aggiunti come salti condizionati, poi e' stata introdotta la
criptazione delle informazioni che permettono di risalire al punto dove saltare
data una int3. Tale evoluzione non ha permesso di elaborare dei metodi fissi
nella risoluzione dei nanometti. Inizialmente quando alla UIC abbiamo cominciato
ad affrontare il problema ci siamo subito accorti della mancanza di un metodo
generale per la risoluzione dei nanometti oltreche' delle grosse difficoltà
di reversare l'algoritmo che sta dietro alla crittazione/decrittazione delle
tabelle (cio' non e' impossibile, ma se cambiano il come generare le tabelle
siamo al punto di prima). La cosa che mi era sembrata piu' ovvia era quella
di effettuare un log del contesto (o delle parti piu' interessanti di esso,
quali l'eip e i registri di flags ed ecx) del processo figlio, prima e dopo
che un nanometto fosse risolto. Ho anche codificato un tools che patcha il
programma dumpato cercando di capire quali sono le istruzioni di salto "rubate"
al programma originale. Tale tool tuttavia presentava grosse limitazioni legate
all'environment in cui un programma viene eseguito ed ai flussi seguiti dal
programma stesso. Era necessario un approccio piu' sistematico ed esaustivo.
Dopo un po' di rimurginamento mi decisi a codificare un SEH che risolvesse
le nanomites sfruttando lo stesso codice del processo padre che guarda caso
abbiamo gia' nel dumpato! Per chi si fosse sganciato brevemente: il programma
da noi dumpato e' un singolo processo costellato di int3. Per risolverle o
le si sostituisce con le istruzioni originali, cosa abbastanza difficile da
trovare per i motivi su esposti, oppure? Oppure ad ogni int3 mettiamo una
chiamata al codice originale del processo padre (che guarda caso sta già nel
dumpato) che le risolve. Come fare una chiamata ad una funzione a partire
da una int3? semplice, installando un SEH, cioe' un gestore delle eccezzioni
che guarda l'eip dove e' stata incontrato l'interrupt e restituisce il controllo
del processo al punto corretto. Il punto piu' naturale dove collocare il nostro
SEH e' proprio dove il processo padre effettua tale elaborazione. Ogni volta
che il processo padre deve gestire un evento di debug, guarda se si tratta
di una int3 e se e' cosi' recupera il contesto del figlio con la GetThreadContext,
risolve il nanometto, riaggiusta il contesto del figlio con la SetThreadContext
e lo fa ripartire dal punto giusto. Il SEH deve riciclare il codice del padre
che sta subito dopo la chiamata a GetThreadContext, pertanto lo metteremo
proprio in quel punto. Cerchiamo con Olly tutte le intermodular calls e nella
lista trovata verranno visualizzate alcune GetThreadContext.
- Quella corretta e' quella che si trova a
0x75468d. Questo si vede perche' un po' piu' sotto si trova la chiamata a
SetThreadContext. Poiche' assembleremo un po' di istruzioni con Olly prima
dell'utilizzo del codice effettivo dopo la GetThreadContext, dovremmo metterci
un po' prima della GetThreadContext. Cio' in realtà non e' necessario perche'
armadillo e' talmente stupido da inserire spesso del codice di offuscamento
che e' del tutto inutile e che pertanto sovrascriveremo tranquillamente :-)))
In particolare si vede subito che il codice che va da 0x754693 a 0x7546d6
e' perfettamente inutile! pertanto selezioniamolo con olly e noppiamolo cosi'
da avere una lavagna bianca su cui divertirci a fare i nostri disegni.
- Va da se cmq che il codice che assembleremo
starà un pò prima perche' le istruzioni che ci servono sono tante. A conti
fatti un buon punto da dove iniziare ad assemblare e' l'indirizzo 0x754675
-
- Prima di proseguire con la codifica del SEH,
e' necessario capire come installarlo perche' dovremo testarlo ed e' da questa
opera di debug che potremo capire cosa e' necessario dumpare e quando per
farlo funzionare. Avviamo il dumped.exe
con olly facendo in modo che olly non passi le eccezzioni al programma. F9 e
troviamo il punto in cui c'e' la prima int3: 0x434a81. Vediamo quale SEH sta
attualmente servendo le eccezzioni in questo punto del programma. Andiamo nella
finestra di dump di olly e selezioniamo goto-> Expression inseriamo fs:[0]
come indirizzo. Selezioniamo la prima DWORD della finestra dump e col tasto
destro ->follow dword in dump. Si vede chiaramente che il SEH attualmente
installato e' all'indirizzo 0x5540e9 (per chi non avesse idea di cosa sto dicendo
vada a leggersi il tutozzo del que sui SEH). Tale SEH e' quello maligno, cioe'
quello che non sa' risolvere gli int3. Andiamo all'indirizzo 0x5540E9 noteremo
che la prima istruzione che viene eseguita e' una salto non condizionato all'indizzo
0x5C6514. Questo e' l'indirizzo dell'SEH effettivo!!!! Dico cio' perche' nel
programma si possono notare diverse installazioni di SEH (ad indirizzi differenti)
che iniziano sempre con un salto a tale locazione. Cio' in realta' e' il frutto
di una certa opera di analisi, in ogni caso se il nuovo SEH rimpiazzasse quello
a ox5540e9 e non quello a 0x5c6514 ci si renderebbe presto conto che le
cose non andrebbero come vorremmo! Quello che faremo e' pertanto sostituire
le istruzioni a 0x5c6514 con un salto al nostro SEH, che inoltre dovra' 'emulare'
quelle che siamo stati costretti a togliere.
-
In particolare, sostituiamo queste istruzioni con un salto al nostro SEH,
e ricordiamoci che nel caso in cui il nostro SEH non sia in grado
di gestire l'eccezzione (in quanto non e' una int3) dovra' prima emularle
e poi ritornare alla istruzione che immediatamente le segue.
-
- 005C6514 8B4424 04 MOV
EAX,DWORD PTR SS:[ESP+4]
- 005C6518 F740 04 06000000
TEST DWORD PTR DS:[EAX+4],6
|
-
-
-
- Vediamo a questo punto come e'
strutturato il nostro SEH (si ricorda che assembliamo a partire da 0x754675) :
- 1) L'SEH e' di tipo per-Thread handler.
- Questa scelta e' stata motivata da iniziali
inghippi che mi hanno impedito di utilizzarlo come final. Da una successiva
lettura del codice sono convinto che si potrebbe studiare anche quest'ultima
soluzione, lasciata ai piu' volenterosi. Cio' premesso la prima cosa da fare
nel nostro SEH e' vedere se l'eccezzione che ha portato alla sua chiamata
e' una eccezzione di tipo int3, se non lo e' allora passiamo la stessa agli
altri della catena. Altrimenti andiamo al punto 2.
- Il codice che segue implementa quanto detto,
assembliamolo direttamente in Olly:
-
-
- 00754675 PUSH EBX
- 00754676 PUSH EDI
- 00754677 PUSH ESI
- 00754678 MOV EDX,DWORD
PTR SS:[EBP+8]
- 0075467B POP ESI
- 0075467C POP EDI
- 0075467D POP EBX
- 0075467E CMP DWORD PTR
DS:[EDX],80000003 ; L'eccezione e' un int3 ?
- 00754684 JE SHORT dumped_.00754696
- 00754686 MOV EAX,DWORD
PTR SS:[ESP+4]
; emulazione delle due istruzioni originali
- 0075468A TEST DWORD
PTR DS:[EAX+4],6 ; -----------------------------------------
- 00754691 JMP dumped_.005C651F
|
-
2) A questo punto bisogna ricopiare il contesto che si ottiene in seguito
alla int3 nel punto dove se lo aspetterebbe il padre subito dopo la GetThreadContext.
Le istruzioni che seguono adempiono a tale compito:
-
- 00754696 PUSHAD
- 00754697 PUSHFD
- 00754698 MOV EAX,DWORD PTR SS:[ESP+30]
- 0075469C PUSH EBP 0075469D 8BEC MOV EBP,ESP
- 0075469F PUSH 0D0
- 007546A4 PUSH EAX
- 007546A5 MOV EAX,ESP
- 007546A7 SUB EAX,1460
- 007546AC PUSH EAX
- 007546AD CALL MSVCRT.memcpy
- 007546B2 ADD ESP,0C
- 007546B5 MOV EAX,DWORD PTR SS:[EBP-13B0] ; preleviamo il program
counter dove e' avvenuta
- 007546BB INC EAX
; l'eccezzione e lo incrementiamo di uno
- 007546BC MOV DWORD PTR SS:[EBP-13B0],EAX ; proprio come se
lo aspetta il codice originale
|
Si noti la chiamata alla memcpy. Tale funzione non figura nella import table
del programma dumpato vedremo successivamente come fissarla
- per far si che il programma funzioni su tutte
le piattaforme.
- Il punto giusto dove copiare il contesto lo
si deduce con un po' di debugging del programma originale. Un metodo semplice
e' quello di analizzare il codice con Olly a run time. Basta mettere in relazione
il buffer fornito alla GetThreadContext con il valore di ebp. Oppure lo si
puo' dedurre da come il codice di risoluzione dei nanometti interagisce con
il contesto fornito dalla GetThreadContext. E' da notare inotre che il contesto
fornito dalla GetThreadContext e' leggermente diverso da quello che si ottiene
quando viene chiamato l'SEH a causa di una eccezzione. In particolare l'EIP
in quest'ultimo caso e' inferiore di 1 rispetto al caso della GetThreadContext
e questo e' il motivo per cui l'ho incrementato di uno.
- 3) Segue tutto il codice che serve alla decrittazione
delle tabelle.
- Tale codice va lasciato essenzialmente inalterato
e si estende fino alla SetThreadContext all'indirizzo 0x754A31.
- Ovviamente a noi interessa uscire dal SEH
subito dopo che il programma ha calcolato correttamente il nuovo program counter
cui saltare dopo la int3. Effettuando un po di tracing nel codice del debugger
si deduce che la parte di codice che va da 0x754964 a 0x754989 e del codice
di offuscamento che puo' essere noppato con olly. Inoltre l'istruzione
-
- 007549BE 8995 50ECFFFF MOV DWORD PTR SS:[EBP-13B0],EDX
non fa altro che copiare il nuovo valore del program
counter nel contesto. Ottimo! E' qui che terminera' il nostro SEH.
- 007549C4 MOV ESP,EBP
- 007549C6 POP EBP
- 007549C7 POPFD
- 007549C8 POPAD
- 007549C9 MOV EAX,DWORD PTR SS:[ESP-13D8]
- 007549D0 MOV ECX,DWORD PTR SS:[ESP+C]
- 007549D4 ADD ECX,0B8
- 007549DA MOV DWORD PTR DS:[ECX],EAX
- 007549DC MOV EAX,0 007549E1
C3 RETN
|
- Tale codice non fa altro che copiare nel contesto il
valore corretto del Program Counter che armadillo ha teneramente calcolato.
Assembliamo tale codice con olly. Un altro punto di uscita del codice che
risolve le nanomites si ha all'indirizzo 0x754a3d. Per tale motivo l'istruzione
successiva va assemblata per uscire dal SEH come segue:
-
- 00754A43 JMP dumped_.007549C4
Il nostro SEH non funzionerà ancora tuttavia. Perche'
tale codice fa uso di tabelle create a runtime nel padre. Questo
significa che il figlio dovrebbe ricrearsele in qualche modo. Si aprono allora
due scenari possibili:
a) cercare di riciclare il codice originale del padre
per fargli generare le tabelle
b) dumpare direttamente tali tabelle dal padre ed
attaccarle nel figlio come nuove sezioni o semplicemente inserirle nelle
sezioni eventualmente già presenti. Io ho seguito la strada b), ma proprio come
ho detto all'inizio del tutorial questa soluzione non e' a mio parere la piu'
elegante e lascio ai posteri il compito e la voglia di studiare la a). Come ho
fatto? semplice lancio il programma e se c'e' qualcosa che non va faccio un po'
di tracing e mi aiuto con il programma originale che lanciato normalmente
funziona come padre. Non scoraggiatevi se siete arrivati a questo punto,
abbiamo quasi finito!
Per capire cosa dumpare e come, seguiamo sempre la
procedura del confronto diretto tra il codice per come si comporta nel
programma originale e quello che si ottiene nel programma da noi dumpato con il
SEH appena installato. Chiudiamo le diverse istanze di olly attualmente in uso,
ed apriamone due: una con il dumped.exe, ed una con il programma originale. In
quest'ultimo ci interessa tracciare il codice del debugger o padre pertanto le
patch alla OpenMutex non vanno applicate. In entrambe mettiamo un breakpoint
all'indirizzo 0x7546D7 che e' il punto in cui cominciamo le effettive
elaborazioni del contesto del programma all'atto dell'eccezzione. Effettuiamo
lo step passo passo in entrambi i programmi cominciando a vedere cosa c'e' di
sospetto che puo' provocare problemi. Si ottiene immediatamente che la seguente
istruzione:
0075473C . 8B148D 88E9780>MOV EDX,DWORD PTR
DS:[ECX*4+78E988]
fa riferimento ad una tabella che nel nostro dump e'
vuota, mentre nel programma originale il debugger ha già riempito. Faremo il
dump di tale sezione e siccome e' già presente nel dumpato la piazzeremo
direttamente nell'eseguibile a mano con WinHex. Per capirne di piu' mettiamo un
hardware breakpoint on write all'indirizzo 0x78e988 e vediamo quando tale tabella
viene scritta. Praticamente dal codice si vede facilmente che il processo padre
la riempie dopo aver decrittato alcune sezioni del processo figlio che legge
attraverso la chiamata a ReadProcessMemory. Siccome in questo momento non mi va
di studiare come effettivamente funziona tale codice (sarà oggetto di studio
magari di prossimi tutorials), effettuo un semplice dump della sezione che
contiene la tabella come anzi detto: alt-m in olly e dump della sezione che ha
base pari a 0x788000 (.data1). Inoltre tale tabella contiene dei chiari
riferimenti alle sezioni 0x02a00000 e 0x01e70000 che dumpiamo pure.
La sezione .data1 e' gia' presente nel nostro
dumped.exe pertanto e' necessario copiare i dati dumpati con olly in tale
sezione. Io ho effettuato tale copia con WinHex, usate pure quale tool
desiderate. Dopo aver chiuso l'istanza di olly attualmente in uso con
dumped.exe effettuiamo pertanto l'operazione suddetta. Inoltre con LordPE
aggiungiamo due sezioni con i dati caricati dalle due sezioni 0x2a00000 e
0x1e70000, assicuriamoci che i virtual offset di tali sezioni siano corretti.
Le due sezioni salvate sono precedenti all'ultima che avevamo aggiunto, quindi
e' necessario prima togliere questa (una copia dell'ultima sezione dovreste
averla già sul disco, quella a 0x3000000 per intenderci).
Riavviando
il programma non parte ancora, l'ultima operazione di tracing ci fa capire che
il nostro SEH ha bisogno di alcuni dati che vengono resi disponibili all'inizio
attraverso il codice che va da 0x00753F5D a 0x00754105. Pertanto assembliamo una
RETN a 0x00754105 ed una call 0x753F5D all'indirizzo 0x754979. Anche qesta scelta non e' il massimo dal
punto di vista dell'efficienza dato che la call che abbiamo appena assemblato verrà chiamata ogni
volta che viene incontrata una nanomite; poco male, tuttavia dato che
parecchio dell'overhead introdotto da armadillo e' stato eliminato, primo fra tutti la
presenza di due processi a favore di uno solo.
Et voilà il gioco e' fatto.
Il programma parte in tutta tranquillità.
Ultimo tocco finale. Avevamo assemblato una memcpy
all'inizio del nostro SEH. Se si assembla una call in Olly, essa verrà
assemblata facendo riferimento all'indirizzo virtuale che il loader le ha già
assegnato, pertanto il programma dumpato, se lasciamo tutto in questo modo,
funzionerà olo sul vostro OS!. Inoltre la memcpy non e' presente tra le
funzioni attualmente importate dal dumped.exe. Pertanto aggiungiamo alla import
table una entry e un riferimento alla memcpy di msvcrt.dll. Cio' si fa in
maniera immediata con LordPE. (PeEditor -> Directories -> Import Table
-> add import....), segnamoci il ThunkRVA fornito da lordpe sommiamo la base
0x400000 e modifichiamo l'istruzione call msvcrt.memcpy in CALL DWORD
PTR [0xIndirizzo].
Riavviamo il programma ed otteniamo un dumpato
perfettamente funzionante su cui ci si puo' ora divertire per scopi non
puramente accademici.
Zarathustra
La tecnica
utilizzata non rappresenta sicuramente un capolavoro di reverse engineering, in
quanto non vengono direttamente investigati in modo approfondito alcuni
meccanismi intrinseci del codice di armadillo, ma semplicemente viene dumpato il
programma. Essa mira a mostrare come l'uso di un apposito SEH spesso possa
risolvere molti problemi nel MUP di target ostici come questo, oltre che a
violare la protezione per il gusto di farlo a
tutti i costi. Sicuramente qualcosa da fare e' quello di
rivedere tutto il dump eliminando eventualmente sezioni inutili o altro overhead
introdotto da armadillo. Per esempio la parte relativa ai salti al codice del packer
potrebbe essere sostituita con del codice ad hoc che evita tali salti inutili. Ma
questo e' un altro film.
A questo lavoro hanno partecipato indirettamente
diverse persone che ringrazio per l'aiuto datomi e per le informazioni che mi
hanno passato sul forum della UIC, fra tutti ringrazio in particolare SaturN,
faina_mdc e LittleLuk.
Ringrazio, anche se probabilmente non lo saprà mai,
Oleh Yuschuk, il papà di Olly. E' uno strumento fantastico, immediato e
potente.
Vorrei ricordare che il software va comprato e
non rubato, dovete registrare il vostro prodotto dopo il periodo di
valutazione. Non mi ritengo responsabile per eventuali danni causati al vostro
computer determinati dall'uso improprio di questo tutorial. Questo documento è
stato scritto per invogliare il consumatore a registrare legalmente i propri
programmi, e non a fargli fare uso dei tantissimi file crack presenti in rete,
infatti tale documento aiuta a comprendere lo sforzo che ogni sviluppatore ha
dovuto portare avanti per fornire ai rispettivi consumatori i migliori prodotti
possibili.
Reversiamo al solo scopo informativo e per migliorare
la nostra conoscenza del linguaggio Assembly.