Qui veut en apprendre plus sur le shell coding ne peut faire l'économie de se mettre à l'assembleur et d'apprendre à déboguer des programmes écrits en C ou C++ avec le très fameux GNU Project Debuger, "GDB" pour les intimes.
Bien évidemment, les didacticiels pour apprendre à utiliser GDB sont légion. Toutefois, pourquoi ne pas apporter une pierre de plus à l'édifice en présentant aussi succinctement que possible les principales commandes afin de faciliter une première prise en main, qui permet d'approcher le monstre avant de rentrer dans les entrailles de sa documentation ?
Dans ce petit didacticiel, il s'agira donc de déboguer un programme C des plus élémentaires compilé avec GNU Compiler Collection, "GCC" pour les intimes, dans Kali Linux.
Soit le programme test.c suivant :
#include <stdio.h> int main (int argc, char *argv[]) { for (int i = 0; i != argc; i ++) printf ("Argument %d: %s", i, argv[i]); return (0); }
Compiler avec l'option -g pour intégrer des informations supplémentaires pour le débogueur (-Wall pour demander l'affichage de tous les warnings) :
$ gcc -Wall -g test.c -o test
Exécuter dans le contexte de gdb. Soit en démarrant... :
$ gdb --args test one two three
...ou après avoir démarré gdb sans paramètres :
(gbb) file test Reading symbols from test... (gdb) set args one two three
NB : il est même possible d'attendre de spécifier via run.
A ce stade, l'exécution du programme n'a pas commencé, donc pas de débogage possible au sens propre. Il est tout de même possible de passer en revue certaines choses.
Le saviez-vous ?
Pour saisir plus vite :
- Après avoir saisi le début d'une commande, presser Tab complète la commande si d'autres ne débutent pas pareillement.
- Après avoir saisi le début d'une commande, presser deux fois Tab affiche celles qui débutent pareillement.
- Il suffit de saisir le début d'une commmande si aucune autre ne débute pareillement (ex : br pour breakpoint).
- Il suffit de saisir la première lettre des commandes les plus communes, même si d'autres débutent pareillement (ex : b pour breakpoint).
- Après avoir utilisé une commande, il suffit de presser Entrée pour la saisir de nouveau.
Afficher la structure du programme en mémoire :
(gdb) i file Symbols from "/root/Downloads/test". Local exec file: `/root/Downloads/test', file type elf64-x86-64. Entry point: 0x1050 0x00000000000002a8 - 0x00000000000002c4 is .interp 0x00000000000002c4 - 0x00000000000002e8 is .note.gnu.build-id 0x00000000000002e8 - 0x0000000000000308 is .note.ABI-tag 0x0000000000000308 - 0x000000000000032c is .gnu.hash 0x0000000000000330 - 0x00000000000003d8 is .dynsym 0x00000000000003d8 - 0x000000000000045c is .dynstr 0x000000000000045c - 0x000000000000046a is .gnu.version 0x0000000000000470 - 0x0000000000000490 is .gnu.version_r 0x0000000000000490 - 0x0000000000000550 is .rela.dyn 0x0000000000000550 - 0x0000000000000568 is .rela.plt 0x0000000000001000 - 0x0000000000001017 is .init 0x0000000000001020 - 0x0000000000001040 is .plt 0x0000000000001040 - 0x0000000000001048 is .plt.got 0x0000000000001050 - 0x00000000000011f1 is .text 0x00000000000011f4 - 0x00000000000011fd is .fini 0x0000000000002000 - 0x0000000000002014 is .rodata 0x0000000000002014 - 0x0000000000002050 is .eh_frame_hdr 0x0000000000002050 - 0x0000000000002158 is .eh_frame 0x0000000000003de8 - 0x0000000000003df0 is .init_array 0x0000000000003df0 - 0x0000000000003df8 is .fini_array 0x0000000000003df8 - 0x0000000000003fd8 is .dynamic 0x0000000000003fd8 - 0x0000000000004000 is .got 0x0000000000004000 - 0x0000000000004020 is .got.plt 0x0000000000004020 - 0x0000000000004030 is .data 0x0000000000004030 - 0x0000000000004038 is .bss
Afficher la liste des fonctions, avec comme on le voit la fonction
main ()
:
(gdb) i functions All defined functions: File test.c: 2: int main(int, char **); Non-debugging symbols: 0x0000000000001000 _init 0x0000000000001030 printf@plt 0x0000000000001040 __cxa_finalize@plt 0x0000000000001050 _start 0x0000000000001080 deregister_tm_clones 0x00000000000010b0 register_tm_clones 0x00000000000010f0 __do_global_dtors_aux 0x0000000000001130 frame_dummy 0x0000000000001190 __libc_csu_init 0x00000000000011f0 __libc_csu_fini 0x00000000000011f4 _fini
Avant de démarrer l'exécution, prévoir de l'arrêter avant la fin du programme par un breakpoint, par exemple à l'entrée de
main ()
:
NB : Utiliser starti pour arrêter l'exécution dès la première instruction d'un programme sans passer par un breakpoint.
(gdb) b main Breakpoint 1 at 0x1144: file test.c, line 3.
NB : Utiliser tbreak pour un breakpoint qui ne fonctionne qu'une fois, dit temporaire.
Consulter la liste des breakpoints :
(gdb) i breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x0000000000001144 in main at test.c:3
Désactiver le breakpoint à partir de son numéro :
(gdb) disable 1 (gdb) i breakpoints Num Type Disp Enb Address What 1 breakpoint keep n 0x0000000000001144 in main at test.c:3
Activer le breakpoint à partir de son numéro :
(gdb) enable 1 (gdb) i b Num Type Disp Enb Address What 1 breakpoint keep y 0x0000000000001144 in main at test.c:3
Lancer l'exécution en fournissant "one", "two" et "three" en arguments :
(gdb) r one two three Starting program: /root/Downloads/test one two three Breakpoint 1, main (argc=4, argv=0x7fffffffe268) at test.c:3 3 for (int i = 0; i != argc; i ++)
Noter que GDB s'appuie sur le fichier source pour afficher les instructions en C. Il faut donc que test.c soit disponible pour conduire cette session.
Visualiser les arguments :
(gdb) i args argc = 4 argv = 0x7fffffffe278
Explorer le contenu de
argv[]
:
(gdb) p argv $1 = (char **) 0x7fffffffe278 (gdb) p *argv@4 $2 = {0x7fffffffe552 "/root/Downloads/test", 0x7fffffffe567 "one", 0x7fffffffe56b "two", 0x7fffffffe56f "three"}