Owning Memory: From The Stack to ROP

Owning Memory: From The Stack to ROP

Serrac - R&D @ Erium

Introduction à l'exploitation de la stack : histoire et mécanismes, du BoF au ROP. Les bases essentielles pour comprendre les attaques et les correctifs.

Serrac - 01/2026
Owning Memory: From The Stack to ROP

Buffer Overflow : Un problème sérieux

Serrac - 01/2026
Owning Memory: From The Stack to ROP

Principes de base de l'organisation de la mémoire

Le kernel divise la mémoire d'un processus en plusieurs segments :

  • Segments Fichiers (compilation): .text, .rodata, .data
  • Segments d'Exécution (runtime): .bss, Heap, Stack, Memory Map, Args/Env
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Understanding the Stack Frame

À chaque appel de fonction, une stack frame est créée pour stocker :

  • Les arguments de la fonction
  • Les variables locales
  • L'adresse de retour

auto

Elle est gérée par un prologue (création) et un épilogue (destruction).

Serrac - 01/2026
Owning Memory: From The Stack to ROP

Understanding the Stack Frame

How to call a function

push    20                 ; Push 2nd argument
push    10                 ; Push 1st argument
call    add_func           ; Call add_func

Serrac - 01/2026
Owning Memory: From The Stack to ROP

Understanding the Stack Frame

The Prologue

push    ebp                ; Save caller's EBP
mov     ebp, esp           ; Establish this function's stack frame

Serrac - 01/2026
Owning Memory: From The Stack to ROP

Understanding the Stack Frame

The body

mov     eax, [ebp+8]       ; Arg1 (10)
add     eax, [ebp+12]      ; Arg2 (20) => EAX = 30
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Understanding the Stack Frame

The Epilogue

mov     esp, ebp           ; [Epilogue] Restore stack pointer
pop     ebp                ; [Epilogue] Restore previous base pointer
ret                        ; [Epilogue] Return to the caller

Serrac - 01/2026
Owning Memory: From The Stack to ROP

Stack Buffer Overflow : les bases

Serrac - 01/2026
Owning Memory: From The Stack to ROP

Un peu d'histoire

1972: L'étude "Computer Security Technology Planning Study" de l'U.S. Air Force décrit pour la première fois une attaque par buffer overflow.

auto

Serrac - 01/2026
Owning Memory: From The Stack to ROP

Le code vulnérable

#include <stdio.h>

int main(void)
{
    char private = 0;
    char buf[8] = {0};

    printf("Input: ");
    fgets(buf, 20, stdin); // Lit jusqu'à 19 octets dans un buffer de 8

    if (private == 1) {
        printf("stack buffer overflow success\n");
    } else {
        printf("stack buffer overflow failure\n");
    }
    return 0;
}
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Overflow en action

printf "AAAAAAA" | ./demo.bin # 8 bytes input including the null terminator (0x00)

private est inchangé. Le programme affiche "stack buffer overflow failure".

Serrac - 01/2026
Owning Memory: From The Stack to ROP

Overflow en action

printf "AAAAAAAA\x01" | ./demo.bin # 10 bytes input including the null terminator (0x00)

private passe de 0x00 à 0x01. Le programme affiche "stack buffer overflow success".

Serrac - 01/2026
Owning Memory: From The Stack to ROP

Prochaines étapes

  • Que peut-on écraser d'autre sur la stack ?
  • L'adresse de retour (Saved EIP/RIP) !
  • Cela permet de contrôler l'exécution du programme.
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Injection de code sur la stack

Serrac - 01/2026
Owning Memory: From The Stack to ROP

Un peu d'histoire

  • 1988: Le Morris Worm exploite un buffer overflow dans fingerd.
  • Il injectait du code sur la stack et écrasait l'adresse de retour pour l'exécuter.
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Écrasement de Saved EIP

auto

  • Écraser Saved EIP avec 0x41414141 (AAAA) provoque un SIGSEGV.
  • Le CPU essaie de sauter à une adresse invalide.
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Méthode d'injection

Le principe est simple :

  1. Injecter du code malveillant (shellcode) sur la stack.
  2. Écraser l'adresse de retour pour pointer vers le shellcode.
  3. Utiliser un NOP Sled (\x90) pour faciliter le saut.

auto

Serrac - 01/2026
Owning Memory: From The Stack to ROP

Mitigations

  • 1997: Patch Linux pour une stack non-exécutable (Solar Designer).
  • 2003: Bit NX (No-Execute) au niveau matériel.
  • L'OS peut marquer des pages mémoire (stack, heap) comme non-exécutables.
  • Le CPU ne peut plus y interpréter des données comme du code.
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Prochaine étape

  • On ne peut plus injecter notre propre code.
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Ret2Libc : le contournement de NX

  • Mais on peut réutiliser du code existant !
  • C'est le principe du Return-to-libc.
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Un peu d'histoire

  • 1997: Solar Designer décrit une nouvelle technique d'attaque (Return-to-libc/Ret2Libc).
  • Elle permet de contourner une stack non-exécutable.
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Le concept

  • On forge une fausse stack frame.
  • On remplace l'adresse de retour par l'adresse d'une fonction de la libc (ex: system).
  • On place les arguments de la fonction (ex: "/bin/sh") sur la stack.
  • (opt.) On place une adresse de retour pour system (ex: exit).

