Ottimizzazione: Lezione 11 Per Studenti UIC

Data

by Ntoskrnl

 

20/08/2003

UIC's Home Page

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!

 

....

Home page se presente: http://pmode.cjb.net/
E-mail: ntoskrnl@tiscali.it  

....

Difficoltà

( )NewBies (*)Intermedio (*)Avanzato ( )Master

 

Lezione 11 Per Studenti UIC


Ottimizzazione: Lezione 11 Per Studenti UIC
Written by Ntoskrnl

Introduzione

Ho deciso di scrivere questo tutorial solo perché m'è venuto in mente che si poteva introdurre qualcosa di divertente nella soluzione. Una soluzione standard sarebbe alquanto banale, eppoi lo sapete: io mi getto a capo fitto solo su cose che ritengo originali. Inoltre c'è da dire che una soluzione valida e completa ancora non è stata scritta, onde per cui...

Tools usati

Tasm, VC++, wark, Hex Editor (quello che preferite).

URL o FTP del programma

Andate nella sezione 'Lezioni', non sarà difficile trovarlo...

Notizie sul programma

Questo è ciò che ci dice Quequero:

Come spesso avrete avuto modo di constatare (o almeno lo spero :) 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....Perchè molti di ottimizzazione ne capiscono ben poco. Lo scopo di questo corso è proprio quello di insegnarvi le basi dell'ottimizzazione, non è un corso impegnativo ne difficile e sarà utile anche per farvi distendere un pochetto i nervi.
Vi presento un programma scritto in Asm e compilabile con il Tasm reperibile su questo sito alla sezione downloads, il sorgente è lineare ed il programma.....Non fa assolutamente nulla, si preoccupa solo di generare un numero quasi-random utilizzando il più possibile i cicli di clock :) se infatti avete dimistichezza con le tabelle dove sono riportati i cicli necessari al processore per ogni operazione, vedrete che ho strutturato il programma in modo da farvi utilizzare inutilmente tanto processore :)))). Ma il vostro scopo? Il vostro scopo...O meglio...I vostri scopi sono:
1) ottimizzare il sorgente in modo da avere una sezione CODE nell'eseguibile finale più piccola possibile, non preoccupatevi dei buffer, mi interessa solo la .CODE, il programma integra un misuratore per il tempo di esecuzione che vi servirà a livello intuitivo durante l'ottimizzazione.
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 :).
Suppongo che 15 giorni siano sufficienti, semmai prolungo il termine, buon divertimento ragazzi :)

-=Quequero=-


Va bene, faremo in modo di rendere felice il Que.

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.