• 【kernel exploit】CVE-2022-32250 nftables错误链表操作导致UAF写的漏洞利用


    影响版本:Linux v4.19.37~v5.18.1 v5.18.2 已修补。由syzkaller发现。7.8分。原作者在 Ubuntu 22.04 Kernel 5.15上提权

    测试版本:Linux-5.17.12 exploit及测试环境下载地址—https://github.com/bsauce/kernel-exploit-factory

    编译选项

    CONFIG_NF_TABLES=y

    CONFIG_NETFILTER_NETLINK=y

    CONFIG_BINFMT_MISC=y (否则启动VM时报错)

    CONFIG_USER_NS=y

    在编译时将.config中的CONFIG_E1000CONFIG_E1000E,变更为=y。参考

    $ wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v5.x/linux-5.17.12.tar.xz
    $ tar -xvf linux-5.17.12.tar.xz
    # KASAN: 设置 make menuconfig 设置"Kernel hacking" ->"Memory Debugging" -> "KASan: runtime memory debugger"
    $ make -j32
    $ make all
    $ make modules
    # 编译出的bzImage目录:/arch/x86/boot/bzImage。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    漏洞描述nftables 模块的 net/netfilter/nf_tables_api.c 采用 NFT_MSG_NEWSET 功能来添加 nft_set 时,处理 lookupdynset expression 时,由于错误的 NFT_EXPR_STATEFUL 检查,nft_expr 对象释放后仍位于nft_set->binding 链表中,新加入 nft_expr 时导致UAF写(触发漏洞需要 CAP_NET_ADMIN 权限)。UAF写会往 kmalloc-64 的偏移 0x18 处写入另一个 kmalloc-64 堆块偏移 0x18 的地址值。

    nft_set_elem_expr_alloc() 先调用 nft_expr_init() 初始化 expression,再检查 expression类型是否为 NFT_EXPR_STATEFUL(不是则直接释放该expression,但是忘记从nft_set->binding 链表解绑,后续链入新的 expression 时触发UAF写)。nft_expr_init() 先调用 nf_tables_expr_parse() ,再分配nft_expr对象内存,最后调用 nf_tables_newexpr() 初始化 nft_expr对象(并绑定到 nft_set->binding 链表)。

    补丁patch 先检查 NFT_EXPR_STATEFUL,再分配 expression 内存。

    diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
    index 12fc9cda4a2cf..f296dfe86b622 100644
    --- a/net/netfilter/nf_tables_api.c
    +++ b/net/netfilter/nf_tables_api.c
    @@ -2873,27 +2873,31 @@ static struct nft_expr *nft_expr_init(const struct nft_ctx *ctx,
     
     	err = nf_tables_expr_parse(ctx, nla, &expr_info);
     	if (err < 0)
    -		goto err1;
    +		goto err_expr_parse;
    +
    +	err = -EOPNOTSUPP;
    +	if (!(expr_info.ops->type->flags & NFT_EXPR_STATEFUL))
    +		goto err_expr_stateful;
     
     	err = -ENOMEM;
     	expr = kzalloc(expr_info.ops->size, GFP_KERNEL_ACCOUNT);
     	if (expr == NULL)
    -		goto err2;
    +		goto err_expr_stateful;
     
     	err = nf_tables_newexpr(ctx, &expr_info, expr);
     	if (err < 0)
    -		goto err3;
    +		goto err_expr_new;
     
     	return expr;
    -err3:
    +err_expr_new:
     	kfree(expr);
    -err2:
    +err_expr_stateful:
     	owner = expr_info.ops->type->owner;
     	if (expr_info.ops->type->release_ops)
     		expr_info.ops->type->release_ops(expr_info.ops);
     
     	module_put(owner);
    -err1:
    +err_expr_parse:
     	return ERR_PTR(err);
     }
     
    @@ -5413,9 +5417,6 @@ struct nft_expr *nft_set_elem_expr_alloc(const struct nft_ctx *ctx,
     		return expr;
     
     	err = -EOPNOTSUPP;
    -	if (!(expr->ops->type->flags & NFT_EXPR_STATEFUL))
    -		goto err_set_elem_expr;
    -
     	if (expr->ops->type->flags & NFT_EXPR_GC) {
     		if (set->flags & NFT_SET_TIMEOUT)
     			goto err_set_elem_expr;
    
    • 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

    保护机制:KASLR/SMEP/SMAP/KPTI

    利用总结:**采用 mqueue 中的 msg_msg 来泄露内核基址,因为 mqueue 中的 posix_msg_tree_node->msg_list 偏移为 0x18(且位于kmalloc-64),恰好是UAF写的偏移;另外,posix_msg_tree_node->msg_list 也能用来构造 Unlink 利用来篡改 modprobe_path。**利用user_key_payload泄露堆地址(便于构造unlink),老生常谈了。注意,需使用ubuntu21.04 以上的版本的libmnl 或 libnftnl才行。

    • (0)初始化
      • 设置 namespace 和 CPU 亲属性;
      • 打开5个 mqueue 用于泄露内核基址和进行Unlink;
      • 分配4个 table 和 set,本次利用需触发3次UAF写;
    • (1)泄露堆指针:利用 user_key_payload 对象读取堆地址
      • (1-1)分配漏洞对象 nft_expr1 并释放;
      • (1-2)喷射50个 user_key_payloadkmalloc-64,调用 addkey,占据漏洞对象 nft_expr1
      • (1-3)链入新的 nft_expr 触发UAF写,往 user_key_payload->data[0] (偏移0x18)写入新 nft_expr+0x18 的地址—kmalloc-64
      • (1-4)读取 user_key_payload 泄露堆地址 heap_base
    • (2)泄露内核基址:利用 mqueue 的posix_msg_tree_node->msg_list来读取 user_key_payload->rcu.func 内核函数地址
      • (2-1)堆风水:**喷射 1000 个 percpu_ref_data 对象(kmalloc-64,调用 syscall(__NR_io_uring_setup, ...))**来去碎片化(参考 CVE-2022-34918利用);
      • (2-2)分配漏洞对象 nft_expr2 并释放;
      • (2-3)喷射4个 posix_msg_tree_nodekmalloc-64,通过 mq_send 调用发送消息),占据漏洞对象 nft_expr2
      • (2-4)释放2个 percpu_ref_data 对象(没用,可以跳过这一步);
      • (2-5)链入新的 nft_expr 触发UAF写,往 posix_msg_tree_node->msg_list (偏移0x18)写入新 nft_expr+0x18 的地址,释放新 nft_expr
      • (2-6)喷射100个 user_key_payload,占据释放的新 nft_expr并占据其附近堆块,用于伪造 posix_msg_tree_node->msg_list 指向的 msg_msg (伪造 msg_msg->m_list = heap_basemsg_msg->m_ts = 0x28,确保 msg_msg->security = NULL);
      • (2-7)通过**读取 posix_msg_tree_node->msg_list (调用 mq_receive)**来泄露 user_key_payload->rcu.func (偏移0x8处)
    • (3)unlink篡改 modprobe_path 提权:利用 mqueue 的posix_msg_tree_node->msg_list构造 Unlink
      • (3-1)分配漏洞对象 nft_expr3 并释放;
      • (3-2)同(2-3)喷射4个 posix_msg_tree_nodekmalloc-64,通过 mq_send 调用发送消息),占据漏洞对象 nft_expr3
      • (3-3)同(2-5)。链入新的 nft_expr 触发UAF写,往 posix_msg_tree_node->msg_list (偏移0x18)写入新 nft_expr+0x18 的地址,释放新 nft_expr
      • (3-4)喷射 1 个 user_key_payload,占据释放的新 nft_expr,用于伪造 posix_msg_tree_node->msg_list 指向的 msg_msg (伪造 msg_msg->mlist.next = modprobe_path-7msg_msg->mlist.prev = (heap_base&0xffffffff00000000)+0x2f706d74);(这里喷1个就能占据新 nft_expr,有点神奇)
      • (3-5)通过**读取 posix_msg_tree_node->msg_list (调用 mq_receive)**来释放该 msg_msg,将该 msg_msg 从链表中取出时触发 Unlink;
    • (4)触发执行 modprobe_path 并提权。

    1. 漏洞分析

    1-1. 简介

    Netfilter:这是Linux内核中通过用户定义handler来实现网络相关任务的框架,Netfilter 提供不同的函数用于包过滤、网络地址转换、端口转换、包日志记录,内核模块可以通过Netfilter提供的一系列 hook,将回调函数注册到内核网络栈。

    nftables:属于Netfilter的一个组件,可以根据用户定义的规则来对包进行过滤或路由重定向。nftables支持sets,可以在一个规则中使用多个IP地址、端口号。在定义规则时可以用大括号表示sets(例如{22, 80, 443}),sets类型包括 ipv4_addr, ipv6_addr, ether_addr / inet_proto / inet_service / mark

    nftables 采用 tables / chains / rules / expressions 来存储和处理命令,tables 包含多个 chainstables 与特定协议绑定在一起,例如 IP / IP6chains 包含多个 rules 和待处理网络流量信息的类型;rules 包含多个 expressionsexpressions 评估输入是否满足一系列条件。

    关于nftables 代码和结构分析可参见 【kernel exploit】CVE-2022-1015 nftables 栈溢出漏洞分析与利用 / 【kernel exploit】CVE-2022-34918 nftable堆溢出漏洞利用(list_head任意写)

    1-2. 结构关系

    结构关系nft_table -> nft_set -> nft_expr set 之间是双链表链接,expression 之间也是双链表链接。

    请添加图片描述

    1-3. 代码分析

    调用关系nf_tables_newset() -> nft_set_elem_expr_alloc() (漏洞函数)-> nft_expr_init() (expression初始化) -> nf_tables_newexpr() -> nft_lookup_init()lookup expression) -> nf_tables_bind_set() (将expression绑定到 set->binding

    (1)入口函数 nf_tables_newset()
    static const struct nfnl_callback nf_tables_cb[NFT_MSG_MAX] = {
    	··· ···
    	[NFT_MSG_NEWSET] = {
    		.call		= nf_tables_newset, 		// <---------
    		.type		= NFNL_CB_BATCH,
    		.attr_count	= NFTA_SET_MAX,
    		.policy		= nft_set_policy,
    	},
    	··· ···
    }
    static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info,
    			    const struct nlattr * const nla[])
    {
    	const struct nfgenmsg *nfmsg = nlmsg_data(info->nlh);
    	u32 ktype, dtype, flags, policy, gc_int, objtype;
    	struct netlink_ext_ack *extack = info->extack;
    	u8 genmask = nft_genmask_next(info->net);
    	int family = nfmsg->nfgen_family;
    	const struct nft_set_ops *ops;
    	struct nft_expr *expr = NULL;
    	struct net *net = info->net;
    	struct nft_set_desc desc;
    	struct nft_table *table;
    	unsigned char *udata;
    	struct nft_set *set;
    	struct nft_ctx ctx;
    	size_t alloc_size;
    	u64 timeout;
    	char *name;
    	int err, i;
    	u16 udlen;
    	u64 size;
    	···
    	set = nft_set_lookup(table, nla[NFTA_SET_NAME], genmask);	// [1] 查找现有的 set
    	if (IS_ERR(set)) {	// 找到则直接返回,一般找不到,则进行下面的初始化
    		if (PTR_ERR(set) != -ENOENT) {
    			NL_SET_BAD_ATTR(extack, nla[NFTA_SET_NAME]);
    			return PTR_ERR(set);
    		}
    	} else {
        	...
    	}
    
    	···
    	set = kvzalloc(alloc_size, GFP_KERNEL);						// [2] 分配 set 空间
    	···
    	INIT_LIST_HEAD(&set->bindings);								// [3] 初始化 set, 注意 bindings 成员是 list 结构
    	INIT_LIST_HEAD(&set->catchall_list);
    	set->table = table;
    	write_pnet(&set->net, net);
    	set->ops = ops;
    	set->ktype = ktype;
    	set->klen = desc.klen;
    	set->dtype = dtype;
    	set->objtype = objtype;
    	set->dlen = desc.dlen;
    	set->flags = flags;
    	set->size = desc.size;
    	set->policy = policy;
    	set->udlen = udlen;
    	set->udata = udata;
    	set->timeout = timeout;
    	set->gc_int = gc_int;
    
    	set->field_count = desc.field_count;
    	for (i = 0; i < desc.field_count; i++)
    		set->field_len[i] = desc.field_len[i];
    
    	err = ops->init(set, &desc, nla);
    	if (err < 0)
    		goto err_set_init;
    
    	if (nla[NFTA_SET_EXPR]) {									// [4] 若设置了 NFTA_SET_EXPR 字段, 则调用 nft_set_elem_expr_alloc() 处理 NFTA_SET_EXPR 对应的数据
    		expr = nft_set_elem_expr_alloc(&ctx, set, nla[NFTA_SET_EXPR]); 			// <------------- 
    		if (IS_ERR(expr)) {
    			err = PTR_ERR(expr);
    			goto err_set_expr_alloc;
    		}
    		set->exprs[0] = expr;
    		set->num_exprs++;
    	} 
    	...
    }
    
    
    • 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
    (2)漏洞函数nft_set_elem_expr_alloc()
    struct nft_expr *nft_set_elem_expr_alloc(const struct nft_ctx *ctx,
    					 const struct nft_set *set,
    					 const struct nlattr *attr)
    {
    	struct nft_expr *expr;
    	int err;
    
    	expr = nft_expr_init(ctx, attr); 	// [4-1] 调用 nft_expr_init() 初始化expr
    	if (IS_ERR(expr))
    		return expr;
    
    	err = -EOPNOTSUPP;
    	if (!(expr->ops->type->flags & NFT_EXPR_STATEFUL))
    		goto err_set_elem_expr;			// [4-2] 如果不存在 NFT_EXPR_STATEFUL 标识,则释放刚初始化的 expr
    	...
    
    err_set_elem_expr:
    	nft_expr_destroy(ctx, expr);		// [4-3] 调用 nft_expr_destroy() 释放 expr
    	return ERR_PTR(err);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    (3)初始化 expression nft_expr_init() -> nf_tables_newexpr()
    struct nft_expr {
    	const struct nft_expr_ops	*ops;	//	expr 对应的回调函数表 
    	unsigned char			data[]		//	根据具体 expr 而定
    		__attribute__((aligned(__alignof__(u64))));
    };
    
    static struct nft_expr *nft_expr_init(const struct nft_ctx *ctx,
    				      const struct nlattr *nla)
    {
    	struct nft_expr_info expr_info;
    	struct nft_expr *expr;
    	struct module *owner;
    	int err;
    
    	err = nf_tables_expr_parse(ctx, nla, &expr_info);	// [4-1-1] 初始化expr_info
    	if (err < 0)
    		goto err1;
    
    	err = -ENOMEM;
    	expr = kzalloc(expr_info.ops->size, GFP_KERNEL);	// [4-1-2] 申请空间 `8+私有结构体长度`,本exp中是56
    	if (expr == NULL)
    		goto err2;
    
    	err = nf_tables_newexpr(ctx, &expr_info, expr);		// [4-1-3] 初始化expr
    	if (err < 0)
    		goto err3;
    
    	return expr;
    	...
    }
    
    static int nf_tables_newexpr(const struct nft_ctx *ctx,
    			     const struct nft_expr_info *expr_info,
    			     struct nft_expr *expr)
    {
    	const struct nft_expr_ops *ops = expr_info->ops;
    	int err;
    
    	expr->ops = ops;
    	if (ops->init) { 	// 调用 expr 对应的 init 函数进行初始化
    		err = ops->init(ctx, expr, (const struct nlattr **)expr_info->tb);
    		if (err < 0)
    			goto err1;
    	}
    	...
    }
    
    • 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
    (4)nft_lookup 漏洞expression

    不同类型的 expression 对应的 nft_expr 结构不同(主要是 nft_expr->data存放的内容不同),例如,如果是lookup expression,则nft_expr->data中包含的是 nft_lookup 结构。本漏洞受到影响的 expression 有两种(结构中必须含有 binding 字段,这样才能绑定到 nft_set 结构上):look_up / nft_dynset,分别位于 net\netfilter\nft_lookup.cnet\netfilter\nft_dynset.c。作者对 nft_dynsetkmalloc-96)也完成了利用,本文以 look_upkmalloc-64)为例,初始化函数对应为 nft_lookup_init()

    static const struct nft_expr_ops nft_lookup_ops = {
    	.type		= &nft_lookup_type,
    	.size		= NFT_EXPR_SIZE(sizeof(struct nft_lookup)), 	// 标识 expr->data 的大小 56
    	.eval		= nft_lookup_eval,
    	.init		= nft_lookup_init,//init 是nft_lookup_init
    	.activate	= nft_lookup_activate,
    	.deactivate	= nft_lookup_deactivate,
    	.destroy	= nft_lookup_destroy,
    	.dump		= nft_lookup_dump,
    	.validate	= nft_lookup_validate,
    };
    
    static inline void *nft_expr_priv(const struct nft_expr *expr)
    {
    	return (void *)expr->data;//获取data地址
    }
    
    static int nft_lookup_init(const struct nft_ctx *ctx,
    			   const struct nft_expr *expr,
    			   const struct nlattr * const tb[])
    {
    	struct nft_lookup *priv = nft_expr_priv(expr);		// [1] 获取 expr 的 data 数据段,这里是 nft_lookup 结构体
    	u8 genmask = nft_genmask_next(ctx->net);
    	struct nft_set *set;
    	u32 flags;
    	int err;
    
    	if (tb[NFTA_LOOKUP_SET] == NULL ||
    	    tb[NFTA_LOOKUP_SREG] == NULL)
    		return -EINVAL;
    
    	set = nft_set_lookup_global(ctx->net, ctx->table, tb[NFTA_LOOKUP_SET],
    				    tb[NFTA_LOOKUP_SET_ID], genmask);	// [2] 找到之前创建的set
    	··· // 各种初始化
    
    	priv->binding.flags = set->flags & NFT_SET_MAP;
    
    	err = nf_tables_bind_set(ctx, set, &priv->binding);	// [3] 调用 nf_tables_bind_set() 进行绑定 <---- 将 expression 绑定到 nft_set
    	if (err < 0)
    		return err;
    
    	priv->set = set;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    (5)绑定 nft_set->bindings 链表

    nf_tables_bind_set():主要是调用 list_add_tail_rcu() 函数将 nft_set->bindingsnft_lookup->binding->list 用双向链表链接起来。对应的结构体为 nft_set -> bindingsnft_lookup -> nft_set_binding -> list (结构关系为 nft_expr -> nft_lookup -> nft_set_binding

    int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set,
    		       struct nft_set_binding *binding)
    {
    	struct nft_set_binding *i;
    	struct nft_set_iter iter;
    
    	if (set->use == UINT_MAX)
    		return -EOVERFLOW;
    
    	if (!list_empty(&set->bindings) && nft_set_is_anonymous(set))
    		return -EBUSY;
    
    	if (binding->flags & NFT_SET_MAP) {		// 上层函数设置的,会走入这个分支
    		/* If the set is already bound to the same chain all
    		 * jumps are already validated for that chain.
    		 */
    		list_for_each_entry(i, &set->bindings, list) {
    			if (i->flags & NFT_SET_MAP &&
    			    i->chain == binding->chain)
    				goto bind;
    		}
    		...
    	}
    bind:
    	binding->chain = ctx->chain;
    	list_add_tail_rcu(&binding->list, &set->bindings);	// 调用 list_add_tail_rcu() 链接链表, 将 expression 绑定到 set->binding
    	nft_set_trans_bind(ctx, set);
    	set->use++;
    
    	return 0;
    }
    EXPORT_SYMBOL_GPL(nf_tables_bind_set);
    
    struct nft_set {
    	struct list_head		list;
    	struct list_head		bindings;		// list
    	struct nft_table		*table;
    	possible_net_t			net;
    	char				*name;
    	...
    };
    
    struct nft_lookup {
    	struct nft_set			*set;
    	u8				sreg;
    	u8				dreg;
    	bool				invert;
    	struct nft_set_binding		binding;	// list
    };
    
    struct nft_set_binding {
    	struct list_head		list;
    	const struct nft_chain		*chain;
    	u32				flags;
    };
    
    • 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

    1-4. 漏洞分析

    漏洞分析nft_set_elem_expr_alloc()[4-1] 中已经分成了空间分配、链接到 set 等操作,但如果在 [4-2] 中不满足条件(nft_expr->ops->type->flags != NFT_EXPR_STATEFUL),则 [4-3] 调用 nft_expr_destroy() 释放该 nft_expr 结构体,但是却忘记将 nft_exprset->binding 双向链表中取下来,后面再加入新的 nft_expr 时就会导致UAF。

    释放路径nft_expr_destroy() -> nf_tables_expr_destroy() -> nft_lookup_destroy() -> nf_tables_destroy_set()

    void nft_expr_destroy(const struct nft_ctx *ctx, struct nft_expr *expr)
    {
    	nf_tables_expr_destroy(ctx, expr);			// -> nf_tables_expr_destroy()
    	kfree(expr);
    }
    static void nf_tables_expr_destroy(const struct nft_ctx *ctx,
    				   struct nft_expr *expr)
    {
    	const struct nft_expr_type *type = expr->ops->type;
    
    	if (expr->ops->destroy)						// 调用 lookup 对应的 destory 函数
    		expr->ops->destroy(ctx, expr);
    	module_put(type->owner);
    }
    static void nft_lookup_destroy(const struct nft_ctx *ctx,
    			       const struct nft_expr *expr)
    {
    	struct nft_lookup *priv = nft_expr_priv(expr);
    	
    	nf_tables_destroy_set(ctx, priv->set);		// 啥都没干, nf_tables_destroy_set() 也啥都没干
    }
    void nf_tables_destroy_set(const struct nft_ctx *ctx, struct nft_set *set)
    {
    	if (list_empty(&set->bindings) && nft_set_is_anonymous(set))	// 不满足条件, 因为 set->bindings 包含之前赋值的 expression,所以不会调用 nft_set_destroy() 释放 set 并将 expr 从 set->binding 取出来
    		nft_set_destroy(ctx, set);
    }
    
    
    • 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

    UAF write:再次使用 SET_EXPR 功能,则会在已经释放的nft_expr后面再链接一个nft_expr,造成偏移0x18的uaf 写。根据list 操作的代码和本次参与运算的结构体,可以看出,该uaf写会篡改偏移为0x18 和偏移为0x20的两个字段指向另外两个堆地址。我们这里主要关注偏移0x18,会将其指向一个新的nft_exprkmalloc-64)的偏移0x18处。

    #define list_add_tail_rcu		list_add_tail
    static inline void list_add_tail(struct list_head *new, struct list_head *head)
    {
    	__list_add(new, head->prev, head);
    }
    static inline void __list_add(struct list_head *new,
    			      struct list_head *prev,
    			      struct list_head *next)
    {
    	if (!__list_add_valid(new, prev, next))
    		return;
    
    	next->prev = new;
    	new->next = next;
    	new->prev = prev;
    	WRITE_ONCE(prev->next, new);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2. 漏洞利用

    问题:参见 nft_expr_init()v5.18.1 版本中漏洞对象是采用 GFP_KERNEL_ACCOUNT 标识分配的,v5.17.12 版本中漏洞对象是采用 GFP_KERNEL 标识分配的。 msg_msg 是采用 GFP_KERNEL_ACCOUNT 标识分配的(单独存放在 kmalloc-cg-xx slab 中)。所以 v5.17.12 版本不能使用 msg_msg 对象来利用。并且UAF写会在 kmalloc-64 的偏移 0x18 处写一个堆地址,写偏移和内容不可控。

    // v5.18.1 
    // https://elixir.bootlin.com/linux/v5.18.1/source/net/netfilter/nf_tables_api.c#L2879
    	expr = kzalloc(expr_info.ops->size, GFP_KERNEL_ACCOUNT);
    // v5.17.12
    // https://elixir.bootlin.com/linux/v5.17.12/source/net/netfilter/nf_tables_api.c#L2800
    	expr = kzalloc(expr_info.ops->size, GFP_KERNEL);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2-1. 泄露堆地址-user_key_payload

    方法:使用 user_key_payload 来泄露(采用 GFP_KERNEL 申请),data字段用户可控。且data数据偏移正好是0x18,所以UAF写会正好往data数据上写上一个堆地址(恰好写入到 user_key_payload->data[0:8]),可以泄露出来。

    struct user_key_payload {
    	struct rcu_head	rcu;		/* RCU destructor */
    	unsigned short	datalen;	/* length of this data */
    	char		data[] __aligned(__alignof__(u64)); /* 变长数据区,用户可控数据 */
    };
    int user_preparse(struct key_preparsed_payload *prep)
    {
    	struct user_key_payload *upayload;
    	size_t datalen = prep->datalen;
    
    	...
    	upayload = kmalloc(sizeof(*upayload) + datalen, GFP_KERNEL);
    	if (!upayload)
    		return -ENOMEM;
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2-2. 泄露内核基址-posix_msg_tree_node

    2-2-1. 构造越界读

    方法:采用mqueue 的posix消息队列模块,该模块和msg_msg一样是IPC进程间通信的消息队列功能。mqueue 的消息采用 posix_msg_tree_node 结构来存储,采用 GFP_KERNEL flag 分配。

    步骤:利用UAF写篡改 posix_msg_tree_node->msg_listmsg_msg 链表),指向某个 kmalloc-64 的偏移 0x18 处(某个 nft_expr 对象的0x18偏移处),这样该位置就被当作一个 msg_msg 结构;然后利用 mq_timedreceive 调用来读取消息,就能读到相邻堆块偏移8开始的16字节(0x18+0x30 = 0x48,从偏移0x18开始,0x30的字节被当做 msg_msg 头,所以可读取的data从相邻堆块的第8字节开始)。因为 copy_to_user() 中有 heap_check,会检查拷贝大小是否超出内存所在slab的大小,所以这里只能读取0x10字节。

    struct posix_msg_tree_node {
        struct rb_node      rb_node;		// rb_node 大小为 0x18
        struct list_head    msg_list;		// 偏移0x18,该字段管理了一个 msg_msg 链表,指向首个 msg_msg
        int         priority;
    };
    
    struct rb_node {						// 大小为 0x18
        unsigned long  __rb_parent_color;
        struct rb_node *rb_right;
        struct rb_node *rb_left;
    } __attribute__((aligned(sizeof(long))));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    分配路径SYSCALL-mq_timedsend -> do_mq_timedsend()

    static int do_mq_timedsend(mqd_t mqdes, const char __user *u_msg_ptr,
    		size_t msg_len, unsigned int msg_prio,
    		struct timespec64 *ts)
    {
    	struct fd f;
    	struct inode *inode;
    	struct ext_wait_queue wait;
    	struct ext_wait_queue *receiver;
    	struct msg_msg *msg_ptr;
    	struct mqueue_inode_info *info;
    	ktime_t expires, *timeout = NULL;
    	struct posix_msg_tree_node *new_leaf = NULL;
    	int ret = 0;
    	DEFINE_WAKE_Q(wake_q);
    	...
    	/* First try to allocate memory, before doing anything with
    	 * existing queues. */
    	msg_ptr = load_msg(u_msg_ptr, msg_len); 			// [1] 调用 load_msg() 创建消息队列, 从用户空间拷贝消息
    	if (IS_ERR(msg_ptr)) {
    		ret = PTR_ERR(msg_ptr);
    		goto out_fput;
    	}
    	msg_ptr->m_ts = msg_len;
    	msg_ptr->m_type = msg_prio;
    
    	/*
    	 * msg_insert really wants us to have a valid, spare node struct so
    	 * it doesn't have to kmalloc a GFP_ATOMIC allocation, but it will
    	 * fall back to that if necessary.
    	 */
    	if (!info->node_cache)
    		new_leaf = kmalloc(sizeof(*new_leaf), GFP_KERNEL); 	// [2] 申请 posix_msg_tree_node 对象,采用 GFP_KERNEL flag 分配
    
    	spin_lock(&info->lock);
    
    	if (!info->node_cache && new_leaf) {
    		/* Save our speculative allocation into the cache */
    		INIT_LIST_HEAD(&new_leaf->msg_list);
    		info->node_cache = new_leaf; 						// [3] 将申请的 posix_msg_tree_node 对象存入 mqueue_inode_info 中
    		new_leaf = NULL;
    	} else {
    		kfree(new_leaf);
    	}
    
    	if (info->attr.mq_curmsgs == info->attr.mq_maxmsg) {
    		...
    	} else {
    		receiver = wq_get_first_waiter(info, RECV);
    		if (receiver) {
    			pipelined_send(&wake_q, info, msg_ptr, receiver);
    		} else {
    			/* adds message to the queue */
    			ret = msg_insert(msg_ptr, info); 				// [4] msg_insert() 将消息插入消息队列 
    			if (ret)
    				goto out_unlock;
    			__do_notify(info);
    		}
    		inode->i_atime = inode->i_mtime = inode->i_ctime =
    				current_time(inode);
    	}
    	...
    }
    
    static int msg_insert(struct msg_msg *msg, struct mqueue_inode_info *info)
    {
    	struct rb_node **p, *parent = NULL;
    	struct posix_msg_tree_node *leaf;
    	bool rightmost = true;
    
    	··· ···
        ··· ···
    insert_msg:
    	info->attr.mq_curmsgs++;
    	info->qsize += msg->m_ts;
    	list_add_tail(&msg->m_list, &leaf->msg_list); 		// [4-1] 将 msg_msg 消息添加到 posix_msg_tree_node->msg_list 链表
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 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

    读取路径SYSCALL-mq_timedreceive -> do_mq_timedreceive()

    static int do_mq_timedreceive(mqd_t mqdes, char __user *u_msg_ptr,
    		size_t msg_len, unsigned int __user *u_msg_prio,
    		struct timespec64 *ts)
    {
    	ssize_t ret;
    	struct msg_msg *msg_ptr;
    	struct fd f;
    	struct inode *inode;
    	struct mqueue_inode_info *info;
    	struct ext_wait_queue wait;
    	ktime_t expires, *timeout = NULL;
    	struct posix_msg_tree_node *new_leaf = NULL;
    	...
    	inode = file_inode(f.file);
    	if (unlikely(f.file->f_op != &mqueue_file_operations)) {
    		ret = -EBADF;
    		goto out_fput;
    	}
    	info = MQUEUE_I(inode); 							// [1] 先根据消息队列的文件描述符获取对应inode, 从 inode 中获取 mqueue_inode_info
    	audit_file(f.file);
    	...
    	if (!info->node_cache)
    		new_leaf = kmalloc(sizeof(*new_leaf), GFP_KERNEL);
    
    	spin_lock(&info->lock);
        
    	if (!info->node_cache && new_leaf) {
    		/* Save our speculative allocation into the cache */
    		INIT_LIST_HEAD(&new_leaf->msg_list);
    		info->node_cache = new_leaf; 					// [2] 获取 posix_msg_tree_node
    	} else {
    		kfree(new_leaf);
    	}
    
    	if (info->attr.mq_curmsgs == 0) {
    		...
    	} else {											// 若消息队列消息数量不为0
    		DEFINE_WAKE_Q(wake_q);
    
    		msg_ptr = msg_get(info);						// [3] 从消息队列获取一个消息
    
    		...
    	}
    	if (ret == 0) {
    		ret = msg_ptr->m_ts;
    
    		if ((u_msg_prio && put_user(msg_ptr->m_type, u_msg_prio)) ||
    			store_msg(u_msg_ptr, msg_ptr, msg_ptr->m_ts)) {	// [4] 将消息发给用户, copy_to_user()
    			ret = -EFAULT;
    		}
    		free_msg(msg_ptr);									// [5] 释放消息
    	}
    	...
    }
    
    static inline struct msg_msg *msg_get(struct mqueue_inode_info *info)
    {
    	...
    	} else {
    		msg = list_first_entry(&leaf->msg_list,		// [3-1] 获取msg_list中第一个消息
    				       struct msg_msg, m_list);
    		list_del(&msg->m_list);						// [3-2] 从消息队列中删除
    		if (list_empty(&leaf->msg_list)) {
    			msg_tree_erase(leaf, info);
    		}
    	}
    	info->attr.mq_curmsgs--;						// [3-3] 消息队列数减1
    	info->qsize -= msg->m_ts;
    	return msg;
    }
    
    void free_msg(struct msg_msg *msg)
    {
    	struct msg_msgseg *seg;
    
    	security_msg_msg_free(msg);
    
    	seg = msg->next;
    	kfree(msg);
    	while (seg != NULL) {
    		struct msg_msgseg *tmp = seg->next;
    
    		cond_resched();
    		kfree(seg);
    		seg = tmp;
    	}
    }
    void security_msg_msg_free(struct msg_msg *msg)
    {
    	call_void_hook(msg_msg_free_security, msg);
    	kfree(msg->security);							// [5-1] 伪造 msg_msg 时, 需保证 msg->security 为0 
    	msg->security = NULL;
    }
    
    • 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
    2-2-2. 泄露目标对象

    泄露对象:需找到一个目标对象,放在 posix_msg_tree_node 对象的后面。

    • (1)目标对象需位于 kmalloc-64,且偏移 0x8~0x18 处包含内核地址指针;
    • (2)由于 mq_timedreceive 调用会触发执行 msg_free 来释放 msg_msg->security (恰好与目标对象的前8字节重合),所以目标对象的前8字节需为0

    最终还是选择 user_key_payload,前8字节 user_key_payload->rcu->next 正常为0,且第2个8字节为 user_key_payload->rcu->func 内核函数指针,指向 user_free_payload_rcu() 函数。

    struct user_key_payload {
    	struct rcu_head	rcu;		/* RCU destructor */
    	unsigned short	datalen;	/* length of this data */
    	char		data[] __aligned(__alignof__(u64)); // 变长数据区,用户可控
    };
    struct callback_head {
        struct callback_head *next;
        void (*func)(struct callback_head *head);		// 偏移 0x8 可泄露内核基址
    } __attribute__((aligned(sizeof(void *))));
    #define rcu_head callback_head
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    步骤

    • (1)分配两个漏洞对象 nft_expr 称为 vul1 / vul2

    • (2)触发 do_mq_timedsend() 分配一个 posix_msg_tree_node 来占据 vul1

    • (3)触发UAF写漏洞(创建vul2 并链接到 vul1vul1->binding->next = vul2->binding),通过 vul1 篡改 posix_msg_tree_node

    • (4)通过 keyctl 函数在 vul2 附近布置 user_key_payload 对象。

                    vul 1 (posix_msg_tree_node)                                    vul 2
               -------------------------------      -------------------------------------------------------------------------
      0x0     |    rb color   |    rb_right   |     |           * ops                |                * set                  |
               --------------- ---------------      --------------------------------- ---------------------------------------
      0x10    |    rb_left    |  (vul 2+0x18) |     |          reg & invert          |  bingding.list.next (msg_msg->m_list) |
               --------------- ---------------      --------------------------------- ---------------------------------------
      0x20    |      ....     |      ....     |     |    X.X.prev (msg_msg->m_list)  |       chain (msg_msg->m_type)         |
               --------------- ---------------      --------------------------------- ---------------------------------------
      0x30    |      ....     |      ....     |     |     flags (msg_msg->m_ts)      |           (msg_msg->next)             |
               --------------- ---------------      --------------------------------- ---------------------------------------
                            		                                          user_key_payload
                   								  -------------------------------------------------------------------------
      0x40    								      |   rcu->next (msg_msg->security)|    rcu->func ptr (msg_msg->data[0])   |
               								      --------------------------------- ---------------------------------------
      0x50    								      |             data_len           |                data[0]                |
                								      --------------------------------- ---------------------------------------
      0x60   									      |              data[1]           |                data[2]                |
               								      --------------------------------- ---------------------------------------
      0x70    								      |              data[3]           |                data[4]                |
              								      --------------------------------- ---------------------------------------
      
      vul 1 overlap info: struct nft_expr == struct posix_msg_tree_node
      vul 2: struct nft_expr (nft_expr+0x18 开始被当做 msg_msg 结构)
      next to vul 2: struct user_key_payload (前8字节被当做 msg_msg->security)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
    • 此时, posix_msg_tree_node->msg_list->next = vul2 + 0x18 ,所以从 vul2 + 0x18 开始被当做 msg_msg;但是这个 msg_msgcopy_to_user 只能泄露0x10字节,且 msg_msg->security 必须为NULL(vul2相邻对象的前8字节),所以能泄露 vul2相邻堆块的 0x8 ~ 0x18 字节。过去有人用 percpu_ref_data 来泄露内核基址(参见 CVE-2022-34918利用,来自 io_uring),但不满足前8字节为NULL,所以只能再用 user_key_payload 来泄露。

    2-3. 覆写 modprobe_path 提权

    步骤:构造 msg_msg 的Unlink来篡改 modprobe_path 提权。

    • (1)利用UAF篡改 posix_msg_tree_node->msg_list 指向某个 vul2 + 0x18 处;

                            vul 1                                       vul 2
               -------------------------------             -------------------------------
      0x0     |    rb color   |    rb_right   |           |   rcu->next   | rcu->func ptr |
               --------------- ---------------             --------------- ---------------
      0x10    |    rb_left    |  (vul 2+0x18) |           |   data_len    |     data[0]   |
               --------------- ---------------             --------------- ---------------
      0x20    |      ....     |      ....     |           |     data[1]   |     data[2]   |
               --------------- ---------------             --------------- ---------------
      0x30    |      ....     |      ....     |           |     data[3]   |     data[4]   |
               --------------- ---------------             --------------- ---------------
      
      vul 1 overlap info: struct nft_expr == struct posix_msg_tree_node
      vul 2 overlap info: struct nft_expr == struct user_key_payload == fake msg_msg
      
      data[0] = m_list->next   /   data[1] = m_list->prev   /   data[2] = m_type   /   data[3] = m_ts
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    • (2)释放该 nft_expr - vul2 ,喷射 usr_key_payload 伪造一个假的 msg_msgmsg_msg->m_list.next = &modprobe_path-7msg_msg->mlist.prev = 0xffff????2f706d74

      • &modprobe_path-7 表示修改 modprobe_path 的第 2~9 字节;
      • 0xffff????2f706d74 是 physmap 中可写的堆地址(进行 unlink 时必须为可写地址),前面已经泄露过该地址。可以将 modprobe_path 字符串从/sbin/modprobe改为 /tmp/????\xff\xffprobe
    • unlink操作:执行 mq_timedreceive 调用,触发执行 msg_get() -> list_del() -> __list_del_entry() -> __list_del()msg_msg 对象进行unlink。

      static inline void list_del(struct list_head *entry)
      {
      	__list_del_entry(entry);
      	entry->next = LIST_POISON1;
      	entry->prev = LIST_POISON2;
      }
      static inline void __list_del_entry(struct list_head *entry)
      {
      	if (!__list_del_entry_valid(entry))
      		return;
      
      	__list_del(entry->prev, entry->next);
      }
      static inline void __list_del(struct list_head * prev, struct list_head * next)
      {
      	next->prev = prev;				// <---------- *(entry->next + 8) = entry->prev    unlink操作
      	WRITE_ONCE(prev->next, next);	// *(entry->prev) = entry->next
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18

    2-4. 测试结果

    问题:每次start.sh 启动后执行 ./exploit 总是报错 -sh: 2: ./exploit: Exec format error,只能重新传 ./exploit 进去才能执行。scp -P 10021 ./exploit.c ./exploit hi@localhost:/home/hi

    请添加图片描述

    3. 补充

    3-1. 防护机制

    • 对特定对象的cache隔离,防止UAF对象替换:grsecurity’s autoslab / Google’s experimental mitigations
    • CFI 机制防止执行 ROP gadget,目前在x64上没有有效的机制;
    • 设置 panic_on_oops=1 来避免 unlink 利用;
    • 设置 modprobe_path 为只读—CONFIG_STATIC_USERMODHELPER
    • 关闭 unprivileged namespaces
    • 关闭 userland FUSE server 支持。

    3-2. 常用命令

    exp编译

    
    
    • 1

    libmnl / libnftl 安装

    $ sudo apt-get install libcap2-bin bzip2 make pkg-config        # 安装 setcap/bzip2/make/pkg-config
    $ tar   -jxvf    xx.tar.bz2
    $ ./configure --prefix=/usr && make     # libmnl / libnftl
    $ sudo make install
    
    • 1
    • 2
    • 3
    • 4

    liburing 安装(本次exp不需要安装liburing)

    # 安装 liburing   生成 liburing.a / liburing.so.2.2
    $ apt-get install zip g++
    $ apt-get update
    $ apt-get install gcc-10 g++-10		# 在QEMU中编译liburing总是出错, 可能需要更新 gcc
    $ git clone https://github.com/axboe/liburing
    $ unzip ./liburing-master.zip
    $ cd ./liburing-master
    $ make
    $ sudo make install
    # 另一种方法
    wget  https://github.com/axboe/liburing/archive/liburing-0.2.zip
    unzip liburing-0.2.zip
    cd liburing-liburing-0.2/
    ./configure --libdir=/usr/lib64 
    make CFLAGS=-std=gnu99 && make install
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    常用命令

    # ssh连接与测试
    $ ssh -p 10021 hi@localhost             # password: lol
    $ ./exploit
    
    # 编译exp 				注意libmnl不支持静态编译,加 -static 就会报错; 加 -lrt 表示实时库
    $ gcc ./exploit.c  -lmnl -lnftnl -lrt  -o exploit
    
    # scp 传文件
    $ scp -P 10021 ./exploit hi@localhost:/home/hi      # 传文件
    $ scp -P 10021 hi@localhost:/home/hi/trace.txt ./   # 下载文件
    $ scp -P 10021 ./exploit.c ./get_root.c ./exploit ./get_root  hi@localhost:/home/hi
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    参考

    Linux Kernel Exploit (CVE-2022-32250) with mqueue

    exploit

    Settlers of Netlink - Exploiting a limited kernel UAF on Ubuntu 22.04 — 学习使用 CodeQL搜寻目标对象(指定大小,指定偏移处为指针)。介绍了另一种利用方法,基于nft_dynset漏洞对象,比本文更复杂,要触发4次UAF(用到 cgroup_fs_context->release_agent 来构造任意释放,恰好和 nft_dynset->bindings->prev 重叠)。

    [漏洞分析] CVE-2022-32250 netfilter UAF内核提权

    CVE-2022-32250-email

    nftables-details — 学习nftables的细节

    团队介绍:该团队成功利用了 CVE-2022-0185 / CVE-2022-0995 / CVE-2022-32250

  • 相关阅读:
    flink技术总结待续
    网络程序通信的流程
    ExtJS - ExtJS最佳实践
    在 Vue & react 中,哪些地方用到闭包?
    从零备战蓝桥杯——动态规划(股票问题)
    【Superset3.0】更全面superset相关--配置邮件报告发送: 附件乱码以及导出文件(截屏图片)中文乱码问题
    数据库应用:CentOS 7离线安装PostgreSQL
    makefile目标规则
    linux 下 C++ 与三菱PLC 通过MC Qna3E 二进制 协议进行交互
    USB转2路RS422串口
  • 原文地址:https://blog.csdn.net/panhewu9919/article/details/127679019