diff --git a/CHANGES.txt b/CHANGES.txt index 1f26313..9c2a28e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,15 @@ Please see http://code.google.com/p/idapython/source/list for a detailed list of changes. +Changes from version 1.5.0 to 1.5.1 +------------------------------------ +- Introduced the CLI '?' pseudo-command to retrieve doc strings +- Introduced the CLI '!' pseudo-command to shell execute a command +- bugfix: High 64 bit addresses were not parsed correctly in IDA64 +- Added IDP/assemble notification event +- bugfix: AskUsingForm() C function was not wrapped by SWIG +- NextHead()/PrevHead() have optional 2nd parameter now + Changes from version 1.4.3 to 1.5.0 ------------------------------------ - IDA Pro 6.1 support @@ -17,6 +26,7 @@ Changes from version 1.4.3 to 1.5.0 - bugfix: processor_t.id constants were incorrect - bugfix: get_debug_names() was broken with IDA64 - Various bugfixes +- Added Scripts folder containing various IDAPython scripts Changes from version 1.4.2 to 1.4.3 ------------------------------------ diff --git a/build.py b/build.py index 69c724a..a70e537 100644 --- a/build.py +++ b/build.py @@ -36,7 +36,7 @@ else: # IDAPython version VERSION_MAJOR = 1 VERSION_MINOR = 5 -VERSION_PATCH = 0 +VERSION_PATCH = 1 # Determine Python version PYTHON_MAJOR_VERSION = int(platform.python_version()[0]) diff --git a/examples/ex_askusingform.py b/examples/ex_askusingform.py index 9c6f475..be61d61 100644 --- a/examples/ex_askusingform.py +++ b/examples/ex_askusingform.py @@ -118,8 +118,8 @@ The end! def OnFormChange(self, fid): if fid == self.iButton1.id: print("Button1 fchg;inv=%s" % self.invert) - self.SetFocusedField(self.rNormal.id) - self.EnableField(self.rError.id, self.invert) + self.SetFocusedField(self.rNormal) + self.EnableField(self.rError, self.invert) self.invert = not self.invert elif fid == self.iButton2.id: g1 = self.GetControlValue(self.cGroup1) @@ -134,6 +134,8 @@ The end! print(">>fid:%d" % fid) return 1 + + # -------------------------------------------------------------------------- def stdalone_main(): f = MyForm() @@ -165,7 +167,8 @@ def ida_main(): f.rWarnings.checked = True f.rGreen.selected = True f.iStr1.value = "Hello" - + f.iFileSave.value = "*.*" + f.iFileOpen.value = "*.*" # Execute the form ok = f.Execute() print("r=%d" % ok) @@ -188,6 +191,7 @@ def ida_main(): print("No selection") else: print("Selection: %s" % sel) + # Dispose the form f.Free() diff --git a/python.cpp b/python.cpp index 8ef643e..6c44f79 100644 --- a/python.cpp +++ b/python.cpp @@ -1139,6 +1139,24 @@ bool idaapi IDAPython_cli_execute_line(const char *line) return false; } + // + // Pseudo commands + // + qstring s; + do + { + // Help command? + if ( line[0] == '?' ) + s.sprnt("help(%s)", line+1); + // Shell command? + else if ( line[0] == '!' ) + s.sprnt("idaapi.IDAPython_ExecSystem(r'%s')", line+1); + else + break; + // Patch the command line pointer + line = s.c_str(); + } while (false); + begin_execution(); PythonEvalOrExec(line); end_execution(); diff --git a/python/idc.py b/python/idc.py index 880efab..59bf15c 100644 --- a/python/idc.py +++ b/python/idc.py @@ -1644,12 +1644,9 @@ def GetInputMD5(): @return: MD5 string or None on error """ - ua=idaapi.uchar_array(16) + ua = idaapi.uchar_array(16) if idaapi.retrieve_input_file_md5(ua.cast()): - md5str="" - for i in range(16): - md5str += "%02x" % ua[i] - return md5str + return "".join(["%02X" % ua[i] for i in xrange(16)]) else: return None diff --git a/swig/idaapi.i b/swig/idaapi.i index 0805fa2..78ffed4 100644 --- a/swig/idaapi.i +++ b/swig/idaapi.i @@ -2072,6 +2072,21 @@ def struct_unpack(buffer, signed = False, offs = 0): # Unpack return struct.unpack_from(__struct_unpack_table[n][signed], buffer, offs)[0] + +# ------------------------------------------------------------ +def IDAPython_ExecSystem(cmd): + """ + Executes a command with popen(). + """ + try: + f = os.popen(cmd, "r") + s = ''.join(f.readlines()) + f.close() + return s + except Exception, e: + return str(e) + + # ------------------------------------------------------------ def IDAPython_ExecScript(script, g): """ @@ -2175,10 +2190,10 @@ class __IDAPython_Completion_Util(object): s = self.completion[n] try: attr = getattr(self.lastmodule, s) - # is it callable? + # Is it callable? if callable(attr): - return s + "(" - # is it iterable? + return s + ("" if line.startswith("?") else "(") + # Is it iterable? elif isinstance(attr, basestring) or getattr(attr, '__iter__', False): return s + "[" except: diff --git a/swig/idp.i b/swig/idp.i index c2ca7a4..cf1462f 100644 --- a/swig/idp.i +++ b/swig/idp.i @@ -65,7 +65,12 @@ def AssembleLine(ea, cs, ip, use32, line): pass # */ -static PyObject *AssembleLine(ea_t ea, ea_t cs, ea_t ip, bool use32, const char *line) +static PyObject *AssembleLine( + ea_t ea, + ea_t cs, + ea_t ip, + bool use32, + const char *line) { int inslen; char buf[MAXSTR]; @@ -93,7 +98,12 @@ def assemble(ea, cs, ip, use32, line): """ # */ -bool assemble(ea_t ea, ea_t cs, ea_t ip, bool use32, const char *line) +bool assemble( + ea_t ea, + ea_t cs, + ea_t ip, + bool use32, + const char *line) { int inslen; char buf[MAXSTR]; @@ -391,6 +401,7 @@ class IDP_Hooks(object): """ pass + def unhook(self): """ Removes the IDP hook @@ -398,6 +409,7 @@ class IDP_Hooks(object): """ pass + def custom_ana(self): """ Analyzes and decodes an instruction at idaapi.cmd.ea @@ -410,6 +422,7 @@ class IDP_Hooks(object): """ pass + def custom_out(self): """ Outputs the instruction defined in idaapi.cmd @@ -418,6 +431,7 @@ class IDP_Hooks(object): """ pass + def custom_emu(self): """ Emulate instruction, create cross-references, plan to analyze @@ -428,6 +442,7 @@ class IDP_Hooks(object): """ pass + def custom_outop(self, op): """ Notification to generate operand text. @@ -438,6 +453,7 @@ class IDP_Hooks(object): @return: Boolean (whether the operand has been outputted or not) """ + pass def custom_mnem(self): """ @@ -447,6 +463,8 @@ class IDP_Hooks(object): - None: No mnemonic. IDA will use the default mnemonic value if present - String: The desired mnemonic string """ + pass + def is_sane_insn(self, no_crefs): """ @@ -463,6 +481,7 @@ class IDP_Hooks(object): """ pass + def may_be_func(self, no_crefs): """ Can a function start here? @@ -474,18 +493,21 @@ class IDP_Hooks(object): """ pass + def closebase(self): """ The database will be closed now """ pass + def savebase(self): """ The database is being saved. Processor module should """ pass + def rename(self, ea, new_name): """ The kernel is going to rename a byte. @@ -497,6 +519,8 @@ class IDP_Hooks(object): - If returns value <=0, then the kernel should not rename it. See also the 'renamed' event """ + pass + def renamed(self, ea, new_name, local_name): """ @@ -508,6 +532,8 @@ class IDP_Hooks(object): @return: Ignored """ + pass + def undefine(self, ea): """ @@ -519,6 +545,8 @@ class IDP_Hooks(object): bit0 - ignored bit1 - do not delete srareas at the item end """ + pass + def make_code(self, ea, size): """ @@ -527,6 +555,8 @@ class IDP_Hooks(object): @param size: Instruction size @return: 1-ok, <=0-the kernel should stop """ + pass + def make_code(self, ea, size): """ @@ -535,7 +565,9 @@ class IDP_Hooks(object): @param size: Instruction size @return: 1-ok, <=0-the kernel should stop """ - + pass + + def make_data(self, ea, flags, tid, len): """ A data item is being created @@ -545,14 +577,18 @@ class IDP_Hooks(object): @param len: data item size @return: 1-ok, <=0-the kernel should stop """ + pass + def load_idasgn(self, short_sig_name): """ - FLIRT signature have been loaded for normal processing + FLIRT signature have been loaded for normal processing (not for recognition of startup sequences) @param short_sig_name: signature name @return: Ignored """ + pass + def add_func(self, func): """ @@ -560,6 +596,8 @@ class IDP_Hooks(object): @param func: the func_t instance @return: Ignored """ + pass + def del_func(self, func): """ @@ -567,6 +605,8 @@ class IDP_Hooks(object): @param func: the func_t instance @return: 1-ok,<=0-do not delete """ + pass + def is_call_insn(self, ea, func_name): """ @@ -575,6 +615,8 @@ class IDP_Hooks(object): @param ea: instruction address @return: 1-unknown, 0-no, 2-yes """ + pass + def is_ret_insn(self, ea, func_name): """ @@ -585,6 +627,23 @@ class IDP_Hooks(object): False: include instructions like "leave" which begins the function epilog @return: 1-unknown, 0-no, 2-yes """ + pass + + + def assemble(self, ea, cs, ip, use32, line): + """ + Assembles an instruction + + @param ea: linear address of instruction + @param cs: cs of instruction + @param ip: ip of instruction + @param use32: is 32bit segment? + @param line: line to assemble + + @return: - None to let the underlying processor module assemble the line + - or a string containing the assembled buffer + """ + pass # */ @@ -700,6 +759,15 @@ public: return 0; } + virtual PyObject *assemble( + ea_t ea, + ea_t cs, + ea_t ip, + bool use32, + const char *line) + { + return NULL; + } }; //--------------------------------------------------------------------------- @@ -912,8 +980,36 @@ int idaapi IDP_Callback(void *ud, int notification_code, va_list va) ret = proxy->is_ret_insn(ea, strict); break; } + + case processor_t::assemble: + { + ea_t ea = va_arg(va, ea_t); + ea_t cs = va_arg(va, ea_t); + ea_t ip = va_arg(va, ea_t); + bool use32 = va_argi(va, bool); + const char *line = va_arg(va, const char *); + // Extract user buffer (we hardcode the MAXSTR size limit) + uchar *bin = va_arg(va, uchar *); + // Call python + PyObject *py_buffer = proxy->assemble(ea, cs, ip, use32, line); + if ( py_buffer != NULL && PyString_Check(py_buffer) ) + { + char *s; + Py_ssize_t len; + if ( PyString_AsStringAndSize(py_buffer, &s, &len) != -1 ) + { + if ( len > MAXSTR ) + len = MAXSTR; + memcpy(bin, s, len); + ret = len; + } + } + // ret = 0 otherwise + Py_XDECREF(py_buffer); + break; + } } - } + } catch (Swig::DirectorException &) { msg("Exception in IDP Hook function:\n"); diff --git a/swig/kernwin.i b/swig/kernwin.i index d26aad4..8379e3a 100644 --- a/swig/kernwin.i +++ b/swig/kernwin.i @@ -700,6 +700,12 @@ static bool formchgcbfa_set_field_value( } #undef DECLARE_FORM_ACTIONS + +static size_t py_get_AskUsingForm() +{ + return (size_t)AskUsingForm_c; +} + // %} @@ -1681,10 +1687,6 @@ PyObject *choose2_find(const char *title) } -static size_t py_get_AskUsingForm() -{ - return (size_t)AskUsingForm_c; -} // %} @@ -3409,7 +3411,7 @@ class Choose2(object): # Disable the timeout old = idaapi.set_script_timeout(0) n = _idaapi.choose2_create(self, False) - idaapi.set_script_timeout(old) + _idaapi.set_script_timeout(old) # Delete the modal chooser instance self.Close() @@ -3729,7 +3731,7 @@ class Form(object): """ Free the control """ - # Release parent form reference + # Release the parent form reference self.form = None @@ -4201,7 +4203,9 @@ class Form(object): """ self._reset() self.form = form + """Form string""" self.controls = controls + """Dictionary of controls""" self.__args = None self.title = None @@ -4215,6 +4219,7 @@ class Form(object): """ for ctrl in self.__controls.values(): ctrl.free() + # Reset the controls # (Note that we are not removing the form control attributes, no need) self._reset() @@ -4444,12 +4449,21 @@ class Form(object): return (self, self.__args) + def Compiled(self): + """ + Checks if the form has already been compiled + + @return: Boolean + """ + return self.__args is not None + + def Execute(self): """ Displays a compiled form. @return: 1 - ok ; 0 - cancel """ - if self.__args is None: + if not self.Compiled(): raise SyntaxError("Form is not compiled") # Call AskUsingForm()