November 21, 2018 · Exploit Hardware Security ·

Spectre V1 ( CVE-2017-5753 )

Introduction

DISCLAIMER: This post is strictly for educational purposes. Use at your own risk.

With the new Spectre and Meltdown vulnerabilities discovered recently, let's look at Spectre V1 which was one of the first variants to be discovered.

Spectre V1 ( CVE-2017-5753 )

Systems with microprocessors utilizing speculative execution and branch prediction may allow unauthorized disclosure of information to an attacker with local user access via a side-channel analysis.

To put it simply:

say we have a code that does


 if (x < array_size) {

        /*

            Do something here

            int temp = a ;

        */

    }

The comparison if (x < array_size) , if the operands x/array_size are not in the cache it has to be fetched from the DRAM and this costs some cycles and the processor predicts that if (x < array_size) returns true and starts speculatively executing the statements, when the values of x/array_size arrive and if if (x < array_size) returns true then the processor keeps the states and continues the execution.

If if (x < array_size) returns false, it throws away the state, ie. statements it executed speculatively and continues execution.

So to summarise :

Screenshot-from-2018-11-22-04-16-18

If the prediction made is true we get a boost in the performance.

But what happens when the prediction is wrong? It speculatively executes the upcoming instruction, which might even have some load/store instructions, and throws away the state and continues like it didn't even execute them. Totally safe right? since we discard the state.

That's where the flaw is, even though the states are discarded, the load instructions would fetch the memory onto the cache. so by using a timing side channel like Flush+Reload ( if you haven't checked it out you can find a brief version at here) we can find out the value. Pretty sweat isn't it ?

The authors of the (Spectre paper) have demonstrated a way to extract the data.


void victim(size_t x){

    if(x < array1_size)
     {
         y = array2[array1[x]*4096];
     }
 }

where

  1. x is the attacker controlled data.
  2. array1 and array2 are two array's

and to ensure the validity of memory access to array1 , the above code contails an if statement , which verifies x is always less than array1_size.

This code looks safe from any software vulnerabilities.

But if we call the victim(x) with a value which is less than array1_size for a few time, then we can trick the BPU (Branch prediction unit) into thinking that next time we call victim(x) it's most likely going to evaluate to true.

ie.. Let array1_size = 10;

// we now train the BPU

victim(1);

victim(2);

victim(3);

victim(4);

victim(5);

Now since all these calls were made with x <array1_size, what happens if we send a value of x > array1_size?

victim(100);

the if(x < array1_size) is obviously going to evaluate to false, but since we trained the BPU with values < array1_size , the processor speculatively executes y = array2[array1[x]*4096]; untill the expression is evaluated,if(x < array1_size) evaluates to false and the state is dropped,but the memory fetch it performed on array2 is reflected in the cache , ie..

array2[array1[100] * 4096] would be present in the cache, but the value of y will not be updated to array2[array1[100] * 4096].

So How do we leak any value from the memory including the kernel space?

If we try to read any data in the kernel space from the user space, the program crashes but we can use the speculative execution to bring the data to the cache.

Now we need to a way to find the content of a particular memory address say 0xffff968c942d9001

since we know that array's in C are accessed via the address, we can exploit this to read any memory address we want.

Example.


    

    uint8_t array[100] = {0}; // array of 100 bytes

    array[0] is the same as *(uint8_t *)array

    array[1] is the same as *((uint8_t *)array+1)

    .

    .

    .

    array[n] is the same as *((uint8_t *)array+n)

if n < 100 we are in bounds and the value from the array is retrieved.

But if we get to control the value of n , we can read any memory address we want.

say address of array is 0x55b391c28020 and we want to access 0xffff968c942d9001 , the offset is 18446533899525492705 ,

ie.. (uint8_t *)array+18446533899525492705 == 0xffff968c942d9001

that's exactly out case here


if(x < array1_size)

     {

         y = array2[array1[x]*4096];

     }

if we control the value of x, we can speculatively read off any value from the memory.

So what do we need to make this attack work?

Conditions:

  1. The value X is controlled by the attacker.

  2. array1[x] resolves to a secret byte k somewhere n the victim's memory

  3. array1_size and array2 are uncached but k is cached

  4. previous operations received values of x that were valid leading the BPU to assume the if will likely evaluate to true.

When we run the complied code, the processors compares the malicious value of x with array1_size and since array1_size is not in the cache (condition #3), it results in a cache miss and the processor faces a great delay until it's available. Meanwhile, the BPU assumes that it will be true, since we trained it that way, so the speculative execution logic adds x to the base address of array1 and request the data from the memory subsystem. This read is a cache hit (condition #3) and quickly returns the value of the secret byte k , then the speculative execution logic uses k to compute array2[ k * 4096] which is a cache miss (condition #3). While the read from array2 is still in flight the processor realizes that its speculative execution was incorrect and rewinds its register state, however the speculative read from array2 affects the cache stat in an address specific manner, where the address depends on the secret byte k

Now to finally know the value of k the attacker can use a timing side-channel technique like Flush+Reload, since the array2 is uncached only the

array2[ k * 4096] would be in cache and we can use timing analysis to find this byte.

POC

The proof of concept code for leaking the data via speculative execution is found at

https://www.exploit-db.com/exploits/43427/

But before you run this code make sure PTI is off,

you can disable PTI by adding pti=off in /etc/default/grub at

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash nopti pti=off "

And if we run it we would get

Screenshot-from-2018-11-22-05-19-30

But this is all in user space, lets try extracting a byte from the kernel space.

I've Created a kernel module which would create a "Secret string" in kernel space and spit out the address when accessed via /proc/leakSecretByteAddress

Screenshot-from-2018-11-22-03-42-05

Screenshot-from-2018-11-22-03-46-23

Instruction on how to get it running can be found in my repository

https://github.com/Dhayalanb/SpectrePocKernelModule

Do share and any criticism is welcome :)

  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket