|
Ottimizzazione: Lezione 11 Per Studenti UIC | ||
|
Data |
by Ntoskrnl |
|
|
20/08/2003 |
Published by Quequero | |
|
Bello riempire gli spazi vuoti!
|
Grazie nt per il tutorial come gia ti avevo detto in query l'idea finale non e' proprio validissima per via delle regole... Cmq e' ugualmente buona e originale, bravo davvero! |
Bello riempire gli spazi vuoti!
|
|
.... |
|
.... |
|
Difficoltà |
( )NewBies (*)Intermedio (*)Avanzato ( )Master |
|
Lezione 11 Per Studenti UIC
|
Introduzione |
|
Tools usati |
Tasm, VC++, wark, Hex Editor (quello che preferite).
|
URL o FTP del programma |
|
Notizie sul programma |
|
Essay |
La prima cosa che dobbiamo fare è quella di ottimizzare il codice.
OTTIMIZZAZIONE DEL CODICE
Parte che ritengo anche più noiosa (a me interessa solo il PE), tutto ciò che dobbiamo fare è sostituire le istruzioni che troviamo con istruzioni più piccole (parlando in opcode), cosa che sa fare chiunque abbia un po' di esperienza con l'x86... Vabbe basta con le lamentele, apriamo il foglio asm e iniziamo ad analizzare.
(ah per compilare useremo le normali direttive:
tasm32 -ml -m5 -q src
tlink32 -Tpe -aa -x -c src ,,, import32
non cambieremo nulla di quello che ci ha detto Que, altrimenti la soluzione non sarebbe più valida).
.code
Start:
call GetTickCount
push eax
mov ecx, eax
imul eax, 256 ; sostituiamo con: shl eax, 8
; dato che 256 è uguale a 2^8
mov ecx, eax
step1:
imul eax, 4096 ; shl eax, 12
; 4096 == 2^12
push edx
mov edx, ecx
pop edx
loop step1
Per adesso solamente queste due istruzioni dobbiamo sostituire (ovviamente non vi sto a spiegare perché usare shl invece di imul, la funzione matematica di shl la dovete conoscere, se così non fosse chiudete questo tutorial e aprite una guida assembler (e lo dico senza cattiveria)).
mov eax, 0 ; xor eax, eax
sub edx, edx ; xor edx, edx
mov eax, ecx
db 0fh, 31h
and eax, 0000FFFFh
Andiamo avanti:
mov ecx, eax
push ecx ; pushad ovviamente
push ebx
push eax
push edx
push esi
push edi
Ok giungiamo ad uno stralcio di codice in cui dovremo agire un po' diversamente:
mov dword ptr[calcbuf], eax
mov edx, dword ptr[calcbuf]
db 0fh, 31h
add edx, eax
mov dword ptr[calcbuf+4], eax
add dword ptr[calcbuf], eax
mov dword ptr[calcbuf+8], edx
mov edx, dword ptr[calcbuf]
add eax, edx
mov dword ptr[calcbuf+8], eax
mov eax, dword ptr[calcbuf+4]
loop step2
Per evitare tutti questi accessi a indirizzi che succhiano un monte di byte faremo in modo di avvalerci di un registro che conterrà l'indirizzo:
mov edi, offset calcbuf
mov dword ptr [edi], eax
mov edx, dword ptr [edi]
db 0fh, 31h
add edx, eax
mov dword ptr [edi + 4], eax
add dword ptr [edi], eax
mov dword ptr [edi + 8], edx
mov edx, dword ptr [edi]
add eax, edx
mov dword ptr [edi + 8], eax
mov eax, dword ptr [edi + 4]
loop step2
E così ci siamo risparmiati un sacco di bytes..
pop edi
pop esi
pop edx
pop eax
pop ebx
pop ecx ; popad
mov ecx, 0 ; xor ecx, ecx
Questo mi pare ovvio, siamo giunti allo step 3:
step3:
mov eax, dword ptr[calcbuf+ecx]
mov dword ptr[calcbuf+ecx], eax
inc ecx
cmp ecx, 99
jnz step3
mov ecx, dword ptr[calcbuf]
and ecx, 0000FFFFh ; soliti 5 byte succhiati
db 0fh, 31h
Diverrà:
mov cl, 98d
mov edi, offset calcbuf
step3:
mov eax, dword ptr [edi + ecx]
mov dword ptr [edi + ecx], eax
loop step3
mov ecx, dword ptr [edi]
xchg eax, ecx
cwde
xchg eax, eax
and ecx, 0000FFFFh
db 0fh, 31h
In questo caso ho scelto di usare xchg dato che occupa solamente un byte.
Siamo giunti allo step4:
step4:
and dword ptr[calcbuf], eax
add dword ptr[calcbuf+4], eax
mov ebx, dword ptr[calcbuf+4]
add eax, ebx
mov dword ptr[calcbuf], eax
mul dword ptr[calcbuf]
mul dword ptr[calcbuf]
mul dword ptr[calcbuf]
mul dword ptr[calcbuf]
mul dword ptr[calcbuf]
mul dword ptr[calcbuf]
add eax, 1
add eax, eax
div dword ptr[calcbuf]
rcl dword ptr[calcbuf+8], 135
loop step4
mov ecx, dword ptr[calcbuf]
and ecx, 0000FFFFh
Senza ragionare tanto:
step4:
and dword ptr [edi], eax
add dword ptr [edi + 4], eax
mov ebx, dword ptr [edi + 4]
add eax, ebx
mov dword ptr [edi], eax
mul dword ptr [edi]
mul dword ptr [edi]
mul dword ptr [edi]
mul dword ptr [edi]
mul dword ptr [edi]
mul dword ptr [edi]
inc eax
add eax, eax
div dword ptr [edi]
rcl dword ptr [edi + 8], 135d
loop step4
mov ecx, dword ptr [edi]
and ecx, 0000FFFFh
Ok siamo giunti alla fine:
step5:
mov edx, eax
mov esi, ebx
mov ebx, edx
mov eax, esi
loop step5
call GetTickCount
mov ebx, eax
pop eax
sub ebx, eax
call wsprintf, offset buffer, offset tempo, ebx
call MessageBox, NULL, offset buffer, offset titolo, MB_OK OR MB_ICONSTOP
call wsprintf, offset buf2, offset testo, dword ptr[calcbuf]
call MessageBox, NULL, offset buf2, offset titol0, MB_OK OR MB_ICONQUESTION
call ExitProcess, NULL
Lo trasformeremo in:
step5:
xchg ebx, eax ; quello a cui serve quella marea di mov
loop step5
call GetTickCount
xchg ebx, eax
pop eax
sub ebx, eax
call wsprintf, offset buffer, offset tempo, ebx
call MessageBox, NULL, offset buffer, offset titolo, MB_OK + MB_ICONSTOP
call wsprintf, offset buf2, offset testo, dword ptr [calcbuf]
call MessageBox, NULL, offset buf2, offset titol0, MB_OK + MB_ICONQUESTION
call ExitProcess, NULL
Ok abbiamo finito, per sicurezza reincollo un'altra volta il codice ottimizzato (non vorrei aver fatto casino col copia + incolla).
----------------------------------------------------------------
.586P
LOCALS
.MODEL FLAT, STDCALL
UNICODE=0
nojumps
INCLUDE c:\tasm\include\W32.inc
extrn GetTickCount:Proc
extrn wsprintf:Proc
.data
buffer db 50 dup(0)
buf2 db 30 dup(0)
calcbuf db 12 dup(0)
tempo db 'Il programma ha impiegato %d millisecondi',0dh, 0ah
db 'per eseguire tutte le interazioni!!!',0dh,0ah
db '',0dh,0ah
db 'Puoi migliorare :)',0
titolo db 'Tempo impiegato...',0
titol0 db 'Numero generato',0
testo db 'Giusto per curiosità, ecco il numero che hai generato: %d',0
.code
Start:
call GetTickCount
push eax
mov ecx, eax
shl eax, 8
mov ecx, eax
step1:
shl eax, 12
push edx
mov edx, ecx
pop edx
loop step1
xor eax, eax
xor edx, edx
mov eax, ecx
db 0fh, 31h
and eax, 0000FFFFh
mov ecx, eax
pushad
step2:
mov edi, offset calcbuf
mov dword ptr [edi], eax
mov edx, dword ptr [edi]
db 0fh, 31h
add edx, eax
mov dword ptr [edi + 4], eax
add dword ptr [edi], eax
mov dword ptr [edi + 8], edx
mov edx, dword ptr [edi]
add eax, edx
mov dword ptr [edi + 8], eax
mov eax, dword ptr [edi + 4]
loop step2
popad
xor ecx, ecx
mov cl, 98d
mov edi, offset calcbuf
step3:
mov eax, dword ptr [edi + ecx]
mov dword ptr [edi + ecx], eax
loop step3
mov ecx, dword ptr [edi]
xchg eax, ecx
cwde
xchg eax, eax
and ecx, 0000FFFFh
db 0fh, 31h
step4:
and dword ptr [edi], eax
add dword ptr [edi + 4], eax
mov ebx, dword ptr [edi + 4]
add eax, ebx
mov dword ptr [edi], eax
mul dword ptr [edi]
mul dword ptr [edi]
mul dword ptr [edi]
mul dword ptr [edi]
mul dword ptr [edi]
mul dword ptr [edi]
inc eax
add eax, eax
div dword ptr [edi]
rcl dword ptr [edi + 8], 135d
loop step4
mov ecx, dword ptr [edi]
and ecx, 0000FFFFh
step5:
xchg ebx, eax
loop step5
call GetTickCount
xchg ebx, eax
pop eax
sub ebx, eax
call wsprintf, offset buffer, offset tempo, ebx
call MessageBox, NULL, offset buffer, offset titolo, MB_OK + MB_ICONSTOP
call wsprintf, offset buf2, offset testo, dword ptr [calcbuf]
call MessageBox, NULL, offset buf2, offset titol0, MB_OK + MB_ICONQUESTION
call ExitProcess, NULL
ends
end Start
----------------------------------------------------------------
Ok abbiamo ottimizzato abbastanza il codice e anche se forse si poteva raccattare ancora qualche byte, ma siccome ho ottimizzato in 10 minuti (e a dir la verità di ottimizzare il codice me ne importa na sega (come si dice in toscana)) ritengo che vada più che bene. Ok, abbiamo concluso questo paragrafo.
OTTIMIZZAZIONE DEL PE
E già le cose diventano mooolto più divertenti. Come prima cosa cambiamo l'allineamento del file a 200h, per fare ciò ho usato il wark (cazzo se lo codo almeno qualche volta vorrò usarlo, gh), potete però fare anche a mano con l'hex editor (ve lo sconsiglio) meglio buttare giù allora qualche riga di codice.
Cmq dopo l'allineamento a 200h il file da 4k che pesava adesso pesa: 3k (ancora lontanissimi dall'aver finito). Andiamo avanti e rimuoviamo la sezione .reloc aggiunta dal compilatore ma che a noi non serve per nulla, per rimuoverla riuso il wark, anche se questo si può fare bene anche a mano è necessario rimuovere l'entry dalla section table e i byte fisici della sezione: quindi aggiornare i campi come sizeofimage, numberofsections.
Rimossa la reloc il nostro exe peserà 2.50 k... Mica ci accontentiamo. Si possono raccattare altri 512 byte dal pe header che misteriosamente succhia 1 k. Apriamo l'hex editor e facciamo arrivare il codice dalla posizione 400h alla 200h eliminando i bytes nulli. Apriamo il wpe (o qualsiasi altro pe editor) e modifichiamo i Physical Address delle sezioni, sottraendo ad ognuna 200h. Ricordatevi di cambiare il Size Of Headers da 400h a 200h dato che è stato ridotto. Adesso il nostro exe peserà 2 k netti: pensate di aver finito? Si vede che non mi conoscete.
La prima cosa che faremo è di strettire un po' l'header nel caso ci servisse dopo un po' di spazio. Intanto riduciamo il Dos Header che troviamo così:
00000000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 MZ.........ÿÿ..
00000010 B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 ¸.......@.......
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00 ............€...
00000040 0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68 ..º..´.Í!¸.LÍ!Th
00000050 69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F is program canno
00000060 74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20 t be run in DOS
00000070 6D 6F 64 65 2E 0D 0D 0A 24 00 00 00 00 00 00 00 mode....$.......
00000080 50 45 00 00 4C 01 03 00 C9 2E 7A 4C 00 00 00 00 PE..L...É.zL....
Faremo diventare:
00000000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 MZ.........ÿÿ..
00000010 B8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ¸...............
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 ............@...
00000040 50 45 00 00 4C 01 03 00 C9 2E 7A 4C 00 00 00 00 PE..L...É.zL....
Rimuoviamo quindi il Dos Stub e cambiamo e_lfanew da 80h a 40h. Ah premetto una cosa, per l'ottimizzazione del PE si potrebbero fare impicci strani come usare campi dell'header per mettere dentro altra roba, concatenare Dos e NT header ecc. Non lo faccio perché è una soluzione che va sempre ripensata, preferisco fare qualcosa di più 'regolare': insomma niente impicci strani.
Adesso incolliamo i buffer contenenti le frasi dei msgbox subito dopo il codice (senza però rimuovere i buffer originali, lo faremo dopo quello. Avremo quindi una cosa del genere:
| Codice | Buffers
00000300 00 FF 25 64 30 40 00 49 6C 20 70 72 6F 67 72 61 .ÿ%d0@.Il progra
00000310 6D 6D 61 20 68 61 20 69 6D 70 69 65 67 61 74 6F mma ha impiegato
00000320 20 25 64 20 6D 69 6C 6C 69 73 65 63 6F 6E 64 69 %d millisecondi
00000330 0D 0A 70 65 72 20 65 73 65 67 75 69 72 65 20 74 ..per eseguire t
00000340 75 74 74 65 20 6C 65 20 69 6E 74 65 72 61 7A 69 utte le interazi
00000350 6F 6E 69 21 21 21 0D 0A 0D 0A 50 75 6F 69 20 6D oni!!!....Puoi m
00000360 69 67 6C 69 6F 72 61 72 65 20 3A 29 00 54 65 6D igliorare :).Tem
00000370 70 6F 20 69 6D 70 69 65 67 61 74 6F 2E 2E 2E 00 po impiegato....
00000380 4E 75 6D 65 72 6F 20 67 65 6E 65 72 61 74 6F 00 Numero generato.
00000390 47 69 75 73 74 6F 20 70 65 72 20 63 75 72 69 6F Giusto per curio
000003A0 73 69 74 E0 2C 20 65 63 63 6F 20 69 6C 20 6E 75 sità, ecco il nu
000003B0 6D 65 72 6F 20 63 68 65 20 68 61 69 20 67 65 6E mero che hai gen
000003C0 65 72 61 74 6F 3A 20 25 64 00 erato: %d.
Adesso dobbiamo cambiare i gli offset che abbiamo nel codice e adattarli alle nuove ubicazioni dei buffers...
:0040109E 68 5C 20 40 00 push offset aIlProgrammaHaI
Cambiamo 685C204000 in 6807114000, poi:
:004010AF 68 C2 20 40 00 push offset aTempoImpiegato
Cambiamo 68C2204000 in 686D114000, poi:
:004010C6 68 E5 20 40 00 push offset aGiustoPerCurio
Cambiamo 68E5204000 in 6890114000, poi:
:004010D7 68 D5 20 40 00 push offset aNumeroGenerato
Cambiamo 68D5204000 in 6880114000. Adesso che ci siamo occupati dei buffer "inizializzati", dobbiamo occuparci di quelli non inizializzati (cioè in verità lo sono, però a noi non serve che lo siano dato che vengono sovrascritti comunque) che sono dichiarati originariamente nel source come:
buffer db 50 dup(0)
buf2 db 30 dup(0)
calcbuf db 12 dup(0)
I buffer li faremo puntare all'interno dello spazio allocato per la sezione code immediatamente dopo il file alignment, cioè i buffer non esisteranno fisicamente, esisteranno solo nel file mappato in memoria dal loader. Cominciamo quindi da buffer.
:004010A3 68 00 20 40 00 push offset Text ; buffer
:004010B4 68 00 20 40 00 push offset Text ; buffer
Cambiamo 6800204000 in 6800124000, adesso buf2:
:004010CB 68 32 20 40 00 push offset unk_402032 ; buf2
:004010DC 68 32 20 40 00 push offset unk_402032 ; buf2
Cambiamo 6832204000 in 6832124000 (considerando che buffer è grande 50d ovvero 32h), adesso calcbuf:
:00401026 BF 50 20 40 00 mov edi, offset dword_402050 ; calcbuf
:0040104C BF 50 20 40 00 mov edi, offset dword_402050 ; calcbuf
:004010C0 FF 35 50 20 40 00 push ds:dword_402050 ; calcbuf
Cambiamo BF50204000 in BF50124000 e FF3550204000 in FF3550124000. Adesso affinché queste prime modifiche fungano dobbiamo cambiare l'attributi della sezione CODE togliamo Executable e Contains Code (perché inutili) e lasciamo solo Readable e Writeable (se non aggiungiamo quest'ultimo l'exe crasherà subito). Inoltre azzeriamo il campo Base Of Data poiché logicamente sbagliato ed effettivamente inutile.
Cancelliamo con l'hex editor adesso le entry di DATA e .idata, dalla sezione CODE fino all'inizio fisico di essa avremo A0h bytes, e guardate cosa sta a pennello e dico proprio a pennello in questo spazio:
00000130 00 00 00 00 00 00 00 00 43 4F 44 45 00 00 00 00 ........CODE....
00000140 00 10 00 00 00 10 00 00 00 04 00 00 00 02 00 00 ................
00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C0 ...............À
00000160 49 6C 20 70 72 6F 67 72 61 6D 6D 61 20 68 61 20 Il programma ha
00000170 69 6D 70 69 65 67 61 74 6F 20 25 64 20 6D 69 6C impiegato %d mil
00000180 6C 69 73 65 63 6F 6E 64 69 0D 0A 70 65 72 20 65 lisecondi..per e
00000190 73 65 67 75 69 72 65 20 74 75 74 74 65 20 6C 65 seguire tutte le
000001A0 20 69 6E 74 65 72 61 7A 69 6F 6E 69 21 21 21 0D interazioni!!!.
000001B0 0A 0D 0A 50 75 6F 69 20 6D 69 67 6C 69 6F 72 61 ...Puoi migliora
000001C0 72 65 20 3A 29 00 47 69 75 73 74 6F 20 70 65 72 re :).Giusto per
000001D0 20 63 75 72 69 6F 73 69 74 E0 2C 20 65 63 63 6F curiosità, ecco
000001E0 20 69 6C 20 6E 75 6D 65 72 6F 20 63 68 65 20 68 il numero che h
000001F0 61 69 20 67 65 6E 65 72 61 74 6F 3A 20 25 64 00 ai generato: %d.
00000200 E8 F0 00 00 00 50 8B C8 C1 E0 08 8B C8 C1 E0 0C èð...P‹ÈÁà.‹ÈÁà.
Come vedete da 160h iniza lo spazio libero e da 200h inizia la sezione CODE, insomma ci le due frasi ci stanno alla perfezione (lo sapevo che lo spazio acquistato eliminando il dos stub fosse essenziale). Ripatchiamo quindi i push riguardanti i buffer inizializzati (non preoccupatevi gli altri vanno bene).
:0040109E 68 07 11 40 00 push offset aIlProgrammaHaI
Cambiamo 6807114000 in 6860014000, poi:
:004010C6 68 90 11 40 00 push offset aGiustoPerCurio
Cambiamo 6890114000 in 68C6014000. Adesso ci restano i due buffer di dimensione più piccola che rappresentano i titoli dei msgbox e che abbiamo lasciamo in fondo alla sezione CODE:
| Code | Buffers
00000300 00 FF 25 4A 11 40 00 54 65 6D 70 6F 20 69 6D 70 .ÿ%J.@.Tempo imp
00000310 69 65 67 61 74 6F 2E 2E 2E 00 4E 75 6D 65 72 6F iegato....Numero
00000320 20 67 65 6E 65 72 61 74 6F 00 generato.
Patchiamo...
:004010AF 68 6D 11 40 00 push offset aTempoImpiegato
Cambiamo 686D114000 in 6807114000, poi:
:004010D7 68 80 11 40 00 push offset aNumeroGenerato
Cambiamo 6880114000 in 6880114000. Ok adesso abbiamo D6h bytes in fondo alla sezione CODE per aggiungere l'import table, spazio che basta e avanza. Inoltre sulla IT si possono fare un sacco di ottimizzazioni. Come prima cosa accodiamo i nomi delle dll utilizzate in fondo alla sezione CODE:
| Function's Names
00000320 20 67 65 6E 65 72 61 74 6F 00 4B 45 52 4E 45 4C generato.KERNEL
00000330 33 32 00 55 53 45 52 33 32 00 32.USER32.
Come potete vedere ho omesso ".DLL" per entrambe le dll, questo perché il loader nel caso mancasse l'estensione automaticamente aggiunge ".DLL"
(questo per quanto riguarda il loader di XP, a questo proposito rimando alla
Nota Finale). Già facendo così abbiamo risparmiato 8 bytes e sapete com'è: wer den Pfennig nicht ehrt, ist des Talers nicht wert.
Inoltre accodiamo l'array First Thunk, che però stavolta non riempiremo di RVA che puntano a delle strutture IMAGE_IMPORT_BY_NAME (come nell'exe originale), ma importeremo le funzioni tramite ordinal e ciò ci farà risparmiare un sacco di bytes (per sapere quanto considerate ogni nome di funzione + terminatore stringa + WORD). Per importare tramite ordinal dobbiamo scrivere nell'array per ogni funzione 80000000h (ordinal flag) + ordinal della funzione. Vediamo gli ordinal delle funzioni che ci servono:
KERNEL32
ExitProcess ACh
GetTickCount 1BFh
USER32
MessageBoxA 1DDh
wsprintfA 2D9h
Ovviamente questa tabella vale per il mio Windows XP, sul vostro os dovrete quasi sicuramente cambiare gli Ordinal con quelli che tornano a voi, anche se avete XP è molto probabile che gli ordinal siano diversi, nel caso non aveste esattamente la mia versione di XP (sono questi gli svantaggi dell'import by ordinal, gh). In ogni caso nessuno ce lo vieta, quindi va più che bene. Quindi in hex avremo (o meglio avrò):
00000330 AC 00 00 80 BF 01 ¬..€¿.
00000340 00 80 00 00 00 00 DD 01 00 80 D9 02 00 80 00 00 .€....Ý..€Ù..€..
00000350 00 00 ..
Come vedete alla fine di entrambi gli array è necessario lasciare una dword terminatrice, tenetelo a mente dato che anche questo farà parte dell'ottimizzazione. Ora dobbiamo aggiungere gli import descriptors, di cui vediamo la struttura:
typedef struct _IMAGE_IMPORT_DESCRIPTOR
{
union
{
DWORD Characteristics;
DWORD OriginalFirstThunk;
};
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
Analizziamo ciò che dovranno contenere i nostri descriptors:
OriginalFirstThunk: questo primo parametro non ci serve dato che facciamo uso solo di First Thunk (olé).
TimeDateStamp: 0.
ForwarderChain: 0.
Name: punta al nome del modulo.
FirstThunk: punta al proprio array.
Poiché il primo parametro dei descriptors è 0 possiamo far iniziare il primo descriptor dal terminatore dell'ultimo array First Thunk, Concatenando le due dword risparmieremo su una dword nulla:
00000320 4B 45 52 4E 45 4C KERNEL
00000330 33 32 00 55 53 45 52 33 32 00 AC 00 00 80 BF 01 32.USER32.¬..€¿.
00000340 00 80 00 00 00 00 DD 01 00 80 D9 02 00 80 00 00 .€....Ý..€Ù..€..
00000350 00 00 00 00 00 00 00 00 00 00 2A 11 00 00 3A 11 ..........*...:.
00000360 00 00 00 00 00 00 00 00 00 00 00 00 00 00 33 11 ..............3.
00000370 00 00 46 11 00 00 ..F...
Anche porre i descriptors alla fine è stata una scelta pensata dato che pure l'array di descriptors necessita di un terminatore che consiste di 5 dword nulle. Adesso dobbiamo patchare i 4 jmp che nel file saltano alla IAT.
:004010EF FF 25 54 30 40 00 jmp ds:ExitProcess
Cambiamo FF2554304000 in FF253A114000, poi:
:004010F5 FF 25 58 30 40 00 jmp ds:__imp_GetTickCount
Cambiamo FF2558304000 in FF253E114000, poi:
:004010FB FF 25 60 30 40 00 jmp ds:__imp_MessageBoxA
Cambiamo FF2560304000 in FF2546114000, poi:
:00401101 FF 25 64 30 40 00 jmp ds:__imp_wsprintfA
Cambiamo FF2564304000 in FF254A114000. Se avremo fatto tutto correttamente dovremo avere questo risultato (partendo dall'entry CODE):
00000130 43 4F 44 45 00 00 00 00 CODE....
00000140 00 10 00 00 00 10 00 00 00 04 00 00 00 02 00 00 ................
00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C0 ...............À
00000160 49 6C 20 70 72 6F 67 72 61 6D 6D 61 20 68 61 20 Il programma ha
00000170 69 6D 70 69 65 67 61 74 6F 20 25 64 20 6D 69 6C impiegato %d mil
00000180 6C 69 73 65 63 6F 6E 64 69 0D 0A 70 65 72 20 65 lisecondi..per e
00000190 73 65 67 75 69 72 65 20 74 75 74 74 65 20 6C 65 seguire tutte le
000001A0 20 69 6E 74 65 72 61 7A 69 6F 6E 69 21 21 21 0D interazioni!!!.
000001B0 0A 0D 0A 50 75 6F 69 20 6D 69 67 6C 69 6F 72 61 ...Puoi migliora
000001C0 72 65 20 3A 29 00 47 69 75 73 74 6F 20 70 65 72 re :).Giusto per
000001D0 20 63 75 72 69 6F 73 69 74 E0 2C 20 65 63 63 6F curiosità, ecco
000001E0 20 69 6C 20 6E 75 6D 65 72 6F 20 63 68 65 20 68 il numero che h
000001F0 61 69 20 67 65 6E 65 72 61 74 6F 3A 20 25 64 00 ai generato: %d.
00000200 E8 F0 00 00 00 50 8B C8 C1 E0 08 8B C8 C1 E0 0C èð...P‹ÈÁà.‹ÈÁà.
00000210 52 8B D1 5A E2 F7 33 C0 33 D2 8B C1 0F 31 25 FF R‹ÑZâ÷3À3Ò‹Á.1%ÿ
00000220 FF 00 00 8B C8 60 BF 50 12 40 00 89 07 8B 17 0F ÿ..‹È`¿P.@.‰.‹..
00000230 31 03 D0 89 47 04 01 07 89 57 08 8B 17 03 C2 89 1.ЉG...‰W.‹..‰
00000240 47 08 8B 47 04 E2 DF 61 33 C9 B1 62 BF 50 12 40 G.‹G.âßa3ɱb¿P.@
00000250 00 8B 04 0F 89 04 0F E2 F8 8B 0F 91 98 90 81 E1 .‹..‰..âø‹.‘˜á
00000260 FF FF 00 00 0F 31 21 07 01 47 04 8B 5F 04 03 C3 ÿÿ...1!..G.‹_..Ã
00000270 89 07 F7 27 F7 27 F7 27 F7 27 F7 27 F7 27 40 03 ‰.÷'÷'÷'÷'÷'÷'@.
00000280 C0 F7 37 C1 57 08 87 E2 DD 8B 0F 81 E1 FF FF 00 À÷7ÁW.‡âÝ‹.áÿÿ.
00000290 00 93 E2 FD E8 5C 00 00 00 93 58 2B D8 53 68 60 .“âýè\...“X+ØSh`
000002A0 01 40 00 68 00 12 40 00 E8 54 00 00 00 6A 10 68 .@.h..@.èT...j.h
000002B0 07 11 40 00 68 00 12 40 00 6A 00 E8 3B 00 00 00 ..@.h..@.j.è;...
000002C0 FF 35 50 12 40 00 68 C6 01 40 00 68 32 12 40 00 ÿ5P.@.hÆ.@.h2.@.
000002D0 E8 2C 00 00 00 6A 20 68 1A 11 40 00 68 32 12 40 è,...j h..@.h2.@
000002E0 00 6A 00 E8 13 00 00 00 6A 00 E8 00 00 00 00 FF .j.è....j.è....ÿ
000002F0 25 3A 11 40 00 FF 25 3E 11 40 00 FF 25 46 11 40 %:.@.ÿ%>.@.ÿ%F.@
00000300 00 FF 25 4A 11 40 00 54 65 6D 70 6F 20 69 6D 70 .ÿ%J.@.Tempo imp
00000310 69 65 67 61 74 6F 2E 2E 2E 00 4E 75 6D 65 72 6F iegato....Numero
00000320 20 67 65 6E 65 72 61 74 6F 00 4B 45 52 4E 45 4C generato.KERNEL
00000330 33 32 00 55 53 45 52 33 32 00 AC 00 00 80 BF 01 32.USER32.¬..€¿.
00000340 00 80 00 00 00 00 DD 01 00 80 D9 02 00 80 00 00 .€....Ý..€Ù..€..
00000350 00 00 00 00 00 00 00 00 00 00 2A 11 00 00 3A 11 ..........*...:.
00000360 00 00 00 00 00 00 00 00 00 00 00 00 00 00 33 11 ..............3.
00000370 00 00 46 11 00 00 ..F...
Adesso modifichiamo col pe editor l'entry dell'import table nella Data Directory, mettiamo come RVA 114Eh e come Size 28h (tanto per essere precisi). Rimoviamo i byte superflui e concludiamo il file a 3FFh (nell'hex editor). Adesso abbiamo un file di esattamente 1 k. Bene ma siccome il loader di NT è un ganzo possiamo tagliare il file dalla posizione 375h (dato che non dovremo includere le 5 dword nulle che costituiscono il descriptor terminatore (vedete che avevo pensato bene)), col pe editor settiamo la grandezza fisica della sezione CODE a 176h e aggiorniamo. Adesso
abbiamo un file di 886 byte.
Un piccolo trucco per mangiarci ancora qualche byte sarebbe quello di rinominare la sezione CODE in USER32 e usarla per l'apposito import descriptor. Se lo facciamo otterremo un eseguibile di 879 byte dal quale potremo arrivare a 877 dato che possiamo tagliare gli ultimi due byte che sono nulli.
Ci sarebbero ancora diverse cose da fare, ma non voglio anticipare il prossimo
paragrafo nel quale alcune tecniche sarebbero applicabili anche a questo exe.
COME PRENDERSI GIOCO DEL QUE
Mi spiace per chi si fosse già alzato per andarsene, ne abbiamo ancora per 3 orette e mezza (mitico Luttazzi). Scherzo (mica tanto), ma questo è il paragrafo più divertente e sicuramente senza di esso neanche avrei scritto il tutorial.
Ricito ciò che ci dice Quequero a proposito dell'ottimizzazione sul PE.
2) presentarmi l'exe più piccolo possibile :) e stavolta potete anche toccare i buffer e quello che volete, ma l'exe COMPLETO finale deve essere il più piccolo possibile. La soluzione più rapida sarebbe quella di ottimizzare la .CODE e rimpicciolire l'exe, ma potete anche fare le due cose separatamente se vi piace :)...Ah, il testo delle due Messagebox NON può cambiare, deve restare quello :).
Schematizzando le regole per quanto riguarda l'ottimizzazione del PE sarebbero queste:
1) Il codice 'eseguito' deve essere quello originariamente ottimizzato (e la metto come sottointesa).
2) I conenuti delle msg box non devono cambiare.
3) Scopo: Rendere l'exe più piccolo possibile.
E questo lo abbiamo fatto però non viene specificato da Quequero se non è permesso di usare un modulo di supporto... Voglio dire un modulo che setta tutti i contenuti e runtime nel memory space dell'exe... Eh Que non puoi permetterti di non specificare certe cose con me, mi spiace. E' vero che è uno strattagemma per creare l'exe in assoluto più piccolo però come idea può anche servire nel reversing, non si dissocia totalmente da:
nel reversing spesso è necessario scrivere routine piccole piccole per poter farle entrare in qualche buffer minuscolo che abbiamo a disposizione e spesso questo diventa un problema....
Anzi è un'idea alternativa che può far comodo quella di usare un modulo di supporto.. Se non si tratta di buffer overflow io opterei sempre per un modulo. Voglio dire scrivere un modulo non mi costa nulla e non devo stare attento a nessuna ottimizzazione del cazzo, inoltre anche aggiungere un import ad un exe è una bojata assurda. L'unica differenza è che in genere il modulo nel reversing serve per metterci codice aggiuntivo, nel nostro caso invece ci servirà per fare code, data e IAT injection.
Per comodità non riallocherò i buffers inizializzati e la IAT da altre parti (anche se potrei perché sarebbe forse più bello), ma non ho voglia di ripatchare di nuovo, inoltre il risultato finale non cambia.
Ecco il codice della dll di supporto che ho scritto in VC++ (alla faccia del tasm, tiè).
-- mod.cpp ----------------------------------------------------------
#include <windows.h>
BYTE Buf1[] =
{
0x49, 0x6C, 0x20, 0x70, 0x72, 0x6F, 0x67, 0x72, 0x61,
0x6D, 0x6D, 0x61, 0x20, 0x68, 0x61, 0x20, 0x69, 0x6D,
0x70, 0x69, 0x65, 0x67, 0x61, 0x74, 0x6F, 0x20, 0x25,
0x64, 0x20, 0x6D, 0x69, 0x6C, 0x6C, 0x69, 0x73, 0x65,
0x63, 0x6F, 0x6E, 0x64, 0x69, 0x0D, 0x0A, 0x70, 0x65,
0x72, 0x20, 0x65, 0x73, 0x65, 0x67, 0x75, 0x69, 0x72,
0x65, 0x20, 0x74, 0x75, 0x74, 0x74, 0x65, 0x20, 0x6C,
0x65, 0x20, 0x69, 0x6E, 0x74, 0x65, 0x72, 0x61, 0x7A,
0x69, 0x6F, 0x6E, 0x69, 0x21, 0x21, 0x21, 0x0D, 0x0A,
0x0D, 0x0A, 0x50, 0x75, 0x6F, 0x69, 0x20, 0x6D, 0x69,
0x67, 0x6C, 0x69, 0x6F, 0x72, 0x61, 0x72, 0x65, 0x20,
0x3A, 0x29, 0x00, 0x47, 0x69, 0x75, 0x73, 0x74, 0x6F,
0x20, 0x70, 0x65, 0x72, 0x20, 0x63, 0x75, 0x72, 0x69,
0x6F, 0x73, 0x69, 0x74, 0xE0, 0x2C, 0x20, 0x65, 0x63,
0x63, 0x6F, 0x20, 0x69, 0x6C, 0x20, 0x6E, 0x75, 0x6D,
0x65, 0x72, 0x6F, 0x20, 0x63, 0x68, 0x65, 0x20, 0x68,
0x61, 0x69, 0x20, 0x67, 0x65, 0x6E, 0x65, 0x72, 0x61,
0x74, 0x6F, 0x3A, 0x20, 0x25, 0x64, 0x00
};
BYTE Buf2[] =
{
0xE8, 0xF0, 0x00, 0x00, 0x00, 0x50, 0x8B, 0xC8, 0xC1,
0xE0, 0x08, 0x8B, 0xC8, 0xC1, 0xE0, 0x0C, 0x52, 0x8B,
0xD1, 0x5A, 0xE2, 0xF7, 0x33, 0xC0, 0x33, 0xD2, 0x8B,
0xC1, 0x0F, 0x31, 0x25, 0xFF, 0xFF, 0x00, 0x00, 0x8B,
0xC8, 0x60, 0xBF, 0x50, 0x12, 0x40, 0x00, 0x89, 0x07,
0x8B, 0x17, 0x0F, 0x31, 0x03, 0xD0, 0x89, 0x47, 0x04,
0x01, 0x07, 0x89, 0x57, 0x08, 0x8B, 0x17, 0x03, 0xC2,
0x89, 0x47, 0x08, 0x8B, 0x47, 0x04, 0xE2, 0xDF, 0x61,
0x33, 0xC9, 0xB1, 0x62, 0xBF, 0x50, 0x12, 0x40, 0x00,
0x8B, 0x04, 0x0F, 0x89, 0x04, 0x0F, 0xE2, 0xF8, 0x8B,
0x0F, 0x91, 0x98, 0x90, 0x81, 0xE1, 0xFF, 0xFF, 0x00,
0x00, 0x0F, 0x31, 0x21, 0x07, 0x01, 0x47, 0x04, 0x8B,
0x5F, 0x04, 0x03, 0xC3, 0x89, 0x07, 0xF7, 0x27, 0xF7,
0x27, 0xF7, 0x27, 0xF7, 0x27, 0xF7, 0x27, 0xF7, 0x27,
0x40, 0x03, 0xC0, 0xF7, 0x37, 0xC1, 0x57, 0x08, 0x87,
0xE2, 0xDD, 0x8B, 0x0F, 0x81, 0xE1, 0xFF, 0xFF, 0x00,
0x00, 0x93, 0xE2, 0xFD, 0xE8, 0x5C, 0x00, 0x00, 0x00,
0x93, 0x58, 0x2B, 0xD8, 0x53, 0x68, 0x60, 0x01, 0x40,
0x00, 0x68, 0x00, 0x12, 0x40, 0x00, 0xE8, 0x54, 0x00,
0x00, 0x00, 0x6A, 0x10, 0x68, 0x07, 0x11, 0x40, 0x00,
0x68, 0x00, 0x12, 0x40, 0x00, 0x6A, 0x00, 0xE8, 0x3B,
0x00, 0x00, 0x00, 0xFF, 0x35, 0x50, 0x12, 0x40, 0x00,
0x68, 0xC6, 0x01, 0x40, 0x00, 0x68, 0x32, 0x12, 0x40,
0x00, 0xE8, 0x2C, 0x00, 0x00, 0x00, 0x6A, 0x20, 0x68,
0x1A, 0x11, 0x40, 0x00, 0x68, 0x32, 0x12, 0x40, 0x00,
0x6A, 0x00, 0xE8, 0x13, 0x00, 0x00, 0x00, 0x6A, 0x00,
0xE8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x25, 0x3A, 0x11,
0x40, 0x00, 0xFF, 0x25, 0x3E, 0x11, 0x40, 0x00, 0xFF,
0x25, 0x46, 0x11, 0x40, 0x00, 0xFF, 0x25, 0x4A, 0x11,
0x40, 0x00, 0x54, 0x65, 0x6D, 0x70, 0x6F, 0x20, 0x69,
0x6D, 0x70, 0x69, 0x65, 0x67, 0x61, 0x74, 0x6F, 0x2E,
0x2E, 0x2E, 0x00, 0x4E, 0x75, 0x6D, 0x65, 0x72, 0x6F,
0x20, 0x67, 0x65, 0x6E, 0x65, 0x72, 0x61, 0x74, 0x6F,
0x00
};
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason,
LPVOID lpReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
DWORD OldPr;
// Mi assicuro i permessi
if (!VirtualProtect((LPVOID) 0x400000, 0x2000, PAGE_READWRITE, &OldPr))
return FALSE;
memcpy((void *) 0x400160, Buf1, 0xA0);
memcpy((void *) 0x401000, Buf2, 0x12A);
// costruisco la IAT
HINSTANCE hKernel32 = LoadLibrary("Kernel32.dll");
DWORD Func = (DWORD) GetProcAddress(hKernel32, "ExitProcess");
memcpy((void *) 0x40113A, &Func, sizeof (DWORD));
Func = (DWORD) GetProcAddress(hKernel32, "GetTickCount");
memcpy((void *) 0x40113E, &Func, sizeof (DWORD));
HINSTANCE hUser32 = LoadLibrary("User32.dll");
Func = (DWORD) GetProcAddress(hUser32, "MessageBoxA");
memcpy((void *) 0x401146, &Func, sizeof (DWORD));
Func = (DWORD) GetProcAddress(hUser32, "wsprintfA");
memcpy((void *) 0x40114A, &Func, sizeof (DWORD));
}
return TRUE;
}
extern "C" __declspec(dllexport) VOID Nothing() {}
---------------------------------------------------------------------
Il codice è semplice e le parti da spiegare poche. Come potete vedere ho usato l'EP della dll per iniettare ciò che volevo, poiché questo mi il codice per chiamare una funzione. Inoltre la riga:
extern "C" __declspec(dllexport) VOID Nothing() {}
Esporta una funzione che ci servirà solo come pretesto per caricare la dll di supporto, non ha nessuno scopo utile, ma una funzione devo per forza esportarla per poi importarla nell'exe.
Ed ecco l'exe modificato:
00000000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 MZ.........ÿÿ..
00000010 B8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ¸...............
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 ............@...
00000040 50 45 00 00 4C 01 01 00 C9 2E 7A 4C 00 00 00 00 PE..L...É.zL....
00000050 00 00 00 00 E0 00 8E 81 0B 01 02 19 00 02 00 00 ....à.Ž........
00000060 00 06 00 00 00 00 00 00 00 10 00 00 00 10 00 00 ................
00000070 00 10 00 00 00 00 40 00 00 10 00 00 00 02 00 00 ......@.........
00000080 01 00 00 00 00 00 00 00 03 00 0A 00 00 00 00 00 ................
00000090 00 20 00 00 00 02 00 00 0F A4 00 00 02 00 00 00 . .......¤......
000000A0 00 00 10 00 00 20 00 00 00 00 10 00 00 10 00 00 ..... ..........
000000B0 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 ................
000000C0 04 10 00 00 18 00 00 00 00 00 00 00 00 00 00 00 ................
000000D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000130 00 00 00 00 00 00 00 00 43 4F 44 45 00 00 00 00 ........CODE....
00000140 00 10 00 00 00 10 00 00 18 00 00 00 00 02 00 00 ................
00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C0 ...............À
00000160 6D 6F 64 2E 64 6C 6C 00 00 00 00 00 00 00 00 00 mod.dll.........
00000170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000200 01 00 00 80 00 00 00 00 00 00 00 00 00 00 00 00 ...€............
00000210 60 01 00 00 00 10 00 00 `.......
Analizziamo passo passo, il nome della dll da caricare viene subito dopo l'entry della sezione CODE e di spazio da quelle parti come è facile notare ce n'è ancora tanto. A partire dal raw address della sezione CODE troviamo l'array delle funzioni importate (solamente una in verità la Nothing). Questo array che altro non è che la finta IAT dell'eseguibile, sfrutta il primo campo della import table (OriginalFirstThunk) come terminatore (dato che questo campo è nullo). La import table comincia quindi dall'RVA 1004h e termina a 1018h, il terminatore per i descriptors esisterà ovviamente solo in memoria. Come già detto visto che sfruttiamo l'ep della dll non è necessario mettere nessuna parte di codice e così abbiamo risparmiato altri byte. L'unica cosa che dobbiamo fare è modificare l'entry nella Data Directory della IT e settarla a 1004h, come Size mettete quello che vi pare (io ho messo 18h), cambiate ancora il raw size della sezione CODE e mettete 18h.
Ok adesso che tutto funziona, abbiamo ottenuto un exe di 536 byte... Meglio di niente dato che è già un buon risultato. Pensate di aver finito? Macché adesso viene il bello. Dunque poiché la header zone di un PE è solo readable per il loader (almeno generalmente) teoricamente non potremmo metterci la IAT, invece noi faremo in modo che ciò sia possibile; vi dirò di più: metteremo tutta l'IT nella header zone.
Con l'hex editor facciamo un Taglia/Incolla in modo da ottenere:
00000130 00 00 00 00 00 00 00 00 43 4F 44 45 00 00 00 00 ........CODE....
00000140 00 10 00 00 00 10 00 00 00 00 00 00 00 02 00 00 ................
00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C0 ...............À
00000160 6D 00 01 00 00 80 00 00 00 00 00 00 00 00 00 00 m....€..........
00000170 00 00 60 01 00 00 62 01 00 00 ..`...b...
Come vedete la IAT + IT viene direttamente dopo la parola "m" (che sta per il nome della dll da caricare, ho ridotto da mod.dll a m per un buon motivo che dopo vedremo). Adesso dobbiamo fare in modo che la IAT sia scrivibile per il loader, affinché ciò sia possibile dobbiamo settare l'indirizzo della IAT all'interno della Data Directory. Mettiamo quindi Address 162h e Size 8. Settiamo Address e Size (14h) della IT e rimuoviamo tutti i byte a partire dalla locazione 180h (il loader di XP ce lo consente). Settiamo il Raw Size della sezione CODE a 0 e salviamo. Ovviamente rinominate anche la dll fisicamente da mod.dll a m.dll.
Ok adesso abbiamo un PE di 378 bytes. Potreste pensare che ho concluso... Nemmeno per sogno, solamente che, come sicuramente vi siete accorti, procedo passo passo, altrimenti non si capirebbe più niente.
Intanto eccovi l'hex del PE raggiunto:
00000000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 MZ.........ÿÿ..
00000010 B8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ¸...............
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 ............@...
00000040 50 45 00 00 4C 01 01 00 C9 2E 7A 4C 00 00 00 00 PE..L...É.zL....
00000050 00 00 00 00 E0 00 8E 81 0B 01 02 19 00 02 00 00 ....à.Ž........
00000060 00 06 00 00 00 00 00 00 00 10 00 00 00 10 00 00 ................
00000070 00 10 00 00 00 00 40 00 00 10 00 00 00 02 00 00 ......@.........
00000080 01 00 00 00 00 00 00 00 03 00 0A 00 00 00 00 00 ................
00000090 00 20 00 00 00 02 00 00 0F A4 00 00 02 00 00 00 . .......¤......
000000A0 00 00 10 00 00 20 00 00 00 00 10 00 00 10 00 00 ..... ..........
000000B0 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 ................
000000C0 66 01 00 00 14 00 00 00 00 00 00 00 00 00 00 00 f...............
000000D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000110 00 00 00 00 00 00 00 00 62 01 00 00 08 00 00 00 ........b.......
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000130 00 00 00 00 00 00 00 00 43 4F 44 45 00 00 00 00 ........CODE....
00000140 00 10 00 00 00 10 00 00 00 00 00 00 00 02 00 00 ................
00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C0 ...............À
00000160 6D 00 01 00 00 80 00 00 00 00 00 00 00 00 00 00 m....€..........
00000170 00 00 60 01 00 00 62 01 00 00 ..`...b...
E' già alquanto ridotto, ma possiamo fare di più: è il momento di toccare la header zone. La prima cosa che facciamo è di usare un entry della Data Directory per piazzare la IAT, tanto ci serve solo di piazzare la dword 80000001h, che se messa in un Size field non farà nessun danno, dato che il loader non tiene conto delle entry del quale address è nullo. Personalmente ho piazzato la dword nel Size della Resource Directory (ovvero all'offset CCh). Dopodiché ho rinominato il nome della m.dll in code.dll e uso la stringa 'CODE' come nome della dll (non posso rinominare la sezione, senno verrei a meno con le regole di Quequero), così facendo risparmio altri due byte (in modo pulito).
E adesso una bella scoperta, guardate gli ultimi byte dell'entry della sezione CODE:
00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C0 ...............À
Tutti nulli, tranne l'ultima dword che è C0000000h e serve ad indicare le proprietà della sezione, ma anche quella possiamo azzerarla tranquillamente dato che il VirtualProtect chiamato dalla nostra dll ci fornisce tutti i permessi di cui in seguito avremo bisogno, inoltre il loader non fa caso a valori 'strani'.
Adesso che abbiamo 4 dword nulle: sfruttiamole! Potremmo metterci l'IT (e originariamente lo avevo fatto) ma c'è un modo migliore di sfruttarle: le eliminiamo. Ed eliminiamo anche i due byte (nulli) alla locazione 14Eh.
La IT invece dato che comporta 3 dword nulle seguite da 2 dword non-nulle, le mettiamo invece sempre all'interno della Data Directory. Poiché questa volta si tratta di due dword non-nulle conseguenti, dovremo sceglierci un campo obsoleto e che comunque non viene considerato, io ho preso il debug field, ma potevo anche prendere il copyright, in ogni caso avanza spazio a sufficenza per l'import descriptor nullo.
Setto la giusta posizione dell'IT nella Data Dir (DCh nel mio caso) e salvo tutto. Adesso siamo a 334 bytes.
Eccovi l'hex di questo PE:
00000000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 MZ.........ÿÿ..
00000010 B8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ¸...............
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 ............@...
00000040 50 45 00 00 4C 01 01 00 C9 2E 7A 4C 00 00 00 00 PE..L...É.zL....
00000050 00 00 00 00 E0 00 8E 81 0B 01 02 19 00 02 00 00 ....à.Ž........
00000060 00 06 00 00 00 00 00 00 00 10 00 00 00 10 00 00 ................
00000070 00 10 00 00 00 00 40 00 00 10 00 00 00 02 00 00 ......@.........
00000080 01 00 00 00 00 00 00 00 03 00 0A 00 00 00 00 00 ................
00000090 00 20 00 00 00 02 00 00 0F A4 00 00 02 00 00 00 . .......¤......
000000A0 00 00 10 00 00 20 00 00 00 00 10 00 00 10 00 00 ..... ..........
000000B0 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 ................
000000C0 DC 00 00 00 14 00 00 00 00 00 00 00 01 00 00 80 Ü..............€
000000D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000E0 00 00 00 00 00 00 00 00 38 01 00 00 CC 00 00 00 ........8...Ì...
000000F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000110 00 00 00 00 00 00 00 00 CC 00 00 00 08 00 00 00 ........Ì.......
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000130 00 00 00 00 00 00 00 00 43 4F 44 45 00 00 00 00 ........CODE....
00000140 00 10 00 00 00 10 00 00 00 00 00 00 00 02 ..............
Mi sono accorto che del raw address della sezione non me ne faccio nulla !!! Quindi dalla locazione 146h (compresa) possiamo cancellare tutti i byte. Otteniamo questo:
00000000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 MZ.........ÿÿ..
00000010 B8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ¸...............
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 ............@...
00000040 50 45 00 00 4C 01 01 00 C9 2E 7A 4C 00 00 00 00 PE..L...É.zL....
00000050 00 00 00 00 E0 00 8E 81 0B 01 02 19 00 02 00 00 ....à.Ž........
00000060 00 06 00 00 00 00 00 00 00 10 00 00 00 10 00 00 ................
00000070 00 10 00 00 00 00 40 00 00 10 00 00 00 02 00 00 ......@.........
00000080 01 00 00 00 00 00 00 00 03 00 0A 00 00 00 00 00 ................
00000090 00 20 00 00 00 02 00 00 0F A4 00 00 02 00 00 00 . .......¤......
000000A0 00 00 10 00 00 20 00 00 00 00 10 00 00 10 00 00 ..... ..........
000000B0 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 ................
000000C0 DC 00 00 00 14 00 00 00 00 00 00 00 01 00 00 80 Ü..............€
000000D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000E0 00 00 00 00 00 00 00 00 38 01 00 00 CC 00 00 00 ........8...Ì...
000000F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000110 00 00 00 00 00 00 00 00 CC 00 00 00 08 00 00 00 ........Ì.......
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000130 00 00 00 00 00 00 00 00 43 4F 44 45 00 00 00 00 ........CODE....
00000140 00 10 00 00 00 10 ......
Adesso cominciamo col ridurre drasticamente il Dos Header, prima avevamo tolto lo stub, ma è ancora troppo poco. Per capire ciò che ho in mente vi mostro la struttura del Dos Header:
typedef struct _IMAGE_DOS_HEADER
{
WORD e_magic;
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
LONG e_lfanew;
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
Di questa struttura gli unici campi indispensabili per noi sono e_magic e e_lfanew, tutti gli altri membri 'possono' anche essere invalidi. L'idea è di fondere il campo del Dos Header con quello del Nt Header sfruttando magari un campo (non essenziale) di quest'ultimo per piazzare e_lfanew. Vediamoci l'Nt Header (in forma completa) per vedere dove potremmo agire.
typedef struct _IMAGE_NT_HEADERS
{
DWORD Signature;
struct
_IMAGE_FILE_HEADER
{
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} FileHeader;
struct _IMAGE_OPTIONAL_HEADER
{
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
La distanza tra e_magic e e_lfanew è 58 bytes. Il primo field non essenziale che possiamo raggiunge è BaseOfData, viene una cosa di questo tipo:
00000000 4D 5A 00 00 00 00 00 00 00
00 00 00 50 45 00 00 MZ..........PE..
00000010 4C 01 01 00 C9 2E 7A 4C 00 00 00 00 00 00 00 00
L...É.zL........
00000020 E0 00 8E 81 0B 01 02 19 00 02 00 00 00 06 00 00
à.Ž............
00000030 00 00 00 00 00 10 00 00 00 10 00 00 0C 00 00 00
................
Adesso dobbiamo rimettere a posto tutti gli
offset (IT, IAT), risultato (funzionante):
00000000 4D 5A 00 00 00 00 00 00
00 00 00 00 50 45 00 00 MZ..........PE..
00000010 4C 01 01 00 C9 2E 7A 4C 00 00 00 00 00 00 00 00
L...É.zL........
00000020 E0 00 8E 81 0B 01 02 19 00 02 00 00 00 06 00 00
à.Ž............
00000030 00 00 00 00 00 10 00 00 00 10 00 00 0C 00 00 00
................
00000040 00 00 40 00 00 10 00 00 00 02 00 00 01 00 00 00
..@.............
00000050 00 00 00 00 03 00 0A 00 00 00 00 00 00 20 00 00
............. ..
00000060 00 02 00 00 0F A4 00 00 02 00 00 00 00 00 10 00
.....¤..........
00000070 00 20 00 00 00 00 10 00 00 10 00 00 00 00 00 00 .
..............
00000080 10 00 00 00 00 00 00 00 00 00 00 00 A8 00 00 00
............¨...
00000090 14 00 00 00 00 00 00 00 01 00 00 80 00 00 00 00
...........€....
000000A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
................
000000B0 00 00 00 00 04 01 00 00 98 00 00 00 00 00 00 00
........˜.......
000000C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
................
000000D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
................
000000E0 00 00 00 00 98 00 00 00 08 00 00 00 00 00 00 00
....˜...........
000000F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
................
00000100 00 00 00 00 43 4F 44 45 00 00 00 00 00 10 00 00
....CODE........
00000110 00 10
..
Siamo a 274 bytes e già è il record mondiale (per appena 2 bytes). Ma io
ancora non mi accontento, infatti mi è venuto in mente che la parola CODE sono 4
byte e tra quella parola e la prossima dword piena c'è una dword nulla, posso
quindi far iniziare la Section Table (ho due entry libere) dalla fine della Data
Directory sfruttando la caratteristica che il Size delle entry senza il relativo
Address neanche viene considerato. Per avvicinare la sezione devo solo ridurre
il Size Of Optional Header che faccio diventare CCh, avvicino la sezione e metto
a posto l'indirizzo relativo al nome della dll che è diverso dato che ho
avvicinato la sezione, giudicate voi stessi il risultato finale:
00000000 4D 5A 00 00 00 00 00 00 00 00
00 00 50 45 00 00 MZ..........PE..
00000010 4C 01 01 00 C9 2E 7A 4C 00 00 00 00 00 00 00 00
L...É.zL........
00000020 CC 00 8E 81 0B 01 02 19 00 02 00 00 00 06 00 00
Ì.Ž............
00000030 00 00 00 00 00 10 00 00 00 10 00 00 0C 00 00 00
................
00000040 00 00 40 00 00 10 00 00 00 02 00 00 01 00 00 00
..@.............
00000050 00 00 00 00 03 00 0A 00 00 00 00 00 00 20 00 00
............. ..
00000060 00 02 00 00 0F A4 00 00 02 00 00 00 00 00 10 00
.....¤..........
00000070 00 20 00 00 00 00 10 00 00 10 00 00 00 00 00 00 .
..............
00000080 10 00 00 00 00 00 00 00 00 00 00 00 A8 00 00 00
............¨...
00000090 14 00 00 00 00 00 00 00 01 00 00 80 00 00 00 00
...........€....
000000A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
................
000000B0 00 00 00 00 F0 00 00 00 98 00 00 00 00 00 00 00
....ð...˜.......
000000C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
................
000000D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
................
000000E0 00 00 00 00 98 00 00 00 08 00 00 00 00 00 00 00
....˜...........
000000F0 43 4F 44 45 00 00 00 00 00 10 00 00 00 10
CODE..........
Abbiamo adesso un PE (funzionante) di 254 bytes
(yahoo).
Nota Finale: In questo tutorial ho sfruttato tra i vari artifici
quello di risparmiare il .DLL per le stringhe relative alle dll da importare,
questo trick funziona su XP perché il suo loader fa uno strcat(Name, ".dll")
ogni volta che non trova un'estensione il modulo da importare, mentre invece
win2k se trova il nome CODE tenta di caricare il modulo CODE senza alcuna
estensione: per fare in modo che il nostro modulo venga caricato e il prog funga
anche su win2k dovremo semplicemente eliminare l'estensione della dll. Entrambi
i metodi di caricameto comportano vantaggi e svantaggi, ma a conti fatti
preferisco il metodo xpiano (che neologista che sono). Ad onta di ciò la cosa
migliore sarebbe di implementare entrambi i metodi, tentando prima in un modo e
poi nell'altro: prendete nota voi della MS.
Ok abbiamo, anzi ho, finito. Spero che vi siate almeno divertiti a leggere (sebbene fosse semplice) il tutorial: io per conto mio mi sono divertito a scriverlo.
Ntoskrnl
|
Note finali |
Beh qualche ringraziamento finale mi pare doveroso: ringrazio i coders che hanno fatto il loader di 2k/XP poiché senza il loro lavoro questo tutorial non sarebbe nemmeno stato possibile.
Mando invece affanculo chi ha fatto quel cesso di loader di 9x/ME, DOVETE MORIRE !!
|
Disclaimer |
Scopo didattico? Macché io reverso per soldi e basta.
P.S. Se sei una ragazza (e sei carina) mi puoi anche pagare in altra maniera... Semmai ci mettiamo d'accordo.