Yado's Krypton v0.5: | ||
Data |
by anonymous |
|
31/01/2004 |
Published by Quequero | |
|
Complimentoni, ci son volute quasi due ore per leggerlo tutto ma bravo davvero! |
...che ci lascia lo zampino -.- |
.... |
| |
Difficoltà |
( )NewBies ( )Intermedio ( )Avanzato (X)Master |
The story of the yado's krypton!
In principio Yado creò un crackme... tutto sommato di
semplice soluzione. Il suo nome era Krypton. Ancora l'umanità era all'oscuro di
quella che era la verità assoluta riguardo a quel nome! Tempo dopo nacque il
Krypton 2. Un essere spaventoso! Nulla a che vedere con la sua prima versione!
Un eseguibile in grado di danneggiare irreparabilmente i pc dei poveri utenti a
forza di reset! Era l'avvento dello yado's Krypton - The krypter! Era nato!
Anche se in una versione non ancora al massimo delle sue facoltà... aveva già
provocato la morte di innumerevoli innocenti. Qualcuno doveva fermarlo! Il turno
fu di AndreaGeddon, che, col suo impeccabile tutorial, spiegò passo passo la
strategia di guerra utilizzata per annientare quel mostro. Ma il krypton non si
diede per vinto! Riuscì a perfezionare la sua potenza distruttiva e la sua forza
psichica! Adesso l'impresa sarà ancora più ardua! Siamo arrivati al Krypton -
The krypter v0.5! Terribile macchina da guerra in grado di trasformare il più
innocuo degli eseguibili in un vero e proprio inferno di bogus code!
Un uomo! Un giovane ragazzo deciso a distruggere per sempre questo incredibile
nemico, affrontò il viaggio all'interno di quelle istruzioni! Oltrepassati 3
layer di dekrypting riuscì a ricostruire la import table per poi trovarsi di
fronte ad altri 5 terrificanti mostri! Gli FF15! Il ragazzo riuscì con
l'astuzia ad ingannare quell'applicazione, distruggendone tutte le parti che
difettavano del bene umano! L'ora era giunta! Il krypton venne spazzato via! Non
rimase più nulla... se non il ricordo di quel programma, dato dall'uguaglianza
del virtual offset e del raw offset, vago ricordo di un mapping del file
all'interno della memoria... ormai letta e stravolta del terribile ma allo
stesso tempo sconfitto Krypton 0.5!
Così anche questa volta l'umanità potrà godere un altro periodo di pace e
tranquillità... ma per quanto tempo? o_O
Introduzione |
Tools usati |
URL o FTP del programma |
Notizie sul programma |
Essay |
;
********************************************* ; * cavia.asm ; * ; ********************************************* .386 .model flat, stdcall option casemap: none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\user32.lib .data msgtext db "Simple code by anonymous", 0 appname db "anonymous", 0 .code main: push 0 push offset appname push offset msgtext push 0 call MessageBox push 0 call ExitProcess call GetModuleHandle call GetTickCount end main |
Le ultime due chiamate non servono a nulla... oddio... non servono a nulla per quanto riguarda il codice ma servono per garantire un corretto packing dell'eseguibile in quanto il krypton richiede un certo numero di importazioni dal kernel (se non ricordo male...) comunque con questo codice siste a posto. A questo punto packiamo il file. Per il packing io seleziono queste opzioni:
1. Enable k-exec
2. k-exec on api
3. tutti i livelli del k-execution
4. k-protect on api
Seleziono il mio piccolo
eseguibile e lo crypto. Se lo testate per l'esecuzione potete assicurarvi che
funziona vedendo la messagebox che appare sullo schermo. Mi sa che possiamo
procedere con il reversing.
Prima di tutto è opportuno dire che con il krypton 0.5 o con qualsiasi altro
krypter che si rispetti, sferrare un attacco rapito (del tipo settando bpx o
provare a dumpare...) non è assolutamente pensabile! Così facendo infatti altro
non farete che fare il gioco del krypter e andare a finire dove vuole lui
proprio per ingannarvi. Se esiste un modo per unpackare questi eseguibili è
stepparci dentro col softice dall'inizio alla fine. Fatevene una ragione.
Adesso io vi suggerirei di giocare un po' di intelletto. Provate a iniziare a
steppare col softice all'interno dell'eseguiibile packato. Come potete notare il
codice è automodificante... ed è proprio quella la difficoltà del krypter, in
quanto, dentro tutto a quel casino è molto difficile capire il codice
effettivamente utile. Per questo vi consiglio di prendere carta e penna e di
riscrivervi tutte le istruzioni necessarie saltando tutto il bogus code. Però
però però... se andate a leggere dentro al krypton... nei ringraziamenti viene
citato un certo iNX. Vi spiego meglio come stanno le cose... tutto quel casino
che vedete nel codice... non pensate che l'abbia scritto yado facendosi tutti i
conticini su dove sarebbe andato a finire l'eip dopo un salto del codice
ammucchiato... vi spiego: solitamente la cosa è suddivisa in due fasi, ovvero il
normale programma + lo scramble. Nella maggior parte dei casi, salvo eccezioni,
il codice originale viene scritto dichiarando tra un'istruzione e l'altra un
certo numero di bytes (solitamente di nop). Una volta compilato l'eseguibile,
viene utilizzata una seconda applicazione, ovvero lo scrambler, che, nei punti
di spazio dichiarati tra le istruzioni va ad infilarci del codice bogus che
serve solo a confondere il reverser in questione ovvero me in questo caso :). Il
problema di fronte a cui mi pongo io è il seguente: lo scrambler è un
programma... e per quanto polimorfico possa essere, deve seguire una certa
logica... ora se io riuscissi a capire questa logica, riuscirei a scrivere
qualcosa in grado di ripristinare tutti quei nop al posto di tutto quel bogus
code? Beh... se non ci riesco per lo meno capisco bene come è sistemato quel
codice inutile e vi garantisco che alla fine evitarlo viene naturale.
Ok... oggi è il primo dell'anno. Oggi ho analizzato molto bene quello che
succede con lo scrambler del krypton. La conclusione? Hehehe... si... l'ho fatto
fuori! In poche parole, il krypton si trasforma completamente... dal programma
impossibile e complicato da unpackare alla canonica routine assembly da
interpretare. Infatti la difficoltà del programma era proprio in quella marea di
codice ad capocchiam da steppare. Vediamo un po' come funziona in linea teorica:
il tutto inizia con un JMP che ci porta su un'istruzione, ovvero PUSH ECX.
Subito dopo notiamo che c'è una chiamata a una procedura che è sempre la stessa,
ovvero:
pop ecx
pushfd
add ecx, -19
popfd
jmp ecx
Questa qua è il nostro mito. Siccome dovrò citarla spesso all'interno del tutorial, per convenzione la chiameremo "famosa". ^^' Adesso però spieghiamo un po' come funziona il resto del codice. Prendiamo come esempio l'inizio dell'inferno, ovvero il bogus code che si trova a partire da 00447015, che inizia con il nostro bel jump. Vediamo lo scheletro principale di tutto lo scrambling:
_______:00447015 jmp short loc_0_44705A ; questo è il jmp che dà inizio alle danze
_______:00447015 start endp ; e che ci porta esattamente qua -+
_______:00447015 ; |
_______:00447015 ; -------------------------------------------------------|---------
_______:00447017 dd 584E69DFh, 0F07259DFh, 73DF01EBh, 9C59DFEBh, 9DE7C183 |
_______:00447017 dd 0DEBE1FFh, 0FFF0E851h, 0B1DFFFFFh, 9A3F22A3h, 0E883F9 |
_______:00447017 dd 0DF47EB06h, 0DF584E69h, 0EBF37959h, 0EE78DF01h |
_______:0044704F db 0DFh |
_______:00447050 |
_______:00447050 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ SUBROUTINE ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦|¦¦¦¦¦¦¦¦¦
_______:00447050 |
_______:00447050 |
_______:00447050 sub_0_447050 proc near ; CODE XREF: _______:0044705B p |
_______:00447050 pop ecx |
_______:00447051 pushf |
_______:00447052 add ecx, 0FFFFFFE7h |
_______:00447055 popf |
_______:00447056 jmp ecx |
_______:00447056 sub_0_447050 endp |
_______:00447056 |
_______:00447058 ; -------------------------------------------------------|---------
_______:00447058 jmp short loc_0_447067 |
_______:0044705A ; -------------------------------------------------------|---------
_______:0044705A |
_______:0044705A loc_0_44705A: ; CODE XREF: start+15 j <------------------+
_______:0044705A push ecx ; ecx viene pushato
_______:0044705B call sub_0_447050 ; qua viene chiamata la famosa proc :)
_______:00447060 db 3Eh ; <- 00407060 dovrebbe essere l'eip di...
_______:00447060 mov ebx, eax ; ritorno dopo la chiamata a quella proc...
_______:00447063 db 66h
Allora questo è lo scheletro principale dell'esecuzione del bogus code. All'indiricco 0044705B viene chiamata la nostra famosa procedura. Di conseguenza, in un normale listato assembly, dopo il ret all'interno della procedura chiamata si dovrebbe ritornare all'esecuzione del codice principale partendo da 00447060 (esattamente dopo la call...). Infatti, dopo che una procedura viene chiamata, in [esp] c'è il valore di ritorno di questa procedura. Una volta saputo questo, possiamo continuare l'analisi del nostro codice commentando la nostra famosa procedura:
_______:00447050 sub_0_447050 proc near ; CODE XREF: _______:0044705B p
_______:00447050 pop ecx ; questo mette in ecx il valore di ritorno della proc
_______:00447051 pushf ; pusha tutti i flags per evitare inconvenienti
_______:00447052 add ecx, -19h ; sottrae ad ecx -19h
_______:00447055 popf ; ripoppa i flags precedentemente pushati
_______:00447056 jmp ecx ; salta ad ecx
_______:00447056 sub_0_447050 endp
Vediamo meglio anche qua: la prima istruzione è "pop ecx". Guardiamo un po' di cosa si tratta... qualche rigo fa abbiamo detto che dopo una chiamata a procedura, in [esp] c'è l'indirizzo di ritorno della procedura chiamata. Facendo "pop ecx", si mette in ECX questo indirizzo e lo stack viene modificato (ma queste cose dovreste già saperle -.-). La pushf è solo un'istruzione preventiva in quanto sottraendo 19h ad ecx si potrebbe andare incontro ad un cambiamento dei flags, rischiando così di cambiare il normale corso dell'applicazione... così con la pushf vengono salvati per poi essere ripristinati dopo la sottrazione. Con "add ecx, -19h" viene sottratto 19h da ecx... quindi il normale valore di ritorno viene modificato portando l'esecuzione del codice a 19h bytes prima di quella originale. Popf ripristina i flags mentre "jump ecx" sostituisce il ret poiché all'inizio della subroutine è stata poppata una DWORD dallo stack, quindi al salto lo stack risulterà compensato. In pochissime parole, quella chiamata è uno pseudo-salto che ci porta all'interno di altro bogus, prima della procedura famosa ^^'. Rivediamo ora lo scheletro principale dopo queste nuove informazioni :D
_______:00447015 jmp short loc_0_44705A ; questo è il jmp che dà inizio alle danze
_______:00447015 start endp ; e che ci porta esattamente qua -+
_______:00447015 ; |
_______:00447015 ; -------------------------------------------------------|---------
_______:00447017 dd 584E69DFh, 0F07259DFh, 73DF01EBh, 9C59DFEBh, 9DE7C183 |
_______:00447017 dd 0DEBE1FFh, 0FFF0E851h, 0B1DFFFFFh, 9A3F22A3h, 0E883F9 |
_______:00447017 dd 0DF47EB06h, 0DF584E69h, 0EBF37959h, 0EE78DF01h |
_______:0044704F db 0DFh ^ |
_______:00447050 | |
_______:00447050 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ SUBROUTINE ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦|¦|¦¦¦¦¦¦¦¦¦
_______:00447050 | |
_______:00447050 | |
_______:00447050 sub_0_447050 proc near ; CODE XREF: _______:0044705B p | |
_______:00447050 pop ecx | |
_______:00447051 pushf | |
_______:00447052 add ecx, 0FFFFFFE7h | |
_______:00447055 popf | |
_______:00447056 jmp ecx | |
_______:00447056 sub_0_447050 endp | |
_______:00447056 | |
_______:00447058 ; -----------------------------------------------------|-|---------
_______:00447058 jmp short loc_0_447067 | |
_______:0044705A ; -----------------------------------------------------|-|---------
_______:0044705A | |
_______:0044705A loc_0_44705A: ; CODE XREF: start+15 j <----------------|-+
_______:0044705A push ecx ; ecx viene pushato |
_______:0044705B call sub_0_447050 --+ ; qua viene chiamata la famosa pr|c :)
_______:00447060 db 3Eh -------------+----------------------------------+ di...
_______:00447060 mov ebx, eax ; ritorno dopo la chiamata a quella proc...
_______:00447063 db 66h
Mooooolto bene. Quella in azzurro è la freccia che ci porta vicino al punto che ci interessa. Dico vicino perché là dentro IDA ha visualizzato tutto quel codice come una serie di DWORD. Per vedere bene di cosa si tratta, diciamo ad ida di visualizzare quel codice non in DWORD ma in singoly bytes in modo da trasformare il tutto il codice a partire dal punto esatto, in modo da capire di cosa si tratta. Andando all'inizio di quel blocco e premendo il tasto Undefine si ottiene la visualizzazione in bytes di tutta la massa di codice:
_______:00447015 ; -----------------------------------------------------------------
_______:00447017 db 0DFh ; ¯
_______:00447018 db 69h ; i
_______:00447019 db 4Eh ; N
_______:0044701A db 58h ; X
_______:0044701B db 0DFh ; ¯
_______:0044701C db 59h ; Y
_______:0044701D db 72h ; r
_______:0044701E db 0F0h ;
_______:0044701F db 0EBh ; Ù
_______:00447020 db 1 ;
_______:00447021 db 0DFh ; ¯
_______:00447022 db 73h ; s
_______:00447023 db 0EBh ; Ù
_______:00447024 db 0DFh ; ¯
_______:00447025 db 59h ; Y
_______:00447026 db 9Ch ; £
_______:00447027 db 83h ; â
_______:00447028 db 0C1h ; -
_______:00447029 db 0E7h ; þ
_______:0044702A db 9Dh ; Ø
_______:0044702B db 0FFh ;
_______:0044702C db 0E1h ; ß
_______:0044702D db 0EBh ; Ù
_______:0044702E db 0Dh ;
_______:0044702F db 51h ; Q
_______:00447030 db 0E8h ; Þ
_______:00447031 db 0F0h ;
_______:00447032 db 0FFh ;
_______:00447033 db 0FFh ;
_______:00447034 db 0FFh ;
_______:00447035 db 0DFh ; ¯
_______:00447036 db 0B1h ; ¦
_______:00447037 db 0A3h ; ú
_______:00447038 db 22h ; "
_______:00447039 db 3Fh ; ?
_______:0044703A db 9Ah ; Ü
_______:0044703B db 0C0h ; +
_______:0044703C db 0F9h ; ¨
_______:0044703D db 83h ; â
_______:0044703E db 0E8h ; Þ
_______:0044703F db 6 ;
_______:00447040 db 0EBh ; Ù
_______:00447041 db 47h ; G
_______:00447042 db 0DFh ; ¯
_______:00447043 db 69h ; i
_______:00447044 db 4Eh ; N
_______:00447045 db 58h ; X
_______:00447046 db 0DFh ; ¯
______:00447047 db 59h ; Y ; questa è la riga su cui salta la proc. famosa.
_______:00447048 db 79h ; y
_______:00447049 db 0F3h ; ¾
_______:0044704A db 0EBh ; Ù
_______:0044704B db 1 ;
_______:0044704C db 0DFh ; ¯
_______:0044704D db 78h ; x
_______:0044704E db 0EEh ; ¯
_______:0044704F db 0DFh ;
Verifichiamo: seguendo lo scheletro principale del bogus, dopo la chiamata alla famosa, si dovrebbe arrivare su 00447060. Tutto quello che dobbiamo fare è sottrarre 19h per vedere dove si arriva:
00447060 - ; questo dovrebbe essere il regolare indirizzo di ritorno
19 = ; togliamo 19h
-----------
00447047 ; e otteniamo il nostro caro indirizzo di arrivo :D
Siamo quindi nella tecnica del Delta offset: il codice utile è sistemato in modo tale da trovarsi 19h bytes prima del codice bogus eseguito per trovarsi al punto giusto al momento del salto. Bando alle chiacchiere... andiamo con ida su quel diavolo di indirizzo e premiamo la Code per convertire il tutto in codice. Il risultato è questo:
_______:00447047 ; -----------------------------------------------------------------
_______:00447047 pop ecx ;
_______:00447048 jns short loc_0_44703D ; eccolo qua! :D
_______:0044704A jmp short loc_0_44704D ----+
_______:0044704A ; -------------------------|---------------------------------------
_______:0044704C db 0DFh ; ¯ |
_______:0044704D ; -------------------------|---------------------------------------
_______:0044704D |
_______:0044704D loc_0_44704D: ; CODE XREF: |______:0044704A j
_______:0044704D js short loc_0_44703D <----+
_______:0044704D ; -----------------------------------------------------------------
Prima istruzione: "pop ecx" che serve per compensare lo stack (poiché prima della chiamata alla famosa c'è un push ecx... ricordate?). Subito dopo abbiamo un JNS che salta ad un determinato indirizzo... poi abbiamo un JMP in caso di salto non eseguito e poi un JS... che salta allo stesso identico indirizzo del JNS di prima... questo fatto deve farvi riflettere un po'. Ve lo spiego subito... o grazie al JNS o grazie al JS, il salto verrà sempre fatto in base a come sn settati i flags. Detto in altre parole, anche questo sistema serve per confondervi le idee. Siccome lo scrambler, al momento dell'encrypting dell'eseguibile, non conosce come sono settati i flags al momento del passaggio da quel punto, per assicurarsi di arrivare al punto del "codice utile" è costretto a settare due jumps in modo che uno dei due per forza alla fine salterà al punto desiderato. JNS e JS servono per confondervi le idee... noi abbiamo preso in esame la prima parte del bogus code, ma successivamente incontrerete una roba simile ma con i tipi di jump diversi ad esempio troverete un JNO insieme ad un JO, un JNP insieme ad un JP, un JNZ insieme ad un JZ... non so se mi spiego... beh se non mi spiego i problemi sono vostri non miei. ^^' Si giunge alla conclusione che la destinazione di quei salti è il punto in cui si trova il nostro benedettissimo "codice utile"! Infatti se da ida premiamo INVIO su quel salto là arriviamo esattamente qua:
_______:0044703D loc_0_44703D: ; CODE XREF: _______:00447048 j
_______:0044703D ; _______:0044704D j
_______:0044703D sub eax, 6 ; istruzione utile :D
_______:00447040 jmp short near ptr dword_0_447071+18h ; ricomincia un altro bogus!
Grosso modo, per tutto il
programma le istruzioni son sempre disposte in questo modo: codice utile -
codice bogus - codice utile - codice bogus, bla bla bla bla...
E così abbiamo capito come funziona tutta 'sta roba qua. Ora vogliamo o no farci
un generic remover per tutta quella sfilza di bytes in modo da steppare un
codice scorrevole e comprensibile? Sii! Vediamo in linea teorica come si può
fare (che poi alla fine io dico "vediamo se si può fare" quando in realtà l'ho
già fatto -.-). Innanzitutto impariamo a capire dove è che inizia questo
codice... abbiamo detto che inizia con un JMP e fin qua ci siamo ma dobbiamo
saper riconoscere se quel JMP è del bogus oppure è un normale jump che fa parte
del codice utile. Niente di più facile e di più statico! Se osservate bene
l'inizio di ogni bogus (intendo osservare gli opcodes) visualizzerete sempre una
roba di questo tipo:
EB XX jmp short loc_0_44705A ; questo è il salto iniziale
DF
69
4E
58
DF
59
In ogni punto di inizio del bogus troverete sempre l'opcode EB che è quella del JMP seguita da un'opcode variabile che indica la lunghezza del salto. Dopo queste due opcode ce ne sono altre che sono sempre DF 69 4E ecc... (a noi ce ne interessano poche per verificare se siamo in bogus o meno). Quindi... nel file dobbiamo cercare tutti gli EB. Dopo di ciò dobbiamo controllare che la word che segue saltando un byte sia uguale a 69DF. Se tutto rientra nel caso, siamo in opcodes e siamo quindi pronti per rimuoverlo dall'esecuzione. Sposteremo il nostro puntatore di tanti bytes più avanti quanti ne sono indicati dall'opcode che indica la lunghezza del salto. Una volta arrivati a questo punto (siamo sul "pop ecx" prima della famosa chiamata) basta avanzare di 6 bytes ("pop ecx": 1 byte + 5 bytes per la call... arriviamo a 6) per arrivare a quello che dovrebbe essere il normale valore di ritorno di questa call. Sottraiamo 19h da questo indirizzo ed arriviamo al gruppo di bytes che contiene quei salti associati. Prendiamo il primo (tanto tutti e due saltano alla stessa posizione)... ci estrapoliamo la lunghezza di questo salto, la sottraiamo dal nostro attuale indirizzo ed arriviamo al codice utile. Prendiamo l'offset del codice utile, sottraiamo l'offset di destinazione e otteniamo come risultato la lunghezza del salto da effettuare per arrivare direttamente dal salto iniziale al codice utile. Da questa togliamo 2 perchè sono il numero degli opcodes del jump e sostituiamo il nostro valore con il vecchio XX e il gioco sarà fatto! Ora... siccome qua in mezzo a questa spiegazione non capirete un'emerita mazza, vi scrivo il mio codice asm (fa skifo... non è ottimizzato per niente...) poi ve lo debuggate come diavolo volete per vedere di capire quanto meglio le operazioni che ho fatto:
;
********************************************* ; * descrambler.asm ; * ; ********************************************* .386 .model flat, stdcall option casemap: none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc include \masm32\include\comdlg32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\comdlg32.lib .data ofn OPENFILENAME <> FileName db 0 dup (255) hFile dd 0 FileSize dd 0 hInstance dd 0 AppName db "descrambler by anonymous", 0 _file_error_message db "Can't open selected file!", 0 lpData dd 0 mdw dd 0 jump_offset dd 0 questo_offset dd 0 outfile db "c:\windows\desktop\outfile.exe", 0 .code main: mov ecx, sizeof OPENFILENAME xor eax, eax lea edi, ofn rep stosb mov [ofn.lStructSize], sizeof OPENFILENAME mov [ofn.lpstrFile], offset FileName mov [ofn.nMaxFile], 255 push 0 call GetModuleHandle mov [ofn.hInstance], eax mov [ofn.Flags], OFN_HIDEREADONLY or OFN_EXPLORER push offset ofn call GetOpenFileName cmp eax, 0 jz _quit push 0 push 0 push OPEN_EXISTING push 0 push 0 push GENERIC_READ push offset FileName call CreateFile mov [hFile], eax inc eax jz _file_error push 0 push [hFile] call GetFileSize mov [FileSize], eax push PAGE_EXECUTE_READWRITE push MEM_COMMIT push eax push 0 call VirtualAlloc mov [lpData], eax push 0 push offset mdw push [FileSize] push [lpData] push [hFile] call ReadFile push [hFile] call CloseHandle mov ecx, [FileSize] mov eax, [lpData] _loop1: cmp ecx, 0 jz _end_loop1 cmp byte ptr [eax], 0ebh jz _is_eb dec ecx inc eax jmp _loop1 _is_eb: cmp word ptr [eax+2], 69dfh jz _is_bogus dec ecx inc eax jmp _loop1 _is_bogus: pushad mov [jump_offset], eax ;indirizzo del salto iniziale xor edx, edx mov dl, [eax+1] ;dl = lunghezza del salto add eax, edx ;eax = offset "push ecx" prima della proc famosa add eax, 6 ;eax = fake retval sub eax, 19h ;eax = jump_zone add eax, 3 ;eax punta al secondo salto xor ebx, ebx mov bl, [eax+1] not bl sub eax, ebx inc eax mov ebx, [jump_offset] ;vengono presi i due indirizzi per il delta-offset sub eax, ebx ;al contiene l'esatto valore :-) sub al, 2 mov ebx, [jump_offset] mov byte ptr [ebx+1], al ;mettiamo il valore al suo posto :D popad inc eax dec ecx jmp _loop1 _end_loop1: push 0 push 0 push CREATE_ALWAYS push 0 push 0 push GENERIC_WRITE push offset outfile call CreateFile mov [hFile], eax push 0 push offset mdw push [FileSize] push [lpData] push eax call WriteFile push [hFile] call CloseHandle _quit: push 0 call ExitProcess _file_error: push MB_ICONERROR or MB_OK push offset AppName push offset _file_error_message push 0 call MessageBox jmp _quit end main |
Questo è il mio codice utilizzato momentaneamente per rimuovere tutto quel codice inutile. Se ho fatto bene i calcoli, selezionando il file packato, sul desktop dovrebbe venire salvato un file di nome outfile.exe che dovrebbe eseguirsi senza grossi problemi. Infatti il file viene salvato e... CA**O! Non funziona! O meglio dà errore ad un determinato punto! Hmmm... deve esserci qualche errore nel codice... no impossibile sono troppo bravo per sbagliare... non ci resta che steppare (tanto per la prima parte è facilissimo... il codice è comprensibilissimo e il tempo si riduce drasticamente :DD). Steppando, in pochissimo tempo si giunge in un punto in cui c'è un JMP EAX. Ovviamente un reverser, quando vede quest'istruzione, capisce che è sulla buona strada! :D Steppando (o anche guardando con ida), vi sarete resi conto che fino a quel punto non ci sono controlli o tricks speciali da evitare o cose del genere... quindi saltate ad EAX che vi porta su 00404000. Adesso è ora di guardare la section table dell'eseguibile packato:
SECTION VIRTUAL SIZE VIRT. OFFSET RAW SIZE RAW OFFSET CHARACT. YADO 0000003C 00001000 00000000 00000000 E0000020 YADO 000000C6 00002000 00000000 00000000 C0000040 YADO 00000023 00003000 00000000 00000000 C0000040 krypton 00043000 00004000 00000000 00000000 E0000020 _!_!_!_ 00014000 00047000 000132CE 00000400 E0000020
Diamo anche uno sguardo all'entry point... quello dell'exe packato è uguale a 47000. Il programma parte quindi dall'ultima sezione(_!_!_!_). Quando noi arriviamo al nostro JMP EAX, veniamo spediti su 00404000, ovvero all'inizio della sezione "krypton". Ora accendete il cervello... al momento dell'avvio del processo, questa sezione non ha nè raw size nè raw offset. Questo può indicare solo una cosa: che è stata creata a runtime dalla sezione _!_!_!_. A sua volta questo implica che ci sono stati sicuramente degli spostamenti di bytes e chissà quali altre operazioni (non mi sono steppato niente per capire... sono andato avanti e basta). Per questo, il lavoro svolto dal mio descrambler, per quella sezione, avrà di certo generato qualche errore. Ora ci sono 2 opzioni: 1) quella di effettuare la correzione manuale dei salti sbagliati (io l'ho fatto non sono molti, ma ve lo sconsiglio); 2) dato che fino a 00404000 non ci sono stati imprevisti di esecuzione, io suggerirei di partire dal packato originale e di fare, col softice, "g 00404000" in modo da arrivare in quel punto senza problemi. Una volta arrivati là, blocchiamo il processo per prepararci a dumparlo. Vediamo, tutto inizia così:
8B 0C 24 mov ecx, [esp+0]
Questa è la prima riga a 00404000. Innanzitutto prendiamo nota dei primi 2 opcode (8B 0C). Per bloccare il processo, inseriamo dei nostri opcodes, e precisamente EB FE che corrispondono all'istruzione "jmp eip". In questo modo, effettuiamo un jump sempre sullo stesso punto, così il processo rimane bloccato. Per inserire gli opcode, facciamo col softice "ew eip FEEB" (FEEB sarebbero le opcode EB FE scritte in notazione intel in formato DWORD). Ora che il nostro processo rimarrà bloccato, usciamo fuori dal softice e mettiamo le mani sul PEditor! Clicchiamo sul pulsante "tasks" per ottenere la lista di tutti i processi correntemente attivi sul sistema. Dalla lista, scorriamo in basso fino ad incontrare il percorso del processo del nostro eseguibile (bloccato su quel punto). Tasto destro del mouse -> dump -> dump full. Salviamo il file in una cartella... dove volete... e poi con il tasto terminate process, interrompiamo il processo precedentemente bloccato. Che cosa abbiamo fatto? Si chiama dumping... se non sapete che cosa significa, lasciate questo tutorial e datevi fuoco. Bene... ora andiamo a ritoccare manualmente il file dumpato. Innanzitutto la sezione _!_!_!_ non serve più perché l'abbiamo tutta steppata. Quella sezione serve per ricostruire tutta la section table dell'eseguibile (dopo ci daremo uno sguardo). Settiamo l'entry point su 4000 (ovvero il punto in cui ci eravamo fermati, in modo da ricominciare direttamente da là). Però però... dobbiamo ripristinare le opcode che avevamo modificato per bloccare il processo... niente di più semplice. Prima di tutto diamo uno sguardo alla section table che il file assume dopo l'esecuzione della sezione _!_!_!_:
SECTION VIRTUAL SIZE VIRT. OFFSET RAW SIZE RAW OFFSET CHARACT. YADO 0000003C 00001000 0000003C 00001000 E0000020 YADO 000000C6 00002000 000000C6 00002000 C0000040 YADO 00000023 00003000 00000023 00003000 C0000040 krypton 00043000 00004000 00004300 00004000 E0000020 _!_!_!_ 00014000 00047000 00014000 00047000 E0000020
Come potete vedere, si giunge facilmente alla conclusione che il programma è stato unpackato e che ora bisogna steppare a partire dalla sezione krypton! Siccome per dumpare abbiamo usato il PEditor, l'eseguibile risulterà perfettamente allineato. Per cui, se dobbiamo patchare i bytes di 00404000, il raw offset sarà 4000. Non credo vi servano commenti per patchare due bytes, no? Quindi mano all'hex workhsop e patchate con 8B 0C. Se ora ricominciamo a steppare, il programma funzionerà benissimo, per cui, riprocessiamo questo file col mio descrambler per steppare anche questo pezzo di codice senza troppi problemi! Ovviamente anche in questo caso, a causa delle manovre del krypton, l'eseguibile modificato dal mio programma inevitabilmente crasherà ad un determinato punto, ma prima di arrivare là ne passerà di tempo per cui stepperemo senza grosse difficoltà. Alla fine a noi è questo che interessa! Adesso però scordatevi completamente tutti i trucchetti e i descrambling possibili perché dovete solo armarvi di carta e penna per scrivere ogni istruzione e commentare rigo per rigo! Ecco il mio codice commentato (miticooo! :) [attenzione il mio codice lo scrivo molto schematizzato per farvi capire meglio la situazione. Il tutto non rispetterà esattamente il vero codice del krypton]
00404000: mov ecx, [esp] ; ecx = retval nel kernel
call ...
mov [ebp+0044780C], ecx
push eax
push esi
00443F66: xor edx, edx ; azzera EDX
dec ecx ; decrementa ECX (nel kernel)
mov dx, [ecx+3C] ; mette in DX una word dal kernel
test dx, F800 ; controlla se dx è uguale a F800
jnz 00443F66
cmp ecx, [edx+ecx+34] ; se è uguale fa anche questo controllo
jnz 00443F66
mov [ebp+0044994B], ecx ; tutto va bene solo se in ECX si ha
; l'indirizzo in memoria del kernel
; che poi verrà salvato in [ebp+0044994B] :)
Questa è una delle procedure importanti. Come ho accennato prima, vi scriverò soltanto il codice principale del krypton, limitandomi nella scelta a quello che serve per la comprensione del funzionamento del kryptrer. Capirete pure che è disumano copiare tutto il programma no? ^^' Vediamo ora il punto in cui viene chiamata una VirtualAlloc per capire a cosa serve:
mov eax, [ebp+0044992C] ; eax = indirizzo della VirtualAlloc
cmp byte ptr [eax], CC ; questo è un anti-bpx: se avete messo un
jz 00445F69 ; bpx sulla VirtualAlloc, il codice salta
... ; ad errore e vi porta dove dice lui.
call [ebp+0044992C] ; altrimenti chiama normalmente VirtualAlloc.
mov [ebp+00449953], eax ; eax = 006A0000 viene salvato in una var.
Questa procedura viene ripetuta per due volte (però in due punti diversi). In effetti vengono allocate 2 porzioni di memoria per il momento vuote. Andando più avanti nel codice possiamo accorgerci a che cosa vengono usate (tenendo presente che sul mio sistema la seconda VirtualAlloc ha dato come risultato 006B0000):
mov esi, eax
mov esi, edi
mov eax, 0044794B
add eax, ebp ; EAX contiene un indirizzo del programma
mov ecx, 0003FEBE ; ECX prende una dimensione da elaborare
xor ebx, ebx ; viene azzerato EBX
004459A4: mov bl, [eax] ; BL prende un carattere dal codice
ror bl, cl ;
xor bl, cl ;
add bl, cl ; in questo pezzo di codice elabora il carattere..
xor bl, cl ;
ror bl, cl ;
mov [edi], bl ; il carattere elaborato viene messo in [EDI]
inc edi ; incrementa EDI
inc eax ; incrementa EAX
loopnz 004459A4 ; finisce se ECX = 0 decrementandolo ogni volta
In pratica... la seconda VirtualAlloc ha allocato dello spazio per il momento vuoto. Nella prima istruzione della routine sopra parte subito dopo la seconda VirtualAlloc, quindi con EAX che punta nella zona di memoria appena allocata (nel mio caso 006B0000). Quindi, osservando il codice, questo spazio viene riempito prendendo dei bytes del codice del programma e decryptandoli secondo quella piccola routine. Secondo voi questo che cosa significa? Significa che il mio programma farà cilecca nel punto in cui il krypton salterà dentro quell'allocazione di memoria! Infatti il mio descrambler va alla ricerca del bogus code cercando tutti i bytes che lo compongono. Ovviamente all'inizio, siccome quei bytes devono ancora essere decryptati, il mio programmino non troverà un'emerita mazza! Ma siccome noi abbiamo (per definizione) un'intelligenza, facciamo un'altra semi-release del decrambler che andrà a decryptare subito quel pezzo di codice e toglierà il bogus. Dobbiamo anche ricordarci di noppare tutta questa procedura di decrypting perché noi oltre che a togliere il bogus da quel pezzo di file, decrypteremo anche il tutto. Vediamo un po' di farci i nostri bei calcoletti: prima EAX prende un indirizzo, ovvero 0044794B. Poi a questo gli viene aggiunto EBP. Alla fine si avrà EAX = 00404008. Siccome il file è stato dumpato col peditor, il raw offset corrispondente sarà 00004008. Quindi io ho fatto un programmino che esegue la stessa identica routine di decrypting a partire da 00404008 fino a 00404008 + 0003FEBE. Ora che abbiamo il codice già decifrato, dobbiamo noppare le operazioni di decrypting della routine del file. La routine inizia a 004459A4. Gli indirizzi su cui si trovano le istruzioni sono questi:
004459C6 ror bl, cl
004459EA xor bl, cl
00445A0E add bl, cl
00445A31 xor bl, cl
00445A56 ror bl, cl
Ogni istruzione di queste occupa 2 bytes. Per cui ogni istruzione deve essere rimpiazzata da 2 NOP (90h). I raw offset corrispondenti sono questi:
004459C6 -> 000459C6
004459EA -> 000459EA
00445A0E -> 00045A0E
00445A31 -> 00045A31
00445A56 -> 00045A56
Ok. La prima routine è stata
rimossa dal codice. Per questo, arrivati a quel punto, l'eseguibile si limiterà
a copiare quei bytes all'interno della memoria allocata dalla VirtualAlloc. Ora,
siccome abbiamo sul file i bytes del codice già decifrati, possiamo
tranquillamente usare il descrambler (con quale piccola modifica) in modo da
farlo funzionare solo sul nuovo pezzo di codice per rimuovere il bogus anche da
là in mezzo. Ovviamente lascio a voi effettuare le modifiche allo scrambler...
non posso perdere ogni volta tutto il tempo a scrivere il codice. ^^'
Ho effettuato il descrambling, ma come al solito qualcosina va storta... se devo
essere sincero non capisco come mai non va tutto alla perfezione, comunque, per
fortuna sono solo piccolezze da aggiustare. Ogni volta che il programma mi dà
errore, prendo il file con il bogus, vado allo stesso indirizzo dell'errore per
vedere dove devo effettuare la correzione. Vabè, le correzioni non sono
difficili da inserire. Andando avanti col codice arriviamo ad un punto
importante, ovvero all'indirizzo 006BB32A:
mov si, 'FG'
mov di, 'JM'
int 3
Fate molta arrenzione perché questo codice fa eccezione: se quando viene eseguito l'int 3, SI e DI hanno quei valori là, l'int 3 diventa un ottimo sistema per vedere se nel sistema è presente un debugger (come il SoftICE in questo caso). In situazioni normali, questa chiamata darebbe errore, ma siccome il debugger è presente, non succederà assolutamente nulla. Il nostro compito è quello di far comportare questo codice come se ci fosse il debugger, ovvero facendo dare errore al codice. Come fare? Beh, basta cambiare il valore di SI e di DI a runtime in modo che l'int 3 sia una semplicissima chiamata a interruzione. Così facendo dà errore. Attenzione... non dobbiamo steppare sull'int 3 col softice ma dobbiamo andare a continuare il nostro codice sulla SEH (structured exception handler, se non sapete cos'è, andate a farvi benedire). Comunque siamo magnanimi: come fare per vedere l'indirizzo della seh? Dal softice digitare:
dd fs:0
Così nella data window del softice appariranno tutte le dword della struttura fs:[0000]. Prendiamo la prima dword e facciamo:
dd ds:xxxxxxxx (dove al posto delle x ci mettete la dword)
Ancora una volta compariranno
altre dword nella data window. Di queste, la seconda a partire da sinistra
corrisponde all'indirizzo del gestore di eccezioni. Quindi, per andare là sopra,
prima di eseguire l'int 3, dopo aver cambiato DI e SI, digitiamo "g" seguito
dall'indirizzo della procedura.
Continuando l'esecuzione del codice, arriviamo in un punto dove possiamo vedere
del codice sistemato in questo modo:
pushfd ; pusha i flag nello stack
or byte ptr [esp+1], 1 ; abilita il trap flag
popfd ; ripoppa i flags col trap flag abilitato
nop ; single step
A prima vista non sembra nulla
di particolare. In realtà fa eccezione anche questo. Si tratta del trap flag. Se
questo flag è abilitato, lo step che viene eseguito col debugger provocherà
un'eccezione. Per ovviare anche a questo inconveniente, basterà mettere un
breackpoint nella riga successiva alla nop in modo da non steppare col softice
su quel codice e tutto andrà per il verso giusto. Arrivati a questo punto, devo
darvi una tristissima notizia... continuando di questo passo, dovrete
abbandonare il file senza bogus code e prendere sotto stepping il file kryptato
con tutte le protezioni di questo mondo. Questo perché ci sono dei controlli
relativi all'integrità del codice (violata più di una volta col descrambler
ecc... bla bla) e poi successivamente anche vengono eseguite delle istruzioni
con un tipo di bogus code diverso. Leggendo la spiegazione data prima su quel
tipo di bogus, potete facilmente intuire che sarebbe disumano creare tutte le
procedure per rimuovere altro bogus differente (anche perché queste istruzioni
saranno allocate in memoria e subiranno modifiche a runtime). Detto questo... il
descrambler ci ha facilitato la vita fino ad ora... ma è arrivato il momento di
subire la vera potenza del krypton... non vi preoccupate perché ovviamente la
faticaccia l'ho fatta io... a voi vi dico direttamente le cose principali perché
i dettagli non sono "spiegabili"... se ne avete voglia (see see... -.-)
steppatevi il programma dalla prima all'ultima istruzione.
Continuando a steppare, trovate altri due int 3 agli indirizzi 006B0BEE e
006B6638. Continuando il lavoro arriverete ad una chiamata a CreateThread.
Arrivati qua dovete mettere un breackpoint sul thread creato (006B6D35) e
premere F5 per continuare lo stepping da là dentro. Arrivati a 006BCDAF
troverete un'altro int 3. Poco dopo abbiamo 2 chiamate a CreateFile: una che
scrive su disco un file che si chiama kkk.tmp. Vediamo un po'... se andate a
vedere di che file si tratta, scoprirete che è un driver... precisamente è
l'antisoftice. E' facile... al momento della chiamata andate a cambiare il nome
di questo file in modo che la seconda CreateFile (chiamando il file col nome
originale) non trovi più questo file fallendo la chiamata al driver.
Adesso che il gioco sembra quasi fatto, arriva una parte inaspettata... come
tutorial, vi dico io che cosa succede. Voi vi aspetterete delle routine di
decrypting per il codice... beh si... è così... ma queste routine non sono
sempre le stesse, o per meglio dire... sono polimorfiche. Precisamente succede
che le istruzioni di decrypting possono essere spostate, posposte o anteposte le
une alle altre. Il modo migliore è quello di copiare per intero la routine che
si trova nel file kryptato per poi eseguirla nel nostro codice (ovviamente
l'indirizzo della routine non cambia, quindi dobbiamo solo prelevare i bytes).
Ricapitolando velocemente, Vediamo di cosa abbiamo bisogno per decryptare
completamente il file:
Copiare tutte le routine che servono per decifrare le sezioni di codice
Individuare l'indirizzo della IT originale
Individuare e copiare la routine di ripristino della IT
Aggiustare le FF15 (maledetti... :)
Rimuovere il loader del krypton dall'eseguibile
Aggiustare gli Header del PE in base alle nuove modifiche
Procedendo per ordine, vediamo di individuare tutte le
routine di decrypting per le sezioni di codice e di data. Innanzitutto bisogna
dire che ci troviamo all'interno dello spazio allocato in memoria (006B0000).
Ovviamente, ogni volta che dobbiamo copiare qualche informazione, dobbiamo
ricavare il raw offset ed applicare la routine di decrypting applicata
precedentemente dal mio descrambler per questa stessa porzione di memoria
(infatti ricordate che alla fine il nostro decrypter automatico processerà il
file originale e non quello senza bogus code). Ovviamente come avete letto in
precedenza, non sempre sarà possibile utilizzare il descrambler... ma ora basta
con le chiacchiere... passiamo ad analizzare il dekrypting!
Prima di tutto dobbiamo attendere che l'esegubile si unpacki da solo. Quindi
dobbiamo creare un processo, scriverci sopra un EBFE (jmp eip) dumpare e
proseguire col dekrypting. Ovviamente dobbiamo tracciarci tutto il krypton. Io
vi semplifico (per modo di dire) il lavoro dicendovi che la parte utile si trova
tutta nell'area di memoria allocata (nel mio caso 006E0000). Prima però di
proseguire, devo informarvi di un'altra cosa: da un certo punto in poi sn stato
costretto per ovvi motivi a cambiare eseguibile. Comunque il mio lavoro sarà
molto schematizzato e non corrispondera _esattamente_ al lavoro del krypton in
quanto cambio qualche indirizzo per farvi capire meglio il tutto. E ora...
preparatevi... tenete i nervi saldi e state pronti allo spettacolo (mi ripeto,
ho dovuto cambiare eseguibile, ma se volete debuggare cavia.exe, gli indirizzi
sono gli stessi, ma dovete sostituire il prefisso 006E con 006B perché cavia.exe
è più piccolo dell'eseguibile che ho usato io). Mi preoccupo per la vostra
incolumità... ma potete farcela. Mi raccomando:
L ' I M P A T T O S A R A ' T E R R I B I L E !
006E0000 (spazio allocato in
memoria)
006E0000 call 006EB122
006EB122 call 006EB128
006EB128 pop ebp
sub ebp, 00412A72
mov [ebp+004142DA], eax
mov eax, 00413C30
add eax, ebp
push eax
push dword ptr fs:[0000]
mov fs:[0000], esp
mov word ptr [ebp+00410412], FEEB
lea eax, [ebp+00412AC0]
mov ebx, [eax+02]
add ebx, ebp
mov [eax+02], ebx
lea eax, [ebp+00412ADB]
mov ebx, [eax+02]
add ebx, ebp
mov [eax+02], ebx
sub eax, eax
call 006EB17E
006EB17E push dword ptr
fs:[eax]
mov fs:[eax], esp
pushfd
; pusha i flag
or byte ptr [esp+1], 01
; abilita il trap flag
popfd
; ripoppa i flag col trap flag abilitato
nop
; single step
pop dword ptr fs:[eax]
pop eax
dec byte ptr [006EC975]
js 006EC883
; questo salto non deve avvenire perché
; altrimenti vuol dire che avete steppato
; sul single step col trap flag abilitato
call 006EB1A2
006EB1A2 pop ebp
sub ebp, 00412AEC
mov eax, 0040794B
add eax, ebp
; eax = 006E0000
mov ebx, 00449B70
add ebx, ebp
_loop01 add
ecx, [eax]
inc eax
cmp eax, ebz
jl _loop01
mov edi, 00410234
add edi, ebp
mov [edi], ecx
lea eax, [ebp+004142BF]
lea ebx, [ebp+00412C7D]
mov eax, [eax]
rol eax, 02
mov [ebx], al
mov byte ptr [ebx+01], 90
mov si, 'FG'
; magic value
mov di, 'JM'
; magic value
int 3
; questo deve saltare a SEH
SEH: 006EC2E5
006EC2E5 call 006EC2EA
006EC2EA pop ebp
sub ebp, 00413C35
mov eax, fs:[0000]
mov esp, [eax]
pop dword ptr fs:[0000]
mov eax, 00408479
mov ecx, 0040794B
add ecx, ebp
mov [ecx], 000000E8
mov [ebp+004142BB], ecx
lea esi, [ebp+0041429F]
; questa è la reale posizione della prima
; routine di dekrypting del codice (002D86B5)
lea edi, [ebp+00407BB4]
mov ecx, 6
; dimensione della routine
repz movsb
push eax
repz movsb
sub eax, eax
push dword ptr fs:[0000]
mov fs:[0000], esp
mov ecx, [ebp+004142BB]
jmp ecx
; ECX = 006E0000
006E0000 call
006E0005
006E0005 pop ebp
mov eax, ebp
sub ebp, 00447950
movzx esi, byte ptr [ebp+004142DE] ; numero di
sezioni da dekryptare
test esi, esi
jnz ????????
mov edi, ebp
mov word ptr [ebp+00410412], FEEB
006E01C1 mov ebx,
[ebp+004142DA] ; ImageBase
mov eax, [edi+004142E3]
; Virtual Offset della sezione da dekryptare
add ebx, eax
; EBX = 00401000 (.text)
mov ecx, [edi+004142E3+4]
; Dimensione della sezione
/* Apro un blocco di commenti
per spiegarvi meglio come stanno le cose: edi+004142E3
è una tabella che viene costruita durante
l'encrypting del file originale. Questa
tabella contiene tutti i virtual offset
delle sezioni da dekryptare con tutte le
rispettive dimensioni. Nel mio eseguibile
la situazione è strutturata in questo
modo:
edi+004142E3+00 = 00001000 -> VOffset prima sezione
edi+004142E3+04 = 00000400 -> Dimensione della prima sezione
edi+004142E3+08 = 00002000 -> VOffset seconda sezione
edi+004142E3+0C = 00000400 -> Dimensione della seconda sezione
edi+004142E3+10 = 00003000 -> VOffset terza sezione
edi+004142E3+14 = 00000800 -> Dimensione della terza sezione
In questo modo, il krypton ha uno schema
ben preciso di quelle che sono il numero
di sezioni da dekryptare e per ognuna di
esse ha un punto di partenza e il numero di
bytes che bisogna processare. Una volta
fatto questo esame, ritorniamo al codice,
precisamente alla prima routine di
dekrypting delle sezioni dell'eseguibile. */
mov al, [ebp+004142C5] ; questa è
una chiave usata per il dekrypting
006E0269 ror byte
ptr [ebx], cl
xor [ebx], al
add [ebx], al
nop
; fate attenzione a questa NOP
inc al
inc ebx
dec ecx
jnz 006E0269
add edi, 8
; passa alla sezione successiva
dec esi
; ESI contiene il numero di sezioni
jnz 006E01C1
; ritorna sopra per dekryptare la sez. successiva
sub eax, eax
mov [eax], ebx
; ovviamente questo fa eccezione, quindi
; dobbiamo saltare a SEH
SEH: 006E0B2E
006E0B2E call 006E0B33
006E0B33 pop ebp
sub ebp, 0040847E
mov eax, fs:[0000]
mov esp, [eax]
pop dword ptr fs:[0000]
lea eax, [ebp+0040873B]
push eax
mov dword ptr fs:[0000], esp
006E0BEE mov si,
'FG'
; magic value da evitare
mov di, 'JM'
; qua fate quello che volete
int 3
; stesso trucchetto di prima e salto a SEH
SEH: 006E0DF0
006E0DF0 call 006E0DF5
006E0DF5 pop ebp
sub ebp, 00408740
mov eax, fs:[0000]
mov esp, [eax]
pop dword ptr fs:[0000]
mov eax, 00449994
add eax, ebp
push eax
; EAX punta a "KERNEL32.DLL"
call [ebp+00449977]
; GetModuleHandle
mov [ebp+00410216], eax
mov eax, 00410381
add eax, ebp
push eax
; nome API
push dword ptr [ebp+00410216]
; hKernel
call [ebp+0044996F]
; GetProcAddress
mov [ebp+004103F6], eax
mov eax, 0041038E
add eax, ebp
push eax
; nome API
push dword ptr [ebp+00410216]
; hKernel
call [ebp+0044996F]
; GetProcAddress
mov [ebp+004103FA], eax
mov eax, 00410347
add eax, ebp
push eax
; nome API
push dword ptr [ebp+00410216]
; hKernel
call [ebp+0044996F]
; GetProcAddress
mov [ebp+004103E2], eax
mov [ebp+00415D7B], eax
mov eax, 00410353
add eax, ebp
push eax
; nome API
push dword ptr [ebp+00410216]
; hKernel
call [ebp+0044996F]
; GetProcAddress
mov [ebp+004103E6], eax
mov [ebp+00415D7F], eax
mov eax, 0041035D
add eax, ebp
push eax
; nome API
push dword ptr [ebp+00410216]
; hKernel
call [ebp+0044996F]
; GetProcAddress
mov [ebp+004103EA], eax
mov [ebp+00415D83], eax
mov eax, 0041039F
add eax, ebp
push eax
; nome API
push dword ptr [ebp+00410216]
; hKernel
call [ebp+0044996F]
; GetProcAddress
mov [ebp+004103FE], eax
mov [ebp+0041B6CD], eax
mov eax, 00410369
add eax, ebp
push eax
; nome API
push dword ptr [ebp+00410216]
; hKernel
call [ebp+0044996F]
; GetProcAddress
mov [ebp+004103EE], eax
mov [ebp+00415D87], eax
mov eax, 00410375
add eax, ebp
push eax
; nome API
push dword ptr [ebp+00410216]
; hKernel
call [ebp+0044996F]
; GetProcAddress
mov [ebp+004103F2], eax
mov eax, 004103A5
add eax, ebp
push eax
; nome API
push dword ptr [ebp+00410216]
; hKernel
call [ebp+0044996F]
; GetProcAddress
mov [ebp+00410402], eax
mov eax, 004103B2
add eax, ebp
push eax
; nome API
push dword ptr [ebp+00410216]
; hKernel
call [ebp+0044996F]
; GetProcAddress
mov [ebp+00410406], eax
mov eax, 004103C3
add eax, ebp
push eax
; nome API
push dword ptr [ebp+00410216]
; hKernel
call [ebp+0044996F]
; GetProcAddress
mov [ebp+0041040A], eax
mov eax, 004103D4
add eax, ebp
push eax
; nome API
push dword ptr [ebp+00410216]
; hKernel
call [ebp+0044996F]
; GetProcAddress
mov [ebp+0041040E], eax
cmp byte ptr [ebp+004437C7], FF
jnz 006E6510
006E6510 lea eax,
[ebp+0040E0AD]
push eax
push dword ptr fs:[0000]
mov fs:[0000], esp
006E6638 mov si,
'FG'
; da evitare come al solito
mov di, 'JM'
int 3
; questo ci porta ancora a SEH
SEH: 006E6762
006E6762 call 006E6768
006E6768 pop ebp
sub ebp, 0040EB02
mov eax, 0040DC2E
add eax, ebp
push eax
push 00
push 00
mov eax, 0040E680
add eax, ebp
push eax
; EAX = 006E6D35
push 00
push 00
call
[ebp+00410402]
; CreateThread -> da qua in poi bisogna
; passare alla ThreadProc
THREAD PROC: 006E6D35
006E6D35 call 006E6D3B
pop ebp
sub ebp, 0040E685
mov eax, 004102A6
add eax, ebp
push
eax
; EAX punta a "ADVAPI32.DLL"
push eax
mov eax, [ebp+0040997F]
cmp byte ptr [eax], CC
jz 00721F61
pop eax
call
[ebp+0044997F]
; LoadLibrary
mov [ebp+0041021A], eax
mov ebx, 004102B3
add ebx, ebp
mov esi, 004102DF
add esi, ebp
mov edi,
[ebp+0041021A] ;
hADVAPI32
_get_api push
ebx
; nome API
push edi
push eax
mov eax, [ebp+0044996F]
cmp byte ptr [eax], CC
jz 00721F61
pop eax
call [ebp+0044996F]
; GetProcAddress
add [esi], eax
_next_api
inc ebx
cmp byte ptr [ebx], 00
jnz _next_api
inc ebx
add esi, 04
cmp byte ptr [esi], FF
jnz _get_api
mov eax, 00449994
add eax, esp
push
eax
; EAX punta a "KERNEL32.DLL"
push eax
mov eax,
[ebp+00449977]
; GetModuleHandle
cmp byte ptr [eax], CC
jz 00721F61
pop eax
call
[ebp+00449977]
; GetModuleHandle
mov [ebp+00410216], eax
mov eax, 00413A84
add eax, ebp
push
eax
; nome API
mov eax,
[ebp+00410216]
; hKernel
push eax
push eax
mov eax, [ebp+0044996F]
cmp byte ptr [eax], CC
jz 00721F61
pop eax
call
[ebp+0044996F]
; GetProcAddress
add [ebp+004102EC], eax
push eax
mov eax, [ebp+004102EC]
cmp byte ptr [eax], CC
jz 00721F61
pop eax
call
[ebp+004102EC]
; GetCommandLine
push
eax
; lpCommandLine
mov edi, eax
xor al, al
mov ecx, -1
repnz scasb
neg ecx
dec ecx
pop esi
mov edi, 00410252
add edi, ebp
xor ebx, ebx
_mk_key mov
bl, [esi]
cmp bl, 5C
jnz 006E7B8A ; a
questo indirizzo il char '\' viene cambiato in '-'
mov [edi], bl
inc esi
inc edi
loopnz _mk_key
mov byte ptr [edi], 00
mov eax, 004102A2
add eax, ebp
push eax
mov eax, 0041029E
add eax, ebp
push eax
push 00
push 001F0003
push 00
push 00
push 00
mov eax, 00410241
add eax, ebp
push eax
; nome della chiave di registro
mov eax, [ebp+0041022A]
push eax
push eax
mov eax, [ebp+004102DF]
cmp byte ptr [eax], CC
jz 00721F61
pop eax
call
[ebp+004102DF]
; RegCreateKeyExA
mov eax, [ebp+0041023D]
push eax
mov eax, 00410234
add eax, ebp
mov ebx, [eax]
mov [ebp+00411787], ebx
push eax
push 03
push 00
mov eax, 0041022E
add eax, ebp
push eax
...
EAX =
C69ABDD0
; questo non so cosa sia... il debugger non
...
; mi ha fatto vedere... suppongo una mov... boh :)
push eax
push eax
mov eax, [ebp+004102E3]
cmp byte ptr [eax], CC
jz 00721F61
pop eax
call
[ebp+004102E3]
; RegSetValueExA
movzx esi, byte ptr [ebp+004142DE] ;
Numero delle sezioni da dekryptare
test esi, esi
jz 006E878C
mov edi, ebp
_dekr
mov ebx,
[ebp+004142DA] ;
ImageBase
mov eax,
[edi+004142E3] ;
La tabella coi valori descritta sopra
add ebx, eax
mov ecx,
[edi+004142E3+4] ;
Dimensione del codice da dekryptare
006E8632 rol
byte ptr [ebx], cl
xor [ebx], cl
inc ebx
dec ecx
jnz 006E8632
add edi, 8
; Sezione successiva
dec esi
; Le sezioni sono finite?
jnz _dekr
mov edx, [ebp+004142DA]
; ImageBase
mov esi, [ebp+004142CE]
; Import Table
add esi, edx
mov byte ptr [ebp+00407D9D], 8B
; 8B è una MOV
mov eax, [esi+0C]
or eax, eax
jz 006E9EE6
; Errore
add eax, edx
; EAX punta a "KERNEL32.DLL" dalla IT
mov ebx, eax
push eax
; "KERNEL32.DLL"
push eax
mov eax, [ebp+00449977]
cmp byte ptr [eax], CC
jz 00721F61
pop eax
call [ebp+00449977]
; GetModuleHandle
or eax, eax
jnz 006EA8C9
006EA8C9 cmp word
ptr [ebp+00410412], FEEB
jnz 006E08FB
mov [ebp+004142C6], eax
mov dword ptr [ebp+004142CA], 00000000
cmp dword ptr [ebp+00447813], 00000000
jnz 006E8DC0
006E8DC0 mov edx,
[ebp+004142DF]
; 00001000
add edx, [ebp+004142DA]
; ImageBase
mov [ebp+00447813], eax
...
EDX = 00414646 ; Non ho visto nemmeno questo... un altro
mov?
...
; Qualsiasi cosa sia non ci interessa per il reversing :)
add edx, ebp
push edx
push dword ptr fs:[0000]
mov dword ptr fs:[0000], esp
push esi
006E8DDF mov edx,
[ebp+004142DA]
;ImageBase
mov eax, [esi]
; ESI = 00402050 (import table)
or eax, eax
jnz 006E8EC0
006E8EC0 add eax,
edx
add eax, [ebp+004142CA]
; nAPI
mov ebx, [eax]
mov edi, [esi+10]
add edi, edx
add edi, [ebp+004142CA]
; nAPI
push edi
push eax
mov edi, [ebp+004117BE]
sub eax, edi
cmp edi, 00000000
jg 006E9114
mov [ebp+004117BE], eax
pop eax
pop edi
test ebx, ebx
jz 006E9E77
test ebx, 11000000
jnz 006E93E0
test ebx, 80000000
jnz 006E93E0
add ebx, edx
add ebx, 2
cmp word ptr [ebx], 'ataF'
jnz 006E93D5
mov [ebp+00411783], ebx
006E9462 nop
; inizio ricostruzione della import tab.
nop
; attenzione a questi due nop
ror byte ptr [ebx], 2
; EBX punta al nome dell'api
inc ebx
cmp byte ptr [ebx], 00
; il nome dell'api è stato decryptato?
jnz 006E9462
mov ebx, [ebp+00411783]
nop
and ebx, 0FFFFFFF
push ebx
; nome dell'api
push dword ptr [ebp+004142C6]
; hModule
call [ebp+0044996F]
; GetProcAddress
cmp dword ptr [ebp+00411783]
jz 006E960B
mov ecx, [ebp+00411783]
006E95B4 nop
; Attenzione :)))
nop
nop
shl byte ptr [ecx], cl
xor [ecx], al
inc ecx
cmp byte ptr [ecx], 00
jnz 006E95B4
/* La routine che vedete qua sopra è un
anti-dumping: cancella i nomi delle api */
or eax, eax
jz 006E08FB
cmp byte ptr [ebp+004142BA], FF
jnz 006E9DE3
pushad
mov ebx, [ebp+00449953]
mov [ebp+0041177A], eax
lea edi, [ebp+0041178F]
add dword ptr [ebp+004117B6], 0000001F
add ebx, [ebp+004117B6]
mov [ebp+004117BA], ebx
mov eax, ebx
006E9810 mov cl,
[edi]
mov [ebx], cl
inc ebx
inc edi
cmp dword ptr [edi], 00000000
jnz 006E9810
mov ecx, eax
add ecx, 1B
mov [eax+02], ecx
mov [eax+11], ecx
mov [eax+0B], ecx
mov ebx, eax
mov eax, [ebp+00411787]
mov ecx, 41C64E6D
mul ecx
add eax, 00003039
and eax, 7FFFFFFF
mov [ebp+00411787], eax
cmp eax, 3F000000
jge 006E9C98
mov [ebx+06], eax
mov [ebx+15], eax
xor [ebx+1B], eax
popad
mov eax, [ebp+004117BA]
mov [edi], eax
add dword ptr [ebp+004142CA], 04
jmp 006E8DC0
; salta sopra
add esi, 14
mov edx, [ebp+004142DA]
; ImageBase
jmp 006E0452
006E0452 mov eax,
[esi+0C]
or eax, eax
jz 006E9EE6
006E9EE6 pop esi
jmp 006E94F1
006E94F1 mov eax,
[ebp+004117BE]
006E9F55 mov byte
ptr [esi], 00
inc esi
cmp esi, eax
jnz 006E9F55
mov eax, 00413AA3
add eax, ebp
push eax
; EAX punta a "GetVolumeInformationA"
mov eax, [ebp+0044994B]
; hKernel
push eax
push eax
mov eax, [ebp+0044996F]
cmp byte ptr [eax], CC
jz 00721F61
pop eax
call [ebp+0044996F]
; GetProcAddress
add [ebp+00413AC1], eax
mov eax, 005C3A43
; "C:\"
mov ebx, 004499A5
add ebx, ebp
mov [ebx], eax
mov ebx, 004499A5
add ebx, ebp
push 00
push 00
mov eax, 004499B9
add eax, ebp
push eax
mov eax, 004499BD
add eax, ebp
push eax
mov eax, 004499C9
add eax, ebp
push eax
push 00000104
mov eax, 0044369B
add eax, ebp
push eax
push ebx
; "C:\"
push eax
mov eax, [ebp+00413AC1]
cmp byte ptr [eax], CC
jz 00721F61
pop eax
call [ebp+00413AC1]
; GetVolumeInformationA
mov mov eax, 0044369B
add eax, ebp
; EAX punta a "MATTIA" ovvero alla
; label del vostro hard disk
mov eax, [eax]
mov ebx, 004499C9
add ebx, ebp
mov ebx, [ebx]
add eax, ebx
mov edi, 00410234
add edi, ebp
mov [edi+04], eax
xor ebx, ebx
mov eax, 00410252
add eax, ebp
006EA861 mov edi,
00410234
add edi, ebp
mov ecx, 8
006EA8F9 mov bl,
[eax]
add [edi], bl
inc eax
cmp byte ptr [eax], 00
jz 006EAAD7
inc edi
dec ecx
test ecx, ecx
jnz 006EA8F9
cmp byte ptr [eax], 00
jnz 006EA861
006EAAD7 ...
EAX = 8
...
push eax
mov eax, 00410234
add eax, ebp
push eax
push 03
push 00
mov eax, 0041022E
add eax, ebp
push eax
mov eax, [ebp+0041029E]
push eax
mov eax, [ebp+004102E3]
cmp byte ptr [eax], CC
jz 00721F61
pop eax
call [ebp+004102E3]
; RegSetValueExA
mov eax, [ebp+0041029E]
push eax
push eax
mov eax, [ebp+004102E7]
cmp byte ptr [eax], CC
jz 00721F61
pop eax
call [ebp+004102E7]
; RegCloseKey
lea eax, [ebp+00413F30]
cmp byte ptr [eax], 33
jnz 006EB0AB
006EB0AB lea eax,
[ebp+00410F02] ; Da qua inizia una routine utile
dopo per
; aggiustare uno dei 5 tipi di FF15 (FF15-00)
mov ecx, 0000087C
; Dimensione dell'iterazione
sub ebx, ebx
; Azzera EBX
006ECAA5 add ebx,
[eax] ; Praticamente questa routine calcola un
valore che verrà
dec ecx
; usato come chiave di xoring sul codice. Questo valore
inc eax
; viene calcolato con una funzione simile a quella di un
test ecx, ecx ; CRC.
Per questo è importante che il codice sia integro
jnz 006ECAA5 ;
ed esente da breakpoint al momento dell'iterazione
lea eax, [ebp+004142BA]
sub ecx, ecx
mov eax, [eax]
mov cl, al
add ebx, ecx
mov [ebp+0041C4C0], ebx
; la chiave viene salvata
mov eax, [ebp+004142DA]
; ImageBase
mov [ebp+0041C4DD], eax
lea ebx, [ebp+0040794B]
lea ecx, [ebp+00449B70]
006ECBB6 mov byte
ptr [ebx], 00
inc ebx
cmp ebx, ecx
jnz 006ECBB6
; questo fa eccezione perché viene
; sovrascritto anche il codice corrente.
; quindi si passa a SEH! :D
SEH: 006ECCFB
006ECCFB mov eax,
fs:[0000]
mov esp, [eax]
pop dword ptr fs:[0000]
call 006ECD0D
006ECD0D pop ebp
sub ebp, 00414658
lea eax, [ebp+00415D8B]
push eax
push dword ptr fs:[0000]
mov fs:[0000], esp
006ECDAF mov si,
'FG'
mov di, 'JM'
int 3
; Esattamente come prima! Tutti a SEH!
SEH: 006EE440
006EE440 mov eax,
fs:[0000]
mov esp, [eax]
pop dword ptr fs:[0000]
call 006EE452
006EE452 pop ebp
sub ebp, 00415D9D
mov cx, ss
xor cl, cl
jecxz 006EE4B2
push 00
push 00
push 02
push 00
push 00
push 40000000
lea ebx, [ebp+00415D6B]
push ebx
; EBX punta a "KKK.TMP"
call [ebp+00415D7B]
; CreateFile (crea kkk.tmp sul disco)
mov [ebp+00415D73], eax
push 00
lea ebx, [ebp+00415D77]
push ebx
push 000012FB
lea ebx, [ebp+00414A63]
push ebx
push eax
; hFile
call [ebp+00415D7F]
; WriteFile (scrive il contenuto del file)
push [ebp+00405D73]
; hFile
call [ebp+00415D83]
; CloseHandle (chiude l'open handle del file)
/* E' arrivato il momento di un'altro blocco di commenti! Come
potete notare dal codice
sopra, il krypton crea sull'hard disk un
file che si chiama kkk.tmp. Se provate ad
interrompere il debugging e ad aprire
questo file, potete facilmente accorgervi che
si tratta di un driver, precisamente di un
VxD. E' l'anti-softice. Questo driver non
si deve debuggare ma semplicemente
eviteremo che esso venga installato :) Come? Beh
per adesso è stato creato su disco. Questo
implica che dopo troveremo un'altra chiamata
a CreateFile col compito di installare il
driver. Per evitare che questo accada, basta,
prima di eseguire la chiamata, modificare
il nome del file che deve essere aperto.
Nella CreateFile, seguendo il modello LIFO
dello stack, il punutatore al nome corrispon-
de all'ultima DWORD pushata. Per questo,
per modificare il nome del file, dovete digi-
tare nel softice "e *esp". Modificate il
nome del file e premete F10 per far fallire
la chiamata. Mi raccomando, dopo il
fallimento, rimettete a posto il nome del file per
non incorrere in qualche controllo di
integrità del codice. Arriviamo subito al punto */
push 00
push 04000000
push 00
push 00
push 00
lea ebx, [ebp+00415D5f]
push ebp
; Puntatore al nome del driver (da modificare)
006EE922 call
[ebp+00415D7B]
; CreateFile (questa chiamata deve fallire)
push eax
; hFile
call [ebp+00415D6B]
; CloseHandle
lea ebx, [ebp+00415D6B]
push ebx
; EBX punta a "KKK.TMP"
call [ebp+00415D87]
; DeleteFile
cmp byte ptr [0041C4DC], FF
jz 006EEB39
006EEB39 call 006EEB3E
006EEB3E pop ebp
sub ebp, 00416489
mov eax, 002DC6C0
push 40
push 1000
push eax
push 00
push eax
mov eax, [ebp+0044992C]
cmp byte ptr [eax], CC
jz 00721F61
pop eax
call [ebp+0044992C]
; VirtualAlloc
mov [ebp+0041C4CA], eax
mov edi, [ebp+0041C4CA]
lea esi, [ebp+00416B27]
mov [esi], edi
add dword ptr [esi], 4
lea esi, [ebp+00416B27]
mov edi, [ebp+0041C4CA]
mov ecx, 725
repz movsb
mov esi, eax
mov eax, 00401000
mov ecx, [ebp+00426155]
006EEFFD cmp word
ptr [eax], 15FF ; Inizia la scansione
per il primo FF15
jnz 006EF0F3
cmp dword ptr [eax], 05
; Questo lo uso per distinguere gli FF15
jnz 006EF0F3
; ad esempio in questo caso lo chiamo FF15-05
mov [eax+02], esi
006EF0Fe inc eax
dec ecx
test ecx, ecx
jnz 006EEFFD
OK fermiamoci un minuto! (beh dovrò pur spiegarvi in qualche modo no? :). Alor... dopo tutto questo diavolo di codice (cazz... non è uno scherzo ricopiarlo tutto...) siamo arrivati ai famosi FF15. Voi dite << famosi perché? >> Beh... grazie a dio voi non li avete affrontati come ho fatto io. ^^' Innanzitutto chiariamo. Per la spiegazione partiamo dall'eseguibile originale (ora capirete anche perché sono stato costretto a cambiare eseguibile). Quando viene kryptato, il krypton itera per tutta la sezione di codice alla ricerca di determinate istruzioni (parliamo di istruzioni lunghe 6 bytes che vedremo dopo con calma) e le sostituisce con del suo codice. Questo codice consiste in 2 bytes: FF 15 seguiti da una DWORD che contiene un numero che varia da 0 a 5. FF 15 sono le opcodes di una CALL FAR. Arrivati a questo punto del programma, il loader del krypton, itera per tutta la sezione di codice proprio alla ricerca di queste FF 15. Appena ne trova una vede che DWORD hanno dopo. Questa DWORD io la uso per distinguere i vari FF15. E non solo io! Anche il krypter! Infatti questi numeri sono provvisori: col ciclo riportato sopra, quella dword che contiene il numero dell'ff15 viene sostituita con una dword che contiene l'indirizzo di una procedura. Sarà in questa procedura che avverà il dekrypting delle istruzioni originali che poi saranno rimesse al loro posto. Devo dirvi un'altra cosetta: questo di cui vi ho parlato è il meccanismo utilizzato dagli FF15-00, FF15-01, FF15-02, FF15-03 e FF15-04. Infatti proprio il primo caso fa eccezione. l'FF15-05 corrisponde al K-Execution previsto dal krypter all'interno della sezione di codice. Se leggete la guida del Krypton v0.5, troverete all'interno le istruzioni su come utilizzare questo manual K-Execution. L'FF15-05 è proprio il meccanismo di crittografia che viene usato per proteggere il codice all'interno del blocco del manual k-exec. E' il primo caso... non c'è niente da fare... dobbiamo analizzarlo! Per analizzarlo dobbiamo steppare nella procedura che lui va ad inserire dopo le FF15. Quindi con un "g 00401000" arriviamo col SoftICE all'original entry point e steppiamo fino a quando non incontriamo la FF15 (potete quindi capire che mi son dovuto scrivere altri eseguibili con dentro il manual k-execution per fare questo reversing. Non solo, ho dovuto trovare tutti i casi di istruzione che vengono sostituite dagli altri FF15 per questo mi son scritto parecchi eseguibili. Siccome questo tutorial è stato "interattivo" (ovvero l'ho scritto mentre reversavo e non l'ho incominciato una volta finito il reversing), all'inizio, non conoscendo questo krypter, non potevo immaginare che ci fosse una cosa del genere... (anche se poi vedendola mi son ricordato del krypton3... ma è passato un macello di tempo :). Ora mano alla procedura della FF15! Per analizzarlo dobbiamo prina visionare il codice sorgente dell'esebuibile originale per poi passare ad analizzare l'eseguibile kryptato partendo tranquillamente dall'original entry point. Vediamo 'sto sorgente...
.code main: push 0 push offset appname push offset msgtext push 0 call MessageBox db 0EBh, 0Eh db 'KDES' db 00, 00, 00, 00, 00 db 00, 00, 00, 00, 00 mov eax, ebx mov ecx, eax xor ebx, edx db 0EBh, 0Eh db 'KDEE' db 00, 00, 00, 00, 00 db 00, 00, 00, 00, 00 push 0 call ExitProcess call GetModuleHandle call GetTickCount end main |
Non è difficile da capire. Vediamo un po' la situazione... ho messo qualche istruzione ad capocchiam tanto per avere qualche punto di riferimento all'interno del codice una volta assemblato. Quello che però veramente ci interessa è la procedura che si trova all'interno della dichiarazione del manual k-execution (usare il KDES/KDEE o usare il KEES/KEEE è la stessa cosa, come reversing non cambia nulla e anche l'algoritmo di unpacking è identico). Vediamo innanzitutto che cosa succede all'OEP dell'eseguibile assemblato:
00401000
push 00
push 00403019 ;
offset AppName
push 00403000 ;
offset msgtext
push 00
call 0040105C ;
MessageBox
_old_kdes
push 000169EB ;
[esp+24]
push 000107C2 ;
[esp+20]
call [00820000] ; dword ptr
[00820000] = 00820004
...
...
Ecco qua un po' come si presenta la situazione sotto il softice dopo che arriviamo all'entry point (tenete conto che ormai il numero che vien dopo l'FF15 è stato sostituito con l'indirizzo del puntatore della procedura da chiamare!). La MessageBox non si vede perché il krypton, al contrario di quello che faremo noi, prende gli indirizzi delle api che gli servono e le salva all'interno di una sua import table. Da questa richiamerà tutte le procedure del nostro eseguibile. Quello che a noi interessa ora è il codice che inizia ad _old_kdes. Se fate caso, nel codice originale, al posto di quelle due push, c'era la dichiarazione del manual k-execution. Iniziamo a capire come funziona: la dichiarazione viene sostituita da queste istruzioni qua. Innanzitutto vi vengo incontro dicendovi che i valori pushati sono fondamentali per ripristinare il codice di partenza. Ora non ci resta altro da fare che analizzare.
00820004
push eax
pushfd
mov eax, [esp+08]
; RetVal
push ebx
push ecx
push esi
push edi
push ebp
call 00820014
00820014 pop ebp
sub ebp, 00416B3B
mov esi, [ebp+00416E1F]
xor [esp+20], esi
; In questo si ha [esp+20] = alla
; dimensione del bytes di codice all'interno
; del blocco del manual k-execution.
xor [esp+24], si
mov ecx, [esp+20]
; dimensione
mov ebx, [esp+24]
lea edi, [ebp+00417248]
mov esi, eax
repz movsb
mov ecx, edi
add ecx, 6
mov dword ptr [edi], 000025FF
mov [edi+02], ecx
mov [edi+06], esi
mov ebx, [esp+24]
and ebx, FFFF0000
cmp ebx, 00001000
jnz 00820443
mov ecx, [esp+20]
mov esi, eax
dec esi
008203B5 inc esi
mov byte ptr [esi], 00
loopnz 008203B5
mov ebx, [esp+24]
mov ecx, [esp+20]
lea edi, [ebp+00417248]
xor eax, eax
mov ax, bx
008206EA neg cl
; questa è la procedura di dekrypting
add [edi], cl
; del codice.
xor [edi], cl
rol byte ptr [edi], cl
neg cl
sub [edi], al
add [edi], ah
xor [edi], al
rol byte ptr [edi], cl
xor [edi], ah
inc edi
loopnz 008206EA
Basta fin qua. Alor: a partire da 008206EA inizia la procedura di dekrypting del codice camuffato. EDI punta a questo codice, ECX contiene la dimensione del codice da dekryptare. Per quanto riguarda EAX, possiamo calcolarlo andando a ritroso nella procedura. Questo proviene da [ESP+24] xorato con la dword ptr [ebp+00416E1F]. Quindi non è difficile da dekryptare. Basta prelevare dal codice dell'eseguibile dumpato tutto il materiale che ci serve e ripetere la stessa identica operazione (il codice ve lo posto tutto insieme alla fine del tutorial. Per ora passiamo avanti col reversing). Possiamo ritornare al codice del krypton. Proseguiamo con l'analisi del secondo FF15. Precisamente partiamo da 006BF905.
006BF905
cmp byte ptr [ebp+0041C4CE], FF
jnz 006BFC10
mov eax, 00401000
mov ecx, [ebp+0041C4E1]
; dimensione da scansionare
lea ebx, [ebp+0041B83B]
mov [ebp+0042FDC1], ebx
lea ebx, [ebp+0042FDC1]
006BFA81 cmp word
ptr [eax], 15FF
jnz 006BFB4F
cmp dwoed ptr [eax+02], 00000000 ;
FF15-00
jnz 006BFB4F
mov [eax+02], ebx
006BFB4F inc eax
dec ecx
test ecx, ecx
jnz 006BFA81
Anche qua la stessa cosa del codice di prima: viene ricercato l'FF15 nel file e gli viene piantato dentro l'indirizzo della procedura da eseguire. Questa volta però la cosa sarà più semplice da gestire. Infatti il problema più grosso era l'FF15-05 in quanto la dimensione del blocco di codice da decifrare cambiava e i dati erano forniti da quelle due push prima della call. In questi altri quattro casi abbiamo degli FF15 che dekryptano sempre 6 bytes. Per questo son dovuto andare a tentoni per trovare tutte le istruzioni che venivano sostituite dagli FF15 per creare degli eseguibili da reversare. Vediamo un po'... un'istruzione che viene sostituita con un FF15-00 è ad es:
mov ecx, dword ptr [offset]
Ok. Basta creare un eseguibile che contenga quest'istruzione:
.code main: push 0 push offset appname push offset msgtext push 0 call MessageBox mov ecx, dword ptr [_myoff] push 0 call ExitProcess _myoff: call GetModuleHandle call GetTickCount end main |
Questo era il mio eseguibile. L'ho compilato, l'ho kryptato, e sono arrivato di nuovo a 00401000. Vediamo questa volta che cosa cambia nel codice:
00401000 push
00
push 00403019
; offset AppName
push 00403000
; offset msgtext
push 00
call 0041103C
; MessageBox
ffacll
call [006D8476]
; FF15-00
push 00
call 0040102A
; ExitProcess
call 00401030
; GetModuleHandle
call 00401036
; GetTickCound
Vediamo un po': la prima cosa che deve saltarvi all'occhio è che questa volta il codice successivo alla FF15 non è cambiato (invece nel primo caso dovevamo dekryptare anche quello successivo). Allora il lavoro si restringe notevolmente in quanto questa volta, al posto della FF15 (6 bytes) dobbiamo reinserire il codice originale (sempre di 6 bytes), ma questa volta non ci sono push o altri trucchetti prima. Anche qua andiamo ad analizzare la FAR-PROC chiamata dalla FF15:
006C3EF0 push
ecx
pushfd
sub dword ptr [esp+08], 6
; così [ESP+08] = ffcall (RetVal)
mov eax, [esp+08]
; ffcall
push eax
push edx
push ebp
push ebx
call 006C3F04
006C3F04 pop ebp
sub ebp, 0041B84F
not dword ptr [ebp+0041C4D8]
test edx, edx
jz 006C49E7
mov edx, [ecx]
;
ffcall
mov [ebp+0041C4E5], edx
mov edx, [ecx+04]
; ffcall + 4
mov [ebp+0041C4E5+04], edx
mov edx, [ecx+08]
; ffcall + 8
mov [ebp+0041C4E5+08], edx
mov edx, [ecx+0C]
; ffcall + 12
mov [ebp+0041C4E4+0C], edx
mov word ptr [ecx+6], 15FF
; questo non ci interessa affatto
mov edx, [ecx+02]
; ffcall + 02 (ma non ci interessa)
mov [ecx+08], edx
; e nemmeno questo c'entra col reversing
lea eax, [ebp+0041C509]
; offset _nostri_bytes - 04
mov ebx, 0041C4C0
add ebx, ebp
; EBX punta ad una chiave di xoring
006C41D5 mov edx,
[eax]
xor edx, [ebx]
sub edx, ecx
test edx, edx
; Verifica
jz 006C4303
add eax, 0A
; Da questo capiamo che tra i vari FF15-0
jmp 006C41D5
; i bytes sono a 10 bytes di distacco.
006C4303 mov edx,
[eax+4]
; Nostro codice kryptato
xor edx, [ebx]
mov [ecx], edx
; Rimpiazza la FF15 col codice giusto (4 bytes)
mov dx, [eax+08]
; Ultimi due bytes per l'istruzione
xor edx, [ebx]
mov [ecx+04], dx
; Rimpiazza gli ultimi 2 bytes
pop ebx
pop ebp
pop edx
pop eax
popfd
pop ecx
ret
Anche questo FF15 è stato reversato. Passiamo avanti e proviamo gli altri (che poi io li ho già provati tra l'altro ^^'). Il mprossimo caso è quello del jump far. Si tratta dell'FF15-01. Come al solito vediamo il codice sorgente dell'eseguibile originale:
.data myoff dd offset _end_code .code main: push 0 push offset appname push offset msgtext push 0 call MessageBox jmp [myoff] push 0 call ExitProcess _end_code: call GetModuleHandle call GetTickCount end main |
Ora vediamo il codice del krypton al momento della scansione:
006BFC30 cmp
byte ptr [ebp+004104D7]
jnz 006BFF26
mov eax, 00401000
mov ecx, [ebp+00426155]
; Dimensione del codice da scansionare
lea ebx, [ebp+0041BD6A]
mov [ebp+0042FDC5], ebx
lea ebx, [ebp+0042FDD5]
006BFD82 cmp word
ptr [eax], 15FF
jnz 006BF363
cmp dword ptr [eax+02], 00000001
; FF15-01
jnz 006BF363
mov [eax+02], ebx
006BF363 inc eax
dec ecx
test ecx, ecx
jnz 006BFC30
Amici... è la stessa identica cosa! Questa volta qualconisa cambia nella procedura sostituita... ma alla fine si tratta sempre della stessa roba. Vediamo questa volta di cosa si tratta:
006C441F
push edi
push ecx
pushfd
mov ecx, [esp+0C]
; RetVal
sub ecx, 6
; ffcall
push edx
push ebp
push ebx
call 006C4431
006C4431 pop ebp
sub ebp, 0041BD7C
mov edx, [ecx]
mov [ebp+00426159], edx
mov edx, [ecx+04]
mov [ebp+00426159+04], edx
mov edx, [ecx+08]
mov [ebp+00426159+08], edx
mov edx, [ecx+0C]
mov [ebp+00426159+0C], edx
lea eax, [ebp+0042617D]
; puntatore ai nostry bytes (sempre meno 4)
mov ebx, 0042614D
add ebx, ebp
; punta a key di xor. Questa volta è statica
_do_verify
mov edx, [ecx]
xor edx, [ebx]
sub edx, ecx
test edx, edx
; verifica la posizione della ffcall
jz _is_zero
add ecx, 0A
; anche qua 10 bytes di stacco tra i codici
jmp _do_verify
_is_zero mov edx,
[ecx+04]
xor edx, [ebx]
test dl, dl
...
Sentite basta così (O_O sono pazzo!). La procedura è identica alle altre! (non sono pazzo :) Anche se potete notare qualche differenza. Al contrario delle altre, questa rocedura analizza il nostro codice per poi fare da sè un salto sul punto desiderato con un jmp [eax]. L'unica cosa che dobbiamo tener presente è il primo byte: dovrebbe essere FF ma diventa zero. Quindi dopo il promo xoring, dobbiamo impostare DL = FF. Cambiano ovviamente (ma questo è normale -.-) la chiave di xoring (che questa volta è statica... non c'è bisogno di generarla in base al file) e la posizione dei bytes. Passiamo avanti! FF15-02: Istruzione:
mov dword ptr [offset], numero ; 10 bytes
Come al solito vediamo il nostro eseguibile:
.code main: push 0 push offset appname push offset msgtext push 0 call MessageBox mov dword ptr [mydd], 333h mov dword ptr [mydd2], 444h push 0 call ExitProcess call GetModuleHandle call GetTickCount end main |
Facile facile. Vediamo come al solito il codice del krypton:
006BFF4A cmp
byte ptr [ebp+0041C4CF], FF
jnz 006C062C
mov eax, 00075300
push 40
push 00001000
push eax
push 00
push eax
mov eax, [ebp+0044992C]
cmp byte ptr [eax], CC
jz 007AF161
pop eax
call [ebp+0044992C]
; VirtualAlloc
mov [ebp+0041C4BC], eax
mov eax, 00401000
mov edx, [ebp+0042FDE5]
; dimensione
mov edi, [ebp+00401C4BC]
; AllocMem
mov [ebp+0042FDC9], edi
lea ebx, [ebp+0042FE0D]
; questa loc è importante
006B02A7 cmp word
ptr [eax], 15FF
jnz 006B05F6
cmp dword ptr [eax+02], 02
jnz 006B056F
mov edi, [ebx]
mov [ebp+0041B7CD], edi
mov edi, [ebx+04]
mov [ebp+0041B7CD+04], edi
...
add ebx, 8
; per logica suppongo sia una ADD
...
lea esi, [ebp+0041B78D]
mov edi, [ebp+0042FDC9]
mov [esi], edi
add dword ptr [esi], 04
mov [eax+02], EDI
mov ecx, 48
repz movsb
mov [ebp+0042FDC9], edi
006B056F inc eax
dec edx
test edx, edx
jnz 006B02A7
Soffermiamoci sul rigo prima di 006B02A7. Se col SoftICE proviamo a visualizzare il contenuto della locazione ebp+0042FE0D troviamo 4 DWORD. Vi anticipo io qualcosa: sono le dword utilizzate dal programma per riemulare l'istruzione mov [xxxxxxxx], xxxxxxxx. Si raggruppano a due a due per ogni FF15-02. Ma andiamo con calma. Prima di tutto vediamo come viene mutato il codice una volta arrivati all'entry point:
00401000
push 00
push 00403019
; offset AppName
push 00403000
; offset msgtext
push 00
call 00401040
; MessageBox
push eax
push ebx
call [00B00000]
; ffcall
pop ebx
pop eax
push eax
push ebx
call [00B0000X]
; ffcall
pop ebx
pop eax
push 00
call 0040102E
; ExitProcess
Come vedete il krypter, per recuperare i 4 bytes (perché la nostra istruzione è di 10 bytes mentre la FF15 ne prende solo 6) utilizza due push e due pop che non fanno nulla. Ora che avete capito come sta sistemato il codice passiamo ad analizzare la procedura chiamata dalla FF15:
00B00004
push ebp
push eax
push ebx
pushfd
call 00B0000D
00B0000D pop ebp
sub ebp, 0041B79A
lea eax, [ebp+0041B7CD]
; questa è la locazione che contiene le 4 dword
mov ebx, [eax]
; in ebx la prima dword
mov eax, [eax+04]
; in eax la seconda dword
xor ebx, eax
; così otteniemo la lpDword
add eax, ebx
; così invece otteniemo il valore da inserire
mov [ebx], eax
; ecco la nostra istruzione eseguita diversamente!
and eax, FFFF0000
lea ebx, [ebp+0041B7C9]
mov ebx, [ebx]
shr ebx, 18
cmp ebx, eax
popfd
pop ebx
pop eax
pop ebp
ret
Quindi: per ottenere i valori, prendiamo le DWORD a due a due e le mettiamo una in EAX e l'altra in EBX. Xorando EAX con EBX si ottiene la lpDWORD. Aggiungendo questa alla chiave di xoring si ottiene il valore da inserire (notevole eh?). Anche questa è fatta signori! Passiamo all'ultimo punto della nostra incredibile sfaticata! L'FF15-03! Siamo nel caso della cmp:
cmp dword ptr [xxxxxxxx], xx
Con questo caso abbiamo una piccolissima differenza rispetto agli altri. Non posso farvi l'esempio del codice perché il krypton non trasforma tutte le cmp in FF15. Infatti il programma seleziona le istruzioni da trasformare in base a dei suoi criteri e controllando se la sostituzione è possibile o meno. Vabè... comunque il problema non si pone: ho preso le istruzioni di esempio dall'eseguibile del krypton stesso (sapete che è kryptato con sè stesso no? :P). Ok: si parte da 00330650:
00330650
cmp byte ptr [ebp+0041C4D0], FF
jnz 003336D4
mov eax, 75300
push 40
push 1000
push eax
push 00
push eax
mov eax, [ebp+0044992C]
cmp byte ptr [eax], CC
jz 00361F61
pop eax
call [ebp+0044992C]
; VirtualAlloc
mov [ebp+0041C4C6], eax
mov eax, 401000
mov edx, [ebp+00439A53]
; dimensione del codice
mov edi, [ebp+0041C4C6]
; allocmem
mov [ebp+0042FDD1], edi
lea ebx, [ebp+00439A57]
; questo è importante come quello del caso
; precedente. E' qua che si trova tutto.
Raga... mi fermo qua... è identica a quella di prima. A noi per risolverla interessava soltanto la locazione a cui punta EBX per prenderci i valori che ci servono, per il resto, dopo c'è la classica scansione e sostituzione della procedura. Andiamo a guardare invece direttamente la procedura rimpiazzata (anche qua dentro ve la scrivo semplificata perché è piena di istruzioni inutili):
00DC043F
push ebp
push eax
push ebx
pushfd
call 00DC0448
00DC0448 pop ebp
sub ebp, 0041B7E6
lea eax, [ebp+0041B7F7]
mov ebx, [eax]
mov eax, [eax+04]
xor ebx, eax
; questo punta alla locazione in memoria
add eax, ebx
and eax, FFFF0000
shr eax, 18
; così in AL si ottiene il valore
Anche qua possiamo fermarci.
Abbiamo quello di cui abbiamo bisogno. Adesso ricapitoliamo un po' la situazione
per vedere che cosa dobbiamo fare per un corretto unpacking:
Innanzitutto dobbiamo fare in modo di creare un processo che poi dumperemo una
volta arrivato alla fine dell'ultima sezione del krypter (sezione "_!_!_!_", che
poi sarebbe quella di unpacking dell'eseguibile). Una volta fatto questo, noi
abbiamo l'eseguibile dumpato in modo grezzo. Ci toccherà riallinearlo ma non
dovrebbero esserci problemi. Poi dobbiamo ritirare dall'interno dell'eseguibile
tutte le routine non-static che poi useremo per dekryptare l'eseguibile. Una
volta fatto questo ricostruiremo tutte le sezioni. Dopo le sezioni tocca alla
ricostruzione della import table. Non è molto difficile in quanto solo i nomi
delle api sono crittografati... il resto rimane come sta. Affinché funzioni però
dobbiamo anche ripristinare nell'header dell'eseguibile l'indirizzo della nostra
import table. Fatto questo rimangono solo le FF15 (io ho scritto cinque
procedure diverse per facilitarvi nella comprensione). Abbiamo finito! Basta
rimuovere dall'eseguibile le sezioni del loader, aggiornare l'header, aggiustare
l'entry point e aggiustare il sizeof image (per la compatibilità con windowsNT);
salvare il file su disco e festeggiare! Ora è arrivato il momento di
visualizzare questo benedetto codice di dekrypting (anche qua non sarà un
magnifico spettacolo... sono due chilometri -.-).
Pronti? Vi... HEY HEY HEY! Aspettate un attimo! C'è una cosa che devo ancora
dirvi. Voi non lo sapete... ma il mio caro amico yado ha inventato dei
trucchetti che permettono al programma di individuare la presenza del loader del
krypton. Per questo motivo il programma si accorgerà se le ultime sezioni sono
state rimosse o meno. Vediamo di svelare questo trucchetto? :D Ho preso il
dekrypter pensando che fosse tutto finito ed ho unpackato l'eseguibile del
krypton stesso. Il programma partiva! :D Però nel momento in cui ho premuto il
tasto "Browse", l'applicazione si è subito chiusa... così sono andato a spiare
quello che succedeva all'interno del codice. Siamo esattamente su 00401E48.
Vediamo un po di che si tratta:
YADO:004014E8 loc_0_4014E8: ; CODE XREF: sub_0_4011C8+310 j
YADO:004014E8 push eax
YADO:004014E9 mov eax,
ds:dword_0_40680C
YADO:004014EE xor eax,
112233h
YADO:004014F3 and eax,
11000000h
YADO:004014F8 test eax, eax
YADO:004014FA jz loc_0_4011A4
YADO:00401500 mov eax,
ds:dword_0_40680C
YADO:00401505 xor eax,
112233h
YADO:0040150A and eax,
0FFFFFFh
YADO:0040150F call eax
YADO:00401511 pop eax
YADO:00401512 not ds:byte_0_4142BA
YADO:00401518 not ds:byte_0_40774F
YADO:0040151E jmp short loc_0_40152E
Non so se ve rendete conto... ma a 004014EE
avviene uno xoring e un and molto strani... poi lo stesso viene ripetuto e poi
il tutto viene chiamata una procedura che si trova all'indirizzo ottenuto dalle
operazioni fatte su quella dword. E' un trucchetto che serve per individuare la
presenza del loader nel file (ne ho avuto la conferma parlando con yado).
Volendo anche qualsiasi utente che conosce bene il krypton potrebbe implementare
questa cosa nel suo codice. Per questo ho abbozzato una funzioncina che rimuove
tutta questa roba qua. Controllo la presenza di quelle DWORD consecutive per
vedere se è presente questo codice (il tutto comporta un minimo rischio nel caso
si verificasse che un programma abbia quelle dword sistemate allo stesso modo...
ma credo sia impossibile... comunque la funzione è facoltativa nel mio
programmino). Con una semplice funzioncina ora abbiamo l'unpacker completo! E
per la gioia dei bambini...
E C C O I L C O D I C E ! ! ! !
;************************************************************************************* ;* KRYPTON-D]i[E v0.5 ;* IRC: #crack-it | #fuckinworld | #informazionelibera @ Azzurra.org ;* ;************************************************************************************* .386 .model flat, stdcall option casemap: none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc include \masm32\include\comdlg32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\comdlg32.lib include resource.inc include data.inc WndProc PROTO hWnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM dekrypt PROTO memdekrypt PROTO import_routines PROTO routine1 PROTO routine2 PROTO routine3 PROTO import_rinfo PROTO ff15_05 PROTO ff15_00 PROTO ff15_01 PROTO ff15_02 PROTO ff15_03 PROTO remove_tricks PROTO FileExists PROTO lpFileName: LPCSTR personalizza PROTO .const AppName db "KRYPTON-DiE v0.5", 0 imagebase dd 00400000h jmp_eip dw 0FEEBh kdes db 0ebh, 0eh db 'KDES' db 00, 00, 00, 00, 00 db 00, 00, 00, 00, 00 error_open_file db "ERROR: Can't open selected file!", 0 error_invalid_pe db "ERROR: This is not a valid PE file format!", 0 error_invalid_kr db "ERROR: This file is not protected with krypton 0.5!", 0 unknown_error db "ERROR: Unhandled exception - unknown error!", 0 dekrypted db "File succesfully dekrypted! :D", 0 dekrypting db "Unpacking in progress. Please wait...", 0 last_section_name db "_!_!_!_", 0 krypton_sec_name db "krypton", 0 rtable dd 0C95Ah dd offset routine1 dd 6 dd 8632h dd offset routine2 dd 2 dd 8658h dd offset routine2+2 dd 2 dd 9462h dd offset routine3 dd 5 file_exists db "The selected file already exists:", 13, 10 db "Do you want to overwrite?", 0 .data hInstance dd 0 hFile dd 0 FileSize dd 0 lpData dd 0 hMainWindow dd 0 mdw dd 0 nSections dw 0 vsize_last_section dd 0 vaddr_last_section dd 0 vaddr_kryp_section dd 0 file_alignment dd 0 StartupInfo STARTUPINFO <> ProcessInfo PROCESS_INFORMATION <> ofn OPENFILENAME <> lpContext dd 0 FindData WIN32_FIND_DATA <> ProcessSize dd 0 lpDumped dd 0 ;************************************************************************************** nSec dd 0 ; Sezioni da decryttare DekrypterTableSize dd 0 ; Dimensione della tabella di informazioni per il dekrypting lpDekrypterTable dd 0 ; Puntatore alla tabella di informazioni per il dekrypting dekrypting_key db 0 ; chiave utilizzata dalla prima routine code_section_size dd 0 it dd 0 nModules dd 0 lpModules dd 0 ;************************************************************************************** current_log db 1024 dup (0) InitLog db "Press ''Browse'', select an already encrypted file and then press" db " ''Dekrypt'' to enjoy! :D.", 13, 10, 13, 10, 0 FileName db 255 dup (0) clear db "Ready.", 0 Filter db "Exe filex", 0 db "*.exe", 0, 0 OpenName db "Select a file...", 0 outfile db 512 dup (0) suffix db "_unpacked.exe", 0 .code Main: invoke GetModuleHandle, 0 mov [hInstance], eax invoke VirtualAlloc, 0, 1000h, MEM_COMMIT, PAGE_EXECUTE_READWRITE mov [lpContext], eax ; Ho dovuto fare così perché altrimenti il CONTEXT ; non funzionava correttamente... invoke VirtualProtect, addr routine1, 14, PAGE_EXECUTE_READWRITE, addr mdw invoke VirtualProtect, addr routine2, 9, PAGE_EXECUTE_READWRITE, addr mdw invoke VirtualProtect, addr routine3, 12, PAGE_EXECUTE_READWRITE, addr mdw invoke DialogBoxParam, hInstance, IDD_DIALOG1, 0, addr WndProc, 0 invoke ExitProcess, 0 WndProc PROC hWnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM mov eax, [uMsg] .if(ax == WM_CLOSE) invoke PostQuitMessage, 0 xor eax, eax ret .elseif(ax == WM_INITDIALOG) push [hWnd] pop [hMainWindow] xor eax, eax ret .elseif(ax == WM_COMMAND && wParam == IDC_BROWSE) mov [ofn.lStructSize], sizeof OPENFILENAME push [hWnd] pop [ofn.hwndOwner] push [hInstance] pop [ofn.hInstance] mov [ofn.lpstrFilter], offset Filter mov [ofn.lpstrFile], offset FileName mov [ofn.nMaxFile], 255 mov [ofn.Flags], OFN_HIDEREADONLY or OFN_EXPLORER mov [ofn.lpstrTitle], offset OpenName invoke GetOpenFileName, addr ofn .if(eax == 0) xor eax, eax ret .endif invoke SetDlgItemText, hWnd, IDC_EDIT1, addr FileName invoke SetDlgItemText, hWnd, IDC_STATUS, addr clear xor eax, eax ret .elseif(ax == WM_COMMAND && wParam == IDC_INFO) invoke MessageBox, hWnd, addr AboutText, addr AppName, 0 xor eax, eax ret .elseif(ax == WM_COMMAND && wParam == IDC_DEKRYPT) invoke dekrypt ; Questa è la procedura di dekrypting xor eax, eax ret .endif xor eax, eax ret WndProc ENDP dekrypt PROC ;********************************************* ;* Apertura del file ;* ;********************************************* pushad invoke SetDlgItemText, hMainWindow, IDC_STATUS, addr dekrypting mov [nModules], 0 invoke CreateFile, addr FileName, GENERIC_READ or GENERIC_WRITE, \ 0, 0, OPEN_EXISTING, 0, 0 .if(eax == INVALID_HANDLE_VALUE) invoke SetDlgItemText, hMainWindow, IDC_STATUS, offset error_open_file popad xor eax, eax ret .endif mov [hFile], eax ;************************************************ ;* Lettura del file ;* ;************************************************ invoke GetFileSize, eax, 0 mov [FileSize], eax invoke VirtualAlloc, 0, eax, MEM_COMMIT, PAGE_EXECUTE_READWRITE mov [lpData], eax invoke ReadFile, hFile, eax, FileSize, addr mdw, 0 invoke CloseHandle, hFile ;************************************************ ;* Controllo del formato PE ;* ;************************************************ mov edi, [lpData] assume edi: ptr IMAGE_DOS_HEADER mov bx, word ptr [edi] .if(bx != 'ZM') invoke VirtualFree, lpData, FileSize, MEM_DECOMMIT invoke SetDlgItemText, hMainWindow, IDC_STATUS, offset error_invalid_pe popad xor eax, eax ret .endif mov ecx, [edi].e_lfanew add edi, ecx assume edi: ptr IMAGE_NT_HEADERS mov ebx, [edi].Signature .if(ebx != IMAGE_NT_SIGNATURE) invoke VirtualFree, lpData, FileSize, MEM_DECOMMIT invoke SetDlgItemText, hMainWindow, IDC_STATUS, offset error_invalid_pe popad xor eax, eax ret .endif ;****************************************************** ;* Controllo della presenza del krypton ;* ;****************************************************** mov edx, [edi].OptionalHeader.FileAlignment mov [file_alignment], edx mov edx, edi add edx, 4 add edx, sizeof IMAGE_FILE_HEADER add dx, [edi].FileHeader.SizeOfOptionalHeader ; EDX punta alla section table movzx eax, [edi].FileHeader.NumberOfSections assume edi: nothing mov [nSections], ax ; prelevo il numero di sezioni dell'eseguibile dec eax assume edx: ptr IMAGE_SECTION_HEADER mov edi, edx mov ecx, sizeof IMAGE_SECTION_HEADER mul ecx add edi, eax invoke lstrcmp, edi, addr last_section_name .if(eax != 0) invoke VirtualFree, lpData, FileSize, MEM_DECOMMIT invoke SetDlgItemText, hMainWindow, IDC_STATUS, addr error_invalid_kr popad xor eax, eax ret .endif sub edi, sizeof IMAGE_SECTION_HEADER invoke lstrcmp, edi, addr krypton_sec_name .if(eax != 0) invoke VirtualFree, lpData, FileSize, MEM_DECOMMIT invoke SetDlgItemText, hMainWindow, IDC_STATUS, addr error_invalid_kr popad xor eax, eax ret .endif assume edi: ptr IMAGE_SECTION_HEADER mov eax, [edi].VirtualAddress mov [vaddr_kryp_section], eax add edi, sizeof IMAGE_SECTION_HEADER mov eax, [edi].VirtualAddress mov [vaddr_last_section], eax mov eax, [edi].Misc.VirtualSize mov [vsize_last_section], eax invoke VirtualFree, lpData, FileSize, MEM_DECOMMIT assume edi: nothing ;********************************************************* ;* Creazione del processo ;* ;********************************************************* invoke GetStartupInfo, addr StartupInfo mov edi, [lpContext] assume edi: ptr CONTEXT mov [edi].ContextFlags, CONTEXT_FULL invoke CreateProcess, addr FileName, 0, 0, 0, 0, CREATE_SUSPENDED, 0, 0, addr StartupInfo, addr ProcessInfo mov eax, [imagebase] add eax, [vaddr_last_section] add eax, 00000CF1h invoke WriteProcessMemory, ProcessInfo.hProcess, eax, addr jmp_eip, 2, addr mdw .while(1) invoke ResumeThread, ProcessInfo.hThread invoke Sleep, 1000 invoke SuspendThread, ProcessInfo.hThread invoke GetThreadContext, ProcessInfo.hThread, lpContext mov eax, [imagebase] add eax, [vaddr_last_section] add eax, 00000CF1h mov edi, [lpContext] assume edi: ptr CONTEXT mov ebx, [edi].regEip .if(ebx == eax) .break .endif .endw ;******************************************* ;* Dumping del processo ;* ;******************************************* mov eax, [vaddr_last_section] add eax, [vsize_last_section] mov [ProcessSize], eax invoke VirtualAlloc, 0, ProcessSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE mov [lpDumped], eax invoke ReadProcessMemory, ProcessInfo.hProcess, imagebase, lpDumped, ProcessSize, addr mdw invoke TerminateProcess, ProcessInfo.hProcess, 0 ;*********************************************** ;* Reallineamento eseguibile dumpato ;* ;*********************************************** assume edi: ptr IMAGE_DOS_HEADER mov edi, [lpDumped] add edi, [edi].e_lfanew assume edi: ptr IMAGE_NT_HEADERS mov edx, edi add edx, 4 add edx, sizeof IMAGE_FILE_HEADER add dx, [edi].FileHeader.SizeOfOptionalHeader assume edi: nothing assume edx: ptr IMAGE_SECTION_HEADER movzx ecx, [nSections] sub edx, sizeof IMAGE_SECTION_HEADER .while(ecx > 0) add edx, sizeof IMAGE_SECTION_HEADER mov esi, [edx].VirtualAddress mov [edx].PointerToRawData, esi mov esi, [edx].Misc.VirtualSize mov [edx].SizeOfRawData, esi dec ecx .endw ;************************************************ ;* Prelevamento delle informazioni necessarie ;* ;************************************************ invoke memdekrypt ; Questa routine sistema il file per la lettura ; delle informazioni invoke import_routines ; Questa routine importa tutte le funzioni di ; dekrypting dall'interno del file dumpato ; seguendo una tabella costante (rtable) invoke import_rinfo ; Questa preleva altre informazioni necessarie ;************************************************ ;* Let's dekrypt!!! ;* ;************************************************ mov esi, [nSec] ;numero delle sezioni da dekryptare mov edi, [lpDekrypterTable] .while(esi > 0) mov ebx, [lpDumped] add ebx, [edi] mov ecx, [edi+4] movzx eax, [dekrypting_key] invoke routine1 mov ebx, [lpDumped] add ebx, [edi] mov ecx, [edi+4] invoke routine2 add edi, 8 dec esi .endw invoke VirtualFree, lpDekrypterTable, DekrypterTableSize, MEM_DECOMMIT ;************************************************** ;* Ricostruzione della IT ;* ;************************************************** lea ebx, routine3 mov bx, [ebx] cmp bx, 9090h jnz _end_it mov ebx, [it] add ebx, [lpDumped] add ebx, 0Ch _count: cmp dword ptr [ebx], 0 jz _end_count inc [nModules] add ebx, 14h jmp _count _end_count: xor eax, eax mov ecx, [nModules] _api_loop: mov ebx, [it] add ebx, [lpDumped] add ebx, eax ;cambiamento di puntatore di elementi mov ebx, [ebx] add ebx, [lpDumped] _k_api: push ebx mov ebx, [ebx] cmp ebx, 0 jz _end add ebx, [lpDumped] add ebx, 2 ; ebx punta al nome dell'api crittografato invoke routine3 ; Questa routine ricostruisce il nome dell'api pop ebx add ebx, 4 jmp _k_api _end: pop ebx add eax, 20 loopnz _api_loop _end_it: ;************************************************** ;* Aggiustiamo le FF15 ;* ;************************************************** invoke ff15_05 ; Manual K-Execution invoke ff15_00 ; mov ecx, dword ptr [xxxxxxxx] invoke ff15_01 ; jmp far dword ptr [xxxxxxxx] invoke ff15_02 ; mov dword ptr [xxxxxxxx], xxxxxxxx invoke ff15_03 ; cmp dword ptr [xxxxxxxx], xx invoke IsDlgButtonChecked, hMainWindow, IDC_CHECK .if(eax == BST_CHECKED) invoke remove_tricks .endif ;************************************************** ;* Qualche altra operazione facile facile ;* ;************************************************** mov edi, [lpDumped] assume edi: ptr IMAGE_DOS_HEADER add edi, [edi].e_lfanew assume edi: ptr IMAGE_NT_HEADERS mov [edi].OptionalHeader.AddressOfEntryPoint, 1000h ;Aggiornare l'entry point mov edi, [lpDumped] add edi, dword ptr [edi+3Ch] mov eax, [it] mov dword ptr [edi+80h], eax ;Correggere l'indirizzo della Import Table sub [edi].FileHeader.NumberOfSections, 2 ;Aggiorna il numero di sezioni mov eax, [vaddr_kryp_section] mov [edi].OptionalHeader.SizeOfImage, eax invoke personalizza ;****************************************************** ;* Salvataggio del file e liberazione ;* della memoria allocata ;* ;****************************************************** invoke lstrcpy, addr outfile, addr FileName lea edi, outfile push edi xor eax, eax or ecx, -1 repnz scasb not ecx dec ecx pop edi _ext_check: cmp byte ptr [edi], '.' jz _end_ext_check inc edi loopnz _ext_check _end_ext_check: mov byte ptr [edi], 0 invoke lstrcat, addr outfile, addr suffix mov [ofn.lpstrFile], offset outfile invoke GetSaveFileName, addr ofn .if(eax == 0) invoke SetDlgItemText, hMainWindow, IDC_STATUS, addr dekrypted popad xor eax, eax ret .endif invoke FileExists, addr outfile .if(eax == TRUE) invoke MessageBox, hMainWindow, addr file_exists, \ addr AppName, MB_YESNO or MB_ICONQUESTION .if(eax == IDNO) invoke VirtualFree, addr lpDumped, ProcessSize, MEM_DECOMMIT invoke SetDlgItemText, hMainWindow, IDC_STATUS, addr dekrypted popad xor eax, eax ret .endif .endif invoke CreateFile, addr outfile, GENERIC_READ or GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0 mov [hFile], eax invoke WriteFile, hFile, lpDumped, vaddr_kryp_section, addr mdw, 0 invoke CloseHandle, hFile invoke VirtualFree, addr lpDumped, ProcessSize, MEM_DECOMMIT invoke SetDlgItemText, hMainWindow, IDC_STATUS, addr dekrypted popad xor eax, eax ret dekrypt ENDP memdekrypt PROC mov eax, [vaddr_kryp_section] add eax, [lpDumped] add eax, 8 mov ecx, 3FEBEh .while(ecx > 0) mov bl, [eax] ror bl, cl xor bl, cl add bl, cl xor bl, cl ror bl, cl mov [eax], bl inc eax dec ecx .endw ret memdekrypt ENDP import_routines PROC mov eax, [vaddr_kryp_section] add eax, [lpDumped] add eax, 8 xor edx, edx mov edi, 4 .while(edi > 0) push eax add eax, [rtable+edx] mov esi, [rtable+edx+4] mov ecx, [rtable+edx+8] .while(ecx > 0) mov bl, [eax] mov [esi], bl inc eax inc esi dec ecx .endw add edx, 0Ch dec edi pop eax .endw ret import_routines ENDP routine1 PROC nop nop nop nop nop nop inc al inc ebx dec ecx jnz routine1 ret routine1 ENDP routine2 PROC nop nop nop nop inc ebx dec ecx jnz routine2 ret routine2 ENDP routine3 PROC nop nop nop nop nop inc ebx cmp byte ptr [ebx], 0 jnz routine3 ret routine3 ENDP import_rinfo PROC mov eax, [lpDumped] add eax, [vaddr_kryp_section] add eax, 8 add eax, 1E80Ah mov eax, [eax] .if(eax == 0) mov edi, [lpDumped] add edi, dword ptr [edi+3Ch] mov edx, edi add edx, 4 add edx, sizeof IMAGE_FILE_HEADER add dx, [edi].FileHeader.SizeOfOptionalHeader ; EDX punta alla section table assume edx: ptr IMAGE_SECTION_HEADER mov eax, [edx].Misc.VirtualSize assume edx: nothing .endif mov [code_section_size], eax ; Vi spiego questo sistema: tra le varie di un beta-tester, è successo che un ese- ; guibile non riportasse la dimensione della sezione di codice al solito offset. ; Così ho fatto un controllo che, se si verifica questo caso, prende la dimensione ; della sezione direttamente dalla section table, anche se in questo caso sarà ; un valore arrotondato in base al section allignment perché dalla tabella non ; abbiamo il RawSize ma solo il VirtualSize. mov eax, [vaddr_kryp_section] add eax, [lpDumped] add eax, 0C97Ah add eax, 8 mov al, [eax] mov [dekrypting_key], al ; Ritiro della chiave per la prima routine mov eax, [vaddr_kryp_section] add eax, [lpDumped] add eax, 0C993h add eax, 8 movzx eax, byte ptr [eax] mov [nSec], eax ; Ritiro del numero di sezioni da decrittare shl eax, 3 mov [DekrypterTableSize], eax invoke VirtualAlloc, 0, DekrypterTableSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE mov [lpDekrypterTable], eax mov eax, [vaddr_kryp_section] add eax, [lpDumped] add eax, 0C998h add eax, 8 mov ecx, [DekrypterTableSize] mov edi, [lpDekrypterTable] .while(ecx > 0) ; Ritiro della tabella di informazioni mov bl, [eax] mov [edi], bl inc eax inc edi dec ecx .endw mov eax, [vaddr_kryp_section] add eax, [lpDumped] add eax, 0C983h add eax, 8 mov esi, [eax] mov [it], esi add esi, [lpDumped] mov eax, [esi+0Ch] add eax, [lpDumped] ret import_rinfo ENDP ff15_05 PROC LOCAL esp20: DWORD LOCAL esp24: DWORD LOCAL RetVal: DWORD LOCAL proce: DWORD LOCAL ffcall: DWORD LOCAL dimens: DWORD LOCAL key: DWORD mov eax, [lpDumped] add eax, 1000h mov ecx, [code_section_size] .while(ecx > 0) cmp word ptr [eax], 15FFh jnz _continue_scan05 cmp dword ptr [eax+2], 05 jnz _continue_scan05 ;********************************************************************* pushad mov [ffcall], eax mov ebx, [eax-4] mov [esp20], ebx ;Ottengo esp+20 mov ebx, [eax-9] mov [esp24], ebx ;Ottengo esp+24 mov ebx, eax add ebx, 6 mov [RetVal], ebx ;Ottengo RetVal mov eax, [vaddr_kryp_section] add eax, [lpDumped] add eax, 8 add eax, 0F1DCh mov [proce], eax ;Ottengo l'indirizzo della procedura FF15-05 su disco add eax, 2F8h mov eax, [eax] mov ecx, [esp20] xor ecx, eax ;Ottengo la dimensione di codice da decifrare mov [dimens], ecx mov ecx, [esp24] xor cx, ax mov [key], ecx mov ebx, [key] xor eax, eax mov ax, bx mov ecx, [dimens] mov esi, [ffcall] add esi, 6 .while(ecx > 0) mov dl, [esi] neg cl add dl, cl xor dl, cl rol dl, cl neg cl sub dl, al add dl, ah xor dl, al rol dl, cl xor dl, ah mov [esi], dl inc esi dec ecx .endw mov edi, [ffcall] sub edi, 10 lea esi, kdes mov ecx, 13 rep movsb popad ;********************************************************************* _continue_scan05: inc eax dec ecx .endw ret ff15_05 ENDP ff15_00 PROC LOCAL bytes: DWORD LOCAL ptrcode: DWORD LOCAL key00: DWORD ;************************************************** ;* Ritiro dei valori necessari ;* ;************************************************* pushad mov eax, [lpDumped] add eax, [vaddr_kryp_section] add eax, 8 add eax, 14BBEh mov [bytes], eax ;Salvo lpCodice xor ebx, ebx mov eax, [lpDumped] add eax, [vaddr_kryp_section] add eax, 8 add eax, 95B7h mov ecx, 87Ch _getkey00: add ebx, [eax] inc eax dec ecx test ecx, ecx jnz _getkey00 mov eax, [lpDumped] add eax, [vaddr_kryp_section] add eax, 8 add eax, 0C96Fh mov eax, [eax] xor ecx, ecx mov cl, al add ebx, ecx mov [key00], ebx ;Salvo la chiave mov [ptrcode], 0 mov eax, [lpDumped] add eax, 1000h mov ecx, [code_section_size] .while(ecx > 0) cmp word ptr [eax], 15FFh jnz _continue_scan00 cmp dword ptr [eax+2], 0 jnz _continue_scan00 ;********************************************************************** pushad mov ecx, eax lea ebx, [key00] mov eax, [bytes] add eax, [ptrcode] mov edx, [eax+4] xor edx, [ebx] mov [ecx], edx mov dx, [eax+8] xor edx, [ebx] mov [ecx+4], dx add [ptrcode], 10 popad ;********************************************************************** _continue_scan00: inc eax dec ecx .endw popad ret ff15_00 ENDP ff15_01 PROC LOCAL bytes: DWORD LOCAL ptrcode: DWORD LOCAL key01: DWORD pushad ;************************************************** ;* Ritiro dei valori necessari ;* ;************************************************* mov eax, [lpDumped] add eax, [vaddr_kryp_section] add eax, 8 add eax, 1E832h mov [bytes], eax ;Salvo lpCodice mov ebx, [lpDumped] add ebx, [vaddr_kryp_section] add ebx, 8 add ebx, 1E802h mov ebx, [ebx] mov [key01], ebx ;Salvo la chiave mov [ptrcode], 0 mov eax, [lpDumped] add eax, 1000h mov ecx, [code_section_size] .while(ecx > 0) cmp word ptr [eax], 15FFh jnz _continue_scan01 cmp dword ptr [eax+2], 1 jnz _continue_scan01 ;********************************************************************** pushad mov ecx, eax lea ebx, [key01] mov eax, [bytes] add eax, [ptrcode] mov edx, [eax+4] xor edx, [ebx] mov dl, -1 mov [ecx], edx mov dx, [eax+8] xor edx, [ebx] mov [ecx+4], dx add [ptrcode], 10 popad ;********************************************************************** _continue_scan01: inc eax dec ecx .endw popad ret ff15_01 ENDP ff15_02 PROC LOCAL ptrcode: DWORD LOCAL lptab: DWORD pushad mov [ptrcode], 00 mov eax, [lpDumped] add eax, [vaddr_kryp_section] add eax, 8 add eax, 284C2h mov [lptab], eax mov eax, [lpDumped] add eax, 1000h mov ecx, [code_section_size] .while(ecx > 0) cmp word ptr [eax], 15FFh jnz _continue_scan02 cmp dword ptr [eax+02], 02 jnz _continue_scan02 ;***************************************************** pushad mov ecx, eax sub ecx, 2 mov word ptr [ecx], 05C7h ; mov [xxxxxxxx], xxxxxxxx mov eax, [lptab] add eax, [ptrcode] mov ebx, [eax] mov eax, [eax+04] xor ebx, eax ; EAX = VALUE add eax, ebx ; EBX = LOCATION mov [ecx+02], ebx mov [ecx+06], eax add [ptrcode], 8 popad ;***************************************************** _continue_scan02: inc eax dec ecx .endw popad ret ff15_02 ENDP ff15_03 PROC LOCAL ptrcode: DWORD LOCAL lptab: DWORD pushad mov [ptrcode], 00 mov eax, [lpDumped] add eax, [vaddr_kryp_section] add eax, 8 add eax, 3210Ch mov [lptab], eax mov eax, [lpDumped] add eax, 1000h mov ecx, [code_section_size] .while(ecx > 0) cmp word ptr [eax], 15FFh jnz _continue_scan03 cmp dword ptr [eax+02], 03 jnz _continue_scan03 ;***************************************************** pushad mov ecx, eax mov word ptr [ecx], 3D83h ; cmp [xxxxxxxx], xx mov eax, [lptab] add eax, [ptrcode] mov ebx, [eax] mov eax, [eax+04] xor ebx, eax ; EBX = LOCATION add eax, ebx ; EAX = VALUE and eax, 0FFFF0000h shr eax, 18h mov [ecx+02], ebx mov byte ptr [ecx+06], al add [ptrcode], 8 popad ;***************************************************** _continue_scan03: inc eax dec ecx .endw popad ret ff15_03 ENDP remove_tricks PROC mov ebx, [lpDumped] add ebx, 1000h mov ecx, [code_section_size] _loop: cmp dword ptr [ebx+1], 112233h jz _step2 inc ebx dec ecx test ecx, ecx jnz _loop ret _step2: cmp dword ptr [ebx+6], 11000000h jz _step3 inc eax dec ecx test ecx, ecx jnz _loop ret _step3: push ecx mov ecx, 41 mov al, 90h sub ebx, 6 _delete: mov [ebx], al inc ebx loopnz _delete pop ecx inc ebx dec ecx test ecx, ecx jnz _loop ret remove_tricks ENDP FileExists PROC lpFileName: LPCSTR xor eax, eax or ecx, -1 mov edi, [lpFileName] repnz scasb not ecx dec ecx .if(!ecx) mov eax, FALSE ret .endif invoke FindFirstFile, lpFileName, addr FindData invoke FindClose, eax ret FileExists ENDP personalizza PROC pushad mov edi, [lpDumped] add edi, dword ptr [edi+3Ch] mov edx, edi add edx, 4 add edx, sizeof IMAGE_FILE_HEADER add dx, [edi].FileHeader.SizeOfOptionalHeader ; EDX punta alla section table movzx ecx, word ptr [nSections] sub ecx, 2 assume edx: ptr IMAGE_SECTION_HEADER .while(ecx > 0) mov edi, dword ptr [secname] mov dword ptr [edx], edi mov edi, dword ptr [secname+4] mov dword ptr [edx+4], edi add edx, sizeof IMAGE_SECTION_HEADER dec ecx .endw xor eax, eax mov dword ptr [edx], eax mov dword ptr [edx+4], eax add edx, sizeof IMAGE_SECTION_HEADER mov dword ptr [edx], eax mov dword ptr [edx+4], eax popad xor eax, eax ret personalizza ENDP end Main |
Amici miei... aiuto! Alor questo è il risultato. Molte volte ho dichiarato procedure a parte per farvi capire meglio come è stato schematizzato il codice. We mi son fatto tanto di culo per finire 'sta roba... spero di essere riuscito almeno a guadagnare qualche lettura da parte vostra :). Credo che per oggi possa bastare!! Ciao a tutti!
Note finali |
Ahhh... finalmente posso scrivere su questo tutorial senza farmi venire un
incredibile mal di testa! Prima di tutto un ringraziamento va a Yado ;). Un
saluto a Reload85, AndreaGeddon, Quequero (che mi farà mettere l'uic form nel
mio sito se no lo prendo a pugni :), CrashRR, Ntoskrn, Quake2^AM, Evilcry,
CyberDude e a tutti i membri di #crack-it di #fuckinworld e di
#informazionelibera.
Saluto anche la mia professoressa di inglese (non c'è un motivo preciso... la
saluto e basta :) poi i miei colleghi Cazzato, Lubello e Cortese, oltre che a
tutta la classe :).
Un saluto maligno con minaccia di morte a tutti coloro che o per un motivo o per
un altro o addirittura senza un motivo mi vogliono morto :). Tutto il resto è
noia! :DDD
Disclaimer |
Qui inserirete con questo carattere il vostro piccolo disclaimer, non è obbligatorio però è meglio per voi se c'è. Dovete scrivere qualcosa di simile a: 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 immane che ogni singolo programmatore ha dovuto portare avanti per fornire ai rispettivi consumatori i migliori prodotti possibili.
Noi reversiamo al solo scopo informativo e di miglioramento del linguaggio Assembly.