├── .gitignore ├── .travis.yml ├── README.md ├── __init__.py └── idasix.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | 91 | # ignore temporary OS files 92 | .DS_Store 93 | Thumbs.db 94 | *.un~ 95 | *.swp 96 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.5" 5 | 6 | branches: 7 | only: 8 | - master 9 | 10 | install: 11 | - if [ -f ${PROJECT}/requirements.txt ]; then pip install -r ${PROJECT}/requirements.txt ; fi 12 | - pip install flake8 13 | 14 | script: flake8 ${PROJECT} --show-source --statistics 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # idasix 2 | IDAPython compatibility library. idasix aims to create a smooth ida development process and allow a single codebase to function with multiple IDA/IDAPython versions. It is supposed to be a very slim module that should be easily included in third party modules that would otherwise rather avoid dependencies, by directly including it inside their repository. 3 | 4 | # Inclusion in projects 5 | One of idasix's goals is ease of incorporation inside any user project. Therefore, it is built as a single file that can be easily copied to any repository. For the same reason it is also built without directory hierarchies, so submoduling idasix and importing it will also work. It is also possible to provide idasix as an independent IDA plugin, in which case idasix will automatically provide it's functionalities without being imported from any specific idapython plugin. 6 | 7 | idasix is designed not to break if multiple versions and copies are included in multiple projects. It protects itself from causing harm. 8 | 9 | # Usage 10 | once idasix is included in your project, it should be your source of IDA related modules. 11 | While the modules you're used to will be automatically loaded by idasix, it is encouraged to import from it instead of original modules when manually importing. 12 | Instead of `import idaapi` you should use `from idasix import idaapi`. 13 | Instead of `from PySide import QtGui` you should write `from idasix import QtGui`, which will provide you with a QtGui module regardless of IDA version (i.e. for both PySide and PyQt5). 14 | 15 | # Currently addressed issues 16 | This list tries being up to date and include all currently addressed IDA issues, users are encouraged to raise issues to request additional IDA version incompatibility problems. 17 | 18 | Currently addressed issues are: 19 | 20 | 1. `action_handler_t` is not a python class (Doesn't inherit `Object`) before IDA version 6.95. idasix makes sure `action_handler_t` always inherits `Object`, which enables some more python magic. 21 | 2. Linux IDA versions have an issue with using packages installed by the external python interpreter. This is a mishap by IDA. idasix adds the right "site-packages" directory to the list of python packages. 22 | 3. With IDA version 6.9, PySide (a python Qt4 library) was replaced with pyqt (using newer Qt5). idasix exposes one interface (`form idasix import QtGui`) to the appropriate version and tries mitigating some of the differences between Qt5 and 4. 23 | 4. Expose `QtCore.Signal` and `QtCore.Slot` from `idasix.QtCore` in IDA versions using pyqt5. 24 | 25 | # Projects using idasix 26 | 27 | 1. [[REmatch](https://github.com/nirizr/rematch)] - A binary matching framework that actually works. 28 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .idasix import * # noqa: F401,F403 2 | -------------------------------------------------------------------------------- /idasix.py: -------------------------------------------------------------------------------- 1 | IDA_SDK_VERSION = None 2 | try: 3 | import ida_pro 4 | IDA_SDK_VERSION = ida_pro.IDA_SDK_VERSION 5 | except ImportError: 6 | import idaapi 7 | IDA_SDK_VERSION = idaapi.IDA_SDK_VERSION 8 | 9 | if not IDA_SDK_VERSION: 10 | raise Exception("Couldn't figure out IDA version") 11 | 12 | # Handle different Qt versions. instead of: 13 | # 1. `from PySide import QtCore, QtGui` or 14 | # 2. `form PyQt5 import QtCore, QtWidgets` 15 | # use: 16 | # `from idasix import QtCore, QtWidgets` 17 | QtGui = None 18 | QtWidgets = None 19 | QtCore = None 20 | if IDA_SDK_VERSION >= 690: 21 | # IDA version >= 6.9 22 | from PyQt5 import QtCore, QtGui, QtWidgets 23 | _ = QtCore 24 | elif IDA_SDK_VERSION < 690: 25 | # IDA version <= 6.8 26 | from PySide import QtCore, QtGui 27 | QtWidgets = QtGui 28 | _ = QtCore 29 | 30 | # Create definitions for used ida modules. They'll actually be imported by 31 | # the following version based import resolution code. This is for automated 32 | # code analysis 33 | ida_idaapi = None 34 | ida_kernwin = None 35 | ida_name = None 36 | 37 | # resolve ida module names based on version. 38 | modules_list = ['ida_allins', 'ida_area', 'ida_auto', 'ida_bytes', 'ida_dbg', 39 | 'ida_diskio', 'ida_entry', 'ida_enum', 'ida_expr', 'ida_fixup', 40 | 'ida_fpro', 'ida_frame', 'ida_funcs', 'ida_gdl', 'ida_graph', 41 | 'ida_hexrays', 'ida_ida', 'ida_idaapi', 'ida_idd', 'ida_idp', 42 | 'ida_ints', 'ida_kernwin', 'ida_lines', 'ida_loader', 43 | 'ida_moves', 'ida_nalt', 'ida_name', 'ida_netnode', 44 | 'ida_offset', 'ida_pro', 'ida_queue', 'ida_registry', 45 | 'ida_search', 'ida_segment', 'ida_srarea', 'ida_strlist', 46 | 'ida_struct', 'ida_typeinf', 'ida_ua', 'ida_xref'] 47 | if IDA_SDK_VERSION >= 695: 48 | for module in modules_list: 49 | try: 50 | imported_module = __import__(module) 51 | except ImportError: 52 | import idaapi 53 | imported_module = idaapi 54 | globals()[module] = imported_module 55 | elif IDA_SDK_VERSION < 695: 56 | # this allows code written for IDA>6.95 (where idaapi is split to multiple 57 | # modules) to function under ida versions below 6.95, by mapping all module 58 | # names to the idaapi (which will hopefully expose the required api). 59 | import sys 60 | import idaapi 61 | 62 | for module in modules_list: 63 | sys.modules[module] = idaapi 64 | 65 | 66 | # expose an ida plugin so when this is loaded as a plugin, ida will keep it 67 | # loaded. this enables using idasix as an independent ida plugin instead of 68 | # carrying idasix with every module. 69 | class DummyIDASixPlugin(ida_idaapi.plugin_t): 70 | # Load when IDA starts and don't unload until it exits 71 | flags = ida_idaapi.PLUGIN_FIX 72 | 73 | def init(self, *args, **kwargs): 74 | super(DummyIDASixPlugin, self).__init__(*args, **kwargs) 75 | return ida_idaapi.PLUGIN_KEEP 76 | 77 | def run(self): 78 | pass 79 | 80 | def term(self): 81 | pass 82 | 83 | 84 | def PLUGIN_ENTRY(): # noqa: N802 85 | return DummyIDASixPlugin() 86 | 87 | 88 | # methods in charge of actually fixing ida-related differances between versions 89 | class Fix(object): 90 | @staticmethod 91 | def packagespath(): 92 | """Hack required in relatively old IDA linux/osx versions (around 93 | 6.4/5) to successfully load python packages installed in site-packages. 94 | 95 | IDA for linux/osx was using the machine's installed python instead of a 96 | packaged version, but that version was running without using 97 | site-packages. This made a user unable to install python packages and 98 | use them within ida without going through quite a bit of truble, 99 | without using this. 100 | """ 101 | import sys 102 | import os 103 | new_path = os.path.join(sys.prefix, "Lib", "site-packages") 104 | if os.path.exists(new_path) and new_path not in sys.path: 105 | sys.path += [new_path] 106 | 107 | @staticmethod 108 | def actionhandlerobject(): 109 | """Before IDA 6.95, `action_handler_t` does not inherit from `object` 110 | and that makes some python magic fail. Since 6.95 `action_handler_t` 111 | inherits `object`. This fix makes reachable `action_handler_t` inherit 112 | from `object` before 6.95, and also protects against multiple-object 113 | inheritance. 114 | """ 115 | # if action_handler_t is already defined and has the name of our mocked 116 | # fucntion, this method has been called for the second time and should 117 | # be ignored. 118 | action_handler_t_name = ida_kernwin.action_handler_t.__name__ 119 | if action_handler_t_name == "action_handler_t_objprotect": 120 | return 121 | 122 | # this makes sure we have an `object` inheriting action_handler_t 123 | # only once, regardless of regardless of version or anyone else 124 | # doing something similar before us 125 | if issubclass(ida_kernwin.action_handler_t, object): 126 | return 127 | 128 | class action_handler_t_obj(object, # noqa: N801 129 | ida_kernwin.action_handler_t): 130 | """A base object created by `idasix.Fix.actionhandlerobject` to 131 | inherit `object`.""" 132 | pass 133 | 134 | # this makes sure object will not be inherited for a second time, which 135 | # is an issue for certain ida versions. 136 | class action_handler_mc(type): # noqa: N801 137 | def __new__(cls, name, bases, dct): 138 | bases = tuple(base for base in bases if base is not object) 139 | return super(action_handler_mc, cls).__new__(cls, name, bases, 140 | dct) 141 | 142 | class action_handler_t_objprotect(action_handler_t_obj): # noqa: N801 143 | """An object inheriting from ``idasix.Fix.action_handler_t_obj` 144 | that uses a metaclass to protect against multiple `object` 145 | inharitance. This makes sure that `object` is only inherited once 146 | even when a user manually inherits from it again""" 147 | __metaclass__ = action_handler_mc 148 | 149 | ida_kernwin.action_handler_t = action_handler_t_objprotect 150 | 151 | @staticmethod 152 | def qtsignalslot(): 153 | """While pre-6.8 qt4 library pyside exposted `Qtcore.Signal` and 154 | `QtCore.Slot`, new pyqt library exposes those same methods as 155 | `QtCore.pyqtSignal` and `QtCore.pyqtSlot`. This fix makes sure 156 | `Qtcore.Signal` and `QtCore.Slot` are always available""" 157 | if IDA_SDK_VERSION >= 690: 158 | QtCore.Signal = QtCore.pyqtSignal 159 | QtCore.Slot = QtCore.pyqtSlot 160 | elif IDA_SDK_VERSION < 690: 161 | pass 162 | 163 | @staticmethod 164 | def idaname_getname(): 165 | """In IDA 7.0, the function `ida_name.get_name` dropped it's first 166 | input parameter. This replaces the function to a dummy function that 167 | can handle both scenarios.""" 168 | 169 | # if get_name is already defined and has the name of our mocked 170 | # fucntion, this method has been called for the second time and should 171 | # be ignored. 172 | getname_name = ida_name.get_name.__name__ 173 | if getname_name.startswith("idasix_get_name_"): 174 | return 175 | 176 | # save original function, so wrappers could call it directly 177 | get_name_original = ida_name.get_name 178 | 179 | # Build a function that would throw the 1st argument in case 2 180 | # arguments were given to it. This will handle IDA 6 code calling an 181 | # IDA 7 method, while keeping IDA 6 calling IDA 6 method intact. 182 | def idasix_get_name_7(*args): 183 | if len(args) == 2: 184 | args = args[1:] 185 | return get_name_original(*args) 186 | 187 | # Build a function to add the default value in the 1st argument in case 188 | # only one argument was given to it. This will handle IDA 7 code 189 | # calling an IDA 6 method, while keeping IDA 7 calling IDA 7 method 190 | # intact. 191 | def idasix_get_name_6(*args): 192 | if len(args) == 1: 193 | args = [-1] + args 194 | return get_name_original(*args) 195 | 196 | # Override real IDA API functions with the correct patch based on IDA 197 | # version. In case of IDA < 6.95, changing the function in the ida_name 198 | # module will suffice although the actual implemetnation is in idaapi 199 | # so there's no need to replace both. 200 | if IDA_SDK_VERSION >= 700: 201 | ida_name.get_name = idasix_get_name_7 202 | else: 203 | ida_name.get_name = idasix_get_name_6 204 | 205 | 206 | Fix.packagespath() 207 | Fix.actionhandlerobject() 208 | Fix.qtsignalslot() 209 | Fix.idaname_getname() 210 | --------------------------------------------------------------------------------