- Cette extension Ghidra permet d’exporter une partie d’un programme sous forme de fichier objet, et le fichier exporté contient des métadonnées valides comme les symboles et la table de relocalisation, ce qui permet de le retraiter dans la toolchain
- Les principaux usages sont le patching binaire avancé, le portage logiciel, la conversion de formats de fichier, la création de bibliothèques, et la réimplémentation d’un programme en plusieurs fichiers objet dans un projet de décompilation
- Les combinaisons prises en charge sont COFF x86/x86_64, ELF x86/x86_64/MIPS et OMF x86 ; COFF MIPS, OMF x86_64 et OMF MIPS ne sont pas pris en charge
- L’utilisateur sélectionne la plage d’adresses à extraire dans le Listing de Ghidra, exécute l’analyseur Relocation table synthesizer, puis appelle l’exporteur de fichiers objets relogeables via
File > Export Program… - Comme la synthèse de la table de relocalisation dépend de la précision de la base de données Ghidra, des informations erronées ou manquantes peuvent casser ou faire disparaître des relocalisations ; il est donc prudent de relancer l’analyseur juste avant l’export
Que fait cette extension ?
- Object file exporter extension for Ghidra est une extension Ghidra qui permet d’exporter une partie d’un programme en fichier objet
- Le fichier objet généré contient des métadonnées valides comme les symboles et la table de relocalisation
- Grâce à ces métadonnées, le fichier objet exporté peut être réutilisé directement dans une toolchain pour un traitement supplémentaire
Cas d’usage
- Advanced binary patching
- Au lieu d’ajuster manuellement la partie d’origine et la partie modifiée, on peut utiliser le linker pour les faire s’emboîter correctement
- Software ports
- Il est possible d’isoler le code indépendant du système dans un programme et de remplacer le reste
- On peut convertir un programme ou un fichier objet d’un format de fichier à un autre
- Extraction de parties d’un programme et création de bibliothèques
- Il est possible d’extraire une partie d’un programme pour la réutiliser dans un autre contexte
- Dans un projet de décompilation, on peut découper le programme en plusieurs fichiers objet et le réimplémenter selon une approche Ship of Theseus
Architectures et formats de fichiers objet pris en charge
- La matrice de prise en charge est la suivante
- COFF : x86, x86_64 pris en charge / MIPS non pris en charge
- ELF : x86, x86_64, MIPS pris en charge
- OMF : x86 pris en charge / x86_64, MIPS non pris en charge
Build et installation
- Procédure de build en CLI
- cloner le dépôt
- définir la variable d’environnement
GHIDRA_INSTALL_DIRvers le répertoire d’installation de Ghidra - exécuter
gradle buildExtension - l’archive d’extension Ghidra générée est créée dans le répertoire
dist/
- Une authentification est nécessaire pour télécharger le package depuis le dépôt GitHub Maven
- créer un GitHub classic token avec le droit
read:packages, puis ajoutergithubToken=ghp_xxxdans${GRADLE_USER_HOME}/gradle.properties - ou exécuter
gradle installStandaloneDepspour construire et installer la dépendance vendored du submodule
- créer un GitHub classic token avec le droit
- Procédure d’installation
- télécharger l’extension depuis la page des releases ou la construire localement
- installer l’extension dans Ghidra via File > Install Extensions…
- activer le plugin RelocationTableSynthesizedPlugin dans File > Configure > Experimental de la fenêtre CodeBrowser
Flux d’utilisation et points d’attention
- Procédure d’utilisation de base
- sélectionner l’ensemble d’adresses à extraire dans la vue Listing
- exécuter l’analyseur Relocation table synthesizer, fourni en mode one-shot
- appeler l’exporteur de fichiers objets relogeables via File > Export Program…
- Les relocalisations reconstruites peuvent être consultées dans Window > Relocation table (synthesized)
- Un rapport d’évaluation détaillé peut être activé en configurant l’option Evaluation report policy de l’analyseur Relocation table synthesizer dans la boîte de dialogue Analysis > Auto Analyze...
- Il n’est pas nécessaire d’avoir entièrement rétroconçu tout le programme avant d’utiliser l’extension
- Un delinking réussi dépend généralement fortement des métadonnées de la partie à exporter et des références externes
- les fonctions et pointeurs utilisés comme emplacements de relocalisation
- l’empreinte des symboles utilisés comme cibles de relocalisation
- les références entre les deux
- L’analyseur Relocation table synthesizer dépend de la précision de la base de données Ghidra
- des informations inexactes ou manquantes peuvent entraîner des relocalisations corrompues ou absentes pendant l’analyse
- L’exporteur de fichiers objet dépend des résultats d’analyse de Relocation table synthesizer
- en cas de doute, il faut relancer l’analyseur juste avant d’exporter le fichier objet afin de vérifier que la table de relocalisation est à jour
Principe de fonctionnement
- Un fichier objet se compose de trois parties
- les octets de section relogeables
-
table des symboles
- table de relocalisation
- voici ce que fait le linker lorsqu’il crée un exécutable à partir de plusieurs fichiers objet
- il place les sections en mémoire
- il calcule l’adresse des symboles dans l’espace d’adressage virtuel
- il applique les relocalisations aux octets de section en fonction des adresses finales des symboles
- en général, une fois ce processus terminé, la table de relocalisation est supprimée
- si les symboles de débogage ne sont pas conservés, la table des symboles est également supprimée, et il ne reste plus que des octets de section non relogeables
- cette extension peut recréer ces données grâce à une analyse minutieuse, ce qui permet de delinker à nouveau le programme en fichier objet
1 commentaires
Avis Hacker News
Ravi de voir ça ici. Je trouve que c’est un projet vraiment chouette, et j’ai aidé à ajouter la prise en charge de MS COFF
Cela dit, ma PR initiale était assez en retrait par rapport à la prise en charge d’ELF qui existait déjà, donc s’il y a des problèmes, c’est probablement de ma faute. Mais je vois que ça s’améliore
Je ne l’ai pas encore utilisé sur un gros travail, mais ce qui m’a le plus amusé, c’est de « délinker » un exécutable Hello World compilé avec Visual Studio 2003, de le relinker avec GCC+glibc sous Linux x86, puis de le relinker à nouveau avec MinGW+msvcrt
Pour quelque chose de plus gros qu’un Hello World, c’est encore difficile, et comme je ne connais pas très bien Ghidra en particulier, je n’ai pas encore trouvé de bonne méthode pour choisir la plage à délinker dans un gros binaire
Justement, aujourd’hui, le paquet dérivé Nixpkgs de cet outil a été fusionné, donc sur NixOS unstable, on peut l’installer avec
ghidra.withExtensions. Il se trouve àghidra-extensions.ghidra-delinker-extensionEn revanche, une nouvelle version est sortie il y a quelques jours, mais je n’ai pas rebasé la PR, donc c’est pour l’instant l’ancienne version ; je compte proposer une mise à jour bientôt
Par exemple, si vous avez un programme Ghidra dans lequel vous avez identifié les noms et les plages des différents fichiers objets qui composaient l’exécutable d’origine, vous pouvez faire un clic droit sur le dossier ou le fragment concerné > Select Addresses pour tout sélectionner d’un coup
L’analyseur de synthèse des relocalisations et l’export peuvent aussi être scriptés séparément, ou automatisés via le gestionnaire d’arborescence du programme, ce qui réduit le besoin de sélectionner manuellement la plage voulue puis de lancer l’analyseur et l’export à la main
Ça a l’air assez intéressant, et ça me donne envie de ressortir un projet de reverse engineering de jeu que j’avais mis de côté il y a quelques années
Ce serait bien d’avoir un exemple complet montrant de bout en bout comment l’utiliser et quoi faire de la sortie
https://github.com/boricj/ghidra-delinker-extension/blob/mas...
Je me demande combien de travail il faut pour déterminer quelle section de l’exécutable exporter
Est-il réaliste d’exporter un jeu Win32 relativement moderne, disons de 2008 à 2015, en fichier objet, puis de le recompiler/relinker en un exécutable complet en quelques heures ?
Ce qu’il faut exporter est un autre problème, qui demande de connaître le programme. Des symboles de débogage rendent les choses beaucoup plus simples ; même sans eux, une fois que la base de données Ghidra est assez précise pour permettre l’export, on finit généralement par avoir une idée de ce qui se trouve où
Dans le cas d’usage de l’article soumis, l’utilisateur a ouvert le premier ticket début juillet, et vers la mi-août il a obtenu un exécutable relinké fonctionnellement équivalent
Cela dit, à l’époque, il y avait beaucoup de bugs à corriger dans l’export COFF, et l’analyseur i386 avait aussi des points à revoir ; j’espère donc que d’autres personnes rencontreront désormais moins ces problèmes
Je ne sais pas combien de temps cela prendrait, mais sauf si vous avez des symboles de débogage et vraiment beaucoup de chance, il y a de fortes chances que cela prenne plus que quelques heures. Un reverse engineer expérimenté pourrait probablement obtenir quelque chose qui se lance dans ce laps de temps, mais ça pourrait aussi planter au milieu du premier écran de chargement ; c’est plutôt le genre de tâche dont on ne sait pas quand elle se terminera avant d’y être arrivé
Ça a l’air chouette. Peut-être qu’un jour cela pourra aider à décomposer plus facilement des programmes existants pour les utiliser comme on le souhaite
Il existe des recherches comme le LLM Compiler de Meta ou la décompilation à l’aide de LLM ; découper un programme et en remplacer certaines parties pourrait être un domaine intéressant, riche en données, que les LLM pourraient explorer et améliorer par eux-mêmes
Du point de vue de l’apprentissage aussi, il semble y avoir beaucoup de tokens intéressants cachés là-dedans, et beaucoup de choses qu’on pourrait en faire
Honnêtement, ça ressemble à de la magie. Il va falloir que je comprenne comment c’est possible.
Quand l’éditeur de liens crée un exécutable à partir de plusieurs fichiers objets, il place les sections en mémoire, calcule les adresses des symboles dans l’espace d’adressage virtuel, puis applique les relocalisations aux octets des sections à partir des adresses finales des symboles.
L’astuce du délinking consiste à retrouver où ces relocalisations ont été appliquées, à les annuler, et à récupérer à nouveau des octets relocalisables. Ensuite, on construit une table de relocalisation et une table de symboles à partir de ce qui a été annulé, on emballe le tout, et on obtient un fichier objet.
La partie vraiment difficile est l’analyse qui permet de retrouver les points de relocalisation. L’essentiel est confié à Ghidra, mais il faut transformer les références en points de relocalisation. Sur x86, c’est relativement simple ; sur MIPS, c’est cauchemardesque. Cette extension a été créée pour automatiser jusqu’à la collecte des données nécessaires et la sérialisation du fichier objet.
En dehors des plateformes Microsoft, le format de fichier réel est même souvent le même ; ELF, par exemple.
La grande différence, ce sont les relocalisations. Un fichier objet contient des informations de relocalisation fines, alors qu’un exécutable n’en contient généralement pas. Les images exécutables Windows ne contiennent que le minimum de relocalisations indiquant les adresses de code et de données à corriger quand l’exécutable est relocalisé, et l’image elle-même ne peut être relocalisée que globalement par rapport à son adresse de base.
À l’inverse, un fichier objet contient des relocalisations au niveau des symboles. Pour reconstruire correctement ces informations, il faut associer au désassemblage des informations assez précises sur les symboles.
Une autre grande différence est qu’un fichier objet n’a pas encore été lié. Aucun symbole n’a été résolu. C’est plutôt facile à corriger : pendant le délinking, si un symbole est en dehors de la portée actuelle, on peut généralement le remplacer par un symbole non résolu. Lors du relinking ultérieur, un autre fichier objet ou une bibliothèque devra fournir ce symbole pour que le lien soit rétabli.
Il y a aussi de petites différences, comme l’absence apparente de point d’entrée, mais elles n’ont pas grande importance.
Les limites auxquelles découper un fichier objet dans une image exécutable ou un objet partagé sont donc en pratique arbitraires. À l’origine, lors de la compilation, le code devait être découpé en unités de traduction, mais à l’étape de l’édition de liens ces frontières comptent assez peu. Bien sûr, si l’on veut une décompilation qui corresponde exactement, se tromper sur les limites des fichiers objets peut rendre les choses très difficiles ; mieux vaut donc les retrouver si possible.
J’ai déjà travaillé concrètement sur ce problème, mais il est possible que je me trompe sur certains détails, donc à prendre comme une indication. J’avais envisagé d’écrire un billet de blog sur les fichiers objets, et il existe déjà quelques bons articles sur le sujet.
Je me demande si ce processus est totalement sûr. J’aimerais savoir s’il est toujours garanti de réussir, ou si l’analyse fonctionne de manière conservatrice.
Par exemple, si certains morceaux, données ou fonctionnalités manquent dans l’ELF, est-ce que le délinking échoue ?
Mon analyseur dépend d’une base de données Ghidra exacte, au moins pour la partie à exporter. J’ai fait pas mal d’efforts pour journaliser les différents problèmes à corriger, mais on ne peut pas voir ce qui n’existe pas.
En particulier, les références manquantes et les variables tronquées ne sont pas détectées, et peuvent mener à des comportements indéfinis étranges.
Il existe des moyens de traquer une partie de ces problèmes. La meilleure méthode que j’aie trouvée jusqu’ici consiste à relinker l’exécutable à une autre adresse de base, en évitant de mapper la plage d’adresses du programme d’origine. Ainsi, les points de relocalisation absolus manqués provoquent une erreur de segmentation et peuvent être débogués. Cela n’est toutefois possible que si la cible dispose d’une MMU.
Les variables tronquées sont particulièrement difficiles à traquer si l’on ne les soupçonne pas, car ce qui est corrompu est la mémoire située après la variable tronquée. Les cas où un entier est pris à tort pour un pointeur sont eux aussi difficiles à suivre. La valeur de l’entier dépend alors de l’adresse à laquelle le symbole cible a été placé, ce qui rend le comportement du programme erratique, surtout pour les programmes chargés très bas dans l’espace d’adressage.
Cela dit, si la base de données Ghidra est suffisamment exacte, que l’on réexporte vers le même format de fichier objet que celui utilisé à l’origine, puis que l’on utilise la même plateforme et la même chaîne d’outils, il est possible de délinker avec succès des mégaoctets de code et de données de programme. À mon avis, si l’éditeur de liens l’a fait, il devrait être possible de l’annuler.
À l’inverse, si l’on commence à faire du délinking croisé qui ne correspond pas à la plateforme ni à la chaîne d’outils du programme d’origine, par exemple délinker un exécutable Linux i386 ELF vers un fichier objet COFF pour l’utiliser avec une chaîne d’outils Windows i386, c’est une autre histoire. Si l’export peut représenter les relocalisations, on peut éventuellement obtenir un fichier objet relocalisable fonctionnel, mais il faudra aussi lutter contre les incompatibilités d’ABI. C’est possible, mais je ne le recommanderais pas comme premier projet.
En résumé, selon ce que l’on fait et l’exactitude de la base de données Ghidra, cela va de « ça marche tout seul » à « prier Cthulhu pour sa miséricorde ».
Prenons par exemple une fonction comme
int getSpecialArrayElement(char *array, uint64 key) { i = computeOffset(key); return array[i]; }.computeOffsetpeut être arbitrairement complexe, et si l’on veut obfusquer le code, on peut même la rendre volontairement difficile à analyser. Rien n’empêche cette fonction d’accéder à n’importe quel emplacement mémoire.À moins d’essayer absolument toutes les entrées possibles, on ne peut pas savoir si elle accède à un symbole que l’éditeur de liens a déjà défini en mémoire, et
computeOffsetpeut aussi contenir des pièges de Turing destinés à empêcher de telles tentatives.Ce serait intéressant de relier cela à une idée que j’avais seulement imaginée autrefois, sans jamais la réaliser : générer des fichiers d’en-tête à partir des informations de débogage, puis, si nécessaire, les faire nettoyer par un LLM.
https://github.com/wbenny/pdbex
Pour la partie nettoyage par LLM, je n’ai pas encore vu beaucoup de grands succès dans l’application de modèles LLM à la rétro-ingénierie. Peut-être que c’est justement un domaine où les limites de l’architecture des LLM apparaissent
Je ne suis pas spécialiste, mais si je devais parier, je dirais que pour de nombreux cas d’usage en rétro-ingénierie, les modèles de diffusion semblent plus intéressants
Ce n’est pas exactement la même chose, mais Binary Ninja a une fonctionnalité appelée Sidekick, qui essaie de nettoyer le désassemblage avec un LLM. Personnellement, je n’ai pas été très impressionné, mais cela peut être utile à quelqu’un
paholepermet de produire des fichiers d’en-tête C compilables à partir des informations ELF DWARFIci, le LLM ne semble pas vraiment pertinent. Soit le fichier d’en-tête contient correctement tous les types exportés par l’exécutable, avec leurs valeurs d’origine, et il est utilisable ; soit il est faux ou incomplet. Faire inventer quelque chose de plus à un LLM n’aidera pas
Ghidra dispose aussi d’une fonctionnalité de base pour exporter les structures de données, et peut les générer à partir des structures DWARF. Clic droit -> Export to C header
La génération d’en-têtes, ainsi que la génération de mutateurs pouvant ensuite être modifiés en respectant les contraintes de type, en faisaient partie
En ajoutant une couche LLM, il semble possible, par exemple, de nommer des structures anonymes, mais je ne sais pas si c’est une bonne idée. Plus intéressant serait peut-être d’essayer de faire résumer en langage naturel, par un LLM, les contraintes de type connues à des fins de documentation
Si je ne l’ai pas encore implémenté, c’est parce que j’ai réussi à m’en passer jusqu’ici. Et puis ça ressemble à un sacré terrier de lapin, alors que celui dans lequel je suis déjà est largement assez profond
Ça a vraiment l’air génial, et c’est aussi lié à une idée de modding de jeu que j’avais en tête. La série d’articles de blog sur la décompilation de Tenchu était également excellente
Je n’ai pas d’usage immédiat pour ce sur quoi je travaille actuellement, mais ça ressemble à un outil qui m’aurait vraiment été utile par le passé
J’espère avoir bientôt le temps ou l’occasion de l’essayer