Corso 11 UIC

Un po' di sana ottimizzazione
(coding)

Data

by "Winebag"

 

31/05/2001

UIC's Home Page

Published by Quequero


Non è stato facile dirti ti amo;
Non è stato facile dirti che con te mi trovo bene;
non è stato facile dirti addio.

Mi complimento con te perché il tutorial è dettagliatissimo e sei stato davvero bravo, ma avevo specificato anche in mailinglist che non potevi togliere le routine ma solo ottimizzarle…Perché? Perché se ognuno toglie la sua istruzione allora si riscrive il programma e il mio intento non era quello di farvi scrivere un programma ma quello di ottimizzarne uno…

Certo che il cinese è proprio una lingua del cazzo!

  Flavio Oreglio

....

E-mail: W1n3b4g@usa.net

....

Difficoltà

(X)NewBies ( )Intermedio ( )Avanzato ( )Master

 

In questo corso dovremo fingerci programmatori,ma lo faremo a modo nostro: il nostro compito infatti è quello di allenarci a scrivere funzioni nel minor spazio possibile. Per una volta il nostro migliore amico sarà il TASM, ma non dimentichiamo certo il debugger.


Un po' di sana ottimizzazione

(Coding)
Written by Winebag


Introduzione

Ciao a tutti, questo è il mio primo tute per l' UIC, pertanto il livello sarà abbastanza basso e comprensibile. Probabilmente vi sembrerà disordinato, infatti non segue necessariamente un ordine logico e compaiono in più punti le stesse parti di codice; questo perché spesso prima di togliere alcune istruzioni è necessario toglierne altre, ma lo capirete leggendo. Le istruzioni che andrò di volta in volta a togliere dovrebbero essere in rosso.

Tools usati


Tasm 5.0 

Un debugger

Dasm
Un editor esadecimale

URL o FTP del programma


Il programma e i sorgenti li trovate al sito della UIC.

Notizie sul programma

Il programma.....Non fa assolutamente nulla, si preoccupa solo di generare un numero quasi-random utilizzando il più possibile i cicli di clock (parole di Quequero)…Rendiamo grazie a Quequero :) NdQue.

Essay

Allora ,la prima cosa che facciamo è eseguire il programma src.exe e scoprire che non fa davvero un cazzo : dopo aver fatto alcune seghe alla nostra CPU salta fuori con una messagebox che ha il solo scopo di prenderci per il culo seguita da una seconda che ci saluta con un numero non troppo random , ma lo mettiamo a posto noi :-). Incominciamo a dare uno sguardo ai sorgenti, il programma legge il tempo dall' avvio di win genera alcuni numeri a caso che mette in un buffer che provvederà a sodomizzare con vari cicli e infine a mostrarcelo dopo averci detto il tempo che ha impiegato il programma.
Poiché ci saranno da compilare cose e cose ci scriviamo un BAT che ci velocizzerà il lavoro
----------------------------compile.bat----------------------------------
@echo off
cd \tasm
tasm32 -ml -m5 -q c:\windows\desktop\uic11\%1.asm
tlink32 -Tpe -aa -x -c %1.obj ,,, import32
copy %1.exe c:\windows\desktop\uic11\%1.exe
del %1.exe
del %1.obj
--------------------------------------------------------------------------

Uso: compile nome(ovviamente in c:\windows\desktop\uic11). Il BAT va messo in windows\command; se siete troppo legati alla riga di comando potete evitare tutto ciò, io l'ho messo perché questo in fondo è un tutorial sull'ottimizzazione.

0 Prima di iniziare

Quando si ottimizza qualcosa si cerca di limitare l'uso della CPU accelerando i tempi, di ridurre le dimensioni dell'eseguibile e, ovviamente di non alterare il funzionamento. Noi baderemo soprattutto agli ultimi 2 punti, ma si pone un problema:in 5 passaggi vengono inseriti valori in eax che variano di volta in volta, per cui non possiamo verificare se effettivamente non abbiamo alterato il funzionamento. Ma noi, mica scemi, possiamo modificare queste istruzioni in modo da renderle costanti. Io le ho sostituite nell'ordine così:


mov eax,00112233h;call GetTickCount
mov eax ,12345678h;db 0fh, 31h
mov eax ,87654321h;db 0fh, 31h
mov eax, 12345678h;db 0fh, 31h
mov eax,00112234h;call GetTickCount


