环境 #
$ lsb_release -a
Distributor ID: Ubuntu
Description: Ubuntu 20.04.6 LTS
Release: 20.04
Codename: focal
需要编译四部分:
- patched kvm(kernel)
- patched qemu
- libkvmi
- 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权限限制了。
- 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
- 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;
}