Comment jeter un coup d'œil à l'intérieur des fichiers binaires à partir de la ligne de commande Linux

Publié: 2022-01-29
Un terminal Linux stylisé avec des lignes de texte vert sur un ordinateur portable.
fatmawati achmad zaenuri/Shutterstock

Vous avez un dossier mystère ? La commande file Linux vous indiquera rapidement de quel type de fichier il s'agit. S'il s'agit d'un fichier binaire, cependant, vous pouvez en savoir encore plus à ce sujet. file a toute une série de stablemates qui vous aideront à l'analyser. Nous allons vous montrer comment utiliser certains de ces outils.

Identification des types de fichiers

Les fichiers ont généralement des caractéristiques qui permettent aux progiciels d'identifier de quel type de fichier il s'agit, ainsi que ce que les données qu'il contient représentent. Cela n'aurait aucun sens d'essayer d'ouvrir un fichier PNG dans un lecteur de musique MP3, il est donc à la fois utile et pragmatique qu'un fichier porte avec lui une forme d'identification.

Il peut s'agir de quelques octets de signature au tout début du fichier. Cela permet à un fichier d'être explicite sur son format et son contenu. Parfois, le type de fichier est déduit d'un aspect distinctif de l'organisation interne des données elles-mêmes, connu sous le nom d'architecture de fichier.

Certains systèmes d'exploitation, comme Windows, sont entièrement guidés par l'extension d'un fichier. Vous pouvez l'appeler crédule ou confiant, mais Windows suppose que tout fichier avec l'extension DOCX est vraiment un fichier de traitement de texte DOCX. Linux n'est pas comme ça, comme vous le verrez bientôt. Il veut une preuve et regarde à l'intérieur du fichier pour la trouver.

Les outils décrits ici étaient déjà installés sur les distributions Manjaro 20, Fedora 21 et Ubuntu 20.04 que nous avons utilisées pour rechercher cet article. Commençons notre enquête en utilisant la commande file .

Utilisation de la commande de fichier

Nous avons une collection de différents types de fichiers dans notre répertoire actuel. Il s'agit d'un mélange de documents, de code source, d'exécutables et de fichiers texte.

Publicité