I byte 0F 31 indicano l' istruzione RDTSC che copia nella qword EDX:EAX il valore contenuto in un registro di 64 bit che si occupa di contare i cicli di clock.Avrei perciò dovuto inserire dei valori in edx, ma ho visto che era superfluo. Il 2°RDTSC è all'interno di un loop ma lo vedremo quando sarà il caso.
Adesso possiamo fare 2 copie: 1 la chiamiamo test.asm e la compiliamo(compile test), sull'altra lavoreremo controllando sempre che i numeri generati coincidano. Alla fine ripristineremo le istruzioni originali che ho lasciato di commento.

1 togliamo il 1° step

Start:
call GetTickCount ; Iniziamo il conteggio del tempo
push eax
<---salva il valore nello stack
mov ecx, eax <---inutile
imul eax, 256<---------moltiplica il n° di cicli per 256 (100h)       
mov ecx, eax<--- e lo copia in ecx
step1:
imul eax, 4096<---- moltiplica eax x 4096(1000h)
push edx<-----salva edx nello stack
mov edx,ecx<---lo sovrascrive
pop edx<---lo ripristina
loop step1

Questa routine moltiplica eax x 4096 un casino di volte. Ciò corrisponde a uno shift a dx di 12. Quindi eax , sia esso positivo o negativo dopo al massimo 3  cicli varrà 0.Per cui possiamo sostituire al ciclo, per adesso, xor eax,eax:

xor eax,eax<-- step 1

mov eax, 0<--vale già 0! 
sub edx, edx<--azzera edx
mov eax, ecx<--sovrascrive eax
mov eax,12345678h;db 0fh, 31h
<----sovrascrive eax e edx 
and eax, 0000FFFFh
<--azzera la word alta di eax
mov ecx, eax
<--lo mette in ecx

È facile capire che le prime 4 istruzioni sono inutili quindi via tutte.

2  Lo step 2:Push e pop

Questo è solo il 1° paragrafo dedicato allo step 2. Spiego dopo perchè non faccio tutto adesso.


push vari

step2:
.......

pop vari


mov ecx,0 

step3:
mov eax, dword ptr[calcbuf+ecx]
<---qui viene sovrascritto eax

....db 0fh, 31h<--qui edx

Quequero seguendo le regole del buon programmatore prima del ciclo ha salvato nello stack i registri (bravo Que :-) Noi però  non badiamo tanto all' estetica e, purtroppo per lui, togliamo via push e pop superflui.Per prima cosa ebx ,edi e esi che non vengono toccati dal loop, poi ecx che viene azzerato subito dopo.Infine possiamo sbattercene le balle di ripristinare eax e edx perchè  tanto vengono sovrascritti poco dopo.

3 Una routine non troppo utile

mov ecx ,0

step3:
mov eax, dword ptr[calcbuf+ecx]
<---mette una dword qualunque  in eax
mov dword ptr[calcbuf+ecx], eax
<---la riscrive ESATTAMENTE dove era prima
inc ecx
cmp ecx, 99
jnz step3
<---ripete il ciclo per 99 dword 

mov ecx, dword ptr[calcbuf]<---sovrascrive ecx
and ecx, 0000FFFFh 
mov eax,12345678h;db 0fh, 31h
<--sovrascrive eax

Non merita ulteriori commenti cancelliamo immediatamente le istruzioni in rosso e passiamo al prossimo paragrafo.

4 Anche questa non è da meno

mov ecx, dword ptr[calcbuf]
and ecx, 0000FFFFh
step5:
mov edx, eax
mov esi, ebx
mov ebx, edx
mov eax, esi
loop step5



mov eax,00112234h;call GetTickCount
<-----qui riscrive eax e edx
mov ebx, eax
<---qui ebx

Lo step 5 oltre ovviamente ad azzerare ecx riscrive continuamente i registri esi,edx,ebx,eax.Questi ultimi 3 vengono modificati poco dopo, mentre gli altri 2 non vengono più utilizzati in lettura.Quindi eliminiamo tutte le istruzioni senza problemi.  

5 Liberiamoci di calcbuf e guadagnamo Byte su Byte

Come promesso ritorniamo allo step 2.Non notate un po' troppi puntatori a calcbuf?Se avete mai fatto caso agli opcode in Dasm o Sice, i puntatori occupano un macello di byte, questo perché ovviamente sono necessari 4byte per l'indirizzo oltre ai byte dell' istruzione vera e propria.Allora vediamo di fare un po' di pulizia osservando tutto il sorgente.

mov dword ptr[calcbuf+8], edx
mov dword ptr[calcbuf+8], eax
rcl dword ptr[calcbuf+8], 135
<--non è seguita da un salto condizionato da CF

