前言
本文将介绍如何绕过KASLR
以及如何提权利用。
KASLR绕过
可以利用byteorder
操作加上netlink
组订阅可以泄露rule
中的handle
字段。该方法应该是可以用来泄露kernel
基地址的,但是作者还提出另一种方法进行泄露。应该是为了提权利用做铺垫。
由于发现已经泄露的模块的基地址,因此可以利用模块地址伪造表达式。
作者找到了range
表达式,用于伪造其余表达式。总大小为0x23
。并且表达式是八字节对齐的,因此该结构体会占用0x28
字节。
struct nft_range_expr { struct nft_data data_from; struct nft_data data_to; u8 sreg; u8 len; enum nft_range_ops op:8; };
具体的布局如下
可以看到data_from
与data_to
都是从用户态中传递过去的数据,因此我们可以在这些区域内伪造表达式,这有点像在CTF
中,我们泄露了堆块基址后,随意伪造堆块。
由于我们有0x28
字节的空间,但是实际能够操作的空间是data_from
与data_to
两段,即0x20
的空间大小。因此我们需要挑选小于0x20
的规则表达式进行伪造,并且能够执行泄露功能的。
这里作者选用了byteorder
表达式,可以看到该表达式在对齐后只占用八字节。
struct nft_byteorder { u8 sreg; u8 dreg; enum nft_byteorder_ops op:8; u8 len; u8 size; };
构造后,可以发现还有八字节的data_to
没有用,但是并不能直接丢弃不适用,因为在调用完byteorder
操作后还需要继续执行其他规则表达式。
但是其他表达式都是需要大于0x8
的,毕竟一个操作指针就占用八字节了,此时作者选用了meta
表达式。可以看到meta
表达式也只用到了三个字节,刚好对应range
表达式的sreg
、len
以及op
。
struct nft_meta { enum nft_meta_keys key:8; u8 len; union { u8 dreg; u8 sreg; }; };
meta
表达式的操作如下,在meta
表达式中meta->key
执行具体的操作,如[1],但是若该值是非法值则会执行[2],可以看到该meta
表达式仅会抛出异常,但是不会终止运行,这就说明即使meta->key
是非法值也不会影响后续规则表达式的正常执行。
File: linux-5.19\net\netfilter\nft_meta.c 418: void nft_meta_set_eval(const struct nft_expr *expr, 419: struct nft_regs *regs, 420: const struct nft_pktinfo *pkt) 421: { 422: const struct nft_meta *meta = nft_expr_priv(expr); 423: struct sk_buff *skb = pkt->skb; 424: u32 *sreg = ®s->data[meta->sreg]; 425: u32 value = *sreg; 426: u8 value8; 427: 428: switch (meta->key) { ----> [1] 429: case NFT_META_MARK: 430: skb->mark = value; 431: break; 432: case NFT_META_PRIORITY: 433: skb->priority = value; 434: break; 435: case NFT_META_PKTTYPE: 436: value8 = nft_reg_load8(sreg); 437: 438: if (skb->pkt_type != value8 && 439: skb_pkt_type_ok(value8) && 440: skb_pkt_type_ok(skb->pkt_type)) 441: skb->pkt_type = value8; 442: break; 443: case NFT_META_NFTRACE: 444: value8 = nft_reg_load8(sreg); 445: 446: skb->nf_trace = !!value8; 447: break; 448: #ifdef CONFIG_NETWORK_SECMARK 449: case NFT_META_SECMARK: 450: skb->secmark = value; 451: break; 452: #endif 453: default: 454: WARN_ON(1); ---->[2] 455: } 456: }
因此第二个伪造的表达式也找到了,就是meta
表达式。由于我们直接伪造了规则表达式,因此不会进行寄存器参数的校验,只要选择内核地址选择泄露即可。
这里简单说一下伪造的规则头,此时的len
需要设置为0x20以及islast
需要设置为0。
最后泄露的效果如下,即使内核已经抛出了异常,但是不会影响后续表达式的正常执行。
【---- 帮助网安学习,以下所有学习资料免费领!领取资料加 we~@x:dctintin,备注 “开源中国” 获取!】
① 网安学习成长路径思维导图
② 60 + 网安经典常用工具包
③ 100+SRC 漏洞分析报告
④ 150 + 网安攻防实战技术电子书
⑤ 最权威 CISSP 认证考试指南 + 题库
⑥ 超 1800 页 CTF 实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP 客户端安全检测指南(安卓 + IOS)
提权利用
既然可以随意伪造表达式,因此我们可以伪造payload
表达式。
struct nft_payload { enum nft_payload_bases base:8; u8 offset; u8 len; u8 dreg; };
在regs
下方存在着nft_do_chain
函数返回地址,因此可以直接通过payload
表达式将提权payload
注入进来。
由于我们可以伪造payload
表达式,因此注入的payload
长度不受限,因此采用
-
commit_creds(prepare_kernel_cred(0))
,构造root
凭证 -
接着利用
find_task_by_vpid
、init_nsproxy
以及switch_task_namespaces
切换命名空间。 -
最后利用蹦床
swapgs_restore_regs_and_retrun_to_usermode
返回到用户空间完成提权利用。
完整exp
:https://github.com/h0pe-ay/Vulnerability-Reproduction/tree/master/CVE-2023-35001(nftables)