diff options
-rw-r--r-- | ChangeLog | 46 | ||||
-rw-r--r-- | plugins/pychrysa/analysis/content.c | 27 | ||||
-rw-r--r-- | plugins/pychrysa/analysis/contents/Makefile.am | 3 | ||||
-rw-r--r-- | plugins/pychrysa/analysis/contents/module.c | 3 | ||||
-rw-r--r-- | plugins/pychrysa/analysis/contents/restricted.c | 227 | ||||
-rw-r--r-- | plugins/pychrysa/analysis/contents/restricted.h | 42 | ||||
-rwxr-xr-x | src/analysis/contents/Makefile.am | 3 | ||||
-rw-r--r-- | src/analysis/contents/restricted.c | 613 | ||||
-rw-r--r-- | src/analysis/contents/restricted.h | 58 | ||||
-rw-r--r-- | tests/analysis/__init__.py | 0 | ||||
-rw-r--r-- | tests/analysis/contents/__init__.py | 0 | ||||
-rw-r--r-- | tests/analysis/contents/restricted.py | 141 | ||||
-rw-r--r-- | tests/arch/__init__.py | 0 | ||||
-rwxr-xr-x | tests/arch/vmpa.py | 35 | ||||
-rw-r--r-- | tests/chrysacase.py | 48 | ||||
-rw-r--r-- | tests/format/__init__.py | 0 | ||||
-rw-r--r-- | tests/format/elf/Makefile | 5 | ||||
-rw-r--r-- | tests/format/elf/__init__.py | 0 | ||||
-rw-r--r-- | tests/format/elf/non_existing_binary.py | 20 | ||||
-rw-r--r-- | tests/format/elf/oob_section_name.py | 36 | ||||
-rwxr-xr-x | tests/run.sh | 8 | ||||
-rw-r--r-- | tests/test.py | 39 |
22 files changed, 1271 insertions, 83 deletions
@@ -1,3 +1,49 @@ +15-11-26 Cyrille Bagard <nocbos@gmail.com> + + * plugins/pychrysa/analysis/content.c: + Set description strings for errors as needed. + + * plugins/pychrysa/analysis/contents/Makefile.am: + Add the new 'restricted.[ch]' files to + libpychrysaanalysiscontents_la_SOURCES. + + * plugins/pychrysa/analysis/contents/module.c: + Update code. + + * plugins/pychrysa/analysis/contents/restricted.c: + * plugins/pychrysa/analysis/contents/restricted.h: + New entries: implement restricted contents for Python. + + * src/analysis/contents/Makefile.am: + Add the new 'restricted.[ch]' files to libanalysiscontents_la_SOURCES. + + * src/analysis/contents/restricted.c: + * src/analysis/contents/restricted.h: + New entries: implement restricted contents. + + * tests/analysis/contents/__init__.py: + * tests/analysis/contents/restricted.py: + * tests/analysis/__init__.py: + * tests/arch/__init__.py: + New entries: create test cases for a Python test suite. + + * tests/arch/vmpa.py: + Update code. + + * tests/chrysacase.py: + * tests/format/elf/__init__.py: + + * tests/format/elf/Makefile: + * tests/format/elf/non_existing_binary.py: + * tests/format/elf/oob_section_name.py: + Update code. + + * tests/format/__init__.py: + * tests/run.sh: + + * tests/test.py: + Deleted entry. + 15-11-12 Cyrille Bagard <nocbos@gmail.com> * plugins/ropgadgets/select.c: diff --git a/plugins/pychrysa/analysis/content.c b/plugins/pychrysa/analysis/content.c index bd25589..00265e3 100644 --- a/plugins/pychrysa/analysis/content.c +++ b/plugins/pychrysa/analysis/content.c @@ -29,6 +29,9 @@ #include <pygobject.h> +#include <i18n.h> + + #include <analysis/content.h> #include <common/endianness.h> @@ -154,7 +157,11 @@ static PyObject *py_binary_content_read_u8(PyObject *self, PyObject *args) assert(addr != NULL); status = g_binary_content_read_u8(content, addr, &val); - if (!status) return NULL; + if (!status) + { + PyErr_SetString(PyExc_Exception, _("Invalid read access.")); + return NULL; + } result = PyBytes_FromStringAndSize((char *)&val, 1); @@ -197,7 +204,11 @@ static PyObject *py_binary_content_read_u16(PyObject *self, PyObject *args) assert(addr != NULL); status = g_binary_content_read_u16(content, addr, endianness, &val); - if (!status) return NULL; + if (!status) + { + PyErr_SetString(PyExc_Exception, _("Invalid read access.")); + return NULL; + } result = PyBytes_FromStringAndSize((char *)&val, 2); @@ -239,7 +250,11 @@ static PyObject *py_binary_content_read_u32(PyObject *self, PyObject *args) assert(addr != NULL); status = g_binary_content_read_u32(content, addr, endianness, &val); - if (!status) return NULL; + if (!status) + { + PyErr_SetString(PyExc_Exception, _("Invalid read access.")); + return NULL; + } result = PyBytes_FromStringAndSize((char *)&val, 4); @@ -281,7 +296,11 @@ static PyObject *py_binary_content_read_u64(PyObject *self, PyObject *args) assert(addr != NULL); status = g_binary_content_read_u64(content, addr, endianness, &val); - if (!status) return NULL; + if (!status) + { + PyErr_SetString(PyExc_Exception, _("Invalid read access.")); + return NULL; + } result = PyBytes_FromStringAndSize((char *)&val, 8); diff --git a/plugins/pychrysa/analysis/contents/Makefile.am b/plugins/pychrysa/analysis/contents/Makefile.am index ff835e3..3cd00a6 100644 --- a/plugins/pychrysa/analysis/contents/Makefile.am +++ b/plugins/pychrysa/analysis/contents/Makefile.am @@ -3,7 +3,8 @@ noinst_LTLIBRARIES = libpychrysaanalysiscontents.la libpychrysaanalysiscontents_la_SOURCES = \ file.h file.c \ - module.h module.c + module.h module.c \ + restricted.h restricted.c libpychrysaanalysiscontents_la_LDFLAGS = diff --git a/plugins/pychrysa/analysis/contents/module.c b/plugins/pychrysa/analysis/contents/module.c index f97ba27..366a158 100644 --- a/plugins/pychrysa/analysis/contents/module.c +++ b/plugins/pychrysa/analysis/contents/module.c @@ -29,6 +29,7 @@ #include "file.h" +#include "restricted.h" @@ -80,8 +81,8 @@ bool add_analysis_contents_module_to_python_module(PyObject *super) result &= register_python_binary_content(module); - result &= register_python_file_content(module); + result &= register_python_restricted_content(module); loading_failed: diff --git a/plugins/pychrysa/analysis/contents/restricted.c b/plugins/pychrysa/analysis/contents/restricted.c new file mode 100644 index 0000000..0b8c7da --- /dev/null +++ b/plugins/pychrysa/analysis/contents/restricted.c @@ -0,0 +1,227 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * restricted.c - prototypes pour l'équivalent Python du fichier "analysis/contents/restricted.c" + * + * Copyright (C) 2015 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 "restricted.h" + + +#include <pygobject.h> + + +#include <i18n.h> + + +#include <analysis/contents/restricted.h> + + +#include "../content.h" +#include "../../arch/vmpa.h" + + + +/* Crée un nouvel objet Python de type 'BinContent'. */ +static PyObject *py_restricted_content_new(PyTypeObject *, PyObject *, PyObject *); + + + +/****************************************************************************** +* * +* Paramètres : type = type de l'objet à instancier. * +* args = arguments fournis à l'appel. * +* kwds = arguments de type key=val fournis. * +* * +* Description : Crée un nouvel objet Python de type 'BinContent'. * +* * +* Retour : Instance Python mise en place. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *py_restricted_content_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyObject *result; /* Instance à retourner */ + PyObject *content_obj; /* Objet pour le contenu */ + PyObject *range_obj; /* Objet pour la restriction */ + int ret; /* Bilan de lecture des args. */ + GBinContent *content; /* Instance GLib correspondante*/ + mrange_t *range; /* Restriction à appliquer */ + GBinContent *restricted; /* Création GLib à transmettre */ + + ret = PyArg_ParseTuple(args, "OO", &content_obj, &range_obj); + if (!ret) return NULL; + + ret = PyObject_IsInstance(content_obj, (PyObject *)get_python_binary_content_type()); + if (!ret) + { + PyErr_SetString(PyExc_TypeError, _("The first argument must be an instance of BinContent.")); + return NULL; + } + + ret = PyObject_IsInstance(range_obj, (PyObject *)get_python_mrange_type()); + if (!ret) + { + PyErr_SetString(PyExc_TypeError, _("The second argument must be an instance of mrange.")); + return NULL; + } + + content = G_BIN_CONTENT(pygobject_get(content_obj)); + + range = get_internal_mrange(range_obj); + + restricted = g_restricted_content_new(content, range); + + result = pygobject_new(G_OBJECT(restricted)); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Fournit un accès à une définition de type à diffuser. * +* * +* Retour : Définition d'objet pour Python. * +* * +* Remarques : - * +* * +******************************************************************************/ + + + +PyMethodDef py_restricted_content_methods[] = { + { NULL } +}; + +PyGetSetDef py_restricted_content_getseters[] = { + { NULL } +}; + +PyTypeObject py_restricted_content_type = { + + PyVarObject_HEAD_INIT(NULL, 0) + + .tp_name = "pychrysalide.analysis.contents.RestrictedContent", + .tp_basicsize = sizeof(PyGObject), + + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + + .tp_doc = "PyChrysalide binary restricted content", + + /* + .tp_methods = py_restricted_content_methods, + .tp_getset = py_restricted_content_getseters, + .tp_new = (newfunc)py_restricted_content_new + */ + .tp_new = (newfunc)py_restricted_content_new + +}; + + + +PyTypeObject *get_python_restricted_content_type(void) +{ +#if 0 + static PyMethodDef py_restricted_content_methods[] = { + { NULL } + }; + + static PyGetSetDef py_restricted_content_getseters[] = { + { NULL } + }; + + static PyTypeObject py_restricted_content_type = { + + PyVarObject_HEAD_INIT(NULL, 0) + + .tp_name = "pychrysalide.analysis.contents.RestrictedContent", + .tp_basicsize = sizeof(PyGObject), + + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | 1 << 9, + + .tp_doc = "PyChrysalide binary restricted content", + + /* + .tp_methods = py_restricted_content_methods, + .tp_getset = py_restricted_content_getseters, + .tp_new = (newfunc)py_restricted_content_new + */ + + }; +#endif + return &py_restricted_content_type; + +} + + +/****************************************************************************** +* * +* Paramètres : module = module dont la définition est à compléter. * +* * +* Description : Prend en charge l'objet 'pychrysalide.glibext.BinContent'. * +* * +* Retour : Bilan de l'opération. * +* * +* Remarques : - * +* * +******************************************************************************/ +#include "../content.h"////////////////////////////////////////// +bool register_python_restricted_content(PyObject *module) +{ + PyTypeObject *py_restricted_content_type; /* Type Python 'BinContent' */ + int ret; /* Bilan d'un appel */ + PyObject *dict; /* Dictionnaire du module */ + + py_restricted_content_type = get_python_restricted_content_type(); + + //py_restricted_content_type->tp_base = &PyGObject_Type; + //py_restricted_content_type->tp_basicsize = py_restricted_content_type->tp_base->tp_basicsize; + + /* + if (PyType_Ready(py_restricted_content_type) != 0) + return false; + */ + + /* + Py_INCREF(py_restricted_content_type); + ret = PyModule_AddObject(module, "RestrictedContent", (PyObject *)py_restricted_content_type); + if (ret != 0) return false; + */ + + dict = PyModule_GetDict(module); + pygobject_register_class(dict, "RestrictedContent", G_TYPE_RESTRICTED_CONTENT, py_restricted_content_type, + Py_BuildValue("(O)", &PyGObject_Type/*py_restricted_content_type->tp_base*/, + get_python_binary_content_type())); + + /* + if (PyType_Ready(py_restricted_content_type) != 0) + return false; + */ + + + return true; + +} diff --git a/plugins/pychrysa/analysis/contents/restricted.h b/plugins/pychrysa/analysis/contents/restricted.h new file mode 100644 index 0000000..24f2697 --- /dev/null +++ b/plugins/pychrysa/analysis/contents/restricted.h @@ -0,0 +1,42 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * restricted.h - prototypes pour l'équivalent Python du fichier "analysis/contents/restricted.h" + * + * Copyright (C) 2015 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 + */ + + +#ifndef _PLUGINS_PYCHRYSA_ANALYSIS_CONTENTS_RESTRICTED_H +#define _PLUGINS_PYCHRYSA_ANALYSIS_CONTENTS_RESTRICTED_H + + +#include <Python.h> +#include <stdbool.h> + + + +/* Fournit un accès à une définition de type à diffuser. */ +PyTypeObject *get_python_restricted_content_type(void); + +/* Prend en charge l'objet 'pychrysalide.glibext.BinContent'. */ +bool register_python_restricted_content(PyObject *); + + + +#endif /* _PLUGINS_PYCHRYSA_ANALYSIS_CONTENTS_RESTRICTED_H */ diff --git a/src/analysis/contents/Makefile.am b/src/analysis/contents/Makefile.am index e2eec74..2580357 100755 --- a/src/analysis/contents/Makefile.am +++ b/src/analysis/contents/Makefile.am @@ -2,7 +2,8 @@ noinst_LTLIBRARIES = libanalysiscontents.la libanalysiscontents_la_SOURCES = \ - file.h file.c + file.h file.c \ + restricted.h restricted.c libanalysiscontents_la_LIBADD = diff --git a/src/analysis/contents/restricted.c b/src/analysis/contents/restricted.c new file mode 100644 index 0000000..a8f1763 --- /dev/null +++ b/src/analysis/contents/restricted.c @@ -0,0 +1,613 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * restricted.c - chargement de données binaires à partir d'un contenu restreint + * + * Copyright (C) 2015 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 Foobar. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "restricted.h" + + +#include <assert.h> +#include <string.h> + + +#include "../content-int.h" + + + +/* Contenu de données binaires issues d'un contenu restreint (instance) */ +struct _GRestrictedContent +{ + GObject parent; /* A laisser en premier */ + + GBinContent *internal; /* Contenu de sous-traitance */ + + mrange_t range; /* Restriction de couverture */ + +}; + +/* Contenu de données binaires issues d'un contenu restreint (classe) */ +struct _GRestrictedContentClass +{ + GObjectClass parent; /* A laisser en premier */ + +}; + + +/* Initialise la classe des contenus de données binaires. */ +static void g_restricted_content_class_init(GRestrictedContentClass *); + +/* Initialise une instance de contenu de données binaires. */ +static void g_restricted_content_init(GRestrictedContent *); + +/* Procède à l'initialisation de l'interface de lecture. */ +static void g_restricted_content_interface_init(GBinContentInterface *); + +/* Supprime toutes les références externes. */ +static void g_restricted_content_dispose(GRestrictedContent *); + +/* Procède à la libération totale de la mémoire. */ +static void g_restricted_content_finalize(GRestrictedContent *); + +/* Donne accès à une portion des données représentées. */ +static const bin_t *g_restricted_content_get_raw_access(const GRestrictedContent *, vmpa2t *, phys_t); + +/* Fournit une portion des données représentées. */ +static bool g_restricted_content_read_raw(const GRestrictedContent *, vmpa2t *, phys_t, bin_t *); + +/* Lit un nombre non signé sur quatre bits. */ +static bool g_restricted_content_read_u4(const GRestrictedContent *, vmpa2t *, bool *, uint8_t *); + +/* Lit un nombre non signé sur un octet. */ +static bool g_restricted_content_read_u8(const GRestrictedContent *, vmpa2t *, uint8_t *); + +/* Lit un nombre non signé sur deux octets. */ +static bool g_restricted_content_read_u16(const GRestrictedContent *, vmpa2t *, SourceEndian, uint16_t *); + +/* Lit un nombre non signé sur quatre octets. */ +static bool g_restricted_content_read_u32(const GRestrictedContent *, vmpa2t *, SourceEndian, uint32_t *); + +/* Lit un nombre non signé sur huit octets. */ +static bool g_restricted_content_read_u64(const GRestrictedContent *, vmpa2t *, SourceEndian, uint64_t *); + +/* Lit un nombre non signé encodé au format LEB128. */ +static bool g_restricted_content_read_uleb128(const GRestrictedContent *, vmpa2t *, uleb128_t *); + +/* Lit un nombre signé encodé au format LEB128. */ +static bool g_restricted_content_read_leb128(const GRestrictedContent *, vmpa2t *, leb128_t *); + + + +/* Indique le type défini par la GLib pour les contenus de données. */ +G_DEFINE_TYPE_WITH_CODE(GRestrictedContent, g_restricted_content, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(G_TYPE_BIN_CONTENT, g_restricted_content_interface_init)) + + +/****************************************************************************** +* * +* Paramètres : klass = classe à initialiser. * +* * +* Description : Initialise la classe des contenus de données binaires. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_restricted_content_class_init(GRestrictedContentClass *klass) +{ + GObjectClass *object; /* Autre version de la classe */ + + object = G_OBJECT_CLASS(klass); + + object->dispose = (GObjectFinalizeFunc/* ! */)g_restricted_content_dispose; + object->finalize = (GObjectFinalizeFunc)g_restricted_content_finalize; + +} + + +/****************************************************************************** +* * +* Paramètres : content = instance à initialiser. * +* * +* Description : Initialise une instance de contenu de données binaires. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_restricted_content_init(GRestrictedContent *content) +{ + +} + + +/****************************************************************************** +* * +* Paramètres : iface = interface GLib à initialiser. * +* * +* Description : Procède à l'initialisation de l'interface de lecture. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_restricted_content_interface_init(GBinContentInterface *iface) +{ + iface->get_raw_access = (get_raw_access_fc)g_restricted_content_get_raw_access; + + iface->read_raw = (read_raw_fc)g_restricted_content_read_raw; + iface->read_u4 = (read_u4_fc)g_restricted_content_read_u4; + iface->read_u8 = (read_u8_fc)g_restricted_content_read_u8; + iface->read_u16 = (read_u16_fc)g_restricted_content_read_u16; + iface->read_u32 = (read_u32_fc)g_restricted_content_read_u32; + iface->read_u64 = (read_u64_fc)g_restricted_content_read_u64; + + iface->read_uleb128 = (read_uleb128_fc)g_restricted_content_read_uleb128; + iface->read_leb128 = (read_leb128_fc)g_restricted_content_read_leb128; + +} + + +/****************************************************************************** +* * +* Paramètres : content = instance d'objet GLib à traiter. * +* * +* Description : Supprime toutes les références externes. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_restricted_content_dispose(GRestrictedContent *content) +{ + g_object_unref(G_OBJECT(content->internal)); + + G_OBJECT_CLASS(g_restricted_content_parent_class)->dispose(G_OBJECT(content)); + +} + + +/****************************************************************************** +* * +* Paramètres : content = instance d'objet GLib à traiter. * +* * +* Description : Procède à la libération totale de la mémoire. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_restricted_content_finalize(GRestrictedContent *content) +{ + G_OBJECT_CLASS(g_restricted_content_parent_class)->finalize(G_OBJECT(content)); + +} + + +/****************************************************************************** +* * +* Paramètres : content = contenu binaire où puiser les données à fournir. * +* range = espace de restrictions pour les accès. * +* * +* Description : Charge en mémoire le contenu d'un contenu restreint. * +* * +* Retour : Représentation de contenu à manipuler ou NULL en cas d'échec.* +* * +* Remarques : - * +* * +******************************************************************************/ + +GBinContent *g_restricted_content_new(GBinContent *content, const mrange_t *range) +{ + GRestrictedContent *result; /* Structure à retourner */ + + result = g_object_new(G_TYPE_RESTRICTED_CONTENT, NULL); + + result->internal = content; + g_object_ref(G_OBJECT(result->internal)); + + copy_mrange(&result->range, range); + + return G_BIN_CONTENT(result); + +} + + +/****************************************************************************** +* * +* Paramètres : content = contenu binaire à venir lire. * +* addr = position de la tête de lecture. * +* length = quantité d'octets à lire. * +* * +* Description : Donne accès à une portion des données représentées. * +* * +* Retour : Pointeur vers les données à lire ou NULL en cas d'échec. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static const bin_t *g_restricted_content_get_raw_access(const GRestrictedContent *content, vmpa2t *addr, phys_t length) +{ + const bin_t *result; /* Données utiles à renvoyer */ + mrange_t requested; /* Espace demandé en lecture */ + + init_mrange(&requested, addr, length); + + if (!mrange_contains_mrange(&content->range, &requested)) + { + result = NULL; + goto bad_range; + } + + result = g_binary_content_get_raw_access(content->internal, addr, length); + + bad_range: + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : content = contenu binaire à venir lire. * +* addr = position de la tête de lecture. * +* length = quantité d'octets à lire. * +* out = réceptacle disponible pour ces données. [OUT] * +* * +* Description : Fournit une portion des données représentées. * +* * +* Retour : Bilan de l'opération. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool g_restricted_content_read_raw(const GRestrictedContent *content, vmpa2t *addr, phys_t length, bin_t *out) +{ + bool result; /* Bilan à remonter */ + const bin_t *data; /* Pointeur vers données utiles*/ + + data = g_restricted_content_get_raw_access(content, addr, length); + + if (data != NULL) + { + result = true; + memcpy(out, data, length); + } + else + result = false; + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : content = contenu binaire à venir lire. * +* addr = position de la tête de lecture. * +* low = position éventuelle des 4 bits visés. [OUT] * +* val = lieu d'enregistrement de la lecture. [OUT] * +* * +* Description : Lit un nombre non signé sur quatre bits. * +* * +* Retour : Bilan de l'opération : true en cas de succès, false sinon. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool g_restricted_content_read_u4(const GRestrictedContent *content, vmpa2t *addr, bool *low, uint8_t *val) +{ + bool result; /* Bilan de lecture à renvoyer */ + vmpa2t old; /* Copie de sauvegarde */ + bool old_low; /* Côté de l'octet traité */ + + if (!mrange_contains_addr(&content->range, addr)) + { + result = false; + goto bad_range; + } + + copy_vmpa(&old, addr); + old_low = *low; + + result = g_binary_content_read_u4(content->internal, addr, low, val); + + if (result && !mrange_contains_addr_inclusive(&content->range, addr)) + { + copy_vmpa(addr, &old); + *low = old_low; + result = false; + } + + bad_range: + + return result; + +} + + + +/****************************************************************************** +* * +* Paramètres : content = contenu binaire à venir lire. * +* addr = position de la tête de lecture. * +* val = lieu d'enregistrement de la lecture. [OUT] * +* * +* Description : Lit un nombre non signé sur un octet. * +* * +* Retour : Bilan de l'opération : true en cas de succès, false sinon. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool g_restricted_content_read_u8(const GRestrictedContent *content, vmpa2t *addr, uint8_t *val) +{ + bool result; /* Bilan de lecture à renvoyer */ + vmpa2t old; /* Copie de sauvegarde */ + + if (!mrange_contains_addr(&content->range, addr)) + { + result = false; + goto bad_range; + } + + copy_vmpa(&old, addr); + + result = g_binary_content_read_u8(content->internal, addr, val); + + if (result && !mrange_contains_addr_inclusive(&content->range, addr)) + { + copy_vmpa(addr, &old); + result = false; + } + + bad_range: + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : content = contenu binaire à venir lire. * +* addr = position de la tête de lecture. * +* endian = ordre des bits dans la source. * +* val = lieu d'enregistrement de la lecture. [OUT] * +* * +* Description : Lit un nombre non signé sur deux octets. * +* * +* Retour : Bilan de l'opération : true en cas de succès, false sinon. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool g_restricted_content_read_u16(const GRestrictedContent *content, vmpa2t *addr, SourceEndian endian, uint16_t *val) +{ + bool result; /* Bilan de lecture à renvoyer */ + vmpa2t old; /* Copie de sauvegarde */ + + if (!mrange_contains_addr(&content->range, addr)) + { + result = false; + goto bad_range; + } + + copy_vmpa(&old, addr); + + result = g_binary_content_read_u16(content->internal, addr, endian, val); + + if (result && !mrange_contains_addr_inclusive(&content->range, addr)) + { + copy_vmpa(addr, &old); + result = false; + } + + bad_range: + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : content = contenu binaire à venir lire. * +* addr = position de la tête de lecture. * +* endian = ordre des bits dans la source. * +* val = lieu d'enregistrement de la lecture. [OUT] * +* * +* Description : Lit un nombre non signé sur quatre octets. * +* * +* Retour : Bilan de l'opération : true en cas de succès, false sinon. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool g_restricted_content_read_u32(const GRestrictedContent *content, vmpa2t *addr, SourceEndian endian, uint32_t *val) +{ + bool result; /* Bilan de lecture à renvoyer */ + vmpa2t old; /* Copie de sauvegarde */ + + if (!mrange_contains_addr(&content->range, addr)) + { + result = false; + goto bad_range; + } + + copy_vmpa(&old, addr); + + result = g_binary_content_read_u32(content->internal, addr, endian, val); + + if (result && !mrange_contains_addr_inclusive(&content->range, addr)) + { + copy_vmpa(addr, &old); + result = false; + } + + bad_range: + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : content = contenu binaire à venir lire. * +* addr = position de la tête de lecture. * +* endian = ordre des bits dans la source. * +* val = lieu d'enregistrement de la lecture. [OUT] * +* * +* Description : Lit un nombre non signé sur huit octets. * +* * +* Retour : Bilan de l'opération : true en cas de succès, false sinon. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool g_restricted_content_read_u64(const GRestrictedContent *content, vmpa2t *addr, SourceEndian endian, uint64_t *val) +{ + bool result; /* Bilan de lecture à renvoyer */ + vmpa2t old; /* Copie de sauvegarde */ + + if (!mrange_contains_addr(&content->range, addr)) + { + result = false; + goto bad_range; + } + + copy_vmpa(&old, addr); + + result = g_binary_content_read_u64(content->internal, addr, endian, val); + + if (result && !mrange_contains_addr_inclusive(&content->range, addr)) + { + copy_vmpa(addr, &old); + result = false; + } + + bad_range: + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : content = contenu binaire à venir lire. * +* addr = position de la tête de lecture. * +* val = lieu d'enregistrement de la lecture. [OUT] * +* * +* Description : Lit un nombre non signé encodé au format LEB128. * +* * +* Retour : Bilan de l'opération : true en cas de succès, false sinon. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool g_restricted_content_read_uleb128(const GRestrictedContent *content, vmpa2t *addr, uleb128_t *val) +{ + bool result; /* Bilan de lecture à renvoyer */ + vmpa2t old; /* Copie de sauvegarde */ + + if (!mrange_contains_addr(&content->range, addr)) + { + result = false; + goto bad_range; + } + + copy_vmpa(&old, addr); + + result = g_binary_content_read_uleb128(content->internal, addr, val); + + if (result && !mrange_contains_addr_inclusive(&content->range, addr)) + { + copy_vmpa(addr, &old); + result = false; + } + + bad_range: + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : content = contenu binaire à venir lire. * +* addr = position de la tête de lecture. * +* val = lieu d'enregistrement de la lecture. [OUT] * +* * +* Description : Lit un nombre signé encodé au format LEB128. * +* * +* Retour : Bilan de l'opération : true en cas de succès, false sinon. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool g_restricted_content_read_leb128(const GRestrictedContent *content, vmpa2t *addr, leb128_t *val) +{ + bool result; /* Bilan de lecture à renvoyer */ + vmpa2t old; /* Copie de sauvegarde */ + + if (!mrange_contains_addr(&content->range, addr)) + { + result = false; + goto bad_range; + } + + copy_vmpa(&old, addr); + + result = g_binary_content_read_leb128(content->internal, addr, val); + + if (result && !mrange_contains_addr_inclusive(&content->range, addr)) + { + copy_vmpa(addr, &old); + result = false; + } + + bad_range: + + return result; + +} diff --git a/src/analysis/contents/restricted.h b/src/analysis/contents/restricted.h new file mode 100644 index 0000000..3cf5d60 --- /dev/null +++ b/src/analysis/contents/restricted.h @@ -0,0 +1,58 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * restricted.h - prototypes pour le chargement de données binaires à partir d'un contenu restreint + * + * Copyright (C) 2015 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 Foobar. If not, see <http://www.gnu.org/licenses/>. + */ + + +#ifndef _ANALYSIS_CONTENTS_RESTRICTED_H +#define _ANALYSIS_CONTENTS_RESTRICTED_H + + +#include <glib-object.h> + + +#include "../content.h" + + + +#define G_TYPE_RESTRICTED_CONTENT (g_restricted_content_get_type()) +#define G_RESTRICTED_CONTENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), G_TYPE_RESTRICTED_CONTENT, GRestrictedContent)) +#define G_IS_RESTRICTED_CONTENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), G_TYPE_RESTRICTED_CONTENT)) +#define G_RESTRICTED_CONTENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_RESTRICTED_CONTENT, GRestrictedContentClass)) +#define G_IS_RESTRICTED_CONTENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_RESTRICTED_CONTENT)) +#define G_RESTRICTED_CONTENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_RESTRICTED_CONTENT, GRestrictedContentClass)) + + +/* Contenu de données binaires issues d'un contenu restreint (instance) */ +typedef struct _GRestrictedContent GRestrictedContent; + +/* Contenu de données binaires issues d'un contenu restreint (classe) */ +typedef struct _GRestrictedContentClass GRestrictedContentClass; + + +/* Indique le type défini par la GLib pour les contenus de données. */ +GType g_restricted_content_get_type(void); + +/* Charge en mémoire le contenu d'un contenu restreint. */ +GBinContent *g_restricted_content_new(GBinContent *, const mrange_t *); + + + +#endif /* _ANALYSIS_CONTENTS_RESTRICTED_H */ diff --git a/tests/analysis/__init__.py b/tests/analysis/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/analysis/__init__.py diff --git a/tests/analysis/contents/__init__.py b/tests/analysis/contents/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/analysis/contents/__init__.py diff --git a/tests/analysis/contents/restricted.py b/tests/analysis/contents/restricted.py new file mode 100644 index 0000000..e8d3e07 --- /dev/null +++ b/tests/analysis/contents/restricted.py @@ -0,0 +1,141 @@ +#!/usr/bin/python3-dbg +# -*- coding: utf-8 -*- + + +# Tests minimalistes pour valider l'intégration des contenus restreints +# depuis Python. + + +from chrysacase import ChrysalideTestCase +from pychrysalide.analysis.contents import FileContent, RestrictedContent +from pychrysalide.arch import vmpa, mrange +import tempfile + + +class TestRestrictedContent(ChrysalideTestCase): + """TestCase for analysis.contents.RestrictedContent.""" + + @classmethod + def setUpClass(cls): + + super(TestRestrictedContent, cls).setUpClass() + + cls._out = tempfile.NamedTemporaryFile() + + cls._out.write(b'\x01\x02\x03\x04') + cls._out.write(b'\x05\x06\x07\x08') + cls._out.write(b'\x11\x12\x13\x14') + cls._out.write(b'\x15\x16\x17\x18') + cls._out.write(b'\x21\x22\x23\x24') + cls._out.write(b'\x25\x26\x27\x28') + cls._out.write(b'\x31\x32\x33\x34') + cls._out.write(b'\x35\x36\x37\x38') + + cls._out.flush() + + cls.log('Using temporary file "%s"' % cls._out.name) + + + @classmethod + def tearDownClass(cls): + + super(TestRestrictedContent, cls).tearDownClass() + + cls.log('Delete file "%s"' % cls._out.name) + + cls._out.close() + + + def testReadAccess(self): + """Check valid accesses to restricted content.""" + + fcnt = FileContent(self._out.name) + + start = vmpa(12, vmpa.VMPA_NO_VIRTUAL) + covered = mrange(start, 12) # 0x15 ... 0x28 + + rcnt = RestrictedContent(fcnt, covered) + self.assertIsNotNone(rcnt) + + val = rcnt.read_u8(start) + self.assertEqual(val, b'\x15') + + val = rcnt.read_u8(start) + self.assertEqual(val, b'\x16') + + val = rcnt.read_u16(start, vmpa.SRE_LITTLE) + self.assertEqual(val, b'\x17\x18') + + val = rcnt.read_u32(start, vmpa.SRE_LITTLE) + self.assertEqual(val, b'\x21\x22\x23\x24') + + + def testBorderLineAccess(self): + """Check valid border line accesses to restricted content.""" + + fcnt = FileContent(self._out.name) + + start = vmpa(12, vmpa.VMPA_NO_VIRTUAL) + covered = mrange(start, 12) # 0x15 ... 0x28 + + rcnt = RestrictedContent(fcnt, covered) + self.assertIsNotNone(rcnt) + + start = vmpa(12, vmpa.VMPA_NO_VIRTUAL) + val = rcnt.read_u8(start) + self.assertEqual(val, b'\x15') + + start = vmpa(12, vmpa.VMPA_NO_VIRTUAL) + val = rcnt.read_u16(start, vmpa.SRE_LITTLE) + self.assertEqual(val, b'\x15\x16') + + start = vmpa(12, vmpa.VMPA_NO_VIRTUAL) + val = rcnt.read_u32(start, vmpa.SRE_LITTLE) + self.assertEqual(val, b'\x15\x16\x17\x18') + + start = vmpa(12, vmpa.VMPA_NO_VIRTUAL) + val = rcnt.read_u64(start, vmpa.SRE_LITTLE) + self.assertEqual(val, b'\x15\x16\x17\x18\x21\x22\x23\x24') + + start = vmpa(23, vmpa.VMPA_NO_VIRTUAL) + val = rcnt.read_u8(start) + self.assertEqual(val, b'\x28') + + start = vmpa(22, vmpa.VMPA_NO_VIRTUAL) + val = rcnt.read_u16(start, vmpa.SRE_LITTLE) + self.assertEqual(val, b'\x27\x28') + + start = vmpa(20, vmpa.VMPA_NO_VIRTUAL) + val = rcnt.read_u32(start, vmpa.SRE_LITTLE) + self.assertEqual(val, b'\x25\x26\x27\x28') + + start = vmpa(16, vmpa.VMPA_NO_VIRTUAL) + val = rcnt.read_u64(start, vmpa.SRE_LITTLE) + self.assertEqual(val, b'\x21\x22\x23\x24\x25\x26\x27\x28') + + + def testWrongAccess(self): + """Check invalid accesses to restricted content.""" + + fcnt = FileContent(self._out.name) + + start = vmpa(12, vmpa.VMPA_NO_VIRTUAL) + covered = mrange(start, 12) # 0x15 ... 0x28 + + rcnt = RestrictedContent(fcnt, covered) + self.assertIsNotNone(rcnt) + + with self.assertRaisesRegex(Exception, 'Invalid read access.'): + + start = vmpa(1, vmpa.VMPA_NO_VIRTUAL) + val = rcnt.read_u8(start) + + with self.assertRaisesRegex(Exception, 'Invalid read access.'): + + start = vmpa(11, vmpa.VMPA_NO_VIRTUAL) + val = rcnt.read_u16(start, vmpa.SRE_LITTLE) + + with self.assertRaisesRegex(Exception, 'Invalid read access.'): + + start = vmpa(23, vmpa.VMPA_NO_VIRTUAL) + val = rcnt.read_u16(start, vmpa.SRE_LITTLE) diff --git a/tests/arch/__init__.py b/tests/arch/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/arch/__init__.py diff --git a/tests/arch/vmpa.py b/tests/arch/vmpa.py index 9814b20..def61ea 100755 --- a/tests/arch/vmpa.py +++ b/tests/arch/vmpa.py @@ -1,32 +1,35 @@ #!/usr/bin/python3-dbg # -*- coding: utf-8 -*- -import pychrysalide -from pychrysalide.arch import vmpa -from test import TestSuite + +# Tests minimalistes pour valider l'intégration des adresses et espaces mémoire +# depuis Python. -######################## +from chrysacase import ChrysalideTestCase +from pychrysalide.arch import vmpa -TestSuite.print_sep() -addr = vmpa() +class TestVmpa(ChrysalideTestCase): + """TestCase for arch.vmpa.""" -print('repr():', repr(addr)) -print('str(): ', str(addr)) + def testInit(self): + """VMPA values are left uninitialized by default.""" -######################## + v = vmpa() -TestSuite.print_sep() + self.assertIsNone(v.phys) + self.assertIsNone(v.virt) -TestSuite.check_true('Create a virtual memory or physical address', lambda: vmpa()) -v = vmpa() + def testAdd(self): + """Verify the commutative property of addition.""" -TestSuite.check_true('VMPA values are left uninitialized by default', lambda: v.phy == None and v.virt == None) + a = vmpa(0, 0) + 1 -a = vmpa(0, 0) + 1 + b = 1 + vmpa(0, 0) -b = 1 + vmpa(0, 0) + c = vmpa(1, 1) -TestSuite.check_true('Verify the commutative property of addition', lambda: a == b) + self.assertEqual(a, b) + self.assertEqual(a, c) diff --git a/tests/chrysacase.py b/tests/chrysacase.py new file mode 100644 index 0000000..26625e7 --- /dev/null +++ b/tests/chrysacase.py @@ -0,0 +1,48 @@ +#!/usr/bin/python3-dbg +# -*- coding: utf-8 -*- + + +import unittest +import os +import sys + + +class ChrysalideTestCase(unittest.TestCase): + """Base class for all Chrysalide test cases.""" + + @classmethod + def setUpClass(cls): + + fullname = sys.modules[ChrysalideTestCase.__module__].__file__ + filename = os.path.basename(fullname) + + cls._baselen = len(fullname) - len(filename) + + + def shortDescription(self): + """Discard the direct use of __doc__ strings.""" + + return None + + + def __str__(self): + """Display the description of the current tested case.""" + + fullname = sys.modules[self.__class__.__module__].__file__ + + origin = fullname[self._baselen:] + + title = self._testMethodDoc and self._testMethodDoc or self._testMethodName + + return '[%s] "%s"' % (origin, title) + + + @classmethod + def log(cls, msg): + """Display an information message.""" + + fullname = sys.modules[cls.__module__].__file__ + + origin = fullname[cls._baselen:] + + print('[%s] *** %s ***' % (origin, msg)) diff --git a/tests/format/__init__.py b/tests/format/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/format/__init__.py diff --git a/tests/format/elf/Makefile b/tests/format/elf/Makefile index b14ff47..c32392f 100644 --- a/tests/format/elf/Makefile +++ b/tests/format/elf/Makefile @@ -1,11 +1,8 @@ -EXECUTABLES=tiny oob_section_name +EXECUTABLES=oob_section_name all: $(EXECUTABLES) -tiny: tiny.o - $(ARM_CROSS)objcopy $< -O binary $@ - oob_section_name: oob_section_name.o $(ARM_CROSS)objcopy $< -O binary $@ diff --git a/tests/format/elf/__init__.py b/tests/format/elf/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/format/elf/__init__.py diff --git a/tests/format/elf/non_existing_binary.py b/tests/format/elf/non_existing_binary.py index 47c9028..6111f03 100644 --- a/tests/format/elf/non_existing_binary.py +++ b/tests/format/elf/non_existing_binary.py @@ -5,22 +5,20 @@ # Eprouve quelques mécanismes de construction côté Python. -import pychrysalide - +from chrysacase import ChrysalideTestCase from pychrysalide.analysis.contents import FileContent from pychrysalide.format.elf import ElfFormat -cnt = FileContent("non_existing_binary") -print(cnt) +class TestNonExistingBinary(ChrysalideTestCase): + """TestCase for non existent binary loading.""" -print(cnt == None) + def testNonExistent(self): + """Try to load a non existent binary without crashing.""" -try: - fmt = ElfFormat(cnt) -except TypeError as e: - fmt = None + cnt = FileContent('non_existing_binary') + self.assertIsNone(cnt) -print(fmt) + with self.assertRaisesRegex(TypeError, 'The argument must be an instance of BinContent.'): -print(fmt == None) + fmt = ElfFormat(cnt) diff --git a/tests/format/elf/oob_section_name.py b/tests/format/elf/oob_section_name.py index da58e29..8f91efd 100644 --- a/tests/format/elf/oob_section_name.py +++ b/tests/format/elf/oob_section_name.py @@ -10,15 +10,39 @@ # lors de l'accès concret, au moment de l'appel à strlen(). -import pychrysalide - +from chrysacase import ChrysalideTestCase from pychrysalide.analysis.contents import FileContent from pychrysalide.format.elf import ElfFormat +import os +import sys + + +class TestNonExistingBinary(ChrysalideTestCase): + """TestCase for corrupted ELF binaries with wrong section names.""" + + @classmethod + def setUpClass(cls): + + super(TestNonExistingBinary, cls).setUpClass() + + cls.log('Compile binary "oob_section_name" if needed...') + + fullname = sys.modules[cls.__module__].__file__ + dirpath = os.path.dirname(fullname) + + os.system('make -C %s oob_section_name 2>&1 > /dev/null' % dirpath) + + + def testOOBSectionName(self): + """Avoid crashing when dealing with OutOfBound section names.""" -cnt = FileContent("oob_section_name") + fullname = sys.modules[self.__class__.__module__].__file__ + filename = os.path.basename(fullname) -fmt = ElfFormat(cnt) + baselen = len(fullname) - len(filename) -print(fmt) + cnt = FileContent(fullname[:baselen] + 'oob_section_name') + self.assertIsNotNone(cnt) -print(isinstance(fmt, ElfFormat)) + fmt = ElfFormat(cnt) + self.assertIsInstance(fmt, ElfFormat) diff --git a/tests/run.sh b/tests/run.sh new file mode 100755 index 0000000..24f586d --- /dev/null +++ b/tests/run.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +if [ -z "$ARM_CROSS" ]; then + echo "ARM_CROSS is not set!" + exit 1 +fi + +LANG=C python3 -m unittest discover -v -p '*py' diff --git a/tests/test.py b/tests/test.py deleted file mode 100644 index 895c4f6..0000000 --- a/tests/test.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/python3-dbg -# -*- coding: utf-8 -*- - - - -class TestSuite: - - - - - - - - - def print_sep(): - """Print a separator line.""" - - print('------------------------') - - - def check_true(desc, code): - """Check if an expression is true.""" - - try: - test = code() - except: - test = False - - if test: - print('[+] %s: OK' % desc) - else: - print('[+] %s: nok...' % desc) - raise Exception('Unexpected result!') - - - - - - |