Come creare eseguibili ai minimi termini
usando solo compilatore e linker.

Data

by "MrCode"

 

28/02/2004

UIC's Home Page

Published by Quequero


 

Ciao Q, forse non mi merito una Mazda ma almeno una Fiat500 del '63 me la puoi anche concedere....;-)
Se non vi levate il vizio di occupare la mia zona franca vi uccido ;p

 

Ma cosa fate davanti a questi trespoli di PC?
Fuori a vivere, march!!

Home page se presente: http://mrcode.cjb.net
E-mail: mailto:[email protected]
Irc:  irc.azzurra.org   #crack-it

Azz!! Ma quanto siete stati fuori? Poi vi dimenticate tutto!! A studiare reversing, march!

Difficoltà

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

 

Premetto che la ruggine ha ormai ossidato il mio cervello, e oltretutto che le mie capacità di spiegare le cose non sono poi così eccelse, insegnare è un dono e non tutti ce l'hanno! Per non scrivere 4 tutorial in uno solo, do per scontato che sappiate cosa è il PE e come funziona e anche cosa sono le eccezioni. Aggiungo che questo tutorial lo avevo già praticamente finito a metà marzo ma che per motivi del cavolo l'ho tirato avanti fino ad ora ..... meglio tardi che mai......


Come creare eseguibili ai minimi termini
usando solo compilatore e linker.

Written by MrCode

Introduzione

In tutti questi anni che seguo #crack-it e la UIC non ho mai scritto nulla.....vergogna nera! Un modesto contributo ci voleva.
Se non capite le cose che scrivo non è perché sono difficili, ma perché le ho spiegate male!! :-)

Tools usati

 

Una volta tanto nessuno, solo il VisualC++ e il PEditor giusto per vedere cosa esce dal compiler. Se proprio vogliamo esagerare anche ollydebug, giusto per vedere due cosette in memoria. Una scarpa da tirare al gatto per tenerlo lontano dalla tastiera.

 

URL o FTP del programma

www.l'hardiskdeltuocomputer.com il programma te lo crei te .
Scarica lo zippetto del progetto+sorgenti (Q metticelo te!!)

Notizie sul programma

Il programma lo sapete solo voi cosa fa, il mio esempio apre una finestra con una bitmap di sfondo prendendola da un file JPG, un bottone e una statusbar. Da menù è possibile anche scegliere se salvare la bitmap di sfondo in formato BMP. Inoltre ci sono cenni sulla gdiplus.dll e sul Manifest di XP."Eh, bella cavolata!" direte voi...essì, però occupa veramente poco spazio :-P

Essay

 

Detto questo cerchiamo di fare un tour più o meno interessante intorno alla compilazione e all'uso del linker del VisualC++. L'obiettivo iniziale era quello di fare un eseguibile ridotto veramente ai minimi termini, ma un eseguibile che faccia davvero qualcosa, non il solito "Hello World!" (credo che sia il programma più diffuso dopo il virus "ILoveYou" e Windows stesso), poi invece questo testo è diventato una raccolta di "tips'n'tricks" sul Linker, StructuredEH, VectoredEH, uso del ManifestXML di WinXP, gdiplus.dll, cenni sul formato PE e altre cosucce simpatiche. Questi argomenti sono stati sviscerati in mille modi e in giro ci sono mille tut, quindi aggiungere qualcosa di nuovo è praticamente impossibile, ma raschiando il barile qualcosa di interessante alla fine viene fuori....spero.
Allora, innanzitutto ecco lo scheletro del programma, al quale andremo ad aggiungere poi alcune funzioncine per fargli fare qualcosa:
 



// w32app.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
#include "resource.h"

#define MAX_LOADSTRING 100
 

// Global Variables:
HINSTANCE hInst; // current instance
TCHAR szTitle[MAX_LOADSTRING]; // The title bar text
TCHAR szWindowClass[MAX_LOADSTRING]; // The title bar text

// Foward declarations of functions included in this code module:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM);
LONG IsValidNtHeader(int, int);

int APIENTRY WinMain(HINSTANCE hInstance,
 HINSTANCE hPrevInstance,
 LPSTR lpCmdLine,
 int nCmdShow)
 

{
// TODO: Place code here.
MSG msg;

// Initialize global strings
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_W32APP, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);

// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
{
    return FALSE;
}

// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);   
    DispatchMessage(&msg);
}

return msg.wParam;
}



//
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
// COMMENTS:
//
// This function and its usage is only necessary if you want this code
// to be compatible with Win32 systems prior to the 'RegisterClassEx'
// function that was added to Windows 95. It is important to call this function
// so that the application will get 'well formed' small icons associated
// with it.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;

wcex.cbSize = sizeof(WNDCLASSEX);

wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_W32APP);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = (LPCSTR)IDC_W32APP;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);

return RegisterClassEx(&wcex);
}

//
// FUNCTION: InitInstance(HANDLE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;

hInst = hInstance; // Store instance handle in our global variable

hWnd = CreateWindowEx(NULL, szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

if (!hWnd)
{
    return FALSE;
}

ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

return TRUE;
}

//
// FUNCTION: WndProc(HWND, unsigned, WORD, LONG)
//
// PURPOSE: Processes messages for the main window.
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
TCHAR szHello[MAX_LOADSTRING];
LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);

switch (message)
{
    case WM_COMMAND:
            wmId = LOWORD(wParam);
            wmEvent = HIWORD(wParam);
 

            // Parse the menu selections:
            switch (wmId)
            {
                case IDM_EXIT:
                        DestroyWindow(hWnd);
                        break;
                default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
            break;
   

    case WM_PAINT:
            hdc = BeginPaint(hWnd, &ps);
                    // TODO: Add any drawing code here...
            RECT rt;
            GetClientRect(hWnd, &rt);
            DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);
            EndPaint(hWnd, &ps);
            break;
 

    case WM_DESTROY:
            PostQuitMessage(0);
            break;
    default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
 

return 0;
}

 



Certo fin qui c'e' poco da spiegare,è il classico scheletro creato dal Wizard del Visual, registra la finestra, la apre e gestisce la "message pump". Apriamo il progetto, compiliamo la versione "debug" ed avremo un eseguibile win32 che ci mostra lo stato della memoria del nostro computer. Qui i vari tutorial in giro danno subito un consiglio incredibile, ovvero compilare la versione "release" per diminuire la dimensione occupata dal nostro programma, e qui mi verrebbe da dire: "e grazie al piffero, è come dire che Galeazzo Costantino è diventato Mr.Olimpia grazie all'uso del Tesmed" ovvio che se tolgo tutte le informazioni di debug dall'exe la dimensione diminuisce, ma il solo stripping della .debug ovviamente mica ci basta, oltretutto si può egualmente debuggare la versione release mantenendo le info di debug separata nel file .pch, e steppando quindi beatamente nel nostro codice sorgente invece che nell'assembler generato dal compilatore. Entrano in gioco anche i parametri di ottimizzazione del codice, e anche qui andremo a vedere cosa settare e il prezzo da pagare a seconda delle scelte che facciamo.

                       
                               
Le opzioni del progetto
 

Se andate nel menù "Project" e poi alla voce "Settings" (oppure direttamente Alt+F7) vi si apre la ben nota finestra delle opzioni di compilazione, partiamo dalla linguetta "General", qui possiamo settare se la nostra applicazione usa MFC oppure no e se lo usa in maniera statica o dinamica. Statica significa che tutto il codice che serve a far funzionare MFC sarà inglobato nella nostra applicazione, Dinamica significa che la nostra applicazione si appoggerà alla libreria esterna MFC40.DLL (oppure MFC42.DLL). Noi lasceremo il default, ovvero "not using MFC". Lasciamo stare anche la linguetta "Debug" e concentriamoci invece sulle due linguette che contengono le opzioni più interessanti:

C/C++ e Link.

                                                                            Tab C/C++

 

C/C++ gestisce le opzioni di compilazione del nostro sorgente, quelle che genereranno il file oggetto (.obj) da linkare per renderlo un eseguibile (.exe), a seconda della categoria scelta appaiono nuove opzioni da settare:

 

Category=General:

in questa categoria ci interessa la voce "Optimization" che metteremo ovviamente a
"Minimize Size", il compilatore così cercherà di rendere l'eseguibile il più piccolo possibile (non ce la farà, per questo gli diamo una mano noi....;)e anche la voce

"Debug Info" che metteremo a "None" in modo da non appesantire l'eseguibile con la sezione contenente le info di debug. Se avete scelto di compilare la versione release, questa voce è messa a None automaticamente. Warning Level potete lasciarlo a 3, tanto poi i warning che ci scocciano li silenzieremo con la direttiva /IGNORE del preprocessore, ve lo spiego più avanti.

 

Category=C++ Language:

Qui ci interessano le opzioni delle caselle di spunta, ovvero "Enable Exception Handling", "Enable Run time type information" e "Disable construction displacements"

La voce "pointer to member" (puntatore a membro) è specifica del C++ e potete lasciarla a "Best Case Always"."Enable Exception Handling" dovete deselezionarlo, non vogliamo che il compiler metta le proprie routine di gestione eccezioni, più avanti vi spiegherò come metterne di proprie (e più piccole :-P), "Enable Run time type information" è specifico per il C++ e permette di determinare il tipo di un oggetto durante l'esecuzione di un programma. A noi non interessa, quindi toglietelo.

"Disable construction displacements"  invece implementa in C++ il displacement in situazioni in cui è usata l'ereditarietà virtuale, per riprendere e tradurre l'help del compilatore, "Construction Displacement risolve il problema creato quando una funzione virtuale (_virtual) dichiarata in una classe virtuale base e sovraccaricata in una classe derivata, è chiamata da un metodo costruttore durante la costruzione di tale classe derivata" Belle parole se conoscete la programmazione ad oggetti e il C++, a noi ci basta solo deselezionare questa voce, non ci serve niente di tutto questo.

 

Category=Code Generation

Qui si trovano degli switch decisamente interessanti, il tipo di processore (penso che non ci sia bisogno di spiegazioni per questa voce) ci permette di selezionare quale set di istruzioni e quali strategie di ottimizzazione il compilatore adotterà in base al processore su cui dovrà girare il codice. Il massimo, "Pentium Pro" andrà più che bene...oppure usate ancora quel vecchio 486 polveroso? (io ad esempio si :-P).

Calling Convention invece permette di decidere il metodo con cui vengono passati gli argomenti alle funzioni. Di solito gli argomenti vengono pushati sullo stach, ma si può anche decidere diversamente. Le possibilità sono tre: _cdecl e _stdcall differiscono tra loro solo sul fatto che lo stack deve essere risistemato da chi chiama la funzione nel primo caso e dalla funzione chiamata nel secondo (oltre che per i nome assegnati alle funzioni, ma questo lo tralascio) mentre spunta fuori un terza interessante _fastcall, poco usata per via che le librerie sono solitamente compilate tutte quante per seguire lo standard _cdecl. Nel caso in cui la funzione che vogliamo chiamare abbia due parametri soltanto, (massimo 4byte) questi saranno passati tramite registri invece che sullo stack. Nel caso che gli argomenti siano di più, i primi due saranno passati tramite registri, gli altri sullo stack. Questo può farci risparmiare qualche byte qua e la, visto che magari nelle ottimizzazioni svolte dal compilatore, nei registri si potrebbe già trovare l'argomento da passare alla funzione, e "ripusharlo" sullo stack sarebbe un'operazione in più. Ad onor del vero mi sarebbe piaciuto molto di più se l'utente avesse potuto decidere sia che registri usare che, sopratutto, quanti usarne, ma forse tutta questa libertà sarebbe andata a scapito dell'efficienza dei vari algoritmi di ottimizzazione che il Visual Applica a richiesta sul nostro codice. (Ehi! cosa credete?! Ottimizzare il codice "a mano" è più semplice che farlo in maniera automatica! chi meglio di noi può sapere cosa fa il nostro programma e dove intervenire?) La voce Struct Member Alignment invece permette di stabilire come "packare" i membri delle strutture usate nel nostro programma. Se si usano molte strutture si possono risparmiare diversi byte usando l'alignment a 1byte, anche se si perde in velocità nell'accesso al singolo membro, ma la velocità non è il nostro scopo adesso, 1byte andrà più che bene. La voce use runtime library ci da la possibilità di usare per il nostro programma le librerie per programmi a thread singolo oppure multithread, per i nostri scopi sceglieremo single thread, da prove effettuate è il metodo per cui si ottengono file più piccoli.

 

