首页
社区
课程
招聘
[原创]Linux内核Ret2dir
发表于: 2020-12-5 00:52 13209

[原创]Linux内核Ret2dir

2020-12-5 00:52
13209

论文地址:https://www.usenix.org/system/files/conference/usenixsecurity14/sec14-paper-kemerlis.pdf

作者:哥伦比亚大学

发表于: USENIX Security(%,计算机安全顶会)

ret2usr由于smep、smap等缓解机制导致内核与用户空间的隔离,使得原生的ret2usr难以直接利用。

而论文中提出关键的一点:

However, the implicit physical memory sharing between user processes and the kernel allows an attacker to deconstruct the isolation guarantees offered by ret2usr protection mechanisms, and redirect the kernel’s control or data flow to user-controlled code or data.

翻译一下就是:由于内核空间与用户和空间的 隐形内存共享 导致攻击者可以绕过ret2usr的保护机制,使得内核空间的执行流最终流向一块区域,这一块区域中的数据or指令是可以由用户控制的。

紧接着,作者提出:

A key facility that enables the implicit sharing of physical memory is physmap:a large, contiguous virtual memory region inside kernel address space that contains a direct mapping of part or all (depending on the architecture) physical memory.

重点在于physmap这一块在内核中连续的一块空间,并且physmap映射了部分或所有(取决于具体架构)的物理内存。

文章还指出:物理内存分配给用户和内核,可能产生别名现象,具体表现为:多块虚存映射到同一块物理内存地址。

而假设physmap能映射内核中大部分或全部物理地址,那么攻击者控制的用户进程就有可能通过地址别名来访问。

(感觉这个讲的比较清楚Orz)

总体来说:ret2dir利用kernel space与user space的隐形地址共享,使得要劫持的内核数据或者执行流进入这一别名空间,类似一种不用返回用户空间的ret2usr改进版。更重要的是我们的payload不需要显式的放入内核空间(比如使用堆喷等技术),当page frame被给予attack process的时候,我们的攻击payload就会 “emerges in kernel space”

但是这种技术仍有一些限制。

系统会对能分配给内核的kernel-resident buffer加以限制,使得我们的payload不一定出现在kernel space中,进而要求我们尽量将payload封装在一些内核数据对象中。

而这一块区域有以下极为重要的特性:

(i) there exists a direct mapping of part or all physical memory in kernel space.

(ii) the mapping starts at a fixed, known location. The latter is true even in the case where kernelspace ASLR (KASLR) [35] is employed

由后一条我们可以知道,即使开启了ASLR,这一块区域的位置也是固定的。那么我们只要找到基地址,就可以通过线性加减来得到其他的位置。

攻击过程:

1.申请大量内存(利用堆喷等),提高命中率。

2.泄露slab地址,计算physmap基地址。

3.劫持执行流到physmap。

https://github.com/bsauce/CTF/tree/master/ret2dir/kpwn_driver

kpwn.c

实现了几个漏洞

POC主要做了如下事情:

1.首先调用kmalloc申请0x200空间,计算出slab基地址。

2.在 用户态 喷射一大片空间,并用 'K' 填充,作为标记。

3.从内核slab基地址开始,在 内核空间 以页为单位向下搜索,直到找到标记了 'KKKKK...' 的空间,即找到了physmap的地方。

4.利用write函数在 内核空间 中修改被标记的 'KKKKK...'

5.验证 用户空间 中的 'KKKK...' 是否有发生变化。若发生变化,则说明POC成功。

效果:

以图说明:

http://c.biancheng.net/view/1272.html

https://www.jianshu.com/p/3c662b6163a7

https://www.anquanke.com/post/id/185408

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
#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("Dual BSD/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;
};
 
 
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)){  // (void *)buffer
        return -EINVAL;
    }
    return ret;
}
static long del_any(struct in_args *args){
    long ret = 0;
    kfree((void *)args->addr);
    return ret;
}
static long kpwn_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
    long ret = -EINVAL;
    struct in_args in;
    if(copy_from_user(&in,(void *)arg,sizeof(in))){
        return ret;
    }
    switch(cmd){
        case READ_ANY:
            ret = read_any(&in);
            break;
        case WRITE_ANY:
            ret = write_any(&in);
            break;
        case DEL_ANY:
            ret = del_any(&in);
            break;
        case ADD_ANY:
            ret = add_any(&in);
            break;
        default:
            ret = -1;
    }
    return ret;
}
static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open =      NULL,
    .release =   NULL,
    .read =      NULL,
    .write =     NULL,
    .unlocked_ioctl = kpwn_ioctl
};
 
static struct miscdevice misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name  = "kpwn",
    .fops = &fops
};
 
int kpwn_init(void)
{
    misc_register(&misc);
    return 0;
}
 
void kpwn_exit(void)
{
    printk(KERN_INFO "Goodbye hackern");
    misc_deregister(&misc);
}
module_init(kpwn_init);
module_exit(kpwn_exit);
#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("Dual BSD/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;
};
 
 
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)){  // (void *)buffer
        return -EINVAL;
    }
    return ret;
}
static long del_any(struct in_args *args){
    long ret = 0;
    kfree((void *)args->addr);
    return ret;
}
static long kpwn_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
    long ret = -EINVAL;
    struct in_args in;
    if(copy_from_user(&in,(void *)arg,sizeof(in))){
        return ret;
    }
    switch(cmd){
        case READ_ANY:
            ret = read_any(&in);
            break;
        case WRITE_ANY:
            ret = write_any(&in);
            break;
        case DEL_ANY:
            ret = del_any(&in);
            break;
        case ADD_ANY:
            ret = add_any(&in);
            break;
        default:
            ret = -1;
    }
    return ret;
}
static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open =      NULL,
    .release =   NULL,
    .read =      NULL,
    .write =     NULL,
    .unlocked_ioctl = kpwn_ioctl
};
 
static struct miscdevice misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name  = "kpwn",
    .fops = &fops
};
 
int kpwn_init(void)
{
    misc_register(&misc);
    return 0;
}
 

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 6
支持
分享
最新回复 (2)
雪    币: 499
活跃值: (2189)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
2
 给大佬端茶 
2020-12-15 15:40
0
雪    币: 0
活跃值: (372)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
有一点不能理解,为什么开发者会设计出一片物理内存映射到两块区域呢,如果用户读取并修改了相关内核的数据不就是会造成严重的破坏吗(也就是不清楚这个到底是开发者因为什么目的而产生的feature还是单纯的设计失误)
2022-9-30 21:24
0
游客
登录 | 注册 方可回帖
返回
//