Permettez-moi d'abord de décrire ce que je veux faire, suivi de ce que j'arrive à faire, et enfin de mon problème.
Objectif :implémenter l'attaque flush+flush cache en C
J'essaie d'implémenter en C l'attaque de cache flush+flush (https://gruss.cc/files/flushflush.pdf). Fondamentalement, il exploite le fait que deux processus différents peuvent partager les mêmes pages mémoire lors de l'utilisation de bibliothèques partagées. Cela se traduit par une "utilisation partagée" du cache.
Supposons que nous ayons un processus victime, fonctionnant continuellement et exécutant parfois une fonction func
importé depuis une bibliothèque partagée.
En parallèle, nous supposons que nous avons un processus d'espionnage, s'exécutant sur le même ordinateur que la victime, dont le but est d'espionner lorsque la victime appelle func
. L'espion a également accès à la même bibliothèque partagée. Le pseudo-code du processus d'espionnage est le suivant :
i=0;
for (i = 0; i < trace_length ; i++)
{
trace[i] = flush_time( address of function "func");
i++;
}
où flush_time(
<address>
)
est une fonction qui renvoie le temps nécessaire au CPU pour vider la mémoire pointée par address
de tous les niveaux de cache. Sur le processeur Intel, cela peut être réalisé via l'instruction d'assemblage clflush
. On peut observer que l'exécution de clflush
est plus rapide lorsque l'adresse n'est pas présente dans le cache. En conséquence, le temps nécessaire pour vider une adresse mémoire peut être directement traduit en sa présence (ou non) à l'intérieur du cache.
Le processus espion renvoie un vecteur de trace qui contient les résultats flush_time au fil du temps. D'après l'observation précédente, cette trace présentera des délais plus élevés lorsque la victime appelle également la fonction func
. L'espion déduira donc lorsque la victime appelle func
.
Ce que j'ai réussi à faire :faire en sorte que l'attaque fonctionne contre la bibliothèque partagée GSL
J'ai implémenté l'attaque susmentionnée, où la bibliothèque partagée est le GSL. Arbitrairement, j'ai choisi gsl_stats_mean
(défini dans gsl_statistics_double
) pour agir comme la fonction func
Je suis prêt à espionner.
Dans ce cas, l'espionnage fonctionne parfaitement car je peux clairement voir une différence de temps lorsque le programme victime appelle gsl_stats_mean
Mon problème :l'attaque ne fonctionne pas sur une bibliothèque partagée maison
Je veux maintenant créer ma propre bibliothèque partagée et l'utiliser pour le test d'espionnage/victime. Supposons .
désigne le dossier dans lequel mon spy.c
et victim.c
les fichiers sont. J'ai créé deux fichiers myl.c
et myl.h
dans un dossier ./src/myl
, qui contiennent respectivement la description de func
et sa déclaration. Comme précédemment, le but de mon espion est de détecter l'utilisation de func
de la victime.
Les deux spy.c
et victim.c
contenir la ligne d'inclusion :
#include "src/myl/myl.h"
La création de la bibliothèque partagée se fait à l'aide des commandes suivantes :
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
Je lance alors ma victime et mon espion en utilisant les lignes suivantes :
LD_LIBRARY_PATH=$(pwd)/bin/shared ./victim
LD_LIBRARY_PATH=$(pwd)/bin/shared ./spy
Cependant, contrairement au cas où j'utilisais la fonction GSL, je ne vois plus aucune activité sur le cache. Je suppose que cela signifie que mes processus espion et victime ne partagent pas la même page mémoire pour ma bibliothèque partagée (alors que c'était le cas lors de l'utilisation du GSL). Notez qu'en compilant de cette manière, l'espionnage fonctionne toujours lors du ciblage d'une fonction GSL.
Ma question principale est la suivante :comment s'assurer qu'une bibliothèque partagée compilée maison aura la pagination mémoire partagée lorsqu'elle est exécutée par plusieurs processus en même temps ? Il semble que ce soit le cas pour les bibliothèques "correctes" que j'ai installées, telles que GSL, gmp, les bibliothèques natives, etc. Mais pas pour celui que j'ai fait moi-même.
Merci d'avance, et je m'excuse si la réponse est simple.
EDIT :sortie de LD_DEBUG=libs
et files
pour l'espion et la victime.
REMARQUE :la victime est appelée pg2
et l'espion s'appelle pg1
(désolé pour ça)
D'abord, les bibliothèques pour la victime, suivies des fichiers pour la victime (pg2
). Ensuite, les bibliothèques pour l'espion, suivies des fichiers pour l'espion (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:
Réponse acceptée :
Depuis la sortie de débogage de ld
l'éditeur de liens/chargeur dynamique confirme que la victim
et spy
programmes chargent le bon fichier d'entrée, l'étape suivante consisterait à vérifier si le noyau a réellement configuré les pages physiques où libmyl.so
est chargé en mémoire pour être partagé entre la victim
et spy
.
Sous Linux, il est possible de vérifier depuis la version 2.6.25 du noyau via le pagemap
interface dans le noyau qui permet aux programmes de l'espace utilisateur d'examiner les tables de pages et les informations associées en lisant les fichiers dans /proc
.
La procédure générale d'utilisation de pagemap pour savoir si deux processus partagent de la mémoire ressemble à ceci :
- Lire
/proc/<pid>/maps
pour les deux processus afin de déterminer quelles parties de l'espace mémoire sont mappées à quels objets. - Sélectionnez les cartes qui vous intéressent, en l'occurrence les pages vers lesquelles
libmyl.so
est mappé. - Ouvrir
/proc/<pid>/pagemap
. Lepagemap
se compose de descripteurs de pagemap 64 bits, un par page. Le mappage entre l'adresse de la page et l'adresse des descripteurs dans lepagemap
est adresse de la page / taille de la page * taille du descripteur . Recherchez les descripteurs des pages que vous souhaitez examiner. - Lire un descripteur 64 bits sous la forme d'un entier non signé pour chaque page du
pagemap
. - Comparez le numéro de cadre de page (PFN) dans les bits 0 à 54 du descripteur de page entre
libmyl.so
pages pourvictim
etspy
. Si les PFN correspondent, les deux processus partagent les mêmes pages physiques.
L'exemple de code suivant illustre comment le pagemap
peuvent être consultés et imprimés depuis le processus. Il utilise dl_iterate_phdr()
pour déterminer l'adresse virtuelle de chaque bibliothèque partagée chargée dans l'espace mémoire des processus, puis recherche et imprime le pagemap
correspondant depuis /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);
}
La sortie du programme devrait ressembler à ce qui suit :
$ 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
...
où :
address
est l'adresse virtuelle de la pagepfn
est le numéro de cadre de page des pagessoft-dirty
indique si le bit soft-dirty est défini dans les pages Page Table Entry (PTE).excl.
indique si la page est exclusivement mappée (c'est-à-dire que la page n'est mappée que pour ce processus).file / shared anon
indique si la page est un fichier pages ou une page anonyme partagée.swapped
indique si la page est actuellement permutée (impliquepresent
est zéro).present
indique si la page est actuellement présente dans l'ensemble résident des processus (impliqueswapped
est zéro).
(Remarque :j'exécute le programme d'exemple avec sudo
comme depuis Linux 4.0 uniquement les utilisateurs avec le CAP_SYS_ADMIN
la capacité peut obtenir des PFN à partir de /proc/<pid>/pagemap
. À partir de Linux 4.2, le champ PFN est mis à zéro si l'utilisateur n'a pas CAP_SYS_ADMIN
. La raison de ce changement est de rendre plus difficile l'exploitation d'une autre vulnérabilité liée à la mémoire, l'attaque Rowhammer, en utilisant les informations sur le mappage virtuel-physique exposées par les PFN.)
Si vous exécutez l'exemple de programme plusieurs fois, vous devriez remarquer que l'adresse virtuelle de la page devrait changer (en raison de l'ASLR), mais le PFN des bibliothèques partagées utilisées par d'autres processus devrait rester le même.
Si les PFN pour libmyl.so
correspondance entre la victim
et spy
programme, je commencerais à chercher une raison pour laquelle l'attaque échoue dans le code d'attaque lui-même. Si les PFN ne correspondent pas, les bits supplémentaires peuvent indiquer pourquoi les pages ne sont pas configurées pour être partagées. Le pagemap
les bits indiquent ce qui suit :
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)
Pages de copie sur écriture dans (MAP_FILE | MAP_PRIVATE)
les zones sont anonymes dans ce contexte.
Bonus : Pour obtenir le nombre de fois une page a été mappée, le PFN peut être utilisé pour rechercher la page dans /proc/kpagecount
. Ce fichier contient un décompte 64 bits du nombre de fois où chaque page est mappée, indexée par PFN.