标题 | 简介 | 类型 | 公开时间 | ||||||||||
|
|||||||||||||
|
|||||||||||||
详情 | |||||||||||||
[SAFE-ID: JIWO-2025-2631] 作者: 浩丶轩 发表于: [2020-04-10]
本文共 [642] 位读者顶过
关于CVE-2017-16995 ebpf 符号扩展漏洞的学习笔记。
漏洞分析 [出自:jiwo.org] 关于这个漏洞网上已经有很多的文章分析了,这里不做太多描述,只记录一些比较重要的点。 首先是ebpf,上一张图: ![]()
ebpf首先需要ring3传入一段指令(传到JIT),它会在BPF_PROG_RUN里做包过滤, 内核会申请一块共享内存(MAP),内核的数据经过过滤之后放到MAP里面,然后ring3就可以读写MAP来获取内核数据。 这个漏洞简单来说就是符号扩展没有检查好,像前面说的,ebpf分成verifier和BPF_PROG_RUN 两个部分。 传入的指令其实就是原本x64上指令的一个映射,它会检查指令的CFG,是不是有非法内存访问之类的(如果可以的话就直接是内核代码注入了,可以任意执行代码),效率上的考虑,会忽略掉一些分支的检查,像下面这样,r9的值固定是0xffffffff,那么就不会跳转到[4]的部分,所以就不用检查它了,节省时间。
首先看上面第一条指令ALU_MOV_K(9,0xffffffff),它等效于r9 = 0xffffffff,对应的代码在: https://elixir.bootlin.com/linux/v4.4.110/source/kernel/bpf/verifier.c#L1782
调用check_alu_op函数,最后调用regs[insn->dst_reg].imm = insn->imm;,这里的立即数是用signed int保存的。
然后第二条指令JMP_JNE_K(9,0xffffffff,2),其检查在check_cond_jmp_op函数里,这时候用的imm依然是signed int类型,然后后续检查的时候发现前面r9和JMP_JNE_K的imm一样,于是就不去检查[4]开始的指令了。
然后到了运行的之后,对应__bpf_prog_run 函数: https://elixir.bootlin.com/linux/v4.4.110/source/kernel/bpf/core.c#L195 ALU_MOV_K:DST=(u32)IMM这个时候DST=0xffffffff JMP_JNE_K:比较DST和IMM,此时IMM是signed int类型,DST 是 uint64_t 类型, IMM会做位扩展,原来的0xffffffff也就是-1变成0xffffffff ffffffff,0xffffffff != 0xffffffff ffffffff,于是就会跳到前面指令的LD_IMM_DW(9,1,3), // [4] r9=mapfd开始执行,verifrier的时候并没有这一段指令做检查,这时候就可以在内核做任意代码执行了。 我们可以写一段代码验证一下:
输出的结果是vuln,接下来是如何利用。
漏洞利用的话,前面的分析我们知道可以在内核任意代码执行,手写ebpf的指令(其实就和我们手写汇编一样),基本利用思路如下:
泄露出task_struct的地址
借助task_struct地址泄露出cred地址
直接内存写改uid,gid,然后/bin/sh getshell
复现的环境我用的内核是4.4.110版本, 附件中有我的config文件,主要是加上CONFIG_BPF=y 和CONFIG_BPF_SYSCALL=y
这里使用的bpf指令如下,参照panda师傅的分析:
首先是r6=map[0],r7=map[1],r8=map[2] (map 是前面提到的共享内存)
然后是三个判断:
map[0]==0时,根据 map[1] 的值来读内存;
map[0]==1时,获取rbp的值==>addr & ~(0x4000 - 1); 可以读取到 task_struct 的地址;
map[0] ==2时,*map[1]= map[2]([r7]=r8)。
|