Category=Customize

Qui poche spiegazioni, assicuratevi solo che sia abilitato il function level linking (il linker agirà linkando solo le funzioni effettivamente interessate) ed eliminate duplicate string, che ci permette di risparmiare ancora qualche byte.

 

Category=Listing Files

Questa sezione offre delle interessanti possibilità, ed aiuta lo smanettone medio a rispondere a questa domanda senza ricorrere all'aiuto di AndreaGeddon, ovvero: "ma cosa diamine diventa in assembler il mio programma una volta compilato?!". Qui potrete scegliere se far generare al compiler e al linker dei files che sono la traduzione di quello che il vostro programma ha combinato a livello di assembler. Potrete scegliere tra solo mnemonico asm, mnemonico asm mixato al vostro codice C/C++ (l'opzione più interessante imho) e addirittura codice asm più mnemonico asm più codice c/c++.

Grazie a questa opzione si imparano un sacco di cose senza ricorrere a tool esterni (IDA o Wdasm ad esempio) e si può procedere anche a ottimizzazioni spinte nei punti "che prudono" al nostro programma. Sicuramente divertente :-)

 

Category=Optimiziation

Qui obbligatoriamente "minimize size", però consiglierei anche di settare inline function expansion a "any_suitable". Nel mio caso ho risparmiato qualche byte rispetto a "only_inline". Questa voce praticamente istruisce il compilatore su come ottimizzare le chiamate a funzione interne al nostro programma, l'espansione inline sostituisce ad una chiamata a funzione direttamente il codice che la compone, in modo da risparmiare tempo nell'esecuzione, ma tanto noi qui abbiamo tempo da vendere no? ;-)

 

Category=Precompiled Headers

Serve solo per risparmiare tempo durante la compilazione, effettuando una precompilazione degli header che abbiamo incluso ed usando l'apposito file creato invece di andare a ravanare continuamente nel tree degli include in cerca dei files che abbiamo specificato, lasciate stare tutto come sta, non è determinante ai fini del nostro progetto.

 

Category=Preprocessor

Anche qui lasciate stare, serve a definire include path alternativi e a definire variabili di uso negli include o nel nostro progetto.

 

                                                                            Tab LINK

 

Link gestisce le opzioni che pilotano il linker, (LINK.EXE nella directory BIN) e che lo istruiranno su come creare dal file oggetto (.obj) un file eseguibile (.exe) che abbia il giusto header PE per funzionare su Windows. Col linker si possono creare anche altri tipi di eseguibili, ma per il nostro scopo ci interessa Win32.

Scartabellando nelle opzioni del linker si possono reperire interessanti istruzioni che possono aiutarci a rendere il nostro eseguibile più compatto, e sopratutto a capire cosa diamine combinano il compilatore e il linker per produrre il nostro exe.
 

Category=General

In questa pagina troverete settaggi di importanza vitale, segnalo il (forse) poco usato "Generate MapFile" che vi farà ritrovare nella directory release (o debug a seconda di cosa compilate) un file con estensione .map (tuoprogramma.amp) che contiene al suo interno interessanti informazioni su come è stato effettuato il linking delle funzioni della iat al tuo programma. Più avanti ve lo spiego meglio.

Ignore default library ci permette di bypassare la libc.lib, ma bisogna ricordarsi di mettere in Object/library modules almeno la msvcrt.lib. Effettivamente ci farà risparmiare un bel po di spazio, la libc importa staticamente nel nostro programma un mucchio di codice "estraneo", mentre usando la msvcrt si ottiene lo stesso risultato ma sprecando meno spazio visto che la maggior parte delle funzioni è linkata dinamicamente (solo puntatori a funzioni residenti in dll esterna). Fa specie solo la WinMainCRTStartup() croce e delizia dei programmi win32, ma più avanti vedremo come bypassare anche questa funzione.

 

Category=Debug

Vi permette di specificare se usare il mapfile e dove metterlo, permette anche di generare le info di debug (che ovviamente *NON* vogliamo come sezione aggiuntiva nel nostro PE), in che formato generarle (M$ o COFF o entrambi) e tenerle in un file separato oppure includerle nel PE. Tenerle in un file separato è interessante, perché ci permette di debuggare anche la versione release del nostro progetto, peccato che nel mio caso se tento di fare una cosa del genere il linker mi solleva una bella eccezione e si pianta tutto...boh, forse un bug risolto in qualche versione successiva (spero).

 

Category=Input

Qui specifichiamo i path di librerie alternative e ancora una volta se ignorare la defaultlib.

 

Category=Output

Qui ci sono delle simpatiche opzioni, BASE ci permette di stabilire la base del nostro PE, in modo che il loader di Win poi tenti di caricarlo li (se c'è posto ovvio). Stanchi del solito 0x400000 ? Bene, mettete un bel 0x6130000 ad esempio, e spiazzate il povero AndreaGeddon di turno abituato alle solite locazioni fritte e rifritte (difesa psicologica dai cracker?) ;) Potete mettere un po cosa vi pare, ma il linker arrotonda il valore per eccesso al multiplo di 64k più vicino. Occhio che i primi 64kb a partire da 0x00000000 non sono utilizzabili, rappresentano una "safe area" per aiutare nel debugging di applicazioni "scazzone" e quindi se tentiamo di scriverci esce fuori una bella eccezione. Anche se non servirebbe ricordarlo, vi ricordo ugualmente (si sono un pignolo rompipalle) che la memoria a disposizione per i nostri programmi risiede nei primi 2 GB di indirizzamento virtuale, i successivi 2 GB sono riservati al kernel di Win (ring0 e menate varie), per un totale di 4GB totali di capacità di indirizzamento del processore. Solo in alcuni versioni speciali di Win questo partizionamento è 3GB per utente e 1GB per il sistema (Sistemi Win2k Server) Mentre nelle versioni "consumer" che io tradurrei "versioni cazzone" una parte dei dati "salienti" del sistema risiede in memoria che può essere scritta da processi ring3.

 


La mappa di memoria è messa così (preso da "Inside win2k" di Russinovich, altro grande personaggio da osannare insieme a Pietrek :-)

 

Win2k/NT/XP                                                                                              Win95/98/ME

 

--Area Utente-------------------                                                            --Area Utente-------------------

0x00000000     64K Non                                                                             0x00000000        64Kb

0X00010000     accessibili                                                                         0X00010000        Non Accessibili

    Codice Applicazioni (EXE)                                                                Codice Applicazioni (EXE)

    Variabili Globali                                                                              Variabili Globali

    Stack (ogni thread il suo)                                                                Stack (ogni thread il suo)

    Codice delle librerie DLL    

0x7FFF0000    64Kb                                                                  0x7FFF0000  64Kb                                                       

0x7FFFFFFF     Non Accessibili                                                                 0x7FFFFFFF  Non Accessibili

 

--Area di Sistema (ring0)  ------                                                            --Area di Sistema ma accessibile da ring3 ---

0x80000000                                                                                                0x80000000

     Kernel                                                                                          Area di Sistema condivisa dai processi

    HAL                                                                                             (DLL, codice 16bit, memoria condivisa)

    Boot driver

0xC0000000                                                                                                0xC0000000

    Process Page Tables

    Hyperspace                                                                            --Area di Sistema  (ring0) -------------

0xC0800000                                                                                                0xC0000000       

    System cache

    Paged Pool                                                                                    Componenti del Kernel

    NonPaged pool

0xFFFFFFFF                                                                                                0xFFFFFFFF  

   


 

EntryPoint invece specifica quale funzione del vostro programma sarà chiamata per prima all'avvio dell'esecuzione. Quella standard è ovviamente WinMainCRTStartup(), più avanti però vi spiego come evitarla e far diventare ad esempio entrypoint direttamente la vostra WinMain(). Risparmiare!

Stack Allocation vi permette di specificare di quanta memoria ha bisogno lo stack del vostro programma. Ricordatevi che abbiamo disabilitato le routine standard messe a disposizione dal Visual, e che quindi se incorriamo in uno stack overflow nessuno correrà ai ripari per noi, aumentandolo per non farci crashare. Quindi se pensate che il valore standard non sia sufficiente (1 MegaByte), magari dategli una "pompatina" a mano in questa casella, il linker arrotonderà il valore da voi immesso per eccesso ai 4byte superiori. Version manco ve lo dico a cosa serve, indovinatelo da soli.

 

Le altre linguette non ve le spiego, tanto per i nostri scopi non servono, comunque vi segnalo PostBuild e PreLink che permettono di eseguire programmi di terze parti automaticamente sul nostro eseguibile appena compilato, o sugli obj prima che vengano linkati assieme, il tutto condito da un potente linguaggio di scripting dell'ambiente Visual che ci permette di fare molte cose.

 

                                                         Le opzioni inserite nel codice

 

Le istruzioni (o switch) da impartire al linker possono essere messe su linea di comando, (ad esempio di un makefile ad esempio), nelle opzioni del progetto tramite comoda interfaccia grafica ,come spiegato poco prima, oppure all'interno del nostro stesso sorgente, tramite le cosidette direttive.
Una direttiva è una keyword che non è propria del linguaggi che stiamo usando per programmare, ma è analizzata dal preprocessore (la parte del compilatore che da una prima "scorsa" al sorgente) e istruisce in questo caso il linker a determinate azioni.
Ecco quelle da usare nel nostro progettino:

// rinomina la .code in .mrcode, non serve a nulla ma e' bellino :)
#pragma code_seg (".mrcode")
//fonde insieme le varie sezioni in una unica a cui modifica gli attributi (la .data deve avere "W")
#pragma comment(linker,"/MERGE:.data=.mrcode /MERGE:.rdata=.mrcode /MERGE:.text=.mrcode /SECTION:.mrcode,EWR")
//rilocazione della base, /release mette il checksum nel campo apposito del PE e /version la versione dell'exe
#pragma comment(linker,"/BASE:0x6130000 /RELEASE /VERSION:1.1")

 

Come potete vedere con la direttiva #pragma preannunciamo al preprocessore che quello che segue non è roba per il compilatore, vediamo in dettaglio:

La prima, CODE_SEG, rinomina la sezione normalmente chiamata .text in quello che vi pare, un tocco di classe che non serve assolutamente a nulla ma fa tanto mistero quando aprite il file col Peditor :) il loader del PE di Win se ne sbatte altamente di come sono nominate le sezioni. Con lo switch /MERGE invece chiediamo al linker di fondere tutte le sezioni in una unica, in modo da risparmiare ulteriore spazio. Prego notare che l'ultimo switch della riga, /SECTION ci permette di indicare al linker che la sezione risultante (.mrcode appunto) avrà i flag di accesso del tipo E (eseguibile) W (scrivibile) e R (leggibile). Se non capite cosa sto dicendo vi rimando ai mille mila tutorial presenti sulla UIC e in rete sull'argomento PE. Più avanti scenderò nel dettaglio. Il comando /RELEASE fa calcolare al linker il CRC32 (o checksum) del nostro programma, e lo mette nel campo preposto dal PE. Utile? bah...non so, magari potete implementare una vostra routine che calcola il checksum e farla intervenire con una chiamata tramite SEH per vedere se qualcuno vi ha patchato l'exe....boh, tanto lo spazio occupato è lo stesso. Con /VERSION invece mettiamo nel PE l'identificativo del versioning del nostro programma...utile? Mah... occupa spazio in più? No, mettiamo dentro anche questo, allora. :-)

 

Adesso aggiungiamole in testa al nostro sorgente e compiliamo, andiamo a vedere la dimensione dell'exe....bene! E' diminuito un bel po :)
Ma non siamo ancora contenti, siamo sicuri che li dentro ci sia solo quello che serve? Beh, sicuramente si, ma facendo alcune assunzioni si potrebbe castrare ulteriormente il programma. Entra in gioco a questo punto la generazione dei cosidetti "listing files", che non sono molto usati ma che sono una miniera di informazioni per voi curiosi che leggete anche gli ingredienti sulla confezione delle fette biscottate.
I listing files praticamente ci informano su cosa è andato a finire dentro il nostro eseguibile, da dove è stato preso e come il compilatore ha trasformato le nostre istruzioni in assembler. Digressione: tanto tempo fa un mio amico venne a lamentarsi con me, quando sulla schermata del C64 appariva READY col cursore lampeggiante lui scriveva "CIAO COMPUTER, MI CHIAMA PAOLO" ma quello stronzo del C64 non rispondeva, mi chiese se era guasto :)))
Quello che per ora guarderemo è il MAP file (estensione .map) e lo trovate nella root del vostro progetto VisualC++.
Aprendo il file .map trovate qualcosa del genere, o meglio, trovate proprio questo:

Address                   Publics by Value                         Rva+Base            Lib:Object

0001:00000000     __imp__InitCommonControls@0      061301e0         comctl32:COMCTL32.dll
0001:00000004 \177COMCTL32_NULL_THUNK_DATA    061301e4         comctl32:COMCTL32.dll
0001:00000008 __imp__lstrcmpiA@8                         061301e8         kernel32:KERNEL32.dll
0001:0000000c __imp__GlobalMemoryStatus@4          061301ec         kernel32:KERNEL32.dll
0001:00000010 __imp__LocalAlloc@8                          061301f0         kernel32:KERNEL32.dll
0001:00000014 __imp__LocalLock@4                       061301f4         kernel32:KERNEL32.dll
0001:00000018 __imp__LocalUnlock@4                     061301f8         kernel32:KERNEL32.dll
0001:0000001c __imp__LocalFree@4                       061301fc         kernel32:KERNEL32.dll
0001:00000020 __imp__AddVectoredExceptionHandler@8 06130200     kernel32:KERNEL32.dll
0001:00000024 __imp__GetModuleHandleA@4            06130204         kernel32:KERNEL32.dll
0001:00000028 __imp__RemoveVectoredExceptionHandler@4 06130208 kernel32:KERNEL32.dll
0001:0000002c \177KERNEL32_NULL_THUNK_DATA     0613020c             kernel32:KERNEL32.dll
0001:00000030 __imp___vsnwprintf                       06130210             msvcrt:MSVCRT.dll
0001:00000034 __imp___vsnprintf                        06130214             msvcrt:MSVCRT.dll
0001:00000038 __imp__malloc                            06130218             msvcrt:MSVCRT.dll
0001:0000003c __imp__free                              0613021c             msvcrt:MSVCRT.dll

 

Non fate caso allo strano valore di RVA+BASE, poi vi dico come cambiarlo rispetto al canonico 0x0400000. Il map vi informa dettagliatamente di come e dove trovare le chiamate alle funzione che usate nel vostro programma e sopratutto DA DOVE le ha prese il linker per far funzionare tutto quanto. quando nella colonna "lib:object" trovate il nome di una libreria o del vostro stesso modulo di programma, (KERNEL32.DLL COMCTRL32.DLL USER32.DLL MIOPROGRAMMA.OBJ) allora tutto ok, le chiamate sono state effettuate dinamicamente (nessuna inclusione di codice extra preso da librerie di object) se invece trovate qualcosa tipo questo:

 

Address       Publics by Value             Rva+Base         Lib:Object

 

0001:00000310 _strlen                         06130500     f msvcrt:MSVCRT.dll
0001:00000316 _WinMainCRTStartup      06130506     f msvcrt:crtexew.obj
0001:00000474 __XcptFilter                  06130664     f msvcrt:MSVCRT.dll
0001:0000047a __initterm                     0613066a     f msvcrt:MSVCRT.dll
0001:00000480 __setdefaultprecision      06130670     f msvcrt:fp8.obj
0001:00000492 __matherr                     06130682     f msvcrt:merr.obj
0001:00000495 __setargv                     06130685     f msvcrt:dllargv.obj
0001:000004a0 __except_handler3         06130690     f msvcrt:MSVCRT.dll
0001:000004a6 __controlfp                   06130696     f msvcrt:MSVCRT.dll

 

allora non ci siamo, vedete sotto la colonna Lib:Object quelle voci che finiscono in .obj invece che in .dll? Bene, significa che quella specifica funzione è stata linkata staticamente nel vostro eseguibile, praticamente adesso fa parte integrante del vostro exe!! Azzo! ma chi mi ha buttato dentro tutta sta roba? Il colepvole è LIBC.LIB.
LIBC è la libreria delle funzioni standard C che usa il Visual, ed ogni funzione è linkata staticamente, ovvero estrapolata dalla LIB, che altro non è che una raccolta di file .obj, e schiaffata nell'exe.
Altra cosa da evitare come la peste per diminuire il size è proprio il linking statico. Molto meglio il linking dinamico, in cui la funzione è residente in una DLL esterna e quando la chiamiamo il nostro codice fa solo un salto nella IAT per chiamarla. Al massimo cresce la IAT, ma si risparmia un monte di spazio. Qui entra in campo MattPietrek, che già nel '97 aveva intuito questa cosa e scritto la sua bella LIBCTINY una libreria più piccola da usare al posto della LIBC con funzioni ottimizzate. Noi però non ci accontentiamo, vogliamo 0 funzioni importate staticamente.
Ci viene in aiuto MSVCRT.DLL, la libreria MicrosoftVisualCRunTime che contiene al suo interno praticamente tutte le funzioni della libc. da poter linkare dinamicamente! Togliete dalle opzioni del progetto "use default lib" e aggiungete MSVCRT.LIB nella riga delle lib da linkare .
Ricompilare il todos e rileggete il map. Molta meno roba di prima, tanta linkata dinamicamente ma qualcosa ancora è stato preso dalla MSVCRT.LIB e importato staticamente. Ci salta agli occhi la WinMainCRTStartup, ma cosa diamine è? Pietrek ce lo spiega nel suo articolo, e io ve lo sintetizzo qui per amor di chiarezza. Comunque sappiate che se quando avete installato il Visual avete scelto l'installazione completa con i sorgenti, troverete nella directory C:\Programmi\Microsoft Visual Studio\VC98\CRT\SRC un sorgente C che si chiama "CRTEXE.C" che altro non è che il codice sorgente della WinMainCRTStartup() Quella funzione è quella che precede il nostro WinMain() e che si occupa di inizializzare tutto quanto in preparazione del lancio del nostro programma, e per essere sinceri di cose intelligenti ne fa eccome, tutto per preparare il campo nel migliore dei modi per accogliere la nostra applicazione.

Il listato è pieno di commenti e praticamente si spiega da solo, se siete curiosi (se non lo siete cosa ci state a fare nelle UIC? fuori!!) vi consiglio di leggerlo, ci troverete anche la mainCRTStartup() che è l'equivalente per le applicazioni in CONSOLE MODE. Vi sintetizzo qui in quattro righe i compiti che svolgono queste funzioni:

E vi riporto, preso direttamente dal file .map ,cosa va a finire nel vostro eseguibile preso dalla LIBC e linkato staticamente:

 

0001:00000240 _strlen 00401240 f LIBC:strlen.obj
0001:000002bb _WinMainCRTStartup 004012bb f LIBC:wincrt0.obj
0001:000003b1 __amsg_exit 004013b1 f LIBC:wincrt0.obj
0001:000003fa __cinit 004013fa f LIBC:crt0dat.obj
0001:00000427 _exit 00401427 f LIBC:crt0dat.obj
0001:00000438 __exit 00401438 f LIBC:crt0dat.obj
0001:000004fc __XcptFilter 004014fc f LIBC:winxfltr.obj
0001:00000680 __wincmdln 00401680 f LIBC:wincmdln.obj
0001:000006d8 __setenvp 004016d8 f LIBC:stdenvp.obj
0001:00000791 __setargv 00401791 f LIBC:stdargv.obj
0001:000009de ___crtGetEnvironmentStringsA 004019de f LIBC:a_env.obj
0001:00000b10 __ioinit 00401b10 f LIBC:ioinit.obj
0001:00000cbb __GetLinkerVersion 00401cbb f LIBC:heapinit.obj
0001:00000ce8 ___heap_select 00401ce8 f LIBC:heapinit.obj
0001:00000e30 __heap_init 00401e30 f LIBC:heapinit.obj
0001:00000e90 __global_unwind2 00401e90 f LIBC:exsup.obj
0001:00000ed2 __local_unwind2 00401ed2 f LIBC:exsup.obj
0001:00000f2a __NLG_Return2 00401f2a f LIBC:exsup.obj
0001:00000f3a __abnormal_termination 00401f3a f LIBC:exsup.obj
0001:00000f5d __NLG_Notify1 00401f5d f LIBC:exsup.obj
0001:00000f66 __NLG_Notify 00401f66 f LIBC:exsup.obj
0001:00000f79 __NLG_Dispatch 00401f79 f LIBC:exsup.obj
0001:00000f88 __except_handler3 00401f88 f LIBC:exsup3.obj
0001:00001045 __seh_longjmp_unwind@4 00402045 f LIBC:exsup3.obj
0001:00001060 __FF_MSGBANNER 00402060 f LIBC:crt0msg.obj
0001:00001099 __NMSG_WRITE 00402099 f LIBC:crt0msg.obj
0001:000011ec __ismbblead 004021ec f LIBC:ismbbyte.obj
0001:0000122e __setmbcp 0040222e f LIBC:mbctype.obj
0001:000015f2 ___initmbctable 004025f2 f LIBC:mbctype.obj
0001:0000160e _free 0040260e f LIBC:free.obj
0001:00001680 _strcpy 00402680 f LIBC:strcat.obj
0001:00001690 _strcat 00402690 f LIBC:strcat.obj
0001:00001770 _malloc 00402770 f LIBC:malloc.obj
0001:00001782 __nh_malloc 00402782 f LIBC:malloc.obj
0001:000017ae __heap_alloc 004027ae f LIBC:malloc.obj
0001:00001830 _memcpy 00402830 f LIBC:memcpy.obj
0001:00001b65 _strtol 00402b65 f LIBC:strtol.obj
0001:00001da0 _strchr 00402da0 f LIBC:strchr.obj
0001:00001da6 ___from_strstr_to_strchr 00402da6 f LIBC:strchr.obj
0001:00001e60 _strstr 00402e60 f LIBC:strstr.obj
0001:00001ee0 _strncmp 00402ee0 f LIBC:strncmp.obj
0001:00001f20 __chkstk 00402f20 f LIBC:chkstk.obj
0001:00001f20 __alloca_probe 00402f20 f LIBC:chkstk.obj
0001:00001f4f ___sbh_heap_init 00402f4f f LIBC:sbheap.obj
0001:00001f97 ___sbh_find_block 00402f97 f LIBC:sbheap.obj
0001:00001fc2 ___sbh_free_block 00402fc2 f LIBC:sbheap.obj
0001:000022eb ___sbh_alloc_block 004032eb f LIBC:sbheap.obj
0001:000025f4 ___sbh_alloc_new_region 004035f4 f LIBC:sbheap.obj
0001:000026a5 ___sbh_alloc_new_group 004036a5 f LIBC:sbheap.obj
0001:000027a0 ___old_sbh_new_region 004037a0 f LIBC:sbheap.obj
0001:000028e4 ___old_sbh_release_region 004038e4 f LIBC:sbheap.obj
0001:0000293a ___old_sbh_decommit_pages 0040393a f LIBC:sbheap.obj
0001:000029fc ___old_sbh_find_block 004039fc f LIBC:sbheap.obj
0001:00002a53 ___old_sbh_free_block 00403a53 f LIBC:sbheap.obj
0001:00002a98 ___old_sbh_alloc_block 00403a98 f LIBC:sbheap.obj
0001:00002ca0 ___old_sbh_alloc_block_from_page 00403ca0 f LIBC:sbheap.obj
0001:00002dc4 ___crtMessageBoxA 00403dc4 f LIBC:crtmbox.obj
0001:00002e50 _strncpy 00403e50 f LIBC:strncpy.obj
0001:00002f4e ___crtLCMapStringA 00403f4e f LIBC:a_map.obj
0001:0000319d ___crtGetStringTypeA 0040419d f LIBC:a_str.obj
0001:000032e6 __callnewh 004042e6 f LIBC:handler.obj
0001:00003301 _toupper 00404301 f LIBC:toupper.obj
0001:000033cd __isctype 004043cd f LIBC:isctype.obj
0001:00003450 _memmove 00404450 f LIBC:memmove.obj
0001:00003790 _memset 00404790 f LIBC:memset.obj

 

Tanta robba eh?! Si, forse anche troppa per un normale "hello world" del cavolo.