Incominciamo dalla dword [calcbuf+8]:quelli  sopra sono gli unici riferimenti. Come si nota il puntatore compare sempre a sinistra, questo vuol dire che vi si accede solo in scrittura, perciò possiamo eliminare tutto quanto.

Ora ci rimangono [calcbuf]che costituirà il numero random e[calcbuf+4] a cui il programma accede sempre  direttamente ,infatti abbiamo eliminato lo step 3 in cui accedeva usando il puntatore[calcbuf+ecx].Ogni dword può stare comodamente in un registro e noi abbiamo esi e edi che da quando abbiamo liberato ai punti 2 e 4 non fanno più un cazzo(spiegato il motivo per cui sono ripassato dallo step2 e non ho fatto tutto prima).Ora è sufficiente sostituire al posto di  [calcbuf] , esi  e edi invece di[calcbuf+4] (e NON il contrario).
Compiliamo e testiamo il risultato con quello di test.exe, ma stranamente questi  non coincidono eppure abbiamo fatto tutto bene e al passo precedente coincidevano.Cosa sarà successo? Di chi è la colpa? Quequero avrà voluto divertirsi alle nostre spalle? E se in vi stessi prendendo per il culo? E se in realtà coincidessero? E se vi dicessi cazzate? Abbiamo di nuovo bevuto troppo? O forse Quequero?Il seguito alla prossima.

6 Fine del mistero

Adesso dovrei dirvi cosa è successo,probabilmente ci siete già arrivati, ma non posso dirvelo adesso occorrerebbe un  po' di suspense per cui  vi metto un po' di nop:

90
90
90
90
90
90
90
90
90
90
90
90
90
90
90
90
90
90
90
90
Benissimo adesso scopriamo chi è il colpevole: il buffer overflow.  Se proviamo a debuggare l'eseguibile di partenza scopriamo che una chiamata alla funzione wsprint sovrascrive calcbuf.

call wsprintf, offset buffer, offset tempo, ebx

Infatti  buffer comincia 80 byte prima di calcbuf, mentre "tempo" è lungo più di 100 byte a cui si aggiunge la lunghezza di ebx, che è il numero di millisecondi che impiega il programma.Questo ci spiega il motivo per cui uscivano sempre gli stessi valori all' inizio, che potevano variare solo al variare della lunghezza del n° di ms. Ad esempio 168632609  corrisponde alla dword 21 21 0D 0A  che è un pezzo del testo della 1° messagebox.
Adesso possiamo scegliere tra 2 soluzioni.

1_ Se seguissimo il principio di non modificare il funzionamento potremmo agevolmente eliminare tutta la 1a parte che si preoccupa di randomizzare calcbuf, potremmo dire di aver rimpicciolito l'area code a sufficienza e andare a giocare tutti quanti a briscola. Ma in questo modo tutto il lavoro perderebbe di significato e non sarebbe nemmeno troppo utile; per cui abbandoniamo questa idea.

2_Ora siamo obbligati a modificare il funzionamento ,contro il principio del punto 0, ma in fondo correggiamo un errore che forse Quequero ha messo per vedere se siamo veramente dei fessi ma in questo modo noi lo smentiamo.Per correggere questo bug non dobbiamo fare un cazzo, infatti con la mossa del paragrafo precedente l'abbiamo risolto senza volerlo quindi torniamo a test.asm e lo correggiamo anche lì:    


step5:
mov edx, eax
mov esi, ebx
mov ebx, edx
mov eax, esi
loop step5
mov esi,dword ptr[calcbuf]<--aggiungiamo qui questa riga

.....
call wsprintf,offset buf2,offset testo,esi<-- invece di dword ptr[calcbuf]

Facciamo un confronto: entrambe generano il numero -433958592 e passiamo al prossimo punto.

7 Di nuovo lo step 2

step2:

mov esi, eax
<--mette eax in esi
mov edx, esi<--lo copia in edx 
mov eax ,87654321h;db 0fh, 31h
<--questo riscrive eax e edx 
(mov edx,xxxxx)<--ma di questo non ci preoccupiamo
add edx, eax
mov edi, eax
add esi, eax
mov edx, esi
<--perchè qui edx viene riscritto
add eax, edx
mov eax, edi
loop step2

Come avevo preannunciato al punto 0 di edx ce ne possiamo fottere perchè viene riscritto senza essere copiato da altre parti quindi eliminiamo un po' di istruzioni.

step2:
mov esi, eax
mov eax ,87654321h;db 0fh, 31h
mov edi, eax
add esi, eax
mov edx, esi<---dopo aver tolto l'istr sotto edx non serve più
add eax, edx<----aggiunge a eax edx
mov eax, edi
<--ma gli scrive sopra edi
loop step2

