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

Vous avez un fichier mystère? La commande de fichier Linux vous indiquera rapidement de quel type de fichier il s’agit. S’il s’agit d’un fichier binaire, vous pouvez en savoir plus à ce sujet. file a toute une série de partenaires stables 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’ils contiennent. 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 comporte une forme d’identité.

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 le 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 de fichier.

Utilisation de la commande de fichier

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

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

ls -hl

ls -hl dans une fenêtre de terminal.

Essayons de classer quelques-uns d’entre eux et voyons ce que nous obtenons:

file build_instructions.odt
file build_instructions.pdf
file COBOL_Report_Apr60.djvu

fichier build_instructions.odt dans une fenêtre de terminal.

Les trois formats de fichiers sont correctement identifiés. Dans la mesure du possible, le fichier nous donne un peu plus d’informations. Le fichier PDF serait dans le 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 Fichiers, l’icône appropriée est attribuée. Sur la ligne de commande, file ignore l’extension et regarde à l’intérieur du fichier pour déterminer son type:

file build_instructions.xyz

fichier build_instructions.xyz dans une fenêtre de terminal.

L’utilisation de fichiers sur des supports, tels que des fichiers d’image et de musique, fournit généralement des informations concernant leur format, leur encodage, leur résolution, etc.

file screenshot.png
file screenshot.jpg
file Pachelbel_Canon_In_D.mp3

file screenshot.png dans une fenêtre de terminal.

Fait intéressant, même avec des fichiers en texte brut, le fichier 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 du code source, le fichier ne le prend pas pour un véritable C fichier de code source:

file function+headers.h
file makefile
file hello.c

fonction de fichier + headers.h dans une fenêtre de terminal.

file identifie correctement le fichier 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.

  Comment extraire plusieurs pistes audio d'une vidéo sous Windows 10

Utilisation d’un 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 logiciel approprié. Les fichiers binaires, cependant, sont plus un défi.

Par exemple, les fichiers «hello» et «wd» sont des exécutables binaires. Ce sont des programmes. Le fichier appelé «wd.o» est un fichier objet. Lorsque le code source est compilé par un compilateur, un ou plusieurs fichiers objets 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é de manière croisée pour s’exécuter sous Windows:

file wd
file wd.o
file hello
file watch.exe

fichier wd dans une fenêtre de terminal.

En prenant le dernier en premier, le fichier nous indique que le fichier «watch.exe» est un exécutable PE32 +, programme console, pour la famille de processeurs x86 sous 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 Format exécutable et liable (ELF). 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 examiner le 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 Base standard Linux (LSB) objets partagés, et le fichier objet «wd.o» est identifié comme un LSB relocalisable. Le mot exécutable est évident en son absence.

Les fichiers objets sont déplaçables, 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 sorte qu’ils héritent de cette fonctionnalité.

Cela permet au Randomisation de la disposition de l’espace d’adressage (ASMR) pour 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.

  Comment modifier votre nom d'utilisateur et votre avatar Google Stadia

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. Positionner les exécutables indépendants (PIE) positionné à une adresse aléatoire surmonte cette susceptibilité.

Si nous compiler notre programme avec le compilateur gcc et fournir l’option -no-pie, nous allons générer un exécutable conventionnel.

L’option -o (fichier de sortie) nous permet de fournir un nom pour notre exécutable:

gcc -o hello -no-pie hello.c

Nous utiliserons le fichier sur le nouvel exécutable et verrons ce qui a changé:

file hello

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

ls -hl hello

gcc -o bonjour -no-pie bonjour.c dans une fenêtre de terminal.

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 il pourrait difficilement être qualifié de gros, mais alors, tout est relatif. Le code source est de 120 octets:

cat hello.c

Qu’est-ce qui gonfle le binaire si tout ce qu’il fait est 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, ça doit être autre chose:

ls -hl hello

cat hello.c dans une fenêtre de terminal.

Allons scannez le binaire avec le strings comme une première étape simple pour découvrir ce qu’il contient. Nous allons le réduire en moins:

strings hello | less

cordes bonjour | moins dans une fenêtre de terminal.

Il y a de nombreuses 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 dans le binaire, ainsi que les noms et les informations de liaison des objets partagés. Celles-ci incluent les bibliothèques et les fonctions de ces bibliothèques, dont dépend le binaire.

le commande ldd nous montre les dépendances d’objets partagés d’un binaire:

ldd hello

ldd bonjour dans une fenêtre de terminal.

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

linux-vdso.so: Objet partagé dynamique virtuel (VDSO) est un mécanisme de noyau qui permet d’accéder à un ensemble de routines d’espace noyau par un binaire d’espace utilisateur. Ce évite la surcharge d’un changement de contexte depuis le 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 norme Bibliothèque GNU C si le noyau prend en charge le schéma ASMR.
libc.so.6: Le Bibliothèque GNU C objet partagé.
/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 quelles dépendances il a. Il lance ces objets partagés en mémoire. Il prépare le binaire à s’exécuter et à trouver et accéder aux dépendances en mémoire. Ensuite, il lance le programme.

  Mettez en surbrillance le texte et atténuez les autres éléments de la page pour mieux lire

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 hello

readelf -h bonjour dans une fenêtre de terminal.

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

Sortie de readelf -h bonjour dans une fenêtre de terminal.

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 comme un binaire ELF. Pour rendre cela parfaitement clair, les trois octets suivants épellent «ELF» dans ASCII:

Classe: indique si le binaire est un exécutable 32 ou 64 bits (1 = 32, 2 = 64).
Données: indique le endianité utilisé. Le codage endian définit la manière dont les nombres multi-octets sont stockés. Dans l’encodage 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 de interface binaire d’application utilisé. Cela définit l’interface entre deux modules binaires, tels qu’un programme et une bibliothèque partagée.
Version ABI: La version de l’ABI.
Type: Le type de binaire ELF. Les valeurs courantes sont ET_REL pour une ressource relocalisable (comme 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: Le Architecture d’ensemble d’instructions. Cela 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 aperçu rapide des huit premiers octets du binaire avec hexdump affichera 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 hello

hexdump -C -n 8 bonjour dans une fenêtre de terminal.

objdump et la vue granulaire

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

objdump -d hello | less

objdump -d bonjour | moins dans une fenêtre de terminal.

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

Cela n’est utile que si vous pouvez lire le langage d’assemblage ou si vous êtes curieux de ce qui se passe derrière le rideau. Il y a beaucoup de sortie, donc nous l’avons diffusée dans moins.

Putput de objdump -d bonjour | moins dans une fenêtre de terminal.

Compilation et liaison

Il existe de nombreuses façons de compiler un binaire. Par exemple, le développeur choisit d’inclure ou non les 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 se lient statiquement.

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