Già passando a MSVCRT.LIB (e cioè spuntando "ignore default library" e inserendo "msvcrt.lib" nella stringa delle lib da linkare) guardate un po cosa si ottiene:

 

 0001:0000023c _strlen 0040123c f msvcrt:MSVCRT.dll
0001:00000242 _WinMainCRTStartup 00401242 f msvcrt:crtexew.obj
0001:000003a0 __XcptFilter 004013a0 f msvcrt:MSVCRT.dll
0001:000003a6 __initterm 004013a6 f msvcrt:MSVCRT.dll
0001:000003ac __setdefaultprecision 004013ac f msvcrt:fp8.obj
0001:000003be __matherr 004013be f msvcrt:merr.obj
0001:000003c1 __setargv 004013c1 f msvcrt:dllargv.obj
0001:000003d0 __except_handler3 004013d0 f msvcrt:MSVCRT.dll
0001:000003d6 __controlfp 004013d6 f msvcrt:MSVCRT.dll
0002:00000000 __imp__GetStartupInfoA@4 00402000 kernel32:KERNEL32.dll
0002:00000004 __imp__GetModuleHandleA@4 00402004 kernel32:KERNEL32.dll
0002:00000008 \177KERNEL32_NULL_THUNK_DATA 00402008 kernel32:KERNEL32.dll
0002:0000000c __imp___controlfp 0040200c msvcrt:MSVCRT.dll
0002:00000010 __imp___except_handler3 00402010 msvcrt:MSVCRT.dll
0002:00000014 __imp____set_app_type 00402014 msvcrt:MSVCRT.dll
0002:00000018 __imp____p__fmode 00402018 msvcrt:MSVCRT.dll
0002:0000001c __imp____p__commode 0040201c msvcrt:MSVCRT.dll
0002:00000020 __imp___adjust_fdiv 00402020 msvcrt:MSVCRT.dll
0002:00000024 __imp____setusermatherr 00402024 msvcrt:MSVCRT.dll
0002:00000028 __imp___initterm 00402028 msvcrt:MSVCRT.dll
0002:0000002c __imp____getmainargs 0040202c msvcrt:MSVCRT.dll
0002:00000030 __imp___acmdln 00402030 msvcrt:MSVCRT.dll
0002:00000034 __imp__exit 00402034 msvcrt:MSVCRT.dll
0002:00000038 __imp__strlen 00402038 msvcrt:MSVCRT.dll
0002:0000003c __imp___exit 0040203c msvcrt:MSVCRT.dll
0002:00000040 __imp___XcptFilter 00402040 msvcrt:MSVCRT.dll

...............................

...............................

0003:00000000 ___xc_a 00403000 msvcrt:cinitexe.obj
0003:00000004 ___xc_z 00403004 msvcrt:cinitexe.obj
0003:00000008 ___xi_a 00403008 msvcrt:cinitexe.obj
0003:0000000c ___xi_z 0040300c msvcrt:cinitexe.obj
0003:00000010 ___defaultmatherr 00403010 msvcrt:merr.obj
...............................

...............................

0003:000000f0 __dowildcard 004030f0 msvcrt:wildcard.obj
0003:000000f4 __newmode 004030f4 msvcrt:_newmode.obj
0003:000000f8 __commode 004030f8 msvcrt:xncommod.obj
0003:000000fc __fmode 004030fc msvcrt:xtxtmode.obj

 

Come potete vedere moltissime funzioni sono linkate dinamicamente (es: 0002:00000038 __imp__strlen 00402038 msvcrt:MSVCRT.dll ) e poche altre staticamente, altre sono addirittura scomparse perchè gestite all'interno della libreria, non male

Ne fa di cose essì! Ma se io ad esempio il floating point non lo uso? Cosa me ne frega? E poi tutto il resto? Me ne importa di cosa viene passato sulla riga di comando?

Miglioriamo ulteriormente il codice scrivendo noi stessi una WinMainCRTStartup. Matt Pietrek, il signore degli anelli di Win ci fa vedere come nella sua libctiny si possa intervenire con una cosa del genere:

 


extern "C" int __cdecl WinMainCRTStartup(void)
{
//Metodo M.Pietrek
STARTUPINFO startInfo={sizeof(STARTUPINFO),0};

GetStartupInfo(&startInfo);

return WinMain(GetModuleHandle(NULL),NULL,GetCommandLine(),

 ((startInfo.dwFlags & STARTF_USESHOWWINDOW)?startInfo.wShowWindow:SW_SHOWDEFAULT));

}


 

Praticamente ci procuriamo l'handle del processo corrente, la commandline e passiamo il tutto come parametro alla WinMain() Dalla struttura STARTINFO ci procuriamo invece i parametri che ci diranno se aprire la finestra in una posizione di default (SW_SHOWDEFAULT) oppure in una posizione che abbiamo deciso noi. Ricompiliamo con questa aggiunta e andiamo a vedere il .map

 

0001:0000023c _WinMainCRTStartup 0040123c f w32app.obj
0001:0000028c _strlen 0040128c f msvcrt:MSVCRT.dll
........................

........................
0002:00000010 __imp__strlen 00402010 msvcrt:MSVCRT.dll
0002:00000014 \177MSVCRT_NULL_THUNK_DATA 00402014 msvcrt:MSVCRT.dll

 

Cavolo!! ne è sparita di roba!!! Praticamente tutto quello che serviva per una corretta inizializzazione si è andato a benedire, ma vedrete che lanciando il programma tutto funziona come prima :-) Bella strizzatina eh? Come vedete adesso WinMainCRTStartup fa riferimento ad una funzione (f) all'interno del nostro file oggetto (w32app.obj).

Si ma non ci basta, noi siamo più bravi di Pietrek....zzzzaaaaakkkkkkkk arghhhhhh!!! sono stato fulminato da una saetta! Scusa O Sommo, non lo dirò mai più...;-) Scherzi a parte, togliamoci di torno anche questa WinMainCRT posticcia grazie ancora una volta alle opzioni del linker. Nelle opzioni C/C++ del progetto dico di sbattersene dell'ExceptionHandling, dico al linker che l'entrypoint è la mia WinMain e mi procuro da solo l'handle all'istanza del mio processo con GetCurrentProcess(). Ce ne freghiamo bellamente della struttura STARTUP_INFO, la nostra finestra sarà aperta da win in una posizione che lui sceglie per default.

Ecco qui la modifica:

//cambia l'entrypoint redirigendolo alla mia funzione
#pragma comment(linker,"/ENTRY:WinMiao")
 

............

............

int APIENTRY WinMiao(void)
{
    MSG msg;
    int nCmdShow=SW_SHOWDEFAULT;           //apro la finestra nella posizione di default
    HINSTANCE hInstance=GetModuleHandle(NULL); // recupero l'handle dell'istanza

............

............


Ricompiliamo il tutto e guardiamo un po cosa succede nel .map

Si sparito anche il riferimento alla WinMainCRTStartup, abbiamo risparmiato anche quello!! Tutta quella "robaccia" è sparita (robaccia un cavolo, sono le classiche "mutande di lamiera" per il nostro exe) e la dimensione del codice è ovviamente diminuita. La /ENTRY non fa altro che mettere nel campo "EntryPoint" del PE il RVA della funzione da noi specificata e il PeLoader di Win una volta sistemato il nostro exe in memoria inizierà ad eseguirlo proprio da li.

Qui spenderei due parole su una cosa che non sapevo e che ho scoperto ciacciando e sperimentando per questo tutorial, ovvero l'uso della variabile _fltused.

Pasticciando nel codice per far combinare qualcosa al mio programma, ho dichiarato una

variabile float, per scanf"arci" dentro una valore. Compilo il programma e mi ritrovo dentro all'eseguibile moltissimi routine per l'init e il controllo del coprocessore matematico...troppa grazia!! Io non faccio nessun calcolo float! Solo un semplice scanf("%f",&miofloat); si porta dietro tutta quella roba? Se anche a voi da fastidio come a me, allora dichiarando una variabile "ad hoc" vi toglierete dai piedi un sacco di funzioni:

 

// rendiamoci conto di quanta roba non desiderata ci si porta dietro
// includendo la WinMainCRTStartup della msvcrt oppure della libc !!
extern "C" int _fltused = 0x00;

 

Questo basta per evitare l'import statico o dinamico di molte funzioni per l'init e la gestione dell'unità floating point. Ah! mi scordavo, questo solo nel caso che usiate la WinMainCRTStartup della LIBC o della MSVCRT, se usate il metodo della redirezione dell'entry point (/ENTRY:miaentry) il float non sarò minimamente toccato, e quindi non viene incluso nulla di tutto questo e questa variabile non ha senso. Ci sono i pro, cioè si guadagna spazio, ma ci sono anche i contro, e cioè le operazioni in virgola mobile potrebbero essere inesatte, ed eventuali eccezioni del coprocessore in virgola mobile non gestite, ma se il vostro scopo è solo quello di passare un valore float ad una funzione senza fare conti...beh allora ci si può stare.

Il nostro eseguibile continua a diminuire di dimensione, ma non siamo ancora contenti, vediamo un po cosa si può fare ancora.....

                                                                   

                                                                    Lo stub del PE


Per chi non lo sapesse la prima parte del programma eseguibile è chiamata STUB e contiene un programma MSDOS16bit (si proprio MSDOS) che è il responsabile della fantastica scritta "This program must run under win32". Domanda: siamo nel 2004, le applicazioni che ci sono in giro sono ultramegamultimediali, graficamente evolute e bla bla bla.......ma chi è quel tonto che prova a far girare mathcad sul 386 con dos6.22? e ancora....chi è che ha in casa un computer con installato sopra qualcosa di meno di win98? E ALLORA COSA DIAMINE CI STA A FARE QUELLO STUB? EH!! :-\ Ecco che ci torna in aiuto il linker e la sua bella opzione:

//linka uno stub minimo di 64byte, altrimenti mette lo stub di default che sono 512byte in piu'!!
#pragma comment(linker,"/STUB:MINISTUB.BIN")


posso sostituire allo stub standard uno stub "personalizzato", quindi posso togliere quel pesantissimo programmone con un bel:

seg000:0000 ; --------------------------------------------------------------------------
seg000:0000 MINISTUB.BIN (dimensione 64byte)
seg000:0000 ; --------------------------------------------------------------------------
 

seg000:0000 ; Segment type: Pure code
seg000:0000 seg000 segment byte public 'CODE' use16
seg000:0000 assume cs:seg000
seg000:0000 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
seg000:0000 aWin32Only db 'Win32 only!',0Dh,0Ah,'$'
seg000:000E assume ss:seg001, ds:nothing
 

seg000:000E ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
seg000:000E public start
seg000:000E start proc near
seg000:000E push cs
seg000:000F mov ah, 9
seg000:0011 mov dx, 0
seg000:0014 pop ds
seg000:0015 assume ds:nothing
seg000:0015 int 21h             ; DOS - PRINT STRING
seg000:0015                       ; DS:DX -> string terminated by "$"
seg000:0017 mov ax, 4C01h
seg000:001A int 21h             ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT)
seg000:001A start endp        ; AL = exit code
seg000:001A ; --------------------------------------------------------------------------
 

seg000:001C db 40h, 3 dup(0)
seg000:001C seg000 ends


Ricompiliamo...un warning ci dice che lo stub non è un programma a 16bit valido, in effetti /STUB vuole solo exe, nemmeno com, però possiamo ignorare bellamente questo messaggio, perchè in realtà il loader del pe cerca soltanto la stringa MZ all'inizio del file, del resto se ne sbatte e noi altrettanto. Per zittire definitivamente questo (noioso) messaggio un bel comando da impartire al linker:

 

//non fa apparire il warning specifico per il nuovo stub

#pragma comment(linker,"/IGNORE:4060")

 

Ecco quindi che da 512byte dello stub standard messo dal linker, siamo arrivati a 64byte del nostro stub, più che sufficiente allo scopo di mantenere la compatibilità col formato PE. Nel progetto allegato a questo testo troverete anche quello.

 

                                                                      Allineamento di file e sezioni

 

