DreamHack 시스템 해킹 문제 Docker 구성 방법

DreamHack 시스템 해킹 문제 Docker 구성 방법

Dreamhack 은 티오리에서 운영하는 사이트로, 해킹 및 사이버 보안과 관련된 내용을 공부하고 워게임, CTF 콘텐츠를 이용할 수 있다.

Dreamhack 에서 이용할 수 있는 워게임 콘텐츠 중 시스템 해킹 분야는 바이너리 파일을 실행하는 환경에 따라 exploit 의 성공 여부가 좌우되는 경우가 많기 때문에 해당 바이너리를 실행하는 도커 환경을 구성하는 DockerFile 이 함께 첨부된다.

이 글에서는 문제에 첨부된 DockerFile 을 이용해 바이너리 파일 실행 환경을 구성하고, pwndbg 로 동적 디버깅을 하는 방법에 대해 설명한다.

1. 테스트 환경

테스트 용 문제는 struct person_t 을 사용했다. 해당 문제의 첨부 파일을 다운로드하고 zip 압축 해제하면 아래와 같은 폴더 구조를 가진다.

.
├── deploy
│   ├── chall
│   ├── chall.c
│   └── flag
└── Dockerfile

deploy 내 파일은 다음과 같은 역할을 가진다.

  • chall : chall.c 로 빌드한 바이너리 실행 파일.
  • chall.c : 취약점을 가지는 소스 코드.
  • flag : flag 값을 가지는 텍스트 파일. 첨부 파일은 더미 값이 적혀 있다.

Docker 이미지를 구성하는 내용은 Dockerfile 에 아래와 같이 적혀 있다.

FROM ubuntu:22.04@sha256:340d9b015b194dc6e2a13938944e0d016e57b9679963fdeb9ce021daac430221

ENV user chall
ENV chall_port 31337

RUN apt-get update
RUN apt-get -y install socat

RUN adduser $user -u 1337

ADD ./deploy/flag /home/$user/flag
ADD ./deploy/$user /home/$user/$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 90 TCP-LISTEN:$chall_port,reuseaddr,fork EXEC:/home/$user/$user

OS 환경은 Linux kali 6.12.13-amd64 이며 pwndbg 버전은 아래와 같다.

  • Pwndbg: 2025.02.19 build: a82e152f
  • Python: 3.13.2 (main, Mar 13 2025, 14:29:07) GCC 14.2.0
  • GDB: 16.2
  • Capstone: 5.0.5
  • Unicorn: 2.1.3
  • Pwnlib: 4.14.0

2. Docker 환경 구성

2.1 이미지 빌드

첨부 파일을 압축 해제한 디렉토리로 working directory 를 이동한 후, 아래와 같은 docker 명령어를 실행하여 현재 경로의 Dockerfile 의 정보를 참조한 이미지를 빌드해야 한다.

$ sudo docker build -t {임의의 이미지 이름} .

docker images 명령어를 실행하면 현재 로컬 시스템 내 docker 이미지 목록을 확인할 수 있다.

$ sudo docker images           
REPOSITORY                   TAG        IMAGE ID       CREATED        SIZE
... 중략 ...
struct                       latest     4ee4a8e5667f   3 weeks ago    140MB
... 후략 ...

2.2 컨테이너 생성

이제 앞선 과정에서 빌드한 이미지를 기반으로 컨테이너를 생성해야 한다. 컨테이너 생성 옵션 중 중요한 것은 -p 옵션이다. 이 옵션은 컨테이너 외부-내부 포트를 매칭시키는 값을 지정한다. 이 문제의 Dockerfile 은 컨테이너 내부에서 31337번 포트를 통해 socat 을 실행해 통신하므로, 컨테이너 외부 포트를 31337번 포트와 매칭시켜야 한다.

$ sudo docker run -d -p {임의의 포트}:{Dockerfile 에서 expose 한 포트 번호} --name {임의의 컨테이너 이름} {앞서 빌드한 이미지 이름}

예제에서는 대충 1234번 포트를 31337 포트와 매칭시키고 컨테이너 이름은 struct_container1 로 설정했다.

$ sudo docker run -d -p 1234:31337 --name struct_container1 struct
c79a35372970ec7104694891ff40723c8565a50135f7aa58cf67e48fd4bcc22b