Ovviamente bisogna prima togliere add eax, edx dopodiché ci sarà consentito eliminare mov edx,esi.
Per capire il prossimo passaggio bisogna immaginare di essere all'ultimo ciclo.

step2:

mov esi, eax
<---copia il penultimo valore di eax 
mov eax ,87654321h;db 0fh, 31h
<---"estrae" l'ultimo valore di eax
mov edi, eax
<----lo copia in edi
add esi, eax
<---lo  somma a esi
mov eax, edi<---eax e edi contengono già lo stesso valore
loop step2

Dopo n cicli avremo esi=eax(n-1)+eax(n) e edi=eax(n). Eax(n-1) e eax(n) sono a tutti gli effetti casuali per cui si potrebbe pensare di eliminare il loop mettendo per 2 volte RDTSC. Forse questa è la soluzione migliore come numero di byte, ma secondo me altera il funzionamento del programma perchè un loop che si ripete un numero casuale di volte rende tutto "un po' più casuale".Quindi io seguirò questo principio anche se forse Que l'aveva pensata diversamente.Concludendo questo è ciò che rimane del ciclo. 

step2:

mov esi, eax
(mov eax ,87654321h);db 0fh, 31h
add esi, eax
loop step2

mov edi, eax<--essendo l'ultimo valore di eax lo possiamo togliere dal ciclo

Sarebbe possibile anche lasciare l'ultima istruzione nel ciclo utilizzando lo stesso numero di byte, ma in questo modo ripeteremmo la stessa istruzione più volte utilizzando inutilmente il processore.

8 Lo step 4

mov ecx, esi<--mette esi, reduce dallo step2, nel contatore 
and ecx, 0000FFFFh
<---ne azzera la parte alta
mov eax,12345678h;db 0fh, 31h
<--estrae un valore random
step4:
and esi, eax<---fa l'and bit a bit con eax  in esi  
add edi, eax
mov ebx, edi<--sposta edi in ebx      
add eax, ebx<---- e lo somma a eax ; più velocemente add eax, edi 
mov esi, eax
<---riscrive esi
mul esi
<----li vediamo dopo
mul esi
mul esi
mul esi
mul esi
mul esi
add eax, 1<---3Byte!? inc eax
add eax, eax
div esi

loop step4

Passiamo ad un analisi un po' più matematica della routine(questo è totalmente inutile e siete autorizzati a saltarlo):

mul esi<--moltiplica eax x esi e lo mette in edx:eax
inc eax
add eax, eax
<---raddoppia eax
div esi
<--divide edx:eax per esi e lo mette in eax

In questo passaggio non eliminiamo alcuna istruzione, tuttavia ho preferito inserirlo per analizzare queste istruzioni. Debuggando mi sono accorto in diversi casi che eax dopo queste 4 istruzioni era uguale al valore che aveva prima di esse aumentato di 1.Prima di cambiare qualcosa mi sono chiesto il perchè di questo e se ciò fosse sempre vero. Dobbiamo pensare a cosa succede arrivati a queste istruzioni.
Eax viene moltiplicato per esi e il risultato viene messo nella qword edx:eax;a questa,prima di essere divisa per esi, viene aggiunto il valore contenuto in eax dopo la moltiplicazione(aumentato di 2,ma per il nostro ragionamento non è una variazione significativa); se questo valore è maggiore di esi, ma minore di esi*2 può essere pensato come esi+k(con k<esi); edx:eax dopo quest'ultima somma sarà esi*eax(0)+esi+k dividendo per esi  si otterrà eax(0)+1 (in eax)resto k (in edx);quindi risulta chiaro che quest'aumento non avverrà sempre anche perchè se eax  dopo la moltiplicazione è maggiore di 80000000h, raddoppiandolo si ottiene un riporto.Ho anche pensato, viste le premesse di poter eliminare inc eax, che , complessivamente aumenta di 2 eax, una variazione minima che potrebbe in casi molto remoti influenzare il risultato: se ad esempio esi per qualche strano caso valesse 2 con eax uguale per esempio a 400h:

