一步一步理解CPU芯片漏洞:Meltdown与Spectre(2)

一个具有用户级权限的攻击者在第三条指令中试图访问内核地址,处理器会对其作安全检查,检查该进程是否有权限访问该地址,于是这条指令会触发异常,该指令及之后的指令对寄存器的修改都会被丢弃,处理器重新回到能正常执行的指令中。但由于处理器采用乱序执行方式,在等待处理器完成该指令执行的同时(权限检查结束之前),后面两条指令已经被执行了(尽管最终会被丢弃)。

将指令3读取到的数据乘以4096(4KB),至于为什么是4096,会在下文具体exploit中介绍。

将指令4的结果作为索引对探测数组probe_array(rbx[al*4096])进行访问并进行探测。由于一个内存页的大小是4KB,不同的数据将会导致不同的内存页被访问并存放到CPU缓存中。

此后,攻击者就可以通过缓存侧信道攻击,不断遍历加载rbx[al*4096],由于该数据此时已经在缓存中,攻击者总会遍历出一个加载时间远小于其它的数据,推测哪个内存页被访问过了,从而推断出被访问的内核内存数据。

强调一下,攻击者的目标是要不断探测probe_array来获取内核地址指向的数据。

0X02 Exploit 分析

来看在github上爆出的一个POC[4],也是目前来看比较能让大家深入理解meltdown的一个exploit。该POC能利用应用程序读取内核中的linux_proc_banner变量,这个变量存储了Linux内核的版本信息,可以通过命令cat /proc/version获取。cat /proc/version触发了系统调用将linux_proc_banner变量的信息返回给应用程序。而利用meltdown漏洞可以直接从应用程序中访问linux_proc_banner变量,破坏了内存隔离。
该POC首先利用“sudo cat /proc/kallsyms | grep “linux_proc_banner””获取linux_proc_banner在内核中的地址,再读取该地址上的值。从该地址读取变量的值正是利用了meltdown漏洞。

总的来说,攻击者要窃取内核数据,包括四个过程:Flush阶段,Speculate阶段,Reload阶段以及Probe阶段。值得注意的是,Reload阶段包含在Speculate阶段中,但由于Reload阶段与Flush阶段是一个完整的缓存侧信道攻击过程,不得不把它单独列出来。整个执行顺序是Flush阶段-Speculate阶段(包含Reload阶段)-Probe阶段,这四个过程我们会在下文一一提到。为便于理解,先讲Speculate阶段。

Speculate阶段

Speculate阶段执行上一章节的代码序列过程,利用乱序执行将目标内核地址以索引的形式访问探测数组并加载到缓存中。由speculate函数实现。

为了解该过程,首先用gdb调试meltdown可执行程序了解下该exploit的执行过程

一步一步理解CPU芯片漏洞:Meltdown与Spectre

可以看到在spcculate函数处会触发段错误,而speculate函数也正是该POC的关键代码,其由一段汇编代码组成:

lea %[target], %%rbx\n\t"
    "1:\n\t"

".rept 300\n\t"
    "add $0x141, %%rax\n\t"
    ".endr\n\t"

"movzx (%[addr]), %%eax\n\t"
    "shl $12, %%rax\n\t"
    "movzx (%%rbx, %%rax, 1), %%rbx\n"

"stopspeculate: \n\t"
    "nop\n\t"
    :
    : [target] "m" (target_array),
    [addr] "r" (addr)
    : "rax", "rbx"

该函数的目的是欺骗CPU的乱序执行机制。此处是AT&T 汇编语法,AT&T格式的汇编指令是“源操作数在前,目的操作数在后”,而intel格式是反过来的。我们来一条一条分析上述汇编指令。

lea %[target], %%rbx: 把全局变量target_array的地址放到RBX寄存器中,这里的target_ array正是上一章节中的探测数组probe_array, target_array正好设置为256*4096字节大小,这个设置也是有讲究的,一个字节的取值范围正是0-255,共256个数。4096正好是x86架构中一个页面的大小4KB。那target_array数组正好填充256个页面。
如下:

#define TARGET_OFFSET    12
#define TARGET_SIZE    (1 << TARGET_OFFSET)
#define BITS_READ    8
#define VARIANTS_READ    (1 << BITS_READ)

static char target_array[VARIANTS_READ * TARGET_SIZE];

add $0×141, %%rax: 是一条加法指令,会重复300次,这条指令的作用只是测试处理器能乱序执行成功。

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

转载注明出处:https://www.heiqu.com/5183437e7a9f9b6721710e28dadd4f80.html