Squelette du code d'une DLL

Nouveaux concepts :

Librairies et liaison
La procédure EntryPoint des DLL
Code squelette pour créer des DLL
Utilisation d'un fichier DEF pour l'exportation de fonctions

 


Les DLL (Dynamic Link Libraries) ont plusieurs utilisations importantes en Reverse Engineering. Tout d'abord, il est utile de pouvoir écrire des plugins pour vos outils de reverse préférés tels que ImpREC, Olly et PEID, ou même pour votre IDE préféré. Les plugins prennent généralement la forme d'une DLL utilisée par l'application. Une utilisation plus importante est l'injection de DLL, grâce à laquelle une DLL peut être forcée à se charger dans l'espace d'adressage d'un processus cible. Comme nous l'avons déjà appris, une fois chargée, elle peut accéder à toute la mémoire du processus et contrôler l'ensemble de l'application. Un bon exemple d'utilisation est les articles sur l'unpacking avec des traceurs de NCR. C'est un sujet avancé, donc dans ce tutoriel, nous commencerons par apprendre les bases des DLL et examinerons un code squelette pour en écrire une.

Les portions de code ou les fonctions fréquemment utilisées sont souvent stockées sous forme compilée en dehors des exécutables qui les utilisent, dans des modules appelés bibliothèques. Il existe deux principaux types :

bibliothèques statiques

Celles-ci sont principalement utilisées pendant le développement du programme. Dans un processus appelé liaison statique, le linker copie les fonctions requises de la bibliothèque (qui a généralement une extension .LIB) directement dans d'autres modules pour former le fichier exécutable final. Les bibliothèques d'exécution C en sont un bon exemple. L'inconvénient est que les exécutables utilisant les mêmes fonctions contiendront du code dupliqué et consommeront donc plus de mémoire vive (RAM).


Dynamic Link Libraries

Ces bibliothèques sont utilisées pendant l'exécution du programme. Dans ce cas, les fonctions de la bibliothèque ne sont pas intégrées au fichier .EXE à aucun moment. Au lieu de cela, elles résident dans un module séparé sous forme exécutable (suivant le format de fichier PE) appelé bibliothèque de liens dynamiques (Dynamic Link Library - DLL). Cette bibliothèque contient des fonctions et des données pouvant être utilisées par un ou plusieurs autres modules ou "clients". Lorsque le premier client a besoin d'une DLL, Windows charge la DLL dans l'espace d'adresse virtuelle du client afin que ce dernier puisse accéder aux fonctions dont il a besoin.

Les processus multiples qui chargent la même DLL à la même adresse de base partagent une seule copie de la DLL en mémoire physique. Cela permet d'économiser de la mémoire système et de réduire le swapping. Tous les processus qui utilisent la même DLL partagent son code, mais disposent de leur propre copie unique de sa section de données.

Windows propose deux méthodes pour lier dynamiquement une DLL : le liage (linking) au moment du chargement (implicit) et le liage au moment de l'exécution (explicit). La différence réside du côté du client ; la DLL est la même dans les deux cas.

Liage au moment du chargement

Windows charge automatiquement la DLL avec le premier programme client et écrit les adresses des fonctions requises dans la table d'importation du client avant le début de son exécution. C'est simple et ne nécessite aucun code supplémentaire, mais pour l'utiliser, l'application cliente doit être liée à une bibliothèque d'importation (.LIB) pour la DLL en utilisant l'instruction includelib.

Lorsque le module client est en cours de construction, le linker effectue les ajustements d'adresse pour le fichier exécutable final, mais dans le cas des fonctions qui résident dans une DLL externe, le linker ne connaît pas les adresses au moment de la construction. Dans ce cas, le linker utilise une bibliothèque d'importation pour la DLL (confusément appelée également un fichier .LIB). Une bibliothèque d'importation ne contient aucun code exécutable, seulement des noms et des emplacements des fonctions exportées par la DLL. Le linker utilise les emplacements dans la bibliothèque d'importation pour résoudre les références aux fonctions externes de la DLL dans le client.

