cours/content/secu_logicielle/td8-gdb/index.md
2023-04-17 18:00:20 +02:00

452 lines
11 KiB
Markdown

---
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 = <optimized out>
```
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 <printf@plt>
```
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 `<optimized out>` :
```
(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 = <optimized out>
#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 <main+31>
(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 <c code that we don't see>
(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 <memset@plt>
=> 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=<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**.