--- title: "Sécurité logicielle : TD8 Utiliser GDB" date: 2023-04-07 author: - Yorick Barbanneau - Gwendal Aupee tags: ["gdb", "x86", "debug"] categories: ["Sécurité logicielle", "TD"] --- ## Partie 1 Commençons par récupérer l'ensemble des éléments de `x` à savoir * son contenu (une adresse) * le contenu pointé cette adresse * son adresse ``` (gdb) p x $1 = (int *) 0xffffd690 (gdb) p *x $2 = 2 (gdb) p &x $3 = (int **) 0xffffd680 ``` Regardons maintenant le contenu de la pile et retrouvons les différents élements: ``` 0xffffd6b8 0xf7ffcff4 0xffffd6b4 0x00000070 0xffffd6b0 0x00000000 0xffffd6ac 0xf7c23295 0xffffd6a8 0x00000000 0xffffd6a4 0xffffd6c0 0xffffd6a0 0x00000001 0xffffd69c 0x00000001 0xffffd698 0x08049198 0xffffd694 0xffffd6a8 0xffffd690 0x00000002 <- La valeur de x (son adresse sur la pile) 0xffffd68c ... 0xf7fc14a0 0xffffd688 arg3 0xf7c1ca2f 0xffffd684 arg2 0xf7fd98cb 0xffffd680 arg1 0xffffd690 <- L'adresse de x (&x) 0xffffd67c ret@ 0x0804917b 0xffffd678 bp sp 0xffffd694 ``` La *backtrace* de `f()`: ``` (gdb) bt #0 f (x=0xffffd690) at stack.c:4 #1 0x0804917b in g (y=1) at stack.c:9 #2 0x08049198 in main () at stack.c:13 ``` Adresse de retour dans pframe: ``` ... 0xffffd680 arg1 0xffffd690 0xffffd67c ret@ 0x0804917b <- adresse de retour de f() (vers g()) 0xffffd678 bp sp 0xffffd694 ``` Après un `up`, voici les adresses demandées: ``` Breakpoint 1, f (x=0xffffd690) at stack.c:4 4 return *x+1; (gdb) up #1 0x0804917b in g (y=1) at stack.c:9 9 return f(&z); (gdb) p y $1 = 1 (gdb) p&y $2 = (int *) 0xffffd69c (gdb) p z $3 = 2 (gdb) p &z $4 = (int *) 0xffffd690 (gdb) ``` ## Partie 2 Lançons `gdb` sur optim.02 et posons notre point d'arrêt sur `f()` ``` Breakpoint 1, f (x=2) at optim.c:4 4 return x+1; (gdb) bt #0 f (x=2) at optim.c:4 #1 0x080491c1 in g (y=1) at optim.c:9 #2 0x08049068 in main () at optim.c:15 (gdb) up #1 0x080491c1 in g (y=1) at optim.c:9 9 int a = f(z); (gdb) p z $1 = 2 (gdb) p &z Can't take address of "z" which isn't an lvalue. (gdb) ``` Effectivement `gdb` ne peut nous afficher la valeur. Après avoir désassemblé la fonction `g()`, nous pouvons effectivement voir que `z` n'existe pas en tant que tel: il **est directement mis sur la pile**. ``` 0x080491b4 <+4>: mov 0x10(%esp),%eax 0x080491b8 <+8>: add $0x1,%eax <- z 0x080491bb <+11>: push %eax ``` Lorsqu'on essaye d'afficher `a`, *gdb* nous affiche que cette variable a été optimisée ``` (gdb) p a $2 = ``` En observant le code assembleur, ici encore la variable `a` n'existe que sur la pile dans `%eax`. Le programme passe ensuite `%eax` dans `%ebx` pour le passer à la fonction `printf` ``` ... 0x080491c5 <+21>: mov %eax,%ebx 0x080491c7 <+23>: push $0x804a008 0x080491cc <+28>: call 0x8049040 ``` Dans la version optimisée du programme, le compilateur a donc réduit au maximum la création de variables. La *backtrace* complète, `a` est aussi `` : ``` (gdb) bt full #0 f (x=2) at optim.c:4 No locals. #1 0x080491c1 in g (y=1) at optim.c:9 z = 2 a = #2 0x08049068 in main () at optim.c:15 ``` ### Partie 3 Nous lançons puis plaçons un point d'arrêt sur `main()`: ``` (gdb) b main Breakpoint 1 at 0x804918a: file modif.c, line 12. (gdb) r Breakpoint 1, main () at modif.c:12 12 int b = 1; (gdb) wa a Watchpoint 2: a ``` Après avoir continué l'exécution de notre programme, `gdb` s'arrête lors de la modification de `a`: ``` [...] (gdb) c Continuing. Watchpoint 2: a Old value = -134474120 New value = 2 main () at modif.c:14 14 int c = 3; ``` Nous pouvons maintenant afficher `%eip` et désassembler: ``` (gdb) p $eip $2 = (void (*)()) 0x8049198 (gdb) disassemble Dump of assembler code for function main: 0x08049179 <+0>: lea 0x4(%esp),%ecx 0x0804917d <+4>: and $0xfffffff0,%esp 0x08049180 <+7>: push -0x4(%ecx) 0x08049183 <+10>: push %ebp 0x08049184 <+11>: mov %esp,%ebp 0x08049186 <+13>: push %ecx 0x08049187 <+14>: sub $0x14,%esp 0x0804918a <+17>: movl $0x1,-0xc(%ebp) 0x08049191 <+24>: movl $0x2,-0x14(%ebp) <- initialisation de a => 0x08049198 <+31>: movl $0x3,-0x10(%ebp) <- %eip ... ``` On peut demander à gdb de calculer l'adresse de `%ebp -14` et de la comparer avec l'adresse de `a`: ``` (gdb) p $ebp - 0x14 $8 = (void *) 0xffffd694 (gdb) p &a $9 = (int *) 0xffffd694 ``` Demandons à `gdb` de nous afficher le contenu de cette case mémoire: ``` #0 main () at modif.c:14 (gdb) p/x *(int*)($ebp - 0x14) $8 = 0x2 ``` Continuons maintenant l'exécution ``` (gdb) c Continuing. Watchpoint 2: a Old value = 2 New value = 3 f (x=0xffffd694) at modif.c:5 ``` On peu observer l'incrémentation de `a` dans la fonction `f()`: ``` (gdb) disassemble Dump of assembler code for function f: 0x08049156 <+0>: push %ebp 0x08049157 <+1>: mov %esp,%ebp 0x08049159 <+3>: mov 0x8(%ebp),%eax 0x0804915c <+6>: mov (%eax),%eax 0x0804915e <+8>: lea 0x1(%eax),%edx <-incrémentation de a 0x08049161 <+11>: mov 0x8(%ebp),%eax 0x08049164 <+14>: mov %edx,(%eax) <- a est placé dans %eax => 0x08049166 <+16>: nop 0x08049167 <+17>: pop %ebp 0x08049168 <+18>: ret End of assembler dump. ``` La variable `a` est incrémentée en utilisant `lea`, le résultat de cette opération est placé dans `%ebx` avant d'être positionnée dans `%eax`. C'est ce qui cause l'arrêt de *gdb*. Après avoir relancé le programme, nous obtenons l'adresse de `a`: ``` Breakpoint 1, main () at modif.c:12 12 int b = 1; (gdb) p &a $1 = (int *) 0xffffd694 ``` Effectivement en positionnant un `watch` sur l'adresse de `a`, on obtient le même comportement: ``` (gdb) wa *(int *)0xffffd694 Hardware watchpoint 2: *(int *)0xffffd694 (gdb) c Continuing. Hardware watchpoint 2: *(int *)0xffffd694 Old value = -134474120 New value = 2 main () at modif.c:14 14 int c = 3; ``` ## Partie 4 Exécution du programme `modif2`: ``` ./modif2 0xffe7d18c:1234567890 0xffe7d188:1234567680 0xffe7d17c ``` On voit bien que le second entier est différent du premier. Exécutons le programme avec `gdb` en posant un *watchpoint* sur les deux entiers. Pour ce faire nous allons poser un *breackpoint* sur `main()`, exécuter pas à pas pour repérer l'initialisation des deux entiers et poser nos *watchpoint* dessus: ``` (gdb) b main Breakpoint 1 at 0x80491ad: file modif2.c, line 13. (gdb) r [...] 13 int b = 1234567890; (gdb) n 14 int c = 1234567890; (gdb) wa b Hardware watchpoint 2: b (gdb) wa c Hardware watchpoint 3: c ``` Nous voyons biens que ces entiers ont été initialisée avec *1234567890*. Continuons l'exécution du programme, nous pouvons alors voir que la variable `c` est modifiée dans `memset-sse2.S` : ``` [...] Old value = 1234567890 New value = 1234567680 __memset_sse2_rep () at ../sysdeps/i386/i686/multiarch/memset-sse2-rep.S:173 ``` En désassemblant,, nous pouvons effectivement voir que nous sommes au beau milieu de la fonction `memset`: ``` (gdb) disass Dump of assembler code for function __memset_sse2_rep: [...] 0xf7d779fc <+108>: mov %eax,-0xd(%edx) 0xf7d779ff <+111>: mov %eax,-0x9(%edx) 0xf7d77a02 <+114>: mov %eax,-0x5(%edx) 0xf7d77a05 <+117>: mov %al,-0x1(%edx) => 0xf7d77a08 <+120>: mov 0x8(%esp),%eax 0xf7d77a0c <+124>: pop %ebx 0xf7d77a0d <+125>: ret [...] End of assembler dump. ``` Maintenant remontons dans la fonction appelante: ``` (gdb) up #1 0x0804917d in f (x=0xffffd67c "", n=13) at modif2.c:5 5 (gdb) disassemble Dump of assembler code for function f: 0x08049166 <+0>: push %ebp 0x08049167 <+1>: mov %esp,%ebp 0x08049169 <+3>: sub $0x8,%esp 0x0804916c <+6>: mov 0xc(%ebp),%eax 0x0804916f <+9>: sub $0x4,%esp 0x08049172 <+12>: push %eax 0x08049173 <+13>: push $0x0 0x08049175 <+15>: push 0x8(%ebp) 0x08049178 <+18>: call 0x8049050 => 0x0804917d <+23>: add $0x10,%esp 0x08049180 <+26>: nop 0x08049181 <+27>: leave 0x08049182 <+28>: ret End of assembler dump. ``` Nous allons maintenant relancer notre exécution en posant un point d'arrêt sur l'adresse `0x08049178`. Lançons maintenant notre exécution: ``` (gdb) b * 0x08049178 Breakpoint 1 at 0x8049178: file modif2.c, line 5. (gdb) r [...] Breakpoint 1, 0x08049178 in f (x=0xffffd67c "Hello, you!", n=13) at modif2.c:5 (gdb) p &x[0] $3 = 0xffffd67c "Hello, you!" (gdb) up (gdb) up (gdb) p &c $5 = (int *) 0xffffd688 (gdb) p &b $6 = (int *) 0xffffd68c ``` Maintenant que nous avons les adresses, intéressons nous à la fonction `memset`. Sa signature est `memset(void s, int c, size_t n)`, elle remplie `n` élément de la zone mémoire `s` avec `c`. D'après `gdb`, voici l'appel de cette fonction dans `f()` : `memset(x, '\0', n);` où `x` est un pointeur vers `a` et `n` est égal à *13*. Adresse | Variable | Memset ---------|:--------:|-------- ... | ... | ... 0xffffd68c | b | 0xffffd68b | c | 0xffffd68a | c | 0xffffd689 | c | 0xffffd688 | c | memset[12] 0xffffd687 | a[11] | memset[11] 0xffffd686 | a[10] | memset[10] ... | ... | ... 0xffffd67e | a[2] | memset[2] 0xffffd67d | a[1] | memset[1] 0xffffd67c | a[0] | memset[0] Comme nous pouvons le voir sur le tableau, le `memset` écrase le **bit de poids faible** de notre variable `c`. Nous avons la cause de sa modification! Le calcul est simple : adresse de `a` + 12 bits = `0xfffd67c + 0xc = 0xfffd688`, notre `memset` empiète bien sur `c`. Le bug est simple le développeur a ajouter un à `sizeof(a)` surement pour prendre en compte le caractère `/0`, or cette opération est effectuée dès l'initialisation de notre constante : ``` (gdb) frame 2 #2 0x080491de in main () at modif2.c:16 16 g(a, sizeof(a) + 1); ``` ## Partie 5 Effectivement, lors de l'exécution du programme avec *Valgring*, celui-ci reporte une erreur: ``` [...] ==4666== Invalid write of size 1 ==4666== at 0x4049FF0: memset (in /usr/libexec/valgrind/vgpreload_memcheck-x86-linux.so) ==4666== by 0x804919C: f (modif3.c:6) ==4666== by 0x80491B6: g (modif3.c:10) ==4666== by 0x80491ED: main (modif3.c:15) ==4666== Address 0x428a02c is 0 bytes after a block of size 4 alloc'd ==4666== at 0x4040660: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-x86-linux.so) ==4666== by 0x4101315: strdup (strdup.c:42) ==4666== by 0x80491D9: main (modif3.c:14) [...] ``` Après avoir lancé *Valgrind* avec le paramètre `--vgdb-error=1` et lancé gdb avec `target remote | /usr/bin/vgdb --pid=`, nous pouvons observer la même erreur que pour la partie précédente: ``` (gdb) bt #0 0x04049ff0 in _vgr20210ZZ_libcZdsoZa_memset () from /usr/libexec/valgrind/vgpreload_memcheck-x86-linux.so #1 0x0804919d in f (x=0x428a028 "", n=5) at modif3.c:6 #2 0x080491b7 in g (y=0x428a028 "", n=5) at modif3.c:10 #3 0x080491ee in main () at modif3.c:15 (gdb) frame 3 #3 0x080491ee in main () at modif3.c:15 15 g(a, sizeof(a)+1); ``` Un a été ajouté à `sizeof(a)` **alors qu'il ne faut pas**.