Malware analysis

Virtual Machine Introspection in Malware Analysis – LibVMI

August 8, by Youness Zougar

In the last article in this series, we have seen what Virtual Machine Introspection is and how it works in general. Now, in this article, we’ll see how we can set up VMI and what tools to use.

What is LibVMI?

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.

Use Cases

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.

Process Listing

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.

vmi_init_complete(vmi, name, VMI_INIT_DOMAINNAME, NULL, VMI_CONFIG_GLOBAL_FILE_ENTRY, NULL, NULL)

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.

if (VMI_OS_LINUX == vmi_get_ostype(vmi)) {

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;

si (VMI_FAILURE == vmi_get_offset(vmi, «win_pid», pid_offset))

ir a error_exit;

} si no (VMI_OS_FREEBSD == vmi_get_ostype(vmi)) {

tareas_offset = 0;

si (VMI_FAILURE == vmi_get_offset(vmi, «freebsd_name», name_offset))

ir a error_exit;

si (VMI_FAILURE == vmi_get_offset(vmi, «freebsd_pid», pid_offset))

ir a error_exit;

}

Después de recuperar los datos interesantes, recorreremos la lista de procesos y los imprimiremos en la consola (PID, nombre del proceso, dirección de memoria) uno por uno hasta el final de la cadena de procesos.

mientras (1) {

proceso_actual = cur_list_entry – tareas_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);

si (!procname) {

printf(«Error al encontrar el nombre de proceso»);

ir a error_exit;

}

printf(«[%5d] %s (struct addr:%»PRIx64″)n», pid, procname, current_process);

si (nombreproc) {

gratis(procnombre);

nombreproc = NULL;

}

si (VMI_OS_FREEBSD == sistema operativo next_list_entry == list_head) {

romper;

}

cur_list_entry = next_list_entry;

estado = vmi_read_addr_va(vmi, cur_list_entry, 0, next_list_entry);

si (estado == VMI_FAILURE) {

printf(«Error al leer el siguiente puntero en el bucle en %»PRIx64″n», cur_list_entry);

ir a error_exit;

}

si (VMI_OS_WINDOWS == sistema operativo next_list_entry == list_head) {

romper;

} más si (VMI_OS_LINUX == sistema operativo cur_list_entry == list_head) {

romper;

}

};

Finalmente, llamaremos a la función que destruirá cada instancia de LibVMI creada para la máquina virtual y limpiará cada región de memoria correspondiente.

vmi_destroy(vmi);

Monitoreo de eventos

Además de su uso clásico demostrado en el ejemplo anterior, LibVMI le permite monitorear eventos de memoria sobre la marcha, no solo notificando al hipervisor cada acceso a la memoria (lectura, escritura, ejecución) sino también informando excepciones (puntos de interrupción, violación de acceso).

A continuación se muestra un ejemplo extraído de ejemplos de LibVMI, que le permite crear eventos de interrupción.

A diferencia de la parte de inicialización del ejemplo del proceso de listado, tenemos que superar una pequeña modificación. Necesitaremos agregar el indicador VMI_INIT_EVENTS para que la instancia de LibVMI tenga la capacidad de monitorear eventos sobre la marcha.

vmi_init(vmi, VMI_XEN, (void*)nombre, VMI_INIT_DOMAINNAME | VMI_INIT_EVENTS, NULL, NULL)

Después de inicializar LibVMI y determinar el uso de los eventos, ahora nos es posible crear devoluciones de llamada para rastrear los tipos de eventos elegidos. En el siguiente ejemplo, la elección se realizó en el evento de interrupción INT3.

memset(interrupt_event, 0, sizeof(vmi_event_t));

interrupción_event.version = VMI_EVENTS_VERSION;

interrupción_event.type = VMI_EVENT_INTERRUPT;

interrupción_event.interrupt_event.intr = INT3;

interrupción_event.callback = int3_cb;

vmi_register_event(vmi, interrupt_event);

Luego inicializamos nuestra función de devolución de llamada, que se ejecutará cada vez que se atrape la interrupción. Esta función de devolución de llamada supone que todos los eventos INT3 pueden ser causados ​​por un depurador y simplemente los reinyectará sin hacer nada.

event_response_t int3_cb(vmi_instance_t vmi, vmi_event_t *evento)

{

evento-interrupt_event.reinject = 1;

if ( !event-interrupt_event.insn_length )

event-interrupt_event.insn_length = 1;

return 0;

}

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.

while (!interrupted) {

vmi_events_listen(vmi, 500);

}

Become a certified reverse engineer!

Get live, hands-on malware analysis training from anywhere, and become a Certified Reverse Engineering Analyst. Start Learning

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.