Lascia che ti descriva prima cosa voglio fare, poi cosa riesco a fare e infine il mio problema.
Obiettivo:implementare un attacco flush+flush cache in C
Sto cercando di implementare in C l'attacco flush+flush cache (https://gruss.cc/files/flushflush.pdf). Fondamentalmente, sfrutta il fatto che due diversi processi potrebbero condividere le stesse pagine di memoria quando si utilizzano librerie condivise. Ciò si traduce in un "utilizzo condiviso" della cache.
Supponiamo di avere un processo vittima, che esegue continuamente e talvolta esegue una funzione func
importato da una libreria condivisa.
Parallelamente, assumiamo di avere un processo di spionaggio, in esecuzione sullo stesso computer della vittima, il cui obiettivo è spiare quando la vittima chiama func
. La spia ha anche accesso alla stessa libreria condivisa. Lo pseudo-codice del processo spia è il seguente:
i=0;
for (i = 0; i < trace_length ; i++)
{
trace[i] = flush_time( address of function "func");
i++;
}
dove flush_time(
<address>
)
è una funzione che restituisce il tempo impiegato dalla CPU per svuotare la memoria indicata da address
da tutti i livelli di cache. Sui processori Intel, questo può essere ottenuto tramite l'istruzione di montaggio clflush
. Si può osservare che l'esecuzione di clflush
è più veloce quando l'indirizzo non è presente nella cache. Di conseguenza, i tempi necessari per svuotare un indirizzo di memoria possono essere tradotti direttamente in sua presenza (o meno) all'interno della cache.
Il processo spia restituisce un vettore di traccia che contiene i risultati flush_time nel tempo. Dall'osservazione precedente, questa traccia mostrerà tempi più elevati quando la vittima chiama anche la funzione func
. La spia quindi detrarrà quando la vittima chiama func
.
Cosa riesco a fare:far funzionare l'attacco contro la libreria condivisa GSL
Ho implementato il suddetto attacco, dove la libreria condivisa è la GSL. Arbitrariamente, ho scelto gsl_stats_mean
(definito in gsl_statistics_double
) per fungere da funzione func
Sono disposto a spiare.
In tal caso, lo spionaggio funziona perfettamente poiché posso vedere chiaramente una differenza di tempo quando il programma vittima effettua chiamate a gsl_stats_mean
Il mio problema:l'attacco non funziona su una libreria condivisa fatta in casa
Ora voglio creare la mia libreria condivisa e usarla per il test spia/vittima. Assumiamo .
denota la cartella in cui il mio spy.c
e victim.c
i file sono. Ho creato due file myl.c
e myl.h
in una cartella ./src/myl
, che contengono rispettivamente la descrizione di func
ed è dichiarazione. Come in precedenza, l'obiettivo della mia spia è rilevare l'utilizzo di func
dalla vittima.
Entrambi spy.c
e victim.c
contengono la riga di inclusione:
#include "src/myl/myl.h"
La creazione della libreria condivisa avviene tramite i seguenti comandi:
gcc -c -fPIC src/myl/myl.c -o bin/shared/myl.o #creation of object in ./bin/shared
gcc -shared bin/shared/myl.o -o bin/shared/libmyl.so #creation of the shared library in ./bin/shared
gcc -c spy.c -o spy.o #creation of spy's process object file
gcc -c victim.c -o victim.o #creation of victim's process object file
gcc spy.o -Lbin/shared -lmyl -o spy #creation of spy's executable
gcc victim.o -Lbin/shared -lmyl -o victim #creation of victim's executable
Quindi lancio la mia vittima e spio usando le seguenti righe:
LD_LIBRARY_PATH=$(pwd)/bin/shared ./victim
LD_LIBRARY_PATH=$(pwd)/bin/shared ./spy
Tuttavia, a differenza del caso in cui stavo usando la funzione GSL, non riesco più a vedere alcuna attività nella cache. Immagino che questo significhi che i miei processi spia e vittima non condividono la stessa pagina di memoria per la mia libreria condivisa (mentre, tuttavia, era il caso quando si utilizzava il GSL). Nota che quando si compila in questo modo, lo spionaggio funziona ancora quando si prende di mira una funzione GSL.
La mia domanda principale è la seguente:come garantire che una libreria condivisa compilata in casa abbia il paging della memoria condivisa quando viene eseguita da più processi contemporaneamente? Sembra essere il caso delle librerie "corrette" che ho installato, come GSL, gmp, librerie native ecc…. Ma non per quello che ho fatto io.
Grazie in anticipo e mi scuso se la risposta è semplice.
EDIT:output di LD_DEBUG=libs
e files
sia per la spia che per la vittima.
NOTA:la vittima si chiama pg2
e spia si chiama pg1
(mi dispiace per quello)
Innanzitutto, le librerie per la vittima, seguite dai file per la vittima (pg2
). Quindi, le librerie per la spia, seguite dai file per la spia (pg1
):
LD_DEBUG=libs LD_LIBRARY_PATH=$(pwd)/bin/shared ./pg2
31714: find library=libmyl.so [0]; searching
31714: search path=/home/romain/Documents/work/test/page_sharing/bin/shared/tls/x86_64:/home/romain/Documents/work/test/page_sharing/bin/shared/tls:/home/romain/Documents/work/test/page_sharing/bin/shared/x86_64:/home/romain/Documents/work/test/page_sharing/bin/shared (LD_LIBRARY_PATH)
31714: trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/tls/x86_64/libmyl.so
31714: trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/tls/libmyl.so
31714: trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/x86_64/libmyl.so
31714: trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/libmyl.so
31714:
31714: find library=libc.so.6 [0]; searching
31714: search path=/home/romain/Documents/work/test/page_sharing/bin/shared (LD_LIBRARY_PATH)
31714: trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/libc.so.6
31714: search cache=/etc/ld.so.cache
31714: trying file=/lib/x86_64-linux-gnu/libc.so.6
31714:
31714:
31714: calling init: /lib/x86_64-linux-gnu/libc.so.6
31714:
31714:
31714: calling init: /home/romain/Documents/work/test/page_sharing/bin/shared/libmyl.so
31714:
31714:
31714: initialize program: ./pg2
31714:
31714:
31714: transferring control: ./pg2
31714:
LD_DEBUG=files LD_LIBRARY_PATH=$(pwd)/bin/shared ./pg2
31901:
31901: file=libmyl.so [0]; needed by ./pg2 [0]
31901: file=libmyl.so [0]; generating link map
31901: dynamic: 0x00007f5a3b34be48 base: 0x00007f5a3b14b000 size: 0x0000000000201028
31901: entry: 0x00007f5a3b14b580 phdr: 0x00007f5a3b14b040 phnum: 7
31901:
31901:
31901: file=libc.so.6 [0]; needed by ./pg2 [0]
31901: file=libc.so.6 [0]; generating link map
31901: dynamic: 0x00007f5a3b144ba0 base: 0x00007f5a3ad81000 size: 0x00000000003c99a0
31901: entry: 0x00007f5a3ada1950 phdr: 0x00007f5a3ad81040 phnum: 10
31901:
31901:
31901: calling init: /lib/x86_64-linux-gnu/libc.so.6
31901:
31901:
31901: calling init: /home/romain/Documents/work/test/page_sharing/bin/shared/libmyl.so
31901:
31901:
31901: initialize program: ./pg2
31901:
31901:
31901: transferring control: ./pg2
31901:
LD_DEBUG=libs LD_LIBRARY_PATH=$(pwd)/bin/shared ./pg1
31938: find library=libmyl.so [0]; searching
31938: search path=/home/romain/Documents/work/test/page_sharing/bin/shared/tls/x86_64:/home/romain/Documents/work/test/page_sharing/bin/shared/tls:/home/romain/Documents/work/test/page_sharing/bin/shared/x86_64:/home/romain/Documents/work/test/page_sharing/bin/shared (LD_LIBRARY_PATH)
31938: trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/tls/x86_64/libmyl.so
31938: trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/tls/libmyl.so
31938: trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/x86_64/libmyl.so
31938: trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/libmyl.so
31938:
31938: find library=libgsl.so.23 [0]; searching
31938: search path=/home/romain/Documents/work/test/page_sharing/bin/shared (LD_LIBRARY_PATH)
31938: trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/libgsl.so.23
31938: search cache=/etc/ld.so.cache
31938: trying file=/usr/local/lib/libgsl.so.23
31938:
31938: find library=libgslcblas.so.0 [0]; searching
31938: search path=/home/romain/Documents/work/test/page_sharing/bin/shared (LD_LIBRARY_PATH)
31938: trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/libgslcblas.so.0
31938: search cache=/etc/ld.so.cache
31938: trying file=/usr/local/lib/libgslcblas.so.0
31938:
31938: find library=libc.so.6 [0]; searching
31938: search path=/home/romain/Documents/work/test/page_sharing/bin/shared (LD_LIBRARY_PATH)
31938: trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/libc.so.6
31938: search cache=/etc/ld.so.cache
31938: trying file=/lib/x86_64-linux-gnu/libc.so.6
31938:
31938: find library=libm.so.6 [0]; searching
31938: search path=/home/romain/Documents/work/test/page_sharing/bin/shared (LD_LIBRARY_PATH)
31938: trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/libm.so.6
31938: search cache=/etc/ld.so.cache
31938: trying file=/lib/x86_64-linux-gnu/libm.so.6
31938:
31938:
31938: calling init: /lib/x86_64-linux-gnu/libc.so.6
31938:
31938:
31938: calling init: /lib/x86_64-linux-gnu/libm.so.6
31938:
31938:
31938: calling init: /usr/local/lib/libgslcblas.so.0
31938:
31938:
31938: calling init: /usr/local/lib/libgsl.so.23
31938:
31938:
31938: calling init: /home/romain/Documents/work/test/page_sharing/bin/shared/libmyl.so
31938:
31938:
31938: initialize program: ./pg1
31938:
31938:
31938: transferring control: ./pg1
31938:
0: 322 # just some output of my spying program
1: 323 # just some output of my spying program
31938:
31938: calling fini: ./pg1 [0]
31938:
31938:
31938: calling fini: /home/romain/Documents/work/test/page_sharing/bin/shared/libmyl.so [0]
31938:
31938:
31938: calling fini: /usr/local/lib/libgsl.so.23 [0]
31938:
31938:
31938: calling fini: /usr/local/lib/libgslcblas.so.0 [0]
31938:
31938:
31938: calling fini: /lib/x86_64-linux-gnu/libm.so.6 [0]
31938:
LD_DEBUG=files LD_LIBRARY_PATH=$(pwd)/bin/shared ./pg1
31940:
31940: file=libmyl.so [0]; needed by ./pg1 [0]
31940: file=libmyl.so [0]; generating link map
31940: dynamic: 0x00007fb3d8794e48 base: 0x00007fb3d8594000 size: 0x0000000000201028
31940: entry: 0x00007fb3d8594580 phdr: 0x00007fb3d8594040 phnum: 7
31940:
31940:
31940: file=libgsl.so.23 [0]; needed by ./pg1 [0]
31940: file=libgsl.so.23 [0]; generating link map
31940: dynamic: 0x00007fb3d8582ac8 base: 0x00007fb3d8126000 size: 0x000000000046da60
31940: entry: 0x00007fb3d8180e30 phdr: 0x00007fb3d8126040 phnum: 7
31940:
31940:
31940: file=libgslcblas.so.0 [0]; needed by ./pg1 [0]
31940: file=libgslcblas.so.0 [0]; generating link map
31940: dynamic: 0x00007fb3d8124df0 base: 0x00007fb3d7ee8000 size: 0x000000000023d050
31940: entry: 0x00007fb3d7eea120 phdr: 0x00007fb3d7ee8040 phnum: 7
31940:
31940:
31940: file=libc.so.6 [0]; needed by ./pg1 [0]
31940: file=libc.so.6 [0]; generating link map
31940: dynamic: 0x00007fb3d7ee1ba0 base: 0x00007fb3d7b1e000 size: 0x00000000003c99a0
31940: entry: 0x00007fb3d7b3e950 phdr: 0x00007fb3d7b1e040 phnum: 10
31940:
31940:
31940: file=libm.so.6 [0]; needed by /usr/local/lib/libgsl.so.23 [0]
31940: file=libm.so.6 [0]; generating link map
31940: dynamic: 0x00007fb3d7b1cd88 base: 0x00007fb3d7815000 size: 0x00000000003080f8
31940: entry: 0x00007fb3d781a600 phdr: 0x00007fb3d7815040 phnum: 7
31940:
31940:
31940: calling init: /lib/x86_64-linux-gnu/libc.so.6
31940:
31940:
31940: calling init: /lib/x86_64-linux-gnu/libm.so.6
31940:
31940:
31940: calling init: /usr/local/lib/libgslcblas.so.0
31940:
31940:
31940: calling init: /usr/local/lib/libgsl.so.23
31940:
31940:
31940: calling init: /home/romain/Documents/work/test/page_sharing/bin/shared/libmyl.so
31940:
31940:
31940: initialize program: ./pg1
31940:
31940:
31940: transferring control: ./pg1
31940:
0: 325 # just some output of my spying program
1: 327 # just some output of my spying program
31940:
31940: calling fini: ./pg1 [0]
31940:
31940:
31940: calling fini: /home/romain/Documents/work/test/page_sharing/bin/shared/libmyl.so [0]
31940:
31940:
31940: calling fini: /usr/local/lib/libgsl.so.23 [0]
31940:
31940:
31940: calling fini: /usr/local/lib/libgslcblas.so.0 [0]
31940:
31940:
31940: calling fini: /lib/x86_64-linux-gnu/libm.so.6 [0]
31940:
Risposta accettata:
Dal momento che l'output di debug da ld
il linker/caricatore dinamico conferma che sia la victim
e spy
i programmi caricano il file di input corretto, il passaggio successivo sarebbe verificare se il kernel ha effettivamente impostato le pagine fisiche in cui libmyl.so
viene caricato in memoria per essere condiviso tra la victim
e spy
.
In Linux questo è possibile verificare dalla versione del kernel 2.6.25 tramite la pagemap
interfaccia nel kernel che consente ai programmi dello spazio utente di esaminare le tabelle delle pagine e le relative informazioni leggendo i file in /proc
.
La procedura generale per l'utilizzo di pagemap per scoprire se due processi condividono la memoria è la seguente:
- Leggi
/proc/<pid>/maps
per entrambi i processi per determinare quali parti dello spazio di memoria sono mappate a quali oggetti. - Seleziona le mappe che ti interessano, in questo caso le pagine a cui
libmyl.so
è mappato. - Apri
/proc/<pid>/pagemap
. Lapagemap
è costituito da descrittori di pagemap a 64 bit, uno per pagina. La mappatura tra l'indirizzo della pagina e l'indirizzo dei descrittori nellapagemap
è indirizzo della pagina/dimensione della pagina * dimensione del descrittore . Cerca i descrittori delle pagine che vorresti esaminare. - Leggi un descrittore a 64 bit come un intero senza segno per ogni pagina dalla
pagemap
. - Confronta il numero di frame della pagina (PFN) nei bit 0-54 del descrittore di pagina tra
libmyl.so
pagine pervictim
espy
. Se le PFN corrispondono, i due processi condividono le stesse pagine fisiche.
Il codice di esempio seguente illustra come la pagemap
è possibile accedere e stampare dall'interno del processo. Usa dl_iterate_phdr()
per determinare l'indirizzo virtuale di ciascuna libreria condivisa caricata nello spazio di memoria dei processi, quindi cerca e stampa la corrispondente pagemap
da /proc/<pid>/pagemap
.
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <inttypes.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <link.h>
#include <errno.h>
#include <error.h>
#define E_CANNOT_OPEN_PAGEMAP 1
#define E_CANNOT_READ_PAGEMAP 2
typedef struct __attribute__ ((__packed__)) {
union {
uint64_t pmd;
uint64_t page_frame_number : 55;
struct {
uint64_t swap_type: 5;
uint64_t swap_offset: 50;
uint64_t soft_dirty: 1;
uint64_t exclusive: 1;
uint64_t zero: 4;
uint64_t file_page: 1;
uint64_t swapped: 1;
uint64_t present: 1;
};
};
} pmd_t;
static int print_pagemap_for_phdr(struct dl_phdr_info *info,
size_t size, void *data)
{
struct stat statbuf;
size_t pagesize = sysconf(_SC_PAGESIZE);
char pagemap_path[BUFSIZ];
int pagemap;
uint64_t start_addr, end_addr;
if (!strcmp(info->dlpi_name, "")) {
return 0;
}
stat(info->dlpi_name, &statbuf);
start_addr = info->dlpi_addr;
end_addr = (info->dlpi_addr + statbuf.st_size + pagesize) & ~(pagesize-1);
printf("n%10p-%10p %snn",
(void *)start_addr,
(void *)end_addr,
info->dlpi_name);
snprintf(pagemap_path, sizeof pagemap_path, "/proc/%d/pagemap", getpid());
if ((pagemap = open(pagemap_path, O_RDONLY)) < 0) {
error(E_CANNOT_OPEN_PAGEMAP, errno,
"cannot open pagemap: %s", pagemap_path);
}
printf("%10s %8s %7s %5s %8s %7s %7sn",
"", "", "soft-", "", "file /", "", "");
printf("%10s %8s %7s %5s %11s %7s %7sn",
"address", "pfn", "dirty", "excl.",
"shared anon", "swapped", "present");
for (unsigned long i = start_addr; i < end_addr; i += pagesize) {
pmd_t pmd;
if (pread(pagemap, &pmd.pmd, sizeof pmd.pmd, (i / pagesize) * sizeof pmd) != sizeof pmd) {
error(E_CANNOT_READ_PAGEMAP, errno,
"cannot read pagemap: %s", pagemap_path);
}
if (pmd.pmd != 0) {
printf("0x%10" PRIx64 " %06" PRIx64 " %3d %5d %8d %9d %7dn", i,
(unsigned long)pmd.page_frame_number,
pmd.soft_dirty,
pmd.exclusive,
pmd.file_page,
pmd.swapped,
pmd.present);
}
}
close(pagemap);
return 0;
}
int main()
{
dl_iterate_phdr(print_pagemap_for_phdr, NULL);
exit(EXIT_SUCCESS);
}
L'output del programma dovrebbe essere simile al seguente:
$ sudo ./a.out
0x7f935408d000-0x7f9354256000 /lib/x86_64-linux-gnu/libc.so.6
soft- file /
address pfn dirty excl. shared anon swapped present
0x7f935408d000 424416 1 0 1 0 1
0x7f935408e000 424417 1 0 1 0 1
0x7f935408f000 422878 1 0 1 0 1
0x7f9354090000 422879 1 0 1 0 1
0x7f9354091000 43e879 1 0 1 0 1
0x7f9354092000 43e87a 1 0 1 0 1
0x7f9354093000 424790 1 0 1 0 1
...
dove:
address
è l'indirizzo virtuale della paginapfn
è il numero del frame della pagina delle paginesoft-dirty
indica se il bit soft-dirty è impostato nelle pagine Page Table Entry (PTE).excl.
indica se la pagina è mappata esclusivamente (cioè la pagina è mappata solo per questo processo).file / shared anon
indica se la pagina è un file pagine o una pagina anonima condivisa.swapped
indica se la pagina è attualmente scambiata (implicapresent
è zero).present
indica se la pagina è attualmente presente nel set residente dei processi (implicaswapped
è zero).
(Nota:eseguo il programma di esempio con sudo
come da Linux 4.0 solo utenti con il CAP_SYS_ADMIN
capacità può ottenere PFN da /proc/<pid>/pagemap
. A partire da Linux 4.2 il campo PFN viene azzerato se l'utente non possiede CAP_SYS_ADMIN
. Il motivo di questa modifica è rendere più difficile lo sfruttamento di un'altra vulnerabilità correlata alla memoria, l'attacco Rowhammer, utilizzando le informazioni sulla mappatura da virtuale a fisica esposte dalle PFN.)
Se esegui più volte il programma di esempio, dovresti notare che l'indirizzo virtuale della pagina dovrebbe cambiare (a causa di ASLR), ma il PFN per le librerie condivise che sono utilizzate da altri processi dovrebbe rimanere lo stesso.
Se i PFN per libmyl.so
corrispondenza tra la victim
e spy
programma, inizierei a cercare un motivo per cui l'attacco non riesce nel codice di attacco stesso. Se i PFN non corrispondono, i bit aggiuntivi potrebbero fornire qualche suggerimento sul motivo per cui le pagine non sono impostate per essere condivise. La pagemap
i bit indicano quanto segue:
present file exclusive state:
0 0 0 non-present
1 1 0 file page mapped somewhere else
1 1 1 file page mapped only here
1 0 0 anonymous non-copy-on-write page (shared with parent/child)
1 0 1 anonymous copy-on-write page (or never forked)
Copia in scrittura delle pagine in (MAP_FILE | MAP_PRIVATE)
le aree sono anonime in questo contesto.
Bonus: Per ottenere il numero di volte una pagina è stata mappata, la PFN può essere utilizzata per cercare la pagina in /proc/kpagecount
. Questo file contiene un conteggio a 64 bit del numero di volte in cui ciascuna pagina è stata mappata, indicizzata da PFN.