A proposito di PE, possiamo limare qualcosa? No, stavolta non possiamo limare nulla, i campi del PE servono, e due in particolare attirano la nostra attenzione, FileAlignment e SectionAlignment, tanto più che ritroviamo due switch del linker che si occupano di questi due campi, ovvero: /FILEALIGN e /ALIGN. Se guardiamo in giro nella documentazione ufficiale e non del PE, troviamo che "FileAlign deve essere un multiplo di 2 compreso tra 512 e 64k" mentre "SectionAlign è usualmente la dimensione di una pagina di memoria" che come tutti sapete nell'architettura intel32bit è 4kb, anche se può essere settata a 2MB oppure 4MB.

 

//allinea il file e le sezioni a 16 byte invece che a 4k, pero' l'exe funzionerà solo su nt/2k/xp
#pragma comment(linker,"/FILEALIGN:0x10 /ALIGN:0x10")


Il filealign e' un retaggio del passato, quando allineare il file a multipli della grandezza di un settore significava salvare tempo nel caricamento in ram dell'eseguibile. Oggi secondo me non serve a nulla, le velocità dei vari udma100/133 e dei bus di sistema non fanno certo percepire sostanziali differenze di caricamento come ai tempi dei 486 33Mhz, però ci torna comodo perchè lo spazio su disco occupato dal file è "sprecato" arrotondando la grandezza del file al multiplo di 4096byte piu' vicino, (di default filealign e' 4kb per win9x, 512byte per winnt/2k/xp) quindi nel caso peggiore ben (!) 4095bytes possono andar persi. SectionAlign invece regola la quantità di ram che il nostro programma occupa in memoria. A grandi linee vi dico cosa combina il LoaderPE: prende il nostro exe, fa un "sanity check" dell'header PE più o meno meticoloso a seconda della versione di Windows che stiamo usando e poi alloca la memoria richiesta all'esecuzione del programma, a partire dalla base (con l'opzione /BASE del linker possiamo scegliere dove far partire il programma, vi ricordo che la memoria a disposizione di un programma utente è tra xxxxxxxxx e xxxxxxxxx) riserva tante pagine di memoria quante ne servono per contenere le sezioni del programma, assegna alle pagine i vari permessi (RWE) e poi passa la palla ad un'altra parte del kernel che crea il processo, setta l'environment per l'esecuzione (gestore eccezioni, strutture di sistema, registrazione nelle liste di sistema etc.) e finalmente lancia il programma. Bene, anche qui il discorso è identico allo spazio occupato su disco, ovvero se il mio programma occupa 7Kb, il loader è costretto ad allocare due pagine da 4kb ognuna (meno non si puo) e ben (!) 1Kb è paddato con zero. Se il file PE ha più di una sezione, ogni sezione è direttamente paddata nel file per rispettare il boundary delle pagine (creando le famose "cave" che possono poi essere sfruttate per ehm...giocare :). Allora facciamo una bella cosa, cerchiamo di ridurre al minimo lo spazio sprecato. Prima cosa una bella fusione delle sezioni, in modo da non sprecare spazio, la direttiva /MERGE capita proprio a fagiolo, prima però dobbiamo dire al compilatore che la sezione risultante deve avere gli attributi RWE per non incappare in errori di accesso alla memoria a run time. La direttiva /SECTION: permette di scegliere gli attributi. Sinteticamente vi dico che questi attributi influenzeranno direttamente gli attributi della zona di memoria in cui il loader del PE schiafferà il vostro programma, ed ovviamente una zona DATI che deve essere sia letta che scritta (RW) non potrà convivere in una zona .code (ER) a meno di questo "mastruzzo". Si lo so, è pericoloso, il codice risiede in una zona potenzialmente riscrivibile da errori o overflow compiuti dal programma stesso, ma che vi devo dire....programmate bug-free e non avrete problemi ;-)
Giusto per manie di grandezza aggiungiamo anche una bella
#pragma code_seg(".mrcode") così quando apriamo il file col peditor esce il mio nome :-P. Vi ricordo comunque che al loader di sistema non gliene frega assolutamente nulla di come chiamate le sezioni. L'unica che non possiamo fondere insieme alle altre è la .rsrc, la sezione che contiene le risorse, altrimenti scordatevi l'icona del programma (Win non la trova e mette quella di default). Ricompiliamo e.....warning! "Attenzione, stai fondendo insieme delle sezioni che hanno notoriamente attributi diversi", si lo so, l'ho scelto io...allora poniamo rimedio con un'altro switch: #pragma comment(linker,"/IGNORE:4078")
Ok, andiamo a guardare cosa è successo, prendiamo il PE e vediamo le sezioni adesso ce ne sono solo due, una che ingloba codice dati etc.etc. e la .rsrc. Possiamo spingerci oltre? "Si-puo'-fareeeee" (cit. da "Frankestain Junior" ;) Andiamo a stuzzicare FileAlign e SectionAlign e metterli a dei valori perlomeno strani, ma utili. Il sectionalign nei sistemi nt/2k/xp può essere anche 16byte, bene, mettiamocelo e mettiamo a 16byte anche il filealign, sempre tramite le due famose direttive dette prima /FILEALIGN:0x10 e /ALIGN:0x10. Ma come, direte voi, non va mica bene di mettere il filealign a 16, mi avevi detto che doveva essere multiplo di 2 e compreso tra 512 e 64k. E io vi dico: "eh no! mica ve l'ho detto io! sta scritto nel manuale del bravo programmatore e io ve l'ho solo riportato!". Già, anche questo align è valido, almeno sui sistemi nt/2k/xp tante' che vi riporto lo pseudocodice (hehe) del validatore del pe che si occupa di stabilire se questi due valori sono corretti:
 



LONG IsValidNtHeader(int SectionAlign, int FileAlign) {

    if (((FileAlign & 511) != 0) && (FileAlign != SectionAlign))
  return INVALID_IMAGE_FORMAT;

    if (FileAlign == 0)
  return INVALID_IMAGE_FORMAT;

    if (((FileAlign - 1) & FileAlign) != 0)
        return INVALID_IMAGE_FORMAT;

    if (SectionAlign < FileAlign)
        return INVALID_IMAGE_FORMAT;

 return VALID_IMAGE_FORMAT;
}

 



Se fate due prove vi accorgerete che anche valori di alignment di 1byte sono validi, ma il linker non ci permette di usarli, segnalandoci l'errore, inoltre è bene sapere che non è possibile usare allineamenti di sezione di dimensione più piccola della più piccola struttura presente nel nostro eseguibile.
Andiamo a guardare adesso cosa succede al nostro eseguibile, prendiamo di nuovo il peditor e andiamo a vedere VirtualSize e RealSize...alla faccia!! Lo spazio sprecato nelle sezioni è pochissimo! Niente "cave" per giocare cari miei! e abbiamo raggiunto in fase di compilazione addirittura uno spazio minore di quello che ci permette il realign del mitico peditor di yoda!!
Prendiamo ora un debugger comodo...tipo ollydebug vai, che mi piace, e guardiamo come viene mappato adesso il nostro exe in memoria, visto? Unica sezione in memoria ridotta allo spazio effettivamente occupato dai bytes + un'altra sezione che "padda" l'intero file alla dimensione di pagina, questo vuol dire che nel caso peggiore, qualunque sia la grandezza dell'eseguibile, posso sprecare al massimo 4095bytes, meno male perché con i mega di ram che scarseggiano oggi come oggi, lo spazio è prezioso ;) Riporto in ultima istanza i #define da mettere in testa al progetto, riuniti tutti insieme:

 

// rinomina la .code in .mrcode, non serve a nulla ma e' bellino :)
#pragma code_seg (".mrcode")
//allinea il file e le sezioni a 16 byte invece che a 4k, pero' l'exe funzionerà solo su nt/2k/xp
#pragma comment(linker,"/FILEALIGN:0x10 /ALIGN:0x10")
//fonde insieme le varie sezioni in una unica a cui modifica gli attributi (la .data deve avere "W")
#pragma comment(linker,"/MERGE:.data=.mrcode /MERGE:.rdata=.mrcode /MERGE:.text=.mrcode /SECTION:.mrcode,EWR")
//ignora i warning per la rinominazione delle sezioni e dello stub e cambia l'entrypoint alla mia funzione
#pragma comment(linker,"/IGNORE:4078,4060 /ENTRY:WinMiao")
//rilocazione della base, /release mette il checksum nel campo apposito del PE e /version la versione dell'exe
#pragma comment(linker,"/BASE:0x6130000 /RELEASE /VERSION:1.1")
//linka uno stub minimo di 64byte, altrimenti mette lo stub di default che sono 512byte in piu'!!
//poi chiedo anche di non includere i dati mai referenziati all'interno del mio programma
#pragma comment(linker,"/STUB:MINISTUB.BIN /OPT:REF")

 

Non vi avevo spiegato /OPT:REF, praticamente elimina dall'eseguibile le variabili o gli oggetti che sono stati dichiarati ma che non vengono mai usati all'interno del programma, un altro pochettino di spazio guadagnato! :-P

Certo questa storia di "fondere" tutto in un unica sezione è bello, ma si paga con l'esposizione ad un rischio non da poco, ovvero del codice errato potrebbe sovrascrivere l'area dati, e provocare un bell' errore che mi impalla il programma. Ottima scusa per introdurre un argomento trito e ritrito, che però io ho approfondito solo adesso e che quindi vi spappardello con piacere: la gestione delle eccezioni.

 

                                                    SEH e Vectored Exception Handling


Ricordate quando vi ho detto prima che disabilitando la gestione delle eccezioni in fase di compilazione si risparmiava codice? Questo è vero, ma se facciamo una divisione per zero all'interno del programma, salta tutto! Certo, c'e' sempre l'handler che il sistema mette di default per ogni processo che crea, ma noi vogliamo qualcosa di più, vogliamo un *nostro* gestore delle eccezioni. _except_handler3 e _CxxExceptionFrame sono le routine standard messe a disposizione staticamente da LIBC e dinamicamente da MSVCRT, noi le abbiamo escluse, ma un blocco
_try, _catch, _finally (rimando all'ottima spiegazione sull'uso di queste keyword fatta dal buon ntoskrnl) ce lo possiamo fare anche "in casa" si tratta solo di costruire sullo stack i parametri della struttura EXCEPTION_FRAME a cui far puntare fs:[0] ma facciamo chiarezza con un po di codice, poi riprendo a spiegare

__asm
{                             // Costruisco una struttura EXCEPTION_REGISTRATION sullo stack:
    push handler          // Indirizzo della funzione handler

    push FS:[0]           // Indirizzo dell'handler precedente
    mov FS:[0],ESP     // Installo la nuova EXECEPTION_REGISTRATION nel registro FS
}

__asm
{                            // Rimuovo la struttura EXECEPTION_REGISTRATION
    mov eax,[ESP]      // Prendo il puntatore alla precedente struttura
    mov FS:[0], EAX   // Lo ripristino al suo posto

    add esp, 8           // Tolgo dallo stack la mia struttura EXECEPTION_REGISTRATION
}


bene, il discorso è abbastanza semplice, il registro FS nell'architettura intel è usato per tenere lo stack delle eccezioni, se vuoi che una tua routine sia avvisata quando è sollevata un'eccezione (ecco i tipi possibili, trovate le definizioni nel file "WinNT.h" nella vostra directory "include").


#define STATUS_WAIT_0               ((DWORD )0x00000000L)
#define STATUS_ABANDONED_WAIT_0     ((DWORD )0x00000080L)
#define STATUS_USER_APC             ((DWORD )0x000000C0L)
#define STATUS_TIMEOUT              ((DWORD )0x00000102L)
#define STATUS_PENDING              ((DWORD )0x00000103L)
#define DBG_EXCEPTION_HANDLED       ((DWORD )0x00010001L)
#define DBG_CONTINUE                ((DWORD )0x00010002L)
#define STATUS_SEGMENT_NOTIFICATION ((DWORD )0x40000005L)
#define DBG_TERMINATE_THREAD        ((DWORD )0x40010003L)
#define DBG_TERMINATE_PROCESS       ((DWORD )0x40010004L)
#define DBG_CONTROL_C               ((DWORD )0x40010005L)
#define DBG_CONTROL_BREAK           ((DWORD )0x40010008L)
#define DBG_COMMAND_EXCEPTION       ((DWORD )0x40010009L)
#define STATUS_GUARD_PAGE_VIOLATION ((DWORD )0x80000001L)
#define STATUS_DATATYPE_MISALIGNMENT ((DWORD )0x80000002L)
 

#define STATUS_BREAKPOINT           ((DWORD )0x80000003L)

