This commit is contained in:
Lenni Hein 2025-02-20 19:31:32 +01:00
commit fb14fd90a4
14 changed files with 17085 additions and 0 deletions

1
.envrc Normal file
View File

@ -0,0 +1 @@
use flake

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
.obsidian
.direnv
\.idea/
cmake-build-debug/
cmake-build-release/
flush_flush/histogram/ff/calibration
flush_flush/histogram/ff/file

148
README.md Normal file
View File

@ -0,0 +1,148 @@
## Flush+Reload Covert Channel
### Primitive
in `./src/lib.c` sind folgende Primitive `flush`, `reload` und `time_maccess`:
`maccess` greift auf diese Speicheradresse `addr` zu.
```C
void maccess(void* addr)
{
asm volatile ("movq (%0), %%rax\n"
:
: "c" (addr)
: "rax");
}
```
`flush` verdrängt die Speicheradresse `addr` aus dem Cache.
```C
void flush(void* addr)
{
asm volatile ("clflush 0(%0)\n"
:
: "c" (addr)
: "rax");
}
```
`time_maccess` misst wie lange der Zugriff auf die Speicheradresse `addr` dauert.
Zum Messen der Dauer des Speicherzugriffs lesen wir vor und nach dem Zugriff die Zeit aus.
Um unsere Reihenfolge zu bewahren reicht das Ausschalten von Compiler Optimierungen ggfs. nicht aus. Um zu verhindern, dass die CPU die Instruktionen parallel oder in anderer Reihenfolge ausführt können sog. Fences verwendet werden:
```C
size_t time_maccess(void (* addr)(void))
{
uint64_t start, end, delta;
uint64_t lo, hi;
asm volatile ("LFENCE");
asm volatile ("RDTSC": "=a" (lo), "=d" (hi));
start = (hi<<32) | lo;
asm volatile ("LFENCE");
asm volatile ("movq (%0), %%rax\n"
:
: "c" (addr)
: "rax");
asm volatile ("LFENCE");
asm volatile ("RDTSC": "=a" (lo), "=d" (hi));
end = (hi<<32) | lo;
asm volatile ("LFENCE");
delta = end - start;
return delta;
}
```
### Block 1: Messen der Timing-Differenzen + Threshold bestimmen
##### Tips
- am besten kompiliert man ohne Compileroptimierungen (`-O0`):
```bash
gcc -O0 cache_test.c lib.c -o cache_test
```
- CMake ist praktisch! siehe `./src/CMakeList.txt`. Verwenden durch `cmake .` und dann `make`.
Muster: `./src/cache_test.c`
### Block 2: Signale über den Cache
**Jetzt können wir den Cache doch mal als Medium verwenden. Der Sender kann `0` und `1` über den (ausbleibenden) Speicherzugriff kodieren. Der Empfänger interpretiert dann, je nach Zugriffszeit.**
`sharedlib.c` sieht so aus:
```C
void function(void)
{ asm volatile (
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
...
```
Die Adresse von `function` kann also zum Flushen und Reloaded verwendet werden.
Da immer eine gesamte Cacheline geladen wird, sollte sich nichts anderes in dem selben 64Byte Block befinden.
Die Funktionen `padding_before` und `padding_after` sind beide 64 Byte groß, also eine Cacheline.
Damit kann dann auch gar nichts mehr schief gehen :)
`sharedlib.c` kann man so kompilieren und linken:
```bash
# sharedlib kompilieren
gcc -fPIC -shared -O0 -o libsharedlib.so sharedlib.c
# sharedlib bei Sender und Empfänger linken
gcc -O0 sender.c lib.c -L. -lsharedlib -o sender
gcc -O0 empfaenger.c lib.c -L. -lsharedlib -o empfaenger
# ggfs. muss man noch den PATH exportieren
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
```
*(oder einfach `cmake` verwenden)*
dann kann man `function` verwenden:
```C
# in e.g. sender.c
maccess((void *) function)
...
```
##### Tips
- `taskset` zum kann pinnen der Programme auf einen bestimmten CPU-Kern benutzt werden. Das kann helfen.
- Verwendet man blockierenden Sleep um zu Takten, z.B. `clock_nanosleep`, dann sollte man unbedingt darauf achten nicht eine Periodendauer zu schlafen, sondern nur bis zum nächsten Zeitabschnitt. Einfacher geht es mit nicht-blockierendem Sleep, z.B. `ualarm`.
So kann man `ualarm` nutzen, um alle x µs einen `SIGARLM` auszulösen:
```C
int main(void){
...
signal(SIGALRM, signal_handler);
ualarm(sample_interval, sample_interval);
```
Bei jedem `SIGALRM` Signal wird `signal_handler` ausgeführt:
```c
void signal_handler(int signo) {
if (signo == SIGALRM) {
# CODE HERE
}
}
```
- Der Zeitabstand von Flush zu Reload sollte MAXIMIERT werden! Dies ist das Zeitfenster, in welchem der Sender die Adresse(n) zurück in den Cache laden kann.
- Der Sender kann nicht nur einmal Laden. Das kann man auch mehrfach in einem Zeitfenster machen, um sicher zu gehen, dass die Cacheline auch geladen ist, wenn der Empfänger ausließt.
### Block 3: Einen String über den Cache als Medium senden
- Kann man irgendwie mehr als ein Bit gleichzeitig senden? ;)
Muster: `./src/sender.c` und `./src/receiver.c`

