GNU/Linux >> Znalost Linux >  >> Linux

Linux – Jak zajistit, aby sdílená knihovna měla své paměťové stránky sdílené několika procesy?

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:

  1. 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.
  2. Vyberte mapy, které vás zajímají, v tomto případě stránky, na které libmyl.so je zmapován.
  3. 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ů v pagemap je adresa stránky / velikost stránky * velikost deskriptoru . Hledejte deskriptory stránek, které byste chtěli prozkoumat.
  4. Přečtěte 64bitový deskriptor jako celé číslo bez znaménka pro každou stránku z pagemap .
  5. Porovnejte číslo rámce stránky (PFN) v bitech 0–54 deskriptoru stránky mezi libmyl.so stránky pro victim a spy . Pokud se PFN shodují, oba procesy sdílejí stejné fyzické stránky.
Související:Windows – Jak by Windows NEPOŠKOZIL souborový systém Linux, když kazí schémata oddílů?

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ánky
  • pfn je číslo rámce stránky
  • soft-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 (implikuje present je nula).
  • present označuje, zda je stránka aktuálně přítomna v rezidentní sadě procesů (implikuje swapped je nula).
Související:základní linux (1) Cheat Sheet

(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.


Linux
  1. Jak vymazat mezipaměť v Linuxu

  2. Jak zobrazím seznam funkcí, které exportuje sdílená knihovna Linuxu?

  3. Jak udělat verzování sdílené knihovny v Linuxu?

  1. Jak vypočítat využití CPU procesu a všech jeho podřízených procesů v Linuxu?

  2. Jak nastavit googleTest jako sdílenou knihovnu v Linuxu

  3. Jak může sdílená knihovna (.so) volat funkci, která je implementována v jejím načítacím programu?

  1. Jak zabít běžící procesy v Linuxu

  2. Úvod do sdílených knihoven Linuxu (Jak vytvořit sdílené knihovny)

  3. Jak vytvořit zpoždění sdílené knihovny načtené v systému Linux