Permítanme describir primero lo que quiero hacer, luego lo que logro hacer y finalmente mi problema.
Objetivo:implementar el ataque flush+flush cache en C
Estoy tratando de implementar en C el ataque de caché flush+flush (https://gruss.cc/files/flushflush.pdf). Básicamente, aprovecha el hecho de que dos procesos diferentes pueden compartir las mismas páginas de memoria cuando usan bibliotecas compartidas. Esto da como resultado un "uso compartido" del caché.
Supongamos que tenemos un proceso víctima, que se ejecuta continuamente y, a veces, ejecuta una función func
importado de una biblioteca compartida.
Paralelamente, asumimos que tenemos un proceso de espionaje, ejecutándose en la misma computadora que la víctima, cuyo objetivo es espiar cuando la víctima llama a func
. El espía también tiene acceso a la misma biblioteca compartida. El pseudocódigo del proceso de espionaje es el siguiente:
i=0;
for (i = 0; i < trace_length ; i++)
{
trace[i] = flush_time( address of function "func");
i++;
}
donde flush_time(
<address>
)
es una función que devuelve el tiempo que tarda la CPU en vaciar la memoria señalada por address
de todos los niveles de caché. En el procesador Intel, esto se puede lograr a través de la instrucción de ensamblaje clflush
. Se puede observar que la ejecución de clflush
es más rápido cuando la dirección no está presente en el caché. Como resultado, el tiempo requerido para vaciar una dirección de memoria puede traducirse directamente en su presencia (o no) dentro del caché.
El proceso de espionaje devuelve un vector de seguimiento que contiene los resultados de flush_time a lo largo del tiempo. De la observación anterior, este rastro exhibirá tiempos más altos cuando la víctima también llame a la función func
. El espía deducirá así cuando la víctima está llamando a func
.
Lo que logro hacer:hacer que el ataque funcione contra la biblioteca compartida de GSL
Implementé el ataque antes mencionado, donde la biblioteca compartida es la GSL. Arbitrariamente, elegí gsl_stats_mean
(definido en gsl_statistics_double
) para actuar como la función func
Estoy dispuesto a espiar.
En ese caso, el espionaje funciona perfectamente ya que claramente puedo ver una diferencia de tiempo cuando el programa de la víctima hace llamadas a gsl_stats_mean
Mi problema:el ataque no funciona en una biblioteca compartida casera
Ahora quiero crear mi propia biblioteca compartida y usarla para la prueba de espía/víctima. Supongamos .
denota la carpeta en la que mi spy.c
y victim.c
los archivos son. Creé dos archivos myl.c
y myl.h
en una carpeta ./src/myl
, que contienen respectivamente la descripción de func
y su declaración. Como antes, el objetivo de mi espía es detectar el uso de func
de la víctima.
Ambos spy.c
y victim.c
contener la línea de inclusión:
#include "src/myl/myl.h"
La creación de la biblioteca compartida se realiza mediante los siguientes comandos:
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
Luego lanzo a mi víctima y espío usando las siguientes líneas:
LD_LIBRARY_PATH=$(pwd)/bin/shared ./victim
LD_LIBRARY_PATH=$(pwd)/bin/shared ./spy
Sin embargo, a diferencia del caso en el que estaba usando la función GSL, ya no puedo ver ninguna actividad en el caché. Supongo que esto significa que mis procesos de espía y víctima no comparten la misma página de memoria para mi biblioteca compartida (aunque, sin embargo, era el caso cuando usaba la GSL). Tenga en cuenta que al compilar de esta manera, el espionaje aún funciona cuando se dirige a una función GSL.
Mi pregunta principal es la siguiente:¿cómo garantizar que una biblioteca compartida compilada casera tenga la paginación de memoria compartida cuando varios procesos la ejecutan al mismo tiempo? Parece ser el caso de la biblioteca "adecuada" que instalé, como GSL, gmp, bibliotecas nativas, etc. Pero no por el que hice yo mismo.
Gracias de antemano y pido disculpas si la respuesta es sencilla.
EDITAR:salida de LD_DEBUG=libs
y files
tanto para el espía como para la víctima.
NOTA:la víctima se llama pg2
y el espía se llama pg1
(perdón por eso)
Primero, libs para la víctima, seguido de archivos para la víctima (pg2
). Luego, libs para el espía, seguido de archivos para el espía (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:
Respuesta aceptada:
Dado que la salida de depuración del ld
El enlazador/cargador dinámico confirma que tanto la victim
y spy
programas cargan el archivo de entrada correcto, el siguiente paso sería verificar si el kernel realmente ha configurado las páginas físicas donde libmyl.so
se carga en la memoria para ser compartida entre la victim
y spy
.
En Linux esto es posible de verificar desde la versión 2.6.25 del kernel a través del pagemap
interfaz en el kernel que permite que los programas del espacio de usuario examinen las tablas de páginas y la información relacionada mediante la lectura de archivos en /proc
.
El procedimiento general para usar el mapa de páginas para averiguar si dos procesos comparten memoria es así:
- Leer
/proc/<pid>/maps
para que ambos procesos determinen qué partes del espacio de memoria se asignan a qué objetos. - Seleccione los mapas que le interesen, en este caso las páginas a las que
libmyl.so
está mapeado. - Abrir
/proc/<pid>/pagemap
. Elpagemap
consta de descriptores de mapas de página de 64 bits, uno por página. El mapeo entre la dirección de la página y la dirección de sus descriptores en elpagemap
es dirección de la página/tamaño de la página * tamaño del descriptor . Busque los descriptores de las páginas que le gustaría examinar. - Leer un descriptor de 64 bits como un número entero sin signo para cada página del
pagemap
. - Compare el número de marco de página (PFN) en los bits 0-54 del descriptor de página entre
libmyl.so
páginas paravictim
yspy
. Si los PFN coinciden, los dos procesos comparten las mismas páginas físicas.
El siguiente código de ejemplo ilustra cómo el pagemap
se puede acceder e imprimir desde dentro del proceso. Utiliza dl_iterate_phdr()
para determinar la dirección virtual de cada biblioteca compartida cargada en el espacio de memoria de los procesos, luego busca e imprime el pagemap
correspondiente de /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 salida del programa debe ser similar a la siguiente:
$ 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
...
donde:
address
es la dirección virtual de la páginapfn
es el número de marco de página de las páginassoft-dirty
indica si el bit soft-dirty está configurado en las páginas Entrada de tabla de páginas (PTE).excl.
indica si la página está asignada exclusivamente (es decir, la página solo está asignada para este proceso).file / shared anon
indica si la página es una página de archivo o una página anónima compartida.swapped
indica si la página está actualmente intercambiada (implicapresent
) es cero).present
indica si la página está actualmente presente en el conjunto residente de procesos (implicaswapped
es cero).
(Nota:ejecuto el programa de ejemplo con sudo
como desde Linux 4.0 solo usuarios con CAP_SYS_ADMIN
la capacidad puede obtener PFN de /proc/<pid>/pagemap
. A partir de Linux 4.2, el campo PFN se pone a cero si el usuario no tiene CAP_SYS_ADMIN
. El motivo de este cambio es dificultar la explotación de otra vulnerabilidad relacionada con la memoria, el ataque Rowhammer, utilizando la información de la asignación virtual a física expuesta por los PFN.)
Si ejecuta el programa de ejemplo varias veces, debe notar que la dirección virtual de la página debe cambiar (debido a ASLR), pero el PFN para las bibliotecas compartidas que están en uso por otros procesos debe permanecer igual.
Si los PFN para libmyl.so
coincidencia entre la victim
y spy
programa, comenzaría a buscar una razón por la cual el ataque falla en el propio código de ataque. Si los PFN no coinciden, los bits adicionales pueden dar alguna pista de por qué las páginas no están configuradas para compartirse. El pagemap
los bits indican lo siguiente:
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)
Copiar en páginas de escritura en (MAP_FILE | MAP_PRIVATE)
las áreas son anónimas en este contexto.
Bonificación: Para obtener el número de veces una página ha sido mapeada, el PFN se puede usar para buscar la página en /proc/kpagecount
. Este archivo contiene un recuento de 64 bits del número de veces que se mapea cada página, indexado por PFN.