Smashing the Stack en 2020 ?

Divulgâchage : Quand Petit scarabée voudra suivre la Voie des Grands Maîtres, il lui faudra peut être une carte car les paysages ont changé…

Lorsque j’étais étudiante, j’ai eu un cours sur les buffer overflow. À l’époque, c’était relativement facile, il suffisait de suivre pas à pas l’article de référence, c’est à dire « Smashing the stack for fun and profit », ou sa traduction en français, pour comprendre ce que c’était et en réaliser un facilement.

Depuis, de l’eau à coulé sous les ponts, et les OS ont mis au point différents mécanismes qui rendent beaucoup compliqué, si ce n’est même impossible, l’exploitation d’un buffer overflow.

Geralt @ pixabay

Je reste persuadée que Smashing the stack est une référence en la matière, et qu’il est nécessaire de l’avoir compris avant de pouvoir aller plus loin dans l’exploitation logicielle.

Du coup, une de solutions possibles est d‘installer un OS « comme à l’époque ». Mais cela peut s’avérer un peu pénible.

Une autre solution est de désactiver ce qui a changé entre 1996 et 2020, et autres protections, mises en place par les OS et les compilateurs depuis et qui seraient embêtant pour suivre de nouveau l’article.

Dans cet article, nous prendrons comme exemple une Ubuntu 18.04, mais il est vraisemblable que tous les OS et compilateurs modernes implémentent les différents mécanismes que nous allons citer ici.

Protections anti débordements

On va commencer par les protections conçues pour interdire l’exploitation d’un débordement. Que ce soit par la détection du débordement (SSP), la randomisation des adresses (ASLR) ou l’impossibilité d’exécution dans la pile (NX).

SSP

Canary utilisé dans les mines, photoptimist @ flickr

Stack-Smashing-Protector, initialement nommé ProPolice, est un système de protection développé par IBM à partir de 2001, entièrement redéveloppé par Red Hat en 2005. Sur Ubuntu, cette protection est ajoutée par défaut à la compilation depuis la version 6.10, soit depuis 2006.

Il s’agit d’un patch appliqué à gcc, afin que celui-ci ajoute un canary à la compilation, qui reprends l’idée générale de Stackguard. L’idée du canary est d’ajouter une donnée à côté des variables locales de chaque fonction, juste après la sauvegarde de l’adresse de retour. Si cette donnée est écrasée, alors un débordement est détecté et le programme s’arrête, avec un joli message d’erreur :

*** stack smashing detected ***: <unknown> terminated
Abandon (core dumped)

Cette protection va donc détecter lorsque vous effectuerez un débordement de buffer, et bloquera la suite de exécution.

Pour la désactiver : utiliser l’option -fno-stack-protector à la compilation.

ASLR

compte inactif @ pixabay

Address Space Layout Randomization (ou distribution aléatoire de l’espace d’adressage en français) est un mécanisme de protection disponible dans le noyau Linux par défaut depuis juin 2005. Sur Ubuntu, ce noyau est utilisé depuis octobre 2005.

L’ASLR permet de placer de manière aléatoire les zones de données dans la mémoire virtuelle (tas, pile, bibliothèques…). En plaçant ces différents éléments aléatoirement, vous ne pourrez plus prédire l’adresse de votre shellcode. Cela rend donc l’exploitation d’un buffer overflow plus compliquée.

Pour la désactiver : Mettre 0dans le fichier /proc/sys/kernel/randomize_va_space ou utiliser la ligne de commande suivante :

sysctl -w kernel.randomize_va_space=0

NX

ivanacoi @pixabay

Cette protection a été introduite dans les processeurs depuis 2003 pour AMD et 2004 pour Intel. Celle-ci est pris en compte par le noyau Linux depuis la version 2.6.8 (août 2004). Elle n’était cependant pas activée par défaut sur la plupart des systèmes Linux avant 2009, à cause de problèmes d’erreurs causés sur certains processeurs. Ubuntu gère cette protection entièrement et sur tous les processeurs depuis 2011.

Il s’agit d’une protection qui empêche d’exécuter du code dans des zones mémoire qui ne devrait pas être utilisées pour ça, telle que la pile. Celle-ci est implémentée directement dans le processeur. Il s’agit d’un bit, NX (pour No eXecute), permettant de définir si une page mémoire est exécutable (0 pour non, 1 pour oui). Ce bit est interprété par la MMU lors l’accès aux pages mémoire.

Pour la désactiver : il est nécessaire de marquer un programme comme nécessitant une pile exécutable, en utilisant execstack. Installer le paquet comme n’importe quel autre paquet :

apt-get install execstack

Il faut également compiler l’exécutable avec l’option -z execstack.

Nettoyage du code assembleur

À ce stade, vous pourriez mettre en œuvre vos débordements comme à l’époque mais pour rester au plus près des exemples de l’article, il y a encore trois petites choses à désactiver pour simplifier les codes compilés et assembleurs… L’exécution indépendante de la position en mémoire pour le code (PIC), pour les exécutables (PIE) et les exceptions frames.

PIC

Tama66 @ pixabay

Le mécanisme PIC (pour Position Independant Code) est activé par défaut à la compilation sous Ubuntu depuis avril 2017.

Son but est de permettre aux bibliothèques de pouvoir être chargés à des adresses mémoire différentes d’une fois à l’autre. Pour cela, tous les appels à des variables seront remplacés par des adresses relatives et non absolues.

Pour le désactiver : utiliser l’option-fno-pic de gcc.

PIE

OpenClipart-Vectors @ pixabay

Comme pour PIC, le PIE (pour Position Independant Executable) est activé par défaut à la compilation sous Ubuntu depuis avril 2017.

Ce mécanisme ressemble au PIC sur le principe, puisqu’il permet aux exécutables d’être chargés à des adresses mémoire différentes à chaque exécution. Cette fois-ci, cela se fait à l’édition de lien, pour, entre autre, les adresses de la table GOT.

Pour le désactiver : utiliser l’option -no-pie de gcc pour désactiver la protection pour les exécutables.

Exception Frames

Free-Photos @ pixabay

Ce système a été standardisé dans le document Linux Standard Base Core Specification 4.0, datant de 2008.

Il s’agit de données ajoutées pour faciliter le débogage et gérer les exceptions. Le compilateur va ajouter des sections (.eh_frame and .eh_framehdr) dans laquelle seront stockées des données qui permettrons de remonter la pile d’appel de fonctions quand une exception se produit.

Pour le désactiver :, il faut supprimer ces sections et les instructions correspondantes via l’option -fno-asynchronous-unwind-tables de gcc.

Dernières petites choses

Maintenant que les protections sont enlevées et les decorations retirées, il reste encore deux derniers détails pour être fidèle à l’article original. Passer le code en 32bits et ajouter quelques includes pour compiler.

Processeur

AMD Ryzen par Fritzchens Fritz @flickr

L’article datant de 1996, le processeur utilisé par Aleph One était un processeur 32 bits, il même est envisageable que ç’ait été un Pentium Pro (1995-1998). Cela se voit grâce au jeu d’instructions des dumps assembleurs, et surtout que les premières spécifications des processeurs 64 bits ne sortiront qu’en août 2000.

Si vous souhaitez vous rapprocher au plus près de sa machine, et avoir un jeu d’instructions quasi similaires :

Errata

Si on écrit les programmes tels quels, le compilateur va râler… Il manque en fait l’inclusion des en-têtes des bibliothèques utilisées. Pourtant, elles sont définies dans le standard C90 et existaient donc déjà en 1996.

Ajoutez :

Par facilité, vous pouvez rajouter ces trois lignes en tête de tous les codes :

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