---
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**.
