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.
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.
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:
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
.
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
.
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-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.
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.
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.
3.148.144.100