La commande ls nous montrera ce qu'il y a dans le répertoire, et l' -hl (tailles lisibles par l'homme, liste longue) nous montrera la taille de chaque fichier :

 ls -hl 

Essayons de file quelques-uns d'entre eux et voyons ce que nous obtenons :

 fichier build_instructions.odt
 fichier build_instructions.pdf
 fichier COBOL_Report_Apr60.djvu 

Les trois formats de fichiers sont correctement identifiés. Dans la mesure du possible, file nous donne un peu plus d'informations. Le fichier PDF est signalé comme étant au format de la version 1.5.

Même si nous renommons le fichier ODT pour avoir une extension avec la valeur arbitraire de XYZ, le fichier est toujours correctement identifié, à la fois dans le navigateur de fichiers Files et sur la ligne de commande en utilisant file .

Fichier OpenDocument correctement identifié dans le navigateur de fichiers Fichiers, même si son extension est XYZ.

Dans le navigateur de fichiers Files , l'icône correcte lui est attribuée. Sur la ligne de commande, file ignore l'extension et regarde à l'intérieur du fichier pour déterminer son type :

 fichier build_instructions.xyz 

Publicité

L'utilisation de file sur des supports, tels que des fichiers d'images et de musique, fournit généralement des informations sur leur format, leur encodage, leur résolution, etc. :

 fichier screenshot.png
 capture d'écran du fichier.jpg
 fichier Pachelbel_Canon_In_D.mp3 

Fait intéressant, même avec des fichiers en texte brut, file ne juge pas le fichier par son extension. Par exemple, si vous avez un fichier avec l'extension ".c", contenant du texte brut standard mais pas de code source, file ne le confond pas avec un véritable fichier de code source C :

 fonction de fichier + en-têtes.h
 fichier makefile
 fichier hello.c 

identifie correctement le file d'en-tête (".h") comme faisant partie d'une collection de fichiers de code source C, et il sait que le makefile est un script.

Utilisation de fichier avec des fichiers binaires

Les fichiers binaires sont plus une "boîte noire" que d'autres. Les fichiers image peuvent être visualisés, les fichiers audio peuvent être lus et les fichiers de document peuvent être ouverts par le progiciel approprié. Les fichiers binaires, cependant, sont plus difficiles.

Par exemple, les fichiers « hello » et « wd » sont des exécutables binaires. Ce sont des programmes. Le fichier nommé « wd.o » est un fichier objet. Lorsque le code source est compilé par un compilateur, un ou plusieurs fichiers objet sont créés. Ceux-ci contiennent le code machine que l'ordinateur exécutera éventuellement lorsque le programme terminé s'exécutera, ainsi que des informations pour l'éditeur de liens. L'éditeur de liens vérifie chaque fichier objet pour les appels de fonction aux bibliothèques. Il les relie à toutes les bibliothèques utilisées par le programme. Le résultat de ce processus est un fichier exécutable.

Le fichier « watch.exe » est un exécutable binaire qui a été compilé pour fonctionner sous Windows :

 fichier wd
 fichier wd.o
 fichier bonjour
 fichier watch.exe 

Publicité

En prenant le dernier en premier, file nous indique que le fichier "watch.exe" est un exécutable PE32 +, programme de console, pour la famille de processeurs x86 sur Microsoft Windows. PE signifie format exécutable portable, qui a des versions 32 et 64 bits. Le PE32 est la version 32 bits et le PE32+ est la version 64 bits.

Les trois autres fichiers sont tous identifiés comme des fichiers ELF (Executable and Linkable Format). Il s'agit d'une norme pour les fichiers exécutables et les fichiers d'objets partagés, tels que les bibliothèques. Nous allons jeter un coup d'œil au format d'en-tête ELF sous peu.

Ce qui pourrait attirer votre attention, c'est que les deux exécutables ("wd" et "hello") sont identifiés comme des objets partagés Linux Standard Base (LSB) et que le fichier objet "wd.o" est identifié comme un LSB déplaçable. Le mot exécutable est évident en son absence.

Les fichiers objets sont relocalisables, ce qui signifie que le code qu'ils contiennent peut être chargé en mémoire à n'importe quel endroit. Les exécutables sont répertoriés en tant qu'objets partagés car ils ont été créés par l'éditeur de liens à partir des fichiers objets de telle manière qu'ils héritent de cette capacité.

Cela permet au système ASMR (Address Space Layout Randomization) de charger les exécutables en mémoire aux adresses de son choix. Les exécutables standard ont une adresse de chargement codée dans leurs en-têtes, qui dicte où ils sont chargés en mémoire.

L'ASMR est une technique de sécurité. Le chargement d'exécutables en mémoire à des adresses prévisibles les rend vulnérables aux attaques. En effet, leurs points d'entrée et les emplacements de leurs fonctions seront toujours connus des attaquants. Les exécutables indépendants de la position (PIE) positionnés à une adresse aléatoire surmontent cette susceptibilité.

Publicité

Si nous compilons notre programme avec le compilateur gcc et fournissons l'option -no-pie , nous générerons un exécutable conventionnel.

L'option -o (fichier de sortie) nous permet de donner un nom à notre exécutable :

 gcc -o bonjour -no-pie bonjour.c

Nous allons utiliser file sur le nouvel exécutable et voir ce qui a changé :

 fichier bonjour

La taille de l'exécutable est la même qu'avant (17 Ko) :

 ls -hl bonjour 

Le binaire est maintenant identifié comme un exécutable standard. Nous faisons cela à des fins de démonstration uniquement. Si vous compilez des applications de cette façon, vous perdrez tous les avantages de l'ASMR.

Pourquoi un exécutable est-il si gros ?

Notre exemple de programme hello fait 17 Ko, donc on pourrait difficilement l'appeler gros, mais tout est relatif. Le code source fait 120 octets :

 chat bonjour.c
Publicité

Qu'est-ce qui gonfle le binaire s'il ne fait qu'imprimer une chaîne dans la fenêtre du terminal? Nous savons qu'il existe un en-tête ELF, mais il ne fait que 64 octets pour un binaire 64 bits. En clair, il doit s'agir d'autre chose :

 ls -hl bonjour 

Analysons le binaire avec la commande strings comme première étape simple pour découvrir ce qu'il contient. Nous allons le diriger vers less :

 cordes bonjour | moins 

Il y a beaucoup de chaînes à l'intérieur du binaire, en plus du "Hello, Geek world!" à partir de notre code source. La plupart d'entre eux sont des étiquettes pour les régions du binaire, ainsi que les noms et les informations de liaison des objets partagés. Ceux-ci incluent les bibliothèques et les fonctions au sein de ces bibliothèques, dont dépend le binaire.

La commande ldd nous montre les dépendances d'objet partagé d'un binaire :

 ldd bonjour 

Il y a trois entrées dans la sortie, et deux d'entre elles incluent un chemin de répertoire (la première n'en a pas) :

  • linux-vdso.so : Virtual Dynamic Shared Object (VDSO) est un mécanisme de noyau qui permet à un ensemble de routines de l'espace noyau d'être accessible par un binaire de l'espace utilisateur. Cela évite la surcharge d'un changement de contexte à partir du mode noyau utilisateur. Les objets partagés VDSO adhèrent au format ELF (Executable and Linkable Format), ce qui leur permet d'être liés dynamiquement au binaire lors de l'exécution. Le VDSO est alloué dynamiquement et tire parti de l'ASMR. La capacité VDSO est fournie par la bibliothèque GNU C standard si le noyau prend en charge le schéma ASMR.
  • libc.so.6 : l'objet partagé de la bibliothèque GNU C.
  • /lib64/ld-linux-x86-64.so.2 : c'est l'éditeur de liens dynamique que le binaire veut utiliser. L'éditeur de liens dynamique interroge le binaire pour découvrir ses dépendances. Il lance ces objets partagés en mémoire. Il prépare le binaire à s'exécuter et peut trouver et accéder aux dépendances en mémoire. Ensuite, il lance le programme.

