网络攻防技术——熔断攻击和幽灵攻击

发布时间 2024-01-06 21:04:29作者: Leo1017

实验12:熔断攻击与幽灵攻击实验

实验内容:

幽灵攻击于2017年发现,并于2018年1月公开披露,它利用关键漏洞进行攻击,存在于许多现代处理器中,包括Intel、AMD和ARM处理器。漏洞允许程序突破进程间和进程内的隔离,以便恶意程序可以读取来自无法访问区域的数据。硬件保护不允许这样的访问机制(用于进程间的隔离)或软件保护机制(用于进程内的隔离),但CPU设计中存在漏洞,可能会破坏保护。因为缺陷存在于硬件中,很难从根本上解决问题,除非更换CPU。幽灵和熔断漏洞代表了CPU设计中的一种特殊类型的漏洞,它们还为安全教育提供了宝贵的一课。

本实验的学习目标是让学生获得幽灵攻击的第一手经验。攻击本身非常复杂,因此我们将其分解为几个小步骤,每个步骤都是易于理解和执行。一旦学生理解了每一步,就不难理解了把所有的东西放在一起进行实际的攻击。本实验涵盖了以下内容:

•幽灵攻击

•侧通道攻击

•CPU缓存

•CPU微体系结构内的无序执行和分支预测

 

  1. 程序会首先初始化一个大小为10*4096字节的数组,相当于一个数组中存放了10个内存页(在Linux中一个内存页的大小是4KB),在读写时各个内存页不会相互影响,并将其每一页的第一个字节设为1。然后,它会将整个数组从CPU缓存中清除,接着访问数组中的某些项,最后对数组中的每一个页进行访问,并测量每次访问所需的CPU周期数。

    从实验结果可以看出,因为array[3*4096]和array[7*4096]被读取到了CPU缓存中,所以访问时间明显小于访问数组中其他页的时间

  1. 由于secret是占一个字节,有256个可能的值,我们需要找到这个索引的具体值,这里为了方便实验,将每个值映射到一个数组元素,同时为了避免映射的数据处于同一个缓存块中,设置一个间隔——4096,Flush和Reload都是针对映射后的一个数组元素进行操作

    整个流程是攻击者先刷新CPU缓存,然后调用受害者函数,在受害者函数中使用了一个秘密信息(一个未知的数组下标)访问了对应映射在数组中的元素,攻击者不知道访问了哪个元素,于是遍历数组元素,访问时间最短的数组元素秘密信息映射到的元素

    实验代码中的flushSideChannel()函数用于清空CPU缓存,以确保后续的内存访问会从主存中加载数据。该函数通过写入数组来将其置于RAM中,并使用_mm_clflush()函数将其从CPU缓存中去除

victim()函数是一个虚拟的受害者程序,它会访问一个敏感的内存位置(即secret*4096 + DELTA),并将其缓存到CPU缓存中。

reloadSideChannel()函数对数组进行遍历,每次访问一个数组元素的同时测量访问时间。如果访问时间小于给定的阈值(即 CACHE_HIT_THRESHOLD),则可以认为该数组元素已经被缓存在 CPU 缓存中,该数组元素的下标i就是秘密信息secret

注:添加DELTA的原因是担心array[0*4096]处的元素可能和数组旁边的其他变量处在同一个缓存块中,由于把其他变量加入缓存,导致array[0*4096]也被加入缓存,影响实验结果,因此加上一个偏移量DELTA

编译并运行程序,发现同时打印了很多secret值,于是调整阈值,最终修改为80

通过实验结果可以看出由于secret位于CPU缓存中,访问时间明显小于访问其他数据的时间,成功破解了secret的值

  1. 乱序执行:乱序执行是一种优化技术,它允许 CPU 最大限度地利用其所有执行单元。CPU不是严格按照顺序处理指令,而是在所有所需资源可用时立即并行执行指令。当前操作的执行单元被占用时,其他执行单元可以抢先运行

    预测执行:在分支执行的程序中,CPU会先根据之前的执行清空预测一个结果并执行对应的分支语句,如果预测是对的,那么推测执行的结果会被承认,性能会得到很大的提升。如果预测是错的,那么 CPU 会恢复回保存的状态,就好像从来没有执行过这个分支一样

    攻击原理:CPU不会清空位于CPU缓存中的错误执行结果,于是可以上面的计算访问时间差的方法获取到CPU缓存中的值(这里也是映射到数组元素上,进行了抽象)

    修改victim()函数为一个条件判断语句,对传入参数进行判断,小于10则执行真分支,访问参数指向的元素(相当于放入缓存),否则不执行

在main()函数中,首先调用flushSideChannel()函数清空CPU缓存,然后使用循环来训练CPU执行victim()函数中的真分支,这样CPU会将下一次分支执行的结果预测为true

