Cet article explique les outils et les commandes qui peuvent être utilisés pour désosser un exécutable dans un environnement Linux.
L'ingénierie inverse est l'acte de comprendre ce que fait un logiciel, pour lequel il n'y a pas de code source disponible. L'ingénierie inverse peut ne pas vous donner les détails exacts du logiciel. Mais vous pouvez assez bien comprendre comment un logiciel a été implémenté.
L'ingénierie inverse implique les trois étapes de base suivantes :
- Recueillir les informations
- Déterminer le comportement du programme
- Intercepter les appels de la bibliothèque
Je. Rassembler les informations
La première étape consiste à rassembler les informations sur le programme cible et ce qu'il fait. Pour notre exemple, nous prendrons la commande « who ». La commande ‘who’ imprime la liste des utilisateurs actuellement connectés.
1. Commande de chaînes
Strings est une commande qui imprime les chaînes de caractères imprimables dans les fichiers. Alors maintenant, utilisons cela contre notre commande cible (qui).
# strings /usr/bin/who
Certaines des chaînes importantes sont,
users=%lu EXIT COMMENT IDLE TIME LINE NAME /dev/ /var/log/wtmp /var/run/utmp /usr/share/locale Michael Stone David MacKenzie Joseph Arceneaux
À partir de la sortie about, nous pouvons savoir que « who » utilise 3 fichiers (/var/log/wtmp, /var/log/utmp, /usr/share/locale).
En savoir plus :Exemples de commandes de chaînes Linux (rechercher du texte dans des fichiers binaires UNIX)
2. Commande nm
nm, est utilisée pour lister les symboles du programme cible. En utilisant nm, nous pouvons connaître les fonctions locales et de bibliothèque ainsi que les variables globales utilisées. nm ne peut pas fonctionner sur un programme qui est rayé à l'aide de la commande "strip".
Remarque :Par défaut, la commande « who » est supprimée. Pour cet exemple, j'ai de nouveau compilé la commande "who".
# nm /usr/bin/who
Cela listera les éléments suivants :
08049110 t print_line 08049320 t time_string 08049390 t print_user 08049820 t make_id_equals_comment 080498b0 t who 0804a170 T usage 0804a4e0 T main 0804a900 T set_program_name 08051ddc b need_runlevel 08051ddd b need_users 08051dde b my_line_only 08051de0 b time_format 08051de4 b time_format_width 08051de8 B program_name 08051d24 D Version 08051d28 D exit_failure
Dans la sortie ci-dessus :
- t|T – Le symbole est présent dans la section de code .text
- b|B – Le symbole est dans la section .data initialisée UN
- D|d – Le symbole est dans la section Initialized .data.
La lettre majuscule ou minuscule détermine si le symbole est local ou global.
À partir de la sortie à propos, nous pouvons savoir ce qui suit,
- Il a la fonction globale (main,set_program_name,usage,etc..)
- Il a quelques fonctions locales (print_user,time_string etc..)
- Il a des variables globales initialisées (Version,exit_failure)
- Il contient les variables initialisées par l'ONU (time_format, time_format_width, etc.)
Parfois, en utilisant les noms des fonctions, nous pouvons deviner ce que les fonctions vont faire.
Lire la suite :10 exemples pratiques de commandes Linux nm
Les autres commandes qui peuvent être utilisées pour obtenir des informations sont
- commande ldd
- commande fuser
- commande lsof
- /système de fichiers proc
II. Détermination du comportement du programme
3. Commande ltrace
Il trace les appels à la fonction de bibliothèque. Il exécute le programme dans ce processus.
# ltrace /usr/bin/who
La sortie est illustrée ci-dessous.
utmpxname(0x8050c6c, 0xb77068f8, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0 setutxent(0x8050c6c, 0xb77068f8, 0, 0xbfc5cdc0, 0xbfc5cd78) = 1 getutxent(0x8050c6c, 0xb77068f8, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 realloc(NULL, 384) = 0x09ed59e8 getutxent(0, 384, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 realloc(0x09ed59e8, 768) = 0x09ed59e8 getutxent(0x9ed59e8, 768, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 realloc(0x09ed59e8, 1152) = 0x09ed59e8 getutxent(0x9ed59e8, 1152, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 realloc(0x09ed59e8, 1920) = 0x09ed59e8 getutxent(0x9ed59e8, 1920, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 getutxent(0x9ed59e8, 1920, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 realloc(0x09ed59e8, 3072) = 0x09ed59e8 getutxent(0x9ed59e8, 3072, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 getutxent(0x9ed59e8, 3072, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 getutxent(0x9ed59e8, 3072, 0, 0xbfc5cdc0, 0xbfc5cd78)
Vous pouvez observer qu'il existe un ensemble d'appels à getutxent et sa famille de fonctions de bibliothèque. Vous pouvez également noter que ltrace donne les résultats dans l'ordre dans lequel les fonctions sont appelées dans le programme.
Nous savons maintenant que la commande « who » fonctionne en appelant le getutxent et sa famille de fonctions pour obtenir les utilisateurs connectés.
4. Commande strace
La commande strace est utilisée pour tracer les appels système effectués par le programme. Si un programme n'utilise aucune fonction de bibliothèque et qu'il n'utilise que des appels système, alors en utilisant plain ltrace, nous ne pouvons pas suivre l'exécution du programme.
# strace /usr/bin/who
[b76e7424] brk(0x887d000) = 0x887d000 [b76e7424] access("/var/run/utmpx", F_OK) = -1 ENOENT (No such file or directory) [b76e7424] open("/var/run/utmp", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3 . . . [b76e7424] fcntl64(3, F_SETLKW, {type=F_RDLCK, whence=SEEK_SET, start=0, len=0}) = 0 [b76e7424] read(3, "\10\325"..., 384) = 384 [b76e7424] fcntl64(3, F_SETLKW, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
Vous pouvez observer que chaque fois que la fonction malloc est appelée, elle appelle l'appel système brk(). La fonction de la bibliothèque getutxent appelle en fait l'appel système 'open' pour ouvrir '/var/run/utmp' et il met un verrou en lecture et lit le contenu puis libère les verrous.
Maintenant, nous avons confirmé que la commande who a lu le fichier utmp pour afficher la sortie.
'strace' et 'ltrace' ont tous deux un ensemble de bonnes options qui peuvent être utilisées.
- -p pid – Attache au pid spécifié. Utile si le programme est déjà en cours d'exécution et que vous souhaitez connaître son comportement.
- -n 2 – Indenter chaque appel imbriqué de 2 espaces.
- -f - Suivre la fourche
Lire la suite :7 exemples Strace pour déboguer l'exécution d'un programme sous Linux
III. Intercepter les appels de la bibliothèque
5. LD_PRELOAD &LD_LIBRARY_PATH
LD_PRELOAD nous permet d'ajouter une bibliothèque à une exécution particulière du programme. La fonction de cette bibliothèque écrasera la fonction de bibliothèque actuelle.
Remarque :Nous ne pouvons pas l'utiliser avec des programmes définis avec le bit "suid".
Prenons le programme suivant.
#include <stdio.h> int main() { char str1[]="TGS"; char str2[]="tgs"; if(strcmp(str1,str2)) { printf("String are not matched\n"); } else { printf("Strings are matched\n"); } }
Compilez et exécutez le programme.
# cc -o my_prg my_prg.c # ./my_prg
Il affichera "Les chaînes ne correspondent pas".
Nous allons maintenant écrire notre propre bibliothèque et nous verrons comment nous pouvons intercepter la fonction de bibliothèque.
#include <stdio.h> int strcmp(const char *s1, const char *s2) { // Always return 0. return 0; }
Compilez et définissez la variable LD_LIBRARY_PATH sur le répertoire courant.
# cc -o mylibrary.so -shared library.c -ldl # LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
Maintenant, un fichier nommé "library.so" sera créé.
Définissez la variable LD_PRELOAD sur ce fichier et exécutez le programme de comparaison de chaînes.
# LD_PRELOAD=mylibrary.so ./my_prg
Maintenant, il affichera "Les chaînes correspondent" car il utilise notre version de la fonction strcmp.
Remarque :Si vous souhaitez intercepter une fonction de bibliothèque, votre propre fonction de bibliothèque doit avoir le même prototype que la fonction de bibliothèque d'origine.
Nous venons de couvrir les éléments de base nécessaires à la rétro-ingénierie d'un programme.
Pour ceux qui souhaitent passer à l'étape suivante de l'ingénierie inverse, la compréhension du format de fichier ELF et du programme de langage d'assemblage aidera dans une plus grande mesure.