内容简介:为了避免重复,这里只根据微软的文章简述Speculative Store Bypass的原理。考虑下面的代码。
前言
下图是bleepingcomputer上总结的一些CPU漏洞,已经有许多博客和论文分析了这些漏洞。值得注意的是,ebpf的Spectre变种1(CVE-2017-5753)的补丁并没有考虑全面,导致了CVE-2019-7308。对于Spectre变种4(CVE-2018-3639)Jann Horn也给出了一个需要patch内核的利用ebpf的POC。这篇文章主要分析这两个漏洞ebpf的利用方式。在一些漏洞分析的文章中对ebpf有所提及,所以这里也不再赘述,不清楚的读者在阅读接下来的部分之前最好自行查看参考资料。
CVE-2018-3639利用ebpf的攻击方式
为了避免重复,这里只根据微软的文章简述Speculative Store Bypass的原理。考虑下面的代码。
01: 88040F mov [rdi+rcx],al 02: 4C0FB6040E movzx r8,byte [rsi+rcx] 03: 49C1E00C shl r8,byte 0xc 04: 428B0402 mov eax,[rdx+r8]
如果CPU推测加载指令与之前的存储指令无关,那么可以在存储指令执行之前执行加载指令。第1行的MOV指令在特殊情况下可能需要额外的时间来执行(如果计算RDI+RCX的地址表达式正在等待先前的指令执行)。在这种情况下,CPU可能会推测MOVZX指令不依赖于MOV指令,并且可能在MOV指令执行之前执行MOVZX指令。这可能会导致位于RSI+RCX的内存中的旧数据被加载到R8中,从而导致第四行代码使用了错误的数据。
project zero给出的利用CVE-2018-3639攻击ebpf的EXP需要patch linux内核源码,先来看看改动的内容。在ebpf系统调用中增加了处理0x13370001的情况,处理这个新增的cmd用的是 map_time_flush_loc
函数。
该函数读取 ptr + attr->flags
指向的内容,使用rdtscp计算读取的时间。
EXP的 array_time_flush_loc
函数中用到这部分逻辑计算读取mapfd中偏移 idx+off
处内容需要的时间。
还有就是在 bpf_base_func_proto
函数中加了一个helper函数 bpf_clflush_mfence_proto
,它的主要作用是清空缓存。
EXP中使用 BPF_CALL
可以调用到这部分逻辑。
EXP中我们先看ebpf指令部分,旁边我已经加上了伪代码注释。首先是一些设置。
然后拿到需要泄露的内存的地址。之前 leak_bit
函数调用了 array_set_dw(input_map, 0, addr)
对此进行了设置。
设置r9为 leak_map
偏移2048处。
设置r1=r7,然后清一下缓存。
设置r3=fp-216。
接下来就是最关键的部分了。因为 *(u64 *)(r8)=r3
执行起来比较慢,所以提前执行了 r1=*(u64 *)(r6)
取到了需要泄露的内存的地址,然后 r2=*(u8 *)(r1)
取到了其中的值。
后面的代码需要结合 leak_bit
函数理解。对于sockfds[16]来说根据最开始的 BPF_ST_MEM(BPF_B, BPF_REG_FP, -216, dummy_ff ? 0x00 : 0xff)
指令可以知道sockfds[bit+8]的 *(u8 *)(fp-216)=0x00
,sockfds[bit+0]的 *(u8 *)(fp-216)=0xff
。对于sockfds[bit+8]和sockfds[bit+0],它们的selected_bit=bit&7是相同的,如果取到的值是1,r2=0x1000,那么最后 r0=*(u8 *)(r9)= r0=*(u8 *)( leak_map偏移2048+0x1000)
;如果取到的值是0,r2=0,那么最后 r0=*(u8 *)(r9)= r0=*(u8 *)( leak_map偏移2048)
。而sockfds[bit+8]没有推测执行的话 r0=*(u8 *)( leak_map偏移2048)
;sockfds[bit+0]没有推测执行的话 r0=*(u8 *)( leak_map偏移2048+0x1000)
。所以如果sockfds[bit+8]读取 leak_map
偏移2048+0x1000需要的时间短,说明取到的值应该是1;如果sockfds[bit+0]读取 leak_map
偏移2048需要的时间短,说明取到的值应该是0。
后面再在 leak_byte
函数中进一步调用 leak_bit
函数一个一个bit拼起来就得到了泄露的byte。
用该EXP泄露指定地址的数据如下。
$ sudo grep core_pattern /proc/kallsyms ffffffff9b2954e0 D core_pattern $ gcc -o bpf_store_skipper_assisted bpf_store_skipper_assisted.c $ time ./bpf_store_skipper_assisted ffffffff9b2954e0 5 BPF PROG LOADED SUCCESSFULLY BPF PROG LOADED SUCCESSFULLY BPF PROG LOADED SUCCESSFULLY BPF PROG LOADED SUCCESSFULLY BPF PROG LOADED SUCCESSFULLY BPF PROG LOADED SUCCESSFULLY BPF PROG LOADED SUCCESSFULLY BPF PROG LOADED SUCCESSFULLY BPF PROG LOADED SUCCESSFULLY BPF PROG LOADED SUCCESSFULLY BPF PROG LOADED SUCCESSFULLY BPF PROG LOADED SUCCESSFULLY BPF PROG LOADED SUCCESSFULLY BPF PROG LOADED SUCCESSFULLY BPF PROG LOADED SUCCESSFULLY BPF PROG LOADED SUCCESSFULLY 4 vs 96 1 vs 99 100 vs 0 100 vs 0 100 vs 0 2 vs 98 0 vs 100 100 vs 0 ffffffff9b2954e0: 0x63 ('c') 2 vs 98 1 vs 99 1 vs 99 1 vs 99 100 vs 0 2 vs 98 0 vs 100 100 vs 0 ffffffff9b2954e1: 0x6f ('o') 100 vs 0 3 vs 97 100 vs 0 100 vs 0 1 vs 99 2 vs 98 0 vs 100 100 vs 0 ffffffff9b2954e2: 0x72 ('r') 2 vs 98 100 vs 0 0 vs 100 100 vs 0 100 vs 0 0 vs 100 0 vs 100 100 vs 0 ffffffff9b2954e3: 0x65 ('e') 100 vs 0 100 vs 0 100 vs 0 100 vs 0 100 vs 0 100 vs 0 100 vs 0 100 vs 0 ffffffff9b2954e4: 0x00 ('') real 0m31.591s user 0m2.547s sys 0m27.429s
ebpf中CVE-2017-5753的补丁不完善导致的CVE-2019-7308
我们仍然只根据project zero的文章简述Bounds Check Bypass的原理。考虑下面的代码。
CPU可能会不检查if语句中的条件就推测其为真,先将下一步指令所需要的内存加载到缓存之中,假如发现推测错误再回滚指令。但是这时CPU缓存并没有清空,通过侧信道攻击可以取到 arr1->data[untrusted_offset_from_caller]
中的值。
在ebpf系统中为了修补这个漏洞,在每个需要取数组元素的操作中都将数组的索引和掩码相与避免越界访问。
如果map不是由具有 CAP_SYS_ADMIN
权限的用户创建的,还会为JIT代码添加同样的操作。
补丁对数组中的指针计算并没有作用,来看看EXP是如何利用这一点的。EXP中有两个重要的结构体, mem_leaker_prog
和 array_timed_reader_prog
。
首先设置 mem_leaker_prog.data_map
的偏移0x1200,0x2000和0x3000处为1。
在 leak_byte
函数中依次调用 leak_bit
函数得到泄露出指定地址的内存。
下面来看 trigger_proc(leakprog->sockfd)
调用的ebpf指令。首先,取到之前 array_set_2dw(leakprog->control_map, 0, 12-bit_index, byte_offset)
设置的 12-bit_index
和 byte_offset
。
取 mem_leaker_prog.data_map
的偏移0x1200处的值,前面说了这个值被设置为1, BPF_AND
和 BPF_OR
对值没有影响,只是为了延长时间,使得之后的 BPF_JGT
指令CPU推测执行为false。
通过 r4=*(u8 *)(r4)
读到了泄露地址的内存,左移 12-bit_index
位之后和0x1000相与,如果 bit_index
位的值是1,结果是0x1000,加上0x2000导致 mem_leaker_prog.data_map
偏移0x3000处的值被加载到了缓存;如果 bit_index
位的值是0,结果是0x0,加上加上0x2000导致 mem_leaker_prog.data_map
偏移0x2000处的值被加载到了缓存。
返回到 leak_bit
函数中,接下来分别去算读 mem_leaker_prog.data_map
偏移0x2000处的值需要的时间times[0]和 mem_leaker_prog.data_map
偏移0x3000处的值需要的时间times[1]。times[0] < times[1]说明 bit_index
位的值是0,times[0] > times[1]说明 bit_index
位的值是1。ebpf指令非常简单,结合注释可以很容易看懂。
对于这种情况的补丁,掩码需要考虑的情况有很多,要处理的值可能存在于源寄存器中(如 ptr+=val
),也可以存在于目的寄存器中(如 val+=ptr
);limit取决于ALU操作是加或减以及偏移量的正负。在补丁增加的 retrieve_ptr_limit
函数中如果ALU操作是加但是偏移是负的,或者ALU操作是减但是偏移是正的说明是减法, mask_to_left
被设置为1;否则说明是加法, mask_to_left
被设置为0。对于map类型的指针减法的 limit=ptr_reg->umax_value+ptr_reg->off
,加法的 limit=ptr_reg->map_ptr->value_size–(ptr_reg->smin_value+ptr_reg->off)
;对于stack类型的指针减法的 limit=MAX_BPF_STACK+off
,加法的 limit=-off
,其中 off=ptr_reg->off+ptr_reg->var_off.value
。
在 fixup_bpf_calls
函数中根据前面计算的limit对寄存器中的值进行掩码运算从而避免越界访问。
参考
https://bbs.pediy.com/thread-249033.htm
http://man7.org/linux/man-pages/man2/bpf.2.html
https://bugs.chromium.org/p/project-zero/issues/detail?id=1528
https://bugs.chromium.org/p/project-zero/issues/detail?id=1711
https://www.kernel.org/doc/Documentation/networking/filter.txt
https://blogs.technet.microsoft.com/srd/2018/05/21/analysis-and-mitigation-of-speculative-store-bypass-cve-2018-3639/以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 【技术分享】如何通过数据包套接字攻击Linux内核
- 严重的Linux内核缺陷为攻击者提供了root访问权限
- BUF早餐铺 |Linux内核漏洞可为攻击者提供Root访问权限;非官方渠道下载的软件潜藏恶意挖矿代码;G...
- 内核必须懂(六): 使用kgdb调试内核
- Linux内核如何替换内核函数并调用原始函数
- Linux内核工程师是怎么步入内核殿堂的?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Flash ActionScript 3.0从入门到精通
章精设、胡登涛 / 清华大学出版社 / 2008-10-1 / 69.00元
Flash ActionScript 3.0的出现,不仅从形式上改变了ActionScript,而且从本质上改变了ActionScript,使ActionScript 3.0成为了真正的面向对象编程语言。 本书从最简单的编程知识出发,带领读者走进编程的大门,是一本不可多得的ActionScript 3.0入门书。本书在注重基础的同时,从更高的层次来介绍ActionScript 3.0的面向对......一起来看看 《Flash ActionScript 3.0从入门到精通》 这本书的介绍吧!