auto

Serrac - 01/2026
Owning Memory: From The Stack to ROP

Le Payload

auto

Structure du payload :

  1. Padding pour atteindre l'adresse de retour.
  2. Adresse de system().
  3. Adresse de exit() (pour un retour propre, optionnel).
  4. Adresse de la chaîne "/bin/sh".
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Mitigation : Les Canaris (Stack Canaries)

  • 1998: Proposés par Cowan et al. (StackGuard).
  • Un "canari" est une valeur secrète placée sur la stack avant l'adresse de retour.
  • Si le canari est modifié, le programme détecte une corruption et s'arrête.
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Mitigation : ASLR (Address Space Layout Randomization)

  • 2001: Introduit dans Linux.
  • Randomise les adresses de base de la stack, la heap et les bibliothèques.
  • Rend les attaques Ret2Libc (et autres) beaucoup plus difficiles.
  • L'attaquant doit d'abord trouver où se trouvent les fonctions (address leak, ex. vulnérabilité de format string).
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Prochaine étape

  • Comment contourner NX et ASLR?
Serrac - 01/2026
Owning Memory: From The Stack to ROP

ROP : Return-Oriented Programming

  • C'est là que le ROP entre en jeu.
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Un peu d'histoire

  • 2007: Hovav Shacham présente le concept de Return-Oriented Programming (ROP).
  • C'est une attaque par réutilisation de code (code-reuse attack).
  • Elle permet de contourner les protections modernes comme NX et ASLR, et est souvent utilisée dans des exploits qui déjouent aussi les canaris.
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Le concept

  1. Trouver des gadgets: de courtes séquences d'instructions se terminant par ret. (ex. dans .text)
    auto
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Le concept

  1. Chaîner les gadgets: on place les adresses des gadgets sur la stack.
    auto
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Le concept

  1. Exécuter la chaîne: le ret de chaque gadget passe au suivant.
    auto
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Visualisation de la chaîne ROP

auto

Serrac - 01/2026
Owning Memory: From The Stack to ROP

PoC : ROP

  • Objectif:
    • rax = 59 (syscall execve)
    • rdi = &"/bin/sh"
    • rsi = 0
    • rdx = 0
  • Puis exécuter l'instruction syscall.
Serrac - 01/2026
Owning Memory: From The Stack to ROP

PoC : ROP

0x00000000004010c6 : xor rdx, rdx
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Mitigations

  • 2007: Shadow Stacks: une copie de la stack, protégée, pour vérifier les adresses de retour.
  • 2005/2014: Control-Flow Integrity (CFI) / Code Pointer Integrity (CPI): protège tous les pointeurs de code, pas seulement les adresses de retour.
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Conclusion

  • Une même vulnérabilité (écriture hors limites sur la stack) peut mener à plusieurs familles d’attaques.
  • Les protections modernes ont déplacé l’exploitation de l’injection de code vers la réutilisation de code (ret2libc / ROP).
  • En pratique, la sécurité repose sur la défense en profondeur : aucune mitigation seule n’est suffisante, mais leur combinaison augmente fortement le coût d’une exploitation.
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Conclusion

(diagramme : Mohamed (Tarek Ibn Ziad) Hassan, "Why is memory safety still a concern?")
Serrac - 01/2026
Owning Memory: From The Stack to ROP

Bibliographie / En savoir plus : blog.serrac.fr - Posts 2 à 11

Serrac - 01/2026
Owning Memory: From The Stack to ROP

Questions ?

Serrac - 01/2026
Owning Memory: From The Stack to ROP

Slideshow disponible ici :
https://blog.serrac.fr/files/003/

Serrac - 01/2026
Owning Memory: From The Stack to ROP
Serrac - 01/2026

Explorer l'exploitation de la stack : gestion et organisation mémoire, comment corrompre la mémoire, du BoF au ROP. En abordant les contre-mesures.

l'on peut constater que les Buffer Overflow sont un problème sérieux

Quelques exemples marquants : - **Stuxnet** : Cyber-arme ayant saboté le programme nucléaire iranien - **Toyota** : Bug logiciel causant des accélérations involontaires mortelles - **Pegasus** : Logiciel espion utilisé contre journalistes et activistes (exploit BoF WhatsApp) - **Davis-Besse** : Ver Slammer infectant une centrale nucléaire - **Ouïghours** : Campagne de piratage d'iPhones ciblant cette minorité

