Aller au contenu

DLL hijacking Windows et ordre de recherche des bibliothèques

DLL hijacking Windows et ordre de recherche des bibliothèques

Section intitulée « DLL hijacking Windows et ordre de recherche des bibliothèques »

Le détournement de bibliothèque dynamique (DLL hijacking, ou DLL sideloading dans sa variante par cohabitation) fait exécuter du code arbitraire à une application de confiance sans la modifier. On place une bibliothèque malveillante portant le nom que l’application attend, dans un répertoire qu’elle consulte avant le bon. Cette page monte une application vulnérable en laboratoire, exécute le détournement, puis referme la faille sur l’ordre de recherche qui la rend possible.

→ Lire l’explication complète de la marmotte

Pourquoi l’ordre de recherche de Windows rend le détournement possible

Section intitulée « Pourquoi l’ordre de recherche de Windows rend le détournement possible »

Une application Windows ne contient pas tout son code : elle s’appuie sur des bibliothèques dynamiques, les fichiers DLL (Dynamic-Link Library), chargées à l’exécution. Quand un programme demande une DLL par son nom seul, sans chemin absolu, Windows doit décider où la trouver. Il ne devine pas : il parcourt une liste de répertoires dans un ordre fixe et prévisible, et prend la première DLL du bon nom qu’il rencontre.

Cet ordre commence par le répertoire de l’application, puis (selon la configuration) le répertoire de travail courant, les répertoires système, et enfin les dossiers listés dans la variable d’environnement PATH. La faille naît de deux propriétés combinées : l’ordre est connu de tous, et un attaquant qui peut écrire dans un répertoire consulté avant l’emplacement légitime y dépose sa DLL. Windows la trouve en premier, la charge, et exécute le code qu’elle contient, sous l’identité et avec les privilèges de l’application de confiance.

Le point décisif est que l’application n’a aucun bug au sens habituel : elle demande une DLL par son nom, ce que fait tout programme. Le défaut est dans l’hypothèse implicite que le nom suffit à désigner le bon fichier. Vingt ans de packaging Windows apprennent que le répertoire de travail d’un processus est rarement celui qu’on croit : un fichier ouvert par double-clic, une archive décompressée dans le dossier Téléchargements, un raccourci mal câblé, et le répertoire de travail devient un endroit où un attaquant a pu écrire. Le sideloading pousse la logique plus loin : on livre un exécutable légitime et signé accompagné de sa DLL piégée dans le même dossier, et c’est la signature de l’exécutable qui sert de couverture au code non signé chargé à côté.

Monter l’ouvrage : une application qui charge une DLL par son nom

Section intitulée « Monter l’ouvrage : une application qui charge une DLL par son nom »

Le décor tient en une petite application de laboratoire, montée pour l’occasion, qui charge une DLL par son nom seul, et en une DLL malveillante déposée dans le répertoire de travail. Tout se passe sur une machine de test jetable.

// MONTAGE DE LABORATOIRE, application volontairement vulnérable.
// Elle charge "greffon.dll" par son NOM seul : Windows applique alors
// son ordre de recherche et prend la première trouvée.
#include <windows.h>
int main(void) {
// Faille : nom sans chemin absolu. Le répertoire de travail courant
// est consulté avant les dossiers systeme (hors recherche sécurisée).
HMODULE h = LoadLibraryA("greffon.dll"); // ← chargement détournable
if (h) {
// L'application appelle une fonction légitime du greffon.
FARPROC f = GetProcAddress(h, "Demarrer");
if (f) ((void(*)(void))f)();
}
return 0;
}

La DLL malveillante exporte le même nom de fonction que la légitime, mais glisse son code dans DllMain, la fonction appelée automatiquement par Windows au chargement de toute DLL, avant même que l’application n’appelle quoi que ce soit.

// DLL malveillante de laboratoire : meme nom de fichier que la légitime.
// DllMain s'exécute AU CHARGEMENT, avant tout appel de l'application.
#include <windows.h>
BOOL WINAPI DllMain(HINSTANCE h, DWORD raison, LPVOID reserve) {
if (raison == DLL_PROCESS_ATTACH) {
// Preuve d'exécution en laboratoire : dépose un fichier témoin.
// Le code tourne sous l'identité de l'application de confiance.
WinExec("cmd.exe /c echo preuve > %TEMP%\\preuve-labo.txt", SW_HIDE);
}
return TRUE;
}

En plaçant cette DLL dans le répertoire depuis lequel l’application est lancée, le fichier témoin apparaît dès le démarrage : le code non prévu s’est exécuté avant même la première ligne utile du programme. Aucun exploit mémoire, aucun dépassement de tampon, juste un fichier bien nommé au bon endroit. Linux, qui se croit volontiers au-dessus de ces mœurs de bureautique, rejoue la même farce avec son chargeur dynamique, décrite dans le détournement de bibliothèque partagée sous Linux.

