Skip to content

Commit

Permalink
contrib/plugins: Add virtual machine introspection
Browse files Browse the repository at this point in the history
VMI provides a generic interface for virtual machine introspection
while vmi linux implements the backend for Linux guests
  • Loading branch information
Andrew Fasano committed Sep 13, 2024
1 parent bb3a80c commit ac171b2
Show file tree
Hide file tree
Showing 15 changed files with 3,561 additions and 0 deletions.
38 changes: 38 additions & 0 deletions contrib/plugins/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ NAMES += cflow
NAMES += stringsearch
NAMES += syscalls
NAMES += syscalls_logger
NAMES += vmi/core
NAMES += vmi/linux

# Replace '/' with '_' in plugin names
NAMES_UNDERSCORE := $(subst /,_,$(NAMES))
Expand Down Expand Up @@ -62,6 +64,36 @@ all: $(SONAMES)

PLUGIN_OBJS := $(addsuffix .o,$(NAMES_UNDERSCORE))

# Build VMI core and VMI Linux
VPATH += vmi vmi/linux
VMI_LINUX_SRCS := \
vmi/linux/vmi_linux.cpp \
vmi/linux/default_profile.cpp \
vmi/linux/kernelinfo_read.c

# Function to generate object file names from source file paths
define obj_from_src
$(subst /,_,$(basename $(1))).o
endef

VMI_LINUX_OBJS := $(foreach src,$(VMI_LINUX_SRCS),$(call obj_from_src,$(src)))

define compile_vmi_linux
$(call obj_from_src,$(1)): $(1)
$(call quiet-command, \
$(CC) $(CFLAGS) $(PLUGIN_CFLAGS) -c -o $$@ $$<, \
BUILD, plugin $$@)
endef

# Apply the compilation rules to all source files
$(foreach src,$(VMI_LINUX_SRCS),$(eval $(call compile_vmi_linux,$(src))))

# Define rule to build the shared library from multiple object files
libvmi_linux$(SO_SUFFIX): $(VMI_LINUX_OBJS)
$(call quiet-command, \
$(CC) -shared -o $@ $^ $(LDLIBS), \
LINK, plugin $@)

# Rule for creating shared libraries for individual plugins
lib%$(SO_SUFFIX): %.o
$(call quiet-command, \
Expand Down Expand Up @@ -92,6 +124,12 @@ endif
$(CXX) $(CXXFLAGS) $(PLUGIN_CFLAGS) -c -o $@ $<, \
BUILD, plugin $@)

# Special rule to build vmi_core.o
vmi_core.o: vmi/core.c
$(call quiet-command, \
$(CC) $(CFLAGS) $(PLUGIN_CFLAGS) -c -o $@ $<, \
BUILD, plugin $@)

clean distclean:
rm -f *.o *$(SO_SUFFIX) *.d
rm -Rf .libs
Expand Down
40 changes: 40 additions & 0 deletions contrib/plugins/vmi/core.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include <stdio.h>
#include <qemu-plugin.h>
#include <plugin-qpp.h>
#include <gmodule.h>

QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
QEMU_PLUGIN_EXPORT const char *qemu_plugin_name = "vmi";
#include "vmi.h"

static qemu_plugin_id_t self_id;
QEMU_PLUGIN_EXPORT VmiProc *get_current_process(void) {
VmiProc *p = NULL;
qemu_plugin_run_callback(self_id, "on_get_current_process", &p, NULL);
return p;
}

QEMU_PLUGIN_EXPORT VmiProc *get_process(const VmiProcHandle *h) {
VmiProc *p = NULL; // output
struct get_process_data* evdata = (struct get_process_data*)malloc(sizeof(struct get_process_data));
evdata->h = h;
evdata->p = &p;

qemu_plugin_run_callback(self_id, "on_get_process", evdata, NULL);
return p;
}

QEMU_PLUGIN_EXPORT VmiProcHandle *get_current_process_handle(void) {
VmiProcHandle *h = NULL;
qemu_plugin_run_callback(self_id, "on_get_current_process_handle", &h, NULL);
return h;
}

QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
const qemu_info_t *info, int argc, char **argv) {
self_id = id;
qemu_plugin_create_callback(id, "on_get_current_process");
qemu_plugin_create_callback(id, "on_get_process");
qemu_plugin_create_callback(id, "on_get_current_process_handle");
return 0;
}
3 changes: 3 additions & 0 deletions contrib/plugins/vmi/linux/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
docs
*.sw.*
*~
70 changes: 70 additions & 0 deletions contrib/plugins/vmi/linux/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
Plugin: VMI linux
===========

Summary
-------

`vmi_linux` provides Linux introspection information and makes it available through the OSI interface. It does so by knowing about the offsets for various Linux kernel data structures and then providing algorithms that traverse these data structures in the guest virtual machine.

Because the offsets of fields in Linux kernel data structures change frequently (and can even depend on the specific compilation flags used), `vmi_linux` uses a configuration file to specify the offsets of critical data structures. A portion of such a configuration file, which is in the [GLib key-value format](https://developer.gnome.org/glib/stable/glib-Key-value-file-parser.html) (similar to .ini files), is given below:

[ubuntu:4.4.0-98-generic:32]
name = 4.4.0-98-generic|#121-Ubuntu SMP Tue Oct 10 14:23:20 UTC 2017|i686
version.a = 4
version.b = 4
version.c = 90
task.init_addr = 3249445504
#task.init_addr = 0xC1AE9A80
#task.per_cpu_offset_0 = 0x34B42000
task.per_cpu_offset_0 = 884219904
#task.current_task_addr = 0xC1C852A8
task.current_task_addr = 3251131048
task.size = 5824
task.tasks_offset = 624
task.pid_offset = 776

[... omitted ...]

[debian:4.9.0-6-686-pae:32]
name = 4.9.0-6-686-pae|#1 SMP Debian 4.9.82-1+deb9u3 (2018-03-02)|i686
version.a = 4
version.b = 9
version.c = 88
task.init_addr = 3245807232
#task.init_addr = 0xC1771680
#task.per_cpu_offset_0 = 0x36127000
task.per_cpu_offset_0 = 907177984
#task.current_task_addr = 0xC18C3208
task.current_task_addr = 3247190536
task.size = 5888
task.tasks_offset = 708
task.pid_offset = 864

[... omitted ...]

Of course, generating this file by hand would be extremely painful. We refer interested readers to PANDA.re's infrastructure to generate these configurations: https://github.com/panda-re/panda/tree/dev/panda/plugins/osi_linux/utils

Arguments
---------

* `conf`: string: The location of the configuration file that gives the required offsets for different versions of Linux.
* `group`: string, The name of a configuration group to use from the kernelinfo file (multiple configurations can be stored in a single `kernelinfo.conf`).

Dependencies
------------

`vmi_linux` is an introspection provider for the `vmi` plugin.

Example
-------

Assuming you have a `kernelinfo.conf` in the current directory with a configuration named `my_kernel_info`, you can run the OSI test plugin on a Linux replay as follows:

```bash
[qemu args] -plugin vmi_core -pluginvmi_linux,kconf_file=kernelinfo.conf,kconf_group=my_kernel_info
```

Background
---------

VMI Linux was originally developed for [PANDA.re](https://panda.re) and named `osi_linux` with OSI standing for Operating System Introspection, the name of PANDA's VMI system.
68 changes: 68 additions & 0 deletions contrib/plugins/vmi/linux/default_profile.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include "vmi_linux.h"
#include "default_profile.h"
#include "../vmi_types.h"

/**
* @brief Retrieves the task_struct address using per cpu information.
*/
target_ptr_t default_get_current_task_struct(void)
{
struct_get_ret_t err;
target_ptr_t current_task_addr;
target_ptr_t ts;
current_task_addr = ki.task.current_task_addr;
err = struct_get(&ts, current_task_addr, ki.task.per_cpu_offset_0_addr);
//assert(err == struct_get_ret_t::SUCCESS && "failed to get current task struct");
if (err != struct_get_ret_t::SUCCESS) {
// Callers need to check if we return NULL!
return 0;
}

fixupendian(ts);
return ts;
}

/**
* @brief Retrieves the address of the following task_struct in the process list.
*/
target_ptr_t default_get_task_struct_next(target_ptr_t task_struct)
{
struct_get_ret_t err;
target_ptr_t tasks;
err = struct_get(&tasks, task_struct, ki.task.tasks_offset);
fixupendian(tasks);
assert(err == struct_get_ret_t::SUCCESS && "failed to get next task");
return tasks-ki.task.tasks_offset;
}

/**
* @brief Retrieves the thread group leader address from task_struct.
*/
target_ptr_t default_get_group_leader(target_ptr_t ts)
{
struct_get_ret_t err;
target_ptr_t group_leader;
err = struct_get(&group_leader, ts, ki.task.group_leader_offset);
fixupendian(group_leader);
assert(err == struct_get_ret_t::SUCCESS && "failed to get group leader for task");
return group_leader;
}

/**
* @brief Retrieves the array of file structs from the files struct.
* The n-th element of the array corresponds to the n-th open fd.
*/
target_ptr_t default_get_file_fds(target_ptr_t files)
{
struct_get_ret_t err;
target_ptr_t files_fds;
err = struct_get(&files_fds, files, {ki.fs.fdt_offset, ki.fs.fd_offset});
if (err != struct_get_ret_t::SUCCESS) {
printf("Failed to retrieve file structs (error code: %d)", err);
return (target_ptr_t)NULL;
}
fixupendian(files_fds);
return files_fds;
}

/* vim:set tabstop=4 softtabstop=4 expandtab: */
19 changes: 19 additions & 0 deletions contrib/plugins/vmi/linux/default_profile.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include "kernel_profile.h"

target_ptr_t default_get_current_task_struct();
target_ptr_t default_get_task_struct_next(target_ptr_t ts);
target_ptr_t default_get_group_leader(target_ptr_t ts);
target_ptr_t default_get_file_fds(target_ptr_t files);
bool can_read_current();
void on_first_syscall(target_ulong pc, target_ulong callno);

const KernelProfile DEFAULT_PROFILE = {
.get_current_task_struct = &default_get_current_task_struct,
.get_task_struct_next = &default_get_task_struct_next,
.get_group_leader = &default_get_group_leader,
.get_files_fds = &default_get_file_fds
};

/* vim:set tabstop=4 softtabstop=4 expandtab: */
23 changes: 23 additions & 0 deletions contrib/plugins/vmi/linux/endian_helpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// TODO: QEMU plugin model builds a signle plugin for all guests,
// we do not have a compile-time way to check if we need to swap endianness.
// Not sure if there's a way to check at runtime

// VMI Linux works with a bunch of pointers which we need to
// flip if the guest/host endianness mismatch.
#if defined(TARGET_WORDS_BIGENDIAN) != defined(HOST_WORDS_BIGENDIAN)
// If guest and host endianness don't match:
// fixupendian will flip a dword in place
#define fixupendian(x) {x=bswap32((target_ptr_t)x);}
#define fixupendian64(x) {x=bswap64((uint64_t)x);}
// of flipbadendian will flip a dword
#define flipbadendian(x) bswap32((target_ptr_t)x)
#define flipbadendian64(x) bswap64((uint64_t)x)


#else
#define fixupendian(x) {}
#define fixupendian64(x) {}
#define flipbadendian(x) x
#define flipbadendian64(x) x
#endif

12 changes: 12 additions & 0 deletions contrib/plugins/vmi/linux/kernel_profile.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once
#include "../vmi_types.h"

struct KernelProfile
{
target_ptr_t (*get_current_task_struct)();
target_ptr_t (*get_task_struct_next)(target_ptr_t ts);
target_ptr_t (*get_group_leader)(target_ptr_t ts);
target_ptr_t (*get_files_fds)(target_ptr_t files);
};

/* vim:set tabstop=4 softtabstop=4 expandtab: */
Loading

0 comments on commit ac171b2

Please sign in to comment.