//eccezione sollevata da un breakpoint (0xCC ovvero int 3)
 

#define STATUS_SINGLE_STEP          ((DWORD )0x80000004L)

//la cpu e' in stato "single step" e solleva questa eccezione ogni volta che esegue un istruzione


#define DBG_EXCEPTION_NOT_HANDLED   ((DWORD )0x80010001L)
 

#define STATUS_ACCESS_VIOLATION     ((DWORD )0xC0000005L)

//qualcuno ha cercato di leggere/scrivere in una zona di memoria che non poteva leggere/scrivere


#define STATUS_IN_PAGE_ERROR        ((DWORD )0xC0000006L)
#define STATUS_INVALID_HANDLE       ((DWORD )0xC0000008L)
#define STATUS_NO_MEMORY            ((DWORD )0xC0000017L)
#define STATUS_ILLEGAL_INSTRUCTION  ((DWORD )0xC000001DL)
 

#define STATUS_NONCONTINUABLE_EXCEPTION ((DWORD )0xC0000025L)

// questa eccezione impalla tutto, non possiamo risolverla, salvare il salvabile e chiudere il programma


#define STATUS_INVALID_DISPOSITION  ((DWORD )0xC0000026L)
#define STATUS_ARRAY_BOUNDS_EXCEEDED ((DWORD )0xC000008CL)
 

#define STATUS_FLOAT_DENORMAL_OPERAND ((DWORD )0xC000008DL)
#define STATUS_FLOAT_DIVIDE_BY_ZERO  ((DWORD )0xC000008EL)
#define STATUS_FLOAT_INEXACT_RESULT  ((DWORD )0xC000008FL)
#define STATUS_FLOAT_INVALID_OPERATION ((DWORD )0xC0000090L)
#define STATUS_FLOAT_OVERFLOW        ((DWORD )0xC0000091L)
#define STATUS_FLOAT_STACK_CHECK     ((DWORD )0xC0000092L)
#define STATUS_FLOAT_UNDERFLOW       ((DWORD )0xC0000093L)
//eccezioni sollevate in varie situazione dalla FPU (virgola mobile)

 

#define STATUS_INTEGER_DIVIDE_BY_ZERO ((DWORD )0xC0000094L)

//abbiamo tentato di fare una divizione per zero


#define STATUS_INTEGER_OVERFLOW      ((DWORD )0xC0000095L)

// il risultato è troppo grande per essere contenuto in un intero


#define STATUS_PRIVILEGED_INSTRUCTION ((DWORD )0xC0000096L)
//si tenta di eseguire un istruzione privilegiata in un livello che non lo consente

//a ring3 lo sono CLI, oppure WRTSC ecc.

 

#define STATUS_STACK_OVERFLOW        ((DWORD )0xC00000FDL)

//lo stack di default non è sufficiente (1Mega) incrementarlo!


#define STATUS_CONTROL_C_EXIT        ((DWORD )0xC000013AL)
#define STATUS_FLOAT_MULTIPLE_FAULTS ((DWORD )0xC00002B4L)
#define STATUS_FLOAT_MULTIPLE_TRAPS  ((DWORD )0xC00002B5L)
#define STATUS_REG_NAT_CONSUMPTION   ((DWORD )0xC00002C9L)

 

I codici non rispecchiano gli interrupt che sono generati dall'architettura Intel, che sono:

 

00- Divide Error

01- Debug exception (single-step and hardware debugging)

02- NMI interrupt

03- Breakpoint

04- INTO detected overflow

05- BOUND range exceeded

06- Invalid opcode

07- Coprocessor Device not available

08- Double Fault

0A- Invalid Task State Segment (TSS)

0B- Segment not present

0C- Stack fault

0D- General protection fault (GPF)

0E- Page fault

 

ma Window li ricostruisce e li reinterpreta in una più significativa notazione, potete trovare in WINERROR.H come interpretare correttamente il codice che Win presenta in caso di eccezione, ed esattamente:

 

//
// Values are 32 bit values layed out as follows:
//
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
// +---+-+-+-----------------------+-------------------------------+
// |Sev|C|R| Facility | Code |
// +---+-+-+-----------------------+-------------------------------+
//
// where
//
// Sev - is the severity code
//
// 00 - Success
// 01 - Informational
// 10 - Warning
// 11 - Error
//
// C - is the Customer code flag
//
// R - is a reserved bit
//
// Facility - is the facility code
//
// Code - is the facility's status code
//
 

In realtà tutto passa attraverso la IDT (interrupt Descriptor Table) che rimappa gli interrupt generati ad altrettante funzioni ring0 gestite dal kernel di Win.

Quando un interrupt avviene, l'esecuzione passa alla routine preposta dalla IDT a gestire quel tipo di int, e tutto procede. Anche qui mi sto dilungando troppo, esistono qui sulla UIC molti bei tutorial sulla modalità protetta e su cosa sia una IDT, leggeteveli! Ma torniamo a noi, gestiamo queste eccezioni: mettiamo nella struttura l'indirizzo dell' handler e la carichi in FS:0 a questo punto la routine sarà interpellata *DOPO* quella di sistema per vedere un po il da farsi in caso di exception. Volendo si può anche chiamare una bella SetErrorMode(SEM_NOGPFAULTERRORBOX) in modo che l'unico messaggio (o meno) che appare in caso di exception sia solo il nostro (quello di sistema viene "silentato"). Importante ricordarsi di fare l'unwinding dello stack frame ("srotolamento" non è bello da dire), altrimenti non torna più niente con la nidificazione. La funzione SetErrorMode() specifica come il sistema si deve comportare nel caso in cui la nostra applicazione generi un errore serio. Passando il valore SEM_NOGPFAULTERROBOX il sistema non mostrerà più il classico messaggio di errore e demanderà ogni onere alla nostra applicazione.

Il registro FS punta sempre al TIB (thread information block sotto W9x/ME detto anche TEB, Thread Environment Block sotto WinNT/XP/2K) del processo corrente, all'interno del file include WINNT.H troverete la definizione della struttura NT_TIB:

 

typedef struct _NT_TIB {
    struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
    PVOID StackBase;
    PVOID StackLimit;
    PVOID SubSystemTib;
    union {
        PVOID FiberData;
        DWORD Version;
        };
    PVOID ArbitraryUserPointer;
    struct _NT_TIB *Self;
} NT_TIB;
typedef NT_TIB *PNT_TIB;

Questa è la struttura che definisce le informazioni di un thread, quindi ogni thread ha il proprio gestore di eccezioni. Il membro che trovate all'indirizzo puntato da FS:[0] contiene un puntatore ad una struttura EXCEPTION_REGISTRATION_RECORD che a sua volta trovare nel file EXCEPT.INC. La struttura è così composta:

__EXCEPTIONREGISTRATIONRECORD struc
prev_structure dd ?
ExceptionHandler dd ?
ExceptionFilter dd ?
FilterFrame dd ?
PExceptionInfoPtrs dd ?
__EXCEPTIONREGISTRATIONRECORD ends

il primo membro è un puntatore alla struttura precedente, il secondo membro punta alla nostra routine che gestisce le eccezioni. Il VisualC++ costruisce sulla base del SEH una gestione delle eccezioni detta "frame-based". Nella EXCEPTION_REGISTRATION la DWORD che punta all'handler, punta sempre ad una funzione che si chiama _except_handler3, sarà poi questa funzione a chiamare quella che noi abbiamo definito come filtro (blocco _try,_catch). Oltretutto non importa quanti blocchi _try,_catch vengono usati nel nostro codice, il Visual inserirà una sola EXCEPTION_REGISTRATION_RECORD in FS, che punterà sempre a _except_handler3. Per gestire correttamente tutto quanto la struttura è espansa, potrete trovare in EXSUP.INC la definizione:

typedef struct _EXCEPTION_REGISTRATION PEXCEPTION_REGISTRATION;
 struct _EXCEPTION_REGISTRATION{
 struct _EXCEPTION_REGISTRATION *prev;
 void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);
 struct scopetable_entry *scopetable;
 int trylevel;
 int _ebp;
 PEXCEPTION_POINTERS xpointers;
};

i primi due valori sono una struttura EXCEPTION_REGISTRATION classica, quella che usa il sistema per intendersi, gli ultimi tre campi scopetable, trylevel e _ebp sono le aggiunte necessarie per gestire tutte le eccezioni in modo frame-based. Quest'ulitmo valore in particolare è inserito per poter permettere l'accesso al resto della struttura per mezzo di spiazzamento negativo rispetto al frame pointer (EBP appunto).

Quindi con riferimenti del tipo [ebp-8] ad esempio indicizziamo trylevel e via via ogni membro come qui riportato:

   EBP-00 _ebp
    EBP-04 trylevel
    EBP-08 scopetable pointer
    EBP-0C handler function address
    EBP-10 previous EXCEPTION_REGISTRATION
    EBP-14 GetExceptionPointers
    EBP-18 Standard ESP in frame

Okkio, ai fini del sistema conta sempre e solo [EBP-10] e [EBP-0C], cioè la EXCEPTION_REGISTRATION nuda e cruda! Il VisualC++ usa trylevel come indice da usare nell'array scopetable, e grazie a questo trucco permette l'uso di una sola EXCEPTION_REGISTRATION per la gestione di molteplici blocchi _try,_catch anche se nidificati tra loro. La _except_handler3 gestisce i vari messaggi di ritorno, quale filtro chiamare, se continuare nella ricerca di altre routine di filtro e chiama le funzioni di unwind del frame strack. Giusto per completezza vi dico che ogni chiamata "classica" a WinMain o main è controllata da un blocco _try,_catch, come si può bene vedere dal file CRT0.C, un file sorgente della LIBC del VisualC++ la cui routine di filtro si trova nel file sorgente WINXFLTR.C . Un ulteriore handler di eccezioni è installato a monte da Kernel32.DLL al momento della chiamata alla funzione BaseProcessStart(), che dopo aver settato il TIB lancia il nostro processo.
Quindi male che vada, se togliamo tutto il meccanismo del FrameBasedSEH tramite apposito switch del compilatore, avremo sempre questo per-process handler che interverrà in caso di errore (tanto se bisogna crashare....) Con
/ENTRY:miaentry bypassiamo gli startup originali e quindi anche il codice che installa i filtri, tranne ovviamente il gestore installato dalla funzione BaseProcessStart(). Vi rimando al bellissimo articolo "A crash course on the depths of Win32 SEH" di MattPietrek per ulteriori approfondimenti su questo argomento. Si possono usare le eccezioni per tante cosette interessanti, a scopo di debug, oppure per far ammattire la gente intorno ai crypter di PE :-)
Con l'avvento di NT5 (ovvero XP) anche le eccezioni hanno subito un restyling, ed anche molto intelligente ad onor del vero. Si tratta del cosidetto "Vectored Exception Handling" (il mitico Pietrek si è occupato di questa innovazione in uno dei suoi articoli "Under The Hood" su MSDJ).
A questo giro l'eccezione viene installata e disinstallata tramite due comode API di sistema, e cosa alquanto più interessante la lista circolare in cui è inserito il puntatore al nostro gestore è interpellata *PRIMA* di quella standard di sistema. Rifacendosi all'esempio di Pietrek, si possono fare cose ganzette, tipo patchare in memoria l'eseguibile mettendo un bel 0xCC (int 3) dove più ti pare e ti piace, aspettare che l'eccezione venga sollevata e in seguito far fare alla nostra routine quel cavolo che ci pare col ReadProcessMemory()/WriteProcessMemory(), poi se vogliamo possiamo passare il controllo all'handler di sistema, oppure no. Potremmo altresì creare un gestore di eccezioni superintelligente, notificare all'utente un qualcosa tipo "vai tranquillo, ci ho pensato io" e continuare con l'esecuzione del programma


 

// Questa è la gestione vectored dell'SEH disponibile solo su XP e 2K
// interviene PRIMA della gestione di sistema.


LONG __stdcall VectoredHandler(
struct _EXCEPTION_POINTERS *ExceptionInfo
){
 

TCHAR excpstr[MAX_STRING];

        StringCbPrintf(excpstr, MAX_STRING, "*ERRORE* \nException Code: %08X \nException Flags: %X \nException Address: %08X",ExceptionInfo->ExceptionRecord>ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionFlags, ExceptionInfo->ExceptionRecord->ExceptionAddress);

        MessageBox(NULL, excpstr,"Gestore interno VectoredSEH", MB_OK );
        return EXCEPTION_CONTINUE_SEARCH;
}
 

 

