Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
ExportHider — ExportHider: Génération de la table d'exportation lors de l'exécution pour masquer les fonctions exportées du fichier DLL. | Kitploit
Outils/GitHubGitHub/frkngksl/exporthider
Analyse Dynamique de Code (DAST)ExploitationRétro-ingénierieAnalyse de MalwareAnalyse de BinairesRed TeamingDéveloppement de Charges Utiles
GitHubfrkngksl/exporthider

ExportHider

ExportHider: Génération de la table d'exportation lors de l'exécution pour masquer les fonctions exportées du fichier DLL.

Voir le dépôt
332il y a 4 moisVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

ExportHider

ExportHider génère un modèle de DLL C++ qui contient un stub de code vous permettant de masquer les fonctions exportées du répertoire d'exportation de la DLL sur le système de fichiers. Après avoir défini les fonctions et compilé le fichier, vous ne verrez pas les fonctions exportées cachées via les visualiseurs de fichiers PE comme CFF Explorer. Cependant, comme le stub de code dans le modèle recrée le répertoire d'exportation pendant l'exécution, les appels légitimes à GetProcAddress seront exécutés avec succès. Cette méthode ne fonctionne que pour le chargement dynamique de DLL ou les cas de chargeur de DLL personnalisé.

Comment ça marche ?

Normalement, lorsque vous souhaitez définir une fonction exportée dans les fichiers DLL (en C ou C++), vous placez simplement le mot-clé __declspec(dllexport) avant le nom de la fonction, ou créez un fichier .def. Après la compilation, le compilateur crée une table spécifique appelée le répertoire d'exportation pour stocker les informations relatives aux fonctions exportées. La structure du répertoire d'exportation peut être vue ci-dessous :

Lorsqu'un processus souhaite utiliser une fonction d'un fichier DLL, le chargeur Windows analyse simplement cette structure et importe les fonctions demandées en utilisant les tableaux AddressOfFunctions, AddressOfNames, AddressOfNameOrdinals.

La routine spécifique pour importer une fonction est expliquée en détail dans l'article de blog de ferreirasc, mais en bref, pour une fonction importée par nom, le chargeur parcourt le tableau AddressOfNames (les valeurs de ce tableau sont simplement des valeurs RVA) et recherche le nom donné. Une fois que le chargeur trouve une correspondance à la position « i », il se réfère à l'indice i du tableau AddressOfNameOrdinals et obtient l'ordinal associé à cette fonction. Ayant l'ordinal, le chargeur se réfère à AddressOfFunctions à la position de la valeur ordinale pour finalement obtenir le RVA associé à la fonction importée.

Le point crucial ici est que toutes ces opérations de recherche et d'accès par le chargeur Windows, lorsque LoadLibrary est appelé, sont effectuées après que la DLL est mappée dans l'espace d'adressage du processus. Pendant le mappage de la DLL, l'ensemble du fichier DLL, y compris ses en-têtes PE, est écrit en mémoire, et le chargeur analyse les en-têtes en mémoire pour atteindre le répertoire d'exportation. Cela signifie que si la DLL elle-même est capable de réécrire ses en-têtes PE en mémoire pour modifier l'adresse du répertoire d'exportation (simplement l'indice 0 du DataDirectory) après s'être attachée au processus, le chargeur peut chercher les fonctions à importer dans un répertoire d'exportation arbitraire plutôt que dans celui ajouté par le compilateur.

Paramètres de la ligne de commande

