Intro
Dans le cadre du Groupe de Travail sur les protections Anti-Sice, j'ai eu l'occasion d'étudier la protection de ZipStudio. Ce programme contenait une protection Anti-Sice de type 07 ( INT 68 ) contenue dans une DLL ( ZSTUDIO.DLL ). Notre problème, maintenant résolu, était que la détection de Softice se faisait avant même l'exécution de la première instruction du .EXE. Nous nous sommes alors demandé comment, pourquoi et quand une DLL peut être exécutée avant même l'exécution de programme. J'espère amener ici un élément de réponse majeur. Un grand merci à Christal.Qu'est-ce qu'une DLL ?
Sous Windows, les bibliothèques de liens dynamiques ( dynamic-link libraries DLL ) sont des modules qui contiennent des FONCTIONS et du DATA. Une DLL est chargée lors de l'exécution de son module appelant ( .EXE ou une autre DLL ). Quand une DLL est chargée, elle est mapée dans l'espace des adresses du processus appelant.
Rem : Processus appelant et module appelant sont les .EXE ou .DLL qui utilisent les FONCTIONS exportées.
Les DLLs peuvent définir deux sortes de FONCTIONS: Les FONCTIONS exportées et internes.
o Les FONCTIONS exportées peuvent être appelées par un autres modules3
o Les FONCTIONS internes peuvent par contre n'être appelées que par la DLL elle-même.Aussi, les DLLs peuvent exporter du DATA, mais il est plus généralement utilisé par les FONCTIONS de la DLL.
Rem : J'ai déjà eu l'occasion de voir une DLL qui ne possèdait pratiquement que des Ressources. Le .EXE utilisant les Ressources de la DLL. Ainsi, tout les Référencement sous WDASM sont inutiles.
Les DLLs donnent un moyen de moduler les applications pour qu'une fonctionnalité puisse être améliorée et réutilisée plus facilement. Elles aident aussi à réduire la mémoire utilisée lorsque plusieurs applications utilisent la même fonction au même moment, car elles se partagent le code.
L'interface de programmation des applications Win32 ( Application Programming Interface API ) est implémentée sous forme de DLLs, ainsi tous les processus utilisant les API Win32 utilisent les liens dynamiques.Qu'est-ce qu'il y a à l'Entry Point d'une DLL ?
Dans les DLLs, il existe une fonction qui commence à l'EntryPoint de la DLL. Cette FONCTION est appelée par Windows lors de l'exécution d'une application Win16-32. Cette FONCTION que Microsoft a appelée DLLEntryPoint nécessite 3 paramètres :
BOOL WINAPI DllEntryPoint(
HINSTANCE hinstDLL, // handle to DLL module
DWORD fdwReason, // reason for calling function
LPVOID lpvReserved // reserved
);1) Le premier paramètre : hinstDLL
Il ne nécessite pas beaucoup d'explications, c'est juste le Handle de la DLL.
Mais nous pouvons faire quelques remarques :o Ce Handle à la valeur de l'adresse de base de la DLL.
o Et cette valeur est exactement la même que le HMODULE de la DLL.
2) Le second paramètre : fdwReason
Ce deuxième paramètre est beaucoups plus intéréssant pour comprendre comment, quand et pourquoi cette FONCTION DllEntryPoint est exécutée.1er Valeur de fdwReason possible : DLL_PROCESS_ATTACH
Cette valeur indique à la FONCTION que la DLL est en train d'être attachée à l'espace des adresses du processus courant. dfwReason possède cette valeur dans 2 cas :
(a) Soit lorsque le processus démarre ( avant l'exécution de la premiere instruction du processus ( .EXE généralement ) appelant )
(b) Soit lors de l'appel à la FONCTION LoadLibraryRem : LoadLibrary est une FONCTION et elle sera donc appelée lorsque le Processus sera en cours d'exécution. Nous ce qui nous intéresse ce n'est donc pas le point (b) mais bien le point (a) !
Les autres valeurs de fdwReason possibles sont
o DLL_PROCESS_DETACH ( appelé soit par une FONCTION Exit ou par FreeLibrary )
o DLL_THREAD_ATTACH
o DLL_THREAD_DETACHPS : je ne donnerai pas d'explications sur ces différentes valeurs étant donné que leur
nom est assez évocateur.
3) Le dernier paramètre : lpwReserved
Ce paramètre spécifie un peu plus l'initialisation ou la "libération" de la DLL.o Si fdwReason vaut DLL_PROCESS_ATTACH :
lpwReserved est NULL pour un chargement dynamique
et non NULL pour un chargement statiqueo Si fdwReason vaut DLL_PROCESS_DETACH :
lpwReserved est NULL si DllEntryPoint est appelée en utilisant FreeLibrary
et non NULL si DllEntryPoint est appelée car c'est la fin du ProcessRem : Nous savons maintenant que c'est la fonction DllEntryPoint de ZSTUDIO.DLL qui exécute l'Interruption 0x68. Mais nous ne savons toujours pas pourquoi cette DLL n'est pas chargée par LoadLibrary... Continuons !
Mais pourquoi la DLL se charge avant le Processus ?
Pourquoi celui-ci n'utilise pas LoadLibrary pour charger la DLL ?Evidemment, c'est la question qu'on peut dès lors se poser. Nous savons que quand une DLL est chargée, elle est mapée dans l'espace des adresses du processus appelant. Je vous avoue que cela n'est pas plus clair pour vous que pour moi. Allons éclaircir tout ca! Mais pour cela, nous allons devoir faire un petit détour vers la structure et le fonctionnement d'un fichier PE.
Dans le Optional Header d'un fichier PE, il y a 16 bits ( DllCharacteristics ) utilisé pour avertir si le fichier est une DLL et, théoriquement, quand DllEntryPoint doit être appelée mais cela ne semble pas être utilisé par Windows et la Dll est toujours avertie de tous :o Si le bit 0 vaut 1, la DLL est avertie de l'attachement à un Processus ( par exemple lors du chargement d'une DLL ).
o Si le bit 1 vaut 1, la DLL est avertie du dé-attachement à un thread ( par exemple lorsque qu'un thread est terminé).
o Si le bit 2 vaut 1, la DLL est avertie de l'attachement à un thread ( par exemple lors de la création d'un thread ).
o Si le bit 3 vaut 1, la DLL est avertie du dé-attachement à un Processus ( par exemple lors du 'déchargement' d'une DLL ).Cela était juste pour information, si on repense maintenant à la source : quand le compilateur trouve un CALL d'une FONCTION qui se trouve dans un autre Module ( DLL, VxD, etc.. ) il va, dans le plus simple des cas, écrire une instruction CALL normale. Ce CALL pointera vers une instruction JUMP propre a chaque FONCTION. Ce JUMP sautera lui-même vers l'adresse de la FONCTION importée en utilisant l'Import Table.
L'Import Address Table ( IAT ) se situe à la fin de la section contenant le Code Exécutable du programme. Cette IAT est constitué d'autant de DWORD ( 32 Bits ) qu'il y a de FONCTIONS exportées et se termine par un DWORD NULL. Chaque DWORD correspond à l'adresse réelle de la FONCTION.Par exemple : si il n'y a qu'une FONCTION exportée de COMDLG32.DLL, IAT sera du style :
BFFA125F 00000000 <-- Fin de l'IAT
^ ^ ^ | ^ ^ ^
|--> Addresse de la FONCTIONDonc, si l'IAT se trouve en :406514, le Linker mettera par exemple en :00404E72 :
* Reference To: comdlg32.CommDlgExtendedError, Ord:0004h
|
:00404E72 FF2514654000 Jmp dword ptr [00406514]Et pour appeler cette fonction, le programme fera :
* Reference To: comdlg32.CommDlgExtendedError, Ord:0004h
|
:004014B8 E8B5390000 Call 00404E72Donc pour résumer, dans l'ordre, lorsqu'un programme voudra appeler une FONCTION, il y aura un CALL addresse_du_jump_ver_la_FONCTION à cette addresse il y aura un JMP [addresse_de_l'addresse_reelle_de_la_FONCTION], l'adresse réelle de la fonction se trouvant dans l'IAT.
MAIS, dans DLL il y a 'Liens Dynamiques', en d'autres mots, les adresses des FONCTIONS exportées par les DLLs ne sont pas statiques et peuvent changer. L'IAT doit donc être modifié en conséquence. Si l'IAT n'était pas changé, les adresses deviendraient erronées et le programme ne fonctionnerait plus. De plus, pour connaître les adresses des FONCTIONS Exportées par la DLL, il faut qu'elle soit préalablement chargée. Voilà donc pourquoi les DLLs doivent généralement être chargées avant l'exécution de la première instruction du processus appelant.
Pour que Windows sache quelle DLL charger et quelle FONCTION maper, il lui suffit de lire dans l'Import Directory de l'exécutable.
Voici par exemple pour NotePad :
¢ßé ÔcaßÜXßÜ_ß j ShellAboutA DragFinish DragQueryFileA N SHGetSpe
cialFolderPathA ¤ DragAcceptFiles l ShellExecuteA SHELL32.dll ExitProcess ¶ G
etModuleHandleA @ GetStartupInfoA ð GetCommandLineA t GlobalFree GetLocaleI
nfoA ß lstrcpyA Í MulDiv @ CreateFileA GetLastError Ï lstrcatA ƒ FindClo
se ú FindFirstFileA _ lstrcmpA 9 GetProfileStringA þ lstrlenA RtlMoveMemor
y õ lstrcpynA ¢ LocalReAlloc + LocalLock  LocalAlloc + LocalUnlock ð _lclose
i _lwrite _ DeleteFileA Ð _lcreat Ë _lopen È _lread Ê _llseek ¦ LocalFree ~
GlobalUnlock x GlobalLock m GlobalAlloc GetLocalTime ^ GetTimeFormatA Õ G
etDateFormatA Ì lstrcmpiA KERNEL32.dll © MoveWindow q InvalidateRect SetFJuste après l'IAT se trouve le Nom des FONCTIONS Exportées et le Nom de la DLL exporteuse.
Il y a encore un MAIS, les différentes FONCTIONS importées ne doivent pas obligatoirement être référencées dans l'exécutable. Si le programme désire utiliser une FONCTION non référencée contenue dans une DLL, il doit utiliser la FONCTION LoadLibrary, et ensuite GetProcAddress pour récupérer l'adresse de la FONCTION. Ainsi, la DLL n'est pas chargée avant l'exécution de l'exécutable. Pour libérer la DLL, il lui suffit d'appeler la FONCTION FreeLibrary. La DLL sera alors déchargée et les appels des FONCTIONS de cette DLL ne seront donc plus possibles.
Note Finale
Je sais que cela n'est pas simple à comprendre, moi-même je ne suis pas encore sur d'avoir tout parfaitement compris. Je pense quand même en avoir encore une fois apprit beaucoup. Pour terminer, je vous prie de m'excuser si une faute se serait glissée dans ces quelques lignes, je ne demande qu'à les corriger.
Amicalement,
TeeJi ( teejee@hotmail.com )