The osi
module forms the core of PANDA's OS-specific introspection support. The osi
plugin itself acts as a glue layer, offering a uniform API that can be called by plugins without that plugin needing to know anything about the underlying operating system. An OSI provider plugin then implements the necessary functionality for each operating system.
Expressed graphically, this arrangement looks like:
+-------------------+ +-------------------+
| Your Plugin | | Your Plugin |
+-------------------+ +-------------------+
| osi | | osi |
+-------------------+ +-------------------+
| osi_linux | | wintrospection |
+-------------------+ +-------------------+
The key is that you can swap out the bottom layer to support a new operating system without needing to modify your plugin.
os
: The target os. This argument is validated against a list of regular expressions in common.c. For linux, the specified os must also match an existing kernel profile. See osi_linux documentation for details.
const char * valid_os_re[] = {
"windows[-_]32[-_]xpsp[23]",
"windows[-_]32[-_]2000",
"windows[-_]32[-_]7sp[01]",
"windows[-_]64[-_]7sp[01]",
"linux[-_]32[-_].+",
"linux[-_]64[-_].+",
"freebsd[-_]32[-_].+",
"freebsd[-_]64[-_].+",
NULL
};
disable-autoload
: bool, defaults to false. OSI will try to automatically load an OSI provider plugin unless this set. Useful if you need to initialize an OSI provider plugin with non-default arguments.
Depends on some OS-specific plugin to register callbacks that implement the various APIs OSI exposes. Otherwise, any call to OSI will simply fail to return any useful data, as the OSI plugin itself does not know anything about specific operating systems.
The following data structures are defined in the osi_types.h
header and for use by the PANDA OSI framework:
// Represents a process handle
typedef struct osi_prochandle_struct {
target_ptr_t taskd;
target_ptr_t asid;
} OsiProcHandle;
// Represents a thread
typedef struct osi_thread_struct {
target_pid_t pid;
target_pid_t tid;
} OsiThread;
// Represents a page of memory (not implemented so far)
typedef struct osi_page_struct {
target_ptr_t start;
target_ulong len;
} OsiPage;
// Represents a single module (userspace library or kernel module)
typedef struct osi_module_struct {
target_ptr_t modd;
target_ptr_t base;
target_ptr_t size;
char *file;
char *name;
} OsiModule;
// Represents a single process
typedef struct osi_proc_struct {
target_ptr_t taskd;
target_ptr_t asid;
target_pid_t pid;
target_pid_t ppid;
char *name;
OsiPage *pages; // TODO in osi_linux
} OsiProc;
// Represents process memory details
typedef struct osi_proc_mem {
target_ptr_t start_brk;
target_ptr_t brk;
} OsiProcMem;
When PANDA OSI plugins need a dynamically allocated/freed, it is recommended to use one of GLib allocation functions. This offloads a lot of boilerplate checks (e.g. aborting when an allocation fails) to GLib, which results in more compact plugin code.
In addition to simple allocation, GLib also offers functions that address some common allocation-related anti-patterns. E.g. when dynamically constructing strings, using g_strdup_printf
helps to avoid the common fallback of using snprintf
on a static-sized buffer, and typically ommiting checking if the buffer was long enough.
Note: Currently, the GLib allocation functions use the vanilla malloc
internally. So mixing g_alloc
/g_free
with malloc
/free
should not be a problem. However, when given the opportunity, code should be updated to us the GLib allocation functions for the sake of uniformity.
Some interfaces provided by the PANDA OSI framework require the exchange of collections of the base data structures listed above. To avoid reimplementing staple ADTs for this purpose, PANDA OSI framework currently relies on the container types provided by GLib. Currently, only GArray
is used when needed to return multiple OsiProc
or OsiModule
structs.
Implementations of API calls that use data containers are expected to follow the following rules for the used GLib containers.:
- Typically, a double pointer (e.g.
GArray **out
) is passed to the OSI API function implementation. The value of*out
is expected to either be NULL or point to a valid container. - In the former case (
*out == NULL
), the implementation is expected to allocate and populate a new container. In case where the container supports setting a free function for its elements, the function has to be set appropriately (e.g. usingg_array_set_clear_func
). - In the latter case (
*out != NULL
), the implementation is expected to add values to the pointed container. - In case the implementation encounters an error, it is expected to free the container and its contents and set it to NULL (
*out = NULL
) before returning.
Implementation behaviour: The implementation should create and populate a GArray
filled with OsiProc
elements and set the element-content free function using g_array_set_clear_func
. The returned data have to be freed using the respective GArray
deallocation function.
Note: The use of GLib containers is for their external interfaces. Internally, OSI plugins may choose to use any type of container it is found to be convenient.
Plugins may register for task change notifications using the following callback:
Name: on_task_change
Signature:
typedef void (*on_task_change_t)(CPUState *)
Description: Used to notify a plugin that the current task (thread/process) within the operating system has changed.
To implement OS-specific introspection support, an OSI provider should register the following callbacks:
Name: on_get_processes
Signature:
typedef void (*on_get_processes_t)(CPUState *, GArray **)
Description: Retrieves the process list from the guest OS, along with detailed information for each process. to get the process list from the guest OS.
Implementation behaviour: The implementation should populate a GArray
filled with OsiProc
elements, following the rules described in the data containers section above. Results need to be freed using g_array_free
.
Name: on_get_process_handles
Signature:
typedef void (*on_get_process_handles_t)(CPUState *, GArray **)
Description: Retrieves an array of minimal handles of type OsiProcHandle
for the processes of the guest OS. Using the process list from the guest OS. The minimal handles contain just enough information to (a) uniquely identify a process and (b) retrieve the full process information when needed. This allows for lightweight tracking of processes.
Implementation behaviour: The implementation should populate a GArray
filled with OsiProcHandle
elements, following the rules described in the data containers section above. Results need to be freed using g_array_free
.
Name: on_get_current_process
Signature:
typedef void (*on_get_current_process_t)(CPUState *, OsiProc **)
Description: Called to get the currently running process in the guest OS. The implementation should allocate memory and fill in the pointer to an OsiProc
struct. The returned OsiProc
can be freed with free_osiproc
.
Name: on_get_current_process_handle
Signature:
typedef void (*on_get_current_process_handle_t)(CPUState *, OsiProcHandle **)
Description: Called to get the handle of the currently running process in the guest OS. The implementation should allocate memory and fill in the pointer to an OsiProcHandle
struct. The returned OsiProcHandle
can be freed with free_osiprochandle
.
Name: on_get_process
Signature:
typedef void (*on_get_process_t)(CPUState *, OsiProcHandle *, OsiProc **)
Description: Called to retrieve full process information about the process pointed to by OsiProcHandle
. Implementation should allocate memory and fill in the pointer to an OsiProc
struct. The returned OsiProc
can be freed with free_osiproc
.
Name: on_get_proc_mem
Signature:
typedef void (*on_get_proc_mem_t)(CPUState *, OsiProc *, OsiProcMem **)
Description: Called to retrieve process memory details. Currently only implemented in osi_linux. Returns a pointer to a static memory address, which will be overwritten on the next call to this function.
Name: on_get_current_thread
Signature:
typedef void (*on_get_current_thread_t)(CPUState *, OsiThread **)
Description: Called to retrieve the current thread from the guest OS. The implementation should allocate memory and fill in the pointer to an OsiThread
struct. The returned OsiThread
can be freed with free_osithread
.
Name: on_get_modules
Signature:
typedef void (*on_get_modules_t)(CPUState *, GArray **)
Description: Retrieves the kernel modules loaded in the guest OS, along with detailed information for each process. to get the process list from the guest OS.
Implementation behaviour: The implementation should populate a GArray
filled with OsiModule
elements, following the rules described in the data containers section above. Results need to be freed using g_array_free
.
Name: on_get_mappings
Signature:
typedef void (*on_get_mappings_t)(CPUState *, OsiProc *, GArray**)
Description: Retrieves the shared libraries loaded for the specified process of the guest OS. The process OsiProc
can be aquired via a previous call to on_get_current_process
or on_get_processes
.
Implementation behaviour: The implementation should populate a GArray
filled with OsiModule
elements, following the rules described in the data containers section above. Results need to be freed using g_array_free
.
Name: on_get_file_mappings
Signature:
typedef void (*on_get_file_mappings_t)(CPUState *, OsiProc *, GArray**)
Description: Similar to on_get_mappings, but returns only mappings that are backed by a file. In wintrospection this returns the same results as on_get_mappings. In osi_linux, this returns a subset of the mappings that on_get_mappings returns.
Implementation behaviour: The implementation should populate a GArray
filled with OsiModule
elements, following the rules described in the data containers section above. Results need to be freed using g_array_free
.
Name: on_get_heap_mappings
Signature:
typedef void (*on_get_heap_mappings_t)(CPUState *, OsiProc *, GArray**)
Description: Similar to on_get_mappings, but returns only mappings that are heap segments. Implemented only in osi_linux.
Implementation behaviour: The implementation should populate a GArray
filled with OsiModule
elements, following the rules described in the data containers section above. Results need to be freed using g_array_free
.
Name: on_get_stack_mappings
Signature:
typedef void (*on_get_stack_mappings_t)(CPUState *, OsiProc *, GArray**)
Description: Similar to on_get_mappings, but returns only mappings that are stack segments. Implemented only in osi_linux.
Implementation behaviour: The implementation should populate a GArray
filled with OsiModule
elements, following the rules described in the data containers section above. Results need to be freed using g_array_free
.
Name: on_get_unknown_mappings
Signature:
typedef void (*on_get_unknown_mappings_t)(CPUState *, OsiProc *, GArray**)
Description: Similar to on_get_mappings, but returns only mappings of an unknown origin. These can be additional heap segements for processes that have exhausted the primary heap segment. They can also be memory allocated via mmap() by a process. Implemented only in osi_linux.
Implementation behaviour: The implementation should populate a GArray
filled with OsiModule
elements, following the rules described in the data containers section above. Results need to be freed using g_array_free
.
Name: on_get_mapping_by_addr
Signature:
typedef void (*on_get_mapping_by_addr_t)(CPUState *, OsiProc *, target_ptr_t, OsiModule**)
Description: Returns the mapping that contains a specific virtual memory address.
The implementation should allocate memory and fill in the pointer to an OsiModule
struct. The returned OsiModule
must be freed with free_osimodule
.
Name: on_get_mapping_base_address_by_name
Signature:
typedef void (*on_get_mapping_base_address_by_name_t)(CPUState *, OsiProc *, char *, target_ptr_t *)
Description: Returns the base address of the mapping with the specified name.
Name: has_mapping_prefix
Signature:
typedef void (*on_has_mapping_prefix_t)(CPUState *, OsiProc *, char *, bool *)
Description: Returns true if a mapping exists whose name begins with the specified prefix.
To implement OS-specific introspection support, an OSI provider should call the following OSI APIs:
Name: notify_task_change
Signature:
void notify_task_change(CPUState *)
Description: Runs the task change callbacks.
Implementation behavior: The implementation should call this function after the current thread or process has changed.
The osi
plugin is not very useful on its own. If you want to see an example of how to use when writing your own plugins, have a look at osi_test.