Comment désassembler, modifier puis réassembler un exécutable Linux?

Y a-t-il un moyen de le faire? J’ai utilisé objdump mais cela ne produit pas de sortie d’assemblage qui sera acceptée par tout assembleur que je connais. Je voudrais pouvoir changer les instructions dans un exécutable et le tester ensuite.

Je ne pense pas qu’il existe un moyen fiable de le faire. Les formats de code machine sont très compliqués, plus compliqués que les fichiers d’assemblage. Il n’est pas vraiment possible de prendre un fichier binary compilé (par exemple, au format ELF) et de produire un programme d’assemblage source qui comstackra vers le même fichier binary (ou similaire). Pour mieux comprendre les différences, comparez la sortie de la compilation GCC directement à l’assembleur ( gcc -S ) par rapport à la sortie de objdump sur l’exécutable ( objdump -D ).

Je peux penser à deux complications majeures. Tout d’abord, le code machine lui-même n’est pas une correspondance de 1 à 1 avec le code de l’assemblage, à cause de choses comme les décalages de pointeurs.

Par exemple, considérons le code C pour Hello world:

 int main() { printf("Hello, world!\n"); return 0; } 

Cela comstack le code de l’assembly x86:

 .LC0: .ssortingng "hello" .text  movl $.LC0, %eax movl %eax, (%esp) call printf 

Où .LCO est une constante nommée et printf est un symbole dans une table de symboles de bibliothèque partagée. Comparez à la sortie de objdump:

 80483cd: b8 b0 84 04 08 mov $0x80484b0,%eax 80483d2: 89 04 24 mov %eax,(%esp) 80483d5: e8 1a ff ff ff call 80482f4  

Premièrement, la constante .LC0 n’est plus qu’un décalage aléatoire en mémoire quelque part – il serait difficile de créer un fichier source d’assemblage contenant cette constante au bon endroit, car l’assembleur et l’éditeur de liens sont libres de choisir des emplacements pour ces constantes.

Deuxièmement, je ne suis pas tout à fait sûr de cela (et cela dépend de choses comme le code indépendant de la position), mais je pense que la référence à printf n’est pas encodée à l’adresse du pointeur, mais les en-têtes ELF contiennent table de recherche qui remplace dynamicment son adresse à l’exécution. Par conséquent, le code désassemblé ne correspond pas tout à fait au code de l’assembly source.

En résumé, l’assembly source a des symboles alors que le code machine compilé a des adresses difficiles à inverser.

La deuxième complication majeure est qu’un fichier source d’assembly ne peut pas contenir toutes les informations présentes dans les en-têtes de fichier ELF d’origine, telles que les bibliothèques à associer dynamicment et les autres métadonnées placées par le compilateur d’origine. Il serait difficile de le reconstruire.

Comme je l’ai dit, il est possible qu’un outil spécial puisse manipuler toutes ces informations, mais il est peu probable que l’on puisse simplement produire un code d’assemblage pouvant être réassemblé dans l’exécutable.

Si vous êtes intéressé par la modification d’une petite partie de l’exécutable, je recommande une approche beaucoup plus subtile que la recompilation de toute l’application. Utilisez objdump pour obtenir le code d’assemblage de la ou des fonctions qui vous intéressent. Convertissez-le en “syntaxe d’assemblage source” à la main (et ici, j’aimerais qu’un outil produise un désassemblage dans la même syntaxe que l’entrée) et modifiez-le comme vous le souhaitez. Lorsque vous avez terminé, recomstackz simplement ces fonctions et utilisez objdump pour déterminer le code machine de votre programme modifié. Ensuite, utilisez un éditeur hexadécimal pour coller manuellement le nouveau code machine en haut de la partie correspondante du programme d’origine, en veillant à ce que votre nouveau code ait exactement le même nombre d’octets que l’ancien code (ou tous les décalages seraient incorrects) ). Si le nouveau code est plus court, vous pouvez le remplir en utilisant les instructions NOP. S’il est plus long, vous pourriez avoir des problèmes et devoir créer de nouvelles fonctions et les appeler à la place.

Pour changer le code à l’intérieur d’un assemblage binary, il y a généralement 3 façons de le faire.

  • Si c’est juste une chose sortingviale comme une constante, alors vous changez simplement l’emplacement avec un éditeur hexadécimal. En supposant que vous pouvez le trouver pour commencer.
  • Si vous devez modifier le code, utilisez LD_PRELOAD pour écraser une fonction de votre programme. Cela ne fonctionne pas si la fonction n’est pas dans les tables de fonction si.
  • Hack le code à la fonction que vous souhaitez réparer pour être un saut direct sur une fonction que vous chargez via LD_PRELOAD, puis revenir au même endroit (Ceci est une combinaison des deux précédentes)

Bien sûr, seul le 2ème fonctionnera, si l’assemblée effectue une vérification de son intégrité.

