Como a saída de depuração do carregador / carregador dinâmico ld
confirma que ambos os programas victim
e spy
carregam o arquivo de entrada correto, a próxima etapa seria verificar se o kernel realmente configurou o arquivo físico. páginas em que libmyl.so
é carregado na memória para ser compartilhado entre victim
e spy
.
No Linux, isso é possível verificar desde a versão do kernel 2.6.25 através do pagemap
interface no kernel que permite que os programas userspace examinem as tabelas de páginas e informações relacionadas lendo arquivos em /proc
.
O procedimento geral para usar o pagemap para descobrir se dois processos compartilham memória é o seguinte:
- Leia
/proc/<pid>/maps
para ambos os processos para determinar quais partes do espaço da memória são mapeadas para quais objetos. - Selecione os mapas em que você está interessado, neste caso, as páginas para as quais
libmyl.so
está mapeado. - Abra
/proc/<pid>/pagemap
. Opagemap
consiste em descritores de mapa de página de 64 bits, um por página. O mapeamento entre o endereço da página e o endereço dos descritores nopagemap
é endereço da página / tamanho da página * tamanho do descritor . Procure os descritores das páginas que você gostaria de examinar. - Leia um descritor de 64 bits como um inteiro não assinado para cada página do
pagemap
. - Compare o número do quadro de página (PFN) nos bits 0 a 54 do descritor de página entre as páginas
libmyl.so
paravictim
espy
. Se as PFNs corresponderem, os dois processos compartilharão as mesmas páginas físicas.
O código de exemplo a seguir ilustra como o pagemap
pode ser acessado e impresso a partir do processo. Ele usa dl_iterate_phdr()
para determinar o endereço virtual de cada biblioteca compartilhada carregada em o espaço de memória do processo, depois procura e imprime o pagemap
correspondente 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 %s\n\n",
(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 %7s\n",
"", "", "soft-", "", "file /", "", "");
printf("%10s %8s %7s %5s %11s %7s %7s\n",
"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 %7d\n", 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);
}
A saída do programa deve ser semelhante à seguinte:
$ 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
...
onde:
-
address
é o endereço virtual da página -
pfn
é o número do quadro da página de páginas -
soft-dirty
indica que o bit suja e macia está definido nas páginas Entrada da tabela de páginas (PTE). -
excl.
indica se a página está exclusivamente mapeada (ou seja, a página é mapeada apenas para esse processo). -
file / shared anon
indica se a página é uma página de arquivos ou uma página anônima compartilhada. -
swapped
indica se a página está atualmente trocada (implicapresent
é zero). -
present
indica se a página está atualmente presente no conjunto residente de processos (implicaswapped
é zero).
(Nota: Eu executo o programa de exemplo com sudo
, já que desde o Linux 4.0 somente usuários com o recurso CAP_SYS_ADMIN
podem obter PFNs de /proc/<pid>/pagemap
. A partir do Linux 4.2 o campo PFN é zerado se o usuário não tiver CAP_SYS_ADMIN
.O motivo para essa mudança é dificultar a exploração de outra vulnerabilidade relacionada à memória, o ataque de Rowhammer , usando as informações sobre o mapeamento virtual para físico exposto pelos PFNs.)
Se você executar o programa de exemplo várias vezes, deverá notar que o endereço virtual da página deve ser alterado (devido a ASLR ), mas o PFN para bibliotecas compartilhadas que estão em uso por outros processos deve permanecer o mesmo.
Se as PFNs para libmyl.so
coincidirem entre o programa victim
e spy
, eu começaria procurando por um motivo pelo qual o ataque falha no próprio código de ataque. Se os PFNs não corresponderem, os bits adicionais podem dar alguma dica porque as páginas não estão configuradas para serem compartilhadas. Os pagemap
bits indicam o seguinte :
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)
As páginas de copy-on-write em (MAP_FILE | MAP_PRIVATE)
areas são anônimas neste contexto.
Bônus: Para obter o número de vezes em que uma página foi mapeada, o PFN pode ser usado para procurar a página em /proc/kpagecount
. Este arquivo contém uma contagem de 64 bits do número de vezes que cada página é mapeada, indexada por PFN.