Chapter 17. The IDA Plug-in Architecture

image with no caption

Over the course of the next few chapters, we will cover the types of modules that can be constructed using the IDA SDK. We will also discuss new features (since IDA 5.7) that allow for the development of these same types of modules using one of IDA’s scripting languages. Whether you ever intend to create your own plug-ins or not, a basic understanding of plug-ins will greatly enhance your experience using IDA, since, arguably, the majority of third-party software developed for use with IDA is distributed in the form of plug-ins. In this chapter, we begin the exploration of IDA modules by discussing the purpose of IDA plug-ins, along with how to build, install, and configure them.

Plug-ins are probably best described as the compiled, albeit more powerful, equivalents of IDA scripts. Plug-ins are usually associated with a hotkey and/or a menu item and are typically accessible only after a database has been opened. Individual plug-ins may be general purpose in nature and useful across a wide variety of binary file types and processor architectures, or they may be very specialized, designed to be used only with a specific file format or processor type. In all cases, by virtue of being compiled modules, plug-ins have full access to the IDA API and can generally perform much more complex tasks than you could ever hope to accomplish using scripting alone.

Writing a Plug-in

All IDA modules, including plug-ins, are implemented as shared library components appropriate to the platform on which the plug-in is expected to execute. Under IDA’s modular architecture, modules are not required to export any functions. Instead, each module type must export a variable of a specific class. In the case of plug-ins, this class is called a plugin_t and is defined in the SDK’s loader.hpp file.

In order to understand how to create a plug-in, you must first understand the plugin_t class and its component data fields (the class has no member functions). The layout of the plugin_t class is shown here, with comments taken from loader.hpp:

class plugin_t {
public:
  int version;          // Should be equal to IDP_INTERFACE_VERSION
  int flags;            // Features of the plugin
  int (idaapi* init)(void); // Initialize plugin
  void (idaapi* term)(void);   // Terminate plugin. This function will be called
                            // when the plugin is unloaded. May be NULL.
  void (idaapi* run)(int arg); // Invoke plugin
  char *comment;               // Long comment about the plugin
  char *help;           // Multiline help about the plugin
  char *wanted_name;    // The preferred short name of the plugin
  char *wanted_hotkey;  // The preferred hotkey to run the plugin
};

Every plug-in must export a plugin_t object named PLUGIN. Exporting your PLUGIN object is handled by loader.hpp, which leaves you responsible for declaring and initializing the actual object. Since successful plug-in creation relies on properly initializing this object, we describe the purpose of each member here. Note that even if you prefer to take advantage of IDA’s new scripted plug-in capabilities, you will still need to familiarize yourself with each of these fields because they are used in scripted plug-ins as well.

version

This member indicates the version number of the API that was used to build the plug-in. It is typically set to the constant IDP_INTERFACE_VERSION, which is declared in idp.hpp. The value of this constant has not changed since the API was standardized with SDK version 4.9. The original intent of this field was to prevent plug-ins created with earlier versions of an SDK from being loaded into versions of IDA built with newer versions of the SDK.

flags

This field contains various flags indicating how IDA should treat the plug-in in various situations. The flags are set using a bitwise combination of the PLUGIN_XXX constants defined in loader.hpp. For many plug-ins, assigning zero to this field will be sufficient. Please refer to loader.hpp for the meanings of each flag bit.

init

This is the first of three function pointers contained in the plugin_t class. This particular member is a pointer to the plug-in’s initialization function. The function takes no parameters and returns an int. IDA calls this function to offer your plug-in a chance to be loaded. Initialization of plug-ins is discussed in Plug-in Initialization in Plug-in Initialization.

term

This member is another function pointer. IDA calls the associated function when your plug-in is unloaded. The function takes no arguments and returns no value. The purpose of this function is to perform any cleanup tasks (deallocating memory, closing handles, saving state, and so on) required by your plug-in before IDA unloads it. This field may be set to NULL if you have no actions to perform when your plug-in is unloaded.

run

This member points to the function that should be called whenever a user activates (via a hotkey, menu item, or script invocation) your plug-in. This function is the heart of any plug-in, because it is here that the behaviors users associate with the plug-in are defined. This is the function that bears the most resemblance to scripted behaviors. The function receives a single integer parameter (discussed later under Plug-in Execution in Plug-in Execution) and returns nothing.

comment

This member is a pointer to a character string that serves as a comment for the plug-in. It is not used directly by IDA and can safely be set to NULL.

help

This member is a pointer to a character string that serves as a multiline help string. It is not used directly by IDA and can safely be set to NULL.

wanted_name

This member is a pointer to a character string that holds the name of the plug-in. When a plug-in is loaded, this string is added to the Edit ▸ Plugins menu as a means of activating the plug-in. There is no requirement for the name to be unique among loaded plug-ins, though it is difficult to determine which of two identically named plug-ins will be activated when the name is selected from the menu.

wanted_hotkey

This member is a pointer to a character string that holds the name of the hotkey (such as "Alt-F8") that IDA will attempt to associate with the plugin. Here again, there is no need for this value to be unique among loaded plug-ins; however; if the value is not unique, the hotkey will be associated with the last plug-in to request it. Configuring Plug-ins in Configuring Plug-ins discusses how users may override the wanted_hotkey value.

An example of initializing a plugin_t object is shown here:

int idaapi idaboook_plugin_init(void);
void idaapi idaboook_plugin_term(void);
void idaapi idaboook_plugin_run(int arg);

char idabook_comment[] = "This is an example of a plugin";
char idabook_name[] = "Idabook";
char idabook_hotkey = "Alt-F9";

plugin_t PLUGIN = {
   IDP_INTERFACE_VERSION, 0, idaboook_plugin_init, idaboook_plugin_term,
    idaboook_plugin_run, idabook_comment, NULL, idabook_name, idabook_hotkey
};

The function pointers included in the plugin_t class allow IDA to locate required functions in your plug-in without requiring you to export those functions or to choose specific names for those functions.

The Plug-in Life Cycle

A typical IDA session begins with the launch of the IDA application itself and proceeds through loading and analyzing a new binary file or existing database before settling down to wait for user interaction. During this process, there are three distinct points at which IDA offers plug-ins a chance to load:

  1. A plug-in may load immediately upon IDA startup, regardless of whether a database is being loaded or not. Loading in this manner is controlled by the presence of the PLUGIN_FIX bit in PLUGIN.flags.

  2. A plug-in may load immediately following a processor module and remain loaded until the processor module is unloaded. Tying a plug-in to a processor module is controlled by the PLUGIN_PROC bit in PLUGIN.flags.

  3. In the absence of the flag bits just mentioned, IDA offers plug-ins the opportunity to load each time a database is opened in IDA.

IDA offers plug-ins the opportunity to load by calling PLUGIN.init. When called, the init function should determine whether the plug-in is designed to be loaded given the current state of IDA. The meaning of current state varies depending on which of the three preceding situations are applicable when the plug-in is being loaded. Examples of states that a plug-in may be interested in include the input file type (a plug-in may be designed specifically for use with PE files, for example) and the processor type (a plug-in may be designed exclusively for use with x86 binaries).

To indicate its desires to IDA, PLUGIN.init must return one of the following values defined in loader.hpp.

PLUGIN_SKIP Returning this value signals that the plug-in should not be loaded.
PLUGIN_OK Returning this value instructs IDA to make the plug-in available for use with the current database. IDA loads the plug-in when the user activates the plug-in using a menu action or a hotkey.
PLUGIN_KEEP Returning this value instructs IDA to make the plug-in available for use with the current database and keep the plug-in loaded in memory.

Once a plug-in has been loaded, it may be activated in one of two ways. The most frequent method of activating a plug-in is at the direction of the user in response to a menu selection or hotkey activation. Each time a plug-in is activated in this way, IDA passes control to the plug-in by calling PLUGIN.run. An alternate method for plug-in activation is for the plug-in to hook into IDA’s event-notification system. In such cases, a plug-in must express interest in one or more types of IDA events and register a callback function to be called by IDA when any event of interest occurs.

When it is time for a plug-in to be unloaded, IDA calls PLUGIN.term (assuming it is non-NULL). The circumstances under which a plug-in is unloaded vary according to the bits set in PLUGIN.flags. Plug-ins that specify no flag bits are loaded according to the value returned by PLUGIN.init. These types of plug-ins are unloaded when the database for which they were loaded is closed.

When a plug-in specifies the PLUGIN_UNL flag bit, the plug-in is unloaded after each call to PLUGIN.run. Such plug-ins must be reloaded (resulting in a call to PLUGIN.init) for each subsequent activation. Plug-ins that specify the PLUGIN_PROC flag bit are unloaded when the processor module for which they were loaded is unloaded. Processor modules are unloaded whenever a database is closed. Finally, plug-ins that specify the PLUGIN_FIX flag bit are unloaded only when IDA itself terminates.

Plug-in Initialization

Plug-ins are initialized in two phases. Static initialization of plug-ins takes place at compile time, while dynamic initialization takes place at load time via actions performed within PLUGIN.init. As discussed earlier, the PLUGIN.flags field, which is initialized at compile time, dictates several behaviors of a plug-in.

When IDA is launched, the PLUGIN.flags field of every plug-in in <IDADIR>/plugins is examined. At this point, IDA calls PLUGIN.init for each plug-in that specifies the PLUGIN_FIX flag. PLUGIN_FIX plug-ins are loaded before any other IDA module and therefore have the opportunity to be notified of any event that IDA is capable of generating, including notifications generated by loader modules and processor modules. The PLUGIN.init function for such plug-ins should generally return either PLUGIN_OK or PLUGIN_KEEP, because it makes little sense to request it to be loaded at startup only to return PLUGIN_SKIP in PLUGIN.init.

However, if your plug-in is designed to perform a one-time initialization task at IDA startup, you may consider performing that task in the plug-in’s init function and returning PLUGIN_SKIP to indicate that the plug-in is no longer needed.

Each time a processor module is loaded, IDA samples the PLUGIN_PROC flag in every available plug-in and calls PLUGIN.init for each plug-in in which PLUGIN_PROC is set. The PLUGIN_PROC flag allows plug-ins to be created that respond to notifications generated by processor modules and thereby supplement the behavior of those modules. The PLUGIN.init function for such modules has access to the global processor_t object, ph, which may be examined and used to determine whether the plug-in should be skipped or retained. For example, a plug-in designed specifically for use with the MIPS processor module should probably return PLUGIN_SKIP if the x86 processor module is being loaded, as shown here:

int idaapi mips_init() {
   if (ph.id != PLFM_MIPS) return PLUGIN_SKIP;
   else return PLUGIN_OK;  //or, alternatively PLUGIN_KEEP
}

Finally, each time a database is loaded or created, the PLUGIN.init function for each plug-in that has not already been loaded is called to determine whether the plug-in should be loaded or not. At this point each plug-in may use any number of criteria to determine whether IDA should retain it or not. Examples of specialized plug-ins include those that offer behavior specific to certain file types (ELF, PE, Mach-O, etc.), processor types, or compiler types.

