« February 2010 | Main | April 2010 »

March 29, 2010

Scriptable plugins

In IDA Pro 5.6 we added support for loader scripts, last month we added processor module scripts support, and now by adding support for scriptable plugins (for the next version of IDA) it will be possible to write all sort of IDA Pro extensions using scripting languages.


(A plugin script written using IDC)

Background

Apart from writing scripts, writing plugin modules using the SDK is the second easiest extension to code for IDA Pro. In short, plugins are DLLs that export the PLUGIN symbol which is an instance of plugin_t (check loader.hpp):
  • init/run/term callbacks: These callbacks are called when IDA initializes, invokes and terminates the plugin.
  • flags: The plugin flags describe the kind of the plugin. A plugin could have no flags (flags = 0) which means that this is an ordinary plugin. Or the plugin could act as a processor module extension, thus the PLUGIN_PROC flag. Another type of plugins are the debugger plugins which use the PLUGIN_DBG flag.
  • Description and hotkey: The remaining plugin_t entries are used to designate a name, help text, comment, and a hotkey for the plugin.
The init callback is called when the plugin is loaded by IDA. It decides whether to load the plugin or skip it by returning one of the following constants:
  • PLUGIN_OK: Plugin agrees to work with the current database but will be loaded only when it is about to be invoked.
  • PLUGIN_SKIP: Plugin does not want to work with the current database. For example a plugin may choose to work with certain input file types.
  • PLUGIN_KEEP: Plugin agrees to work with the current database and will remain loaded until the database is closed. It is important to return this value if you hook to notification events or register any sort of callbacks.
A plugin can be invoked using its hotkey or programmatically using the RunPlugin() (in IDC) or run_plugin() (in the SDK / loader.hpp). Upon invocation, the run callback:
int idaapi run(int arg)
is executed. Before explaining the use of the arg argument, it is important to know that plugins can also be configured in the plugins.cfg file, which has the following format:
; plugin_name filename hotkey arg [flags] Extract_File extract 0 1 Extract_Block extract 0 2 Extract_Item extract Alt-F8 3
(In this example, the 'extract' plugin provide at least 3 different functionalities depending on the passed argument value)

If a plugin is not present in plugins.cfg and is invoked using the "Edit/Plugins" menu or with the hotkey described in its PLUGIN entry then run() will be invoked with arg=0. Configured plugins, when invoked, will pass to the run() the argument value specified in the configuration file.

Scriptable plugins

Scriptable plugins behave exactly like a native plugin (written using the SDK). To write a plugin using a scripting language, declare a function called PLUGIN_ENTRY that returns a plugin_t instance (or an object containing all attributes of a plugin_t object). This is an example script written in IDAPython:
import idaapi class myplugin_t(idaapi.plugin_t): flags = idaapi.PLUGIN_UNL comment = "This is a comment" help = "This is help" wanted_name = "My Python plugin" wanted_hotkey = "Alt-F8" def init(self): idaapi.msg("init() called!\n") return idaapi.PLUGIN_OK def run(self, arg): idaapi.msg("run() called with %d!\n" % arg) def term(self): idaapi.msg("term() called!\n") def PLUGIN_ENTRY(): return myplugin_t()
In this example, the plugin flag PLUGIN_UNL instructs IDA to directly unload the plugin after it has been invoked. This flag is very useful if your plugin script does a specific task and finishes. On the other hand, if your script works with forms (choosers, custom viewers, etc.), or registers callbacks (hotkeys, event notifications, etc.) then this flag should not be used and init() should return PLUGIN_KEEP (as discussed earlier) instead of PLUGIN_OK.

Scriptable plugins are so easy to write, and deploy (a simply copy/paste in the plugins folder) and very useful in case you want your plugin to load and unload automatically like native plugins.

March 25, 2010

Using custom viewers from IDAPython

Custom viewers can be used to display arbitrary textual information and can be used in any IDA plugin.They are used in IDA-View, Hex-View, Enum and struct views and the Hex-Rays decompiler.

In this blog entry we are going to write an ASM file viewer in order to demonstrate how to create a custom viewer and populate it with colored lines.
asmview.gif

Writing a custom viewer

