-
-
[原创]浅谈侧信道时序攻击【附Demo】
-
2018-1-12 22:25
11584
-
最近快被Meltdown与Spectre这两个CPU漏洞刷了屏了,于是有空自己写了个demo玩玩。
虽然没能成功演示上面的两个漏洞,不过大概搞清楚了利用原理。
Meltdown与Spectre 成功获取受保护数据的方式,目前据我所知,好像都是基于侧信道时序攻击的。
不同的是 Meltdown是利用CPU的乱序执行泄漏数据,而Spectre是利用分支预测泄漏数据。
泄露的手段就是读取一个byte,然后以byte为数组索引,访问某个大数组的元素。
然后逐个测试各个元素的访问速度,之前访问过的元素,如果还在缓存中,访问起来就要快不少。
废话不多说,上代码:
#include <intrin.h>
#include <stdio.h>
#include <Windows.h>
#define SHIFT_NUMBER 0x0C
#define PAGE_SIZE 4096
#define BLOCK_SIZE (1 << SHIFT_NUMBER)
void CacheLineFlush(LPBYTE lpArray, UINT index) {
_mm_clflush(&(lpArray[index << SHIFT_NUMBER]));
}
void CacheLineFlush_all(LPBYTE lpArray) {
for (UINT i = 0; i < 256; i++) CacheLineFlush(lpArray, i);
}
/************************************************************************/
/* 统计各个块的访问速度,并返回最快的那个块的索引 */
/************************************************************************/
BYTE GetAccessByte(LPBYTE lpArray) {
UINT64 speed[256];
UINT64 start, min;
UINT index, junk;
BYTE result;
//为min赋初始值
min = 0;
//测试访问速度
for (int i = 0; i < 256; i++) {
//获取array[index]的地址
index = i << SHIFT_NUMBER;
//mfence指令用于序列化内存访问,即让乱序执行无效化。
//后面的指令必须在前面的内存读写完成后再开始发射执行。
_mm_mfence();
//记录开始周期
start = __rdtscp(&junk);
junk = *(LPDWORD)(&lpArray[index]);
_mm_mfence();
speed[i] = __rdtscp(&junk) - start;
//如果是初始值,或者比当前值还小
if ((min == 0) || (speed[i] < min)) {
min = speed[i];
result = (BYTE)i;
}
}
return result;
}
/************************************************************************/
/* 用index作为数组索引,访问array的某个元素 */
/************************************************************************/
BYTE AccessArray(LPBYTE lpArray, UINT index) {
return lpArray[index << SHIFT_NUMBER];
}
int main()
{
//假定的kernel内存
BYTE kernel[4];
//array是用户可控制的内存
LPBYTE array;
kernel[0] = 0x55;
kernel[1] = 0xAA;
kernel[2] = 0xF0;
kernel[3] = 0x0F;
array = (LPBYTE)_aligned_malloc(256 * BLOCK_SIZE, PAGE_SIZE);
/*
实现原理:
kernel假定是受保护的内存数据 (本demo里可访问)。
array为用户可控制的一个数组,一共分为256个块,每个块的大小为2的整数倍: (1 << SHIFT_NUMBER)。
然后从kernel中读取一个byte,以这个byte为索引,去访问array所对应的块。
之后立刻循环读取一遍array的各个块,如果之前访问成功了,那么对应的块应该还在缓存中,对应的访问时间要少很多。
统计各个块的访问周期数,最快的块,他的索引就是受保护的那个byte。
CacheLineFlush_all 函数用于把整个数组从缓存中清除出去,这样不至于污染访问速度。
AccessArray 函数用于以一个索引去访问一个数组。
GetAccessByte 函数用于测试数组的各个块的访问速度,返回最快的那个块的索引。
*/
CacheLineFlush_all(array);
AccessArray(array, kernel[0]);
printf("Access fastest: 0x%02X\n", (DWORD)GetAccessByte(array));
CacheLineFlush_all(array);
AccessArray(array, kernel[1]);
printf("Access fastest: 0x%02X\n", (DWORD)GetAccessByte(array));
CacheLineFlush_all(array);
AccessArray(array, kernel[2]);
printf("Access fastest: 0x%02X\n", (DWORD)GetAccessByte(array));
CacheLineFlush_all(array);
AccessArray(array, kernel[3]);
printf("Access fastest: 0x%02X\n", (DWORD)GetAccessByte(array));
getchar();
return 0;
}
以上代码,目前只有编译为Debug版本,可以成功演示,Release版本估计把不少关键点给优化掉了。
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界