接下来,先使用_mm_clflush()函数清空数组和变量size的CPU缓存,然后调用victim()函数并传一个较大的值97,根据乱序执行的特性,尽管if判断的真实结果是false,但在CPU预测结果是true,在真实结果出来之前,CPU会执行真分支语句,同时结果被放入CPU缓存中

最后调用reloadSideChannel()函数对256种可能遍历,如果发现访问第97个元素时时间明显变短,说明程序执行了访问97号元素的指令

编译并运行程序,结果如下,成功读取了缓存内容,发现真分支被CPU执行

  1. 这里研究同一进程中的幽灵攻击。

    如:同一个浏览器访问来自不同服务器的web页面时,一般会在同一个进程中被打开。在浏览器内部提供的沙盒机制为这些页面提供了隔离机制,一个页不能访问另一个页的数据。大多数软件保护依赖于条件判断来决定访问是否得到允许。我们可以将这种机制类比Task3的情况,即使条件检查失败,我们也可以让CPU 执行被保护代码的分支

    实验的原理如下图所示,这里将受害者函数设置成一个沙盒访问函数,buffer可以代表内存中的一个沙盒范围,通过沙盒访问函数只能返回沙盒范围内的地址(黄色部分),假设攻击者知道secret的地址,但攻击者无法通过访问到秘密信息,因为越界后restrictAccess函数会返回0,只能访问buffer[0],但采用Task3中控制程序执行真分支的方法,可以无视判断直接执行访问操作,并根据访问到的秘密信息去访问映射在数组中的元素,并放入CPU缓存中,这时去遍历映射的数组,访问第三个元素时访问时间最短,说明秘密信息是3

    这里有个注意事项,访问secret需要访问buffer[6],但buffer一共只有0-2,数组越界怎么能访问成功呢?这是因为数组越界的概念是在程序代码的层面理解的,在CPU的层面上,判断数组是否越界是操作系统内核的事情,毕竟硬件不可能管理内存,这是操作系统的任务,于是CPU直接去取了buffer[6]这个指针(这里说指针单纯是为了容易理解)指向的内存,而不会判断是否越界

在沙盒函数中定义边界是0-9

首先调用flushSideChannel()函数清空CPU缓存,这里假设攻击者知道秘密信息的内存地址,用buffer的下标index_beyond代表秘密信息的地址

注:size_t是一种无符号整数类型,其大小通常与机器的指针大小相同,用于表示内存中对象的大小、索引和偏移量。

在进行幽灵攻击时,首先训练CPU执行真分支,即判断允许访问,这会导致CPU下一次执行预测分支为真,然后再次清空CPU缓存,然后使用超出沙盒范围的地址(index_beyond)访问秘密信息,由于进行判断用到的数据都不在缓存中,CPU去取数据的同时,根据预测为真会先允许访问秘密信息,即使后面丢弃数据,秘密信息的值还是存在CPU缓存中,最后用秘密信息作为索引访问映射的数组,放在CPU缓存中的应该是数组中以秘密信息为下标对应的元素

最后调用reloadSideChannel()函数从CPU缓存中读取秘密信息(访问时间最短的数组元素的下标)

     编译并执行程序,发现找到了秘密信息的值

 

  1. CPU 有时会在缓存中加载额外的值,期望在稍后使用,或者设置的阈

    值不是很准确。这些噪声都会影响我们的攻击结果

    这里针对Task4中攻击准确率较低做出了改进,从只进行一次攻击就得到结果,改进成重复进行1000次攻击,根据这1000次结果,取出概率最高的结果作为攻击结果

针对数组中映射的256个元素,我们定义了一个大小为256的数组score[],score的每一个值代表判断该下标是秘密信息的次数,确认次数越多表示这个下标是秘密信息的概率最大

最后取出score值最大,即概率最高的内存块作为攻击结果

编译并运行程序,发现读到的值大多是0

1. 得分最高的很可能是score[0]的原因是:虽然在CPU层面确实执行了访问操作,但沙盒函数返回的依然是0,这时访问array[0],array[0]会把CPU缓存中的array[secret]替换掉,所以当返回值是0时不能再去访问array[0],加一条判断语句

2. 个人猜测会不会是多了一条IO语句,对程序执行时间产生延迟,就跟下面的usleep(10);函数一样,不同操作系统对IO的操作不一样,导致结果不同

3. 将 usleep 中的值不断增大,观察到cache hit的值不断减小,最后变为0,原因可能是在 sleep 返回前,cpu 就已经将主存的结果返回了,因此无法得到cache hit

  1. 在main函数中加一个for循环,每次使用buffer[index_beyond]读取的内存地址加一即可

    得到的结果如下:

  1. 由Task1得到CPU缓存如何影响访问时间

    由Task2得到如何根据这一影响使用Flush-Reload得到CPU缓存的值

    由Task3得到如何去影响CPU对分支语句的执行

    由Task4得到如何通过影响分支语句实现对内存任意数据的读取

    由Task5得到通过多次执行提高攻击准确率

    由Task6得到对攻击程序的多字节扩展