Skip to content

Latest commit

 

History

History
376 lines (240 loc) · 14.5 KB

README.md

File metadata and controls

376 lines (240 loc) · 14.5 KB

Plugin: osi

Summary

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.

Command Line Arguments

  • 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
  };

Plugin Arguments

  • 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.

Dependencies

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.

Data Interface

Data types

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;

Data allocation

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.

Data containers

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. using g_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.

APIs and Callbacks

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.


Example

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.