En général, le chargement implicite est préférable lorsque le client a toujours besoin d'au moins une procédure de la DLL, car Windows charge automatiquement la DLL avec le client. Un inconvénient de cette méthode est que si une DLL requise est manquante, Windows refusera d'exécuter votre application ! Si vous chargez la DLL explicitement comme dans la méthode suivante, alors lorsque la DLL non essentielle ne peut pas être trouvée, votre programme peut simplement informer l'utilisateur et continuer l'exécution.

Chargement au moment de l'exécution

Windows ne charge pas la DLL tant que le premier client ne la demande pas explicitement pendant l'exécution. Pour cela, le client utilise les fonctions API suivantes :

LoadLibrary - pour charger la DLL en mémoire
GetProcAddress - pour obtenir l'adresse de chaque fonction requise
FreeLibrary - pour décharger la DLL lorsque plus aucune fonction n'est requise

Bien que cela nécessite plus d'efforts de programmation dans le client, cela permet de contrôler quelles DLL (le cas échéant) se chargent au moment de l'exécution. Un fichier LIB pour la DLL n'est pas nécessaire, bien que vous deviez toujours connaître le nombre de paramètres des fonctions qu'elle contient, etc. De plus, vous pouvez appeler des fonctions non documentées qui ne sont pas détaillées dans les bibliothèques d'importation et qui sont souvent exportées uniquement par ordre.

Pour charger une DLL, qu'il s'agisse d'un chargement implicite ou explicite, Windows recherche le fichier DLL dans les répertoires suivants, dans l'ordre indiqué :

1. Le répertoire courant
2. Le répertoire Windows, qui contient WIN.COM
3. Le répertoire système Windows, qui contient des fichiers système tels que GDI.EXE
4. Le répertoire où se trouve le programme client
5. Les répertoires répertoriés dans la chaîne d'environnement PATH
6. Les répertoires mappés dans un réseau

Si Windows ne trouve pas la DLL dans l'un de ces répertoires, il affiche une boîte de dialogue à l'utilisateur.

Code DLL

Le code d'une DLL est composé de fonctions exportées et non exportées. Les fonctions exportées sont des routines publiques servant les clients, tandis que les fonctions non exportées fournissent un support interne privé aux procédures exportées et ne sont pas visibles par un client. Les fonctions exportées doivent être répertoriées dans la section EXPORTS d'un fichier de définition de module (.DEF) - un fichier texte contenant des informations utilisées par le linker. Nous en parlerons bientôt.

La fonction DLLEntryPoint

Une DLL a généralement une fonction point d'entrée comme indiqué ci-dessous (cela n'a pas besoin d'être appelé DLLEntryPoint, cela peut s'appeler n'importe quoi, par exemple LibMain est couramment utilisé). Le système appelle la fonction de point d'entrée chaque fois qu'un processus ou un thread charge ou décharge la DLL. Elle peut être utilisée pour effectuer des tâches d'initialisation ou de nettoyage simples.

Le système appelle la fonction de point d'entrée dans le contexte du processus ou du thread qui a provoqué l'appel de la fonction. Cela permet à une DLL d'utiliser sa fonction de point d'entrée pour allouer de la mémoire dans l'espace d'adressage virtuel du processus appelant ou pour ouvrir des handles accessibles au processus. La fonction de point d'entrée peut également allouer de la mémoire qui est privée à un nouveau thread en utilisant le stockage local au thread (TLS).