mul esi              edx:eax=800
inc eax              edx:eax=801
add eax, eax         edx:eax=1002
div esi              edx:eax=801(contro 800 senza quell'istruzione)

9 Il controllo del tempo

(mov eax,00112234h);call GetTickCount<---Conta il numero di millisecondi
mov ebx, eax
<---lo sposta in ebx
pop eax
<--- mette in eax il numero di millisecondi pushati all'inizio nello stack
sub ebx, eax
<-- fa la differenza tra i tempi
call wsprintf, offset buffer, offset tempo, ebx
<--la formatta(call bastarda!!!)
call MessageBox.......
<---Ce la mostra

Qui possiamo evitare il travaso di registri semplicemente invertendoli risparmiando 2 byte:

......
pop ebx
sub eax,ebx

call wsprintf,ebx, offset tempo, eax

Già che siamo qui guardiamo l'ultima parte:

call wsprintf, offset buffer, offset tempo, eax
call MessageBox, NULL,
offset buffer, offset titolo, MB_OK OR MB_ICONSTOP
call wsprintf,
offset buf2, offset testo, esi
call MessageBox, NULL,
offset buf2, offset titol0, MB_OK OR MB_ICONQUESTION
call ExitProcess, NULL

Non sono uno spreco 20  Byte per 4 push a 2 a 2 uguali? Se pushamo un registro ci occupa un solo byte, scegliamo ebx perchè eax ecx e edx sono modificati dalle call:

mov ebx,offset buffer

call wsprintf, ebx, offset tempo, eax
call MessageBox, NULL,
ebx, offset titolo, MB_OK OR MB_ICONSTOP

mov ebx,offset buf2
call wsprintf,
ebx, offset testo, esi
call MessageBox, NULL,
ebx, offset titol0, MB_OK OR MB_ICONQUESTION

10 loop vari

Il ciclo "loop" viene usato in assembler per ottimizzare, ma nulla ci vieterebbe di cambiare ogni loop in  3 istruzioni:
dec ecx
test ecx,ecx
jnz xxxxx
Dove la seconda non è necessaria perchè dec ecx setta già ZF quando ecx vale zero.Quindi scriveremmo in 3 Byte ciò che prima scrivevamo in 2, ma questa non è eccessivamente ottimizzazione! Guardate  però questo ciclo.   

and eax, 0000FFFFh<---azzera la word alta di eax
mov ecx, eax
<--copia tutto in ecx

step2:

mov esi, eax
<-- vi ricordate?Quello che conta sono gli ultimi 2 cicli e non importa il 1° eax
mov eax ,87654321h;db 0fh, 31h
add esi, eax
loop step2

Ecco, perché  utilizzare inutilmente i registri a 32bit quando la natura ci ha dato quelli a 16?

and eax, 0000FFFFh<----non serve più
mov ecx, eax

step2:

mov esi, eax
mov eax ,87654321h;db 0fh, 31h
add esi, eax
dec cx
jnz step2

Quindi,2 byte sprecati, ma 5 guadagnati!
Adesso ritorniamo allo step4 dove facciamo lo stesso.

mov ecx, esi
and ecx, 0000FFFFh ;Solo perchè sono gentile :))))<--Grazie Que, ma la togliamo
mov eax,12345678h;db 0fh, 31h

step4:
and esi, eax
add edi, eax
mov ebx, edi
add eax, ebx

mov esi, eax
mul esi
<---qua dopo mettiamo il ciclo
mul esi
mul esi
mul esi
mul esi
mul esi
inc eax
add eax, eax
div esi

loop step4<---dec cx     jnz step4

Avevamo lasciato in sospeso quei 6 mul esi da ottimizzare con un ciclo: l' ideale sarebbe un repz, ma come ci dicono i manuali sull'assembler vuole un'istruzione di stringa,io ho provato a fregarlo, ma si è accorto e non ha voluto collaborare. Potremmo usare bl per il ciclo interno usando 6B. Ci sarebbe anche il loop, ma ecx è già impegnato  con il ciclo esterno, per cui andrebbe salvato ogni volta nello stack.Ma se usassimo bx come contatore del ciclo esterno e  ecx per il ciclo annidato...

mov ebx, esi
mov eax,12345678h;db 0fh, 31h

step4:
add edi, eax
add eax,edi
mov esi, eax
push 6<--stanno per mov ecx,06 che però richiederebbe 5B
pop ecx
<-mentre così ne bastano 3.
mul:
mul esi
loop mul
inc eax
add eax, eax
div esi
dec bx
jnz step4

11 La stronzata

Tutto quello che troverete in questo paragrafo è frutto di malattia mentale. Penso che ci possano essere almeno 2 errori, tuttavia lo faccio lo stesso e tolgo 3B inutili. Se dovesse mai capitarvi una situazione del genere NON FATE NIENTE DI QUELLO CHE TROVATE QUI, a meno che non sia una situazione di reale necessità. Chiaramente questa È una situazione di reale necessità.

