[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)가 갖고 있는 지연 바인딩 메커니즘을 이용해서, 링커가 "이 함수를 찾아서 호출해달라"는 우리의 위조 요청을 그대로 믿게 만드는 기법이다.
Partial RELRO 환경에서 아직 한 번도 호출되지 않은 함수는, PLT를 통해 처음 호출될 때 _dl_runtime_resolve가 심볼 이름으로 실제 주소를 찾아서 GOT에 채워 넣는다. 이 과정은 .dynsym(심볼 이름 테이블)과 .rel.plt(재배치 정보) 같은, 프로그램에 원래 존재하는 구조체를 참조해서 이루어진다.
"이미 존재하는 그 구조체들을 스택 위에 가짜로 하나 더 만들어서, _dl_runtime_resolve가 우리가 원하는 심볼 이름(예: "system")을 찾아 호출하게 속인다." libc 주소가 전혀 필요 없다 — 심볼 이름 문자열만 있으면, 링커가 알아서 그 함수를 찾아 호출해준다.
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) 환경에서만 통한다.