Spectre(幽灵)CPU缓存漏洞原理

1 篇文章 0 订阅
1 篇文章 0 订阅

Spectre(幽灵)CPU缓存漏洞原理

偶然看到了一篇这样的推送,但是感觉作者没有说清楚,所以自己琢磨了好一会儿才弄懂,现在写来说说自己的通俗理解,Meltdown(熔断)原理和这个类似,网上有很多的详解,大家可以去看

相关的Meltdown和Spectre的漏洞代码github上已开源,大家可以去读一下源码

https://github.com/Eugnis/spectre-attack

https://github.com/feruxmax/meltdown

https://github.com/gkaindl/meltdown-poc

https://github.com/turbo/KPTI-PoC-Collection

一、前提知识:CPU的缓存

我们知道,CPU的速度提升发展非常快,而且又发展出多核的CPU技术,但是从内存中读取指令的速度远远小于CPU的执行指令速度,为了提高CPU执行指令的速度,在CPU的内部加上了缓存,来存放内存中某一块有很大概率在下次要被执行指令,缓存也分为一级(L1)、二级缓存(L2)(CPU每个核都有)、三级缓存(L3)(多核共享)。

二、CPU的分支预测执行

如果在一段程序中有这样一段代码

int judge = 114514;
void foo(int x) {
    if (x < judge) { // judge放在内存中
    	/*要执行的代码段*/
	}
}

int main() {
    for (int i = 0; i < 99; i++) {
        foo(1);		// 执行100次这样的函数,每次都传入1,小于judge
    }
    foo(11451444); //此时传入的11451444大于了judge
}

judge被放在了内存中,每次foo()函数被调用执行的时候,都要从内存中拿到judge的值和x进行比较,但是从内存中拿到judge的值的过程是很慢的,但是主函数中调取了foo()函数100次,每次都比judge小,所以CPU会对foo()函数的if分支进行预测优化,在下次调用foo()函数时,CPU预测传入的x比judge小,先把要执行的代码做了,并把数据放到缓存中,等到取到judge的时候再把x和judge做判断,如果x确实比judge小,那么CPU就会从缓存中把之前等待取judge时候存好的数据直接拿出来,如果x比judge大,CPU就会丢弃当前的状态,重新恢复到执行if分支前的状态,但是放在缓存中的数据不会删除!!!

二、Spectre(幽灵)原理

参考上面github上Spectre的源代码

// 首先定义了一个secret数据
char *secret = "The secret data";

// array1_size放在内存中
unsigned int array1_size = 16;

uint8_t array1[160] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};

uint8_t array2[256 * 512];

void victim_function(size_t x) {
    if (x < array1_size)
        temp &= array2[array1[x] * 512];
}

其实array1初始化的数据我们不用管,这里我们用到了一个原理,如果让:

size_t x = (size_t)(secret - (char *)array1);

那么x就是array1地址和secret地址之间的差值,画一个图理解一下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FcgcHSpA-1589872108577)(C:\Users\asus\Desktop\1.JPG)]

那么x = 0x546789 - 0x123456 = 0x333333,那么这时候通过数组array1去访问secret首地址中的值就可以这样

value_type val = array1[x];	
// 那么val的值就是array1的首地址加上偏移量0x333333(也就是0x456789)出的数据
// 当然,这样访问超出了数组array1的大小
// 正常的代码CPU会报异常越界访问并停止,但是通过预测分支的缓存机制可以做到这一点!

所以,如果我们想要访问到地址0x555555中的值(假如他是),那么就可以这样:

size_t visit = 0x555555 - array1;
// 调用victim_function
victim_function((int)visit);

现在,看一下Spectre的过程

  • 先训练CPU得到对victim_function的分支预测处理,比如执行10次,每次让index的值小于array1_size

    for (int i = 0; i < 9; i++) {
        victim_function(5);
    }
    

    10次之后,victim_function()会训练出一个分支预测出来,这时候再传入一个我们想要获取的内核地址中的值,比如说上述的0x555555:

    // 那么传入的x就应该是
    size_t x = 0x555555 - array1; // x = 4320ff
    victim_function(x);
    
  • 现在看一下victim_function()函数中发生了什么

    void victim_function(size_t x) {
        // 此时x就是内核地址0x555555相对于array1的偏移量
        if (x < array1_size)
            temp &= array2[array1[x] * 512];
    }
    

    由于分支预测的功能,再写入缓存中的时候,虽然发生了array1的越界访问,但是由于数据没有写到内存,有异常但不会停止,这时候

    • CPU先从array1[x]处拿到数据(也就是内核地址0x555555处),假设里面的数据是10

    • 与512相乘,得到array2的下标

      10 * 512 = 5120
      
    • 然后从内存中把array2[5120]的数据放入到缓存中,执行和变量temp的&(与)操作

    • 这时候,array1_size的值送过来了,CPU将它和传入的x做一下比较,发现x比array1_size的值大,丢弃CPU之前等待array1_size传入的时候所做的操作,回到if分支处的状态,但是,array2[5120]的值已经被放在了CPU的缓存中,暂时不会丢弃。

  • 获取内核地址0x555555处的值

    • 紧接着上一步,这时候我们马上访问一下array2所在内存的那一片区域,看看array2的哪一个数据我们访问到的时间最短,由于array2[5120]的数据被放在了缓存中,array2[5120]获取数据的时间最短,所以我们马上可以反推出0x555555处的值,即

      5120 / 512 = 10;
      

      那么这个就是内存0x555555出的值了。

这里只是说了一下Spectre的大致原理,更加详细的内容可以去阅读一下源代码。

  • 8
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值