L’exploitation binaire vise un objectif unique, autour duquel tout le reste s’organise : détourner le pointeur d’instruction. Le registre eip en 32 bits, devenu rip en 64, désigne à chaque instant l’instruction que le processeur s’apprête à exécuter — en prendre le contrôle, c’est prendre celui du programme.
Local ou distant
On distingue deux postures d’attaque, qui déterminent la surface exploitable. En exploitation distante, on vise un service qui traite dynamiquement une entrée — un serveur web dont l’exécutable écrit des logs, lit et écrit sur le disque, écoute sur un port. En exploitation locale, l’enjeu est l’élévation de privilèges (privesc) : comment devenir root sur une machine où l’on dispose déjà d’un accès restreint ? On y regarde en priorité les exécutables setuid, les services écoutant sur localhost, et ceux qui traitent des fichiers du système local.
EXAMPLE
ls -l /usr/sbin/sudoretournerws-xr-x: le bitsetuid(les) délègue temporairement au processus les droits de son propriétaire, iciroot, le temps de son exécution.
Quelles instructions modifient rip
Par défaut, rip s’incrémente de la taille en octets de l’instruction courante — sans quoi la même instruction s’exécuterait indéfiniment. Seules quelques familles d’instructions dérogent à cette règle et permettent de sauter ailleurs : jump, call, ret.
Le couple call / ret mérite une attention particulière, car il est indissociable. call n’est, en substance, qu’un raccourci pour un jump vers l’adresse demandée — mais avant de sauter, il calcule l’adresse de l’instruction suivante (rip + taille de l’instruction call) et l’empile, de sorte que rip puisse, plus tard, la retrouver.
Ce que fait le prologue d’une fonction
L’entrée dans une fonction suit un motif quasi invariant :
push rbp
mov rbp, rsp
sub rsp, 0x10Ces trois instructions posent ce qu’on appelle le plancher de la fonction : elles sauvegardent l’ancienne base de pile (rbp de l’appelant), établissent la nouvelle base, et réservent l’espace local nécessaire aux variables de la fonction. Avant même ce prologue, l’appel lui-même (call) a déjà empilé l’adresse de retour — de sorte qu’au sommet d’une stack frame fraîchement établie, on trouve, dans l’ordre, l’adresse de retour sauvegardée (saved RIP) puis la base de pile sauvegardée (saved RBP).
C’est cette structure prévisible — saved RBP puis saved RIP, empilés dans cet ordre à chaque appel — qui rend possible tout débordement de pile contrôlé : écraser l’un ou l’autre, c’est détourner ce que fera la fonction en retournant. C’est le fil que tirent, chacune à leur manière, les notes qui suivent : la convention d’appel qui régit ce qui transite par la pile et les registres, la cartographie mémoire du processus, les protections qui viennent contrarier ce détournement, et le ROP qui les contourne malgré tout.