IDAPython for IDA 7.3

This commit is contained in:
Arnaud Diederen
2019-06-26 14:37:39 +02:00
parent afca63f19a
commit 7a567eecf0
127 changed files with 104365 additions and 3930 deletions
+197 -76
View File
@@ -23,7 +23,10 @@
#endif
#ifdef __MAC__
#include <mach-o/dyld.h>
#undef SEG_DATA // avoid conflict between mach-o/loader.h and segment.hpp
#endif
// Python defines snprintf macro so we need to allow it
#define USE_DANGEROUS_FUNCTIONS
#include <ida.hpp>
#include <idp.hpp>
#include <expr.hpp>
@@ -32,6 +35,13 @@
#include <kernwin.hpp>
#include <ida_highlighter.hpp>
#if defined (PY_MAJOR_VERSION) && (PY_MAJOR_VERSION < 3)
// in Python 2.x many APIs accept char * instead of const char *
GCC_DIAG_OFF(write-strings)
#else
#error remove me once we switch to Python 3
#endif
#include "pywraps.hpp"
#include "pywraps.cpp"
@@ -49,7 +59,8 @@
#define S_IDAPYTHON "IDAPython"
#define S_INIT_PY "init.py"
static const char S_IDC_ARGS_VARNAME[] = "ARGV";
static const char S_IDC_RUNPYTHON_STATEMENT[] = "RunPythonStatement";
static const char S_IDC_EXEC_PYTHON[] = "exec_python";
static const char S_IDC_EVAL_PYTHON[] = "eval_python";
static const char S_IDAPYTHON_DATA_NODE[] = "IDAPython_Data";
//-------------------------------------------------------------------------
@@ -82,6 +93,11 @@ static qstring requested_plugin_path;
// Plugin run() callback
bool idaapi run(size_t);
static PyObject *get_module_globals_from_path(const char *path);
static bool idaapi IDAPython_extlang_eval_expr(
idc_value_t *rv,
ea_t /*current_ea*/,
const char *expr,
qstring *errbuf);
//lint -e818 could be pointer to const
@@ -121,6 +137,7 @@ static bool g_use_local_python = false;
static bool g_autoimport_compat_idaapi = true;
static bool g_autoimport_compat_ida695 = true;
static bool g_namespace_aware = true;
static bool g_repl_use_sys_displayhook = true;
// Allowing the user to interrupt a script is not entirely trivial.
// Imagine the following script, that is run in an IDB that uses
@@ -294,7 +311,8 @@ int execution_t::on_trace(PyObject *obj, _frame *frame, int what, PyObject *arg)
if ( user_cancelled() )
{
LEXEC("on_trace()::INTERRUPTING\n");
PyErr_SetString(PyExc_KeyboardInterrupt, "User interrupted");
if ( PyErr_Occurred() == NULL )
PyErr_SetString(PyExc_KeyboardInterrupt, "User interrupted");
return -1;
}
}
@@ -318,28 +336,25 @@ int execution_t::on_trace(PyObject *obj, _frame *frame, int what, PyObject *arg)
}
//-------------------------------------------------------------------------
//lint -esym(1788, new_execution_t) is referenced only by its constructor or destructor
struct new_execution_t
void ida_export setup_new_execution(
new_execution_t *instance,
bool setup)
{
bool created;
new_execution_t()
if ( setup )
{
created = g_ui_ready && execution.timeout > 0;
if ( created )
instance->created = g_ui_ready && execution.timeout > 0;
if ( instance->created )
{
PYW_GIL_CHECK_LOCKED_SCOPE();
execution.push();
}
}
~new_execution_t()
else
{
if ( created )
{
PYW_GIL_CHECK_LOCKED_SCOPE();
execution.pop();
}
PYW_GIL_CHECK_LOCKED_SCOPE();
execution.pop();
}
};
}
//-------------------------------------------------------------------------
void ida_export set_interruptible_state(bool interruptible)
@@ -403,6 +418,26 @@ static PyObject *get_module_globals(const char *modname=NULL)
return module == NULL ? NULL : PyModule_GetDict(module);
}
//-------------------------------------------------------------------------
static ref_t _get_sys_displayhook()
{
ref_t h;
if ( g_repl_use_sys_displayhook )
{
ref_t py_sys(PyW_TryImportModule("sys"));
if ( py_sys != NULL )
h = PyW_TryGetAttrString(py_sys.o, "displayhook");
}
return h;
}
//-------------------------------------------------------------------------
static const char *bomify(qstring *out)
{
out->insert(0, UTF8_BOM, UTF8_BOM_SZ);
return out->c_str();
}
//------------------------------------------------------------------------
static void PythonEvalOrExec(
const char *str,
@@ -411,7 +446,8 @@ static void PythonEvalOrExec(
// Compile as an expression
PYW_GIL_CHECK_LOCKED_SCOPE();
PyCompilerFlags cf = {0};
newref_t py_code(Py_CompileStringFlags(str, filename, Py_eval_input, &cf));
qstring qstr(str);
newref_t py_code(Py_CompileStringFlags(bomify(&qstr), filename, Py_eval_input, &cf));
if ( py_code == NULL || PyErr_Occurred() )
{
// Not an expression?
@@ -435,7 +471,13 @@ static void PythonEvalOrExec(
}
else
{
if ( py_result.o != Py_None )
ref_t sys_displayhook(_get_sys_displayhook());
if ( sys_displayhook != NULL )
{
//lint -esym(1788, res) is referenced only by its constructor or destructor
newref_t res(PyObject_CallFunctionObjArgs(sys_displayhook.o, py_result.o, NULL));
}
else if ( py_result.o != Py_None )
{
bool ok = false;
if ( PyUnicode_Check(py_result.o) )
@@ -460,6 +502,8 @@ static void PythonEvalOrExec(
}
}
//lint -esym(1788, new_execution_t) is referenced only by its constructor or destructor
//------------------------------------------------------------------------
// Executes a simple string
static bool idaapi IDAPython_extlang_eval_snippet(
@@ -513,7 +557,7 @@ static error_t idaapi idc_runpythonstatement(
static const char idc_runpythonstatement_args[] = { VT_STR, 0 };
static const ext_idcfunc_t idc_runpythonstatement_desc =
{
S_IDC_RUNPYTHON_STATEMENT,
S_IDC_EXEC_PYTHON,
idc_runpythonstatement,
idc_runpythonstatement_args,
NULL,
@@ -521,6 +565,31 @@ static const ext_idcfunc_t idc_runpythonstatement_desc =
0
};
//------------------------------------------------------------------------
// Simple Python expression evaluator for IDC
static error_t idaapi idc_eval_python(
idc_value_t *argv,
idc_value_t *res)
{
qstring errbuf;
const char *snippet = argv[0].c_str();
bool ok = IDAPython_extlang_eval_expr(res, BADADDR, snippet, &errbuf);
if ( !ok )
return throw_idc_exception(res, errbuf.c_str());
return eOk;
}
static const char idc_eval_python_args[] = { VT_STR, 0 };
static const ext_idcfunc_t idc_eval_python_desc =
{
S_IDC_EVAL_PYTHON,
idc_eval_python,
idc_eval_python_args,
NULL,
0,
0
};
//--------------------------------------------------------------------------
static const cfgopt_t opts[] =
{
@@ -530,6 +599,7 @@ static const cfgopt_t opts[] =
cfgopt_t("AUTOIMPORT_COMPAT_IDAAPI", &g_autoimport_compat_idaapi, true),
cfgopt_t("AUTOIMPORT_COMPAT_IDA695", &g_autoimport_compat_ida695, true),
cfgopt_t("NAMESPACE_AWARE", &g_namespace_aware, true),
cfgopt_t("REPL_USE_SYS_DISPLAYHOOK", &g_repl_use_sys_displayhook, true),
};
//-------------------------------------------------------------------------
@@ -829,7 +899,8 @@ static bool idaapi IDAPython_extlang_call_func(
module = PyImport_ImportModule(final_modname);
if ( module == NULL )
{
errbuf->sprnt("couldn't import module %s", final_modname);
if ( errbuf != NULL )
errbuf->sprnt("couldn't import module %s", final_modname);
ok = false;
break;
}
@@ -840,7 +911,8 @@ static bool idaapi IDAPython_extlang_call_func(
PyObject *func = PyDict_GetItemString(globals, funcname);
if ( func == NULL )
{
errbuf->sprnt("undefined function %s", name);
if ( errbuf != NULL )
errbuf->sprnt("undefined function %s", name);
ok = false;
break;
}
@@ -865,13 +937,37 @@ static bool idaapi IDAPython_extlang_call_func(
//-------------------------------------------------------------------------
static void wrap_in_function(qstring *out, const qstring &body, const char *name)
{
out->sprnt("def %s():\n", name);
// dont copy trailing whitespace
int i = body.length()-1;
while ( i >= 0 && qisspace(body.at(i)) )
i--;
out->append(body.substr(0, i+1));
out->replace("\n", "\n ");
qstrvec_t lines;
lines.push_back().sprnt("def %s():\n", name);
qstring buf(body);
while ( !buf.empty() && qisspace(buf.last()) ) // dont copy trailing whitespace(s)
buf.remove_last();
char *ctx;
for ( char *p = qstrtok(buf.begin(), "\n", &ctx);
p != NULL;
p = qstrtok(NULL, "\n", &ctx) )
{
static const char FROM_FUTURE_IMPORT_STMT[] = "from __future__ import";
if ( strneq(p, FROM_FUTURE_IMPORT_STMT, sizeof(FROM_FUTURE_IMPORT_STMT)-1) )
{
lines.insert(lines.begin(), p);
}
else
{
qstring &s = lines.push_back();
s.append(" ", 4);
s.append(p);
}
}
out->qclear();
for ( size_t i = 0; i < lines.size(); ++i )
{
if ( i > 0 )
out->append('\n');
out->append(lines[i]);
}
}
//-------------------------------------------------------------------------
@@ -886,7 +982,11 @@ static bool idaapi IDAPython_extlang_compile_expr(
PyObject *globals = get_module_globals();
bool isfunc = false;
PyCodeObject *code = (PyCodeObject *)Py_CompileString(expr, "<string>", Py_eval_input);
qstring qstr(expr);
PyCodeObject *code = (PyCodeObject *)Py_CompileString(
bomify(&qstr),
"<string>",
Py_eval_input);
if ( code == NULL )
{
// try compiling as a list of statements
@@ -894,6 +994,7 @@ static bool idaapi IDAPython_extlang_compile_expr(
handle_python_error(errbuf);
qstring func;
wrap_in_function(&func, expr, name);
bomify(&func);
code = (PyCodeObject *)Py_CompileString(func.c_str(), "<string>", Py_file_input);
if ( code == NULL )
{
@@ -948,6 +1049,10 @@ static bool idaapi IDAPython_extlang_compile_file(
// Load processor module callback for Python external language evaluator
static bool idaapi IDAPython_extlang_load_procmod(
idc_value_t *procobj,
// hook_cb_t **idp_notifier,
// void **idp_notifier_ud,
// hook_cb_t **idb_notifier,
// void **idb_notifier_ud,
const char *path,
qstring *errbuf)
{
@@ -956,7 +1061,7 @@ static bool idaapi IDAPython_extlang_load_procmod(
{
new_execution_t exec;
PyObject *globals = get_module_globals_from_path(path);
ok = IDAPython_ExecFile(path, globals, errbuf, S_IDAAPI_LOADPROCMOD, procobj, true);
ok = IDAPython_ExecFile(path, globals, errbuf, S_IDAAPI_LOADPROCMOD, procobj, /*want_tuple=*/ true);
}
if ( ok && procobj->is_zero() )
{
@@ -1590,12 +1695,32 @@ void convert_idc_args()
PyObject_SetAttrString(py_mod.o, S_IDC_ARGS_VARNAME, py_args.o);
}
#define DISPATCH_TO_MODULES(Method) \
do \
{ \
for ( size_t i = modules_callbacks.size(); i > 0; --i ) \
modules_callbacks[i-1].Method(); \
} while ( false )
//-------------------------------------------------------------------------
enum module_lifecycle_notification_t
{
mln_init = 0,
mln_term,
mln_closebase
};
static void send_modules_lifecycle_notification(module_lifecycle_notification_t what)
{
PYW_GIL_GET;
for ( size_t i = modules_callbacks.size(); i > 0; --i )
{
const module_callbacks_t &m = modules_callbacks[i-1];
switch ( what )
{
case mln_init: m.init(); break;
case mln_term: m.term(); break;
case mln_closebase: m.closebase(); break;
}
if ( PyErr_Occurred() )
{
msg("Error during module lifecycle notification:\n");
PyErr_Print();
}
}
}
//------------------------------------------------------------------------
//lint -esym(715,va) Symbol not referenced
@@ -1651,7 +1776,7 @@ static ssize_t idaapi on_idb_notification(void *, int code, va_list)
// through all the tinfo_t objects that are embedded in SWIG wrappers,
// (i.e., that were created from Python) and clear those.
til_clear_python_tinfo_t_instances();
DISPATCH_TO_MODULES(closebase);
send_modules_lifecycle_notification(mln_closebase);
break;
}
return 0;
@@ -1734,26 +1859,6 @@ static bool initsite(void)
return true;
}
//-------------------------------------------------------------------------
static void init_ida_modules()
{
// char buf[QMAXPATH];
// // IDA_MODULES must be passed as a define
// qstrncpy(buf, IDA_MODULES, sizeof(buf));
// char *ctx;
// for ( char *module = qstrtok(buf, ",", &ctx);
// module != NULL;
// module = qstrtok(NULL, DELIMITER, &ctx) )
// {
// deb(IDA_DEBUG_PLUGIN, "Initializing \"ida_%s\"\n", module);
// }
// Load the 'ida_idaapi' module, that contains some important bits of code
// ref_t ida_idaapi(PyW_TryImportModule(S_PY_IDA_IDAAPI_MODNAME));
// ref_t sys(PyW_TryImportModule("sys"));
}
//-------------------------------------------------------------------------
// Initialize the Python environment
bool IDAPython_Init(void)
@@ -1862,8 +1967,6 @@ bool IDAPython_Init(void)
if ( !PyEval_ThreadsInitialized() )
PyEval_InitThreads();
init_ida_modules();
#ifdef Py_DEBUG
msg("HexraysPython: Python compiled with DEBUG enabled.\n");
#endif
@@ -1877,11 +1980,7 @@ bool IDAPython_Init(void)
"IDAPYTHON_DYNLOAD_RELPATH = \"ida_%" FMT_Z "\"\n"
"IDAPYTHON_COMPAT_AUTOIMPORT_MODULES = %s\n"
"IDAPYTHON_COMPAT_695_API = %s\n",
VER_MAJOR,
VER_MINOR,
VER_PATCH,
VER_STATUS,
VER_SERIAL,
VER_MAJOR, VER_MINOR, VER_PATCH, VER_STATUS, VER_SERIAL,
g_remove_cwd_sys_path ? "True" : "False",
idadir(NULL),
sizeof(ea_t)*8,
@@ -1927,7 +2026,7 @@ bool IDAPython_Init(void)
}
// Init pywraps and notify_when
if ( !init_pywraps() || !pywraps_nw_init() )
if ( !init_pywraps() )
{
warning("IDAPython: init_pywraps() failed!");
remove_extlang(&extlang_python);
@@ -1938,9 +2037,9 @@ bool IDAPython_Init(void)
PyEval_SetTrace(tracefunc, NULL);
#endif
// Register a RunPythonStatement() function for IDC
// Register a exec_python() function for IDC
add_idc_func(idc_runpythonstatement_desc);
add_idc_func(idc_eval_python_desc);
// A script specified on the command line is run
if ( g_run_when == RUN_ON_INIT )
@@ -1955,7 +2054,8 @@ bool IDAPython_Init(void)
// Enable the CLI by default
enable_python_cli(true);
pywraps_nw_notify(NW_INITIDA_SLOT);
// Let all modules perform possible initialization
send_modules_lifecycle_notification(mln_init);
PyEval_ReleaseThread(PyThreadState_Get());
@@ -1963,6 +2063,27 @@ bool IDAPython_Init(void)
return true;
}
//-------------------------------------------------------------------------
#ifdef TESTABLE_BUILD
// "user-code-leniency" means that, even in TESTABLE_BUILD builds,
// IDAPython will accept that some things are left in an undesirable,
// but recuperable state (e.g., remaining hooks.)
// This should *ONLY* be used for tests that rely on user code that
// is in such a shape that it would require significant changes to
// have it perform proper cleanup, which means that it would have to
// diverge from the original user's code, which means that we
// would have to maintain our own branch of it, which is not
// the best idea of the world, overhead-wise.
static int _is_user_code_lenient = -1;
static bool is_user_code_lenient()
{
if ( _is_user_code_lenient < 0 )
_is_user_code_lenient = qgetenv("IDAPYTHON_USER_CODE_LENIENT");
return _is_user_code_lenient > 0;
}
#endif
//-------------------------------------------------------------------------
// Cleaning up Python
void IDAPython_Term(void)
@@ -1981,7 +2102,7 @@ void IDAPython_Term(void)
}
// Let all modules perform possible de-initialization
DISPATCH_TO_MODULES(term);
send_modules_lifecycle_notification(mln_term);
unhook_from_notification_point(HT_IDB, on_idb_notification);
unhook_from_notification_point(HT_UI, on_ui_notification);
@@ -1989,12 +2110,6 @@ void IDAPython_Term(void)
unhook_from_notification_point(HT_UI, ui_debug_handler_cb);
#endif
// Notify about IDA closing
pywraps_nw_notify(NW_TERMIDA_SLOT);
// De-init notify_when
pywraps_nw_term();
// Remove the CLI
enable_python_cli(false);
@@ -2005,6 +2120,7 @@ void IDAPython_Term(void)
deinit_pywraps();
// Uninstall IDC function
del_idc_func(idc_eval_python_desc.name);
del_idc_func(idc_runpythonstatement_desc.name);
// Shut the interpreter down
@@ -2012,9 +2128,14 @@ void IDAPython_Term(void)
g_instance_initialized = false;
#ifdef TESTABLE_BUILD
// Check that all hooks were unhooked
QASSERT(30509, hook_data_vec.empty());
if ( !is_user_code_lenient() ) // Check that all hooks were unhooked
QASSERT(30509, hook_data_vec.empty());
#endif
for ( size_t i = hook_data_vec.size(); i > 0; --i )
{
const hook_data_t &hd = hook_data_vec[i-1];
idapython_unhook_from_notification_point(hd.type, hd.cb, hd.ud);
}
}
//-------------------------------------------------------------------------