在host上窥探kvm虚拟机内存

729 阅读1分钟

1. kvm内存虚拟化

kvm虚拟机的内存虚拟化,使用了内存转换技术,过程如下:GVA -> GPA -> HVA -> HPA

111.png

通过qemu启动了一个8G内存的虚拟机,查看内存smaps,可以发现有个内存就是8G,这个就是guest所使用的物理内存。

图片

利用这个信息就可以窥探虚拟机内存中特定的信息。

2. 虚拟地址转换为物理地址的原理

内核文档 pagemap.txt 中描述如下:

图片

内存中每一个页对应一个64位,也就是8字节的字段。假设虚拟地址为0xfe0020,其转换过程如下:

图片

虚拟地址0xfe0020,其高52位(0xfe0020>>12)为0xfe0,也就是其虚拟页号为0xfe0。那么该虚拟页的信息处于/proc/self/pagemap这个文件中偏移量为0xfe0*8的地方。从此处读取一个8字节的数据,先检查最高位 "Bit 63 page present",如果是1,那么说明该页处于物理内存中,那么该8字节的第0-54位就是物理页号。假设物理页号是0x40,那么实际的物理地址就是(0x40<<12)+0x20=0x40020。

介绍完原理,下面实现由虚拟地址到物理地址的转换代码就很简单了:

/* gva2gpa.c*/
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PFN_MASK         ((((uint64_t)1) << 55) - 1)
#define PFN_PRESENT_FLAG (((uint64_t)1) << 63)

int gva2gpa(unsigned long vir, unsigned long *phy)
{
  int fd;
  int page_size = getpagesize();                // 获取系统页大小, 通常为 4k
  unsigned long vir_page_idx = vir / page_size;
  unsigned long pfn_item_offset = vir_page_idx * sizeof(uint64_t);  // 在pagemap记录文件中的偏移量
  uint64_t pfn_item;
  char *page_map_file = "/proc/self/pagemap";   // pagemap 文件路径

    // 只读方式方式打开
  fd = open(page_map_file, O_RDONLY);

  if (fd < 0) {
    fprintf(stderr, "open %s failed", page_map_file);
    return -1;
  }

    // 定位到 offset 偏移位置
  if ((off_t) - 1 == lseek(fd, pfn_item_offset, SEEK_SET)) {
    fprintf(stderr, "lseek %s failed", page_map_file);
    return -1;
  }

    // 读取对应项的值, 并判断读取位数
  if (sizeof(uint64_t) != read(fd, &pfn_item, sizeof(uint64_t))) {
    fprintf(stderr, "read %s failed", page_map_file);
    return -1;
  }

    // 判断物理页是否在内存中 "Bit  63    page present"
  if (0 == (pfn_item & PFN_PRESENT_FLAG)) {
    fprintf(stderr, "page is not present");
    return -1;
  }

    // 如果在内存上,物理页号加上偏移地址就是物理地址
    // 对应项 bit0-54 位表示物理页号
  *phy = (pfn_item & PFN_MASK) * page_size + vir % page_size;

  return 0;
}

int main(int argc,char *argv[])
{
    uint8_t  *p_gva;
    uint64_t ptr_mem;

    p_gva = malloc(256);
    strcpy(p_gva, "Where am I?");
    printf("%s\n", p_gva);

    if (gva2gpa((unsigned long)p_gva, &ptr_mem) == 0) {
        printf("Physical address is: 0x%llx\n", ptr_mem);
    }

    getchar();
    free(p_gva);
    return 0;
}

在虚拟机上执行上面程序:

通过虚拟机物理地址获取物理机虚拟地址,即 gpa -> hva 。

在物理机上执行:

现在得到了物理机中的虚拟机地址,下面通过两种方法获取虚拟机内存中的信息:

  • 一种方式是通过gdb提供的 x 指令:

图片

  • 另外一种方法是通过 qmp 指令

图片

现在看到的 "Where am I?" 就是虚拟机内存中写入的信息。


weixinsouyisou.jpg