In this blog post we are going to illustrate how to use the command line interpreter (CLI) interface from Python and how to write a basic command completion functionality for the Python CLI.
IDA Pro SDK allows programmers to implement command line interpreters with the use of the cli_t structure:
struct cli_t { size_t size; // Size of this structure int32 flags; // Feature bits const char *sname; // Short name (displayed on the button) const char *lname; // Long name (displayed in the menu) const char *hint; // Hint for the input line // callback: the user pressed Enter bool (idaapi *execute_line)(const char *line); // callback: the user pressed Tab (optional) bool (idaapi *complete_line)( qstring *completion, const char *prefix, int n, const char *line, int prefix_start); // callback: a keyboard key has been pressed (optional) bool (idaapi *keydown)( qstring *line, int *p_x, int *p_sellen, int *vk_key, int shift); };
For example, the IDAPython plugin defines its CLI like this:
static const cli_t cli_python = { sizeof(cli_t), 0, "Python", "Python - IDAPython plugin", "Enter any Python expression", IDAPython_cli_execute_line, NULL, // No completion support NULL // No keydown support };
And registers/unregisters it with:
void install_command_interpreter(const cli_t *cp); void remove_command_interpreter(const cli_t *cp);
The CLI functionality has been added in IDAPython 1.4.1. Let us suppose that we want to reimplement the Python CLI in Python (rather than in C++), we would do it like this:
class pycli_t(idaapi.cli_t): flags = 0 sname = "PyPython" lname = "Python - PyCLI" hint = "Enter any Python statement" def OnExecuteLine(self, line): """ The user pressed Enter. @param line: typed line(s) @return Boolean: True-executed line, False-ask for more lines """ try: exec(line, globals(), globals()) except Exception, e: print str(e) + "\n" + traceback.format_exc() return True
Summary:
To try this code, simply instantiate a CLI and register it:
pycli = pycli_t() pycli.register()
And later to unregister the CLI:
pycli.unregister()
In order to add command completion, we need to implement the OnCompleteLine() callback:
class cli_t: def OnCompleteLine(self, prefix, n, line, prefix_start): """ The user pressed Tab. Find a completion number N for prefix PREFIX This callback is optional. @param prefix: Line prefix at prefix_start (string) @param n: completion number (int) @param line: the current line (string) @param prefix_start: the index where PREFIX starts in LINE (int) @return: None or a String with the completion value """ print "OnCompleteLine: pfx=%s n=%d line=%s pfx_st=%d" % (prefix, n, line, prefix_start) return None
This callback is invoked everytime the user presses TAB in the CLI. The callback receives:
For example typing “os.path.ex” and pressing TAB would call:
OnCompleteLine(prefix="ex", n=0, line="print os.path.ex", prefix_start=14)
For demonstration purposes, here is a very simple algorithm to guess what could complete the “os.path.ex” expression:
def parse_identifier(line, prefix, prefix_start): id_start = prefix_start while id_start > 0: ch = line[id_start] if not ch.isalpha() and ch != '.' and ch != '_': id_start += 1 break id_start -= 1 return line[id_start:prefix_start + len(prefix)]
def get_completion(id, prefix): try: parts = id.split('.') m = sys.modules['__main__'] for i in xrange(0, len(parts)-1): m = getattr(m, parts[i]) except Exception, e: return None else: completion = [x for x in dir(m) if x.startswith(prefix)] return completion if len(completion) else None
def OnCompleteLine(self, prefix, n, line, prefix_start): # new search? if n == 0: self.n = n id = self.parse_identifier(line, prefix, prefix_start) self.completion = self.get_completion(id, prefix) return None if (self.completion is None) or (n >= len(self.completion)) else \ self.completion[n]
With this approach we could complete something like this:
f = open('somefile.txt', 'r') f.re^TAB => f.read, f.readinto, f.readline or f.readlines
Or:
print idau^TAB.GetRe^TAB => print idautils.GetRegisterList
The sample script can be downloaded from here and a win32 build of the latest IDAPython plugin (with completion integrated in the plugin) can be downloaded from here.