Dovolte mi nejprve popsat, co chci dělat, poté, co se mi podaří udělat, a nakonec můj problém.
Cíl:implementace flush+flush cache útoku v C
Snažím se implementovat v C útok flush+flush cache (https://gruss.cc/files/flushflush.pdf). V podstatě využívá skutečnosti, že dva různé procesy mohou sdílet stejné paměťové stránky při používání sdílených knihoven. Výsledkem je „sdílené využití“ mezipaměti.
Předpokládejme, že máme proces oběti, který neustále běží a někdy provádí funkci func
importované ze sdílené knihovny.
Současně předpokládáme, že máme špionážní proces běžící na stejném počítači jako oběť, jehož cílem je špehovat, když oběť zavolá func
. Špión má také přístup do stejné sdílené knihovny. Pseudokód špionážního procesu je následující:
i=0;
for (i = 0; i < trace_length ; i++)
{
trace[i] = flush_time( address of function "func");
i++;
}
kde flush_time(
<address>
)
je funkce, která vrací čas, který CPU potřebuje k vyprázdnění paměti označené address
ze všech úrovní mezipaměti. Na procesoru Intel toho lze dosáhnout pomocí montážní instrukce clflush
. Lze pozorovat, že provedení clflush
je rychlejší, když adresa není přítomna v mezipaměti. V důsledku toho může být načasování potřebné k vyprázdnění adresy paměti přímo přeloženo v její přítomnosti (nebo ne) uvnitř mezipaměti.
Proces špionáže vrací trasovací vektor, který obsahuje výsledky flush_time v průběhu času. Z předchozího pozorování bude tato stopa vykazovat vyšší časování, když oběť také zavolá funkci func
. Špión tak odečte, když oběť volá func
.
Co se mi podařilo:zajistit, aby útok fungoval proti sdílené knihovně GSL
Implementoval jsem výše zmíněný útok, kde sdílenou knihovnou je GSL. Svévolně jsem zvolil gsl_stats_mean
(definováno v gsl_statistics_double
), aby fungoval jako funkce func
Jsem ochoten špehovat.
V tom případě špehování funguje perfektně, protože jasně vidím rozdíl v načasování, když program oběti volá gsl_stats_mean
Můj problém:útok nefunguje na domácí sdílenou knihovnu
Nyní si chci vytvořit vlastní sdílenou knihovnu a používat ji pro test špionáže/oběti. Předpokládejme .
označuje složku, ve které je můj spy.c
a victim.c
soubory jsou. Vytvořil jsem dva soubory myl.c
a myl.h
ve složce ./src/myl
, které obsahují popis func
a je to prohlášení. Stejně jako dříve je cílem mého špiona odhalit použití func
od oběti.
Oba spy.c
a victim.c
obsahovat řádek pro začlenění:
#include "src/myl/myl.h"
Vytvoření sdílené knihovny se provádí pomocí následujících příkazů:
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
Poté spustím svou oběť a špionáž pomocí následujících řádků:
LD_LIBRARY_PATH=$(pwd)/bin/shared ./victim
LD_LIBRARY_PATH=$(pwd)/bin/shared ./spy
Na rozdíl od případu, kdy jsem používal funkci GSL, však již na mezipaměti nevidím žádnou aktivitu. Myslím, že to znamená, že moje špionážní a oběťové procesy nesdílejí stejnou paměťovou stránku pro mou sdílenou knihovnu (i když tomu tak bylo při použití GSL). Všimněte si, že při kompilaci tímto způsobem špionáž stále funguje při cílení na funkci GSL.
Moje hlavní otázka je následující:jak zajistit, že domácí kompilovaná sdílená knihovna bude mít sdílené stránkování paměti, když ji spouští několik procesů současně? Zdá se, že je to případ „správné“ knihovny, kterou jsem nainstaloval, jako je GSL, gmp, nativní knihovny atd…. Ale ne pro ten, který jsem si sám vyrobil.
Předem děkuji a omlouvám se, pokud je odpověď jasná.
EDIT:výstup LD_DEBUG=libs
a files
pro špiona i oběť.
POZNÁMKA:oběť se nazývá pg2
a spy se nazývá pg1
(omlouvám se za to)
Nejprve libs pro oběť, poté soubory pro oběť (pg2
). Poté libs pro špióna, následované soubory pro špióna (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:
Přijatá odpověď:
Od výstupu ladění z ld
dynamický linker/loader potvrzuje, že obě victim
a spy
programy načítají správný vstupní soubor, dalším krokem by bylo ověřit, zda jádro skutečně nastavilo fyzické stránky, kde libmyl.so
je načten do paměti, aby byl sdílen mezi victim
a spy
.
V Linuxu je to možné ověřit od verze jádra 2.6.25 pomocí pagemap
rozhraní v jádře, které umožňuje programům v uživatelském prostoru zkoumat tabulky stránek a související informace čtením souborů v /proc
.
Obecný postup pro použití mapy stránek ke zjištění, zda dva procesy sdílejí paměť, vypadá takto:
- Přečtěte si
/proc/<pid>/maps
pro oba procesy určit, které části paměťového prostoru jsou mapovány na které objekty. - Vyberte mapy, které vás zajímají, v tomto případě stránky, na které
libmyl.so
je zmapován. - Otevřete
/proc/<pid>/pagemap
.pagemap
sestává z 64bitových deskriptorů mapy stránek, jeden na stránku. Mapování mezi adresou stránky a adresou deskriptorů vpagemap
je adresa stránky / velikost stránky * velikost deskriptoru . Hledejte deskriptory stránek, které byste chtěli prozkoumat. - Přečtěte 64bitový deskriptor jako celé číslo bez znaménka pro každou stránku z
pagemap
. - Porovnejte číslo rámce stránky (PFN) v bitech 0–54 deskriptoru stránky mezi
libmyl.so
stránky provictim
aspy
. Pokud se PFN shodují, oba procesy sdílejí stejné fyzické stránky.
Následující ukázkový kód ukazuje, jak pagemap
lze přistupovat a tisknout z tohoto procesu. Používá dl_iterate_phdr()
k určení virtuální adresy každé sdílené knihovny načtené do paměťového prostoru procesů, poté vyhledá a vytiskne odpovídající pagemap
z /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);
}
Výstup programu by měl vypadat podobně jako následující:
$ 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
...
kde:
address
je virtuální adresa stránkypfn
je číslo rámce stránkysoft-dirty
označuje, zda je na stránkách PTE (Page Table Entry) nastaven bit soft-dirty.excl.
označuje, zda je stránka výhradně mapována (tj. stránka je mapována pouze pro tento proces).file / shared anon
označuje, zda se jedná o stránky souboru nebo sdílené anonymní stránky.swapped
označuje, zda je stránka aktuálně prohozena (implikujepresent
je nula).present
označuje, zda je stránka aktuálně přítomna v rezidentní sadě procesů (implikujeswapped
je nula).
(Poznámka:Spouštím ukázkový program pomocí sudo
jako od Linuxu 4.0 pouze uživatelé s CAP_SYS_ADMIN
může získat PFN z /proc/<pid>/pagemap
. Počínaje Linuxem 4.2 je pole PFN vynulováno, pokud uživatel nemá CAP_SYS_ADMIN
. Důvodem této změny je ztížit zneužití další zranitelnosti související s pamětí, útoku Rowhammer, pomocí informací o mapování mezi virtuálními a fyzickými objekty vystavenými PFN.)
Pokud ukázkový program spustíte několikrát, měli byste si všimnout, že by se virtuální adresa stránky měla změnit (kvůli ASLR), ale PFN pro sdílené knihovny, které používají jiné procesy, by měla zůstat stejná.
Pokud PFN pro libmyl.so
shodu mezi victim
a spy
program, začal bych hledat důvod, proč útok selže v samotném útočném kódu. Pokud se PFN neshodují, další bity mohou napovědět, proč stránky nejsou nastaveny pro sdílení. pagemap
bity označují následující:
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)
Stránky kopírovat při zápisu v (MAP_FILE | MAP_PRIVATE)
oblasti jsou v tomto kontextu anonymní.
Bonus: Chcete-li získat početkrát stránka byla zmapována, PFN lze použít k vyhledání stránky v /proc/kpagecount
. Tento soubor obsahuje 64bitový počet, kolikrát byla každá stránka namapována, indexovaný PFN.