27
flake.lock generated Normal file
View File

@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1739580444,
"narHash": "sha256-+/bSz4EAVbqz8/HsIGLroF8aNaO8bLRL7WfACN+24g4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "8bb37161a0488b89830168b81c48aed11569cb93",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

25
flake.nix Normal file
View File

@ -0,0 +1,25 @@
{
description = "Development environment with CMake, GCC, Clang, Python, Matplotlib, and Scikit-learn";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
in {
devShells.${system}.default = pkgs.mkShell {
buildInputs = [
pkgs.cmake
pkgs.gcc
pkgs.clang
pkgs.python3
pkgs.python3Packages.matplotlib
pkgs.python3Packages.scikit-learn
pkgs.python3Packages.numpy
];
};
};
}

22
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.10)
project(cache_attacks C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_FLAGS "-O0" CACHE STRING "C Compiler flags" FORCE)
set(CMAKE_CXX_FLAGS "-O0" CACHE STRING "C++ Compiler flags" FORCE)
# Add shared library
add_library(sharedlib SHARED sharedlib.c sharedlib.h)
# Add the sender executable
add_executable(send send.c lib.c lib.h)
target_link_libraries(send sharedlib)
# Add the receiver executable
add_executable(recv recv.c lib.c lib.h)
target_link_libraries(recv sharedlib)
# add the cache-test executable
add_executable(cache_test cache_test.c lib.c lib.h)
target_link_libraries(cache_test sharedlib)

56
src/cache_test.c Normal file
View File

@ -0,0 +1,56 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "lib.h"
#include "sharedlib.h"
#define NUM_ACCESSES 1000000
void measure_multiple_accesses(void (*addr)(void), size_t num_accesses, size_t* hits, size_t* misses)
{
for (size_t i = 0; i < num_accesses; i++)
{
flush((void*) addr);
misses[i] = time_maccess(addr);
maccess((void*) addr);
hits[i] = time_maccess(addr);
}
}
size_t find_crossover_point(size_t* hits, size_t* misses, size_t num_accesses)
{
size_t hit_sum = 0, miss_sum = 0;
for (size_t i = 0; i < num_accesses; i++)
{
hit_sum += hits[i];
miss_sum += misses[i];
}
size_t avg_hit = hit_sum / num_accesses;
size_t avg_miss = miss_sum / num_accesses;
return (avg_hit + avg_miss) / 2; // Midpoint as threshold
}
int main()
{
size_t* hits = malloc(NUM_ACCESSES * sizeof(size_t));
size_t* misses = malloc(NUM_ACCESSES * sizeof(size_t));
if (!hits || !misses)
{
perror("Memory allocation failed");
return 1;
}
measure_multiple_accesses((void*) function, NUM_ACCESSES, hits, misses);
size_t threshold = find_crossover_point(hits, misses, NUM_ACCESSES);
printf("Suggested threshold: %zu\n", threshold);
free(hits);
free(misses);
return 0;
}

59
src/lib.c Normal file
View File

@ -0,0 +1,59 @@
#include "lib.h"
size_t time_flush(void (* addr)(void))
{
uint64_t start, end, delta;
uint64_t lo, hi;
asm volatile ("LFENCE");
asm volatile ("RDTSC": "=a" (lo), "=d" (hi));
start = (hi<<32) | lo;
asm volatile ("LFENCE");
asm volatile ("CLFLUSH 0(%0)\n":: "c" (addr): "rax");
asm volatile ("MFENCE");
asm volatile ("RDTSC": "=a" (lo), "=d" (hi));
end = (hi<<32) | lo;
asm volatile ("LFENCE");
delta = end - start;
return delta;
}
size_t time_maccess(void (* addr)(void))
{
uint64_t start, end, delta;
uint64_t lo, hi;
asm volatile ("LFENCE");
asm volatile ("RDTSC": "=a" (lo), "=d" (hi));
start = (hi<<32) | lo;
asm volatile ("LFENCE");
asm volatile ("movq (%0), %%rax\n"
:
: "c" (addr)
: "rax");
asm volatile ("LFENCE");
asm volatile ("RDTSC": "=a" (lo), "=d" (hi));
end = (hi<<32) | lo;
asm volatile ("LFENCE");
delta = end - start;
return delta;
}
void maccess(void* p)
{
asm volatile ("movq (%0), %%rax\n"
:
: "c" (p)
: "rax");
}
void flush(void* p)
{
asm volatile ("clflush 0(%0)\n"
:
: "c" (p)
: "rax");
}

9
src/lib.h Normal file
View File