L'en-tête ELF

Nous pouvons examiner et décoder l'en-tête ELF à l'aide de l'utilitaire readelf et de l'option -h (en-tête de fichier) :

 readelf -h bonjour 

L'en-tête est interprété pour nous.

Publicité

Le premier octet de tous les binaires ELF est défini sur la valeur hexadécimale 0x7F. Les trois octets suivants sont définis sur 0x45, 0x4C et 0x46. Le premier octet est un indicateur qui identifie le fichier en tant que binaire ELF. Pour rendre cela parfaitement clair, les trois octets suivants épellent "ELF" en ASCII :

  • Classe : indique si le binaire est un exécutable 32 ou 64 bits (1=32, 2=64).
  • Données : Indique le endianness utilisé. Le codage Endian définit la manière dont les nombres multi-octets sont stockés. Dans le codage big-endian, un nombre est stocké avec ses bits les plus significatifs en premier. Dans le codage little-endian, le nombre est stocké avec ses bits les moins significatifs en premier.
  • Version : La version d'ELF (actuellement, c'est 1).
  • OS/ABI : Représente le type d'interface binaire d'application utilisée. Ceci définit l'interface entre deux modules binaires, comme un programme et une bibliothèque partagée.
  • Version ABI : La version de l'ABI.
  • Type : Le type de binaire ELF. Les valeurs communes sont ET_REL pour une ressource réadressable (telle qu'un fichier objet), ET_EXEC pour un exécutable compilé avec l'indicateur -no-pie et ET_DYN pour un exécutable compatible ASMR.
  • Machine : L'architecture du jeu d'instructions. Ceci indique la plate-forme cible pour laquelle le binaire a été créé.
  • Version : toujours défini sur 1, pour cette version d'ELF.
  • Adresse du point d'entrée : L'adresse mémoire dans le binaire à laquelle l'exécution commence.

Les autres entrées sont des tailles et des nombres de régions et de sections dans le binaire afin que leurs emplacements puissent être calculés.

Un coup d'œil rapide sur les huit premiers octets du binaire avec hexdump montrera l'octet de signature et la chaîne "ELF" dans les quatre premiers octets du fichier. L'option -C (canonique) nous donne la représentation ASCII des octets à côté de leurs valeurs hexadécimales, et l'option -n (nombre) nous permet de spécifier le nombre d'octets que nous voulons voir :

 hexdump -C -n 8 bonjour 

objdump et la vue granulaire

Si vous voulez voir les moindres détails, vous pouvez utiliser la commande objdump avec l'option -d (désassembler) :

 objdump -d bonjour | moins 

Cela désassemble le code machine exécutable et l'affiche en octets hexadécimaux à côté de l'équivalent en langage d'assemblage. L'emplacement de l'adresse du premier bye de chaque ligne est affiché à l'extrême gauche.

Ceci n'est utile que si vous pouvez lire le langage d'assemblage ou si vous êtes curieux de savoir ce qui se passe derrière le rideau. Il y a beaucoup de sortie, donc nous l'avons dirigée vers less .

Compilation et liaison

Il existe plusieurs façons de compiler un binaire. Par exemple, le développeur choisit d'inclure ou non des informations de débogage. La façon dont le binaire est lié joue également un rôle dans son contenu et sa taille. Si les références binaires partagent des objets en tant que dépendances externes, il sera plus petit que celui auquel les dépendances sont liées de manière statique.

Publicité

La plupart des développeurs connaissent déjà les commandes que nous avons couvertes ici. Pour d'autres, cependant, ils offrent des moyens simples de fouiller et de voir ce qui se cache à l'intérieur de la boîte noire binaire.