跳过正文
  1. Posts/

Ubuntu编译并使用libvmi

·1461 字·3 分钟
睡觉不盖被子
作者
睡觉不盖被子

环境
#

$ lsb_release -a
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.6 LTS
Release:        20.04
Codename:       focal

需要编译四部分:

  1. patched kvm(kernel)
  2. patched qemu
  3. libkvmi
  4. libvmi

编译kvm(kernel)
#

编译该版本(5.4.24)的kernel需要旧版gcc,我使用的是gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.2)

# 安装依赖
$ sudo apt gcc install bc fakeroot flex bison libelf-dev libssl-dev dwarves
$ cd kvm-vmi/kvm
# 复制当前系统的内核配置
$ cp /boot/config-$(uname -r) .config
# 调整内核配置
## disable kernel modules signature
$ ./scripts/config --disable SYSTEM_TRUSTED_KEYS
$ ./scripts/config --disable SYSTEM_REVOCATION_KEYS
## enable KVM
$ ./scripts/config --module KVM
$ ./scripts/config --module KVM_INTEL
$ ./scripts/config --module KVM_AMD
## enable intospection
$ ./scripts/config --enable KVM_INTROSPECTION
## disable hugepage due to compilation issue
$ ./scripts/config --disable TRANSPARENT_HUGEPAGE
## tweak localversion
$ ./scripts/config --set-str CONFIG_LOCALVERSION -kvmi
## ubuntu 22.04 compatibility
$ ./scripts/config --enable PREEMPT
$ ./scripts/config --disable NET_VENDOR_NETRONOME

编译并安装:

# 其余配置默认
$ make olddefconfig
# 编译并安装
$ make -j$(nproc)
# 1. 通过make实现安装内核
$ sudo make modules_install
$ sudo make install
# 2. 编译为ubuntu的deb安装包
# 创建编译所需的证书
$ openssl req -x509 -newkey rsa:4096 -keyout ./mycert.key -out ./mycert.pem -nodes -days 3650
$ openssl req -x509 -newkey rsa:4096 -keyout ./revoked-mycert.key -out ./revoked-mycert.pem -nodes -days 3650
# 修改 CONFIG_SYSTEM_TRUSTED_KEYS 与 CONFIG_SYSTEM_REVOCATION_KEYS
$ make menuconfig
$ make -j$(nproc) bindeb-pkg
# 以编译6.17.0的输出为例
$ ls ..
linux-headers-6.17.0_6.17.0-6_amd64.deb 	linux-image-6.17.0_6.17.0-6_amd64.deb
linux-image-6.17.0-dbg_6.17.0-6_amd64.deb 	linux-libc-dev_6.17.0-6_amd64.deb
# 其中linux-image-6.17.0_6.17.0-6_amd64.deb是内核包
# linux-headers-6.17.0_6.17.0-6_amd64.deb是头文件包
$ cd .. && sudo dpkg -i linux-image-*.deb linux-headers-*.deb

系统启动时选择新编译出的内核或是直接在grub配置中设置默认从新编译内核启动

# 找到menuentry选项
$ sudo cat /boot/grub/grub.cfg | grep menuentry
submenu 'Advanced options for Ubuntu'
	menuentry 'Ubuntu, with Linux 6.17.0-7-generic'
	menuentry 'Ubuntu, with Linux 6.17.0-7-generic (recovery mode)'
	menuentry 'Ubuntu, with Linux 5.4.24-kvmi+'
	menuentry 'Ubuntu, with Linux 5.4.24-kvmi+ (recovery mode)'
$ sudo vim /etc/default/grub
GRUB_DEFAULT="Advanced options for Ubuntu>Ubuntu, with Linux 5.4.24-kvmi+"

启动后如下方式查看是否成功从新编译内核启动:

$ uname -r
5.4.24-kvmi+

编译qemu
#

# 安装依赖
$ sudo apt-get install libpixman-1-dev pkg-config zlib1g-dev libglib2.0-dev dh-autoreconf libspice-server-dev
# 生成Makefile
$ ./configure --target-list=x86_64-softmmu --enable-spice --prefix=/usr/local
# 编译并安装
$ make -j$(nproc) && sudo make install

默认情况下编译得到的qemu会安装于/usr/local/bin/目录下,此时使用编译得到的qemu-system-x86_64启动虚拟机会报错,原因是AppArmor的防御机制把我们编译出的qemu权限限制了。

  1. AppArmor授权qemu,添加对/usr/local/bin/qemu-system-x86_64的许可,共两处:
$ sudo vim /etc/apparmor.d/abstractions/libvirt-qemu
$ sudo vim /etc/apparmor.d/usr.sbin.libvirtd
  1. virt-install启动虚拟机报错: ERROR internal error: qemu unexpectedly closed the monitor: 2025-11-29T18:02:32.690150Z qemu-system-x86_64: warning: host doesn’t support requested feature: MSR(490H).vmx-entry-load-perf-global-ctrl [bit 13],是由于找不到符合权限的固件导致,需要添加固件地址/usr/local/share/qemu/**
$ sudo vim /etc/apparmor.d/abstractions/libvirt-qemu

配置虚拟机xml
#

<!-- 添加qemu插件 --> 
<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
<!-- 添加qemu启动命令行 --> 
  <qemu:commandline>
      <qemu:arg value='-chardev'/>
      <qemu:arg value='socket,path=/tmp/introspector,id=chardev0,reconnect=10'/>
      <qemu:arg value='-object'/>
      <qemu:arg value='introspection,id=kvmi,chardev=chardev0'/>
  </qemu:commandline>
  ...
  <devices>
  <!-- 确认使用的qemu是编译得到的 --> 
      <emulator>/usr/local/bin/qemu-system-x86_64</emulator>

以如上配置启动VM,即可通过使用libkvmi通过socket/tmp/introspector访问虚拟机内部。

编译libkvmi
#

$ cd kvm-vmi/libkvmi
$ ./bootstrap
$ ./configure
$ make
$ sudo make install

测试安装是否成功,能否访问socket:

$ cd libkvmi/examples
$ ./hookguest-libkvmi /tmp/introspector
# 等待几秒应该会有输出

编译libvmi
#

# 安装依赖
$ sudo apt-get install build-essential gcc libtool cmake pkg-config check libglib2.0-dev libvirt-dev flex bison libjson-c-dev
# 编译安装
$ cd kvm-vmi/libvmi
$ mkdir build
$ cd build
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local -DENABLE_KVM=ON -DENABLE_XEN=OFF -DENABLE_BAREFLANK=OFF
$ make -j2
$ sudo make install

下面需要配置/etc/libvmi.conf,相当于给每个VM登记信息。

$ cat /etc/libvmi.conf
tvm {
    ostype = "Linux";
    sysmap = "/home/swbz/Projects/tvm/System.map-6.17.0-7-generic";
    linux_name = 0xcb0;
    linux_tasks = 0x9c0;
    linux_mm = 0xa10;
    linux_pid = 0xa90;
    linux_pgd = 0x78;
}

sysmap需要从VM中复制得到,下面的五个Offset可以通过kvm-vmi/libvmi/tools/linux-offset-finder中的内核模块,在VM中运行后可以从sudo dmesg获取。如果只是访问物理内存则不需要sysmap与五个Offset信息,但是如果需要访问虚拟内存或是其它操作就需要这些Offset信息。

// 访问虚拟机示例
bool read_text(char *domain, uint8_t *buf) {
  vmi_instance_t vmi;
  vmi_init_error_t err;
  vmi_init_data_t *init_data = (vmi_init_data_t *)malloc(
      sizeof(vmi_init_data_t) + sizeof(vmi_init_data_entry_t) * 1);
  if (!init_data) {
    std::cerr << "Failed to allocate space for init_data." << std::endl;
    return false;
  }
  init_data->count = 1;
  init_data->entry[0].type = VMI_INIT_DATA_KVMI_SOCKET;
  // socket路径为 /tmp/<domain_name>
  std::string socket = std::string("/tmp/") + std::string(domain);
  init_data->entry[0].data = (void *)socket.c_str();
  if (vmi_init_complete(&vmi, domain, VMI_INIT_DOMAINNAME, init_data,
                        VMI_CONFIG_GLOBAL_FILE_ENTRY, NULL,
                        &err) != VMI_SUCCESS) {
    std::cerr << "Init error: " << err << std::endl;
    return false;
  }

  size_t bytes_read;
  if (vmi_read_va(vmi, start_addr, 0, all_size, buf, &bytes_read) !=
      VMI_SUCCESS) {
    std::cerr << "Read error" << std::endl;
    return false;
  }
  // TODO: Process buf
  
  vmi_destroy(vmi);
  return true;
}