The simplest custom viewer which does not handle any events (like key presses, mouse or cursor position movements, displaying hints, etc) can be created like this:
v = idaapi.simplecustviewer_t() if v.Create("Simple custom viewer"): for i in xrange(1, 11): v.AddLine("Line %d" % i) v.Show() else: print "Failed to create viewer"
If handling events is required then one has to derive from idaapi.simplecustviewer_t class and implement the required callbacks:
class mycv_t(simplecustviewer_t): def Create(self, sn=None): # Form the title title = "Simple custom view test" if sn: title += " %d" % sn # Create the customview if not simplecustviewer_t.Create(self, title): return False self.menu_hello = self.AddPopupMenu("Hello") self.menu_world = self.AddPopupMenu("World") for i in xrange(0, 100): self.AddLine("Line %d" % i) return True def OnKeydown(self, vkey, shift): # ESCAPE? if vkey == 27: self.Close() # Goto? elif vkey == ord('G'): n = self.GetLineNo() if n is not None: v = idc.AskLong(self.GetLineNo(), "Where to go?") if v: self.Jump(v, 0, 5) elif vkey == ord('R'): print "refreshing...." self.Refresh() else: return False return True def OnPopupMenu(self, menu_id): if menu_id == self.menu_hello: print "Hello" elif menu_id == self.menu_world: print "World" else: # Unhandled return False return True
To instantiate a custom viewer window:
view = mycv_t() if view.Create(1): view.Show()
Or many custom viewers:
def make_many(n): L = [] for i in xrange(1, n+1): v = mycv_t() if not v.Create(i): break v.Show() L.append(v) return L # Create 20 views V = make_many(20)
Please note that no two views should have the same title. To check if a window with a given title exists and then to close it, you can use:
f = idaapi.find_tform("Simple custom view test 2") if f: idaapi.close_tform(f, 0)

For a more comprehensive example on custom viewers, please check the ex_custview.py example.

Using colored lines

To use colored lines, we have to embed color tags to them. All the available foreground colors are defined in lines.hpp header file. The color codes are related to various item kinds in IDA, for example here are some colors:

Color name Description
SCOLOR_REGCMT Regular comment
SCOLOR_RPTCMT Repeatable comment
SCOLOR_INSN Instruction
SCOLOR_KEYWORD Keywords
SCOLOR_STRING String constant in instruction

There are also special color tags treated as escape sequence codes (the concept is similar to ANSI escape codes). They are used to determine how a line is rendered, to mark the beginning/end of a certain color, to insert address marks, or to mark UTF8 string beginnings/endings:

Color name Description
SCOLOR_ON Escape character (ON)
SCOLOR_OFF Escape character (OFF)
SCOLOR_INV Escape character (Inverse colors)
SCOLOR_UTF8 Following text is UTF-8 encoded
SCOLOR_STRING String constant in instruction

In the IDA SDK, a colored line is explained to have the following structure:
// A typical color sequence looks like this: // // COLOR_ON COLOR_xxx text COLOR_OFF COLOR_xxx
Luckily, we don't have to form the colored lines manually, instead we can use helper functions:
colored_line = idaapi.COLSTR("Hello", idaapi.SCOLOR_REG) + " " + idaapi.COLSTR("World", idaapi.SCOLOR_STRING)
If we look at colored_line contents we can see the following:
'\x01!Hello\x02! \x01\x0bWorld\x02\x0b'
Which is interpreted as:
COLOR_ON SCOLOR_REG=0x21 Hello COLOR_OFF COLOR=0x21 SPACE COLOR_ON SCOLOR_STRING=0x0B World COLOR_OFF COLOR=0x0B

In order to strip back color tags from a colored line, use tag_remove():

line = idaapi.tag_remove(colored_line)

Writing an ASM file viewer

Now that we covered all the needed information, let us write a very basic assembly file viewer. To accomplish the task, we need two things:
  1. ASM tokenizer: It should be able to recognize comments, strings and identifiers. For the identifiers, we will take into consideration only register names, instruction names and directives.
    • Instruction names: To get all the instruction names we use idautils.GetInstructionList() which returns all the instruction names from the processor module (the ph.instruc array)
    • Register names: Similarly we can use idautils.GetRegisterList()
  2. Custom viewer to render the text: We derive from simplecustviewer_t to handle key presses and popup menu actions

