Zoom Icon

Tutorial 17: Le Dynamic Link Libraries

From UIC

Tutorial 17: Le Dynamic Link Libraries

Contents


Tutorial 17: Le Dynamic Link Libraries
Author: Iczelion
Email: Translate: -NeuRaL_NoiSE
Website: Mirror
Date: 20/01/2009 (dd/mm/yyyy)
Level: Luck and skills are required
Language: Italian Flag Italian.gif
Comments: Formattazione Wiki: Antelox


Introduzione

In questo tutorial, impareremo cosa sono le DLLs, e come crearle.


Tools

Esempio


Preliminari

Se programmate per un periodo abbastanza lungo, vi renderete conto che i programmi che scrivete hanno alcune routines di codice in comune. E' un enorme spreco di tempo riscriverle ogni volta che cominciate un nuovo programma. Ai tempi del vecchio DOS, i programmatori conservavano le routines usate piu' comunemente in una o piu' librerie. Quando volevano usare le funzioni, non dovevano far altro che linkare le librerie all'object file e il linker estraeva le funzioni dalle librerie e le inseriva nel file eseguibile finale. Questo processo e' chiamato "static linking". Le C runtime libraries sono un buon esempio. Il rovescio della medaglia di questo metodo e' che dovete avere funzioni identiche in ogni programma che le chiama. Lo spazio sul disco e' sprecato nel conservare diverse copie uguali delle funzioni. Ma, per i programmi DOS, questo metodo e' abbastanza accettabile poiche' c'e' solitamente un solo programma attivo in memoria. Percio' non c'e' nessuno spreco di preziosa memoria.Sotto Windows, la situazione diventa molto piu' critica perche' potete avere diverse copie del programma in esecuzione simultanea. La memoria sara' mangiata molto velocemente se il vostro programma e' abbastanza grande. Windows ha una soluzione per questo tipo di problema: le dynamic link libraries. Una dynamic link library e' una specie di "raccolta" di funzioni comuni. Windows non carichera' diverse copie di una DLL in memoria, percio' anche se ci sono diverse copie del vostro programma in esecuzione simultanea, in memoria ci sara' sempre solo una copia della DLL che il programma usa. Il programma si linka ad una DLL durante l'esecuzione (runtime) a differenza delle vecchie static libraries. Ecco perche' le librerie vengono chiamate dynamic link libraries. Potete anche scaricare dalla memoria una DLL durante l'esecuzione quando non ne avete bisogno. Se quel programma e' l'unico che usa quella libreria, questa sara' scaricata immediatamente dalla memoria. Ma se la DLL e' ancora usata da qualche altro programma, essa rimarra' in memoria finche' l'ultimo programma che se ne serve non la scarica. Comunque, il linker ha un lavoro piu' difficile da compiere quando esegue gli address fixups (correzione degli indirizzi, NdT) per il file eseguibile finale. Poiche' non puo' "estrarre" le funzioni e inserirle nel file eseguibile finale, in qualche modo vi deve inserire abbastanza informazioni riguardanti la DLL e funzioni che gli permettano di localizzare e caricare la corretta DLL durante l'esecuzione. E' qui che entra in gioco la import library. Una import library contiene le informazioni riguardanti la DLL che rappresenta. Il linker puo' estrarre le informazioni di cui ha bisogno dalle import libraries e inserirle nel file eseguibile. Quando il loader di Windows carica il programma in memoria, vede che quest'ultimo si linka ad una DLL, e percio' cerca questa DLL e la carica nell'address space del processo ed esegue gli address fixups per le chiamate alle funzioni nella DLL. Potete scegliere di caricare voi stessi la DLL senza basarvi sul loader di Windows. Questo metodo ha i suoi pro e i suoi contro:

  • Non necessita di una import library, percio' potete caricare ed usare qualsiasi DLL anche se non c'e' una relativa import library. Comunque, dovete sempre conoscere le funzioni al suo interno, quanti parametri richiedono e cosi' via.
  • Quando incaricate il loader di caricare la DLL per il vostro programma, se il loader non riesce a trovare la DLL vi riportera' "Una file .DLL necessario al programma, xxxxx.dll e' mancante" e puf! Il vostro programma non potra' mai partire anche se quella DLL non e' essenziale per le sue operazioni. Se caricate la DLL manualmente, invece, quando la DLL non viene trovata e non e' essenziale alle operazioni, il vostro programma puo' semplicemente comunicare la cosa all'utente e continuare con l'esecuzione.
  • Potete chiamare funzioni *non documentate* che non sono incluse nelle import libraries. Ammesso ovviamente che abbiate alcune informazioni riguardo a tali funzioni.
  • Se usate LoadLibrary, dovete chiamare GetProcAddress per ciascuna funzione che volete chiamare. GetProcAddress trova l'entrypoint address di una funzione in una particolare DLL. Percio' il vostro codice potrebbe essere un po' piu' ingombrante e lento, ma non di molto.