@ -0,0 +1,9 @@
# pragma once
#include <stddef.h> // For size_t
#include <stdint.h> // For uint64_t
void maccess(void* p);
void flush(void* p);
size_t time_maccess(void (*addr)(void));
size_t time_flush(void (*addr)(void));

27
src/mksharedlib.py Normal file
View File

@ -0,0 +1,27 @@
import os
def generate_nop_function(filename="sharedlib.c"):
with open(filename, "w") as f:
f.write("#include \"sharedlib.h\"\n\n")
f.write("void padding_before(void) { asm volatile (\n")
for _ in range(64):
f.write(" \"nop\\n\\t\"\n")
f.write(" :::\n); }\n\n")
f.write("void function(void)\n{")
f.write(" asm volatile (\n")
size = 64 * 256
for _ in range(size):
f.write(" \"nop\\n\\t\"\n")
f.write(" :::\n );\n}")
f.write("\n\nvoid padding_after(void) { asm volatile (\n")
for _ in range(64):
f.write(" \"nop\\n\\t\"\n")
f.write(" :::\n); }\n")
if __name__ == "__main__":
generate_nop_function()

98
src/recv.c Normal file
View File

@ -0,0 +1,98 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <ctype.h>
#include "lib.h"
#include "sharedlib.h"
#define BITS 8
#define BUFFER_SIZE 256
volatile uint8_t buffer[BUFFER_SIZE];
volatile size_t byte_index = 0;
volatile int sample_interval = 100; // Default: 100 µs
volatile int threshold = 245; // Default: 245 cycles
void signal_handler(int signo) {
if (signo == SIGALRM)
{
// Check if buffer is full
if (byte_index >= BUFFER_SIZE)
{
ualarm(0, 0); // Stop sampling
printf("Buffer full. Stopping capture.\n");
return;
}
// Receive a byte
uint8_t byte = 0;
for (int i = 0; i < BITS; i++)
{
size_t time = time_maccess((void *) function + (i << 9));
if (time < threshold)
{
// set the ith bit of byte to 1 if it was a cache hit
byte |= (1 << i);
}
}
// flush the cache
for (int i = 0; i < BITS; i++)
{
flush((void *) function + (i << 9));
}
buffer[byte_index++] = byte; // Store received byte
}
}
int main(int argc, char *argv[]) {
if (argc < 3)
{
fprintf(stderr, "Usage: %s <sample_interval> <threshold>\n", argv[0]);
return 1;
}
sample_interval = atoi(argv[1]);
threshold = atoi(argv[2]);
printf("Receiver started with sample interval: %d µs, threshold: %d cycles\n", sample_interval, threshold);
// Flush the cache once before starting
for (int i = 0; i < BITS; i++)
{
flush((void *) function + (i << 9));
}
signal(SIGALRM, signal_handler);
ualarm(sample_interval, sample_interval);
while (byte_index < BUFFER_SIZE) {
pause();
}
// Print received bytes in hex
printf("Received %zu bytes:\n", byte_index);
for (size_t i = 0; i < byte_index; i++) {
printf("%02X ", buffer[i]);
}
printf("\n");
// Print as ASCII characters
printf("ASCII Output:\n");
for (size_t i = 0; i < byte_index; i++) {
//if (buffer[i] >= 32 && buffer[i] <= 126) // Printable ASCII
if (isalnum(buffer[i]))
{
printf("%c", buffer[i]);
} else
{
printf("."); // Placeholder for non-printables
}
}
printf("\n");
return 0;
}

64
src/send.c Normal file
View File

@ -0,0 +1,64 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include "lib.h"
#include "sharedlib.h"
#define BITS 8
volatile uint8_t *buffer = NULL;
volatile size_t buffer_len = 0;
volatile size_t current_index = 0;
volatile uint8_t byte_to_send = 0;
void signal_handler(int signo)
{
if (signo == SIGALRM)
{
if (current_index < buffer_len)
{
byte_to_send = buffer[current_index++];
} else
{
current_index = 0;
}
}
}
int main(int argc, char *argv[])
{
if (argc < 3)
{
fprintf(stderr, "Usage: %s <string> <sample_interval>\n", argv[0]);
return 1;
}
buffer = (uint8_t *) argv[1];
buffer_len = strlen(argv[1]);
int sample_interval = atoi(argv[2]);
// set up an alarm signal that triggers every timeframe
signal(SIGALRM, signal_handler);
ualarm(sample_interval, sample_interval);
// during the timeframe, continuously send the signal
while (1)
{
for (int i = 0; i < BITS; i++)
{
if (byte_to_send & (1 << i))
{
maccess((void *) function + (i<<9));
}
else
{
flush((void *) function + (i<<9));
}
}
}
return 0;
}

16527
src/sharedlib.c Normal file

File diff suppressed because it is too large Load Diff

9
src/sharedlib.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef SHAREDLIB_H
#define SHAREDLIB_H
// Function declarations
void padding_before(void);
void function(void);
void padding_after(void);
#endif // SHAREDLIB_H