-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
contrib/plugins: Add virtual machine introspection
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
Showing
15 changed files
with
3,561 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
docs | ||
*.sw.* | ||
*~ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: */ |
Oops, something went wrong.