Essay

Visti i vantaggi/svantaggi della chiamata a LoadLibrary, entriamo ora nei dettagli di come creare una DLL. Il codice che segue e' lo scheletro di una DLL.

;--------------------------------------------------------------------------------------------------------
;                                         DLLSkeleton.asm
;--------------------------------------------------------------------------------------------------------
include windows.inc
includelib user32.lib
includelib kernel32.lib

.data
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved:DWORD
        mov  eax,TRUE
        ret
DllEntry Endp
;--------------------------------------------------------------------------------------------------------
;                                         Questa e' una funzione inutile
; Non esegue nessuna azione. L'ho messa qui per mostrarvi dove potete inserire le funzioni
; in una DLL.
;--------------------------------------------------------------------------------------------------------
TestFunction proc
    ret
TestFunction endp

End DllEntry

;--------------------------------------------------------------------------------------------------------
;                                          DLLSkeleton.def
;--------------------------------------------------------------------------------------------------------
LIBRARY   DLLSkeleton
EXPORTS   TestFunction

Il programma precedente e' lo scheletro della DLL. Ogni DLL deve avere una funzione di entrypoint. Windows chiamera' la funzione di entrypoint ogni volta che:

  • La DLL e' caricata per la prima volta
  • La DLL e' scaricata dalla memoria
  • Un thread viene creato nello stesso processo
  • Un thread viene distrutto stesso processo
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved:DWORD
        mov  eax,TRUE
        ret
DllEntry Endp

Potete chiamare la funzione di entrypoint in qualsiasi modo, basta che poi inserite un relativo END <Nome della funzione di Entrypoint>. Questa funzione prende tre parametri, dei quali solo i primi due sono importanti.

hInstDLL e' il module handle della DLL. Non e' lo stesso dell'instance handle del processo.

reason puo' essere uno dei seguenti quattro valori:

  • DLL_PROCESS_ATTACH La DLL riceve questo valore quando e' "iniettata" per la prima volta nell'address space del processo. Potete sfruttare questa opportunita' per inzializzare quello che vi serve.
  • DLL_PROCESS_DETACH La DLL riceve questo valore quando viene scaricata dall'address space del processo. Potete usare questa opportunita' per eseguire un po' di "pulizia" come la deallocazione della memoria e cosi' via.
  • DLL_THREAD_ATTACH La DLL riceve questo valore quando il processo crea un nuovo thread.
  • DLL_THREAD_DETACH La DLL riceve questo valore quando un thread nel processo viene distrutto.

Voi restituite TRUE in eax se volete che la DLL continui nell'esecuzione. Se restituite FALSE, la DLL non verra' caricata. Ad esempio, se il vostro codice di inizializzazione deve allocare della memoria e non riesce a farlo con successo, la funzione di entrypoint dovrebbe restituire FALSE per indicare che la DLL non puo' essere eseguita. Potete inserire le vostre funzioni nella DLL prima o dopo la funzione di entrypoint. Ma se volete che esse siano "chiamabili" dagli altri programmi, dovete metterne i nomi nella lista di export nel file di definizione di modulo (module definition file, .def). Una DLL ha bisogno di un file di definizione di modulo nella sua fase di sviluppo. Vi daremo un'occhiata adesso.