root@kitploit:~

   __                       _          _     _
  /__\_  ___ __   ___  _ __| |_  /\  /(_) __| | ___ _ __
 /_\ \ \/ / '_ \ / _ \| '__| __|/ /_/ / |/ _` |/ _ \ '__|
//__  >  <| |_) | (_) | |  | |_/ __  /| | (_| |  __/ |
\__/ /_/\_\ .__/ \___/|_|   \__\/ /_/ |_|\__,_|\___|_|
          |_|
                     by @R0h1rr1m

Usage of C:\Users\Public\DLLDemo\ExportHider.exe:

    -h | --help                                 Show the help message.
    -i | --input <Input Path>                   Input path for the list of function names to be hidden. (Mandatory)
    -o | --output <Output Path>                 Output path for the DLL template. (Mandatory)
    -n | --name <DLL Name>                      Name of the DLL for the Export Directory. (Mandatory)
    -c | --count <Number of Other Functions>    Number of other exported functions that won't be hidden.

Concernant le paramètre -i | --input <Input Path>, vous devez spécifier un chemin pour le fichier d'entrée qui stocke les noms de fonctions à masquer ligne par ligne. Le contenu d'un exemple de fichier d'entrée serait :

root@kitploit:~
TestFunction1
TestFunction2
TestFunction3

Concernant le paramètre -c | --count, si vous ne souhaitez pas masquer toutes vos fonctions exportées (c'est-à-dire qu'il y a des fonctions exportées avec __declspec(dllexport) ou un fichier .def, et qu'elles apparaissent dans le répertoire d'exportation du fichier DLL sur le système de fichiers via les visualiseurs de fichiers PE), indiquez simplement leur nombre en utilisant ce paramètre car l'outil a besoin de cette information lors des calculs de mémoire.

Vidéo de démonstration rapide

Solutions de contournement

Si vous voulez jouer avec la technique, il y a deux points intéressants que j'ai rencontrés lors du développement du projet. Vous pourriez avoir besoin de les connaître avant de modifier le projet :

  • Le chargeur Windows utilise un algorithme de type recherche binaire pour trouver la fonction exportée si vous appelez la fonction GetProcAddress avec un nom. Pour cette raison, les noms de toutes les fonctions exportées, y compris celles cachées, doivent être triés dans le tableau AddressOfNames. Sinon, la fonction GetProcAddress retourne NULL. Par conséquent, j'ai utilisé l'algorithme de tri à bulles pour trier les membres de ce tableau.
  • Comme je l'ai dit plus haut, le tableau AddressOfNames, le tableau AddressOfFunctions, le tableau DataDirectory et certains autres champs nécessitent des valeurs en adresses virtuelles relatives (RVA). De plus, ils stockent ces valeurs RVA dans des champs de taille DWORD. Lorsque vous allouez une région mémoire pour un nouveau répertoire d'exportation arbitraire en utilisant des fonctions d'allocation mémoire dynamique comme VirtualAlloc ou HeapAlloc, les adresses données seront loin de la région mappée de la DLL, et les valeurs RVA ne rentrent pas dans les champs de taille DWORD, ce qui provoque un débordement d'entier. C'est pourquoi j'ai utilisé des variables globales de type tableau d'octets dans le modèle de DLL pour les besoins en mémoire.

Cas de DLL importée statiquement (alias DLL Sideloading)

Mon premier objectif lors de la création de ce projet était de générer une DLL qui a des exportations manquantes (ou une absence totale de table d'exportation) mais qui est toujours chargée avec succès par le processus nouvellement créé. Je pensais que ce comportement pourrait apporter un nouveau terrain de jeu pour les charges utiles de DLL sideloading. Cependant, je n'ai trouvé aucune fonction ni aucun moyen pour que le stub de correction du répertoire d'exportation s'exécute avant que le chargeur Windows vérifie les fonctions exportées de la DLL.

dll_timing_problem (1)

Plus techniquement, j'ai remarqué le flux et l'appel de fonction suivants pour chaque DLL dans les sections de code liées au chargeur Windows dans NTDLL (extraits de mes anciennes notes ; il pourrait y avoir quelques erreurs car je ne suis pas un ingénieur inverse expert) :

root@kitploit:~
1. LdrpMapDll - C'est là que la DLL est mappée dans l'espace d'adressage du processus. Toutes les DLL qui satisfont la condition de nom de DLL sont directement placées en mémoire, sans contrôle de pré-vérification pour la version du système de fichiers.
2. LdrpSnapModule - C'est là que le chargeur Windows commence à résoudre les imports. Pour chaque descripteur d'importation, il analyse la structure PE, vérifie la table d'exportation, effectue une recherche binaire de la fonction importée, calcule son RVA et écrit son adresse dans l'entrée de la table d'adresses d'importation du processus appelant lors de cette fonction.
3. LdrpDoPostSnapWork - Si l'étape 2 réussit pour chaque fonction importée, les protections mémoire, l'initialisation TLS, l'activation de CFG sont effectuées dans cette fonction.
4. LdrpInitializeNode - Si l'étape 3 réussit, il y a des fonctions de liaison de module dans cette étape.
5. LdrpCallTlsInitializers - C'est là que les callbacks TLS sont appelés avant la fonction DllMain.
6. LdrpCallInitRoutine - C'est là que DLLMain lui-même est appelé pour la première fois pour la DLL importée. Dans la solution originale, cette fonction est trop tardive pour corriger la table d'exportation.

Lorsque vous exécutez un exécutable qui importe une DLL, si le chargeur Windows ne trouve pas le nom de fonction requis dans la table d'exportation de la DLL, il arrête l'exécution et n'exécute pas les fonctions qui sont exécutées après LdrpSnapModule.

Pour le cas du DLL sideloading, nous ne pouvons pas modifier le processus appelant ; ainsi, la seule chance de corriger dynamiquement la table d'exportation est de trouver une opportunité d'exécution de code entre les fonctions LdrpMapDll et LdrpSnapModule car le chargeur s'arrête immédiatement lors des vérifications de la fonction LdrpSnapModule. J'ai essayé les callbacks TLS, les deuxièmes chargements de DLL, les exportations transférées et d'autres solutions de contournement, mais aucune ne m'a aidé à trouver un tel endroit, donc malheureusement, cette méthode ne fonctionne pas directement pour le DLL sideloading ou les DLL importées statiquement. Si vous découvrez une solution ou une solution de contournement viable pour ce problème, je serais très heureux de l'explorer davantage — que ce soit en discutant de l'idée, en réfléchissant ensemble à l'approche ou en l'implémentant. Toute contribution dans cette direction serait grandement appréciée.

Une façon possible d'utiliser cette méthode pour les cas de DLL Sideloading est de diviser tout le travail en deux DLL, à savoir la DLL Proxy et la DLL Payload. La DLL Proxy prend le nom attendu par l'EXE et a des exportations visibles qui satisfont la vérification d'importation statique du chargeur, tandis que la DLL Payload contient la fonctionnalité cachée réelle, a des exportations manquantes et reconstruit sa table d'exportation dans DllMain. Je ne pense pas que ce soit une bonne solution de contournement pour ce problème, donc je ne l'ai pas implémentée.

Références

  • https://rioasmara.com/2021/10/10/analyze-dll-export-with-pe-bear/
  • https://ferreirasc.github.io/PE-Export-Address-Table/

Avertissement

Pour les tests de sécurité autorisés uniquement. L'utilisation abusive de cet outil contre des systèmes sans autorisation explicite est illégale.

Télécharger l’outil