← 목록으로

ret2dlresolve: 심볼 정보 없이 임의 함수 호출하기

[Pwn] Pwnable · 작성: 2026-07-20 03:10:34 · 수정: 2026-07-20 03:11:15 · 조회 6

PIE도 아니고, libc 주소도 안 새고, libc 버전조차 모르는 상황이면 ret2libc나 ROP-to-system 같은 기법을 못 쓴다. 이럴 때 쓰는 게 ret2dlresolve다 — 바이너리 자체의 동적 링커(ld.so)가 갖고 있는 지연 바인딩 메커니즘을 이용해서, 링커가 "이 함수를 찾아서 호출해달라"는 우리의 위조 요청을 그대로 믿게 만드는 기법이다.

지연 바인딩(Lazy Binding) 복습

Partial RELRO 환경에서 아직 한 번도 호출되지 않은 함수는, PLT를 통해 처음 호출될 때 _dl_runtime_resolve가 심볼 이름으로 실제 주소를 찾아서 GOT에 채워 넣는다. 이 과정은 .dynsym(심볼 이름 테이블)과 .rel.plt(재배치 정보) 같은, 프로그램에 원래 존재하는 구조체를 참조해서 이루어진다.

공격 아이디어

"이미 존재하는 그 구조체들을 스택 위에 가짜로 하나 더 만들어서, _dl_runtime_resolve가 우리가 원하는 심볼 이름(예: "system")을 찾아 호출하게 속인다." libc 주소가 전혀 필요 없다 — 심볼 이름 문자열만 있으면, 링커가 알아서 그 함수를 찾아 호출해준다.

pwntools로 자동화

from pwn import *

elf = context.binary = ELF('./vuln')
rop = ROP(elf)
dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["/bin/sh"])
rop.read(0, dlresolve.data_addr)
rop.ret2dlresolve(dlresolve)

payload = b'A' * offset + rop.chain()

Ret2dlresolvePayload가 가짜 심볼/재배치 구조체를 자동으로 조립해준다. 원리를 모르고 이 코드만 복붙하면 오프셋 하나만 틀려도 왜 안 되는지 디버깅이 불가능하니, 최소한 "왜 libc 주소 없이도 되는지"는 이해하고 쓰는 게 좋다.

조건

Full RELRO에서는 GOT/링커 구조 자체가 이미 다 resolve되고 읽기전용이라 이 기법이 원천적으로 막힌다. Partial RELRO(또는 No RELRO) 환경에서만 통한다.

Pwnable 카테고리의 글 (14/14)

  1. 메모리 구조 기초: 스택, 힙, 레지스터부터 이해하기
  2. 스택 버퍼 오버플로우: 가장 기본적인 메모리 손상 공격
  3. Format String 취약점: printf가 위험해지는 순간
  4. Stack Canary: 원리와 우회 방법
  5. NX/DEP 우회와 Return-to-libc
  6. ASLR과 정보 유출(Leak)
  7. ROP (Return-Oriented Programming) 완전 정복
  8. 힙 익스플로잇 기초: Use-After-Free와 Double Free
  9. pwntools 실전 활용법
  10. UDF(User-Defined Function) 기반 DB 권한 상승: 원리부터 실전까지
  11. Pass-the-Hash / Pass-the-Ticket: 원리와 실전
  12. eBPF 기반 지속성(Persistence) 기법
  13. Full RELRO 환경에서의 GOT Overwrite와 우회
  14. ret2dlresolve: 심볼 정보 없이 임의 함수 호출하기
← Full RELRO 환경에서의 GOT Overwrite와 우회