溢出漏洞提权/Netfilter漏洞提权利用(CVE-2023-35001)_手把手教渗透自学
前言
是一个用于Linux操作系统的网络数据包过滤框架,它提供了一种灵活的方式来管理网络数据包的流动。允许系统管理员和开发人员控制数据包在Linux内核中的处理方式,以实现网络安全、网络地址转换( ,NAT)、数据包过滤等功能。
漏洞成因
在中存在这函数,该函数的作用是将寄存器中的数据以主机序或网络序存储。具体代码如下,若采用的操作是则是将数据从主机序转化为网络序,而则是从网络序转换为主机序。具体转换多少个字节则是用priv->size指定的,在该操作下可以转换二、四、八字节。该漏洞也是由于在对两字节数据进行大小端序转存时出现了错误所导致的。
可以看到代码【1】中使用了联合体存储了源地址和目的地址,联合体的变量分别是u32与u16分别代表的是四字节与两字节的空间大小。然后在代码【2】与【3】处源地址是直接取出u16的变量存储到目的地址的u16变量中。
乍一看似乎很符合常理,因为在处理双字节的时候,联合体中的变量就以u16存储,若处理四字节就转化为u32存储,但是这里存在个问题,在C语言中,联合体的存储空间是以最大空间为标准,换句话说无论联合体取出的变量是u16还是u32,联合体的大小都是占用四个字节的,而不会出现双字节的情况,因此在对s与d两个联合体进行遍历时,会以四字节为单位找到下一个位置。但是在计算长度时是以双字节进行计算的,因此就会导致拷贝时发生溢出。
File: linux-5.19\net\netfilter\nft_byteorder.c26: void nft_byteorder_eval(const struct nft_expr *expr,27: struct nft_regs *regs,28: const struct nft_pktinfo *pkt)29: { ...33: 【1】union { u32 u32; u16 u16; } *s, *d; //使用联合体存储源地址与目的地址 ...39: switch (priv->size) { ...72: case 2:73: switch (priv->op) {74: case NFT_BYTEORDER_NTOH:75: for (i = 0; i < priv->len / 2; i++)76: 【2】d[i].u16 = ntohs((__force __be16)s[i].u16);//将源地址的数据拷贝到目的地址的低16位中77: break;78: case NFT_BYTEORDER_HTON:79: for (i = 0; i < priv->len / 2; i++)80: 【3】d[i].u16 = (__force __u16)htons(s[i].u16);81: break;82: }83: break;84: }85: }
举个例子,我们自定义一个联合体数组dest,分别向下标0、1以及2进行赋值。
union {short a;long b;} dst[10];int main(){ dst[0].a = 0x1122; dst[1].a = 0x3344; dst[2].a = 0x5566; return 0;}
按照设想的情况,在使用双字节变量进行遍历的时候会以双字节为单位进行遍历,但是实际的情况如下图。可以发现即使每次赋值都是对双字节的变量进行赋值,但是再遍历的时候还是按照联合体中最大的存储空间(四字节)进行遍历的。
因此漏洞的成因如下,因此在使用函数转换双字节的大小端序时溢出。
模块地址泄露
在函数内部,溢出的地址是在寄存器下方。因此可以通过控制寄存器的下标值选择需要泄露的地址。
在此需要观察通过函数可以溢出的范围,priv->len是可以人为控制的,只要满足reg * 4 + priv->len len能设置最大的值,(0x40 / 2) * 4 = 0x80,因此(0xaf8 ~ 0xaf8 + 0x80)范围内都是可以访问到的。但是现在存在一个问题,虽然我们可以越界访问,但是每次只能获取四字节中的低两个字节。
...75: for (i = 0; i < priv->len / 2; i++)76: 【2】d[i].u16 = ntohs((__force __be16)s[i].u16);//将源地址的数据拷贝到目的地址的低16位中...
将下列值传参给函数
/* dst:18 src:8 priv->op:NFT_BYTEORDER_HTON priv->len:24 priv->size:2*/rule_add_byteorder(r, 18, 8, NFT_BYTEORDER_HTON, 24, 2);
泄露的值如下,可以发现高两个字节的值是无法泄露的,因为在中,每次只拷贝了u16的变量。因此每次泄露只能获取低两字节的值。因此需要寻找其他方法进行地址的泄露。
nfo函数用于跟踪数据包,并且会将rule->的值放进数据包中回传给用户。
想要正常执行nfo函数需要绕过条件
因此想要通过nfo函数获取数据的第一步是伪造rule。
帮助网安学习,全套资料S信领取:
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC漏洞分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)
在regs变量的下方存在变量
结构体的构成如下,由chain、rule、组成,并且该结构体变量在regs下方,并且通过操作可以访问到结构体,那么利用操作篡改rule。
struct nft_jumpstack { const struct nft_chain *chain; const struct nft_rule_dp *rule; const struct nft_rule_dp *last_rule;};
接下来看一下结构体,可以发现是调用nfo函数的条件,是泄露的值。
struct nft_rule_dp { u64 is_last:1, dlen:12, handle:42; /* for tracing */ unsigned char data[] __attribute__((aligned(__alignof__(struct nft_expr))));};
在进入nfo函数内部前需要经历规则与表达式的遍历。
File: linux-5.19\net\netfilter\nf_tables_core.c255: for (; rule < last_rule; rule = nft_rule_next(rule)) { //遍历rule256: nft_rule_dp_for_each_expr(expr, last, rule) { //遍历expr257: if (expr->ops == &nft_cmp_fast_ops)258: nft_cmp_fast_eval(expr, ®s);259: else if (expr->ops == &nft_cmp16_fast_ops)260: nft_cmp16_fast_eval(expr, ®s);261: else if (expr->ops == &nft_bitwise_fast_ops)262: nft_bitwise_fast_eval(expr, ®s);263: else if (expr->ops != &nft_payload_fast_ops ||264: !nft_payload_fast_eval(expr, ®s, pkt))265: expr_call_ops_eval(expr, ®s, pkt); //执行expr->ops266: 267: if (regs.verdict.code != NFT_CONTINUE)268: break;269: }270: 271: switch (regs.verdict.code) {272: case NFT_BREAK:273: regs.verdict.code = NFT_CONTINUE;274: nft_trace_copy_nftrace(&info);275: continue;276: case NFT_CONTINUE:277: nft_trace_packet(&info, chain, rule,278: NFT_TRACETYPE_RULE); //跟踪数据包279: continue;280: }281: break;282:
遍历规则的宏定义如下,若是rule->dlen没有进行改写,那么会根据rule->dlen找到下一个rule,但是当前的rule是伪造的,因此会导致在取出expr会报错。倘若将rule->dlen修改为0,则下个rule的位置就是当前rule + 8。
由于不定长数组 char data[],在操作中的值为0,因此(*rule)的值为8。此时将改写成rule + 8就可以直接跳出循环。
#define nft_rule_next(rule) (void *)rule + sizeof(*rule) + rule->dlen
在完场上述流程后,就可以顺利进入函数内部,函数也比较简单,实际是调用了函数
File: linux-5.19\net\netfilter\nf_tables_core.c37: static inline void nft_trace_packet(struct nft_traceinfo *info,38: const struct nft_chain *chain,39: const struct nft_rule_dp *rule,40: enum nft_trace_types type)41: {42: if (static_branch_unlikely(&nft_trace_enabled)) {43: const struct nft_pktinfo *pkt = info->pkt;44: 45: info->nf_trace = pkt->skb->nf_trace;46: info->rule = rule;47: __nft_trace_packet(info, chain, type);48: }49: }
可以发现想要进入函数需要满足info->trace或info->trace不为空。
File: linux-5.19\net\netfilter\nf_tables_core.c24: static noinline void __nft_trace_packet(struct nft_traceinfo *info,25: const struct nft_chain *chain,26: enum nft_trace_types type)27: {28: if (!info->trace || !info->nf_trace)29: return;30: 31: info->chain = chain;32: info->type = type;33: 34: nft_trace_notify(info);35: }
使用meta表达式可以设置skb->,将skb->设置为非空就可以进入到函数。
File: linux-5.19\net\netfilter\nft_meta.c...443: case NFT_META_NFTRACE:444: value8 = nft_reg_load8(sreg);445: 446: skb->nf_trace = !!value8;447: break;...
在函数内部,还会判断是否订阅。没订阅则无法继续执行。
File: linux-5.19\net\netfilter\nf_tables_trace.c ...176: if (!nfnetlink_has_listeners(nft_net(pkt), NFNLGRP_NFTRACE))177: return; ...
在库中使用t函数进行的组订阅,由于在使用宏编译时会提示找不到该值,因此这里使用实际值代替了。
static int group = 9;if (mnl_socket_setsockopt(nleak, NETLINK_ADD_MEMBERSHIP, &group, sizeof(int)) < 0) { perror("mnl_socket_setsockopt"); exit(EXIT_FAILURE);}
接下来就需要具体如何伪造rule,通过操作可以首先可以将原先的chain、rule以及的地址泄露,但是只能泄露四字节。
由于我们需要找到符合上述条件的rule,并且我们只有rule的最低两个字节,因此搜索范围不大,因此需要在泄露的附近寻找一个合适的模块地址。在存储泄露的rule之前存储利用以及操作,我们选择其中一个进行泄露即可。
伪造的方式也比较简单,由于与dlen都需要设置为0,因此我们只需要找到两个字节为0的值,作为伪造的rule即可,伪造的rule如下。
修改后的结果如下
由于实际是占用42比特,但是有3个比特被设置为0了,因此实际泄露的值只有39比特,但是由于模块地址的高4个字节都是固定的,因此不影响模块地址的泄露。通过从数据包中提取数据得到的值为后,简单移位操作就可以还原。
module = ((leak << 13) >> 16);
最后泄露模块基地址成功。
总结
总结一下模块基地址的泄露流程
1. 构造基础链
设置表达式
通过meta设置为
2. 泄露链
表达式触发漏洞,第一次读chain、rule以及,第二次改写为chain,fake rule以及fake
表达式泄露chain、rule、
3. 订阅组,接收数据包
4. 后续接着分享如何绕过kaslr以及最终提权的利用。
原版exp使用go语言写的,我使用c语言重写了一版。
完整exp:.com/h0pe-ay/Vul…(c语言)
网络安全学习路线图(思维导图)
网络安全学习路线图可以是一个有助于你规划学习进程的工具。你可以在思维导图上列出不同的主题和技能,然后按照逻辑顺序逐步学习和掌握它们。这可以帮助你更清晰地了解自己的学习进展和下一步计划。
1. 网络安全视频资料
2. 网络安全笔记/面试题
3. 网安电子书PDF资料
~