Chemin absolu, recherche sécurisée et vérification de signature

Section intitulée « Chemin absolu, recherche sécurisée et vérification de signature »

Trois contre-mesures, dans l’ordre où elles referment la faille.

Charger la DLL par chemin absolu supprime la recherche : LoadLibraryA("C:\\Program Files\\MonAppli\\greffon.dll") désigne exactement un fichier, et Windows ne parcourt plus aucune liste de répertoires. C’est la correction la plus directe quand on maîtrise le code, et elle ferme le vecteur à la racine. Pour les DLL système, LoadLibraryEx avec le drapeau LOAD_LIBRARY_SEARCH_SYSTEM32 restreint la recherche au seul dossier système.

Activer la recherche sécurisée durcit le comportement par défaut quand le nom seul reste employé. L’appel SetDllDirectory("") retire le répertoire de travail courant de l’ordre de recherche, ce qui neutralise le cas le plus courant du détournement. Le mode SafeDllSearchMode, activé par défaut sur Windows moderne, place déjà les répertoires système avant le répertoire de travail ; le vérifier plutôt que le supposer évite les mauvaises surprises sur les parcs hétérogènes.

// Durcissement : on retire le répertoire de travail de la recherche,
// puis on impose la recherche dans les seuls dossiers de confiance.
SetDllDirectoryA(""); // le répertoire de travail courant n'est plus consulté
// Chargement par chemin absolu : aucune recherche, aucun détournement.
HMODULE h = LoadLibraryExA(
"C:\\Program Files\\MonAppli\\greffon.dll",
NULL,
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); // ordre restreint aux dossiers surs

La signature et la vérification d’intégrité des bibliothèques ferment la variante par sideloading, où l’attaquant livre sa DLL à côté d’un exécutable signé. Une application sensible vérifie la signature Authenticode des DLL qu’elle charge, et refuse celles qui ne portent pas une signature attendue. Le compromis central est là : la vérification de signature coûte en maintenance (chaque DLL légitime doit être signée, la chaîne de confiance tenue à jour) et ne dispense jamais du chemin absolu, puisqu’une DLL non signée chargée avant la vérification a déjà exécuté son DllMain. Trancher est net : chemin absolu d’abord, recherche sécurisée ensuite, signature en défense de dernier rang pour les binaires exposés. La logique de désigner sans ambiguïté ce qu’on charge se retrouve dans la section Failles et dans les principes d’architecture cybersécurité, où une ressource appelée par un nom que l’attaquant contrôle est toujours une ressource qu’il finit par fournir.

Sans chargement par chemin absolu ni recherche sécurisée, toute application qui appelle une bibliothèque par son nom seul devient un vecteur d’exécution de code dès qu’un attaquant peut écrire dans un répertoire consulté avant l’emplacement légitime, souvent le répertoire de travail hérité d’un double-clic ou d’une archive décompressée. Le code s’exécute sous l’identité et avec les privilèges de l’application de confiance, ce qui sert aussi bien la persistance que l’escalade quand l’application tourne avec des droits élevés. La détection est pauvre : le processus porte un nom légitime, souvent signé dans la variante par cohabitation, et la bibliothèque piégée n’apparaît suspecte qu’à qui compare le chemin chargé à l’emplacement attendu. Sans vérification de signature des bibliothèques chargées, rien ne distingue le fichier légitime du fichier substitué avant que son code de démarrage n’ait déjà tourné.

Un programme Windows ne contient pas tout son code : il s’appuie sur des fichiers séparés, les bibliothèques, qu’il charge quand il en a besoin. Souvent, il demande une bibliothèque juste par son nom, sans dire où elle se trouve. Windows doit alors la chercher, et il le fait toujours dans le même ordre : d’abord le dossier du programme, puis le dossier depuis lequel il a été lancé, puis les dossiers du système. Il prend la première bibliothèque du bon nom qu’il rencontre.

Un attaquant exploite cet ordre connu de tous. S’il peut écrire un fichier dans un dossier consulté tôt, il y dépose une fausse bibliothèque portant exactement le nom attendu. Le programme, en cherchant, tombe sur la fausse avant la vraie, la charge et exécute le code qu’elle contient, sans se douter de rien. Pire, Windows lance automatiquement un bout de code au moment même du chargement, donc le piège se déclenche avant que le programme ait fait quoi que ce soit d’utile. Le code s’exécute avec les droits du programme de confiance.

Pour refermer la faille, la meilleure solution est de préciser le chemin complet de la bibliothèque, pour que Windows n’ait plus à chercher et ne puisse plus se tromper de fichier. On peut aussi retirer le dossier de lancement de la liste des endroits fouillés, ce qui supprime le cas le plus fréquent, et vérifier la signature de chaque bibliothèque avant de s’y fier.

À lire aussi

Pages voisines

: les deux pages se citent l’une l’autre.