LIBRARY DLLSkeleton
EXPORTS TestFunction

Normalmente dovete inserire la prima linea. Lo statement LIBRARY definisce il nome del modulo interno della DLL. Dovreste farlo combaciare con il nome del file della DLL. Lo statement EXPORTS dice al linker quali funzioni della DLL vengono esportate, cioe' saranno "chiamabili" da altri programmi. Nell'esempio, vogliamo che altri programmi possano chiamare la funzione TestFunction, percio' ne inseriamo il nome dopo lo statement EXPORTS. Un altro cambiamento e' lo switch del linker. Dovete usare lo switch /'DLL e /DEF:<il vostro file def> tra gli switches del linker, come in questo modo:

link /DLL /SUBSYSTEM:WINDOWS /DEF:DLLSkeleton.def /LIBPATH:c:\masm\lib DLLSkeleton.obj

Gli switches dell'assembler sono gli stessi, cioe' /c /coff /Cp. Percio' dopo che avete linkato l'object file, otterrete .dll e .lib. Il file .lib e' la import library che potete usare per linkare altri programmi che usano le funzioni nella DLL. Adesso vi mostrero' come usare LoadLibrary per caricare una DLL.

;--------------------------------------------------------------------------------------------------------
;                                               UseDLL.asm
;--------------------------------------------------------------------------------------------------------
include windows.inc
includelib kernel32.lib
includelib user32.lib

.data
LibName db "DLLSkeleton.dll",0
FunctionName db "TestHello",0
DllNotFound db "Cannot load library",0
AppName db "Load Library",0
FunctionNotFound db "TestHello function not found",0

.data?
hLib dd ?                                         ; l'handle della libreria (DLL)
TestHelloAddr dd ?                        ; l'indirizzo della funzione TestHello

.code
start:
        invoke LoadLibrary,addr LibName
;--------------------------------------------------------------------------------------------------------
; Chiamate LoadLibrary con il nome della DLL desiderata. Se la chiamata ha
; successo, restituira' l'handle della libreria (DLL). Se no, restituira' NULL.
; Potete passare l'handle della libreria a GetProcAddress o a qualsiasi funzione
; che richiede l'handle di una libreria come parametro.
;--------------------------------------------------------------------------------------------------------
        .if eax==NULL
                invoke MessageBox,NULL,addr DllNotFound,addr AppName,MB_OK
        .else
                mov hLib,eax
                invoke GetProcAddress,hLib,addr FunctionName
;--------------------------------------------------------------------------------------------------------
; Quando ottenete l'handle della libreria, lo passate a GetProcAddress con l'indirizzo
; del nome della funzione che volete chiamare in quella DLL. Questa restituisce
; l'indirizzo della funzione, se ha successo. Altrimenti, restituisce NULL.
; Gli indirizzi delle funzioni non cambiano a meno che voi non scarichiate e ricarichiate
; la libreria. Pertanto, potete inserirli in variabili globali per uso futuro.
;--------------------------------------------------------------------------------------------------------
                .if eax==NULL
                        invoke MessageBox,NULL,addr FunctionNotFound,addr AppName,MB_OK
                .else
                        mov TestHelloAddr,eax
                        call [TestHelloAddr]
;--------------------------------------------------------------------------------------------------------
; Successivamente, potete chiamare la funzione con una semplice call con la variabile
; contenente l'indirizzo della funzione come operando.
;--------------------------------------------------------------------------------------------------------
                .endif
                invoke FreeLibrary,hLib
;--------------------------------------------------------------------------------------------------------
; Quando non avete piu' bisogno della libreria, scaricatela con FreeLibrary.
;--------------------------------------------------------------------------------------------------------
        .endif
        invoke ExitProcess,NULL
end start

Percio' potete osservare che l'utilizzo di LoadLibrary e' un po' piu' impegnativo ma e' anche piu' flessibile.


Disclaimer

I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.

Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevole e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.