ENFJ_Blog
SECCOMP 본문
https://dreamhack.io/lecture/courses/263
Dreamhack | 강의 | Dreamhack
dreamhack.io
샌드박스(Sandbox)
Sandbox는 외부의 공격으로부터 시스템을 보호하기 위해 설계한 기법이다.
보호된 영역에서 내부에서 외부로 접근을 제한하는 격리기술과 같은 보안 기술이다. 각 애플리케이션은 실행 목적과 기능이 다 다르기 때문에 샌드박스는 개발자가 직접 명시해야 한다. 만약, 샌드박스 관련 지식이 부족해 너무 많은 샌드박스를 실행하게 되면 서비스 접근성을 해치며, 일부 기능이 실행되지 않을 수 있다.
SECCOMP
SECure COMPuting mode (SECCOMP)는 리눅스 커널 프로그램에서 제공하는 컴퓨터 보안 기능이다. SECCOMP를 통해서 불필요한 시스템 콜을 차단할 수 있다. 샌드박스 기법에 속하는 기법중 하나이다.
SECCOMP는 두 가지 모드 중 하나를 선택해서 사용할 수 있다.
STRICT_MODE
STRICT_MODE는 read, write, exit, sigreturn 시스템 콜의 호출만 허용하여 이외의 시스템 콜이 들어오면 SIGKILL 시그널이 발생하여 프로그램이 종료된다.
작동원리
STRICT_MODE 일부 처리 코드이다.
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
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);
}
위에서 mode1_syscalls은 read, write, exit, sigreturn의 시스템 콜 번호를 저장하고 있다. 또 각 비트에 따라서 맞는 시스템 콜 번호를 저장한다.
애플리케이션이 실행되고 시스템 콜이 호출되면 __secure_computing 함수에 먼저 진입한다. 해당 함수에서 시스템 콜 번호가 전달되어 저장된 mode1_syscalls 또는 mode1_syscalls_32에 정의된 번호와 일치하는지 검사하고, 다르다면 SIGKILL 시그널이 전달되고 SECCOMP_RET_KILL이 반환된다.
FILTER_MODE (라이브러리 함수)
FILTER_MODE는 원하는 시스템 콜의 호출을 허용하거나 거부할 수 있다. 이를 적용하는 방법은 라이브러리 함수를 이용하는 방법과 필터링에 주로 쓰이는 Berkeley Packet Filter (BPF) 문법을 적용하는 방법 두 가지로 나뉜다.
apt install libseccomp-dev libseccomp2 seccomp
SECCOMP 라이브러리 설치 명령어
라이브러리 함수에는 대표적으로 이런 함수들이 존재한다.
seccomp_init | SECCOMP 모드의 기본 값을 설정하는 함수입니다. 임의의 시스템 콜이 호출되면 이에 해당하는 이벤트가 발생합니다. |
seccomp_rule_add | SECCOMP의 규칙을 추가합니다. 임의의 시스템 콜을 허용하거나 거부할 수 있습니다. |
seccomp_load | 앞서 적용한 규칙을 애플리케이션에 반영합니다. |
FILTER_MODE (BFP)
위 라이브러리 함수 말고 Berkeley Packet Filter (BPF) 를 사용해서 SECCOMP를 적용할 수도 있다. 원래 BPF는 커널에서 지원하는 VM으로 네트워크 필터링을 목적으로 했다. 그래서 BFP는 임의의 데이터를 비교하고, 특정 구문으로 분기하는 명령어를 제공한다. 이를 이용해 라이브러리 필터링과 같이 BFP로 특정 명령어를 제어할 수 있다. BPF는 VM이기 때문에 다양한 명령어가 존재한다. 밑에는 SECCOMP 적용하는데 필요한 몇개 명령어이다.
BPF_LD | 인자로 전달된 값을 누산기에 복사합니다. 이를 통해 값을 복사한 후 비교 구문에서 해당 값을 비교할 수 있습니다. |
BPF_JMP | 지정한 위치로 분기합니다. |
BPF_JEQ | 설정한 비교 구문이 일치할 경우 지정한 위치로 분기합니다. |
BPF_RET | 인자로 전달된 값을 반환합니다. |
BPF Macro
BPF 코드를 직접 입력하지 않고 편리하게 원하는 코드를 실행할 수 있게끔 매크로를 제공합니다.
BPF_STMT
operand에 해당하는 값을 명시한 opcode로 값을 가져옵니다. opcode는 인자로 전달된 값에서 몇 번째 인덱스에서 몇 바이트를 가져올 것인지를 지정할 수 있습니다.
BPF_STMT(opcode, operand)
BPF_JUMP
BPF_STMT 매크로를 통해 저장한 값과 operand를 opcode에 정의한 코드로 비교하고, 비교 결과에 따라 특정 오프셋으로 분기합니다.
BPF_JUMP(opcode, operand, true_offset, false_offset)
ALLOW LIST (라이브러리 함수)
#include <seccomp.h>
#include <sys/prctl.h>
#include <unistd.h>
void sandbox() {
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_KILL);
if (ctx == NULL) {
printf("seccomp error\n");
exit(0);
}
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);
}
위 샌드박스 코드 예제를 보면 SCMP_ACT_KILL을 통해 모든 시스템 콜을 허용하지 않는 규칙을 생성한다. 그 뒤 seccomp_rule_add를 통해 원하는 특정 함수만 허용하는 규칙을 적용한다.
이 규칙을 적용하고 난 뒤 만약 여기서 명시한 명령어 이외의 시스템 콜을 수행하게 된다면 시그널을 발생되고 시스템이 종료된다
ALLOW LIST (BPF)
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>
#include <stddef.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;
}
위 SECCOMP 적용 코드는 라이브러리 함수와 같은 기능을 한다. 위 정의된 명령어를 제외한 시스템 콜을 이용하게 되면,시그널이 발생하며 프로세스가 강제로 종료된다.
DENY LIST (라이브러리 함수)
#include <seccomp.h>
#include <sys/prctl.h>
#include <unistd.h>
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(openat), 0);
seccomp_load(ctx);
}
이 DENY LIST의 역할은 정의된 시스템 콜을 실행하게 된다면, 시그널을 발생시켜 종료되게 된다. 위에서는 만약 sandbox 함수가 실행되고, open 함수가 실행된다면, 시그널이 발생되어 강제 종료 되는것과 같다.
DENY LIST (BPF)
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>
#include <stddef.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
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. */
DENY_SYSCALL(open),
DENY_SYSCALL(openat),
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;
}
이 코드도 open과 opennat 시스템 콜을 막는 DENY LIST이다.
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) {
이 구문이 Seccomp를 설정하는 구문이다. 첫 번째 인자가 seccomp 번호, prog가 필터링 할 부분을 알려주는 구조체 주소 이다.
seccomp-tools
seccomp-tools은 seccomp 분석을 도와주는 도구로, seccomp가 적용된 바이너리에 어떤 시스템 콜을 필터링하는지, BFP 어셈블/디스어셈블과 같은 기능을 제공하는 도구이다.
이런식으로 command가 존재하고 만약 dump를 실행하게 되면 seccomp 필터링을 알 수 있다.
ABI
seccomp tools을 활용해서 비교 구문의 일부를 보게 되면
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
와 같이 시스템 콜 번호가 0x40000000 보다 큰지 검사하는 비교 구문이 존재할 때도 있다.
이게 왜 존재하냐 하면 x86-64와 x32, 두 개의 ABI(Application Binary Interface)가 같은 프로세스에서 동작한다. x86-64에서도 x32 명령어는 호환된다. Seccomp를 사용할 때 아키텍처 명시를 AUDIT_ARCH_X86_64를 사용하는 매크로를 사용한다. 이는 x86-64와 x32를 같이 일컫는 필드명이다. 그러나 두 개는 다른 아키텍쳐 이기 때문에 이를 구별하기 위해 커널이 시스템 콜 번호에 특정한 값을 사용하는데, 이 값이 0x40000000 이다.
x32 명령어를 호환하는 do_syscall_x32 함수를 살펴보면, 호출하는 시스템 콜 번호에서 __X32_SYSCALL_BIT 값을 뺀 시스템 콜 번호를 사용합니다. 해당 매크로의 값은 0x40000000로 정의되어 있습니다.
=> SECCOMP는 A<0x40000000 검사코드를 통해 x32 ABI를 이용한 syscall을 막는거다.
'Pwn' 카테고리의 다른 글
Bypass Seccomp-1 (wargame) (0) | 2024.07.30 |
---|---|
Adobe Acrobat 1-day vulnerability CVE-2021-39863 (2) | 2024.07.27 |
[White Hat School] a_piece_of_pie (0) | 2024.03.28 |
[dreamhack wargame] Gaia문제 (0) | 2024.01.25 |
Use-After-Free(UAF) (0) | 2023.10.20 |