Regardless of the reason, when a plug-in decides to return PLUGIN_OK (or PLUGIN_KEEP), the PLUGIN.init function should also take care of any one-time initialization actions necessary to ensure that the plug-in is capable of performing properly when it is eventually activated. Any resources that are requested by PLUGIN.init should be released in PLUGIN.term. A major difference between PLUGIN_OK and PLUGIN_KEEP is that PLUGIN_KEEP prevents a plug-in from being repeatedly loaded and unloaded and thus reduces the need to allocate, deallocate, and reallocate resources as might be required when a plug-in specifies PLUGIN_OK. As a general rule of thumb, PLUGIN.init should return PLUGIN_KEEP when future invocations of the plug-in may depend on states accumulated during previous invocations of the plug-in. A workaround for this is for plug-ins to store any state information in the open IDA database using a persistent storage mechanism such as netnodes. Using such a technique, subsequent invocations of the plug-in can locate and utilize data stored by earlier invocations of the plug-in. This method has the advantage of providing persistent storage not only across invocations of the plug-in but also across IDA sessions.

For plug-ins in which each invocation is completely independent of any previous invocations, it is often suitable for PLUGIN.init to return PLUGIN_OK, which has the advantage of reducing IDA’s memory footprint by keeping fewer modules loaded in memory at any given time.

Event Notification

While plug-ins are quite frequently activated directly by a user via a menu selection (Edit ▸ Plugins) or through the use of a hotkey, IDA’s event-notification capabilities offer an alternative means of activating plug-ins.

When you want your plug-ins to be notified of specific events that take place within IDA, you must register a callback function to express interest in specific event types. The hook_to_notification_point function is used to inform IDA (1) that you are interested in a particular class of events and (2) that IDA should call the function that you indicate each time an event in the indicated class occurs. An example of using hook_to_notification_point to register interest in database events is shown here:

//typedef for event hooking callback functions (from loader.hpp)
typedef int idaapi hook_cb_t(void *user_data, int notification_code, va_list va);
//prototype for  hook_to_notification_point (from loader.hpp)
bool hook_to_notification_point(hook_type_t hook_type,
                                hook_cb_t *callback,
                                void *user_data);
int idaapi idabook_plugin_init() {
   //Example call to  hook_to_notification_point
   hook_to_notification_point(HT_IDB, idabook_database_cb, NULL);
}

Four broad categories of notification exist: processor notifications (idp_notify in idp.hpp, HT_IDP), user interface notifications (ui_notification_t in kernwin.hpp, HT_UI), debugger events (dbg_notification_t in dbg.hpp, HT_DBG), and database events (idp_event_t in idp.hpp, HT_IDB). Within each event category are a number of individual notification codes that represent specific events for which you will receive notifications. Examples of database (HT_IDB) notifications include idb_event::byte_patched, to indicate that a database byte has been patched, and idb_event::cmt_changed, to indicate that a regular or repeatable comment has been changed. Each time an event occurs, IDA invokes each registered callback function, passing the specific event-notification code and any additional parameters specific to the notification code. Parameters supplied for each notification code are detailed in the SDK header files that define each notification code.

Continuing the preceding example, we might define a callback function to handle database events as follows:

int idabook_database_cb(void *user_data, int notification_code, va_list va) {
     ea_t addr;
     ulong original, current;
     switch (notification_code) {
        case idb_event::byte_patched:
         addr = va_arg(va, ea_t);
           current = get_byte(addr);
           original = get_original_byte(addr);
           msg("%x was patched to %x.  Original value was %x
",
                addr, current, original);
           break;
     }
     return 0;
  }

This particular example recognizes only the byte_patched notification message, for which it prints the address of the patched byte, the new value of the byte, and the original value of the byte. Notification callback functions make use of the C++ variable arguments list, va_list, to provide access to a variable number of arguments, depending on which notification code is being sent to the function. The number and type of arguments provided for each notification code are specified in the header files in which each notification code is defined. The byte_patched notification code is defined in loader.hpp to receive one argument of type ea_t in its va_list. The C++ va_arg macro should be used to retrieve successive arguments from a va_list. The address of the patched byte is retrieved from the va_list at in the preceding example.

An example of unhooking from database notification events is shown here:

void idaapi idabook_plugin_term() {
   unhook_from_notification_point(HT_IDB, idabook_database_cb, NULL);
}

