> 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/heap/use-after-free.md).

# Use-after-free

Pensez à réinitialiser vos pointeurs !

Un programme est vulnérable à un use-after-free (UAF) quand un pointeur n'est pas réinitialisé à `NULL` après avoir été libéré (`free`).

## Allocation dynamique de mémoire

L'allocation dynamique de mémoire se fait sur le tas (voir [Prologue](/cyber/rev/prologue.md#le-tas-heap)).

En C des blocs mémoires sont alloués avec [malloc](https://koor.fr/C/cstdlib/malloc.wp) et libérés avec [free](https://koor.fr/C/cstdlib/free.wp).

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FMviAs1MpgULW5jNwXtsN%2Fmalloc%20%26%20free.svg?alt=media&amp;token=ca9bac54-fc2d-4ba9-a4a1-d6f98714093d" alt=""><figcaption><p>Allocation dynamique sur le tas</p></figcaption></figure>

`malloc` renvoie un pointeur vers le début du bloc mémoire alloué.

Regardons ça avec un exemple simple:

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

int main(int argc, char** argv) {
    
    // déclaration de 2 pointeurs vers des entiers signés
    int *pointeur1;
    int *pointeur2;
    
    // allocation de blocs mémoire de taille sizeof(int) = 4 octets
    pointeur1 = (int *) malloc(sizeof(int));
    pointeur2 = (int *) malloc(sizeof(int));
    
    printf("pointeur1 pointe vers l'adresse: %p\n", pointeur1);
    printf("pointeur2 pointe vers l'adresse: %p\n", pointeur2);  
    
    // libération des pointeurs
    free(pointeur1);
    free(pointeur2);
    pointeur2 = NULL;
    
    printf("les blocs mémoires ont été libérés\n");
    
    printf("pointeur1 pointe vers l'adresse: %p\n", pointeur1);
    printf("pointeur2 pointe vers l'adresse: %p\n", pointeur2); 
    
}
```

Dans cet exemple on alloue des zones mémoires à 2 pointeurs et on affiche les adresses de début de chaque bloc. Ensuite on libère les bloc mémoires en ne réinitialisant qu'un seul des 2 pointeurs avant de regarder vers quelles adresse ils pointent après libération.

```
sam@kali:~exploits:$ gcc ptr.c -o ptr
sam@kali:~exploits:$ ./ptr
pointeur1 pointe vers l'adresse: 0x00e42f60
pointeur2 pointe vers l'adresse: 0x00e42f70
les blocs memoires sont libres
pointeur1 pointe vers l'adresse: 0x00e42f60
pointeur2 pointe vers l'adresse: 0x00000000
```

On voit qu'après avoir libéré les zones mémoires allouées aux pointeurs, celui qui n'a pas été réinitialisé (`pointeur1`) pointe toujours vers le début du bloc qui lui était alloué.&#x20;

Cependant, ce bloc est considéré comme disponible et peut être alloué à un autre pointeur (`pointeur3` par exemple) avec `malloc`. Si c'est le cas on aura accès aux données de `pointeur3` avec `pointeur1` et on pourra les modifier.

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2F0GcmRu0kBmLrD9SmKdIY%2Fuse-after-free-1.svg?alt=media&amp;token=4d752697-a7fb-45ed-aead-c7c6a86c4ede" alt=""><figcaption><p>Conséquence de ne pas réinitialiser un pointeur après libération de la zone mémoire associée</p></figcaption></figure>

**Mais pourquoi la même zone mémoire est allouée alors qu'il y en a d'autres de disponibles ?**

Car un système de "bins" stocke les adresses libérées préalablement et les regroupe par taille de bloc dans des listes chaînées. Quand un appel à `malloc` est fait, si la taille correspond à celle d'un "bin" l'adresse en tête de la liste est sélectionnée (voir [Prologue](/cyber/rev/prologue.md#la-memoire)).

```c
pointeur1 = malloc(sizeof(int)) // adresse1

free(pointeur1)  // bin: head -> adresse1 -> tail

pointeur3 = malloc(sizeof(int))  // l'adresse en tête de la liste est sélectionnée
                                 // car la même taille (sizeof(int)) est demandée
```

## Exploitation

En général cette vulnérabilité mène à des fuites de données mais dans certains cas il est possible qu'elle [mène à de l'exécution de code](https://www.amossys.fr/insights/blog-technique/intro-to-use-after-free-detection/) :

1. une zone mémoire `mem1` est allouée à une variable `ptr1` puis libérée sans réinitialiser le pointeur
2. un attaquant alloue une zone mémoire `mem2` qui utilise la zone mémoire `mem1` libérée
3. l'attaquant entre des données dans `mem2`
4. le programme utilise `ptr1` et accède aux données de l'attaquant

J'ai écris ce code vulnérable pour illustrer la vulnérabilité:

````c
// gcc ./uac.c -o uaf

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

struct User {
    char nom[16];
    int admin;
};

struct Chien {
    char nom[16];
    int age;
};

struct User* newUser(char* nom) {
    struct User *user;
    user = (struct User *) malloc(sizeof(struct User));
    strncpy(user->nom, nom, 16);
    user->admin = 0;

    printf("Nouvelle utilisateur %s !\n", user->nom);

    return user;
}

struct Chien* newChien(char* nom, int age) {
    struct Chien *chien;
    chien = (struct Chien *) malloc(sizeof(struct Chien));
    strncpy(chien->nom, nom, 16);
    chien->age = age;

    printf("Nouveau chien %s de %d ans!\n", chien->nom, chien->age);
    printf("uaf uaf !!!\n");

    return chien;
}

void admin() {
    printf("Zone reservee aux admins !!!\n");
}
    
int main(int argc, char** argv) {
    int fin = 0;
    int supprUSer = 0;
    int supprChien = 0;
    char userInput;
    char nom[16];
    int age;
    struct User *user = NULL;
    struct Chien *chien = NULL;

    while (!fin) {
        printf("\nQue voulez-vous faire ?\n\n1: Creer un utilisateur\n2: Adopter un chien\n3: Tuer l'utilisateur\n4: Tuer le chien\n5: Acceder a la zone administrateur\n");
        
        scanf(" %c", &userInput);

        switch (userInput) {
            case '1':
                if (user == NULL) {
                    printf("Quel est le nom de l'utilisateur?\n");
                    scanf("%s", &nom);
                    user = newUser(nom);
                    supprUSer = 1;
                }
                else {
                    printf("Vous avez deja un utilisateur\n");
                }
                break;
            case '2':
                if (chien == NULL) {
                    printf("Quel est le nom du chien?\n");
                    scanf("%s", &nom);
                    printf("Quel age a-t-il?\n");
                    scanf("%d", &age);
                    chien = newChien(nom, age);
                    supprChien = 1;
                }
                else {
                    printf("Vous avez deja un chien\n");
                }
                break;
            case '3':
                if (supprUSer == 1) {
                    printf("%s s'est pris un bus...\n", user->nom);
                    free(user);  
                    supprUSer = 0;
                }
                else {
                    printf("Vous n'avez pas d'utilisateur\n");
                }
                break;
            case '4':
                if (supprChien == 1) {
                    printf("%s s'est pris un bus...\n", chien->nom);
                    free(chien);  
                    supprChien = 0;
                }
                else {
                    printf("Vous n'avez pas de chien\n");
                }
                break;
            case '5':
                if (user != NULL) {
                    if (user->admin == 1) {
                        admin();
                        fin = 1;
                    }
                    else {
                        printf("Vous n'etes pas administrateur !\n");
                    }
                }
                else {
                    printf("Vous n'avez pas d'utilisateur\n");
                }
                break;
            default:
                printf("Choix invalide !\n");
                break;
        }
    }
    
    return 0;
}
```
````

Quand on le lance on a 5 choix:

1. Créer un utilisateur (alloue un bloc mémoire pour `struct User` avec `malloc`)
2. Adopter un chien (alloue un bloc mémoire pour `struct Chien` avec `malloc`)
3. Tuer l'utilisateur (libération de la mémoire allouée à `struct User` avec `free`)
4. Tuer le chien (libération de la mémoire allouée à `struct Chien` avec `free`)
5. Accéder à la zone administrateur

Quand on tente d'accéder à la zone admin (5), le programme vérifie si l'attribut `admin` de `User` est égal à 1. Le problème est qu'à la création d'un `User` l'attribut est mis à 0 sans possibilité de le modifier. A première vue on ne peut donc pas y accéder.

Cependant, les pointeurs ne sont pas réinitialisés après libération donc le programme est vulnérable. On remarque que les structures `User` et `Chien` ont le même format:

* un `char` de 16 octets
* un `int`

```c
struct User {                            struct Chien {
    char nom[16]; /* 16 octets */            char nom[16]; /* 16 octets */
    int admin; /* 4 octets */                int age; /* 4 octets */
};                                        };
```

L'idée est d'exploiter le use-after-free pour modifier le champ `User.admin` en accédant à `Chien.age`. En effet, on n'a aucun contrôle direct sur `User.admin` mais quand on créer un chien (option 2) on choisis son âge.

Pour cela il faut:

* allouer de la mémoire à `struct User` (option 1)
* libérer la zone mémoire (option 3)

A ce moment là, le pointeur `user` pointera encore vers cette zone mémoire considérée comme libre comme nous l'avons vu avant.

* allouer de la mémoire à `struct Chien` (option 2)

La zone mémoire libérée précédemment est allouée à `Chien`

* choisir `1` pour son âge

Nos pointeurs seront alors dans cet état:

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FTTTINmzE46tC5dAl2OH8%2Fuse-after-free-2.svg?alt=media&amp;token=744bbf48-6710-4f9e-a9cb-3558502c6617" alt="" width="382"><figcaption><p>Le pointeur user pointe vers la zone allouée à chien</p></figcaption></figure>

* accéder à la zone administrateur (option 5)

Quand le programme fait la vérification `if (user->admin == 1)`, le pointeur `user` pointe en réalité vers `chien.age` qui est égal à 1 ! On passe ainsi la vérification et on accède à la zone admin !

## Protection

Pour éviter ce problème **il faut réinitialiser un pointeur (mettre sa valeur à NULL) après avoir libéré la mémoire vers laquelle il pointe.**

```
// libération
free(ptr);
// réinitialisation
ptr = NULL;
```