Une DLLEntryPoint typique ressemble à ceci (à partir de l'aide de Win32) :


1


En d'autres termes, lorsque Windows appelle votre DLLEntryPoint, il transmet 3 paramètres mais le 3ème n'est pas utilisé :

hInstDLL est le gestionnaire de module de la DLL. Ce n'est pas le même que le gestionnaire d'instance du processus et il est utile, par exemple, si votre DLL contient des ressources car celles-ci sont accessibles par le gestionnaire de la DLL. Vous devez conserver cette valeur si vous avez besoin de l'utiliser ultérieurement car vous ne pouvez pas l'obtenir à nouveau facilement. La valeur ne change pas à moins que la DLL ne soit déchargée et rechargée, il est donc sûr de la stocker dans une variable globale.

fdwReason peut prendre l'une des quatre valeurs :

DLL_PROCESS_ATTACH - la DLL reçoit cette valeur lorsqu'elle est injectée pour la première fois dans l'espace d'adressage du processus. Vous pouvez profiter de cette occasion pour effectuer une initialisation.

DLL_PROCESS_DETACH - la DLL reçoit cette valeur lorsqu'elle est déchargée de l'espace d'adressage du processus. Vous pouvez profiter de cette occasion pour effectuer un nettoyage tel que la désallocation de mémoire.

DLL_THREAD_ATTACH - la DLL reçoit cette valeur lorsque le processus crée un nouveau thread.

DLL_THREAD_DETACH - la DLL reçoit cette valeur lorsqu'un thread du processus est détruit.

Vous devez renvoyer TRUE dans eax si vous souhaitez que la DLL continue de s'exécuter. Si vous renvoyez FALSE, la DLL ne sera pas chargée. Par exemple, si votre code d'initialisation doit allouer de la mémoire et qu'il ne peut pas le faire avec succès, la fonction de point d'entrée doit renvoyer FALSE pour indiquer que la DLL ne peut pas s'exécuter.

Pour les processus utilisant le chargement au moment de l'exécution, une valeur de retour FALSE entraîne l'échec de l'initialisation du processus et la terminaison du processus comme nous l'avons mentionné ci-dessus. Pour les processus utilisant le chargement au moment de l'exécution, une valeur de retour FALSE entraîne le renvoi NULL de la fonction LoadLibrary ou LoadLibraryEx, indiquant un échec, et le système décharge simplement la DLL.

Autres fonctions

Vous pouvez placer vos fonctions n'importe où dans la DLL, mais si vous voulez qu'elles soient accessibles à d'autres modules, vous devez mettre leurs noms dans la liste des exportations d'un fichier DEF, comme nous l'avons mentionné précédemment. Ensuite, nous examinerons un code minimaliste pour une DLL et son fichier DEF.

 


Le fichier ASM

Créez un nouveau projet WinAsm vide, mais cette fois choisissez "DLL standard" :



1

Enregistrez ensuite le projet comme d'habitude et collez le code depuis DLLskeleton.asm :



1

Si le proc DLLEntryPoint doit être appelé une seule fois lors du chargement de la DLL, les conditions gérant les autres situations peuvent être omises et true peut être renvoyé immédiatement. Cela semble beaucoup plus simple - voir le prochain tutoriel pour un exemple.

Remarque : le soulignement initial pour les noms de nos fonctions exportées n'est pas nécessaire, mais de nombreux programmeurs l'ajoutent pour distinguer leurs propres fonctions des API.

 

Le fichier DEF

Ensuite, créez un fichier texte vide dans le dossier du projet et renommez-le DLLskeleton.def. Revenez à WinAsm et cliquez sur le bouton "Add Files" :


1

Double-cliquez sur le fichier DEF et il devrait apparaître dans la barre d'exploration du projet :


1

Maintenant, WinAsm modifiera correctement les options du linker pour l'utiliser et vous pourrez l'éditer directement dans la fenêtre de code. Ajoutez les lignes suivantes :

LIBRARY   DLLskeleton 
EXPORTS  _ExpFunc1   @   01  
               _ExpFunc2   @   02
               _ExpFunc3   @   03

Les mots LIBRARY et EXPORTS sont des déclarations de définition de module. Il en existe beaucoup détaillées dans la documentation masm61, mais ce sont les seules que nous devons connaître :


1

La déclaration LIBRARY n'est en fait pas nécessaire - notre projet sera toujours construit en tant que DLL sans elle.


1

Veuillez noter le mot-clé NONAME ci-dessus pour exporter des fonctions par ordre seulement. Nous l'utiliserons dans le prochain tutoriel. Pour plus d'informations sur les exports et les ordinaux, consultez mon tutoriel PE. Notez que les commentaires et les fichiers d'inclusion ont la même syntaxe dans les fichiers DEF que dans les fichiers ASM.

En cliquant sur "Go All", vous obtiendrez non seulement la DLL, mais également un fichier LIB que vous pouvez utiliser pour accéder à vos fonctions par liaison au chargement :


1

Dans le prochain tutoriel, nous examinerons différentes méthodes d'appel des fonctions stockées dans une DLL.

 


Copyright (C)- xtx Team (2021)

XHTML valide 1.1 CSS Valide !