docker ps 명령어로 현재 실행 중인 도커 컨테이너 정보를 확인할 수 있다.

$ sudo docker ps                                                  
CONTAINER ID   IMAGE            COMMAND                  CREATED          STATUS          PORTS                                         NAMES
c79a35372970   struct           "/bin/sh -c 'socat -…"   45 seconds ago   Up 44 seconds   0.0.0.0:1234->31337/tcp, :::1234->31337/tcp   struct_container1

3. pwndbg 로 원격 디버깅

디버깅에 앞서 우리가 생성한 도커 컨테이너가 잘 작동하는지 확인해보자. dreamhack 에서 VM 에 접속하듯이 nc 명령어를 실행해 도커 컨테이너에 접속하여 바이너리를 실행할 수 있다. 현재 이 컨테이너는 localhost 에서 실행 중이며, 위 예제에서 외부 포트를 1234로 설정했으므로 아래와 같은 명령어를 실행하여 접속한다.

$ nc localhost 1234

접속에 성공하면 컨테이너 내부의 바이너리와 정상적으로 사용자 입력, 출력을 주고 받을 수 있는 것을 확인할 수 있다.

컨테이너가 정상 실행 중인 것을 확인했다면 이제 pwndbg 로 원격 디버깅을 시도할 차례다. nc localhost {port 번호} 명령어로 컨테이너 내 바이너리 파일을 실행 후, ps -ef 명령어를 실행하면 컨테이너 내에서 실행 중인 바이너리 프로세스를 확인할 수 있다. 아래 예시에서는 /home/chall/chall 경로를 실행 중인 48274 pid 프로세스가 컨테이너 내에서 실행 중인 바이너리다.

$ sudo ps -ef | grep chall
1337       44498   44469  0 11:58 ?        00:00:00 /bin/sh -c socat -T 90 TCP-LISTEN:$chall_port,reuseaddr,fork EXEC:/home/$user/$user
1337       44524   44498  0 11:58 ?        00:00:00 socat -T 90 TCP-LISTEN:31337,reuseaddr,fork EXEC:/home/chall/chall
1337       48273   44524  0 12:05 ?        00:00:00 socat -T 90 TCP-LISTEN:31337,reuseaddr,fork EXEC:/home/chall/chall
1337       48274   48273  0 12:05 ?        00:00:00 /home/chall/chall
kali       48715   48233  0 12:06 pts/5    00:00:00 grep --color=auto chall

sudo gdb -p {원격 디버깅할 프로세스 pid} 명령어를 실행하면 해당 프로세스에 attach 할 수 있다. 앞서 ps 명령어로 찾은 컨테이너 내 바이너리 프로세스를 찾아 attach 하자.

사용자 권한으로 gdb 를 실행하면 ~/.gdbinit 파일 내 명시된 gdbinit.py 파일 경로를 찾아 자동으로 pwndbg 로 변경되지만, sudo 명령어로 gdb 를 실행하면 자동으로 pwndbg 로 변경되지 않을 수 있다. 이 경우엔 gdb 로 원격 접속 후, 직접 source 명령어로 gdbinit.py 경로를 지정하여 pwndbg 로 변경하자.

(gdb) source {pwndbg 경로}/gdbinit.py

pwndbg 로 변경 후 procinfo 명령어를 실행하면 /home/chall/chall 프로세스에 제대로 attach 한 것을 확인할 수 있다.

pwndbg> procinfo
exe        '/home/chall/chall'
cmdline    /home/chall/chall ''
cwd        '/home/chall'
pid        53397
tid        53397
selinux    docker-default (enforce)
ppid       44498
uid        [1337, 1337, 1337, 1337]
gid        [1337, 1337, 1337, 1337]
groups     [1337]
fd[0]      socket:[113898]
fd[1]      socket:[113898]
fd[2]      pipe:[95364]
fd[3]      socket:[113078]
fd[4]      socket:[113079]

이제 원하는대로 브레이크 포인트를 설정하거나, instruction 을 하나하나 실행하면서 디버깅하면 된다. 참고로 attach 한 직후는 아마 사용자 입력을 받기 위해 blocking된 상태라서 step into 명령어를 내려도 instruction 이 실행되지 않을 거다. nc 명령어를 실행한 창으로 이동해 적절히 사용자 입력 값을 입력하면 blocking 상태가 풀린다.