//per usare queste funzioni è obbligatorio compilare con _WIN32_WINNT >= 0x0501

#define _WIN32_WINNT 0x0501

...............

...............

...............

//puntatore all'handle della funzione VectoredHandleSEH

    PVOID vecthndl1;

...............

...............

...............

// aggiungo il mio handler alla lista di sistema
    vecthndl1 = AddVectoredExceptionHandler(1,VectoredHandler);

 

// con questa istruzione tutti gli errori li devo gestire io
// con un mio handler win non poppa nessuna messagebox
    SetErrorMode(SEM_NOGPFAULTERRORBOX);

...............

...............

...............

// rimuovo l'handler dalla lista di sistema
    RemoveVectoredExceptionHandler(vecthndl1);

 

e se siete curiosi, piazzate in giro per il vostro programma qualcosa tipo questo:

 

_asm cli;        // istruzione non valida a ring3

 

oppure questo:

 

*(PDWORD)0 = 0;   // cerco di scrivere nella locazione 0

 

o ancora questo:

 

_asm int 3;        // caro, vecchio, mai troppo lodato  breakpoint

 

e poi vedete da voi cosa succede.

 


 

Invece di fargli fare questa cosa si poteva anche gestire un bug report...insomma un pò quel che vi pare. Ho messo 'sta cosa perché con pochissimo codice si salta l'handler standard in favore di un qualcosa di più personale. Il nostro vectorhandler si installa tramite la funzione dei sistema AddVectoredExceptionHandler() a cui passiamo due parametri, il primo definisce *quando* il nostro handler interverrà, NULL sta a significare "ultimo handler chiamato in caso di eccezione" mentre un valore diverso da zero, ovvero 1 come nell'esempio, sta a significare "primo handler chiamato in caso di eccezione", il secondo parametro è ovviamente il puntatore alla funzione che svolgerà il ruolo di handler. La RemoveVectoredExceptionHandler() rimuove dalla lista di sistema il nostro handler. Non voglio scendere oltre nell'argomento, tanto sono cose dette e ridette mille volte, ci sono fior di personaggi come Matt Pietrek che nella colonna "Under the Hood" di MSDJ hanno sviscerato a fondo questo argomento, oppure senza andare tanto lontano, trovate alla UIC stessa dei bei tutorial sulla SEH scritti da Quequero. Giusto per chiarezza vi informo che il puntatore alla struttura EXCEPTION_POINTERS sarà riempito dal sistema, e da li potremo risalire ad altri interessanti strutture che sono la EXCEPTION_RECORD e la CONTEXT, che ci forniscono in pratica una istantanea del sistema operativo e del processore al momento in cui l'eccezione è occorsa. Potremo quindi lavorare con i registri del processore, conoscere l'indirizzo di memoria del fault, che tipo di eccezione e' stata sollevata e mille altre cose, vi rimando al file include "WINNT.H" che contiene le definizioni di tutte queste strutture, un rapido search anche col modesto notepad vi toglierà ogni dubbio. Alla fine la nostra routine potrà ritornare il valore EXCEPTION_CONTINUE_SEARCH, che in poche parole sta per "io me ne lavo le mani, hai fatto bene a chiamarmi ma lascio la palla a qualche altra routine" oppure il valore EXCEPTION_CONTINUE_EXECUTION che invece sta per "ho rimediato io i casini che hai fatto, puoi riprendere l'esecuzione del programma in tranquillità".

Ma voi vi chiederete "ma chi controlla il controllore?" ovvero, se il nostro handler è buggato e solleva un'eccezione? a quel punto le cose sono tre:
 

1) Lo fate apposta per aggiungere un livello di complessità al vostro programma e renderlo + difficilmente studiabile. In questo caso la maniera più sicura per renderlo indecifrabile è non scriverlo, tanto poi capita sempre il solito AndreaGeddon di turno che vi rovina il lavoro di mesi.....
 

2) Siete dei "duri alla pina verde"....azzo, sbagliate anche il codice dell'handler? mapperpiacere...;-)
 

3) Vimportaunamazza, anche se abbiamo fatto un errore nel nostro handler ce' sempre il per-process handler sapientemente messo dal sistema, si crasha uguale ma la finestrella ci appare ugualmente per avvisarci di che morte siamo morti.

               

                                                                    IL MANIFEST e WinXP

 

Certo che con tutte queste finestre e finestrelle e funzioni specifiche di winxp, è brutto vedere tutto "old style"...e se volessi scrivere un'applicazione che gira solo sotto xp che abbia la possibilità di sfruttare i themes e gli styles per i bottoni, listview, controlbar etc.etc.?! Potrei appoggiarmi alla funzione blah blah blah....macchè, esiste una bella cosetta per rendere più "fulgido" il nostro programma, e si chiama Manifest. Il Manifest , scritto in linguaggo XML (io non lo conosco, ma è tipo html grosso modo) istruisce il loader del PE di WinXP in modo da "ingannare" il nostro programma, aprendo la comctrl.dll di sistema in modo che il nostro programma apra i suoi controlli (bottoni, liste, statusbar, controlbar etc.etc.) con i nuovi stili invece che con i vecchi. Ma guardiamo meglio questo manifest, almeno con qualcosa sotto mano si capisce meglio cosa fare e dove agire:

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
            version="1.0.0.0"
            processorArchitecture="x86"
            name="YourCompanyName.YourDivision.YourAppz"
            type="win32"
            />
                <description>Your app description here</description>
                <dependency>
                    <dependentAssembly>
                    <assemblyIdentity
                        type="win32"
                        name="Microsoft.Windows.Common-Controls"
                        version="6.0.0.0"
                        processorArchitecture="X86"
                        publicKeyToken="6595b64144ccf1df"
                        language="*"
                        />
                    </dependentAssembly>
                </dependency>
</assembly>

 

questo è il Manifest, i campi che ci interessano sono quelli evidenziati in grassetto il resto lo potete tranquillamente lasciare così com'è.

"name" specifica il nome della vostra applicazione, e deve essere necessariamente della forma "YourCompanyName.YourDivision.YourAppz" ovvero una cosa del tipo "UIC.mrcode.miogatto" dove "miogatto" è il nome dell'eseguibile castrato del prefisso ".exe" (anche il gatto è castrato, quindi è normale che il nome subisca la stessa sorte)

"version" invece fa riferimento alla versione della libreria Common-Controls installata nel vostro sistema, la 6.0.0.0 ce l'hanno tutti (XP la installa di default) se avete fatto qualche aggiornamento potrete trovare anche la 6.0.10.0, ma se volete essere sicuri con 6.0.0.0 "beccate" tutti gli XP. Se andate nella directory Windows/WinSxS/Manifest troverete tutti i manifest installati nel vostro sistema, e aprendoli con un normale notepad potrete vedere a cosa fanno riferimento, ad esempio quello relativo ai common controls contiene tra l'altro:

 

<file name="comctl32.dll" hash="4f02ff771050b8657e289d75f19163fe2ab02600"hashalg="SHA1">
<windowClass>ToolbarWindow32</windowClass>
<windowClass>ComboBoxEx32</windowClass>
<windowClass>msctls_trackbar32</windowClass>
<windowClass>msctls_updown32</windowClass>
<windowClass>msctls_progress32</windowClass>
<windowClass>msctls_hotkey32</windowClass>
<windowClass>msctls_statusbar32</windowClass>
<windowClass>SysHeader32</windowClass>
<windowClass>SysListView32</windowClass>
<windowClass>SysTreeView32</windowClass>
<windowClass>SysTabControl32</windowClass>
<windowClass>SysIPAddress32</windowClass>
<windowClass>SysPager</windowClass>
<windowClass>NativeFontCtl</windowClass>
<windowClass>Button</windowClass>
<windowClass>Static</windowClass>
<windowClass>Listbox</windowClass>
<windowClass>ScrollBar</windowClass>
<windowClass>SysLink</windowClass>
<windowClass>tooltips_class32</windowClass>
<windowClass>ButtonListBox</windowClass>
<windowClass>SysAnimate32</windowClass>
<windowClass>SysMonthCal32</windowClass>
<windowClass>SysDateTimePick32</windowClass>
<windowClass>ReBarWindow32</windowClass>
<windowClass>Edit</windowClass>
<windowClass>Combobox</windowClass>
<windowClass>ComboLBox</windowClass>

 

il che vi fa capire bene quali sono i controlli della vostra applicazione che subiranno il "restyling" dei temi di xp.

Bene adesso salvate il Manifest nella directory che contiene il vostro eseguibile, ed appioppategli il nome "Yourappz.exe.manifest", aprite la vostra applicazione, et voilà! Come per magia tutto e diventato "l33t", bottoni bellissimi, tips con l'ombrettina, ingegneri che pensano....ma che bellezza ;) Il sistema dei manifest effettivamente è simpatico e se siete fortunati potrete "ringiovanire" anche applicazioni un po datate, tipo il windasm ad esempio, o qualsiasi programma che faccia uso al suo interno delle common controls. Basta prendere il programma e cercare all'interno della IAT, magari col pedit che è proprio comodo, la funzione InitCommonControls() della libreria COMCTL32.DLL, attenzione, potreste trovarla come "Ordinal" invece che "ByName", quindi se trovate qualcosa del tipo "Ordinal decimal:17 hex:0x00000011" siete in presenza della InitCommonControls(). Se il programma la usa, mettete nei campi del manifest i valori giusti e salvate il manifest nella stessa directory del programma da "rinnovare", poi lanciate il programma. Se siete fortunati vi sarete fatti da soli la versione "Designed for WinXP" ;-) XP cerca il manifest prima nella directory corrente del programma e dopo nella sezione .rsrc dell'eseguibile, in caso sia presente da ambedue le parti quello che comanda è comunque quello residente nella directory (io avrei fatto il contrario, ma si vede che qualche buon motivo ci sarà....forse....) per compattezza però io preferisco mettere il manifest all'interno del programma, nell'apposita sezione .rsrc, del resto il size cresce di poco e l'effetto è veramente notevole. Per mettere il manifest all'interno del programma ci sono alcune regolette da seguire, andiamo a vedere come e cosa fare. Innanzitutto dobbiamo aggiungere agli include del nostro programma un bel

#include <commctrl.h> perchè dobbiamo tassativamente inizializzare il tutto chiamando la funzione InitCommonControls() all'interno del nostro programma (dove vi pare, magari all'interno delle funzioni di inizializzazione e prima di aprire finestre e controlli vari). Poi ricordiamoci di aggiungere nelle librerie da linkare la COMCTRL32.LIB, altrimenti ci becchiamo un errore in fase di compilazione. Poi arriva il bello: il Manifest per essere incluso nelle risorse, ma deve risiedere tutto su una stessa linea, senza "carriage return" quindi armatevi di pazienza e rendetelo qualcosa tipo questo:

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"><assemblyIdentity version="1.0.0.0" processorArchitecture="x86" name="Microsoft.Windows.mioprog" type="win32"/><description>Your app description here</description><dependency><dependentAssembly><assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="X86" publicKeyToken="6595b64144ccf1df"language="*"/></dependentAssembly></dependency></assembly>

 

ovvero "tutto appiccicato" e poi editate a mano il file delle risorse del progetto, ovvero il file con estensione .rc che trovate nella root del progetto, ed inserite questa macro al suo interno:

 

/////////////////////////////////////////////////////////////////////////////
//
// RT_MANIFEST
//
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "MioProgramma.exe.manifest" 

 

Al momento della compilazione il linker penserà ad includere al suo posto il manifest, che è una risorsa di tipo "24" (quando è uscito il VisualC++ 6.0 non c'era il manifest quindi non c'e' un nome che lo definisce)e deve essere tassativamente nominata "1" ed essere la prima risorsa della sezione. Così facendo il loader del PE di XP/2K cercherà prima nella directory corrente il manifest, se non lo trova cercherà nella sezione .rsrc e a questo punto userà le informazioni ivi contenute per bypassare le common control "classiche" in favore di quelle nuove con supporto dei nuovi stili, tutto in maniera trasparente al nostro programma, carino no? Si, carino davvero, allora perchè non usare nel nostro programma un'altra feature esclusiva del nuovo sistema? Ottima scusa mrcode, dicci due parole sulla gdiplus...;-)

 

                                                      La libreria grafica GDIplus - Breve Introduzione

 

