1 points par GN⁺ 2024-08-24 | 1 commentaires | Partager sur WhatsApp
  • 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_DIR vers 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 ajouter githubToken=ghp_xxx dans ${GRADLE_USER_HOME}/gradle.properties
    • ou exécuter gradle installStandaloneDeps pour construire et installer la dépendance vendored du submodule
  • 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

 
GN⁺ 2024-08-24
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-extension
    En 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

    • Une façon de suivre ce qu’il faut délinker consiste à utiliser les dossiers et fragments dans l’arborescence du programme
      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

  • 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 ?

    • Tant que vous ne coupez pas au milieu d’une variable ou d’une fonction, vous pouvez exporter assez librement, et il n’est pas nécessaire de suivre exactement les frontières des fichiers objets d’origine
      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.

    • En résumé, un fichier objet se compose de trois parties : des octets de sections relocalisables, une table de relocalisation et une table de symboles.
      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.
    • Ce ne sera pas facile sur toutes les architectures CPU, mais ce n’est pas aussi absurde que ça en a l’air. Fondamentalement, il n’y a pas une si grande différence entre un fichier objet et un exécutable ou une bibliothèque partagée.
      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 ?

    • C’est compliqué de répondre.
      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 ».
    • Si je comprends bien la question, ce n’est pas totalement sûr, et ça ne peut pas l’être.
      Prenons par exemple une fonction comme int getSpecialArrayElement(char *array, uint64 key) { i = computeOffset(key); return array[i]; }.
      computeOffset peut ê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 computeOffset peut 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.

    • Il existe effectivement quelques tentatives de ce genre. Pour Microsoft Program Database, il y a ceci
      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
    • pahole permet de produire des fichiers d’en-tête C compilables à partir des informations ELF DWARF
      Ici, 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
    • Il y a quelques années, j’ai créé un outil qui génère et insère automatiquement un fuzzer sensible aux types pour des API C à partir des informations DWARF : https://github.com/intel/fffc
      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
    • C’est un peu un autre sujet, mais j’ai déjà envisagé de générer des symboles de débogage pour les fichiers objet exportés, à partir du contenu de la base de données Ghidra, afin d’améliorer l’expérience de débogage
      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

    • Il faudra que je revienne un jour sur ce projet. J’ai dû faire une pause après avoir enchaîné trop de sessions de suivi de versions, et entre-temps la quête annexe de déliaison n’arrête pas de prendre des proportions incontrôlables
  • 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