All well-behaved plug-ins should unhook any notifications whenever the plug-in is unloaded. This is one of the intended purposes of the PLUGIN.term function. Failure to unhook all of your active notifications will almost certainly result in crashing IDA shortly after your plug-in is unloaded.

Plug-in Execution

Thus far we have discussed several instances in which IDA calls functions belonging to a plug-in. Plug-in loading and unloading operations result in calls to PLUGIN.init and PLUGIN.term, respectively. User plug-in activation via the Edit ▸ Plugins menu or the plug-in’s associated hotkey results in a call to PLUGIN.run. Finally, callback functions registered by a plug-in may be called in response to various events that take place within IDA.

Regardless of how a plug-in comes to be executed, it is important to understand a few essential facts. Plug-in functions are invoked from IDA’s main event-processing loop. While a plug-in is executing, IDA cannot process events, including queued analysis tasks or updates to the user interface. Therefore it is important that your plug-in perform its task as expeditiously as possible and return control to IDA. Otherwise IDA will be completely unresponsive, and there will be no way to regain control. In other words, once your plug-in is executing, there is no simple way to break out of it. You must either wait for your plug-in to complete or kill your IDA process. In the latter case, you are likely to have an open database on your hands that may or may not be corrupt and may or may not be repairable by IDA. The SDK offers three functions that you may use to work around this issue. The show_wait_box function may be called to display a dialog that displays the message Please wait. . . along with a Cancel button. You may periodically test whether the user pressed the Cancel button by calling the wasBreak function. The advantage to this approach is that when wasBreak is called, IDA will take the opportunity to update its user interface, and it allows your plug-in the opportunity to decide whether it should stop the processing that it is doing. In any case, you must call hide_wait_box to remove the Wait dialog from the display.

Do not attempt to get creative in your plug-ins by having your PLUGIN.run function create a new thread to handle the processing within your plug-in. IDA is not thread safe. There are no locking mechanisms in place to synchronize access to the many global variables used by IDA, nor are there any locking mechanisms to ensure the atomicity of database transactions. In other words, if you did create a new thread, and you used SDK functions to modify the database from within that thread, you could corrupt the database, because IDA might be in the middle of its own modification to the database that conflicts with your attempted changes.

Keeping these limitations in mind, for most plug-ins, the bulk of the work performed by the plug-in will be implemented within PLUGIN.run. Building on our previously initialized PLUGIN object, a minimal (and boring) implementation for PLUGIN.run might look like the following:

void idaapi idabook_plugin_run(int arg) {
   msg("idabook plugin activated!
");
}

Every plug-in has the C++ and IDA APIs at its disposal. Additional capabilities are available by linking your plug-in with appropriate platform-specific libraries. For example, the complete Windows API is available for plug-ins developed to run with Windows versions of IDA. To do something more interesting than printing a message to the output window, you need to understand how to accomplish your desired task using available functions from the IDA SDK. Taking the code from Example 16-6, for example, we might develop the following function:

void idaapi extended_plugin_run(int arg) {
   func_t *func = get_func(get_screen_ea());  //get function at cursor location
   msg("Local variable size is %d
", func->frsize);
   msg("Saved regs size is %d
", func->frregs);
   struc_t *frame = get_frame(func);          //get pointer to stack frame
   if (frame) {
      size_t ret_addr = func->frsize + func->frregs;  //offset to return address
      for (size_t m = 0; m < frame->memqty; m++) {    //loop through members
         char fname[1024];
         get_member_name(frame->members[m].id, fname, sizeof(fname));
         if (frame->members[m].soff < func->frsize) {
            msg("Local variable ");
         }
         else if (frame->members[m].soff > ret_addr) {
            msg("Parameter ");
         }
         msg("%s is at frame offset %x
", fname, frame->members[m].soff);
         if (frame->members[m].soff == ret_addr) {
            msg("%s is the saved return address
", fname);
         }
      }
   }
}

Using this function, we now have the core of a plug-in that dumps stack frame information for the currently selected function each time the plug-in is activated.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.148.144.100