Per carità, trovate mille e mille tutorial online sulla gdiplus, però avevo voglia di dire qualcosina anche io, giusto un'introduzione a questa libreria. Faccio bene? Boh, comunque non sapevo cosa far fare al programma per non essere il solito trito e ritrito "hello word" allora mi è venuto in mente di fargli visualizzare un'immagine e perché non usare questa nuova libreria? GDI+ è ad oggetti ed è possibile installarla anche su Win98, ME e 2K ed aggiunge molte funzioni che per dei vagabondi professionisti non sono male, come ad esempio supporto nativo in lettura per un tot di formati grafici, primitive di disegno, testo anche con caratteri TT, manipolazione delle immagini e tante altre belle cosette.

Allora, per usare la libreria dovrete includerne l'header, dichiarare l'uso delle funzioni GDI, inizializzare la libreria (e ricordarsi poi di richiuderla) e linkare la gdiplus.lib.

 

Per quanto riguarda gli include:

 

#include <gdiplus.h>
using namespace Gdiplus;

 

e poi mettete gdiplus.lib nell'apposito spazio del project settings (Alt+F7 -> linguetta Link -> Category General -> Objext/Library Modules)

Ecco, ora quando userete le nuove funzioni il linker le troverà e le linkerà correttamente. Poichè questa libreria è entrata in vigore dall'avvento di XP, ricordatevi di definire

 

#define WINVER 0x0501
#define _WIN32_WINNT 0x0501

 

che prima degli include, così i prototype delle funzioni saranno "aperti" alla compilazione. Adesso dobbiamo inizializzare la libreria, per farlo usiamo l'apposita funzione

 

    //da passare alla init della GDI+

    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;

 

    // Inizializza GDI+.
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

 

che ci ritorna un "token" alla libreria e in più ci riempe la struttura GdiplusStartupInput con dei dati che ci serviranno in seguito.

Il Token potete vederlo come un Handler alla libreria, che servirà in seguito per dire alla libreria che il nostro programma non la vuole più usare. La chiusura della GDI+ si fa con:

 

    // Shutdown del GDI+
    GdiplusShutdown(gdiplusToken);

 

Adesso che abbiamo aperto la libreria possiamo usare le funzioni per caricare e salvare i file in formato grafico (è sempre bello vedere qualcosa a video :-) e nella fattispecie vi pupperete l'immagine del mio gattone rosso ehehehe.

 

    //INITCOMMONCONTROLSEX commonctrl;
    Image* image;                     // Variabile Globale (immagine di sfondo da caricare)
    FontFamily *fontFamily;         // una volta inizializzata la GDI+
    Font *font;

 

queste sono le variabili globali per l'oggetto Immagine e l'oggetto Font, si lo so, non si usano mai le variabili globali (o perlomeno il meno possibile) ma qui tornavano proprio comode comode :-P

 

    //adesso posso associare al puntatore alla variabile globale l'immagine di sfondo
    image = new Image(L"Bimbo.jpg");
    fontFamily = new FontFamily(L"Arial");
    font = new Font(fontFamily, 12, FontStyleRegular, UnitPixel);

 

Come vedete con tre righe carico l'immagine e definisco sia font che stile del font, tutto quanto senza appesantire il mio codice, tutto quello che serve risiede già nella dll della GDIPlus.

A questo punto non rimane che mostrare a video l'immagine, il testo e quant'altro volete voi, e per farlo utilizzerò una tecnica chiamata DoubleBuffering, che ai più sicuramente non dirà niente di nuovo, ovvero disegnare su una bitmap non visualizzata e mostrarla a fine redraw per evitare fastidiosi sfarfallamenti. La cosa simpatica è che ancora una volta GDI+ ci viene in aiuto e ci regala le cosidette "CachedBitmap" che implementano di sistema questa tecnica. Comodo! :-) ecco lo spezzone di codice della "OnPaint" la routine responsabile del disegno a video richiesto quando l'applicazione riceve un messaggio WM_PAINT:

 

LRESULT OnPaint(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{

HDC hdc;
RECT rect;
PAINTSTRUCT ps;

// recupero la grandezza della ClientArea della finestra

GetClientRect(hWnd,&rect);

int nWidth = rect.right - rect.left + 1;
int nHeight = rect.bottom - rect.top + 1;

// Ottengo l'handler al contesto del dispositivo, voglio iniziare il Paint

hdc = BeginPaint(hWnd,&ps);

// creo un nuovo oggetto grafico

Graphics graphics(hdc);

// memory bitmap grande quanto la client area della finestra
Bitmap* pMemBitmap = new Bitmap(nWidth, nHeight);

// offscreen graphics (per il doublebuffering)
Graphics* pMemGraphics = graphics.FromImage(pMemBitmap);

//Image image(L"Bimbo.jpg"); disegno il gatto a video :-)
pMemGraphics->DrawImage(image, 0, 0);

//Preparo la cached bitmap per la copia a video

CachedBitmap cBitmap(pMemBitmap, &graphics);

// Disegno la bitmap in memoria sul video

graphics.DrawCachedBitmap(&cBitmap, 0, 0);
 

// Elimino gli oggetti che ho appena usato
delete pMemGraphics;
delete pMemBitmap;
 

// Restituisco la palla, ho finito di disegnare
EndPaint(hWnd, &ps);

return 0;

}

 

Che ci crediate oppure no, questo sistema è veramente veloce nel refresh a video, e come potete vedere anche abbastanza sbrigativo nella stesura del codice. Nel programma Allegato vedrete che la pMemGraphics->DrawImage(image, 0, 0); è rimpiazzata dalla chiamata ad una mia funzione chiamata "Draw" che disegna tutto quello che c'è da disegnare, grafica, testo e ammennicoli vari :-)

Certo il programma è passibile di tante migliorie, si potrebbe evitare di creare e cancellare ogni volta gli oggetto pMemGraphics e pMemBitmap e limando quà e la si può ottenere ancora maggiore velocità di redraw, ma tanto per quello che volevo far vedere non è il massimo dell'importanza. Anche i Font una volta creati e inizializzati sono direttamente "sparabili" sul video senza tante beghe, il metodo è il seguente:

 

// coordinate di dove iniziare a scrivere (sono float)

PointF pointF(10.0f, 10.0f);

 

// Definisco un colore per il brush con cui voglio scrivere

SolidBrush solidBrush(Color(255, 255, 255));

 

// Scrivo il contenuto della stringa szHello

pMemGraphics->DrawString(szHello, -1, font, pointF, &solidBrush);

 

La cosa comoda è che si può cambiare stile del Font in un battibaleno, senza doversi preoccupare più di tanto perché la GDI+ penserà a fare tutto il lavoro per noi. Nel sorgente allegato a questo Tutorial troverete anche la breve routine che permette di salvare l'immagine appena caricata in un qualsiasi formato supportato da GDI+, nel mio caso dopo aver caricato l'immagine in formato JPEG la risalverò in formato PNG (Portable Network Graphics). Qui le cose si complicano (!) un po' visto che è necessario specificare il formato con cui andremo a salvare l'immagine. Ogni "saver" gestito dalla libreria ha un suo CLSID, un numero che lo identifica univocamente, noi dovremo prendere questo numero e passarlo alla funzione che salva l'immagine per fargli capire quale formato generare in output. La cosa simpatica è che i formati sono modulari e che in teoria sarebbe possibile incrementare il numero di formati gestiti dalla GDI+. Dico in Teoria perché in pratica non so come si fa, e adesso non ho voglia di spulciarmi il Platform perché una volta finito questo tut voglio mettermi a studiare come funziona un driver WDM e i vari sources del DDK :)

 

//Prendo il CLSID dell'encoder PNG (funzione gdi+)...

GetEncoderClsid(L"image/png", &encoderClsid);
 

//....e salvo l'immagine in formato PNG, la variabile "stato" se <> 0 mi indica eventuali errori

stato = image->Save(L"Bimbo.png", &encoderClsid, NULL);

 

Con queste poche cose e con un minimo di conoscenza delle altre funzioni grafiche messe a disposizione da questa libreria si possono fare cosette veramente interessanti in un tempo decisamente breve, unico neo è che funzionerà solo su XP o superiori o su 2k una volta installato l'upgrade per la GDI+ fornito da Microsoft nei suoi service pack.

 

                                                Misuriamo il tempo....senza perdere tempo!

 

Per misurare la velocità di redraw di questo (stupendo) programma ho messo mano a un paio di funzionicine ch potrebbero far comodo nel caso ci sia da misurare intervalli di tempo che impiegano millisecondi, e quindi difficilmente rilevabili da un normale timer di Win. Tali funzioni sono state implementate a partire da WinXP, quindi funzioneranno solo da XP in poi, e oltretutto utilizzano variabili del tipo a 64bit cioè LARGE_INTEGER, come le definisce il nostro bel VisualC++.

 

// definisco le variabili a 64bit per misurare i millisecondi

// LARGE_INTEGER è una union di due variabili double

LARGE_INTEGER TIME1,TIME2,FREQ;
double msecs;

 

//Converte in msecs il counter
QueryPerformanceFrequency(&FREQ), FREQ.QuadPart /= 1000;

 

E a questo punto possiamo includere il codice da misurare tra queste due chiamate:

 

//faccio partire il cronometro....

QueryPerformanceCounter(&TIME1);
 

//Routine da misurare

OnPaint(hWnd, message, wParam, lParam);

//....fermo il cronometro

QueryPerformanceCounter(&TIME2);
 

//Calcolo l'equivalente in millisecondi del tempo che è passato

//semplice sottrazione dei due tempi misurati, ma a 64bit !

msecs=((double)TIME2.QuadPart-(double)TIME1.QuadPart)/FREQ.QuadPart;
 

Due banali funzioni, ma che permettono di rilevare tempi molto molto brevi, non si sa mai....potrebbe servire! :-)

 

Bene adesso non vi stresso proprio più, vi rimando alla lettura del sorgente che ho cercato di mantenere più chiaro possibile e anche più commentato possibile, se c'è qualcosa che non capite chiedete pure a qualcun'altro e unica cosa che vi chiedo è di diffondere più possibile l'immagine del mio gatto :-)

Grazie a tutti

 Ciao

  MrCODE

 

 

Note finali

Spero vi sia piaciuto e che in qualche modo quello che qui ho riportato possa tornarvi utile, Io vi dico una cosa: a scoprire tutte queste cosette mi ci sono divertito un casino!! Qui devo obbligatoriamente ringraziare AndreaGeddon, che non solo mi ha spronato a fare questa cosa, ma mi ha anche aiutato quando le mie "virtute e canoscenze" venivano meno. Un saluto a tutti i ragazzi di #crack-it, al mitico ntoskrnl, a tutti i "toscanacci" del chan e a quelli che conosco da tanto tempo. Un in bocca al lupo va a _Ph0b0s_ con la speranza di rivederlo al più presto in chan più "produttivo" che mai. Due pedate nel sedere invece vanno a TheMr, che la deve smettere di star dietro alle moto e tornare sul pc a reversare. Vorrei fare una menzione speciale anche a personaggi che in tutti questi anni mi hanno dato veramente molto dal punto di vista informatico, come Yado, Kill3x, Xoanon e i RingZer0, ma onorare i padri fondatori è superfluo, le basi le hanno gettate loro. Una menzione al merito al buon S1m0 che si sta impegnando molto e quindi solo per questo è già a metà dell'opera, e che è una delle persone più disponibili del canale e a Zairon che è una persona davvero in gamba. Un ringraziamento grande grande va a Elisabetta, solo per il semplice motivo che riesce a sopportarmi (non è poco =). Se mi sono scordato qualcuno, un saluto grande grande anche a lui!:))

Disclaimer

Disclaimer di cosa? Ci starebbe bene qualcosa tipo "se 'un ti piace 'osa l'hai letto a fà?" oppure "ma voi fà questioni proprio 'on me?...ma vai ner culo vai...." ma son troppo educato e non sta bene. Queste cose non ledono il copyright di nessuno, ti prendi un tool freeware, il tuo cervello e inizi a studiare e capire, quindi tu GrandeFratello che vedi tutto e controlli tutto, non rompere le scatole e vai a beccare i disonesti, ma quelli veri!!