Lors de la création d'un nouveau processus, le kernel lui attribue un espace d'adressage virtuel. Cet espace d'adressage virtuel est divisé en segments. Il est possible de diviser ces segments en deux catégories : 1. Segments mappés depuis des fichiers (le binaire lui-même), comme le code. (.text, .rodata, .data) 2. Segments créés et gérés à l'exécution ; les plus importants sont la stack et la heap.

Ici, on zoome sur la stack pour aborder concept de "stack frame". Ici, on zoome sur la stack pour aborder le concept de "stack frame". Chaque fois qu'une fonction est appelée, une nouvelle "boîte" (la frame) est ajoutée en haut de la stack. Cette boîte contient tout ce dont la fonction a besoin pour travailler. Quand la fonction se termine, la stack frame est supprimée. C'est LIFO : Last-In, First-Out. Le prologue et l'épilogue sont les petits bouts de code qui gèrent la création et la suppression de ces boîtes.

Nous allons maintenant voir comment un buffer overflow peut corrompre la stack.

La première description d'une attaque par buffer overflow date de 1972, dans une étude de l'U.S. Air Force.

Voici un code vulnérable simple.

Voici l'exécution classique du programme, donc avec 8 bytes en input (ce qui respecte la taille du buffer).

On écrit 9 bytes dans notre buffer de 8 bytes. Le 9ème byte (`\x01`) écrase la variable `private` qui est adjacente au buffer. La condition `if (private == 1)` est maintenant vraie.

Et après ? On a écrasé une variable locale. Peut-on faire plus ?

Effectivement, en contrôlant la valeur de l'adresse de retour (Saved EIP), on peut rediriger l'exécution n'importe où en mémoire.

Le Morris Worm en 1988 a été l'un des premiers malwares à utiliser l'injection de code via buffer overflow.

On va injecter notre propre code (le shellcode) directement sur la stack, dans le buffer qu'on fait déborder. Puis on écrase l'adresse de retour pour qu'elle pointe vers notre shellcode. Le NOP sled, c'est une astuce. C'est une longue série d'instructions qui ne font rien. Si on saute n'importe où dedans, le processeur va "glisser" jusqu'à notre shellcode. Ça rend l'exploitation plus fiable.

Pour contrer l'injection de code, la stack non-exécutable a été introduite en 1997 par Solar Designer.

Puis cela a été repris en 2003 avec le bit NX (No-Execute) géré au niveau matériel (au niveau du MMU : unité de gestion de la mémoire).

Si on ne peut plus exécuter de code sur la stack, comment faire ?

On ne peut pas réutiliser du code existant ?

L'attaque Ret2Libc a été théorisée par Solar Designer lui-même en 1997, peu après sa proposition de stack non-exécutable.

L'idée est de ne pas injecter de code, mais de réutiliser celui qui existe déjà dans les bibliothèques partagées, comme la libc.

Le payload est simple : du padding, suivi des adresses.

Pour contrer Ret2Libc et d'autres corruptions de stack, les canaris ont été introduits.

L'ASLR est une autre mitigation cruciale. Son but est de rendre les adresses en mémoire imprévisibles. À chaque exécution, la stack, la heap, les bibliothèques (comme la libc) sont chargées à des adresses aléatoires. Pour une attaque Ret2Libc, l'attaquant ne connaît plus l'adresse de `system` ou de `"/bin/sh"`. L'attaque devient beaucoup plus difficile.

Avec NX et l'ASLR, comment peut-on encore exploiter un programme ?

Le ROP a été formalisé en 2007 par Hovav Shacham.

Analyser les zones non rendues aléatoires par l'ASLR et aussi exécutables avec une analyse statique du binaire (ex. RopGadget - Jonathan Salwan).

Au retour de la fonction, le flux est détourné vers le premier gadget de la pile, qui s'exécute puis retourne vers le suivant

La chaîne ROP permet de "programmer" avec le code existant. Peut aussi être visualisé comme cela

Objectif : exécuter `execve("/bin/sh", NULL, NULL)` via un `syscall`.

Écriture de "/bin/sh" en mémoire (Write-What-Where) : - POP RDI : Charge l'adresse de la section .data (destination). - POP RAX : Charge la chaîne "/bin/sh\0" (source). - MOV [RDI], RAX : Écrit la chaîne à l'adresse mémoire spécifiée. Préparation des registres pour l'appel système : - POP RDI : Charge l'adresse de "/bin/sh" (1er argument). - XOR RSI, RSI : Met RSI à 0 (2ème argument NULL). - XOR RDX, RDX : Met RDX à 0 (3ème argument NULL). - POP RAX : Charge 59 (numéro du syscall execve). Exécution : - SYSCALL : Déclenche l'appel système pour ouvrir le shell.

Les Shadow Stacks et le Control-Flow Integrity (CFI) sont des mitigations contre le ROP.