LibVMI is a library written in C which allows users to set up an introspection system of virtual machines under Linux and Windows. It also allows access to a running virtual machine memory and does this by offering several already-made functions for accessing memory using physical or virtual addresses, or even with Kernel symbols. LibVMI even offers access to memory made from a snapshot of a physical memory, which can be especially interesting when doing debugging or forensics.
In addition to memory access, LibVMI supports memory events. These events trigger notifications when a memory region is accessed in read, write or execution mode.
LibVMI was conceived to run under Linux. The most-used platform is Xen, but KVM can be used as well.
Multiple complex levels of abstraction exist when we talk about introspection. Those levels are hopefully handled by LibVMI and are completely transparent for us.
LibVMI offers by default a set of examples with the aim of testing the introspection concept or using them as basis to create a customized introspection system.
The following example allows you to list all running processes on the virtual machine from the hypervisor.
We start by initializing LibVMI contexts (LibVMI instance) which corresponds to the virtual machine on which we are running our program.
After that, initialization of the fields will be different, depending on which operating system we are looking to list the running processes of. In the given example, we consider three types of operating systems: Windows, Linux and FeeBSD.
…
After retrieving the interesting data, we are then going to loop on the processes list and print them to the console (PID, process name, memory address) one by one until the end of the processes chain.
if (VMI_FAILURE == vmi_get_offset(vmi, “linux_tasks”, &tasks_offset))
goto error_exit;
if (VMI_FAILURE == vmi_get_offset(vmi, “linux_name”, &name_offset))
goto error_exit;
if (VMI_FAILURE == vmi_get_offset(vmi, “linux_pid”, &pid_offset))
goto error_exit;
} else if (VMI_OS_WINDOWS == vmi_get_ostype(vmi)) {
if (VMI_FAILURE == vmi_get_offset(vmi, “win_tasks”, &tasks_offset))
goto error_exit;
if (VMI_FAILURE == vmi_get_offset(vmi, “win_pname”, &name_offset))
goto error_exit;
if (VMI_FAILURE == vmi_get_offset(vmi, “win_pid”, &pid_offset))
goto error_exit;
} else if (VMI_OS_FREEBSD == vmi_get_ostype(vmi)) {
tasks_offset = 0;
if (VMI_FAILURE == vmi_get_offset(vmi, “freebsd_name”, &name_offset))
goto error_exit;
if (VMI_FAILURE == vmi_get_offset(vmi, “freebsd_pid”, &pid_offset))
goto error_exit;
}
…
Finally, we are going to call the function which is going to destroy every LibVMI instance created for the virtual machine and clean every memory region corresponding to it.
current_process = cur_list_entry – tasks_offset;
vmi_read_32_va(vmi, current_process + pid_offset, 0, (uint32_t*)&pid);
procname = vmi_read_str_va(vmi, current_process + name_offset, 0);
if (!procname) {
printf(“Failed to find procnamen”);
goto error_exit;
}
printf(“[%5d] %s (struct addr:%”PRIx64″)n”, pid, procname, current_process);
if (procname) {
free(procname);
procname = NULL;
}
if (VMI_OS_FREEBSD == os && next_list_entry == list_head) {
break;
}
cur_list_entry = next_list_entry;
status = vmi_read_addr_va(vmi, cur_list_entry, 0, &next_list_entry);
if (status == VMI_FAILURE) {
printf(“Failed to read next pointer in loop at %”PRIx64″n”, cur_list_entry);
goto error_exit;
}
if (VMI_OS_WINDOWS == os && next_list_entry == list_head) {
break;
} else if (VMI_OS_LINUX == os && cur_list_entry == list_head) {
break;
}
};
…
Events Monitoring
Besides its classic use demonstrated in the example above, LibVMI allows you to monitor memory events on the fly, not only by notifying the hypervisor for each memory access (read, write, execute) but also by reporting exceptions (breakpoints, access violation). … Below is an example extracted from LibVMI examples, allowing you to create interruption events. Unlike the initialization part of the listing process example, we have a little modification to overcome. We will need to add the flag VMI_INIT_EVENTS so that the LibVMI instance will have the capacity to monitor events on the fly. After initializing LibVMI and determining the use of the events, it is now possible for us to create callbacks to track the chosen event types. In the following example, the choice was done on interruption event INT3. … We initialize then our callback function, which will be executed each time the interruption is trapped. This callback function supposes that all INT3 events may be caused by a debugger and will simply reinject them without doing anything. interrupt_event.version = VMI_EVENTS_VERSION; interrupt_event.type = VMI_EVENT_INTERRUPT; interrupt_event.interrupt_event.intr = INT3; interrupt_event.callback = int3_cb; vmi_register_event(vmi, &interrupt_event); … Finally, we call an infinite loop (unless it is interrupted by ctrl+c for example) which will continue to listen permanently to the coming events and process them. event->interrupt_event.reinject = 1; if ( !event->interrupt_event.insn_length ) event->interrupt_event.insn_length = 1; return 0; } Thank you for joining me for this brief look at the uses of LibVMI! We’ll be continuing this and other such examinations in ongoing articles from InfoSec Institute. vmi_events_listen(vmi, 500); } …