CacheAttacks-Bootcamp/README.md
2025-02-20 18:50:04 +01:00

3.2 KiB

Flush+Reload Covert Channel

Primitive

in ./src/lib.c sind folgende Primitive:

time_maccess misst wie lange der Zugriff auf die Speicheradresse addr dauert.

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;
}

maccess greift auf diese Speicheradresse addr zu.

void maccess(void* addr)
{
    asm volatile ("movq (%0), %%rax\n"
    :
    : "c" (addr)
    : "rax");
}

flush verdrängt die Speicheradresse addr aus dem Cache.

void flush(void* addr)
{
    asm volatile ("clflush 0(%0)\n"
    :
    : "c" (addr)
    : "rax");
}

Block 1: Messen der Timing-Differenzen + Threshold bestimmen

Tips
  • am besten kompiliert man ohne Compileroptimierungen (-O0):
      gcc -O0 cache_test.c primitive.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

sharedlib.c sieht so aus:

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:

# sharedlib kompilieren
gcc -fPIC -shared -O0 -o libsharedlib.so sharedlib.c
# sharedlib bei Sender und Empfänger linken
gcc -O0 sender.c primitive.c -L. -lsharedlib -o sender
gcc -O0 empfaenger.c primitive.c -L. -lsharedlib -o empfaenger
# ggfs. muss man noch den PATH exportieren
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

dann kann man function verwenden:

# in e.g. sender.c
maccess((void *) function)
...
Tips
  • taskset zum pinnen der Programme auf einen bestimmten CPU-Kern.
  • 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.
  • 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 das Flushen übernehmen)
  • (Der Sender kann im gesamten Zeitabschnitt Flushen oder Laden, nicht nur einmal)

Block 3: Pakete über den Cache als Medium

  • Kann man irgendwie mehr als ein Bit gleichzeitig senden? ;)

Muster: ./src/sender.c und ./src/receiver.c