How to create a plugin in IDAPython?

Intro

IDAPython API gives you a way to enhance basic IDA capabilities and write your own plugins. These plugins can run directly in IDA as single scripts in the output window or even use complex UI.

IDAPython plugins are faster and easier to develop than C++ SDK plugins (thanks to excluding the build and compilation process) and offer almost the same powerful capabilities as native C++ plugins.

This tutorial will share best practices on how to start writing plugin in IDAPython, acccording to our new plugin framwork and best practises, that will streamline your plugin development.

Before you start

  1. Check our IDAPython reference docs for an up-to-date list of all modules, classes, functions, and so on.

  2. Familiarize yourself with the ida_idaapi.plugin_t class, a basic and required class that provides the necessary structure for your plugin. It mirrors the C++ SDK plugin_t class.

  3. Make sure your plugin will be compatible with the newest version of IDA—check our IDAPython Porting Guide for recent changes.

Get a sample plugin

For this tutorial, we'll use the simple, exemplary plugin which adapted the new plugin framework that simplifies plugin development. It performs one specific task when called by the user after a database is loaded: it lists all functions and their addresses for the current IDA database and then terminates.

You can download "My First Plugin" from here:

Writing a plugin in IDAPython—basic steps

Create a single .py file to start

The minimum that your plugin should contain is a single Python file that will serve as an entry point. This file should define the main logic and include necessary imports and primary functions that will be executed when the plugin runs.

Define base classes

When creating a plugin, it's recommended to create a class inheriting from plugin_t with specific flags and a class inheriting from plugmod_t that performs the core functionality.

Define a class that inherits from ida_idaapi.plugin_t

While creating a plugin, you should include a class that inherits from plugin_t. This base class will outline the core functionality and lifecycle of your plugin.

Example of plugin_t class implementation:

class MyPlugin(ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_UNL | ida_idaapi.PLUGIN_MULTI
    comment = "This is my first simple IDA Pro plugin"
    help = "This plugin lists all functions in the current database"
    wanted_name = "My First Plugin"
    wanted_hotkey = "Shift-P"

    def init(self):
        print(">>>MyPlugin: Init called.")
        return MyPlugmod()

Define a subclass that inherits from ida_idaapi.plugmod_t

When creating a plugin in the new framework, it's recommended to subclass plugmod_t, that performs the main task of the plugin.

Example of plugmod_t class implementation:

class MyPlugmod(ida_idaapi.plugmod_t):
    def __del__(self):
        print(">>> MyPlugmod: destructor called.")
    
    def run(self, arg):
        print(">>> MyPlugmod.run() is invoked with argument value: {arg}.")
        for func_ea in idautils.Functions():
            func_name = ida_funcs.get_func_name(func_ea)
            print(f">>>MyPlugmod: Function{func_name} at address {func_ea:x}")

Overview of MyPlugin class attributes

flags attribute

The flags attribute defines the behavior and plugin properties, and what is crucial, describe its lifecycle: how and when it is loaded into IDA.

Your plugin may have no flags (flags = 0). It is usually a good strategy for basic plugins that perform a specific task once and then are no longer needed. Assigning 0 to flags apply a default behavior to your plugin:

  • it can be loaded and reloaded at any time;

  • it is triggered by the user and does not run constantly in the background;

  • it does not modify the database.

Common flags for plugins:

  • PLUGIN_MULTI: Recommended for all plugins; this flag enables the plugin to run simultaneously across multiple opened IDBs within the same IDA instance.

  • PLUGIN_FIX: The plugin loads when IDA launches and stays loaded until IDA exits.

  • PLUGIN_DRAW: The plugin needs to be invoked for every screen refresh.

  • PLUGIN_MOD: The plugin modifies the IDA database.

  • PLUGIN_PROC: The plugin is a processor module extension.

  • PLUGIN_DBG: The plugin will be loaded only when a debugger is active (for debugger-related plugins)

  • PLUGIN_UNL: The plugin will be unloaded immediately after calling the run method.

In our example, we used PLUGIN_UNL flag, as after performing a specific task—listing functions in the current database—is no longer needed.

For a full list of available flags, refer to the ida_idaapi module.

comment attribute The comment attribute allows you to provide a brief description of your plugin.

wanted_name attribute The wanted_name attribute specifies the preferred short name of the plugin, as it apperas under Edit -> Plugins submenu.

wanted_hotkey attribute The wanted_hotkey attribute specifies a preferred shortcut to run the plugin.

The preferred name and hotkey may be overridden by changing the settings in the plugins.cfg file.

Specify your plugin lifecycle

Below we scrutinize the key components for defining your plugin lifecycle.

Define a PLUGIN_ENTRY function Declare a function called PLUGIN_ENTRY that returns a plugin_t instance (or an object containing all attributes of a plugin_t object).

Initialization

The init() method is called when the plugin is loaded into IDA and is responsible for initializing your plugin.

The init() method returns an pointer to a plugmod_t object and indicate that this object run method is going to be used.

In the new plugin framework, run/term functions of plugin_t are not used. Virtual functions of plugmod_t are used instead.

In our example, when MyPlugin.init() is called it initalizes the plugin and returns a new instance of MyPlugmod.

    def init(self):
        print(">>>MyPlugin: Init called.")
        return MyPlugmod()

Activation

The run method is executed when the user triggers your plugin activation, whether via hotkey or Edit -> Plugins submenu.

An alternative way of activation your plugin is via IDA events and registering a callback functions.

In our example, when the run() method of MyPlugmod is called, it prints function names and addresses in Output window.

    def run(self, arg):
        print(">>> MyPlugmod.run() is invoked with argument value: {arg}.")
        for func_ea in idautils.Functions():
            func_name = ida_funcs.get_func_name(func_ea)
            print(f">>>MyPlugmod: Function{func_name} at address {func_ea:x}")

Unloading

The __del__() is called automatically when the plugin is going to be unloaded (destroyed). The conditions that define the circumstances under which the plugin should be unloaded depend on the flags setting.

Example of plugin lifecycle implementation:

class MyPlugmod(ida_idaapi.plugmod_t):
    def __del__(self):
        print(">>> MyPlugmod: destructor called.")
    
    def run(self, arg):
        print(">>> MyPlugmod.run() is invoked with argument value: {arg}.")
        for func_ea in idautils.Functions():
            func_name = ida_funcs.get_func_name(func_ea)
            print(f">>>MyPlugmod: Function{func_name} at address {func_ea:x}")


class MyPlugin(ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_UNL | ida_idaapi.PLUGIN_MULTI
    comment = "This is my first simple IDA Pro plugin"
    help = "This plugin lists all functions in the current database"
    wanted_name = "My First Plugin"
    wanted_hotkey = "Shift-P"

    def init(self):
        print(">>>MyPlugin: Init called.")
        return MyPlugmod()


def PLUGIN_ENTRY():
    return MyPlugin()

In our example:

  • PLUGIN_ENTRY() returns an instance of the MyPlugin class.

  • MyPlugin.init() is called to initialize the plugin and returns an instance of MyPlugmod. MyPlugmod.run() is called when the plugin is activated by the user via the hotkey or the Plugins menu. Then, the run() method of MyPlugmod is called, which prints function names and addresses in the current IDA database.

  • MyPlugmod.__del__() is called automatically when the plugin is destroyed (unloaded), and it prints a termination message. As we defined in flags, our exemplary plugin unloads directly after performing its task.

Include an ida-plugin.json file

It's recommended to add an ida-plugin.json file to your plugin directory with essential metadata to ensure consistency, ease the organization of your plugin files (allows the plugin to be self-contained in its own sub-directory), and smooth the process for accepting your plugin into our official plugin repository, if you decide to share it with Hex-Rays community.

To work properly, the ida-plugin.json file must contain at the very least the IDAMetadataDescriptorVersion field as well as a plugin object containing the name and entryPoint fields.

The name will be used to identify the plugin and also generate a namespace name for it if necessary (e.g. an IDAPython plugin). The namespace name is generated by converting all non alphanumeric characters of the plugin name to underscores (_) and prepending __plugins__ to it. For example "my plugin" would become __plugins__my_plugin.

The entryPoint must be the filename of the "main" file for the plugin. It should be stored in the same directory as its ida-plugin.json file.

If the entryPoint has no file extension, IDA will assume it is a native plugin and append the appropriate file extension for dynamic shared objects for the host platform (.dll, .so, .dylib).

Example of the ida-plugin.json file:

{
  "IDAMetadataDescriptorVersion": 1,
  "plugin": {
    "name": "My First Plugin",
    "entryPoint": "my-first-plugin.py"
  }
}

Install and execute your plugin

  1. Copy the plugin directory (in our exemplary case, the folder containing my-first-plugin.py and ida.plugin.json files) or single script to plugins directory in your IDA installation folder. Once it's done, you may need to restart your IDA to see your plugin name under Edit -> Plugins submenu.

  2. Run the plugin by pressing the specified hotkey or execute it from Edit -> Plugins -> <your_plugin>.

Last updated