环境搭建
version:5.15.0
commit:8bb7eca972ad531c9b149c0a51ab43a417385813
config:
defconfig+:
CONFIG_USER_NS=y |
漏洞分析
源码分析
这是一个漏洞的越界写,其源码中的位置是:https://elixir.bootlin.com/linux/v5.15/source/net/netfilter/nf_dup_netdev.c#L67
这个函数在这里被调用:
我们希望的是expr类型为nft_dup_detdev_ops,这样在下面的函数中就能间接调用到我们的目标函数:
注意这个expr_type的那么是”dup“:
下面让我们分析其过程,其关键的调用函数是nft_flow_rule_create
,https://elixir.bootlin.com/linux/v5.15/source/net/netfilter/nf_tables_offload.c#L87:
整个函数可以分为4部分:
- 首先根据
expr->ops->offload_flags & NFT_OFFLOAD_F_ACTION
来计算局部变量num_actions
; - 然后分配
flow
,并在里边分配flow->rule
,其大小是根据局部变量num_actions
决定的; - 分配
ctx
,注意到是kzalloc
因此ctx->num_actions
最开始是0; - 最后是循环调用函数,也就是我们的漏洞函数,注意每次循环中
ctx->num_actions
会+1;
相关数据结构
注意flow
和rule
的分配行为:
然后看flow_rule_alloc
函数是如何分配rule
的:
相关数据结构如下:
因此整个分配的结构如下:
成因总结
触发漏洞
首先看调用链:
nf_tables_newrule
->nft_flow_rule_create
之前在笔者的nf_tables的分析中给出了rule的分配方法,因此在nlmsg_hdr->nlmsg_type == (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWRULE
就可以进入到nf_tables_newrule
函数;
然后添加NFTA_RULE_TABLE
、NFTA_RULE_CHAIN
就可以实现rule的分配:
之后对chain->flags
有要求:
可以看到这里有对chain的flags的设置:https://elixir.bootlin.com/linux/v5.15/source/net/netfilter/nf_tables_api.c#L2453
这里注意到在nf_tables_newchain
函数最后调用了nf_tables_addchain
函数发生了错误:https://elixir.bootlin.com/linux/v5.15/source/net/netfilter/nf_tables_api.c#L2084
第一个检查:
如果else了,NFT_CHAIN_HW_OFFLOAD
会直接error:
经过分析+调试发现,我们需要在创建chain的时候添加HOOK,使之创建的是一个base_chain,这里需要添加CONFIG_NF_TABLES_INET
保证chain_type数组对应的项被初始化,netlink构造如下:
有了前面创建的base_chain,我们可以很容易走到nft_flow_rule_create
函数,下面就是考虑如何构造rule的expr的问题了,还是看源码,首先是在这里构造expr https://elixir.bootlin.com/linux/v5.15/source/net/netfilter/nf_tables_api.c#L3358 :
首先要分配一个expr_info数组;
然后每一个子attr都是NFTA_LIST_ELEM类型的,然后就是进入到nf_tables_expr_parse
函数中进行分析:https://elixir.bootlin.com/linux/v5.15/source/net/netfilter/nf_tables_api.c#L2687
可以看到
expr_type的获取代码如下:
后续调试发现,本漏洞涉及的是一个特定的设备,因此family要设置成5;
然后创建base_chain的时候host_num要设置成0,这样才能和family为5的type的mask对上,(这么说netdev那个config是必须的,ipv4不是必须的🤔)
之后在如下地方遇到问题:
因此还需要有一个NFTA_HOOK_DEV的项:
这个项应该就是一个string,但是要在__dev_get_by_name
函数中被找到:
随便搞一个name进去调试看看:
现在有了如下msg:
新的问题是可以成功创建一个chain,但是不知道为啥会有错误,然后到了netlink处理函数中把笔者刚刚创建的chain给释放了,目前处理的办法是,把添加rule的消息也放到同一个netlink批处理消息中:
经过调整发现,NFTA_EXPR_NAME
要8字节对齐,才能添加新的attr,此时我们再添加如下attr即可成功走到函数nft_flow_rule_create
:
但是卡在了这个flags上:
这个flags始终是0;🤔
发现只有immediate类型的expr才有这个flag:
(注意要将immediate后边的0该改成真0)
最终可以实现越界写的一个poc:
其原理是一个immediate类型的expr保证num_actions不为0,然后发现此时的漏洞cache是kmalloc-128,每个action的大小是0x50,写id的偏移是0x20,写dev的偏移是0x38;
因此要有2个dup的expr可以越界写dev,三个dup的expr才可以越界写id,id==5;
这里很奇怪,dup的expr在检查的时候要看一个设备,设备的ifindex需要是1才能匹配成功,但是这个参数却是immediate那个expr的NFTA_DATA_VALUE用小端序传递进去的,就很不理解;
关键代码如下:(需要事先创建一个名为”my_table”的table)
void pwn(int sock){ |
漏洞利用
我们的漏洞就是能越界写dev和id:
写的结构体是kmalloc-128;
每次递增0x50;
id的偏移是0x20;-> 0x20, 0x70, 0xa0/0x20;
dev的偏移是0x38;->0x38->0x88/0x8->0x58->0xa8/0x28
调试技巧:
b nft_fwd_dup_netdev_offload |
内存噪声分析:
释放一个obj之后的freelist如下:
结果发现分配的entries是这样的:
说明有一个分配噪声;
释放两个obj的情况如下:
但是这个dev是来自slab的内存,因此不能直接越界写到pgv里:
方法二
学习使用msg_msg->security构造net_dev的uaf,然后劫持控制流,uaf之后可以使用setxattr占位,因此这个net_dev是一个用户可控的地址,所以我们需要提前泄露:
- 堆风水:rule_flow,user_key_payload,msg_msg
- oob:此时在user_key_payload中会有net_dev的残留,msg_msg->security会被覆盖为net_dev;
- 读key泄露地址;
- 释放msg_smg,构造uaf的net_dev;
- setxattr占位net_dev并伪造结构;
- exit退出,控制流劫持;//不会劫持控制流,以后在学
方法三
这里搞一个EOE的方法,只泄露地址;
msg_msg的结构如下:
可以看到m_ts的偏移是0x18:
dev的偏移是0x38;->0x38->0x88/0x8->0x58->0xa8/0x28->0x78->0xc8/0x8
所以达不到目标;
参考
https://blog.csdn.net/panhewu9919/article/details/128308580
https://github.com/chenaotian/CVE-2022-25636?tab=readme-ov-file
https://www.openwall.com/lists/oss-security/2022/02/21/2