Prerequis
Connaître Linux un minimum
Des bases (ou plus) en programmation C
Savoir utiliser les pages de manuel de Linux (man man)
Savoir chercher sur google les termes que l'on ne connaît pas
Avant propos
Avant tout, vous devriez aller jeter un œil ICI pour avoir une bonne image globale sur le reversing.
Ce tutoriel et les suivants sont réservés au reversing de binaires sous un environnement Linux. Tous les exemples que vous pourrez trouver sont réalisés sur une Debian testing sous Amd64, donc en 64bits. En cas de changement de version, elle sera précisée dans l'article en question ainsi que les versions des différents outils (gcc, gdb ...).
Pour celui-ci :
version de gcc : 4.7.3-4
Quelques outils pratiques pour le reversing de binaires Linux
Notre cher sakarov vous a déjà fourni quelques outils pratiques pour le reversing. Ici je ne rajouterai que les outils pour le reversing de binaires statiques/dynamiques (on y reviendra) Linux
file - Determine file type
C'est certainement l'outil le plus utile lorsque l'on fait du reversing. En effet comment savoir de quel type de fichier vous allez faire l'analyse ? Je doute que GDB réussirait a lancer une image jpeg. C'est pourquoi file est indispensable. cette commande vous affiche le type de fichier que vous lui passez en paramètre parfois avec quelques informations supplémentaires. Si vous voulez plus de précisions sur les types de fichiers : http://fr.wikipedia.org/wiki/Type_MIME.
Essayez donc de lancer ces commandes :
objdump, hexdump, hexedit, gcc, gdb
Vous avez déjà du lire ça dans le tutoriel de sakarov. J'ai ajoute hexedit, un éditeur hexadécimal en ligne de commande assez utile.
ltrace - A library call tracer
Comme sa description l'indique cet outil se charge de tracer les différents appels de fonctions qu'un binaire effectue au cours de son cycle de vie. Les fonctions externes que vous appelez dans vos programmes tels que printf, strcmp ... font partie d'une librairie qui est liée à votre binaire lorsque vous le compilez. C'est ces fonctions-là que ltrace va intercepter pour vous les afficher.
Exemple avec la commande ls (qui est un programme se trouvant dans /bin/ si vous n’êtes pas au courant) :
Dans la partie gauche on retrouve les fonctions avec leurs paramètres, dans la partie droite ce que la fonction a renvoyé. Ainsi dans notre exemple on y retrouve des appels à readdir, malloc, memcpy, strlen ... des fonctions que vous avez sûrement dû déjà utiliser.
Une simple lecture rapide nous montre le flux général d’exécution sans entrer dans les détails. On peut donc constater (entre autres) que le programme ouvre un dossier avec opendir (man opendir) qui renvoie une structure se trouvant à l'adresse "0x024cbc20" puis il est parcouru grâce à readdir (man readdir) qui prend l'adresse de cette structure en paramètre. Je vous laisse deviner la suite.
Essayez donc de compiler ce code et de lancer ltrace sur le binaire :
Comme quoi il n'y a pas forcément toujours besoin de lancer un tas d'outils.
On peut tout de même y voir un sérieux défaut, en effet, les fonctions des librairies utilisées sont affichées avec les paramètres et valeurs de retour, mais qu'en est-il des fonctions écrites par le développeur ?
Vous l'aurez compris ltrace ne peut pas faire le job car ces fonctions-là ne sont pas dans une librairie (sauf si vous créez votre librairie). Un développeur qui va recoder des algorithmes existant sans ce service de librairie ne sera donc pas sujet au tracing fait par ltrace.
strace - Trace system calls and signals
Ce second outil est presque similaire à ltrace. Lui aussi sert à tracer votre binaire seulement il ne va pas tracer les fonctions appelées depuis des librairies mais des appels système ou syscall (cf : http://fr.wikipedia.org/wiki/Appel_syst%C3%A8me). Pour faire simple ce sont des fonctions élémentaires mises en place par le kernel pour vous permettre d’interagir avec celui-ci (afficher un caractère, allouer de la mémoire ...) la plupart des fonctions de la libc font appels a ces syscall. Par exemple, printf et ses dérivés font appel à write (man 2 write) un syscall de plus bas niveau.
Exemple avec notre binaire précédent :
On le constate ici, notre strcmp a disparu, forcément car strcmp n'est pas un syscall. Là où c'est intéressant, c'est pour retrouver des adresses mémoires notamment mais aussi une trace bas niveau du comportement du binaire.
valgrind - A suite of tools for debugging and profiling programs
Si vous faites du reversing, vous l'utiliserez certainement pas tous les jours, cependant cet outil est à connaître.
Il s'agit d'un profiler notamment utilisé pour détecter les fuites de mémoire. Là où c'est interessant, c'est qu'à partir des fuites de mémoire, il est parfois possible d’injecter du code dans le binaire lors d'une exploitation de faille dans celui-ci. Autrement dit, lors de l'exploitation de failles applicatives, il sera parfois utile de se service de valgrind, et souvent il faudra faire du reversing pour trouver une faille à exploiter.
readelf - Display information about ELF files
Comme sa description l'indique là encore, cet utilitaire permet d'afficher des informations sur un binaire Linux (ELF). On y reviendra plus tard.
ldd - Print shared library dependencies
Cet outil permet d’afficher une liste de librairies dynamiques qui seront attaches au binaire pour son exécution (libc ...). On y reviendra également.
nm - List symbols from object files
Cet outil permet d’afficher une liste des symboles d'un fichier ELF. On y reviendra également.
strings - Print the strings of printablecharacters in file
Cette commande affiche les chaînes de caractères affichables contenues dans un fichier pas forcement binaire). Il est important de mémoriser cette commande, elle est forte utile pour retrouver des informations qui permettront de mieux comprendre le programme tel que les chaînes utilisées lors de l'a programmation de celui-ci.
La prochaine, on verra plus en détails les fichier ELF et les outils associés a ce format de fichier.
Connaître Linux un minimum
Des bases (ou plus) en programmation C
Savoir utiliser les pages de manuel de Linux (man man)
Savoir chercher sur google les termes que l'on ne connaît pas
Avant propos
Avant tout, vous devriez aller jeter un œil ICI pour avoir une bonne image globale sur le reversing.
Ce tutoriel et les suivants sont réservés au reversing de binaires sous un environnement Linux. Tous les exemples que vous pourrez trouver sont réalisés sur une Debian testing sous Amd64, donc en 64bits. En cas de changement de version, elle sera précisée dans l'article en question ainsi que les versions des différents outils (gcc, gdb ...).
Pour celui-ci :
version de gcc : 4.7.3-4
Quelques outils pratiques pour le reversing de binaires Linux
Notre cher sakarov vous a déjà fourni quelques outils pratiques pour le reversing. Ici je ne rajouterai que les outils pour le reversing de binaires statiques/dynamiques (on y reviendra) Linux
file - Determine file type
Code:
$ man objdump
Essayez donc de lancer ces commandes :
Code:
$ file /bin/ls $ echo bonjour > toto && file toto $ file /lib/*so*
Code:
$ man objdump $ man hexdump $ man hexedit $ man gcc $ man gdb
ltrace - A library call tracer
Code:
$ man ltrace
Exemple avec la commande ls (qui est un programme se trouvant dans /bin/ si vous n’êtes pas au courant) :
Code:
$ ltrace /bin/ls __libc_start_main(0x4029e0, 1, 0x7fffb73d2588, 0x412b40, 0x412b30 <unfinished ...> strrchr("/bin/ls", '/') = "/ls" setlocale(6, "") = "en_US.UTF-8" bindtextdomain("coreutils", "/usr/share/locale") = "/usr/share/locale" textdomain("coreutils") = "coreutils" __cxa_atexit(0x40a380, 0, 0, 0x736c6974756572, 3) = 0 isatty(1) = 0 getenv("QUOTING_STYLE") = NULL getenv("LS_BLOCK_SIZE") = NULL getenv("BLOCK_SIZE") = NULL getenv("BLOCKSIZE") = NULL getenv("POSIXLY_CORRECT") = NULL getenv("BLOCK_SIZE") = NULL getenv("COLUMNS") = NULL ioctl(1, 21523, 0x7fffb73d2070) = -1 getenv("TABSIZE") = NULL getenv("POSIXLY_CORRECT") = NULL __errno_location() = 0x7fdea234d6c8 malloc(56) = 0x024c7040 memcpy(0x024c7040, "", 56) = 0x024c7040 __errno_location() = 0x7fdea234d6c8 malloc(56) = 0x024c7080 memcpy(0x024c7080, "", 56) = 0x024c7080 malloc(19200) = 0x024c70c0 malloc(32) = 0x024cbbd0 strlen(".") = 1 malloc(2) = 0x024cbc00 memcpy(0x024cbc00, ".", 2) = 0x024cbc00 __errno_location() = 0x7fdea234d6c8 opendir(".") = 0x024cbc20 readdir(0x024cbc20) = 0x024cbc48 readdir(0x024cbc20) = 0x024cbc68 readdir(0x024cbc20) = 0x024cbc88 readdir(0x024cbc20) = 0x024cbca8 readdir(0x024cbc20) = 0x024cbcc0 readdir(0x024cbc20) = 0x024cbce0 readdir(0x024cbc20) = 0x024cbd00 readdir(0x024cbc20) = 0x024cbd20 readdir(0x024cbc20) = 0x024cbd40 readdir(0x024cbc20) = 0x024cbd58 readdir(0x024cbc20) = 0x024cbd78 readdir(0x024cbc20) = 0x024cbda0 readdir(0x024cbc20) = 0x024cbdc8 ... free(0x024cbc00) = <void> free(NULL) = <void> free(0x024cbbd0) = <void> exit(0 <unfinished ...> __fpending(0x7fdea1b10160, 0, 0x7fdea1b10cf0, 4, 0) = 75 fileno(0x7fdea1b10160) = 1 __freading(0x7fdea1b10160, 0, 0x7fdea1b10cf0, 4, 0) = 0 __freading(0x7fdea1b10160, 0, 2052, 4, 0) = 0 fflush(0x7fdea1b10160BH_US_08_Shacham_Return_Oriented_Programming.pdf out perso pro school todo ) = 0 fclose(0x7fdea1b10160) = 0 __fpending(0x7fdea1b10080, 0, 0x7fdea1b11700, 0xfbad000c, 0x7fdea1b117f0) = 0 fileno(0x7fdea1b10080) = 2 __freading(0x7fdea1b10080, 0, 0x7fdea1b11700, 0xfbad000c, 0x7fdea1b117f0) = 0 __freading(0x7fdea1b10080, 0, 4, 0xfbad000c, 0x7fdea1b117f0) = 0 fflush(0x7fdea1b10080) = 0 fclose(0x7fdea1b10080) = 0 +++ exited (status 0) +++
Une simple lecture rapide nous montre le flux général d’exécution sans entrer dans les détails. On peut donc constater (entre autres) que le programme ouvre un dossier avec opendir (man opendir) qui renvoie une structure se trouvant à l'adresse "0x024cbc20" puis il est parcouru grâce à readdir (man readdir) qui prend l'adresse de cette structure en paramètre. Je vous laisse deviner la suite.
Code:
... opendir(".") = 0x024cbc20 readdir(0x024cbc20) = 0x024cbc48 readdir(0x024cbc20) = 0x024cbc68 ...
Code:
$ cat password.c #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { if (argc > 1) { if (!strcmp("le_password", argv[1])) printf("Nice job, you found the password !\n"); else printf("Try again !\n"); } else printf("Please enter a password as second argument : %s YOUR_PASSWORD\n", argv[0]); return (0); } $ gcc -o password password.c $ ./password Please enter a password as second argument : ./password YOUR_PASSWORD $ ltrace ./password test __libc_start_main(0x40058c, 2, 0x7ffff5fcf6e8, 0x400600, 0x400690 <unfinished ...> strcmp("le_password", "test") = -8 puts("Try again !"Try again ! ) = 12 +++ exited (status 0) +++ $ ltrace ./password le_password __libc_start_main(0x40058c, 2, 0x7ffffbf1e178, 0x400600, 0x400690 <unfinished ...> strcmp("le_password", "le_password") = 0 puts("Nice job, you found the password"...Nice job, you found the password ! ) = 35 +++ exited (status 0) +++ $
On peut tout de même y voir un sérieux défaut, en effet, les fonctions des librairies utilisées sont affichées avec les paramètres et valeurs de retour, mais qu'en est-il des fonctions écrites par le développeur ?
Vous l'aurez compris ltrace ne peut pas faire le job car ces fonctions-là ne sont pas dans une librairie (sauf si vous créez votre librairie). Un développeur qui va recoder des algorithmes existant sans ce service de librairie ne sera donc pas sujet au tracing fait par ltrace.
strace - Trace system calls and signals
Code:
$ man strace
Exemple avec notre binaire précédent :
Code:
$ strace ./password test execve("./password", ["./password", "test"], [/* 25 vars */]) = 0 brk(0) = 0x20b9000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1ba9c08000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=81063, ...}) = 0 mmap(NULL, 81063, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f1ba9bf4000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\240\32\2\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=1737136, ...}) = 0 mmap(NULL, 3849280, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f1ba963d000 mprotect(0x7f1ba97df000, 2097152, PROT_NONE) = 0 mmap(0x7f1ba99df000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a2000) = 0x7f1ba99df000 mmap(0x7f1ba99e5000, 15424, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f1ba99e5000 close(3) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1ba9bf3000 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1ba9bf2000 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1ba9bf1000 arch_prctl(ARCH_SET_FS, 0x7f1ba9bf2700) = 0 mprotect(0x7f1ba99df000, 16384, PROT_READ) = 0 mprotect(0x7f1ba9c0a000, 4096, PROT_READ) = 0 munmap(0x7f1ba9bf4000, 81063) = 0 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1ba9c07000 write(1, "Try again !\n", 12Try again ! ) = 12 exit_group(0) = ? $ exit
valgrind - A suite of tools for debugging and profiling programs
Code:
$ man valgrind
Il s'agit d'un profiler notamment utilisé pour détecter les fuites de mémoire. Là où c'est interessant, c'est qu'à partir des fuites de mémoire, il est parfois possible d’injecter du code dans le binaire lors d'une exploitation de faille dans celui-ci. Autrement dit, lors de l'exploitation de failles applicatives, il sera parfois utile de se service de valgrind, et souvent il faudra faire du reversing pour trouver une faille à exploiter.
readelf - Display information about ELF files
Code:
$ man readelf
ldd - Print shared library dependencies
Code:
$ man ldd
nm - List symbols from object files
Code:
$ man nm
strings - Print the strings of printablecharacters in file
Code:
$ man strings
La prochaine, on verra plus en détails les fichier ELF et les outils associés a ce format de fichier.