如何确保共享库的内存页被多个进程共享?

如何确保共享库的内存页被多个进程共享?

让我首先描述我想做什么,然后是我能做什么,最后是我的问题。


目标:用C实现flush+flush缓存攻击

我正在尝试用 C 实现刷新+刷新缓存攻击(https://gruss.cc/files/flushflush.pdf)。基本上,它利用了两个不同进程在使用共享库时可能共享相同内存页的事实。这导致缓存的“共享使用”。

假设我们有一个受害者进程,它不断运行并且有时执行func从共享库导入的函数。

同时,我们假设我们有一个间谍进程,与受害者在同一台计算机上运行,​​其目标是在受害者调用 时进行监视func。间谍还可以访问同一个共享库。间谍进程伪代码如下:

i=0;
for (i = 0; i < trace_length ; i++)
{
    trace[i] = flush_time( address of function "func");
    i++;
}

在哪里flush_time(<address>)是一个函数,返回 CPU 刷新所指向的内存所需的时间address来自所有缓存级别。在Intel处理器上,这可以通过汇编指令来实现clflush。可以观察到,clflush当高速缓存中不存在该地址时,执行速度会更快。因此,刷新内存地址所需的时序可以直接转换为内存地址存在(或不存在)在高速缓存内。

间谍进程返回一个跟踪向量,其中包含一段时间内的flush_time结果。根据之前的观察,当受害者也调用该函数时,该跟踪将表现出更高的计时func。因此,间谍将推断受害者何时打电话func


我设法做的:使攻击针对 GSL 共享库

我实施了上述攻击,其中共享库是GSL。我随意选择gsl_stats_mean(在 中定义gsl_statistics_double)作为func我愿意监视的函数。

在这种情况下,间谍活动可以完美地工作,因为当受害者程序调用时,我可以清楚地看到时间差异gsl_stats_mean


我的问题:该攻击不适用于自制共享库

我现在想创建自己的共享库并将其用于间谍/受害者测试。假设表示 my和files.所在的文件夹。我在一个文件夹中创建了两个文件和,它们分别包含 的描述和它的声明。与之前一样,我的间谍的目标是检测受害者的使用情况。spy.cvictim.cmyl.cmyl.h./src/mylfuncfunc

spy.c和都victim.c包含包含行:

 #include "src/myl/myl.h"

共享库的创建是使用以下命令完成的:

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

然后我使用以下几行启动我的受害者和间谍:

LD_LIBRARY_PATH=$(pwd)/bin/shared ./victim
LD_LIBRARY_PATH=$(pwd)/bin/shared ./spy

然而,与我使用 GSL 函数的情况相反,我再也看不到缓存上的任何活动。我想这意味着我的间谍进程和受害者进程不共享我的共享库的相同内存页面(然而,使用 GSL 时就是这种情况)。请注意,以这种方式编译时,针对 GSL 函数的间谍活动仍然有效。

我的主要问题是:如何确保自制编译的共享库在多个进程同时执行时共享内存分页?我安装的“正确”库似乎就是这种情况,例如 GSL、gmp、本机库等......但不是我自己制作的库。

预先感谢,如果答案很简单,我深表歉意。

编辑:间谍和受害者的LD_DEBUG=libs输出。files注意:受害者被呼叫pg2,间谍被呼叫pg1(对此感到抱歉)

首先是受害者的库,然后是受害者的文件 ( pg2)。然后是间谍的库,然后是间谍的文件 ( 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: 

答案1

由于动态链接器/加载器的调试输出ld确认victimspy程序都加载了正确的输入文件,因此下一步将验证内核是否实际上已将加载到内存中的物理页设置为在和libmyl.so之间共享。victimspy

在 Linux 中,从内核版本 2.6.25 开始可以通过pagemap内核中的接口,允许用户空间程序通过读取 中的文件来检查页表和相关信息/proc

使用页面映射来确定两个进程是否共享内存的一般过程如下:

  1. 读取/proc/<pid>/maps两个进程以确定内存空间的哪些部分映射到哪些对象。
  2. 选择您感兴趣的地图,在本例中libmyl.so是映射到的页面。
  3. 打开/proc/<pid>/pagemap。它pagemap由 64 位页面映射描述符组成,每页一个。页面地址与其描述符地址之间的映射pagemap页地址/页大小*描述符大小。查找您想要检查的页面的描述符。
  4. 从 .txt 文件中读取每个页面的 64 位描述符作为无符号整数pagemap
  5. 比较和libmyl.so的页面之间页面描述符的位 0-54 中的页面帧号 (PFN) 。如果 PFN 匹配,则两个进程共享相同的物理页。victimspy

以下示例代码说明了如何pagemap从流程内访问和打印。它用dl_iterate_phdr()确定加载到进程内存空间中的每个共享库的虚拟地址,然后查找并打印相应的pagemapfrom /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);
}

该程序的输出应类似于以下内容:

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

在哪里:

  • address是页面的虚拟地址
  • pfn是页面页框号
  • soft-dirty表示如果软脏位在页面页表项(PTE)中设置。
  • excl.指示页面是否被独占映射(即页面仅针对此进程进行映射)。
  • file / shared anon指示该页面是文件页面还是共享匿名页面。
  • swapped指示页面当前是否已交换(意味着present为零)。
  • present指示该页面当前是否存在于进程驻留集中(暗示swapped为零)。

(注意:我运行示例程序是sudo因为,从 Linux 4.0 开始,只有具有该CAP_SYS_ADMIN能力的用户才能从 获取 PFN /proc/<pid>/pagemap。从 Linux 4.2 开始,如果用户没有 PFN,则 PFN 字段将被清零CAP_SYS_ADMIN。这一更改的原因是使其变得更加困难为了利用另一个与内存相关的漏洞,行锤攻击,使用 PFN 公开的虚拟到物理映射信息。)

如果多次运行示例程序,您应该注意到页面的虚拟地址应该改变(由于ASLR),但其他进程正在使用的共享库的 PFN 应保持不变。

如果 PFN与程序libmyl.so之间匹配,我将开始在攻击代码本身中寻找攻击失败的原因。如果 PFN 不匹配,附加位可能会提示为什么页面未设置为共享。比特​victimspypagemap表明以下内容:

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)

(MAP_FILE | MAP_PRIVATE)在此上下文中,区域中的写时复制页面是匿名的。

奖金:为了获得次数页面已被映射,PFN 可用于在 中查找页面/proc/kpagecount。该文件包含每个页面映射次数的 64 位计数,由 PFN 索引。

相关内容