The tokenizer (asm_colorizer_t class) will go over the text and when it identifies a token it will call one of the following functions: as_string(), as_comment(), as_num(), and as_id(). Those functions will use idaapi.COLSTR() to colorize the token appropriately. At the end of each line, the tokenizer will call the add_line() method to add the line (after it has been colored).

The custom viewer (implemented by the asmview_t class) will inherit from both asm_colorizer_t and simplecustviewer_t:
class asmview_t(idaapi.simplecustview_t, asm_colorizer_t): def Create(self, fn): # Create the customview if not idaapi.simplecustview_t.Create(self, "ASM View - %s" % os.path.basename(fn)): return False self.instruction_list = idautils.GetInstructionList() self.instruction_list.extend(["ret"]) self.register_list = idautils.GetRegisterList() self.register_list.extend(["eax", "ebx", "ecx", "edx", "edi", "esi", "ebp", "esp"]) self.fn = fn if not self.reload_file(): return False self.id_refresh = self.AddPopupMenu("Refresh") self.id_close = self.AddPopupMenu("Close") return True def reload_file(self): if not self.colorize_file(self.fn): self.Close() return False return True def colorize_file(self, fn): try: f = open(fn, "r") lines = f.readlines() f.close() self.ClearLines() self.colorize(lines) return True except: return False def add_line(self, s=None): if not s: s = "" self.AddLine(s) def as_comment(self, s): return idaapi.COLSTR(s, idaapi.SCOLOR_RPTCMT) def as_id(self, s): t = s.lower() if t in self.register_list: return idaapi.COLSTR(s, idaapi.SCOLOR_REG) elif t in self.instruction_list: return idaapi.COLSTR(s, idaapi.SCOLOR_INSN) else: return s def as_string(self, s): return idaapi.COLSTR(s, idaapi.SCOLOR_STRING) def as_num(self, s): return idaapi.COLSTR(s, idaapi.SCOLOR_NUMBER) def as_directive(self, s): return idaapi.COLSTR(s, idaapi.SCOLOR_KEYWORD) def OnPopupMenu(self, menu_id): if self.id_refresh == menu_id: return self.reload_file() elif self.id_close == menu_id: self.Close() return True return False def OnKeydown(self, vkey, shift): # ESCAPE if vkey == 27: self.Close() return True return False

This blog entry inspired you to write a new plugin? Feel free to participate in our plugin contest!

The ASM viewer script can be downloaded from here (note: it requires IDAPython r289).

March 10, 2010

Preview of the new cross-platform IDA Pro GUI

In order to provide our customers with the best user experience and in order to target many different platforms, the IDA Pro graphical user interface is currently being rewritten using the Qt technology.

Qt (pronounced "cute") is a cross-platform application and UI framework and the Win32 VCL-based IDA Pro interface is being ported to it. The goal is to provide all the features available in the current GUI while maintaining the maximum compatibility with plugins and other external modules.

Here is a screenshot of the current build of idaqt running on Ubuntu:

idaqt_preview_100310_thumb_1.jpg

You can click on the images to enlarge them.

From the first version idaqt will include a fully functional graphing, which is, as it is possible to notice from the screenshot, already implemented. The same is true for hints, navigation band and all other advanced IDA Pro features.

idaqt_preview_100310_thumb_2.jpg

This is idaqt on Windows 7. The text view looks exactly the same and all other features like choosers and forms will be available with no exception on all supported platforms.

idaqt_preview_100310_thumb_3.jpg

The full range of options and customizations which the Win32 interface provides will be available as well.

As you can see, apart from the still to be implemented docking, the new interface looks pretty much the same as the Win32 one.

Not only will it be possible to deploy the same native graphical interface to Windows, OS X, Linux and other platforms which in the future may become popular, but the quality of the user experience and the further development capabilities will be hugely increased thanks to an advanced framework such as Qt.

Although idaqt is going to replace the current GUI completely, for some time they will be deployed together in order to fix any incompatibility issues and to give third party developers the necessary time to thoroughly test their products against the new interface.