call wsprintf, offset buffer, offset tempo, eax
call MessageBox,
NULL, offset buffer, offset titolo, MB_OK OR MB_ICONSTOP
call wsprintf, offset buf2, offset testo, esi
call MessageBox,
NULL, offset buf2, offset titol0, MB_OK OR MB_ICONQUESTION
call ExitProcess,
NULL

Non vi danno un po' fastidio questi null non sembra che ci vogliano pigliare per il culo? Se penso che occupano 6 interi Byte che potrebbero contenere cose molto più utili.Allora cosa facciamo, azzeriamo un registro(2 byte) e lo pushamo 3 volte(3B), oppure cerchiamo un registro già azzerato,magari ecx, ma ecx in quel punto non vale zero perchè una certa funzione pensa bene di cambiarlo. L'unico registro che potrebbe valere 0 è edi, infatti debuggando ho visto che all'inizio vale 0 e nessuna funzione lo modifica.Non so esattamente perché edi valga 0 e gli altri no; non ho mai trovato niente che spiegasse cosa c'è nei registri all' entry point, ma ho sempre visto edi=0 e penso che sia sempre così. Ammesso di usare edi si pone un altro problema edi compare già in 3 punti ed è quello che era[calcbuf+4]. Occorrerebbe un altro registro. Eax, ecx e edx vengono cambiati dalle funzioni, ebx e esi ci servono ,esp e ebp sono per lo stack. Ebp non è però indispensabile IN QUESTO programma, che,essendo molto semplice non fa largo uso dello stack. Per cui sostituiamo edi con ebp in quei 3punti:

mov ebp,eax

add ebp, eax
add eax,ebp

e pushamo edi al posto di null

call wsprintf, offset buffer, offset tempo, eax
call MessageBox,
edi, offset buffer, offset titolo, MB_OK OR MB_ICONSTOP
call wsprintf, offset buf2, offset testo, esi
call MessageBox,
edi, offset buf2, offset titol0, MB_OK OR MB_ICONQUESTION
call ExitProcess,
edi

12 Fine prima parte

Prima di passare al punto successivo, togliamo le istruzioni da noi modificate, rimettendo rdtsc dove l' avevamo tolto.L'area code dopo vari passaggi è così:


.code
Start:
call GetTickCount ; Iniziamo il conteggio del tempo
push eax
db 0fh, 31h
mov ecx, eax

step2:

mov esi, eax
db 0fh, 31h
add esi, eax
dec cx
jnz step2
mov ebp,eax


mov ebx, esi
db 0fh, 31h

step4:
add ebp, eax
add eax,ebp
mov esi, eax
push 6
pop ecx
mul:
mul esi
loop mul
inc eax
add eax, eax
div esi
dec bx
jnz step4


call GetTickCount
pop ebx
sub eax, ebx
mov ebx,offset buffer
call wsprintf,ebx, offset tempo, eax
call MessageBox, edi,ebx, offset titolo, MB_OK OR MB_ICONSTOP
mov ebx, offset buf2
call wsprintf,ebx, offset testo, esi
call MessageBox, edi,ebx, offset titol0, MB_OK OR MB_ICONQUESTION
call ExitProcess, edi


ends
end Start

Adesso misura 142B;probabilmente sarebbe stato possibile ridurre questo numero se si fosse proceduto diversamente; io però adesso non saprei più cosa togliere ,ma in ogni caso sono già soddisfatto di questo, anche perchè non l'avevo mai fatto prima. Possiamo passare al secondo obbiettivo del corso: rimpicciolire l'exe.

13 Passiamo all' eseguibile