Edit: Si ce n’est pas évident, jouer avec des assemblages binarys est TRÈS élevé pour les développeurs, et vous aurez du mal à le demander ici, à moins que ce ne soit des choses vraiment spécifiques que vous demandez.

@mgiuca a correctement répondu à cette réponse d’un sharepoint vue technique. En fait, le déassembly d’un programme exécutable dans une source d’assemblage facile à recomstackr n’est pas une tâche facile.

Pour append quelques éléments à la discussion, il existe quelques techniques / outils qui pourraient être intéressants à explorer, bien qu’ils soient techniquement complexes.

  1. Instrumentation statique / dynamic . Cette technique consiste à parsingr le format de l’exécutable, insérer / supprimer / remplacer des instructions d’assemblage spécifiques pour un usage donné, fixer toutes les références aux variables / fonctions dans l’exécutable et émettre un nouvel exécutable modifié. Certains des outils que je connais sont: PIN , pirate de l’air , PEBIL , DynamoRIO . Considérez que la configuration de ces outils à des fins différentes de celles pour lesquelles ils ont été conçus peut être délicate et nécessite une compréhension à la fois des formats exécutables et des jeux d’instructions.
  2. Décompilation complète exécutable . Cette technique tente de reconstruire une source d’assemblage complète à partir d’un exécutable. Vous voudrez peut-être jeter un coup d’œil au désassembleur en ligne , qui essaie de faire le travail. Vous perdez toute information sur les différents modules sources et éventuellement les noms de fonctions / variables.
  3. Décompilable . Cette technique tente d’extraire plus d’informations de l’exécutable, en examinant les empreintes digitales du compilateur (c’est-à-dire les modèles de code générés par des compilateurs connus) et d’autres éléments déterministes. L’objective principal est de reconstruire le code source de haut niveau, tel que le source C, à partir d’un exécutable. Cela permet parfois de récupérer des informations sur les noms de fonctions / variables. Considérez que la compilation de sources avec -g offre souvent de meilleurs résultats. Vous voudrez peut-être essayer le Retargetable Decomstackr .

La plupart de ces informations proviennent des domaines de l’évaluation de la vulnérabilité et de l’parsing de l’exécution. Ce sont des techniques complexes et les outils ne peuvent souvent pas être utilisés immédiatement. Néanmoins, ils apportent une aide inestimable lors de la tentative d’ingénierie inverse de certains logiciels.

miasme

https://github.com/cea-sec/miasm

Cela semble être la solution concrète la plus prometteuse. Selon la description du projet, la bibliothèque peut:

  • Ouverture / modification / génération de PE / ELF 32/64 LE / BE avec Elfesteem
  • Assemblage / Déassembly X86 / ARM / MIPS / SH4 / MSP430

Donc, en gros:

  • parsingr l’ELF en une représentation interne (déassembly)
  • modifier ce que vous voulez
  • générer un nouvel ELF (assembly)

Je ne pense pas que cela génère une représentation de désassemblage textuel, vous devrez probablement parcourir les structures de données Python.

TODO trouve un exemple minimal de la façon de faire tout cela en utilisant la bibliothèque. Un bon sharepoint départ semble être l’ exemple / disasm / full.py , qui parsing un fichier ELF donné. La structure principale de niveau supérieur est Container , qui lit le fichier ELF avec Container.from_stream . TODO comment le remonter ensuite? Cet article semble le faire: http://www.miasm.re/blog/2016/03/24/re150_rebuild.html

Cette question demande s’il existe d’autres bibliothèques de ce type: https://reverseengineering.stackexchange.com/questions/1843/what-are-the-available-libraries-to-stically-modify-elf-executables

Questions connexes:

Je pense que ce problème n’est pas automatisable

Je pense que le problème général n’est pas entièrement automatisable, et la solution générale est fondamentalement équivalente à “comment désosser” un binary.

Afin d’insérer ou de supprimer des octets de manière significative, nous devrions nous assurer que tous les sauts possibles continuent de sauter aux mêmes emplacements.

En termes formels, nous devons extraire le graphe de stream de contrôle du binary.

Cependant, avec les twigs indirectes par exemple, https://en.wikipedia.org/wiki/Indirect_branch , il n’est pas facile de déterminer ce graphique, voir aussi: Calcul de la destination du saut indirect

Une autre chose que vous pourriez être intéressé à faire:

  • instrumentation binary – changer le code existant

Si vous êtes intéressé, jetez un coup d’œil à: Pin, Valgrind (ou les projets qui font ceci: NaCl – le client natif de Google, peut-être QEmu.)

Vous pouvez exécuter l’exécutable sous la supervision de ptrace (en d’autres termes, un débogueur comme gdb) et contrôler ainsi l’exécution au fur et à mesure, sans modifier le fichier réel. Bien sûr, cela nécessite les compétences d’édition habituelles, telles que la recherche d’influence sur les instructions que vous souhaitez influencer.