这是Meltdown Attack的第二个实验,原实验教程在此:
https://seedsecuritylabs.org/Labs_16.04/System/Meltdown_Attack/
Task 2 : 使用Cache作为侧信道
本实验的目的是利用函数的缺陷来从侧信道获取秘密信息。本次实验基于以下两点假设:
- 存在缺陷的函数使用一个secret作为索引来从数组加载值。
- 这个secret不能从外面获取。
目标:使用FLUSH+RELOAD技术利用侧信道获取这个secret
FLUSH+RELOAD步骤有三步:
- 从cache内存FLUSH所有数组,来保证数组没有被缓存到cache中
- 唤醒缺陷函数。这个缺陷函数基于secret来访问数组的某个元素。这个行为导致相应的数组元素被缓存到cache。
- RELOAD整个数组,并且测量每个元素重载的时间。如果某个元素加载比较快,那么意味着这个元素之前就已经在cache中了。这个元素很有可能就是之前缺陷函数访问的那个元素。因此,我们可以知道secret是什么了。
图片来源于实验教程的讲义
下面的程序使用FLUSH+RELOAD技术来找出变量secret里一个字节的值。而对于一个字节的秘密信息有256个可能的值,我们需要将每个值映射到一个数组元素中去。最简单的方式是定义一个有256个元素的数组。然而,缓存是基于块级别而不是字节级别的。如果访问一个array[k]
,包含这个元素的一整块内存都会被缓存,这也意味着array[k]
的相邻元素也会被缓存,使得我们很难推断secret的值。为了解决这个问题,我们创建一个256*4096的数组。RELOAD步骤中使用的每个元素是array[k * 4096]
。因为一个标准的cache块大小是64字节,所以两个不同的元素array[i * 4096]
和array[j * 4096]
不会在一个cache基本块中。
由于array[0*4096]
可能会作为相邻内存的变量落入同一个cache基本块。因此,我们需要避免使用array[0*4096]
来FLUSH+RELOAD。为了保持程序的一致性,对于所有的k值使用array[k*4096 + DELTA]
,其中DELTA是常数1024。
//FLUSHRELOAD.c
#include <emmintrin.h>
#include <x86intrin.h>
uint8_t array[256*4096];
int temp;
char secret = 94;
/* cache hit time threshold assumed*/
#define CACHE_HIT_THRESHOLD (80)
#define DELTA 1024
void flushSideChannel()
{
int i;
// Write to array to bring it to RAM to prevent Copy-on-write
for (i = 0; i < 256; i++) array[i*4096 + DELTA] = 1;
// Flush the values of the array from cache
for (i = 0; i < 256; i++) _mm_clflush(&array[i*4096 +DELTA]);
}
void victim()
{
temp = array[secret*4096 + DELTA];
}
void reloadSideChannel()
{
int junk=0;
register uint64_t time1, time2;
volatile uint8_t *addr;
int i;
for(i = 0; i < 256; i++){
addr = &array[i*4096 + DELTA];
time1 = __rdtscp(&junk);
junk = *addr;
time2 = __rdtscp(&junk) - time1;
if (time2 <= CACHE_HIT_THRESHOLD){
printf("array[%d*4096 + %d] is in cache.\n", i, DELTA);
printf("The Secret = %d.\n",i);
}
}
}
int main(int argc, const char **argv)
{
flushSideChannel();
victim();
reloadSideChannel();
return (0);
}
根据第一个实验得到的数据来调整CACHE_HIT_THRESHOLD
,可以提高准确率。
实验结果
结果表明,我们找到了secret。