Adesso il nostro eseguibile misura 4kb, ma in realtà è pieno di byte zero,aggiunti dal linker o da chiunque altro per separare gli oggetti. Questi 0 non è che mi diano grosso fastidio, anche perchè in ogni caso un file sull'HD non può occupare meno di 4kB. Ma Quequero ci ha chiesto di rimpicciolirlo e noi proveremo a farlo. Prima di cominciare diamo uno sguardo all'exe tenendo presente lo schema del PE. Ricordate  anche la sintassi Intel in cui per word e dword i Byte sono "rovesciati": 01 00 00 00 vuol  dire 1. Il mio non vuole essere un tutorial sul PE perché non ho né intenzione né le conoscenze per farlo, per cui se non l'avete ancora fatto vi consiglio caldamente di leggervi qualcosa sull'argomento. Io ho letto un' ottima guida in inglese , ma non ricordo più esattamente da dove l'ho scaricata; senza andare lontano potete trovare sul sito della UIC nella sezione Uicstore un onesto tutorial scritto da Andrea Geddon. Un'altra precisazione: tutti i valori che troverete da qui alla fine del paragrafo  saranno in esadecimale, per cui se scrivo che 200 è una potenza di 2 non è solo causa dell' alcol.:-)
Ma torniamo al nostro file e diamogli uno sguardo: inizia con l'header dos; al byte 100 inizia l'header PE poi c'è la tabella degli oggetti e dal byte 600 comincia la sezione CODE ,seguita a intervalli di 200B dalle altre sezioni: data , idata(dove sono linkate le funzioni), reloc, che, come ho trovato scritto, per gli eseguibili non serve praticamente mai. Allora noi la eliminiamo senza problemi:prendiamo il nostro editor esadecimale di fiducia e cancelliamo i Byte  a partire dall'offset ABC, cioè dopo wsprintfA, ultima funzione importata in idata; lasciamo però un Byte 00  per evitare problemi. Dobbiamo cancellare reloc anche dalla object table, ci portiamo dove inizia .reloc (byte 270 dove dovrebbe esserci  2E 72 65 6C...) e copriamo di 0 i byte seguenti; ci resta ancora da modificare il numero di oggetti nell header:

50 45 00 00 4c 01 03 00.

Adesso sembra facile, basta cancellare i byte 0 e cambiare gli offset  nella object table; ma ci troviamo di fronte a un problema: il file align. Infatti uno dei campi del PE header è il file align che stabilisce l'allineamento degli oggetti all'interno dell'exe; ovvero ogni oggetto deve cominciare ad un offset fisico che sia multiplo di tale valore. Per non avere problemi basterebbe ridurlo a 1byte, ma esso deve essere una potenza di 2 maggiore di 200; io ho provato a diminuirne il valore anche solo a 100 e a riallineare gli oggetti al nuovo valore, ma mi diceva che sul computer c'erano delle restrizioni; mentre il dasm, che non esegue materialmente il programma, ha decompilato in gran scioltezza con i riferimrnti alle stringhe al loro posto. Quindi penso che questo valore ,200, sia un valore limite per cui dovremo rispettare tale allineamento.  Dovendo lasciare 200B per gli header cominceremo CODE a 200,.data a 400 e  .idata a 600.Avevo anche provato mettere .data nella sezione CODE, come avviene nei com, ma non sono riuscito neanche cambiando il flag che lo rende accessibile in scrittura. Allora l' unica cosa che resta da fare è ridurre l'header. Considerando che non si possono togliere i byte tra la fine del PE header e la OT non ci resta che togliere i byte che seguono l'header dos.Possiamo togliere anche la stringa This program must..., ma non togliamo la riga precedente che può ancora servire: guardatela un pò, non vi dice niente. Se avete mai provato a compilare qualcosa a 16 bit, gli opcode della 1a cosa che avete compilato saranno simili a questi, che corrispondono a queste istruzioni:

mov dx, 0010
push cs 
pop ds
mov ah, 09
int 21
<---stampa la stringa puntata da ds:dx
mov ax, 4C01
int 21
<---lascia il controllo al dos
nop
nop

Per cui non conviene cancellarle. Se venisse per un qualche motivo eseguito da dos non causerebbe problemi, mostrerebbe solamente un messaggio adesso incomprensibile(almeno credo). Windows invece non eseguirebbe mai queste istruzioni perché, dopo aver letto la Dword precedente (a 3C), passerebbe con arroganza all'header PE puntato da tale dw. A proposito,se spostiamo l'header dobbiamo modificarla con il nuovo valore (50), andiamo all'offset 3c e scriviamo 50 00 00 00. Modifichiamo il campo header size mettendo 200. Così ci ritroviamo con spazio libero all' offset 200, dove andremo ad allineare CODE cancellando i byte inutili. Come abbiamo detto spostiamo le altre sezioni; non ci resta che modificare la object table seguendo lo schema in questo modo:

8 nome 
4 virtual size
4 rva
4 ph size
4 ph offset<-------- questo è quello che andiamo a modificare
C reserved
4 flags

43 4F 44 45 00 00 00 00 00 10 00 00 00 10 00 00
00 02 00 00
00 02 00 00 00 00 00 00 00 00 00 00
00 00 00 00 20 00 00 60 44 41 54 41 00 00 00 00
00 10 00 00 5C 20 00 00 00 02 00 00
00 04 00 00
00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 C0
2E 69 64 61 74 61 00 00 00 10 00 00 00 30 00 00
00 02 00 00
00 06 00 00 00 00 00 00 00 00 00 00
00 00 00 00 40 00 00 C0

14 Accorciamo ancora

