Académique Documents
Professionnel Documents
Culture Documents
Introduction à gdb
17 May 2015 · 16 min Auteur : Pixis
Linux
Que le programmeur qui n’a jamais mis des printf , var_dump , echo , print , System.out ,
, cout plein son code pour savoir d’où venait un bug se dénonce. Que le
console.log
programmeur qui ne s’est jamais arraché les cheveux pour un programme qui plantait
violemment sans crier garde me jette la pierre (C’est une expression, hein !). Heureusement,
il existe pléthore de débogueurs (debuggers), libres ou non, dont un qui est particulièrement
reconnu, le débogueur de GNU nommé GDB (GNU Project Debugger), que nous allons
introduire dans cette introduction.
GDB est portable (cross-platform), donc les commandes que nous allons voir ici pourront être
effectuées sur tous les OS pourvu que GDB soit installé, et les exemples pris ici ont été
effectués sur Linux. C’est un outil très puissant, avec de nombreuses fonctionnalités qu’il
serait difficile de lister et expliquer exhaustivement, c’est pourquoi nous verrons ici ce qui me
paraissait être le plus important (… parmi les fonctionnalités que je connais. Si vous en
connaissez d’autres ou des astuces permettant d’accélérer/simplifier des choses, n’hésitez
pas à m’en faire part dans les commentaires, je les intégrerai dans cet article).
Lancement
Il existe différentes manières de lancer gdb et de charger un binaire dans une session gdb,
voici quelques commandes utiles
Hors gdb
Pour lancer gdb, rien de plus simple. Dans un shell/terminal/console, lancez la commande
suivante
$ gdb
(gdb)
Cette commande permet de lancer une session gdb. Pour l’instant, aucun programme n’est
chargé dans gdb. Mais déjà, nous pouvons faire des choses qui nous serons utiles tout au long
de nos debug. Pour avoir la liste des commandes disponibles, il suffit de lancer la commande
help
(gdb) help
List of classes of commands:
Type "help" followed by a class name for a list of commands in that class.
Type "help all" for the list of all commands.
Type "help" followed by command name for full documentation.
Type "apropos word" to search for commands related to "word".
Command name abbreviations are allowed if unambiguous.
(gdb)
# Lance gdb qui s'attache par la suite au processus PID avec les symboles du binaire "binary"
gdb --pid <PID> --symbols <binary>
Dans gdb
# Envoyer les arguments au binaire qui va être lancé
(gdb) set args <args...>
# Lancer le binaire
(gdb) run
# On peut afficher les variables sous différents formats, de la manière suivante : p/<format>
# Les formats les plus employés sont
# c Caractère
# f Float
# o Octal
# s Chaine de caractères
# t Binaire
# x Hexadécimal
(gdb) p 10+12
$1 = 22
(gdb) p/x 10+12
$2 = 0x16
(gdb) p 0x10
$3 = 16
(gdb) p 0x10 + 10
$4 = 26
(gdb) p/x 0x10 + 10
$5 = 0x1a
(gdb) p/t 12
$6 = 1100
Informations
Quelques informations nécessaires lorsque vous avez chargé un binaire et que vous êtes en
train de le déboguer
Affichage
Syntaxe
Comme expliqué dans l’article sur les notions de base d’assembleur, il existe deux syntaxes
pour lire de l’assembleur : AT&T et Intel. Pour passer de l’une à l’autre, voici comment faire :
AT&T
(gdb) set disassembly-flavor att
(gdb) disass main
Dump of assembler code for function main:
0x080483f2 <+0>: push %ebp
0x080483f3 <+1>: mov %esp,%ebp
...
End of assembler dump.
Intel
(gdb) set disassembly-flavor intel
(gdb) disass main
Dump of assembler code for function main:
0x080483f2 <+0>: push ebp
0x080483f3 <+1>: mov ebp,esp
...
End of assembler dump.
Debug
Lors d’une phase de debug, il peut être utile d’avoir sous les yeux le code machine qui
s’exécute ainsi que l’état des différents registres.
Notez cependant que si vous utilisez ces fenêtres, vous ne serez plus en mesure d’utiliser la
flèche du haut pour revenir dans votre historique, puisque les flèches haut et bas servent à
monter et descendre dans la fenêtre affichant le code assembleur.
Breakpoints
Les breakpoints sont extrêmement puissants. Ils permettent de mettre en pause l’exécution
du programme lorsqu’ils sont rencontrés. Cela permet d’étudier la mémoire à un instant très
précis, quand ça nous intéresse. En effet, souvent il y a des millions d’instructions exécutées
avant l’appel de la fonction qui nous intéresse, donc mettre à breakpoint au bon endroit fait
gagner énormément de temps.
Sans conditions
(gdb) break main
Breakpoint 1 at 0x80483f8
(gdb) break *0x08048400
Breakpoint 2 at 0x8048400
(gdb) delete 1
(gdb) i b
Num Type Disp Enb Address What
2 breakpoint keep y 0x08048400 <main+14>
(gdb) disable 2
(gdb) enable 2
(gdb) i b
Num Type Disp Enb Address What
2 breakpoint keep n 0x08048400 <main+14>
(gdb) delete breakpoints
Delete all breakpoints? (y or n) y
Avec conditions
Soit le programme C suivant :
#include <stdio.h>
int main(void) {
for (int i=0; i<10; i++) {
printf("%s\n", "Boucle ...");
}
}
A la ligne +31 , nous voyons le compteur de notre programme qui s’incrémente. Ici, la boucle
est répétée 10 fois, mais il est possible qu’elle soit répétée des millions de fois. Cependant,
nous ne voulons voir la comparaison à la ligne +36 que pour la dernière boucle. Pour cela,
nous allons mettre un breakpoint conditionnel : Nous ne breakerons dessus que si le contenu
de esp+0x1c vaut 10 (donc 0xa )
(gdb) b *0x08048430
Breakpoint 1 at 0x8048430
(gdb) cond 1 *(int*)($esp+0x1c) == 0xa
(gdb) cond 1
Breakpoint 1 now unconditional.
Pas à pas
# nexti : Permet d'avancer d'une (ou <step>) instruction(s), et si c'est un call, le call est exécuté
# jusqu'à son retour.
(gdb) ni <step>
# stepi : Permet d'avancer d'une (ou <step>) instruction(s), en rentrant dans les calls
(gdb) si <step>
# continue : Permet de continuer jusqu'au prochain breakpoint
(gdb) c
Fonctions
Il est possible de définir des fonctions au sein de gdb, permettant de simplifier la répétition
d’un ensemble de commandes, ou encore de boucler jusqu’à ce qu’une condition soit vérifiée.
Pour cela, il faut lancer la commande define <ma_fonction> puis indiquer les instructions
voulues, et terminer par end . Comme les exemples valent toujours mieux que les beaux
discours :
> if <condition>
> commandes...
> end
> while <condition>
> commandes...
> end
.gdbinit
Bien sûr, avec toutes ces informations, vous pouvez vous créer votre petit environnement gdb
qui satisfait vos besoins et vos préférences, mais vous n’allez évidemment pas taper toutes
les commandes à chaque fois. Il est très fastidieux de devoir taper, à chaque lancement de
gdb, les commandes permettant de changer de syntaxe, de breaker sur la fonction main, de
désassembler le binaire, d’étudier la pile, si c’est ce que vous voulez faire à chaque fois que
vous ouvrez gdb (mais libre à vous de choisir ce que vous voulez)
Pour cela, il vous suffit de créer un fichier .gdbinit dans le même dossier depuis lequel vous
lancez gdb, et dans ce fichier, vous mettez ligne après ligne les commandes que vous
souhaitez lancer. Par exemple :
$ cat .gdbinit
# Pour toujours avoir la syntaxe intel
set disassembly-flavor intel
# Pour que lors d'un fork, gdb suive le processus enfant, plutôt que le processus parent
set follow-fork-mode child
# Si vous savez que vous devez lancer gdb plusieurs fois pour le binaire que
# vous êtes en train de débuguer, et que les 9 première itérations d'une boucle
# ne vous importent pas, autant breaker tout de suite au moment qui vous intéresse
b *0x8048705 if *(int*)($esp+0x10) == 0xa
# Et lancer le binaire
r
# Ensuite, nous voulons souvent utiliser ces deux fonctions en même temps
# Autant les regrouper dans une même fonction !
define afficher_layouts
layout asm
layout regs
end
$
Et pour finir, sachez que si vous avez votre .gdbinit , mais que vous ne voulez pas l’utiliser
pour votre prochaine session gdb, il suffit de passer l’argument -nx à gdb pour lui demander
d’ignorer ce fichier.
Voilà, avec cette introduction à gdb, vous devriez pouvoir l’utiliser et profiter de sa force. Il
manque un tas de choses, j’en suis conscient, et j’ajouterai des fonctions qui me paraitront
pertinentes, que ce soit en les découvrant par moi-même, ou par vos commentaires !
peda
dotgdb
N’hésitez pas à les installer, et les modifier selon vos besoins, vous avez (presque) toutes les
clés en main pour comprendre comment ils fonctionnent. Notez cependant que ces outils ne
sont pas exempts de bugs ou de comportements inattendus. Utilisez les avec discernement,
n’hésitez pas à être bon critique !
Bon reverse
Linux
Articles similaires
Relai NTLM 01 Apr 2020
Pass the Hash 17 Dec 2019
Extraction des secrets de lsass à distance 28 Nov 2019