Nouveaux concepts : Librairies et liaison
|
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. 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 : 1. Le répertoire courant 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). 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 : 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" : Enregistrez ensuite le projet comme d'habitude et collez le code depuis DLLskeleton.asm : 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" : Double-cliquez sur le fichier DEF et il devrait apparaître dans la barre d'exploration du projet : 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 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 : La déclaration LIBRARY n'est en fait pas nécessaire - notre projet sera toujours construit en tant que DLL sans elle. 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 : Dans le prochain tutoriel, nous examinerons différentes méthodes d'appel des fonctions stockées dans une DLL.
|
Copyright (C)- xtx Team (2021)