Ci troviamo in una situazione apparentemente impossibile: infatti considerando quanto detto prima per 3 sezioni meno di 600h byte sono impossibili, dovremmo togliere una sezione. In un primo momento mi erano venuti in mente i COM, ma provando a compilare non sono riuscito a far convivere dati e codice nella stessa sezione. Ma dove non arriva il compilatore arriva l' editor esadecimale. Infatti codice e dati stanno comodi in 200h byte, ma così facendo mandiamo a puttane tutti i puntatori che dovremo ripristinare a mano. Ma andiamo con ordine, incominciamo a togliere l'oggetto, ci portiamo nella OT e cancelliamo da dove comincia .data 40 byte e per riallineare gli oggetti a 200h ne aggiungeremo altrettanti alla fine della OT. Poi scriviamo 2 nel campo numero di oggetti nell' header, come avevamo fatto prima. Eliminato l'oggetto nella OT dobbiamo eliminarlo fisicamente, spostandolo nella sezione CODE, io ho cancellato 60h byte in modo da far cominciare il primo buffer all'offset 2A0h, che corrisponde ad un indirizzo virtuale :004010A0, contro il :00402000 che aveva prima. Teoricamente avrei potuto farlo cominciare prima, ma tanto come sapete i byte alla fine dell'oggetto sarebbero stati sprecati. Successivamente ho cambiato manualmente gli offset dei puntatori alle stringhe, sottraendo dai valori F60h che è poi 402000 - 4010A0, alla fine è rimasto così.

E8 7D 00 00 00 50 0F 31 8B C8 8B F0 0F 31 03 F0 
66 49 75 F6 8B E8 8B DE 0F 31 03 E8 03 C5 8B F0 
6A 06 59 F7 E6 E2 FC 40 03 C0 F7 F6 66 4B 75 EA 
E8 4D 00 00 00 5B 2B C3 BB
A0 10 40 00 50 68 FC 
10 40 00
53 E8 45 00 00 00 6A 10 68 62 11 40 00 
53 57 E8 31 00 00 00 BB
D2 10 40 00 56 68 85 11 
40 00
53 E8 26 00 00 00 6A 20 68 75 11 40 00 53 
57 E8 12 00 00 00 57 E8 00 00 00 00 FF 25 54 30 
40 00 FF 25 58 30 40 00 FF 25 60 30 40 00 FF 25 
64 30 40 00 00 00 00 00 00 00 00 00 00 00 00 00

Abbiamo liberato spazio a sufficienza per allineare .idata a 400h. Non l'ho unito in CODE perchè non sono riuscito e forse non si può neanche. Spostato idata dobbiamo comunicarlo alla OT e già che siamo qui cambiamo un flag di code, che deve essere scrivibile, al contrario di com'è appena compilato, e per far ciò aggiungiamo 80000000h alla dword flags:

43 4F 44 45 00 00 00 00 00 10 00 00 00 10 00 00
00 02 00 00 00 02 00 00 00 00 00 00 00 00 00 00
00 00 00 00
20 00 00 E0 2E 69 64 61 74 61 00 00<--flags
00 10 00 00 00 30 00 00 00 02 00 00
00 04 00 00<--Physical offset
00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 C0

A quanto pare è proprio finito e l' exe misura 1212 byte, forse un po' troppi, ma di meno non saprei come fare.

                                                                                                             Winebag

Note finali

Siamo giunti ai ringraziamenti: un tnx a B0nu$ e Cod, per avermi insegnato che esiste anche una cosa chiamata assembler con i loro tute che potete trovare anche su questo sito.Ringrazio anche chi ha letto quello che sta leggendo e non l'ha ancora cestinato. Un ringraziamento finale va al padrone della baracca, che con i suoi corsi ci ha tenuto svegli per delle notti. Mi spiace solo di essere arrivato tardi ed essermi trovato di fronte uno strainer che mi ha solo fatto incazzare, però potevi anche aspettarmi Que.
Ciauz a tutti!!!!!

Disclaimer

Vi ricordo che non sono responsabile di niente di quello che c'è scritto qui, perché quando l'ho scritto non ero in grado di intendere e di volere come non sono tutt'ora. Per cui non provate a chiedere un rimborso per avervi fatto perdere il vostro preziosissimo tempo. Non sono da ritenermi responsabile per i vostri reati penali e neanche se la vostra fidanzata vi fa le corna o vi ha appena lasciato. Ricordo anche che il software va comprato, cioè anche le altre cose vanno comprate, non dimenticatelo mai.

Noi reversiamo al solo scopo informativo e di miglioramento del linguaggio Assembly.