Malware analysis

Virtual Machine Introspection in Malware Analysis – LibVMI

Youness Zougar
August 8, 2018 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.

Become a certified reverse engineer!

Become a certified reverse engineer!

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

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;

    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;

}

...

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.

...

while (1) {

    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;

    }

};

...

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.


...

vmi_destroy(vmi);

...

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.

...

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

...

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.

...

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

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);

...

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.

event_response_t int3_cb(vmi_instance_t vmi, vmi_event_t *event)

{

    event->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);

}

...

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.

Youness Zougar
Youness Zougar

Youness Zougar is an IT Security Expert, especially in Reverse Engineering and Malware Analysis. He is also a Windows Kernel Drivers developer and co-founder at SAFE-Cyberdefense. In order to offer companies the best level of security, he developed with his teammates an Endpoint solution called SAFE Endpoint Security based on a defense in depth technology applied to workstations.