/* Chrysalide - Outil d'analyse de fichiers binaires * plugin.c - interactions avec un greffon Python * * Copyright (C) 2010-2014 Cyrille Bagard * * This file is part of Chrysalide. * * OpenIDA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * OpenIDA is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "plugin.h" #include #include #include #include #include "helpers.h" /* --------------------- INTERFACE INTERNE POUR GREFFONS PYTHON --------------------- */ /* Ligne de représentation de code binaire (instance) */ struct _GPythonPlugin { GPluginModule parent; /* Instance parente */ PyObject *module; /* Script Python chargé */ PyObject *instance; /* Instance Python du greffon */ }; /* Ligne de représentation de code binaire (classe) */ struct _GPythonPluginClass { GPluginModuleClass parent; /* Classe parente */ }; /* Initialise la classe des greffons Python. */ static void g_python_plugin_class_init(GPythonPluginClass *); /* Initialise l'instance d'un greffon Python. */ static void g_python_plugin_init(GPythonPlugin *); /* Reconstruit la déclaration d'interface à partir de lectures. */ static bool g_python_plugin_read_interface(GPythonPlugin *); /* Procède à l'initialisation du greffon. */ static bool g_python_plugin_do_init(GPythonPlugin *, GObject *); /* Procède à l'extinction du greffon. */ static bool g_python_plugin_do_exit(GPythonPlugin *, GObject *); /* Indique l'utilité pratique du greffon. */ static PluginAction g_python_plugin_get_action(const GPythonPlugin *); /* Indentifie un format à associer à un contenu binaire. */ static MatchingFormatAction g_python_plugin_is_matching(const GPythonPlugin *, char **, bin_t **, off_t *); /* Exécute une action définie sur un binaire chargé. */ static bool g_python_plugin_execute_on_binary(GPythonPlugin *, GLoadedBinary *, PluginAction); /* Exécute une action relative à un débogueur. */ //static bool g_python_plugin_handle_debugger(const GPythonPlugin *, GBinaryDebugger *, PluginAction); /* ------------------------- MODULE PYTHON POUR LES SCRIPTS ------------------------- */ /* Définit les constantes pour les greffons en Python. */ static bool py_plugin_module_define_constants(PyTypeObject *); /* ---------------------------------------------------------------------------------- */ /* INTERFACE INTERNE POUR GREFFONS PYTHON */ /* ---------------------------------------------------------------------------------- */ /* Indique le type définit par la GLib pour le greffon Python. */ G_DEFINE_TYPE(GPythonPlugin, g_python_plugin, G_TYPE_PLUGIN_MODULE); /****************************************************************************** * * * Paramètres : klass = classe à initialiser. * * * * Description : Initialise la classe des greffons Python. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_python_plugin_class_init(GPythonPluginClass *klass) { } /****************************************************************************** * * * Paramètres : plugin = instance à initialiser. * * * * Description : Initialise l'instance d'un greffon Python. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_python_plugin_init(GPythonPlugin *plugin) { GPluginModule *plugin_parent; /* Instance parente */ plugin_parent = G_PLUGIN_MODULE(plugin); plugin_parent->exec_on_bin = (execute_action_on_binary_fc)g_python_plugin_execute_on_binary; //plugin_parent->handle_debugger = (execute_on_debugger_fc)g_python_plugin_handle_debugger; } /****************************************************************************** * * * Paramètres : modname = nom du module à charger. * * filename = chemin d'accès au code Python à charger. * * ref = espace de référencement global. * * * * Description : Crée un greffon à partir de code Python. * * * * Retour : Adresse de la structure mise en place ou NULL si erreur. * * * * Remarques : - * * * ******************************************************************************/ GPluginModule *g_python_plugin_new(const char *modname, const char *filename, GObject *ref) { GPythonPlugin *result; /* Structure à retourner */ PyObject *name; /* Chemin d'accès pour Python */ PyObject *module; /* Script Python chargé */ PyObject *err_type; /* Type d'erreur Python */ PyObject *err_value; /* Instance Python d'erreur */ PyObject *err_traceback; /* Trace Python associée */ PyObject *err_string; /* Description Python d'erreur */ const char *err_msg; /* Représentation humaine */ PyObject *dict; /* Dictionnaire associé */ PyObject *class; /* Classe à instancier */ PyObject *instance; /* Instance Python du greffon */ size_t i; /* Boucle de parcours */ uint32_t category; /* Catégorie principale */ uint32_t sub; /* Sous-catégorie visée */ name = PyUnicode_FromString(modname); if (name == NULL) goto gppn_bad_exit; module = PyImport_Import(name); Py_DECREF(name); if (PyErr_Occurred()) { PyErr_Fetch(&err_type, &err_value, &err_traceback); if (err_value == NULL) log_variadic_message(LMT_ERROR, _("An unknown error occured when importing '%s'..."), modname); else { err_string = PyObject_Str(err_value); err_msg = PyUnicode_AsUTF8(err_string); log_variadic_message(LMT_ERROR, _("An error occured when importing '%s': \"%s\""), modname, err_msg); Py_DECREF(err_string); Py_DECREF(err_value); } Py_XDECREF(err_traceback); Py_XDECREF(err_type); Py_XDECREF(module); module = NULL; } if (module == NULL) goto gppn_bad_exit; dict = PyModule_GetDict(module); class = PyDict_GetItemString(dict, "AutoLoad"); if (class == NULL) goto gppn_no_class; if (!PyType_Check(class->ob_type)) goto gppn_no_class; instance = PyObject_CallFunction(class, NULL); if (instance == NULL) goto gppn_no_instance; result = g_object_new(G_TYPE_PYTHON_PLUGIN, NULL); G_PLUGIN_MODULE(result)->filename = strdup(filename); result->module = module; result->instance = instance; if (!g_python_plugin_read_interface(result)) { printf("bad interface------------\n"); goto gppn_interface_error; } //G_PLUGIN_MODULE(result)->get_action = (get_plugin_action_fc)g_python_plugin_get_action; //G_PLUGIN_MODULE(result)->is_matching = (is_matching_fc)g_python_plugin_is_matching; /* Localisation des différents points d'entrée déclarés */ #define register_python_binding(inst, sym, binding) \ ({ \ bool __result; \ if (!has_python_method(inst, #sym)) \ { \ log_variadic_message(LMT_ERROR, \ _("No '%s' entry in plugin candidate '%s'"), \ #sym, G_PLUGIN_MODULE(result)->filename); \ __result = false; \ } \ else \ { \ G_PLUGIN_MODULE(result)->sym = binding; \ __result = true; \ } \ __result; \ }) for (i = 0; i < G_PLUGIN_MODULE(result)->interface->actions_count; i++) { category = MASK_PLUGIN_CATEGORY(G_PLUGIN_MODULE(result)->interface->actions[i]); sub = MASK_PLUGIN_SUB_CATEGORY(G_PLUGIN_MODULE(result)->interface->actions[i]); switch (category) { case DPC_BASIC: switch (sub) { case DPS_NONE: break; case PGA_PLUGIN_INIT: if (!register_python_binding(instance, init, (pg_management_fc)g_python_plugin_do_init)) goto gppn_bad_plugin; break; case PGA_PLUGIN_EXIT: if (!register_python_binding(instance, exit, (pg_management_fc)g_python_plugin_do_exit)) goto gppn_bad_plugin; break; default: log_variadic_message(LMT_WARNING, _("Unknown sub-category '0x%02x' in plugin '%s'..."), sub, filename); break; } break; #if 0 case DPC_BINARY_PROCESSING: switch (sub) { case DPS_FORMAT: switch (result->interface->actions[i]) { case PGA_FORMAT_LOADER_LAST: if (!load_plugin_symbol(result->module, "handle_binary_format", &result->handle_format)) goto bad_plugin; break; default: log_variadic_message(LMT_WARNING, _("Unknown action '0x%02x' in plugin '%s'..."), result->interface->actions[i], filename); break; } break; case DPS_DISASSEMBLY: if (!load_plugin_symbol(result->module, "process_binary_disassembly", &result->proc_disass)) goto bad_plugin; break; default: log_variadic_message(LMT_WARNING, _("Unknown sub-category '0x%02x' in plugin '%s'..."), sub, filename); break; } break; #endif default: log_variadic_message(LMT_WARNING, _("Unknown category '0x%02x' in plugin '%s'..."), category, filename); break; } } /* Conclusion */ if (!g_plugin_module_load(G_PLUGIN_MODULE(result), ref)) goto gppn_bad_plugin; return G_PLUGIN_MODULE(result); gppn_bad_plugin: gppn_interface_error: g_object_unref(G_OBJECT(result)); //Py_DECREF(instance); gppn_no_instance: gppn_no_class: //Py_DECREF(module); gppn_bad_exit: printf("bad exit :(\n"); return NULL; } /****************************************************************************** * * * Paramètres : plugin = greffon à initialiser. * * * * Description : Reconstruit la déclaration d'interface à partir de lectures. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool g_python_plugin_read_interface(GPythonPlugin *plugin) { bool result; /* Bilan à renvoyer */ plugin_interface interface; /* Recueil des éléments */ PyObject *desc; /* Tableau de description */ PyObject *str; /* Chaîne de caractères */ PyObject *tuple; /* Liste d'éléments à traiter */ Py_ssize_t count; /* Nombre d'éléments présents */ Py_ssize_t i; /* Boucle de parcours */ PyObject *action; /* Identifiant d'une action */ plugin_interface *final; /* Interface finale conservée */ result = true; desc = run_python_method(plugin->instance, "get_interface", NULL); if (!PyDict_Check(desc)) { result = false; goto pgpri_end; } memset(&interface, 0, sizeof(interface)); /* Chargements des premières chaînes */ #define READ_STR_FIELD(name) \ str = PyDict_GetItemString(desc, #name); \ if ((result = PyUnicode_Check(str))) \ interface.name = strdup(PyUnicode_DATA(str)); \ READ_STR_FIELD(name); READ_STR_FIELD(desc); READ_STR_FIELD(version); /* Chargement des actions supportées */ tuple = PyDict_GetItemString(desc, "actions"); if (!PyList_Check(tuple)) { result = false; goto pgpri_failed; } count = PyList_GET_SIZE(tuple); interface.actions = (plugin_action_t *)calloc(count, sizeof(plugin_action_t)); interface.actions_count = count; for (i = 0; i < count; i++) { action = PyList_GET_ITEM(tuple, i); interface.actions[i] = PyLong_AsLong(action); } pgpri_failed: if (result) { final = (plugin_interface *)calloc(1, sizeof(plugin_interface)); memcpy(final, &interface, sizeof(interface)); G_PLUGIN_MODULE(plugin)->interface = final; } else { if (interface.name != NULL) free((char *)interface.name); if (interface.desc != NULL) free((char *)interface.desc); if (interface.version != NULL) free((char *)interface.version); if (interface.actions != NULL) free(interface.actions); } pgpri_end: Py_XDECREF(desc); return result; } /****************************************************************************** * * * Paramètres : plugin = greffon à initialiser. * * ref = espace de référencement global. * * * * Description : Procède à l'initialisation du greffon. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool g_python_plugin_do_init(GPythonPlugin *plugin, GObject *ref) { bool result; /* Bilan à retourner */ PyObject *args; /* Arguments pour l'appel */ PyObject *value; /* Valeur obtenue */ args = PyTuple_New(1); PyTuple_SetItem(args, 0, pygobject_new(ref)); value = run_python_method(plugin->instance, "init", args); result = PyObject_IsTrue(value); Py_XDECREF(value); Py_DECREF(args); return result; } /****************************************************************************** * * * Paramètres : plugin = greffon à initialiser. * * ref = espace de référencement global. * * * * Description : Procède à l'extinction du greffon. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool g_python_plugin_do_exit(GPythonPlugin *plugin, GObject *ref) { bool result; /* Bilan à retourner */ PyObject *args; /* Arguments pour l'appel */ PyObject *value; /* Valeur obtenue */ args = PyTuple_New(1); PyTuple_SetItem(args, 0, pygobject_new(ref)); value = run_python_method(plugin->instance, "exit", args); result = PyObject_IsTrue(value); Py_XDECREF(value); Py_DECREF(args); return result; } /****************************************************************************** * * * Paramètres : plugin = greffon de prise en charge à utiliser. * * * * Description : Indique l'utilité pratique du greffon. * * * * Retour : Action(s) codifiée(s), PGA_NONE par défaut. * * * * Remarques : - * * * ******************************************************************************/ static PluginAction g_python_plugin_get_action(const GPythonPlugin *plugin) { PluginAction result; /* Valeur à retourner */ PyObject *value; /* Valeur obtenue */ value = run_python_method(plugin->instance, "get_action", NULL); if (value != NULL) { result = PyLong_AsLong(value); Py_DECREF(value); } else result = 0;//PGA_NONE; return result; } /****************************************************************************** * * * Paramètres : plugin = greffon de prise en charge à utiliser. * * filename = éventuel nom de fichier associé ou NULL. [OUT] * * data = données chargées. [OUT] * * length = quantité de ces données. [OUT] * * * * Description : Indentifie un format à associer à un contenu binaire. * * * * Retour : Bilan de la recherche de correspondances. * * * * Remarques : - * * * ******************************************************************************/ static MatchingFormatAction g_python_plugin_is_matching(const GPythonPlugin *plugin, char **filename, bin_t **data, off_t *length) { return MFA_NONE; #if 0 MatchingFormatAction result; /* Valeur à retourner */ PyObject *args; /* Arguments pour l'appel */ PyObject *value; /* Valeurs obtenues */ PyObject *action; /* Action à faire suivre */ PyObject *new_filename; /* Nouveau fichier */ PyObject *new_data; /* Nouvelles données */ char *tmp; /* Stockage avant recopie */ if (*filename == NULL) return MFA_NONE; args = PyTuple_New(2); PyTuple_SetItem(args, 0, PyString_FromString(*filename)); PyTuple_SetItem(args, 1, PyByteArray_FromStringAndSize((char *)*data, *length)); value = run_python_method(plugin->instance, "is_matching", args); if (value != NULL && PyTuple_Check(value) && PyTuple_Size(value) == 3) { action = PyTuple_GetItem(value, 0); new_filename = PyTuple_GetItem(value, 1); new_data = PyTuple_GetItem(value, 2); if (action == NULL || new_filename == NULL || new_data == NULL) goto is_matching_bad; if (!PyInt_Check(action) || (new_filename != Py_None && !PyString_Check(new_filename)) || (new_data != Py_None && !PyByteArray_Check(new_data))) goto is_matching_bad; result = PyInt_AsLong(action); if (result >= MFA_COUNT) goto is_matching_bad; if (result != MFA_NONE && new_data == Py_None) goto is_matching_bad; if (new_filename != Py_None) { free(*filename); *filename = strdup(PyString_AsString(new_filename)); } /** * La suppression de la part du greffon n'est permise que * si une prise en charge est assurée. */ else if (result != MFA_NONE) { free(*filename); *filename = NULL; } /** * Pareil que précédemment. */ if (new_data != Py_None) { tmp = PyByteArray_AsString(new_data); *length = PyByteArray_Size(new_data); free(*data); *data = (bin_t *)calloc(*length, sizeof(bin_t)); memcpy(*data, tmp, *length * sizeof(bin_t)); } goto is_matching_ok; } is_matching_bad: printf("Bad result from is_matching() plugin function.\n"); result = MFA_NONE; is_matching_ok: Py_XDECREF(value); Py_DECREF(args); return result; #endif } /****************************************************************************** * * * Paramètres : plugin = greffon de prise en charge à utiliser. * * binary = représentation binaire à traiter. * * action = action attendue. * * * * Description : Exécute une action définie sur un binaire chargé. * * * * Retour : true si une action a été menée, false sinon. * * * * Remarques : - * * * ******************************************************************************/ static bool g_python_plugin_execute_on_binary(GPythonPlugin *plugin, GLoadedBinary *binary, PluginAction action) { bool result; /* Bilan à remonter */ PyObject *args; /* Arguments pour l'appel */ PyObject *value; /* Valeurs obtenues */ args = PyTuple_New(2); PyTuple_SetItem(args, 0, pygobject_new(G_OBJECT(binary))); PyTuple_SetItem(args, 1, Py_None);//PyInt_FromLong(action)); value = run_python_method(plugin->instance, "execute_on_binary", args); result = (value == Py_True); Py_XDECREF(value); Py_DECREF(args); return result; } #if 0 /****************************************************************************** * * * Paramètres : plugin = greffon à consulter. * * debugger = débogueur à l'origine de l'opération. * * action = action attendue. * * * * Description : Exécute une action relative à un débogueur. * * * * Retour : true si une action a été menée, false sinon. * * * * Remarques : - * * * ******************************************************************************/ static bool g_python_plugin_handle_debugger(const GPythonPlugin *plugin, GBinaryDebugger *debugger, PluginAction action) { bool result; /* Bilan à remonter */ PyObject *args; /* Arguments pour l'appel */ PyObject *value; /* Valeurs obtenues */ args = PyTuple_New(2); PyTuple_SetItem(args, 0, py_binary_debugger_from_c(debugger)); PyTuple_SetItem(args, 1, Py_None);//PyInt_FromLong(action)); value = run_python_method(plugin->instance, "handle_debugger", args); result = (value == Py_True); Py_XDECREF(value); Py_DECREF(args); return result; } #endif /* ---------------------------------------------------------------------------------- */ /* MODULE PYTHON POUR LES SCRIPTS */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : obj_type = type dont le dictionnaire est à compléter. * * * * Description : Définit les constantes pour les greffons en Python. * * * * Retour : true en cas de succès de l'opération, false sinon. * * * * Remarques : - * * * ******************************************************************************/ static bool py_plugin_module_define_constants(PyTypeObject *obj_type) { bool result; /* Bilan à retourner */ result = true; result &= PyDict_AddIntMacro(obj_type, PGA_BASIC_NONE); result &= PyDict_AddIntMacro(obj_type, PGA_PLUGIN_INIT); result &= PyDict_AddIntMacro(obj_type, PGA_PLUGIN_EXIT); result &= PyDict_AddIntMacro(obj_type, PGA_FORMAT_MATCHER); result &= PyDict_AddIntMacro(obj_type, PGA_FORMAT_LOADER_LAST); result &= PyDict_AddIntMacro(obj_type, PGA_DISASSEMBLY_STARTED); result &= PyDict_AddIntMacro(obj_type, PGA_DISASSEMBLY_RAW); result &= PyDict_AddIntMacro(obj_type, PGA_DISASSEMBLY_HOOKED_LINK); result &= PyDict_AddIntMacro(obj_type, PGA_DISASSEMBLY_HOOKED_POST); result &= PyDict_AddIntMacro(obj_type, PGA_DISASSEMBLY_LIMITED); result &= PyDict_AddIntMacro(obj_type, PGA_DISASSEMBLY_LOOPS); result &= PyDict_AddIntMacro(obj_type, PGA_DISASSEMBLY_LINKED); result &= PyDict_AddIntMacro(obj_type, PGA_DISASSEMBLY_GROUPED); result &= PyDict_AddIntMacro(obj_type, PGA_DISASSEMBLY_RANKED); result &= PyDict_AddIntMacro(obj_type, PGA_DISASSEMBLY_ENDED); return result; } /****************************************************************************** * * * Paramètres : - * * * * Description : Fournit un accès à une définition de type à diffuser. * * * * Retour : Définition d'objet pour Python. * * * * Remarques : - * * * ******************************************************************************/ PyTypeObject *get_python_plugin_module_type(void) { static PyMethodDef py_plugin_module_methods[] = { { NULL } }; static PyGetSetDef py_plugin_module_getseters[] = { { NULL } }; static PyTypeObject py_plugin_module_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "pychrysalide.PluginModule", .tp_basicsize = sizeof(PyGObject), .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_doc = "Chrysalide plugin for Python.", .tp_methods = py_plugin_module_methods, .tp_getset = py_plugin_module_getseters }; return &py_plugin_module_type; } /****************************************************************************** * * * Paramètres : module = module dont la définition est à compléter. * * * * Description : Prend en charge l'objet 'pychrysalide.PluginModule'. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool register_python_plugin_module(PyObject *module) { PyTypeObject *py_plugin_module_type; /* Type Python 'PluginModule' */ int ret; /* Bilan d'un appel */ PyObject *dict; /* Dictionnaire du module */ py_plugin_module_type = get_python_plugin_module_type(); py_plugin_module_type->tp_base = &PyGObject_Type; py_plugin_module_type->tp_basicsize = py_plugin_module_type->tp_base->tp_basicsize; if (PyType_Ready(py_plugin_module_type) != 0) return false; if (!py_plugin_module_define_constants(py_plugin_module_type)) return false; Py_INCREF(py_plugin_module_type); ret = PyModule_AddObject(module, "PluginModule", (PyObject *)py_plugin_module_type); if (ret != 0) return false; dict = PyModule_GetDict(module); pygobject_register_class(dict, "PluginModule", G_TYPE_PLUGIN_MODULE, py_plugin_module_type, Py_BuildValue("(O)", py_plugin_module_type->tp_base)); return true; }