DLL, plugins et utilisation de Dependency Walker for Win32

Transcription

DLL, plugins et utilisation de Dependency Walker for Win32
TUTORIAL: DLL, plugins et utilisation de Dependency
Walker for Win32 (Intel x86)
Vous allez tout comprendre sur les DLL... :)
Dependency Walker est un utilitaire pour l'exploration 32 bits des tables (statiques) d'importation et
d'exportation d'un fichier exécutable. Vous me direz que ce n'est pas un utilitaire pour faire de la
programmation, mais je répondrais que c'est un tool qui est indispensable dans ce domaine. Il va en plus
nous ammener à faire quelques considérations.
Il permet entre autre :
1) de voir si tous les appels à DLL sont corrects
2) de savoir si un EXE sera probablement exécutable sur une autre version de Windows sans même lancer
l'application
3) de dénicher les fonctions insolites de Windows non référencées dans la VCL de Delphi
4) de savoir si une application a des rapports étroits avec le multimédia, les réseaux, le graphisme, les
imprimantes...
5) de trouver les bugs donc
6) de comparer les versions
7) ... etc
Accrochez-vous bien...
Contenu:
I) Note importante avant toute utilisation
II) Interface du logiciel
III) A quoi ressemble l'assembleur ?
IV) Notion de point d'entrée
V) Index de table
VI) Affichage des informations
VII) Appels dynamiques sur des DLL
VIII) Conclusion
I) Note importante avant toute utilisation
Cet utilitaire comporte un seul fichier et disponible en version 1.0.0.0 :
DEPENDS.EXE
!!!! NOTE ULTRA IMPORTANTE !!!!
vous ne pourrez pas dire que je vous ai caché des choses
- Si vous ne comptez pas utiliser ce logiciel
- S'il ne vous intéresse pas particulièrement
- Si vous avez le moindre doute sur ce produit
- Si vous êtes un grand débutant
- ...
ALORS N'EXECUTEZ PAS L'APPLICATION !!!!!!!!!
La raison est très simple : ce logiciel fait des inscriptions dans la base de registre afin d'ajouter dans
l'explorateur des items "Dependency Walker" sur les fichiers EXE, DLL, SCR, VXD, DRV... bref,
la totale sur tous les fichiers de format exécutable. Ces inscriptions ont lieu à chaque fois que DW est
lancé, et à ma connaissance, il n'est pas possible d'éliminer automatiquement ces traces.
Manuellement, ça se fait facilement, mais c'est prise de tête... long et chiant !
Cependant, que cela ne vous empêche pas de lire ce tutorial, histoire d'apprendre quelques trucs
sympathiques. Je vous donne de toute manière des aperçus d'écran pour que vous voyiez les choses
sans nécessairement utiliser le logiciel.
II) Interface du logiciel
J'ai choisi de lancer l'application et depuis que je l'utilise, je n'ai jamais eu le moindre problème.
Voici ce qui s'affiche en analysant le fichier c:\windows\explorer.exe dans sa version pour
Windows XP :
Cette image peut paraître illisible, mais les informations proposées sont très précieuses.
On remarque 4 zones principales ayant chacune un but précis.
PARTIE N°1
Vous avez en première ligne le fichier analysé
Le niveau juste inférieur donne toutes les DLL dont l'application a strictement besoin pour s'exécuter
Le niveau encore inférieur sont les ressources utilisées par lesdites DLL
Or, DW se présente comme un «Recursive Module Dependency Checker». On ne va pas analyser les
ressources des DLL des DLL de l'EXE, car sinon on rentrerait dans une boucle sans fin. Pour y
remédier DW vous donne le logo
qui indique qu'il faut que vous vous référiez aux données des
niveaux supérieurs (données nécessairement disponibles). On remarque au passage sur la capture
qu'il existe un sous-niveau 3 repéré par le fichier RPCRT4.DLL. Comme ce fichier est à ce stade
encore inconnu, DW se charge d'afficher ses dépendances. Naturellement, si ce fichier est rencontré
dans des niveaux encore plus profonds, alors vous visualiserez .
En faisant un clic droit dans l'arborescence, vous pouvez avoir accès aux propriétés de la ressource.
PARTIE N°2
Cette section affiche toutes les fonctions et procédures utilisées par la ressource parente de la
sélection. Par exemple, sur la capture, vous voyez ce que EXPLORER.EXE vient piocher dans
MSVCRT.DLL. Dependency Walker vous donne leur position dans la table d'importation, leur nom
et enfin leur point d'entrée (on va voir après ce que c'est).
Le "i" encadré en vert signifie "importation".
PARTIE N°3
Ce cadre montre toutes les ressources disponibles dans la ressource sélectionnée dans le cadre N°1.
Le "e" encadré en vert signifie "exportation".
PARTIE N°4
Enfin, ce cadre (aligné en bas de l'écran) résume les propriétés logicielles de toutes les ressources qui
ont été trouvées dans le cadre N°1. Vous avez la date, la plateforme, la machine (c'est preque
toujours Intel x86), les versions...
Que ce soit GUI ou Console, ça dépend si une console (l'écran noir style MsDos) peut être utilisée.
Avant de finir, lancez DW sur le fichier c:\windows\kernel32.dll (pour Windows 98) et sur
c:\windows\system32\kernel32.dll (pour Windows XP). Les dépendences sont bien maigres et
c'est normal pour un fichier noyau de cette importance.
III) A quoi ressemble l'assembleur ?
Prenons la fonction IntPower de l'unité MATH.PAS de Delphi. Nous avons ceci :
function IntPower(Base:extended; Exponent:integer):extended;
asm
MOV
ecx, eax
CDQ
FLD1
XOR
eax, edx
SUB
eax, edx
JZ
@@3
FLD
Base
JMP
@@2
@@1:
FMUL
@@2:
SHR
JNC
FMUL
JNZ
FSTP
CMP
JGE
FLD1
FDIVRP
@@3:
FWAIT
end;
ST, ST
eax,1
@@1
ST(1),ST
@@1
st
ecx, 0
@@3
Même si nos chers mathématiciens calculent simplement x^y, vous voyez que les ordinateurs font
bien différemment et c'est compliqué. Je me dois d'expliquer quelque peu ce que signifient toutes ces
commandes barbares.
Déjà, notre fonction est en assembleur et repérée par le mot clé ASM qui fait office d'un BEGIN. Elle
est composée de commandes élémentaires et irréductibles qui se succèdent les unes derrière les
autres. On a la syntaxe suivante :
[INSTRUCTION]
[Paramètre 1] , [Paramètre 2] , ... , [Paramètre N]
Les marqueurs @@ correspondent à des LABEL. C'est pareil que le LABEL et GOTO de Delphi, chacun
devenant une adresse et un JUMP (conditionnel ou pas) après compilation.
Il est impossible d'avoir une instruction dans le paramétrage d'une autre instruction. C'est à dire
que...
MOV
INC
EAX, 5
EAX
... ne peut pas devenir ce qui suit, sachant que le registre de sortie de la première instruction est
EAX.
INC
MOV EAX, 5
Les instructions ASM sont équivalentes en Delphi à une succession de procédures : les function
sont interdites. Par exemple ...
var i : integer ;
function Resultat(Value:integer):integer;
begin
Resultat := Value+5;
end;
begin
i:=20;
i:=Resultat(i);
//i vaut alors 25
end.
... deviendrait plutôt ceci :
var i : integer ;
procedure Resultat;
begin
i := i+5;
end;
begin
i:=20;
Resultat;
//i vaut alors 25
end.
C'est un peu moins lisible, car il faut se souvenir où le "résultat" de la procédure est stockée. Le rôle
que joue la variable i dans cet exemple en Delphi, est celui des registres en assembleur.
Autre exemple tiré de l'aide de Delphi : additionner deux variables et renvoyer le résultat.
function Sum(var X, Y: Integer): Integer; pascal;
asm
MOV
EAX,X
MOV
EAX,[EAX]
MOV
EDX,Y
ADD
EAX,[EDX]
MOV
@Result,AX
end;
Même en ASM, il y a des pointeurs à tout va. Regardez le tuto de CptPingu si vous avez le moindre
doute sur les pointeurs. C'est très bien fait...
IV) Notion de point d'entrée
Les DLL sont des librairies qui référencent en leur sein pleins de fonctions. Mais pour appeler ces
fonctions, il faut repérer leur position de départ qui est généralement la première instruction codant
pour ladite fonction.
Delphi est un langage compilé, c'est à dire que c'est Windows qui se charge d'exécuter les
applications. Il ne faut pas confondre avec les langages interprétés qui nécessitent d'installer un
logiciel spécifique de lecture (souvent compatible sur différents OS, ce qui dit faussement que les
langages interprétés sont multiplateformes [l'universel n'existant ben sûr pas]). Par exemple, les
applets Java ne sont pas des applications Win32. La structure interne de ces fichiers est spécifique à
un interpréteur, d'où la nécessité d'installer ce dernier.
Malgré la diversité des langages de programmation, leur point commun est le fichier exécutable qui
n'est ni plus ni moins qu'un fichier écrit en assembleur compilé. Disons que c'est de l'assembleur pur
edulcoré autour pour créer un tout exécutable sous Windows. Par exemple, la vérification de la
plateforme (vous savez le fameux message "This application cannot be run under MsDos mode") est
un ajout de la compilation. On note également que les applications Visual Basic rajoutent même un
lancement de Windows via WIN.COM au lieu d'afficher ce message.
Tous les linkers font de la conversion en langage assembleur, d'où la nécessité d'une syntaxe
cohérente, structurée et logique. L'appelation langage de haut niveau dépend de la difficulté de cette
traduction.
Sur un PC, vous avez un processeur Pentium qui fonctionne en 32 bits (soit 4 octets). Il ne sait gérer
que des zéros et des uns. Alors pas besoin de lui balancer une super syntaxe magistrale, il n'y
comprendra rien. En revanche, il connaît des instructions de base (qu'on a pu voir au paragraphe
précédent).
Le processeur (CPU) est en attente. Pour savoir ce qu'il doit exécuter, il faut le commander. Pour ce
faire, on lui envoie un nombre sur 32 bits. Par exemple en lui envoyant la clé 40h (qui vaut 64 en
décimal), vous exécutez la commande :
INC eax
Pour schématiser, il y a la mémoire pour les nombres entiers, pour les nombres flottants, pour les
chaînes de caractère... Bref, EAX correspond aux nombres entiers. En faisant INC eax, vous
incrémentez le contenu d'une variable qui aurait été déclarée en integer. La capture suivante montre
un programme qui n'a aucun sens en soi, mais vous allez voir.
Delphi a décodé pour nous l'état de notre travail juste avant que l'instruction en rouge ait été exécuté.
Vous imaginez bien la puissance de Delphi sur cet outils de débuggage techniquement ultracompliqué vis-à-vis des novices :
assm.dpr.5:
00407670 40
inc EAX
inc eax
[Nom du fichier].[Ligne] : [Code en Delphi]
[Adresse] [Instruction binaire en hexadécimal envoyée au processeur] [Equivalent
en assembleur]
C'est là que j'ai vu que l'instruction binaire 40h correspond à INC eax...
Evidemment les correspondances entre l'électricité et les commandes ASM nécessitent d'avoir une
norme qui dit que çà correspond à cela et pas à ceci. On a donc fixé les normes de Intel (c'était à
l'époque des premiers processeurs), et ça a donné lieu à l'assembleur dit pour Intel x86. DW porte
bien son nom : Dependency Walker for Win32 (Intel x86). Si une application a été compilée dans une
autre norme, alors elle sera quand même exécutée en Intel x86, ce qui peut créer de graves
instabilités. Par exemple, Borland C++Builder (qui fait du C++ mais également du Pascal) propose
des modes de compilation pour différents processeurs.
A chaque instruction correspond une adresse (ex: 00407670h). Eh bien, l'adresse de la première
instruction codant pour la fonction ABCDEF détermine ce qu'on appelle le point d'entrée pour la
fonction considérée. De même, le BEGIN général du fichier DPR détermine le point d'entrée de
l'application.
Dans un programme, quand une exception se produit, vous pouvez lire "Erreur à l'adresse
xxxx:xxxx". C'est la même chose que ci-dessus... Hélas, une fois l'application déployée, retrouver la
commande Pascal à l'origine de l'adresse xxxx:xxxx est une autre affaire. Delphi le sait facilement,
car c'est lui qui a créé l'application, donc il a des historiques dans les fichiers DCU qui l'aident en ce
sens.
Pour vous convaincre de tout ce blabla, on va faire un petit travail pratique. On va créer une DLL
sous forme d'EXE... En faisant ainsi, ça sera plus facile de debugger. Créez donc le fichier DPR
suivant :
program assm;
uses SysUtils;
procedure Fonction;
asm
inc EAX
end;
exports Fonction;
begin
Fonction;
end.
N'oubliez pas de mettre un point d'arrêt sur INC eax. Il suffit de cliquer dans la marge au niveau de
la même ligne et ça devient rouge (par défaut). Lancez l'application. Delphi stoppe et vous voyez à
l'écran ceci :
assm.dpr.5:
00407608 40
inc EAX
inc eax
Si la fenêtre ne s'affiche pas, cliquez sur >"Voir" >"Fenêtre CPU".
Notez le numéro, et regardez ce que renvoie DW :
L'Entry Point est situé à l'adresse 00007608h selon DW, et à 00407608h pour Delphi. Je n'explique
pas pourquoi un chiffre 4 apparaît, car je ne le sais pas, mais je crois que cette démonstration est très
convaincante.
Pour aller encore plus loin, selon la fenêtre CPU, notre procédure FONCTION a été traduite ainsi :
inc EAX
ret
;Correspond au END final de la procédure
Le plus étonnant dans l'affaire, c'est la compilation. Ce qui suit se résume seulement à 2 octet : 40
C3
procedure Fonction;
asm
inc EAX
end;
Ce n'est alors pas étonnant de voir des projets C++ de 300 ko devenir des EXE de 20 ko seulement...
Ca Delphi ne sait pas faire sans créer de dépendences vis-à-vis des paquets d'exécution BPL.
V) Index de table
On n'a pas encore défini ce qu'est l'Ordinal. En tout cas, ce n'est pas important. Disons que c'est
l'index de la fonction dans la table d'exportation. Voyez cette DLL complètement creuse :
library DLL;
procedure FuncOne;
begin
end;
procedure FuncTwo;
begin
end;
exports FuncOne index
1;
exports FuncTwo index
begin
end.
2;
Amusez vous à changer les INDEX et voyez l'effet avec DW. Delphi effectuera des corrections en cas
de litige. C'est comme si vous ordonniez l'ordre des fonctions dans la table. A première vue, ça paraît
gadget, mais à deuxième vue, je n'ai rien à répondre.
En tout cas, si vous mettez ce qui suit, vous aurez un N/A dans la table en position 2.
exports FuncOne index 1;
exports FuncTwo index 3;
Peut-être ces index servent-ils à appeler des fonctions juste en connaissant leur position dans la table
et non leur nom ?!?
VI) Affichage des informations
DW utilise des pictogrammes pour afficher les dépendances.
1) FUNCTIONNAL
Tous les fichiers sont trouvés, et on retrouve les liens apparentés déjà analysés.
2) FILE NOT FOUND
php_java.dll est une extension pour PHP. En réalité, php4ts.dll est situé dans le répertoire parent du
dossier où se trouve php_java.dll. La recherche naturelle et automatique est :
le dossier en cours
c:\windows\system32\
c:\windows\system\
c:\windows\
c:\
puis dans les dossiers spécifiés dans les variables environnementales (comme ça
se faisait avec AUTOEXEC.BAT)
Si le fichier n'est pas trouvé, on voit un logo jaune. Cela ne signifie pas que l'application ou la DLL
ne fonctionne pas...
3) 32 BITS REQUIRED
Soit vous avez ouvert un fichier DOS 16 bits, soit une application GUI pour Win 3.1, soit un fichier
qui n'est pas exécutable. Bref, DW ne supporte que le 32 bits. Pour avoir des informations sur ces
fichiers 16 bits, utilisez QuickView sous Windows 98. DW affiche dans le cadre N°4 la raison du
pictogramme rouge.
4) FAILURE
Il paraîtrait que Half-Life ait un problème avec DirectX (en réalité, c'est faux, c'est un trucage pour
montrer le pictogramme). Vous pouvez être sûr que l'application ne se lancera pas. Vous avez de
même le logo
en cas d'erreur reliée.
5) FILE IS MODIFIED
C'est l'écran le plus dangereux que vous puissiez obtenir (cette capture est une fois de plus truquée) :
Cela signifie que l'application a été modifiée après sa compilation. Ses tables d'importations et/ou
d'exportations ont été flouées, ce qui peut entraîner des instabilités si un logiciel tente d'accéder au
fichier. Si vous voyez ça, jettez immédiatement l'application... !!! La personne qui est derrière a des
intentions malveillantes. De plus, DW ne recherchant que ces tables, rien ne dit que d'autres sections
n'ont pas elles aussi été modifiées.
VII) Appels dynamiques sur des DLL
Beaucoup de logiciels gèrent ce qu'on appelle des plugins. Qu'est-ce que c'est ?
Ce sont tout simplement des DLL, mais dont les appels ne sont pas consignés dans les tables. Cela
donne une flexibilité et si la DLL est manquante, le démarrage de l'application ne sera pas bloqué.
Alors comment fait-on pour faire des plugins ? Regardez donc les exemples suivants qui sont tous
les deux des fichiers DPR.
program Serveur;
uses Windows;
var CallProc : procedure(i:integer); pascal;
DHandle : integer;
begin
DHandle:=LoadLibrary('client.dll'); //on recherchera dans le dossier en cours
en premier
try
if DHandle<>0 then
begin
@CallProc:=GetProcAddress(DHandle, 'DllRunMain');
CallProc(49);
end;
finally
FreeLibrary(DHandle);
end;
end.
library Client;
uses Dialogs, SysUtils;
procedure DllRunMain(Value:integer); pascal;
begin
ShowMessage('Vous avez sélectionné : '+IntToStr(Value));
end;
exports DllRunMain;
begin
end.
Compilez ces deux projets avec F9.
Note importante : le projet CLIENT doit être obligatoirement une DLL grâce au mot clé LIBRARY.
Cela se justifie par le fait que Delphi insère un marqueur caractéristique. Vous ne devez pas utiliser
le mot clé PROGRAM.
Analyser SERVEUR.EXE avec DW et vous ne voyez aucun appel statique...
... et pourtant vous utilisez bien CLIENT.DLL.
Lancez l'exécutable et observez le beau message qu'il s'affiche. C'est pas magique tout ça ?
Alors comment savoir si un logiciel utilise telle ou telle DLL ? Eh bien, il n'y a pas vraiment de
solution miracle sur l'instant.
Je dois rajouter une chose vis-à-vis de l'ouverture et de la fermeture de la DLL. Si vous oubliez de
libérer la DLL avec FreeLibrary, sachez que Kernel32 le fera à votre place lorsque l'application
appelante se coupe. Attendez 3 secondes... MAIS que cela ne vous empêche pas de travailler
proprement. Vous devez utiliser un bloc TRY...FINALLY...END pour vous assurer que la mémoire a
été libérée.
Lorsque vous exécutez SERVEUR.EXE, le curseur système de lecture de l'application se déplace de
bas en haut. Quand il arrive sur CallProc(49), il change de module et passe au fichier DLL a
l'Entry Point. Et donc, tant que vous n'avez pas cliqué sur OK, le fonctionnement de l'application
SERVEUR.EXE est bloqué. Heureusement, sinon la DLL serait libérée au moment où elle fait son
boulot.
On remarquera quand même que ces appels ne sont pas aussi dynamiques que prévu. On a déclaré
une variable de type procedure(i:integer); pascal;. A ma connaissance, il n'est pas possible de
moduler dynamiquement les types pour s'adapter à telle ou telle configuration. Ainsi, si une DLL a
une syntaxe spéciale que l'application ne connait pas, alors elle ne pourra pas être lue. De toute
façon, lorsqu'on fait des plugins, c'est pour un logiciel précis et pas pour piquer les DLL des autres.
Autre chose encore et ce sera tout. Pour ces DLL, il existe des conventions d'appels. Pour afficher la
page de l'aide, écrivez dans Delphi "stdcall", cliquez une fois sur ce mot et appuyez sur F1. Vous
avez alors la feuille "Convention d'appel" qui résume register, pascal, cdecl, stdcall et
safecall.
Ces conventions sont importantes. Ainsi, si ici j'ai utilisé PASCAL, avec les appels sur
Kernel32, il faudra utiliser STDCALL.
VIII) Conclusion
Avec ce tuto, vous devriez être prêt à entamer des séries de debuggage avec Delphi... Ca devrait
paraître moins sombre.
Tanguy ALTERT, http://altert.family.free.fr/