From 811544620f903b137aeb7660183ca9d6bb0d7590 Mon Sep 17 00:00:00 2001 From: Brian Potchik Date: Wed, 20 Jun 2018 16:03:27 -0400 Subject: Add DownloadProvider Support. --- python/downloadprovider.py | 210 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 python/downloadprovider.py (limited to 'python/downloadprovider.py') diff --git a/python/downloadprovider.py b/python/downloadprovider.py new file mode 100644 index 00000000..4756fd90 --- /dev/null +++ b/python/downloadprovider.py @@ -0,0 +1,210 @@ +# Copyright (c) 2015-2018 Vector 35 LLC +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + + +import abc +import code +import ctypes +import sys +import traceback + +try: + from urllib.parse import urlparse, urlencode + from urllib.request import urlopen, Request, build_opener, install_opener, ProxyHandler + from urllib.error import HTTPError, URLError +except ImportError: + from urlparse import urlparse + from urllib import urlencode + from urllib2 import urlopen, Request, build_opener, install_opener, ProxyHandler, HTTPError, URLError + +# Binary Ninja Components +import _binaryninjacore as core +from binaryninja.setting import Setting +import startup +import log + + +class DownloadInstance(object): + def __init__(self, provider, handle = None): + if handle is None: + self._cb = core.BNDownloadInstanceCallbacks() + self._cb.context = 0 + self._cb.destroyInstance = self._cb.destroyInstance.__class__(self._destroy_instance) + self._cb.performRequest = self._cb.performRequest.__class__(self._perform_request) + self.handle = core.BNInitDownloadInstance(provider.handle, self._cb) + else: + self.handle = core.handle_of_type(handle, core.BNDownloadInstance) + self._outputCallbacks = None + + def __del__(self): + core.BNFreeDownloadInstance(self.handle) + + def _destroy_instance(self, ctxt): + try: + self.perform_destroy_instance() + except: + log.log_error(traceback.format_exc()) + + def _perform_request(self, ctxt, url): + try: + return self.perform_request(ctxt, url) + except: + log.log_error(traceback.format_exc()) + return -1 + + @abc.abstractmethod + def perform_destroy_instance(self): + raise NotImplementedError + + @abc.abstractmethod + def perform_request(self, ctxt, url): + raise NotImplementedError + + def perform_request(self, url, callbacks): + return core.BNPerformDownloadRequest(self.handle, url, callbacks) + + +class _DownloadProviderMetaclass(type): + @property + def list(self): + """List all DownloadProvider types (read-only)""" + startup._init_plugins() + count = ctypes.c_ulonglong() + types = core.BNGetDownloadProviderList(count) + result = [] + for i in xrange(0, count.value): + result.append(DownloadProvider(types[i])) + core.BNFreeDownloadProviderList(types) + return result + + def __iter__(self): + startup._init_plugins() + count = ctypes.c_ulonglong() + types = core.BNGetDownloadProviderList(count) + try: + for i in xrange(0, count.value): + yield DownloadProvider(types[i]) + finally: + core.BNFreeDownloadProviderList(types) + + def __getitem__(self, value): + startup._init_plugins() + provider = core.BNGetDownloadProviderByName(str(value)) + if provider is None: + raise KeyError("'%s' is not a valid download provider" % str(value)) + return DownloadProvider(provider) + + def __setattr__(self, name, value): + try: + type.__setattr__(self, name, value) + except AttributeError: + raise AttributeError("attribute '%s' is read only" % name) + + +class DownloadProvider(object): + __metaclass__ = _DownloadProviderMetaclass + name = None + instance_class = None + _registered_providers = [] + + def __init__(self, handle = None): + if handle is not None: + self.handle = core.handle_of_type(handle, core.BNDownloadProvider) + self.__dict__["name"] = core.BNGetDownloadProviderName(handle) + + def register(self): + self._cb = core.BNDownloadProviderCallbacks() + self._cb.context = 0 + self._cb.createInstance = self._cb.createInstance.__class__(self._create_instance) + self.handle = core.BNRegisterDownloadProvider(self.__class__.name, self._cb) + self.__class__._registered_providers.append(self) + + def _create_instance(self, ctxt): + try: + result = self.__class__.instance_class(self) + if result is None: + return None + return ctypes.cast(core.BNNewDownloadInstanceReference(result.handle), ctypes.c_void_p).value + except: + log.log_error(traceback.format_exc()) + return None + + def create_instance(self): + result = core.BNCreateDownloadProviderInstance(self.handle) + if result is None: + return None + return DownloadInstance(self, handle = result) + + +class PythonDownloadInstance(DownloadInstance): + def __init__(self, provider): + super(PythonDownloadInstance, self).__init__(provider) + + @abc.abstractmethod + def perform_destroy_instance(self): + pass + + @abc.abstractmethod + def perform_request(self, ctxt, url): + try: + proxy_setting = Setting('download-client').get_string('https-proxy') + if proxy_setting: + opener = build_opener(ProxyHandler({'https': proxy_setting})) + install_opener(opener) + + r = urlopen(url) + total_size = int(r.headers.get('content-length', 0)) + bytes_sent = 0 + while True: + data = r.read(4096) + if not data: + break + raw_bytes = (ctypes.c_ubyte * len(data)).from_buffer_copy(data) + bytes_wrote = core.BNWriteDataForDownloadInstance(self.handle, raw_bytes, len(raw_bytes)) + if bytes_wrote != len(raw_bytes): + core.BNSetErrorForDownloadInstance(self.handle, "Bytes written mismatch!") + return -1 + bytes_sent = bytes_sent + bytes_wrote + continue_download = core.BNNotifyProgressForDownloadInstance(self.handle, bytes_sent, total_size) + if continue_download is False: + core.BNSetErrorForDownloadInstance(self.handle, "Download aborted!") + return -1 + + if not bytes_sent: + core.BNSetErrorForDownloadInstance(self.handle, "Received no data!") + return -1 + + except URLError as e: + core.BNSetErrorForDownloadInstance(self.handle, e.__class__.__name__) + return -1 + except: + core.BNSetErrorForDownloadInstance(self.handle, "Unknown Exception!") + log.log_error(traceback.format_exc()) + return -1 + + return 0 + + +class PythonDownloadProvider(DownloadProvider): + name = "DefaultDownloadProvider" + instance_class = PythonDownloadInstance + + +PythonDownloadProvider().register() -- cgit v1.3.1 From 67209107fbfe586a39101b7135b3f041cb934abe Mon Sep 17 00:00:00 2001 From: Ryan Snyder Date: Mon, 2 Jul 2018 12:35:24 -0400 Subject: downloadprovider: Fail gracefully on Ubuntu 14.04 installs, add fallback Provide a fallback so that installs with Python < 2.7.9 can still receive updates. Some manual intervention is required, but now we can provide working networking on any install that has either Python >= 2.7.9 or where a user can supply the requests[security] pip package --- python/downloadprovider.py | 176 +++++++++++++++++++++++++++++---------------- 1 file changed, 116 insertions(+), 60 deletions(-) (limited to 'python/downloadprovider.py') diff --git a/python/downloadprovider.py b/python/downloadprovider.py index 4756fd90..c0cecbe5 100644 --- a/python/downloadprovider.py +++ b/python/downloadprovider.py @@ -25,20 +25,11 @@ import ctypes import sys import traceback -try: - from urllib.parse import urlparse, urlencode - from urllib.request import urlopen, Request, build_opener, install_opener, ProxyHandler - from urllib.error import HTTPError, URLError -except ImportError: - from urlparse import urlparse - from urllib import urlencode - from urllib2 import urlopen, Request, build_opener, install_opener, ProxyHandler, HTTPError, URLError - # Binary Ninja Components -import _binaryninjacore as core +import binaryninja._binaryninjacore as core from binaryninja.setting import Setting -import startup -import log +from binaryninja import startup +from binaryninja import log class DownloadInstance(object): @@ -64,7 +55,7 @@ class DownloadInstance(object): def _perform_request(self, ctxt, url): try: - return self.perform_request(ctxt, url) + return self.perform_request(url) except: log.log_error(traceback.format_exc()) return -1 @@ -153,58 +144,123 @@ class DownloadProvider(object): return DownloadInstance(self, handle = result) -class PythonDownloadInstance(DownloadInstance): - def __init__(self, provider): - super(PythonDownloadInstance, self).__init__(provider) - - @abc.abstractmethod - def perform_destroy_instance(self): - pass - - @abc.abstractmethod - def perform_request(self, ctxt, url): - try: - proxy_setting = Setting('download-client').get_string('https-proxy') - if proxy_setting: - opener = build_opener(ProxyHandler({'https': proxy_setting})) - install_opener(opener) - - r = urlopen(url) - total_size = int(r.headers.get('content-length', 0)) - bytes_sent = 0 - while True: - data = r.read(4096) - if not data: - break - raw_bytes = (ctypes.c_ubyte * len(data)).from_buffer_copy(data) - bytes_wrote = core.BNWriteDataForDownloadInstance(self.handle, raw_bytes, len(raw_bytes)) - if bytes_wrote != len(raw_bytes): - core.BNSetErrorForDownloadInstance(self.handle, "Bytes written mismatch!") - return -1 - bytes_sent = bytes_sent + bytes_wrote - continue_download = core.BNNotifyProgressForDownloadInstance(self.handle, bytes_sent, total_size) - if continue_download is False: - core.BNSetErrorForDownloadInstance(self.handle, "Download aborted!") +if sys.version_info >= (2, 7, 9): + try: + from urllib.request import urlopen, build_opener, install_opener, ProxyHandler + from urllib.error import URLError + except ImportError: + from urllib2 import urlopen, build_opener, install_opener, ProxyHandler, URLError + + class PythonDownloadInstance(DownloadInstance): + def __init__(self, provider): + super(PythonDownloadInstance, self).__init__(provider) + + @abc.abstractmethod + def perform_destroy_instance(self): + pass + + @abc.abstractmethod + def perform_request(self, url): + try: + proxy_setting = Setting('download-client').get_string('https-proxy') + if proxy_setting: + opener = build_opener(ProxyHandler({'https': proxy_setting})) + install_opener(opener) + + r = urlopen(url.decode("charmap")) + total_size = int(r.headers.get('content-length', 0)) + bytes_sent = 0 + while True: + data = r.read(4096) + if not data: + break + raw_bytes = (ctypes.c_ubyte * len(data)).from_buffer_copy(data) + bytes_wrote = core.BNWriteDataForDownloadInstance(self.handle, raw_bytes, len(raw_bytes)) + if bytes_wrote != len(raw_bytes): + core.BNSetErrorForDownloadInstance(self.handle, "Bytes written mismatch!") + return -1 + bytes_sent = bytes_sent + bytes_wrote + continue_download = core.BNNotifyProgressForDownloadInstance(self.handle, bytes_sent, total_size) + if continue_download is False: + core.BNSetErrorForDownloadInstance(self.handle, "Download aborted!") + return -1 + + if not bytes_sent: + core.BNSetErrorForDownloadInstance(self.handle, "Received no data!") return -1 - if not bytes_sent: - core.BNSetErrorForDownloadInstance(self.handle, "Received no data!") + except URLError as e: + core.BNSetErrorForDownloadInstance(self.handle, e.__class__.__name__) + return -1 + except: + core.BNSetErrorForDownloadInstance(self.handle, "Unknown Exception!") + log.log_error(traceback.format_exc()) return -1 - except URLError as e: - core.BNSetErrorForDownloadInstance(self.handle, e.__class__.__name__) - return -1 - except: - core.BNSetErrorForDownloadInstance(self.handle, "Unknown Exception!") - log.log_error(traceback.format_exc()) - return -1 - - return 0 + return 0 + + class PythonDownloadProvider(DownloadProvider): + name = "DefaultDownloadProvider" + instance_class = PythonDownloadInstance + + PythonDownloadProvider().register() +else: + try: + import requests + from requests import pyopenssl + class PythonDownloadInstance(DownloadInstance): + def __init__(self, provider): + super(PythonDownloadInstance, self).__init__(provider) + + @abc.abstractmethod + def perform_destroy_instance(self): + pass + + @abc.abstractmethod + def perform_request(self, url): + try: + proxy_setting = Setting('download-client').get_string('https-proxy') + if proxy_setting: + proxies = {"https": proxy_setting} + else: + proxies = None + + r = requests.get(url.decode("charmap"), proxies=proxies) + if not r.ok: + core.BNSetErrorForDownloadInstance(self.handle, "Received error from server") + return -1 + data = r.content + if len(data) == 0: + core.BNSetErrorForDownloadInstance(self.handle, "No data received from server!") + return -1 + raw_bytes = (ctypes.c_ubyte * len(data)).from_buffer_copy(data) + bytes_wrote = core.BNWriteDataForDownloadInstance(self.handle, raw_bytes, len(raw_bytes)) + if bytes_wrote != len(raw_bytes): + core.BNSetErrorForDownloadInstance(self.handle, "Bytes written mismatch!") + return -1 + continue_download = core.BNNotifyProgressForDownloadInstance(self.handle, bytes_wrote, bytes_wrote) + if continue_download is False: + core.BNSetErrorForDownloadInstance(self.handle, "Download aborted!") + return -1 + except requests.RequestException as e: + core.BNSetErrorForDownloadInstance(self.handle, e.__class__.__name__) + return -1 + except: + core.BNSetErrorForDownloadInstance(self.handle, "Unknown Exception!") + log.log_error(traceback.format_exc()) + return -1 + return 0 -class PythonDownloadProvider(DownloadProvider): - name = "DefaultDownloadProvider" - instance_class = PythonDownloadInstance + class PythonDownloadProvider(DownloadProvider): + name = "DefaultDownloadProvider" + instance_class = PythonDownloadInstance + PythonDownloadProvider().register() + except ImportError: + log.log_error("On this version of Python, the pip requests[security] package is required for network connectivity!") + log.log_error("On an Ubuntu 14.04 install, the following three commands are sufficient to enable networking for the current user:") + log.log_error(" sudo apt install python-pip") + log.log_error(" python -m pip install pip --upgrade --user") + log.log_error(" python -m pip install requests[security] --upgrade --user") -PythonDownloadProvider().register() -- cgit v1.3.1 From 4f1a7824357ab58d868dbd675af9b152fc3694e9 Mon Sep 17 00:00:00 2001 From: Ryan Snyder Date: Fri, 6 Jul 2018 21:15:02 -0400 Subject: Python: More useful error when loading too-old Python on Windows --- python/downloadprovider.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'python/downloadprovider.py') diff --git a/python/downloadprovider.py b/python/downloadprovider.py index c0cecbe5..d34ca358 100644 --- a/python/downloadprovider.py +++ b/python/downloadprovider.py @@ -258,9 +258,12 @@ else: PythonDownloadProvider().register() except ImportError: - log.log_error("On this version of Python, the pip requests[security] package is required for network connectivity!") - log.log_error("On an Ubuntu 14.04 install, the following three commands are sufficient to enable networking for the current user:") - log.log_error(" sudo apt install python-pip") - log.log_error(" python -m pip install pip --upgrade --user") - log.log_error(" python -m pip install requests[security] --upgrade --user") + if sys.platform == "win32": + log.log_error("Provided Python version is too old! Only Python 2.7.10 and above are known to work on Windows!") + else: + log.log_error("On Python versions below 2.7.9, the pip requests[security] package is required for network connectivity!") + log.log_error("On an Ubuntu 14.04 install, the following three commands are sufficient to enable networking for the current user:") + log.log_error(" sudo apt install python-pip") + log.log_error(" python -m pip install pip --upgrade --user") + log.log_error(" python -m pip install requests[security] --upgrade --user") -- cgit v1.3.1 From 5d4015659d20cfee839ccccdcfb96094ac8e610a Mon Sep 17 00:00:00 2001 From: KyleMiles Date: Wed, 6 Jun 2018 20:44:47 -0400 Subject: Various Python 3 support changes --- .gitignore | 14 ++ Makefile | 50 +++---- python/__init__.py | 26 ++++ python/architecture.py | 35 ++--- python/basicblock.py | 5 +- python/binaryview.py | 34 +++-- python/callingconvention.py | 7 +- python/databuffer.py | 4 +- python/demangle.py | 4 +- python/downloadprovider.py | 4 +- python/fileaccessor.py | 3 +- python/filemetadata.py | 2 +- python/function.py | 25 +++- python/functionrecognizer.py | 14 +- python/highlight.py | 2 +- python/interaction.py | 17 ++- python/log.py | 14 +- python/lowlevelil.py | 4 +- python/mainthread.py | 2 +- python/mediumlevelil.py | 2 +- python/metadata.py | 6 +- python/platform.py | 76 +++++------ python/plugin.py | 19 +-- python/pluginmanager.py | 6 +- python/scriptingprovider.py | 46 ++++--- python/setting.py | 8 +- python/transform.py | 15 +-- python/types.py | 15 ++- python/undoaction.py | 4 +- python/update.py | 12 +- scripts/install_api.py | 2 +- suite/binaries | 2 +- suite/generator.py | 107 +++++++++------ suite/testcommon.py | 282 ++++++++++++++++++++++++++------------- suite/unit.py | 309 +++++++++++++------------------------------ 35 files changed, 621 insertions(+), 556 deletions(-) (limited to 'python/downloadprovider.py') diff --git a/.gitignore b/.gitignore index fc71333a..eff64eb4 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,17 @@ CTestTestfile.cmake api-docs/source/python.rst api-docs/source/index.rst .coverage +# Make +generator +libcrypto.so.1.0.2 +libcurl.so.4 +libssl.so.1.0.2 +plugins/ +python/examples/binaryninja/ +python/libcrypto.so.1.0.2 +python/libcurl.so.4 +python/libssl.so.1.0.2 +python/plugins/ +python/types/ +suite/binaryninja/ +types/ diff --git a/Makefile b/Makefile index 852569a0..e1576dcf 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,8 @@ json.o: ./json/jsoncpp.cpp ./json/json.h ./json/json-forwards.h install: generate $(TARGET).a @echo "Installing binaryninja API."; cp -r python/* $(INSTALLPATH)/python/binaryninja - cp $(TARGETDIR)/$(TARGET).a $(INSTALLPATH) + cp $(TARGET).a $(INSTALLPATH) + @echo "Done."; generator: python/generator.cpp $(TARGET).a @echo "Building generator..."; @@ -65,43 +66,36 @@ python_test: environment python/_binaryninjacore.py python/enums.py oracle: environment python/_binaryninjacore.py python/enums.py python3 suite/generator.py -environment: python/_binaryninjacore.py python/enums.py - @echo "Copying libs to needed locations..." - @cp $(INSTALLPATH)/libbinaryninjacore.so.1 . - @cp $(INSTALLPATH)/libcurl.so.4 . - @cp $(INSTALLPATH)/libcrypto.so.1.0.2 . - @cp $(INSTALLPATH)/libssl.so.1.0.2 . - - @mkdir -p api/python/examples - @cp python/examples/bin_info.py api/python/examples/ - @cp $(INSTALLPATH)/libbinaryninjacore.so.1 api/python/ - @cp $(INSTALLPATH)/libcurl.so.4 api/python/ - @cp $(INSTALLPATH)/libcrypto.so.1.0.2 api/python/ - @cp $(INSTALLPATH)/libssl.so.1.0.2 api/python/ +environment: environment_clean python/_binaryninjacore.py python/enums.py + @echo "Copying over libs..." + cp $(INSTALLPATH)/libbinaryninjacore.so.1 . + cp $(INSTALLPATH)/libbinaryninjacore.so.1 python/ @echo "Building 'binaryninja' Packages..." - @mkdir -p suite/binaryninja/ - @cp -r python/* suite/binaryninja/ - @mkdir -p api/python/examples/binaryninja/ - @cp -r python/* api/python/examples/binaryninja/ + mkdir -p suite/binaryninja/ + cp -r python/* suite/binaryninja/ + cp -r suite/binaryninja/ python/examples/ @echo "Copying Architectures Over..." - @cp -r $(INSTALLPATH)/types/ . - @cp -r $(INSTALLPATH)/plugins/ . - @cp -r $(INSTALLPATH)/plugins/ api/python/ + cp -r $(INSTALLPATH)/types/ . + cp -r $(INSTALLPATH)/plugins/ . + cp -r $(INSTALLPATH)/types/ python/ + cp -r $(INSTALLPATH)/plugins/ python/ environment_clean: @echo "Removing 'binaryninja' Packages..." - @rm -r suite/binaryninja/ - @rm -r api/ - -@rm suite/*.pyc - + rm -rf suite/binaryninja/ + rm -rf python/examples/binaryninja/ + @echo "Removing libs..." - @rm lib* + rm -f libbinaryninjacore.so.1 + rm -f python/libbinaryninjacore.so.1 @echo "Removing Architectures..." - @rm -r types/ - @rm -r plugins/ + rm -rf types/ + rm -rf plugins/ + rm -rf python/types/ + rm -rf python/plugins/ clean: @echo " Cleaning..."; diff --git a/python/__init__.py b/python/__init__.py index 2f7cc5a0..e1288b0a 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -25,6 +25,32 @@ import sys import ctypes from time import gmtime + +# 2-3 compatibility +try: + import builtins # __builtins__ for python2 +except ImportError: + pass +def range(*args): + """ A Python2 and Python3 Compatible Range Generator """ + try: + return xrange(*args) + except NameError: + return builtins.range(*args) + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + class metaclass(type): + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + + @classmethod + def __prepare__(cls, name, this_bases): + return meta.__prepare__(name, bases) + return type.__new__(metaclass, 'temporary_class', (), {}) + + # Binary Ninja components import binaryninja._binaryninjacore as core # __all__ = [ diff --git a/python/architecture.py b/python/architecture.py index 521c750a..aee7c301 100644 --- a/python/architecture.py +++ b/python/architecture.py @@ -23,13 +23,12 @@ import traceback import ctypes import abc -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components from binaryninja import _binaryninjacore as core from binaryninja.enums import (Endianness, ImplicitRegisterExtend, BranchType, InstructionTextTokenType, LowLevelILFlagCondition, FlagRole) import binaryninja from binaryninja import log - from binaryninja import lowlevelil from binaryninja import types from binaryninja import databuffer @@ -37,8 +36,9 @@ from binaryninja import platform from binaryninja import callingconvention # 2-3 compatibility -from six import with_metaclass -from six.moves import range +from binaryninja import range +from binaryninja import with_metaclass + class _ArchitectureMetaClass(type): @@ -624,7 +624,7 @@ class Architecture(with_metaclass(_ArchitectureMetaClass, object)): def _get_full_width_registers(self, ctxt, count): try: - regs = self._full_width_regs.values() + regs = list(self._full_width_regs.values()) count[0] = len(regs) reg_buf = (ctypes.c_uint * len(regs))() for i in range(0, len(regs)): @@ -639,7 +639,7 @@ class Architecture(with_metaclass(_ArchitectureMetaClass, object)): def _get_all_registers(self, ctxt, count): try: - regs = self._regs_by_index.keys() + regs = list(self._regs_by_index.keys()) count[0] = len(regs) reg_buf = (ctypes.c_uint * len(regs))() for i in range(0, len(regs)): @@ -654,7 +654,7 @@ class Architecture(with_metaclass(_ArchitectureMetaClass, object)): def _get_all_flags(self, ctxt, count): try: - flags = self._flags_by_index.keys() + flags = list(self._flags_by_index.keys()) count[0] = len(flags) flag_buf = (ctypes.c_uint * len(flags))() for i in range(0, len(flags)): @@ -669,7 +669,7 @@ class Architecture(with_metaclass(_ArchitectureMetaClass, object)): def _get_all_flag_write_types(self, ctxt, count): try: - write_types = self._flag_write_types_by_index.keys() + write_types = list(self._flag_write_types_by_index.keys()) count[0] = len(write_types) type_buf = (ctypes.c_uint * len(write_types))() for i in range(0, len(write_types)): @@ -684,7 +684,7 @@ class Architecture(with_metaclass(_ArchitectureMetaClass, object)): def _get_all_semantic_flag_classes(self, ctxt, count): try: - sem_classes = self._semantic_flag_classes_by_index.keys() + sem_classes = list(self._semantic_flag_classes_by_index.keys()) count[0] = len(sem_classes) class_buf = (ctypes.c_uint * len(sem_classes))() for i in range(0, len(sem_classes)): @@ -699,7 +699,7 @@ class Architecture(with_metaclass(_ArchitectureMetaClass, object)): def _get_all_semantic_flag_groups(self, ctxt, count): try: - sem_groups = self._semantic_flag_groups_by_index.keys() + sem_groups = list(self._semantic_flag_groups_by_index.keys()) count[0] = len(sem_groups) group_buf = (ctypes.c_uint * len(sem_groups))() for i in range(0, len(sem_groups)): @@ -938,7 +938,7 @@ class Architecture(with_metaclass(_ArchitectureMetaClass, object)): def _get_all_register_stacks(self, ctxt, count): try: - regs = self._reg_stacks_by_index.keys() + regs = list(self._reg_stacks_by_index.keys()) count[0] = len(regs) reg_buf = (ctypes.c_uint * len(regs))() for i in range(0, len(regs)): @@ -989,7 +989,7 @@ class Architecture(with_metaclass(_ArchitectureMetaClass, object)): def _get_all_intrinsics(self, ctxt, count): try: - regs = self._intrinsics_by_index.keys() + regs = list(self._intrinsics_by_index.keys()) count[0] = len(regs) reg_buf = (ctypes.c_uint * len(regs))() for i in range(0, len(regs)): @@ -2415,8 +2415,8 @@ class CoreArchitecture(Architecture): :param str code: string representation of the instructions to be assembled :param int addr: virtual address that the instructions will be loaded at - :return: the bytes for the assembled instructions or error string - :rtype: (a tuple of instructions and empty string) or (or None and error string) + :return: the bytes for the assembled instructions + :rtype: Python3 - a 'bytes' object; Python2 - a 'str' :Example: >>> arch.assemble("je 10") @@ -2426,8 +2426,11 @@ class CoreArchitecture(Architecture): result = databuffer.DataBuffer() errors = ctypes.c_char_p() if not core.BNAssemble(self.handle, code, addr, result.handle, errors): - return None, errors.value - return str(result), errors.value + raise ValueError("Could not assemble") + if isinstance(str(result), bytes): + return str(result) + else: + return bytes(result) def is_never_branch_patch_available(self, data, addr): """ diff --git a/python/basicblock.py b/python/basicblock.py index b1f95ba3..11d5a7f4 100644 --- a/python/basicblock.py +++ b/python/basicblock.py @@ -20,14 +20,15 @@ import ctypes -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components import binaryninja from binaryninja import highlight from binaryninja import _binaryninjacore as core from binaryninja.enums import BranchType, HighlightColorStyle, HighlightStandardColor, InstructionTextTokenType # 2-3 compatibility -from six.moves import range +from binaryninja import range + class BasicBlockEdge(object): def __init__(self, branch_type, source, target, back_edge): diff --git a/python/binaryview.py b/python/binaryview.py index b1bc4822..d439f563 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -23,7 +23,7 @@ import traceback import ctypes import abc -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components from binaryninja import _binaryninjacore as core from binaryninja.enums import (AnalysisState, SymbolType, InstructionTextTokenType, Endianness, ModificationStatus, StringType, SegmentFlag, SectionSemantics) @@ -38,8 +38,8 @@ from binaryninja import lineardisassembly from binaryninja import metadata # 2-3 compatibility -from six import with_metaclass -from six.moves import range +from binaryninja import range +from binaryninja import with_metaclass class BinaryDataNotification(object): @@ -98,17 +98,19 @@ class StringReference(object): @property def value(self): - return self.view.read(self.start, self.length) + return self.view.read(self.start, self.length).decode("charmap") def __repr__(self): return "<%s: %#x, len %#x>" % (self.type, self.start, self.length) +_pending_analysis_completion_events = {} class AnalysisCompletionEvent(object): """ The ``AnalysisCompletionEvent`` object provides an asynchronous mechanism for receiving callbacks when analysis is complete. The callback runs once. A completion event must be added - for each new analysis in order to be notified of each analysis completion. + for each new analysis in order to be notified of each analysis completion. The + AnalysisCompletionEvent class takes responcibility for keeping track of the object's lifetime. :Example: >>> def on_complete(self): @@ -122,11 +124,19 @@ class AnalysisCompletionEvent(object): self.callback = callback self._cb = ctypes.CFUNCTYPE(None, ctypes.c_void_p)(self._notify) self.handle = core.BNAddAnalysisCompletionEvent(self.view.handle, None, self._cb) + global _pending_analysis_completion_events + _pending_analysis_completion_events[id(self)] = self def __del__(self): + global _pending_analysis_completion_events + if id(self) in _pending_analysis_completion_events: + del _pending_analysis_completion_events[id(self)] core.BNFreeAnalysisCompletionEvent(self.handle) def _notify(self, ctxt): + global _pending_analysis_completion_events + if id(self) in _pending_analysis_completion_events: + del _pending_analysis_completion_events[id(self)] try: self.callback(self) except: @@ -142,6 +152,9 @@ class AnalysisCompletionEvent(object): """ self.callback = self._empty_callback core.BNCancelAnalysisCompletionEvent(self.handle) + global _pending_analysis_completion_events + if id(self) in _pending_analysis_completion_events: + del _pending_analysis_completion_events[id(self)] class ActiveAnalysisInfo(object): @@ -1731,10 +1744,13 @@ class BinaryView(object): """ ``read`` returns the data reads at most ``length`` bytes from virtual address ``addr``. + Note: Python2 returns a str, but Python3 returns a bytes object. str(DataBufferObject) will + still get you a str in either case. + :param int addr: virtual address to read from. :param int length: number of bytes to read. :return: at most ``length`` bytes from the virtual address ``addr``, empty string on error or no data. - :rtype: str + :rtype: python2 - str; python3 - bytes :Example: >>> #Opening a x86_64 Mach-O binary @@ -2784,7 +2800,9 @@ class BinaryView(object): def add_analysis_completion_event(self, callback): """ ``add_analysis_completion_event`` sets up a call back function to be called when analysis has been completed. - This is helpful when using asynchronously analysis. + This is helpful when using ``update_analysis`` which does not wait for analysis completion before returning. + + The callee of this function is not resposible for maintaining the lifetime of the returned AnalysisCompletionEvent object. :param callable() callback: A function to be called with no parameters when analysis has completed. :return: An initialized AnalysisCompletionEvent object. @@ -3481,7 +3499,7 @@ class BinaryView(object): def get_unique_section_names(self, name_list): incoming_names = (ctypes.c_char_p * len(name_list))() for i in range(0, len(name_list)): - incoming_names[i] = name_list[i] + incoming_names[i] = name_list[i].encode('charmap') outgoing_names = core.BNGetUniqueSectionNames(self.handle, incoming_names, len(name_list)) result = [] for i in range(0, len(name_list)): diff --git a/python/callingconvention.py b/python/callingconvention.py index 1b0d068e..6b906184 100644 --- a/python/callingconvention.py +++ b/python/callingconvention.py @@ -21,18 +21,17 @@ import traceback import ctypes -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components import binaryninja from binaryninja import _binaryninjacore as core from binaryninja import log from binaryninja.enums import VariableSourceType # 2-3 compatibility -from six.moves import range +from binaryninja import range class CallingConvention(object): - from binaryninja import types name = None caller_saved_regs = [] int_arg_regs = [] @@ -48,7 +47,7 @@ class CallingConvention(object): _registered_calling_conventions = [] - def __init__(self, arch=None, name=None, handle=None, confidence=types.max_confidence): + def __init__(self, arch=None, name=None, handle=None, confidence=binaryninja.types.max_confidence): if handle is None: if arch is None or name is None: raise ValueError("Must specify either handle or architecture and name") diff --git a/python/databuffer.py b/python/databuffer.py index 1b29a7eb..298d0002 100644 --- a/python/databuffer.py +++ b/python/databuffer.py @@ -20,7 +20,7 @@ import ctypes -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components from binaryninja import _binaryninjacore as core @@ -114,7 +114,7 @@ class DataBuffer(object): def __str__(self): buf = ctypes.create_string_buffer(len(self)) ctypes.memmove(buf, core.BNGetDataBufferContents(self.handle), len(self)) - if type(buf.raw) is str: + if isinstance(buf.raw, str): return buf.raw else: return buf.raw.decode("charmap") diff --git a/python/demangle.py b/python/demangle.py index 90a19761..466324bc 100644 --- a/python/demangle.py +++ b/python/demangle.py @@ -20,11 +20,11 @@ import ctypes -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components from binaryninja import _binaryninjacore as core # 2-3 compatibility -from six.moves import range +from binaryninja import range def get_qualified_name(names): diff --git a/python/downloadprovider.py b/python/downloadprovider.py index d34ca358..c714cb98 100644 --- a/python/downloadprovider.py +++ b/python/downloadprovider.py @@ -28,6 +28,7 @@ import traceback # Binary Ninja Components import binaryninja._binaryninjacore as core from binaryninja.setting import Setting +from binaryninja import with_metaclass from binaryninja import startup from binaryninja import log @@ -109,8 +110,7 @@ class _DownloadProviderMetaclass(type): raise AttributeError("attribute '%s' is read only" % name) -class DownloadProvider(object): - __metaclass__ = _DownloadProviderMetaclass +class DownloadProvider(with_metaclass(_DownloadProviderMetaclass, object)): name = None instance_class = None _registered_providers = [] diff --git a/python/fileaccessor.py b/python/fileaccessor.py index 3c7c5e45..c2539b39 100644 --- a/python/fileaccessor.py +++ b/python/fileaccessor.py @@ -21,11 +21,10 @@ import traceback import ctypes -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components from binaryninja import _binaryninjacore as core class FileAccessor(object): - def __init__(self): self._cb = core.BNFileAccessor() self._cb.context = 0 diff --git a/python/filemetadata.py b/python/filemetadata.py index ee69dce8..cafe1d67 100644 --- a/python/filemetadata.py +++ b/python/filemetadata.py @@ -22,7 +22,7 @@ from __future__ import absolute_import import traceback import ctypes -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja Components import binaryninja from binaryninja import _binaryninjacore as core from binaryninja import associateddatastore #required for _FileMetadataAssociatedDataStore diff --git a/python/function.py b/python/function.py index 6392d7f2..8407eece 100644 --- a/python/function.py +++ b/python/function.py @@ -23,20 +23,20 @@ import threading import traceback import ctypes -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components import binaryninja from binaryninja import _binaryninjacore as core +from binaryninja import associateddatastore #required in the main scope due to being an argument for _FunctionAssociatedDataStore +from binaryninja import highlight +from binaryninja import log +from binaryninja import types from binaryninja.enums import (AnalysisSkipReason, FunctionGraphType, BranchType, SymbolType, InstructionTextTokenType, HighlightStandardColor, HighlightColorStyle, RegisterValueType, ImplicitRegisterExtend, DisassemblyOption, IntegerDisplayType, InstructionTextTokenContext, VariableSourceType, FunctionAnalysisSkipOverride) -from binaryninja import associateddatastore #required in the main scope due to being an argument for _FunctionAssociatedDataStore -from binaryninja import types -from binaryninja import highlight -from binaryninja import log # 2-3 compatibility -from six.moves import range +from binaryninja import range class LookupTableEntry(object): @@ -49,7 +49,7 @@ class LookupTableEntry(object): class RegisterValue(object): - def __init__(self, arch = None, value = None, confidence = binaryninja.types.max_confidence): + def __init__(self, arch = None, value = None, confidence = types.max_confidence): self.is_constant = False if value is None: self.type = RegisterValueType.UndeterminedValue @@ -1160,6 +1160,8 @@ class Function(object): for j in range(0, lines[i].count): token_type = InstructionTextTokenType(lines[i].tokens[j].type) text = lines[i].tokens[j].text + if not isinstance(text, str): + text = text.encode("charmap") value = lines[i].tokens[j].value size = lines[i].tokens[j].size operand = lines[i].tokens[j].operand @@ -1836,6 +1838,7 @@ class DisassemblySettings(object): core.BNSetDisassemblySettingsOption(self.handle, option, state) +_pending_function_graph_completion_events = {} class FunctionGraph(object): def __init__(self, view, handle): self.view = view @@ -1969,6 +1972,9 @@ class FunctionGraph(object): try: if self._on_complete is not None: self._on_complete() + global _pending_function_graph_completion_events + if id(self) in _pending_function_graph_completion_events: + del _pending_function_graph_completion_events[id(self)] except: log.log_error(traceback.format_exc()) @@ -1993,11 +1999,16 @@ class FunctionGraph(object): self._wait_cond.release() def on_complete(self, callback): + global _pending_function_graph_completion_events + _pending_function_graph_completion_events[id(self)] = self self._on_complete = callback core.BNSetFunctionGraphCompleteCallback(self.handle, None, self._cb) def abort(self): core.BNAbortFunctionGraph(self.handle) + global _pending_function_graph_completion_events + if id(self) in _pending_function_graph_completion_events: + del _pending_function_graph_completion_events[id(self)] def get_blocks_in_region(self, left, top, right, bottom): count = ctypes.c_ulonglong() diff --git a/python/functionrecognizer.py b/python/functionrecognizer.py index e390a3c8..319bff0e 100644 --- a/python/functionrecognizer.py +++ b/python/functionrecognizer.py @@ -20,15 +20,15 @@ import traceback -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components from binaryninja import _binaryninjacore as core +from binaryninja import function +from binaryninja import filemetadata +from binaryninja import binaryview +from binaryninja import lowlevelil class FunctionRecognizer(object): - from binaryninja import function - from binaryninja import filemetadata - from binaryninja import binaryview - from binaryninja import lowlevelil _instance = None @@ -54,7 +54,7 @@ class FunctionRecognizer(object): try: file_metadata = filemetadata.FileMetadata(handle = core.BNGetFileForView(data)) view = binaryview.BinaryView(file_metadata = file_metadata, handle = core.BNNewViewReference(data)) - func = binaryninja.function.Function(view, handle = core.BNNewFunctionReference(func)) + func = function.Function(view, handle = core.BNNewFunctionReference(func)) il = lowlevelil.LowLevelILFunction(func.arch, handle = core.BNNewLowLevelILFunctionReference(il)) return self.recognize_low_level_il(view, func, il) except: @@ -68,7 +68,7 @@ class FunctionRecognizer(object): try: file_metadata = filemetadata.FileMetadata(handle = core.BNGetFileForView(data)) view = binaryview.BinaryView(file_metadata = file_metadata, handle = core.BNNewViewReference(data)) - func = binaryninja.function.Function(view, handle = core.BNNewFunctionReference(func)) + func = function.Function(view, handle = core.BNNewFunctionReference(func)) il = mediumlevelil.MediumLevelILFunction(func.arch, handle = core.BNNewMediumLevelILFunctionReference(il)) return self.recognize_medium_level_il(view, func, il) except: diff --git a/python/highlight.py b/python/highlight.py index b04561bd..55246a9a 100644 --- a/python/highlight.py +++ b/python/highlight.py @@ -19,7 +19,7 @@ # IN THE SOFTWARE. -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components from binaryninja import _binaryninjacore as core from binaryninja.enums import HighlightColorStyle, HighlightStandardColor diff --git a/python/interaction.py b/python/interaction.py index 5b78b278..49b4e024 100644 --- a/python/interaction.py +++ b/python/interaction.py @@ -21,12 +21,13 @@ import ctypes import traceback -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components from binaryninja import _binaryninjacore as core from binaryninja.enums import FormInputFieldType, MessageBoxIcon, MessageBoxButtonSet, MessageBoxButtonResult +from binaryninja import binaryview # 2-3 compatibility -from six.moves import range +from binaryninja import range class LabelField(object): @@ -152,7 +153,11 @@ class AddressField(object): class ChoiceField(object): """ ``ChoiceField`` prompts the user to choose from the list of strings provided in ``choices``. Result is stored - in self.result as an index in to the coices array. + in self.result as an index in to the choices array. + + :attr str prompt: prompt to be presented to the user + :attr list(str) choices: list of choices to choose from + """ def __init__(self, prompt, choices): self.prompt = prompt @@ -164,7 +169,7 @@ class ChoiceField(object): value.prompt = self.prompt choice_buf = (ctypes.c_char_p * len(self.choices))() for i in range(0, len(self.choices)): - choice_buf[i] = str(self.choices[i]) + choice_buf[i] = self.choices[i].encode('charmap') value.choices = choice_buf value.count = len(self.choices) @@ -242,8 +247,6 @@ class DirectoryNameField(object): class InteractionHandler(object): - - from binaryninja import binaryview _interaction_handler = None def __init__(self): @@ -617,7 +620,7 @@ def get_choice_input(prompt, title, choices): """ choice_buf = (ctypes.c_char_p * len(choices))() for i in range(0, len(choices)): - choice_buf[i] = str(choices[i]) + choice_buf[i] = str(choices[i]).encode('charmap') value = ctypes.c_ulonglong() if not core.BNGetChoiceInput(value, prompt, title, choice_buf, len(choices)): return None diff --git a/python/log.py b/python/log.py index d4d87019..9a5e1358 100644 --- a/python/log.py +++ b/python/log.py @@ -19,7 +19,7 @@ # IN THE SOFTWARE. -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components from binaryninja import _binaryninjacore as core from binaryninja.enums import LogLevel @@ -55,7 +55,7 @@ def log(level, text): :param str text: message to print :rtype: None """ - core.BNLog(level, "%s", str(text)) + core.BNLog(level,text) def log_debug(text): @@ -70,7 +70,7 @@ def log_debug(text): >>> log_debug("Hotdogs!") Hotdogs! """ - core.BNLogDebug("%s", str(text)) + core.BNLogDebug(text) def log_info(text): @@ -85,7 +85,7 @@ def log_info(text): Saucisson! >>> """ - core.BNLogInfo("%s", str(text)) + core.BNLogInfo(text) def log_warn(text): @@ -101,7 +101,7 @@ def log_warn(text): Chilidogs! >>> """ - core.BNLogWarn("%s", str(text)) + core.BNLogWarn(text) def log_error(text): @@ -117,7 +117,7 @@ def log_error(text): Spanferkel! >>> """ - core.BNLogError("%s", str(text)) + core.BNLogError(text) def log_alert(text): @@ -133,7 +133,7 @@ def log_alert(text): Kielbasa! >>> """ - core.BNLogAlert("%s", str(text)) + core.BNLogAlert(text) def log_to_stdout(min_level=LogLevel.InfoLog): diff --git a/python/lowlevelil.py b/python/lowlevelil.py index 272b2474..e714eb48 100644 --- a/python/lowlevelil.py +++ b/python/lowlevelil.py @@ -21,14 +21,14 @@ import ctypes import struct -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components import binaryninja from binaryninja import _binaryninjacore as core from binaryninja.enums import LowLevelILOperation, LowLevelILFlagCondition, InstructionTextTokenType from binaryninja import basicblock #required for LowLevelILBasicBlock # 2-3 compatibility -from six.moves import range +from binaryninja import range class LowLevelILLabel(object): diff --git a/python/mainthread.py b/python/mainthread.py index 16938f1a..5b0cd53c 100644 --- a/python/mainthread.py +++ b/python/mainthread.py @@ -18,7 +18,7 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components from binaryninja import _binaryninjacore as core from binaryninja import scriptingprovider diff --git a/python/mediumlevelil.py b/python/mediumlevelil.py index f7e9951a..cfa8f900 100644 --- a/python/mediumlevelil.py +++ b/python/mediumlevelil.py @@ -30,7 +30,7 @@ from binaryninja import types from binaryninja import lowlevelil # 2-3 compatibility -from six.moves import range +from binaryninja import range class SSAVariable(object): diff --git a/python/metadata.py b/python/metadata.py index fed8bdaa..b17bbf4f 100644 --- a/python/metadata.py +++ b/python/metadata.py @@ -22,12 +22,12 @@ from __future__ import absolute_import import ctypes -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components from binaryninja import _binaryninjacore as core from binaryninja.enums import MetadataType # 2-3 compatibility -from six.moves import range +from binaryninja import range class Metadata(object): @@ -147,7 +147,7 @@ class Metadata(object): result = core.BNMetadataGetValueStore(self.handle) try: for i in range(result.contents.size): - if type(result.contents.keys[i]) is bytes: + if isinstance(result.contents.keys[i], bytes): yield str(result.contents.keys[i].decode('charmap')) else: yield result.contents.keys[i] diff --git a/python/platform.py b/python/platform.py index c57315ce..9a5f70df 100644 --- a/python/platform.py +++ b/python/platform.py @@ -20,18 +20,18 @@ import ctypes -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components import binaryninja from binaryninja import _binaryninjacore as core +from binaryninja import types -#2-3 compatibility -from six import with_metaclass -from six.moves import range +# 2-3 compatibility +from binaryninja import range +from binaryninja import with_metaclass class _PlatformMetaClass(type): - from binaryninja import types @property def list(self): binaryninja._init_plugins() @@ -240,8 +240,8 @@ class Platform(with_metaclass(_PlatformMetaClass, object)): type_list = core.BNGetPlatformTypes(self.handle, count) result = {} for i in range(0, count.value): - name = binaryninja.types.QualifiedName._from_core_struct(type_list[i].name) - result[name] = binaryninja.types.Type(core.BNNewTypeReference(type_list[i].type), platform = self) + name = types.QualifiedName._from_core_struct(type_list[i].name) + result[name] = types.Type(core.BNNewTypeReference(type_list[i].type), platform = self) core.BNFreeTypeList(type_list, count.value) return result @@ -252,8 +252,8 @@ class Platform(with_metaclass(_PlatformMetaClass, object)): type_list = core.BNGetPlatformVariables(self.handle, count) result = {} for i in range(0, count.value): - name = binaryninja.types.QualifiedName._from_core_struct(type_list[i].name) - result[name] = binaryninja.types.Type(core.BNNewTypeReference(type_list[i].type), platform = self) + name = types.QualifiedName._from_core_struct(type_list[i].name) + result[name] = types.Type(core.BNNewTypeReference(type_list[i].type), platform = self) core.BNFreeTypeList(type_list, count.value) return result @@ -264,8 +264,8 @@ class Platform(with_metaclass(_PlatformMetaClass, object)): type_list = core.BNGetPlatformFunctions(self.handle, count) result = {} for i in range(0, count.value): - name = binaryninja.types.QualifiedName._from_core_struct(type_list[i].name) - result[name] = binaryninja.types.Type(core.BNNewTypeReference(type_list[i].type), platform = self) + name = types.QualifiedName._from_core_struct(type_list[i].name) + result[name] = types.Type(core.BNNewTypeReference(type_list[i].type), platform = self) core.BNFreeTypeList(type_list, count.value) return result @@ -276,8 +276,8 @@ class Platform(with_metaclass(_PlatformMetaClass, object)): call_list = core.BNGetPlatformSystemCalls(self.handle, count) result = {} for i in range(0, count.value): - name = binaryninja.types.QualifiedName._from_core_struct(call_list[i].name) - t = binaryninja.types.Type(core.BNNewTypeReference(call_list[i].type), platform = self) + name = types.QualifiedName._from_core_struct(call_list[i].name) + t = types.Type(core.BNNewTypeReference(call_list[i].type), platform = self) result[call_list[i].number] = (name, t) core.BNFreeSystemCallList(call_list, count.value) return result @@ -328,25 +328,25 @@ class Platform(with_metaclass(_PlatformMetaClass, object)): return Platform(None, handle = result), new_addr.value def get_type_by_name(self, name): - name = binaryninja.types.QualifiedName(name)._get_core_struct() + name = types.QualifiedName(name)._get_core_struct() obj = core.BNGetPlatformTypeByName(self.handle, name) if not obj: return None - return binaryninja.types.Type(obj, platform = self) + return types.Type(obj, platform = self) def get_variable_by_name(self, name): - name = binaryninja.types.QualifiedName(name)._get_core_struct() + name = types.QualifiedName(name)._get_core_struct() obj = core.BNGetPlatformVariableByName(self.handle, name) if not obj: return None - return binaryninja.types.Type(obj, platform = self) + return types.Type(obj, platform = self) def get_function_by_name(self, name): - name = binaryninja.types.QualifiedName(name)._get_core_struct() + name = types.QualifiedName(name)._get_core_struct() obj = core.BNGetPlatformFunctionByName(self.handle, name) if not obj: return None - return binaryninja.types.Type(obj, platform = self) + return types.Type(obj, platform = self) def get_system_call_name(self, number): return core.BNGetPlatformSystemCallName(self.handle, number) @@ -355,15 +355,15 @@ class Platform(with_metaclass(_PlatformMetaClass, object)): obj = core.BNGetPlatformSystemCallType(self.handle, number) if not obj: return None - return binaryninja.types.Type(obj, platform = self) + return types.Type(obj, platform = self) def generate_auto_platform_type_id(self, name): - name = binaryninja.types.QualifiedName(name)._get_core_struct() + name = types.QualifiedName(name)._get_core_struct() return core.BNGenerateAutoPlatformTypeId(self.handle, name) def generate_auto_platform_type_ref(self, type_class, name): type_id = self.generate_auto_platform_type_id(name) - return binaryninja.types.NamedTypeReference(type_class, type_id, name) + return types.NamedTypeReference(type_class, type_id, name) def get_auto_platform_type_id_source(self): return core.BNGetAutoPlatformTypeIdSource(self.handle) @@ -391,7 +391,7 @@ class Platform(with_metaclass(_PlatformMetaClass, object)): filename = "input" dir_buf = (ctypes.c_char_p * len(include_dirs))() for i in range(0, len(include_dirs)): - dir_buf[i] = str(include_dirs[i]) + dir_buf[i] = include_dirs[i].encode('charmap') parse = core.BNTypeParserResult() errors = ctypes.c_char_p() result = core.BNParseTypesFromSource(self.handle, source, filename, parse, errors, dir_buf, @@ -404,16 +404,16 @@ class Platform(with_metaclass(_PlatformMetaClass, object)): variables = {} functions = {} for i in range(0, parse.typeCount): - name = binaryninja.types.QualifiedName._from_core_struct(parse.types[i].name) - type_dict[name] = binaryninja.types.Type(core.BNNewTypeReference(parse.types[i].type), platform = self) + name = types.QualifiedName._from_core_struct(parse.types[i].name) + type_dict[name] = types.Type(core.BNNewTypeReference(parse.types[i].type), platform = self) for i in range(0, parse.variableCount): - name = binaryninja.types.QualifiedName._from_core_struct(parse.variables[i].name) - variables[name] = binaryninja.types.Type(core.BNNewTypeReference(parse.variables[i].type), platform = self) + name = types.QualifiedName._from_core_struct(parse.variables[i].name) + variables[name] = types.Type(core.BNNewTypeReference(parse.variables[i].type), platform = self) for i in range(0, parse.functionCount): - name = binaryninja.types.QualifiedName._from_core_struct(parse.functions[i].name) - functions[name] = binaryninja.types.Type(core.BNNewTypeReference(parse.functions[i].type), platform = self) + name = types.QualifiedName._from_core_struct(parse.functions[i].name) + functions[name] = types.Type(core.BNNewTypeReference(parse.functions[i].type), platform = self) core.BNFreeTypeParserResult(parse) - return binaryninja.types.TypeParserResult(type_dict, variables, functions) + return types.TypeParserResult(type_dict, variables, functions) def parse_types_from_source_file(self, filename, include_dirs=[], auto_type_source=None): """ @@ -437,7 +437,7 @@ class Platform(with_metaclass(_PlatformMetaClass, object)): """ dir_buf = (ctypes.c_char_p * len(include_dirs))() for i in range(0, len(include_dirs)): - dir_buf[i] = str(include_dirs[i]) + dir_buf[i] = include_dirs[i].encode('charmap') parse = core.BNTypeParserResult() errors = ctypes.c_char_p() result = core.BNParseTypesFromSourceFile(self.handle, filename, parse, errors, dir_buf, @@ -450,13 +450,13 @@ class Platform(with_metaclass(_PlatformMetaClass, object)): variables = {} functions = {} for i in range(0, parse.typeCount): - name = binaryninja.types.QualifiedName._from_core_struct(parse.types[i].name) - type_dict[name] = binaryninja.types.Type(core.BNNewTypeReference(parse.types[i].type), platform = self) + name = types.QualifiedName._from_core_struct(parse.types[i].name) + type_dict[name] = types.Type(core.BNNewTypeReference(parse.types[i].type), platform = self) for i in range(0, parse.variableCount): - name = binaryninja.types.QualifiedName._from_core_struct(parse.variables[i].name) - variables[name] = binaryninja.types.Type(core.BNNewTypeReference(parse.variables[i].type), platform = self) + name = types.QualifiedName._from_core_struct(parse.variables[i].name) + variables[name] = types.Type(core.BNNewTypeReference(parse.variables[i].type), platform = self) for i in range(0, parse.functionCount): - name = binaryninja.types.QualifiedName._from_core_struct(parse.functions[i].name) - functions[name] = binaryninja.types.Type(core.BNNewTypeReference(parse.functions[i].type), platform = self) + name = types.QualifiedName._from_core_struct(parse.functions[i].name) + functions[name] = types.Type(core.BNNewTypeReference(parse.functions[i].type), platform = self) core.BNFreeTypeParserResult(parse) - return binaryninja.types.TypeParserResult(type_dict, variables, functions) + return types.TypeParserResult(type_dict, variables, functions) diff --git a/python/plugin.py b/python/plugin.py index 52feced4..b8217f0b 100644 --- a/python/plugin.py +++ b/python/plugin.py @@ -22,15 +22,18 @@ import traceback import ctypes import threading -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components import binaryninja from binaryninja import log from binaryninja import _binaryninjacore as core from binaryninja.enums import PluginCommandType +from binaryninja import filemetadata +from binaryninja import binaryview +from binaryninja import function -#2-3 compatibility -from six import with_metaclass -from six.moves import range +# 2-3 compatibility +from binaryninja import range +from binaryninja import with_metaclass class PluginCommandContext(object): @@ -43,7 +46,6 @@ class PluginCommandContext(object): class _PluginCommandMetaClass(type): - @property def list(self): binaryninja._init_plugins() @@ -73,11 +75,6 @@ class _PluginCommandMetaClass(type): class PluginCommand(with_metaclass(_PluginCommandMetaClass, object)): - - from binaryninja import filemetadata - from binaryninja import binaryview - from binaryninja import function - _registered_commands = [] def __init__(self, cmd): @@ -540,7 +537,6 @@ class MainThreadAction(object): class MainThreadActionHandler(object): - _main_thread = None def __init__(self): @@ -586,7 +582,6 @@ class _BackgroundTaskMetaclass(type): class BackgroundTask(with_metaclass(_BackgroundTaskMetaclass, object)): - def __init__(self, initial_progress_text = "", can_cancel = False, handle = None): if handle is None: self.handle = core.BNBeginBackgroundTask(initial_progress_text, can_cancel) diff --git a/python/pluginmanager.py b/python/pluginmanager.py index ad314629..c7a0c83a 100644 --- a/python/pluginmanager.py +++ b/python/pluginmanager.py @@ -20,11 +20,12 @@ import ctypes -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components from binaryninja import _binaryninjacore as core # 2-3 compatibility -from six.moves import range +from binaryninja import range + class RepoPlugin(object): """ @@ -201,7 +202,6 @@ class RepositoryManager(object): ``RepositoryManager`` Keeps track of all the repositories and keeps the enabled_plugins.json file coherent with the plugins that are installed/unstalled enabled/disabled """ - def __init__(self, handle=None): raise Exception("RepositoryManager temporarily disabled!") self.handle = core.BNGetRepositoryManager() diff --git a/python/scriptingprovider.py b/python/scriptingprovider.py index 374b2f41..37642ed4 100644 --- a/python/scriptingprovider.py +++ b/python/scriptingprovider.py @@ -26,14 +26,15 @@ import threading import abc import sys -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components +import binaryninja from binaryninja import _binaryninjacore as core from binaryninja.enums import ScriptingProviderExecuteResult, ScriptingProviderInputReadyState -import binaryninja.log +from binaryninja import log -#2-3 compatibility -from six import with_metaclass -from six.moves import range +# 2-3 compatibility +from binaryninja import range +from binaryninja import with_metaclass class _ThreadActionContext(object): @@ -75,19 +76,19 @@ class ScriptingOutputListener(object): try: self.notify_output(text) except: - binaryninja.log.log_error(traceback.format_exc()) + log.log_error(traceback.format_exc()) def _error(self, ctxt, text): try: self.notify_error(text) except: - binaryninja.log.log_error(traceback.format_exc()) + log.log_error(traceback.format_exc()) def _input_ready_state_changed(self, ctxt, state): try: self.notify_input_ready_state_changed(state) except: - binaryninja.log.log_error(traceback.format_exc()) + log.log_error(traceback.format_exc()) def notify_output(self, text): pass @@ -123,13 +124,13 @@ class ScriptingInstance(object): try: self.perform_destroy_instance() except: - binaryninja.log.log_error(traceback.format_exc()) + log.log_error(traceback.format_exc()) def _execute_script_input(self, ctxt, text): try: return self.perform_execute_script_input(text) except: - binaryninja.log.log_error(traceback.format_exc()) + log.log_error(traceback.format_exc()) return ScriptingProviderExecuteResult.InvalidScriptInput def _set_current_binary_view(self, ctxt, view): @@ -140,7 +141,7 @@ class ScriptingInstance(object): view = None self.perform_set_current_binary_view(view) except: - binaryninja.log.log_error(traceback.format_exc()) + log.log_error(traceback.format_exc()) def _set_current_function(self, ctxt, func): try: @@ -150,7 +151,7 @@ class ScriptingInstance(object): func = None self.perform_set_current_function(func) except: - binaryninja.log.log_error(traceback.format_exc()) + log.log_error(traceback.format_exc()) def _set_current_basic_block(self, ctxt, block): try: @@ -165,19 +166,19 @@ class ScriptingInstance(object): block = None self.perform_set_current_basic_block(block) except: - binaryninja.log.log_error(traceback.format_exc()) + log.log_error(traceback.format_exc()) def _set_current_address(self, ctxt, addr): try: self.perform_set_current_address(addr) except: - binaryninja.log.log_error(traceback.format_exc()) + log.log_error(traceback.format_exc()) def _set_current_selection(self, ctxt, begin, end): try: self.perform_set_current_selection(begin, end) except: - binaryninja.log.log_error(traceback.format_exc()) + log.log_error(traceback.format_exc()) @abc.abstractmethod def perform_destroy_instance(self): @@ -324,7 +325,7 @@ class ScriptingProvider(with_metaclass(_ScriptingProviderMetaclass, object)): return None return ctypes.cast(core.BNNewScriptingInstanceReference(result.handle), ctypes.c_void_p).value except: - binaryninja.log.log_error(traceback.format_exc()) + log.log_error(traceback.format_exc()) return None def create_instance(self): @@ -390,7 +391,7 @@ class _PythonScriptingInstanceOutput(object): interpreter = PythonScriptingInstance._interpreter.value if interpreter is None: - if binaryninja.log.is_output_redirected_to_log(): + if log.is_output_redirected_to_log(): self.buffer += data while True: i = self.buffer.find('\n') @@ -400,9 +401,9 @@ class _PythonScriptingInstanceOutput(object): self.buffer = self.buffer[i + 1:] if self.is_error: - binaryninja.log.log_error(line) + log.log_error(line) else: - binaryninja.log.log_info(line) + log.log_info(line) else: self.orig.write(data) else: @@ -490,7 +491,7 @@ class PythonScriptingInstance(ScriptingInstance): self.code = None self.input = "" - self.interpreter.push("from binaryninja import *\n") + self.interpreter.push("from binaryninja import *") def execute(self, code): self.code = code @@ -553,8 +554,8 @@ class PythonScriptingInstance(ScriptingInstance): self.locals["current_llil"] = self.active_func.low_level_il self.locals["current_mlil"] = self.active_func.medium_level_il - for line in code.split("\n"): - self.interpreter.push(line) + for line in code.split(b'\n'): + self.interpreter.push(line.decode('charmap')) if self.locals["here"] != self.active_addr: if not self.active_view.file.navigate(self.active_view.file.view, self.locals["here"]): @@ -660,3 +661,4 @@ def redirect_stdio(): sys.stdin = _PythonScriptingInstanceInput(sys.stdin) sys.stdout = _PythonScriptingInstanceOutput(sys.stdout, False) sys.stderr = _PythonScriptingInstanceOutput(sys.stderr, True) + sys.excepthook = sys.__excepthook__ \ No newline at end of file diff --git a/python/setting.py b/python/setting.py index fea06140..355492aa 100644 --- a/python/setting.py +++ b/python/setting.py @@ -20,11 +20,11 @@ import ctypes -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components from binaryninja import _binaryninjacore as core # 2-3 compatibility -from six.moves import range +from binaryninja import range class Setting(object): @@ -113,7 +113,7 @@ class Setting(object): length.value = len(value) default_list = (ctypes.c_char_p * len(value))() for i in range(len(value)): - default_list[i] = value[i].encode('utf-8') + default_list[i] = value[i].encode('charmap') return core.BNSettingSetStringList(self.plugin_name, name, default_list, length, auto_flush) @@ -141,4 +141,4 @@ class Setting(object): core.BNSettingRemoveSettingGroup(self.plugin_name, auto_flush) def remove_setting(self, setting, auto_flush=True): - core.BNSettingRemoveSetting(self.plugin_name, setting, auto_flush) \ No newline at end of file + core.BNSettingRemoveSetting(self.plugin_name, setting, auto_flush) diff --git a/python/transform.py b/python/transform.py index e16c6bfc..1734d248 100644 --- a/python/transform.py +++ b/python/transform.py @@ -22,20 +22,19 @@ import traceback import ctypes import abc -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components import binaryninja from binaryninja import log from binaryninja import databuffer from binaryninja import _binaryninjacore as core from binaryninja.enums import TransformType -#2-3 compatibility -from six import with_metaclass -from six.moves import range +# 2-3 compatibility +from binaryninja import range +from binaryninja import with_metaclass class _TransformMetaClass(type): - @property def list(self): binaryninja._init_plugins() @@ -96,8 +95,6 @@ class TransformParameter(object): class Transform(with_metaclass(_TransformMetaClass, object)): - - from binaryninja import databuffer transform_type = None name = None long_name = None @@ -224,7 +221,7 @@ class Transform(with_metaclass(_TransformMetaClass, object)): def decode(self, input_buf, params = {}): input_buf = databuffer.DataBuffer(input_buf) output_buf = databuffer.DataBuffer() - keys = params.keys() + keys = list(params.keys()) param_buf = (core.BNTransformParameter * len(keys))() for i in range(0, len(keys)): data = databuffer.DataBuffer(params[keys[i]]) @@ -237,7 +234,7 @@ class Transform(with_metaclass(_TransformMetaClass, object)): def encode(self, input_buf, params = {}): input_buf = databuffer.DataBuffer(input_buf) output_buf = databuffer.DataBuffer() - keys = params.keys() + keys = list(params.keys()) param_buf = (core.BNTransformParameter * len(keys))() for i in range(0, len(keys)): data = databuffer.DataBuffer(params[keys[i]]) diff --git a/python/types.py b/python/types.py index 2ad8d2c2..e8ce7464 100644 --- a/python/types.py +++ b/python/types.py @@ -23,23 +23,26 @@ max_confidence = 255 import ctypes -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components import binaryninja from binaryninja import _binaryninjacore as core from binaryninja.enums import SymbolType, TypeClass, NamedTypeReferenceClass, InstructionTextTokenType, StructureType, ReferenceType, VariableSourceType -#2-3 compatibility -from six.moves import range +# 2-3 compatibility +from binaryninja import range class QualifiedName(object): def __init__(self, name = []): if isinstance(name, str): self.name = [name] + self.byte_name = [name.encode('charmap')] elif isinstance(name, QualifiedName): self.name = name.name + self.byte_name = [n.encode('charmap') for n in name.name] else: - self.name = [i.decode('utf-8') for i in name] + self.name = [i.decode('charmap') for i in name] + self.byte_name = name def __str__(self): return "::".join(self.name) @@ -102,7 +105,7 @@ class QualifiedName(object): result = core.BNQualifiedName() name_list = (ctypes.c_char_p * len(self.name))() for i in range(0, len(self.name)): - name_list[i] = self.name[i] + name_list[i] = self.name[i].encode('charmap') result.name = name_list result.nameCount = len(self.name) return result @@ -1022,7 +1025,7 @@ def preprocess_source(source, filename=None, include_dirs=[]): filename = "input" dir_buf = (ctypes.c_char_p * len(include_dirs))() for i in range(0, len(include_dirs)): - dir_buf[i] = str(include_dirs[i]) + dir_buf[i] = include_dirs[i].encode('charmap') output = ctypes.c_char_p() errors = ctypes.c_char_p() result = core.BNPreprocessSource(source, filename, output, errors, dir_buf, len(include_dirs)) diff --git a/python/undoaction.py b/python/undoaction.py index 732d559a..7891bf54 100644 --- a/python/undoaction.py +++ b/python/undoaction.py @@ -22,14 +22,12 @@ import traceback import json import ctypes -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components from binaryninja import _binaryninjacore as core from binaryninja.enums import ActionType class UndoAction(object): - - name = None action_type = None _registered = False diff --git a/python/update.py b/python/update.py index 4c77b202..7f5d6f9c 100644 --- a/python/update.py +++ b/python/update.py @@ -21,18 +21,16 @@ import traceback import ctypes -# Binary Ninja components -- additional imports belong in the appropriate class +# Binary Ninja components from binaryninja import _binaryninjacore as core from binaryninja.enums import UpdateResult - -#2-3 compatibility -from six import with_metaclass -from six.moves import range +# 2-3 compatibility +from binaryninja import range +from binaryninja import with_metaclass class _UpdateChannelMetaClass(type): - @property def list(self): binaryninja._init_plugins() @@ -99,7 +97,6 @@ class _UpdateChannelMetaClass(type): class UpdateProgressCallback(object): - def __init__(self, func): self.cb = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_ulonglong, ctypes.c_ulonglong)(self.callback) self.func = func @@ -114,7 +111,6 @@ class UpdateProgressCallback(object): class UpdateChannel(with_metaclass(_UpdateChannelMetaClass, object)): - def __init__(self, name, desc, ver): self.name = name self.description = desc diff --git a/scripts/install_api.py b/scripts/install_api.py index 53679ba5..3cc0ed0d 100755 --- a/scripts/install_api.py +++ b/scripts/install_api.py @@ -80,6 +80,6 @@ else: sys.exit(1) binaryninja_pth_path = os.path.join(install_path, 'binaryninja.pth') -open(binaryninja_pth_path, 'wb').write(api_path) +open(binaryninja_pth_path, 'wb').write(api_path.encode('charmap')) print("Binary Ninja API installed using {}".format(binaryninja_pth_path)) diff --git a/suite/binaries b/suite/binaries index 7d5d207d..ab6b1816 160000 --- a/suite/binaries +++ b/suite/binaries @@ -1 +1 @@ -Subproject commit 7d5d207de812ccd7c4648331b00fa936ef0a82c3 +Subproject commit ab6b18161a5b9ccd3588c88233f150fb6c7e2ce9 diff --git a/suite/generator.py b/suite/generator.py index b812b1b5..3302d118 100755 --- a/suite/generator.py +++ b/suite/generator.py @@ -10,24 +10,77 @@ import time unit_test_template = """#!/usr/bin/env python # This is an auto generated unit test file do not edit directly import os +import sys import unittest import pickle import zipfile import testcommon -import binaryninja import api_test import difflib +from collections import Counter +global verbose +verbose = False class TestBinaryNinjaAPI(unittest.TestCase): + # Returns a tuple of: + # bool : Two lists are equal + # string : The string diff + # Args: + # list + # list : (compare list one vs list two) + # string : anything additional wanted to be printed before the string diff + # bool : the ordering of the items in the two lists must be the same + def report(self, oracle, test, firstText='', strictOrdering = False): + stringDiff = "" + + equality = False + if not strictOrdering: + equality = (Counter(oracle) == Counter(test)) + else: + equality = (oracle == test) + + if equality: + return (True, '') + elif not strictOrdering: + try: + for elem in oracle: + test.remove(elem) + oracle.remove(elem) # If it's not in the test, it won't get here! + except ValueError: + pass + + differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) + skipped_lines = 0 + for delta in differ.compare(oracle, test): + if delta[0] == ' ': + skipped_lines += 1 + continue + if skipped_lines > 0: + stringDiff += "<---" + str(skipped_lines) + ' same lines--->\\n' + skipped_lines = 0 + delta = delta.replace(\'\\n\', '') + stringDiff += delta + \'\\n\' + + stringDiffList = stringDiff.split(\'\\n\') + + if len(stringDiffList) > 10: + if not verbose: + stringDiff = \'\\n\'.join(line if len(line) <= 100 else line[:100] + "...and " + str(len(line) - 100) + " more characters" for line in stringDiffList[:10]) + stringDiff += \'\\n\\n### And ' + str(len(stringDiffList)) + " more lines, use '-v' to show ###" + elif not verbose: + stringDiff = \'\\n\'.join(line if len(line) <= 100 else line[:100] + "...and " + str(len(line) - 100) + " more characters" for line in stringDiffList) + stringDiff = \'\\n\\n\' + firstText + stringDiff + return (equality, stringDiff) + @classmethod def setUpClass(self): self.builder = testcommon.TestBuilder("{3}") try: - #Python 2 does not have the encodings option - self.oracle_test_data = pickle.load(open(os.path.join("{0}", "oracle.pkl"), "rUb"), errors="ignore") + # Python 2 does not have the encodings option + self.oracle_test_data = pickle.load(open(os.path.join("{0}", "oracle.pkl"), "rb"), encoding='charmap') except TypeError: - self.oracle_test_data = pickle.load(open(os.path.join("{0}", "oracle.pkl"), "rU")) + self.oracle_test_data = pickle.load(open(os.path.join("{0}", "oracle.pkl"), "r")) self.verifybuilder = testcommon.VerifyBuilder("{3}") def run_binary_test(self, testfile): @@ -38,10 +91,10 @@ class TestBinaryNinjaAPI(unittest.TestCase): self.assertTrue(os.path.exists(testname + ".pkl"), "Test pickle doesn't exist") try: - #Python 2 does not have the encodings option - binary_oracle = pickle.load(open(testname + ".pkl", "rUb"), errors="ignore") + # Python 2 does not have the encodings option + binary_oracle = pickle.load(open(testname + ".pkl", "rb"), encoding='charmap') except TypeError: - binary_oracle = pickle.load(open(testname + ".pkl", "rU")) + binary_oracle = pickle.load(open(testname + ".pkl", "r")) test_builder = testcommon.BinaryViewTestBuilder(testname, "{3}") for method in test_builder.methods(): @@ -52,22 +105,16 @@ class TestBinaryNinjaAPI(unittest.TestCase): result = getattr(test_builder, method).__doc__ result += ":\\n" - d = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in d.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\\n' - skipped_lines = 0 - delta = delta.replace('\\n', '') - result += delta + '\\n' - self.assertTrue(False, result) + report = self.report(oracle, test, result) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle os.unlink(testname) {1}{2} if __name__ == "__main__": + if len(sys.argv) > 1: + if sys.argv[1] == '-v' or sys.argv[1] == '-V' or sys.argv[1] == '--verbose': + verbose = True + test_suite = unittest.defaultTestLoader.loadTestsFromModule(api_test) test_suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestBinaryNinjaAPI)) runner = unittest.TextTestRunner(verbosity=2) @@ -83,20 +130,8 @@ test_string = """ def {0}(self): oracle = self.oracle_test_data['{0}'] test = self.builder.{0}() - result = "" - differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in differ.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\\n' - skipped_lines = 0 - delta = delta.replace('\\n', '') - result += delta + '\\n' - - self.assertTrue(oracle == test, result) + report = self.report(oracle, test) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle """ verify_string = """ @@ -108,7 +143,7 @@ verify_string = """ class OracleTestFile: def __init__(self, filename): self.f = open(filename + ".pkl", "wb") - self.pkl = pickle.Pickler(self.f) + self.pkl = pickle.Pickler(self.f, protocol=2) self.filename = filename self.oracle_test_data = {} @@ -131,7 +166,7 @@ class UnitTestFile: self.binary_tests = "" def close(self): - self.f.write(self.template.format(self.outdir, self.tests, self.binary_tests, self.test_store).encode('utf-8')) + self.f.write(self.template.format(self.outdir, self.tests, self.binary_tests, self.test_store).encode('charmap')) self.f.close() def add_verify(self, test_name): @@ -146,8 +181,6 @@ class UnitTestFile: quiet = False - - def myprint(stuff): if not quiet: print(stuff) diff --git a/suite/testcommon.py b/suite/testcommon.py index 1bbc0d47..ef85c053 100644 --- a/suite/testcommon.py +++ b/suite/testcommon.py @@ -1,14 +1,54 @@ import tempfile import pickle import os +import sys import zipfile import inspect import binaryninja as binja from binaryninja.binaryview import BinaryViewType, BinaryView from binaryninja.filemetadata import FileMetadata import subprocess -import traceback -import types +import re + + +# Dear people from the future: If you're adding tests or debuging an +# issue where python2 and python3 are producing different output +# for the same function and it's a issue of `longs`, run the output +# through this function. If it's a unicode/bytes issue, fix it in +# api/python/ +def fixOutput(outputList): + # Apply regular expression to detect python2 longs + splitList = [] + for elem in outputList: + if isinstance(elem, str): + splitList.append(re.split(r"((?<=[\[ ])0x[\da-f]+L|[\d]+L)", elem)) + else: + splitList.append(elem) + + # Resolve application of regular expression + result = [] + for elem in splitList: + if isinstance(elem, list): + newElem = [] + for item in elem: + if len(item) > 1 and item[-1] == 'L': + newElem.append(item[:-1]) + else: + newElem.append(item) + result.append(''.join(newElem)) + else: + result.append(elem) + return result + + +# Alright so this one is here for Binja functions that output +def fixSet(string): + # Apply regular expression + splitList = (re.split(r"((?<=))", string)) + if len(splitList) > 1: + return splitList[0] + ', '.join(sorted(splitList[1].split(', '))) + splitList[2] + else: + return string def get_file_list(test_store): @@ -16,13 +56,12 @@ def get_file_list(test_store): for root, dir, files in os.walk(test_store): for file in files: all_files.append(os.path.join(root, file)) - return all_files def remove_low_confidence(type_string): low_confidence_types = ["int32_t", "void"] for lct in low_confidence_types: - type_string = type_string.replace(lct + " ", '') # done to resolve confidence ties + type_string = type_string.replace(lct + " ", '') # done to resolve confidence ties return type_string class Builder(object): @@ -66,15 +105,24 @@ class BinaryViewTestBuilder(Builder): def test_function_starts(self): """Function starts list doesnt match""" - return ["Function start: " + hex(x.start) for x in self.bv.functions] + result = [] + for x in self.bv.functions: + result.append("Function start: " + hex(x.start)) + return fixOutput(result) def test_function_symbol_names(self): """Function.symbol.name list doesnt match""" - return ["Symbol: " + x.symbol.name + ' ' + str(x.symbol.type) + ' ' + hex(x.symbol.address) for x in self.bv.functions] + result = [] + for x in self.bv.functions: + result.append("Symbol: " + x.symbol.name + ' ' + str(x.symbol.type) + ' ' + hex(x.symbol.address)) + return fixOutput(result) def test_function_can_return(self): """Function.can_return list doesnt match""" - return ["function name: " + x.symbol.name + ' type: ' + str(x.symbol.type) + ' address: ' + hex(x.symbol.address) + ' can_return: ' + str(bool(x.can_return)) for x in self.bv.functions] + result = [] + for x in self.bv.functions: + result.append("function name: " + x.symbol.name + ' type: ' + str(x.symbol.type) + ' address: ' + hex(x.symbol.address) + ' can_return: ' + str(bool(x.can_return))) + return fixOutput(result) def test_function_basic_blocks(self): """Function basic_block list doesnt match (start, end, has_undetermined_outgoing_edges)""" @@ -85,32 +133,31 @@ class BinaryViewTestBuilder(Builder): for anno in func.get_block_annotations(bb.start): bblist.append("basic block {} function annotation: ".format(str(bb)) + str(anno)) bblist.append("basic block {} test get self: ".format(str(bb)) + str(func.get_basic_block_at(bb.start))) - return bblist + return fixOutput(bblist) def test_function_low_il_basic_blocks(self): - """"Function low_il_basic_block list doesnt match""" + """Function low_il_basic_block list doesnt match""" ilbblist = [] for func in self.bv.functions: for bb in func.low_level_il.basic_blocks: ilbblist.append("LLIL basic block {} start: ".format(str(bb)) + hex(bb.start) + ' end: ' + hex(bb.end) + ' outgoing edges: ' + str(len(bb.outgoing_edges))) - return ilbblist + return fixOutput(ilbblist) def test_function_med_il_basic_blocks(self): - """"Function med_il_basic_block list doesn't match""" + """Function med_il_basic_block list doesn't match""" ilbblist = [] for func in self.bv.functions: for bb in func.medium_level_il.basic_blocks: ilbblist.append("MLIL basic block {} start: ".format(str(bb)) + hex(bb.start) + ' end: ' + hex(bb.end) + ' outgoing_edges: ' + str(len(bb.outgoing_edges))) - return ilbblist + return fixOutput(ilbblist) def test_symbols(self): - """"Symbols list doesn't match""" + """Symbols list doesn't match""" return ["Symbol: " + str(i) for i in sorted(self.bv.symbols)] def test_strings(self): """Strings list doesn't match""" - return ["String: " + x.value + ' type: ' + str(x.type) + ' at: ' + hex(x.start) for x in self.bv.strings] - + return fixOutput(["String: " + str(x.value) + ' type: ' + str(x.type) + ' at: ' + hex(x.start) for x in self.bv.strings]) def test_low_il_instructions(self): """LLIL instructions produced different output""" @@ -126,9 +173,7 @@ class BinaryViewTestBuilder(Builder): retinfo.append("Postfix operands: " + str(ins.postfix_operands)) retinfo.append("SSA form: " + str(ins.ssa_form)) retinfo.append("Non-SSA form: " + str(ins.non_ssa_form)) - - return retinfo - + return fixOutput(retinfo) def test_low_il_ssa(self): """LLIL ssa produced different output""" @@ -151,9 +196,7 @@ class BinaryViewTestBuilder(Builder): retinfo.append("SSA instruction index: " + str(func.get_ssa_instruction_index(tempind))) retinfo.append("MLIL instruction index: " + str(func.get_medium_level_il_instruction_index(ins.instr_index))) retinfo.append("Mapped MLIL instruction index: " + str(func.get_mapped_medium_level_il_instruction_index(ins.instr_index))) - - return retinfo - + return fixOutput(retinfo) def test_med_il_instructions(self): """MLIL instructions produced different output""" @@ -165,18 +208,33 @@ class BinaryViewTestBuilder(Builder): retinfo.append("LLIL: " + str(ins.low_level_il)) retinfo.append("Value: " + str(ins.value)) retinfo.append("Possible values: " + str(ins.possible_values)) - retinfo.append("Branch dependence: " + str(ins.branch_dependence)) - retinfo.append("Prefix operands: " + str(sorted([str(i) for i in ins.prefix_operands]))) - retinfo.append("Postfix operands: " + str(sorted([str(i) for i in ins.postfix_operands]))) + retinfo.append("Branch dependence: " + str(sorted(ins.branch_dependence.items()))) + + prefixList = [] + for i in ins.prefix_operands: + if isinstance(i, float) and 'e' in str(i): + prefixList.append(str(round(i, 21))) + elif isinstance(i, float): + prefixList.append(str(round(i, 11))) + else: + prefixList.append(str(i)) + retinfo.append("Prefix operands: " + str(sorted(prefixList))) + postfixList = [] + for i in ins.prefix_operands: + if isinstance(i, float) and 'e' in str(i): + postfixList.append(str(round(i, 21))) + elif isinstance(i, float): + postfixList.append(str(round(i, 11))) + else: + postfixList.append(str(i)) + + retinfo.append("Postfix operands: " + str(sorted(postfixList))) retinfo.append("SSA form: " + str(ins.ssa_form)) retinfo.append("Non-SSA form" + str(ins.non_ssa_form)) - - return retinfo - - + return fixOutput(retinfo) def test_med_il_vars(self): - """"Function med_il_vars doesn't match""" + """Function med_il_vars doesn't match""" varlist = [] for func in self.bv.functions: func = func.medium_level_il @@ -184,14 +242,13 @@ class BinaryViewTestBuilder(Builder): for instruction in bb: instruction = instruction.ssa_form for var in (instruction.vars_read + instruction.vars_written): - #varlist.append((func.get_var_uses(var), func.get_var_definitions(var))) if hasattr(var, "var"): varlist.append("SSA var definition: " + str(func.get_ssa_var_definition(var))) varlist.append("SSA var uses: " + str(func.get_ssa_var_uses(var))) varlist.append("SSA var value: " + str(func.get_ssa_var_value(var))) - varlist.append("SSA var possible values: " + str(instruction.get_ssa_var_possible_values(var))) + varlist.append("SSA var possible values: " + fixSet(str(instruction.get_ssa_var_possible_values(var)))) varlist.append("SSA var version: " + str(instruction.get_ssa_var_version)) - return varlist + return fixOutput(varlist) def test_function_stack(self): """Function stack produced different output""" @@ -211,32 +268,26 @@ class BinaryViewTestBuilder(Builder): funcinfo.append("Sample stack var: " + str(func.get_stack_var_at_frame_offset(0, 0))) func.delete_user_stack_var(0) func.delete_auto_stack_var(0) - return funcinfo def test_function_llil(self): """Function LLIL produced different output""" retinfo = [] - for func in self.bv.functions: for llilbb in func.llil_basic_blocks: retinfo.append("LLIL basic block: " + str(llilbb)) for llilins in func.llil_instructions: retinfo.append("LLIL instruction: " + str(llilins)) - for mlilbb in func.mlil_basic_blocks: retinfo.append("MLIL basic block: " + str(mlilbb)) for mlilins in func.mlil_instructions: retinfo.append("MLIL instruction: " + str(mlilins)) - for ins in func.instructions: - retinfo.append("Instructiin: {}: ".format(hex(ins[1])) + ''.join([str(i) for i in ins[0]])) - - return retinfo - + retinfo.append("Instruction: {}: ".format(hex(ins[1])) + ''.join([str(i) for i in ins[0]])) + return fixOutput(retinfo) def test_functions_attributes(self): - """"Function attributes don't match""" + """Function attributes don't match""" funcinfo = [] for func in self.bv.functions: func.comment = "testcomment " + func.name @@ -284,9 +335,7 @@ class BinaryViewTestBuilder(Builder): token = str(token) token = remove_low_confidence(token) funcinfo.append("Function {} type token: ".format(func.name) + str(token)) - - - return funcinfo + return fixOutput(funcinfo) def test_BinaryView(self): """BinaryView produced different results""" @@ -309,11 +358,12 @@ class BinaryViewTestBuilder(Builder): retinfo.append("BV entry point: " + hex(self.bv.entry_point)) retinfo.append("BV start: " + hex(self.bv.start)) retinfo.append("BV length: " + hex(len(self.bv))) - return retinfo + + return fixOutput(retinfo) class TestBuilder(Builder): - """ The TestBuilder is for tests that need to be checked against + """ The TestBuilder is for tests that need to be checked againsttest_BinaryView stored oracle data that isn't from a binary. These test are generated on your local machine then run again on the build machine to verify correctness. @@ -335,23 +385,81 @@ class TestBuilder(Builder): """unexpected assemble result""" result = [] # success cases - result.append("x86 assembly: " + str(binja.Architecture["x86"].assemble("xor eax, eax"))) - result.append("x86_64 assembly: " + str(binja.Architecture["x86_64"].assemble("xor rax, rax"))) - result.append("mips32 assembly: " + str(binja.Architecture["mips32"].assemble("move $ra, $zero"))) - result.append("mipsel32 assembly: " + str(binja.Architecture["mipsel32"].assemble("move $ra, $zero"))) - result.append("armv7 assembly: " + str(binja.Architecture["armv7"].assemble("str r2, [sp, #-0x4]!"))) - result.append("aarch64 assembly: " + str(binja.Architecture["aarch64"].assemble("mov x0, x0"))) - result.append("thumb2 assembly: " + str(binja.Architecture["thumb2"].assemble("ldr r4, [r4]"))) - result.append("thumb2eb assembly: " + str(binja.Architecture["thumb2eb"].assemble("ldr r4, [r4]"))) + + strResult = binja.Architecture["x86"].assemble("xor eax, eax") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("x86 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("x86 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["x86_64"].assemble("xor rax, rax") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("x86_64 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("x86_64 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["mips32"].assemble("move $ra, $zero") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("mips32 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("mips32 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["mipsel32"].assemble("move $ra, $zero") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("mipsel32 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("mipsel32 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["armv7"].assemble("str r2, [sp, #-0x4]!") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("armv7 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("armv7 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["aarch64"].assemble("mov x0, x0") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("aarch64 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("aarch64 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["thumb2"].assemble("ldr r4, [r4]") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("thumb2 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("thumb2 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["thumb2eb"].assemble("ldr r4, [r4]") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("thumb2eb assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("thumb2eb assembly: " + repr(str(strResult))) + # fail cases - result.append("x86 assembly: " + str(binja.Architecture["x86"].assemble("thisisnotaninstruction"))) - result.append("x86_64 assembly: " + str(binja.Architecture["x86_64"].assemble("thisisnotaninstruction"))) - result.append("mips32 assembly: " + str(binja.Architecture["mips32"].assemble("thisisnotaninstruction"))) - result.append("mipsel32 assembly: " + str(binja.Architecture["mipsel32"].assemble("thisisnotaninstruction"))) - result.append("armv7 assembly: " + str(binja.Architecture["armv7"].assemble("thisisnotaninstruction"))) - result.append("aarch64 assembly: " + str(binja.Architecture["aarch64"].assemble("thisisnotaninstruction"))) - result.append("thumb2 assembly: " + str(binja.Architecture["thumb2"].assemble("thisisnotaninstruction"))) - result.append("thumb2eb assembly: " + str(binja.Architecture["thumb2eb"].assemble("thisisnotaninstruction"))) + try: + strResult = binja.Architecture["x86"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'x86'") + try: + strResult = binja.Architecture["x86_64"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'x86_64'") + try: + strResult = binja.Architecture["mips32"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'mips32'") + try: + strResult = binja.Architecture["mipsel32"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'mipsel32'") + try: + strResult = binja.Architecture["armv7"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'armv7'") + try: + strResult = binja.Architecture["aarch64"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'aarch64'") + try: + strResult = binja.Architecture["thumb2"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'thumb2'") + try: + strResult = binja.Architecture["thumb2eb"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'thumb2eb'") return result def test_Architecture(self): @@ -385,8 +493,6 @@ class TestBuilder(Builder): retinfo.append("Instruction: " + str(ins)) return retinfo - - def test_Function(self): """Function produced different result""" inttype = binja.Type.int(4) @@ -433,7 +539,6 @@ class TestBuilder(Builder): def test_Types(self): """Types produced different result""" - file_name = os.path.join(self.test_store, "helloworld") bv = binja.BinaryViewType.get_view_of_file(file_name) preprocessed = binja.preprocess_source(""" @@ -445,7 +550,7 @@ class TestBuilder(Builder): long long bar1 = 2; #endif """) - source = '\n'.join([i.decode("utf-8") for i in preprocessed[0].split('\n') if not b"#line" in i and len(i) > 0]) + source = '\n'.join([i.decode('utf-8') for i in preprocessed[0].split(b'\n') if not b'#line' in i and len(i) > 0]) typelist = bv.platform.parse_types_from_source(source) inttype = binja.Type.int(4) @@ -464,12 +569,11 @@ class TestBuilder(Builder): def test_Plugin_bin_info(self): """print_syscalls plugin produced different result""" - file_name = os.path.join(self.test_store, "helloworld") self.unpackage_file(file_name) result = subprocess.Popen(["python", os.path.join(self.examples_dir, "bin_info.py"), file_name], stdout=subprocess.PIPE).communicate()[0] # normalize line endings and path sep - return [result.replace(b"\\", b"/").replace(b"\r\n", b"\n")] + return [result.replace(b"\\", b"/").replace(b"\r\n", b"\n").decode("charmap")] def test_linear_disassembly(self): """linear_disassembly produced different result""" @@ -486,7 +590,6 @@ class TestBuilder(Builder): def test_partial_register_dataflow(self): """partial_register_dataflow produced different results""" - file_name = os.path.join(self.test_store, "partial_register_dataflow") self.unpackage_file(file_name) result = [] @@ -505,7 +608,6 @@ class TestBuilder(Builder): del bv finally: os.unlink(file_name) - return result @@ -524,20 +626,13 @@ class TestBuilder(Builder): retinfo.append("LLIL second stack element: " + str(ins.get_stack_contents_after(0,1))) retinfo.append("LLIL possible first stack element: " + str(ins.get_possible_stack_contents(0,1))) retinfo.append("LLIL possible second stack element: " + str(ins.get_possible_stack_contents_after(0,1))) - - for flag in flag_list: retinfo.append("LLIL flag {} value at: ".format(flag, hex(ins.address)) + str(ins.get_flag_value(flag))) retinfo.append("LLIL flag {} value after {}: ".format(flag, hex(ins.address)) + str(ins.get_flag_value_after(flag))) - retinfo.append("LLIL flag {} possible value at {}: ".format(flag, hex(ins.address)) + str(ins.get_possible_flag_values(flag))) retinfo.append("LLIL flag {} possible value after {}: ".format(flag, hex(ins.address)) + str(ins.get_possible_flag_values_after(flag))) - - os.unlink(file_name) - os.unlink(file_name) - - return retinfo + return fixOutput(retinfo) def test_med_il_stack(self): """MLIL stack produced different output""" @@ -555,25 +650,21 @@ class TestBuilder(Builder): retinfo.append("MLIL second stack element: " + str(ins.get_stack_contents_after(0, 1))) retinfo.append("MLIL possible first stack element: " + str(ins.get_possible_stack_contents(0, 1))) retinfo.append("MLIL possible second stack element: " + str(ins.get_possible_stack_contents_after(0, 1))) - + for reg in reg_list: retinfo.append("MLIL reg {} var at {}: ".format(reg, hex(ins.address)) + str(ins.get_var_for_reg(reg))) retinfo.append("MLIL reg {} value at {}: ".format(reg, hex(ins.address)) + str(ins.get_reg_value(reg))) retinfo.append("MLIL reg {} value after {}: ".format(reg, hex(ins.address)) + str(ins.get_reg_value_after(reg))) - retinfo.append("MLIL reg {} possible value at {}: ".format(reg, hex(ins.address)) + str(ins.get_possible_reg_values(reg))) - retinfo.append("MLIL reg {} possible value after {}: ".format(reg, hex(ins.address)) + str(ins.get_possible_reg_values_after(reg))) - + retinfo.append("MLIL reg {} possible value at {}: ".format(reg, hex(ins.address)) + fixSet(str(ins.get_possible_reg_values(reg)))) + retinfo.append("MLIL reg {} possible value after {}: ".format(reg, hex(ins.address)) + fixSet(str(ins.get_possible_reg_values_after(reg)))) for flag in flag_list: retinfo.append("MLIL flag {} value at: ".format(flag, hex(ins.address)) + str(ins.get_flag_value(flag))) retinfo.append("MLIL flag {} value after {}: ".format(flag, hex(ins.address)) + str(ins.get_flag_value_after(flag))) - retinfo.append("MLIL flag {} possible value at {}: ".format(flag, hex(ins.address)) + str(ins.get_possible_flag_values(flag))) - retinfo.append("MLIL flag {} possible value after {}: ".format(flag, hex(ins.address)) + str(ins.get_possible_flag_values(flag))) - + retinfo.append("MLIL flag {} possible value at {}: ".format(flag, hex(ins.address)) + fixSet(str(ins.get_possible_flag_values(flag)))) + retinfo.append("MLIL flag {} possible value after {}: ".format(flag, hex(ins.address)) + fixSet(str(ins.get_possible_flag_values(flag)))) os.unlink(file_name) - - return retinfo - + return fixOutput(retinfo) def test_events(self): """Event failure""" @@ -582,11 +673,9 @@ class TestBuilder(Builder): bv = binja.BinaryViewType['ELF'].open(file_name) results = [] - def simple_complete(self): results.append("analysis complete") - evt = binja.AnalysisCompletionEvent(bv, simple_complete) class NotifyTest(binja.BinaryDataNotification): @@ -643,7 +732,13 @@ class TestBuilder(Builder): def string_found(self, view, string_type, offset, length): def string_found_complete(self): - results.append("string found: offset {0} length {1}".format(hex(offset), hex(length))) + offset = hex(offset) + length = hex(length) + if offset[-1] == 'L': + offset = offset[:-1] + if length[-1] == 'L': + length = length[:-1] + results.append("string found: offset {0} length {1}".format(offset, length)) evt = binja.AnalysisCompletionEvent(bv, string_found_complete) def string_removed(self, view, string_type, offset, length): @@ -666,7 +761,6 @@ class TestBuilder(Builder): bv.register_notification(test) sacrificial_addr = 0x84fc - type, name = bv.parse_type_string("int foo") type_id = type.generate_auto_type_id("source", name) bv.define_type(type_id, name, type) @@ -684,10 +778,10 @@ class TestBuilder(Builder): bv.remove(sacrificial_addr, 4) bv.update_analysis_and_wait() - + bv.unregister_notification(test) - return sorted(results) + return fixOutput(sorted(results)) def unpackage(self, fileName): testname = None diff --git a/suite/unit.py b/suite/unit.py index 9a59b13f..fe2c4b8c 100644 --- a/suite/unit.py +++ b/suite/unit.py @@ -1,24 +1,77 @@ #!/usr/bin/env python # This is an auto generated unit test file do not edit directly import os +import sys import unittest import pickle import zipfile import testcommon -import binaryninja import api_test import difflib +from collections import Counter +global verbose +verbose = False class TestBinaryNinjaAPI(unittest.TestCase): + # Returns a tuple of: + # bool : Two lists are equal + # string : The string diff + # Args: + # list + # list : (compare list one vs list two) + # string : anything additional wanted to be printed before the string diff + # bool : the ordering of the items in the two lists must be the same + def report(self, oracle, test, firstText='', strictOrdering = False): + stringDiff = "" + + equality = False + if not strictOrdering: + equality = (Counter(oracle) == Counter(test)) + else: + equality = (oracle == test) + + if equality: + return (True, '') + elif not strictOrdering: + try: + for elem in oracle: + test.remove(elem) + oracle.remove(elem) # If it's not in the test, it won't get here! + except ValueError: + pass + + differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) + skipped_lines = 0 + for delta in differ.compare(oracle, test): + if delta[0] == ' ': + skipped_lines += 1 + continue + if skipped_lines > 0: + stringDiff += "<---" + str(skipped_lines) + ' same lines--->\n' + skipped_lines = 0 + delta = delta.replace('\n', '') + stringDiff += delta + '\n' + + stringDiffList = stringDiff.split('\n') + + if len(stringDiffList) > 10: + if not verbose: + stringDiff = '\n'.join(line if len(line) <= 100 else line[:100] + "...and " + str(len(line) - 100) + " more characters" for line in stringDiffList[:10]) + stringDiff += '\n\n### And ' + str(len(stringDiffList)) + " more lines, use '-v' to show ###" + elif not verbose: + stringDiff = '\n'.join(line if len(line) <= 100 else line[:100] + "...and " + str(len(line) - 100) + " more characters" for line in stringDiffList) + stringDiff = '\n\n' + firstText + stringDiff + return (equality, stringDiff) + @classmethod def setUpClass(self): self.builder = testcommon.TestBuilder("suite/binaries/test_corpus") try: - #Python 2 does not have the encodings option - self.oracle_test_data = pickle.load(open(os.path.join("suite", "oracle.pkl"), "rUb"), errors="ignore") + # Python 2 does not have the encodings option + self.oracle_test_data = pickle.load(open(os.path.join("suite", "oracle.pkl"), "rb"), encoding='charmap') except TypeError: - self.oracle_test_data = pickle.load(open(os.path.join("suite", "oracle.pkl"), "rU")) + self.oracle_test_data = pickle.load(open(os.path.join("suite", "oracle.pkl"), "r")) self.verifybuilder = testcommon.VerifyBuilder("suite/binaries/test_corpus") def run_binary_test(self, testfile): @@ -29,10 +82,10 @@ class TestBinaryNinjaAPI(unittest.TestCase): self.assertTrue(os.path.exists(testname + ".pkl"), "Test pickle doesn't exist") try: - #Python 2 does not have the encodings option - binary_oracle = pickle.load(open(testname + ".pkl", "rUb"), errors="ignore") + # Python 2 does not have the encodings option + binary_oracle = pickle.load(open(testname + ".pkl", "rb"), encoding='charmap') except TypeError: - binary_oracle = pickle.load(open(testname + ".pkl", "rU")) + binary_oracle = pickle.load(open(testname + ".pkl", "r")) test_builder = testcommon.BinaryViewTestBuilder(testname, "suite/binaries/test_corpus") for method in test_builder.methods(): @@ -43,271 +96,93 @@ class TestBinaryNinjaAPI(unittest.TestCase): result = getattr(test_builder, method).__doc__ result += ":\n" - d = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in d.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\n' - skipped_lines = 0 - delta = delta.replace('\n', '') - result += delta + '\n' - self.assertTrue(False, result) + report = self.report(oracle, test, result) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle os.unlink(testname) def test_Architecture(self): oracle = self.oracle_test_data['test_Architecture'] test = self.builder.test_Architecture() - result = "" - differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in differ.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\n' - skipped_lines = 0 - delta = delta.replace('\n', '') - result += delta + '\n' - - self.assertTrue(oracle == test, result) + report = self.report(oracle, test) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle def test_Architecture_list(self): oracle = self.oracle_test_data['test_Architecture_list'] test = self.builder.test_Architecture_list() - result = "" - differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in differ.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\n' - skipped_lines = 0 - delta = delta.replace('\n', '') - result += delta + '\n' - - self.assertTrue(oracle == test, result) + report = self.report(oracle, test) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle def test_Assemble(self): oracle = self.oracle_test_data['test_Assemble'] test = self.builder.test_Assemble() - result = "" - differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in differ.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\n' - skipped_lines = 0 - delta = delta.replace('\n', '') - result += delta + '\n' - - self.assertTrue(oracle == test, result) + report = self.report(oracle, test) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle def test_BinaryViewType_list(self): oracle = self.oracle_test_data['test_BinaryViewType_list'] test = self.builder.test_BinaryViewType_list() - result = "" - differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in differ.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\n' - skipped_lines = 0 - delta = delta.replace('\n', '') - result += delta + '\n' - - self.assertTrue(oracle == test, result) + report = self.report(oracle, test) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle def test_Enumeration(self): oracle = self.oracle_test_data['test_Enumeration'] test = self.builder.test_Enumeration() - result = "" - differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in differ.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\n' - skipped_lines = 0 - delta = delta.replace('\n', '') - result += delta + '\n' - - self.assertTrue(oracle == test, result) + report = self.report(oracle, test) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle def test_Function(self): oracle = self.oracle_test_data['test_Function'] test = self.builder.test_Function() - result = "" - differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in differ.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\n' - skipped_lines = 0 - delta = delta.replace('\n', '') - result += delta + '\n' - - self.assertTrue(oracle == test, result) + report = self.report(oracle, test) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle def test_Plugin_bin_info(self): oracle = self.oracle_test_data['test_Plugin_bin_info'] test = self.builder.test_Plugin_bin_info() - result = "" - differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in differ.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\n' - skipped_lines = 0 - delta = delta.replace('\n', '') - result += delta + '\n' - - self.assertTrue(oracle == test, result) + report = self.report(oracle, test) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle def test_Struct(self): oracle = self.oracle_test_data['test_Struct'] test = self.builder.test_Struct() - result = "" - differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in differ.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\n' - skipped_lines = 0 - delta = delta.replace('\n', '') - result += delta + '\n' - - self.assertTrue(oracle == test, result) + report = self.report(oracle, test) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle def test_Types(self): oracle = self.oracle_test_data['test_Types'] test = self.builder.test_Types() - result = "" - differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in differ.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\n' - skipped_lines = 0 - delta = delta.replace('\n', '') - result += delta + '\n' - - self.assertTrue(oracle == test, result) + report = self.report(oracle, test) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle def test_events(self): oracle = self.oracle_test_data['test_events'] test = self.builder.test_events() - result = "" - differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in differ.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\n' - skipped_lines = 0 - delta = delta.replace('\n', '') - result += delta + '\n' - - self.assertTrue(oracle == test, result) + report = self.report(oracle, test) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle def test_linear_disassembly(self): oracle = self.oracle_test_data['test_linear_disassembly'] test = self.builder.test_linear_disassembly() - result = "" - differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in differ.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\n' - skipped_lines = 0 - delta = delta.replace('\n', '') - result += delta + '\n' - - self.assertTrue(oracle == test, result) + report = self.report(oracle, test) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle def test_low_il_stack(self): oracle = self.oracle_test_data['test_low_il_stack'] test = self.builder.test_low_il_stack() - result = "" - differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in differ.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\n' - skipped_lines = 0 - delta = delta.replace('\n', '') - result += delta + '\n' - - self.assertTrue(oracle == test, result) + report = self.report(oracle, test) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle def test_med_il_stack(self): oracle = self.oracle_test_data['test_med_il_stack'] test = self.builder.test_med_il_stack() - result = "" - differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in differ.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\n' - skipped_lines = 0 - delta = delta.replace('\n', '') - result += delta + '\n' - - self.assertTrue(oracle == test, result) + report = self.report(oracle, test) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle def test_partial_register_dataflow(self): oracle = self.oracle_test_data['test_partial_register_dataflow'] test = self.builder.test_partial_register_dataflow() - result = "" - differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in differ.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\n' - skipped_lines = 0 - delta = delta.replace('\n', '') - result += delta + '\n' - - self.assertTrue(oracle == test, result) + report = self.report(oracle, test) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle def test_verify_BNDB_round_trip(self): self.assertTrue(self.verifybuilder.test_verify_BNDB_round_trip(), self.test_verify_BNDB_round_trip.__doc__) @@ -386,6 +261,10 @@ class TestBinaryNinjaAPI(unittest.TestCase): if __name__ == "__main__": + if len(sys.argv) > 1: + if sys.argv[1] == '-v' or sys.argv[1] == '-V' or sys.argv[1] == '--verbose': + verbose = True + test_suite = unittest.defaultTestLoader.loadTestsFromModule(api_test) test_suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestBinaryNinjaAPI)) runner = unittest.TextTestRunner(verbosity=2) -- cgit v1.3.1 From 975249d22e10360ed2c4c0bcea151c39d9446b0a Mon Sep 17 00:00:00 2001 From: KyleMiles Date: Thu, 5 Jul 2018 18:28:59 -0400 Subject: Rebased so commit history is correct now --- .gitignore | 6 - python/__init__.py | 1 + python/downloadprovider.py | 1 - python/examples/README.md | 56 +++ python/examples/angr_plugin.py | 153 +++++++ python/examples/arch_hook.py | 16 + python/examples/bin_info.py | 76 ++++ python/examples/breakpoint.py | 50 +++ python/examples/export_svg.py | 224 ++++++++++ python/examples/instruction_iterator.py | 53 +++ python/examples/jump_table.py | 86 ++++ python/examples/nds.py | 120 ++++++ python/examples/nes.py | 650 ++++++++++++++++++++++++++++++ python/examples/notification_callbacks.py | 74 ++++ python/examples/nsf.py | 145 +++++++ python/examples/print_syscalls.py | 55 +++ python/examples/version_switcher.py | 150 +++++++ python/function.py | 14 +- python/plugin.py | 32 +- suite/generator.py | 1 + suite/oracle.pkl | Bin 9098577 -> 440689 bytes suite/testcommon.py | 6 +- suite/unit.py | 7 +- 23 files changed, 1937 insertions(+), 39 deletions(-) create mode 100644 python/examples/README.md create mode 100644 python/examples/angr_plugin.py create mode 100644 python/examples/arch_hook.py create mode 100644 python/examples/bin_info.py create mode 100644 python/examples/breakpoint.py create mode 100755 python/examples/export_svg.py create mode 100644 python/examples/instruction_iterator.py create mode 100644 python/examples/jump_table.py create mode 100644 python/examples/nds.py create mode 100644 python/examples/nes.py create mode 100644 python/examples/notification_callbacks.py create mode 100644 python/examples/nsf.py create mode 100644 python/examples/print_syscalls.py create mode 100644 python/examples/version_switcher.py (limited to 'python/downloadprovider.py') diff --git a/.gitignore b/.gitignore index eff64eb4..302ad960 100644 --- a/.gitignore +++ b/.gitignore @@ -33,14 +33,8 @@ api-docs/source/index.rst .coverage # Make generator -libcrypto.so.1.0.2 -libcurl.so.4 -libssl.so.1.0.2 plugins/ python/examples/binaryninja/ -python/libcrypto.so.1.0.2 -python/libcurl.so.4 -python/libssl.so.1.0.2 python/plugins/ python/types/ suite/binaryninja/ diff --git a/python/__init__.py b/python/__init__.py index e1288b0a..36ed4f0d 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -108,6 +108,7 @@ from binaryninja.lineardisassembly import * from binaryninja.undoaction import * from binaryninja.highlight import * from binaryninja.scriptingprovider import * +from binaryninja.downloadprovider import * from binaryninja.pluginmanager import * from binaryninja.setting import * from binaryninja.metadata import * diff --git a/python/downloadprovider.py b/python/downloadprovider.py index c714cb98..09abd516 100644 --- a/python/downloadprovider.py +++ b/python/downloadprovider.py @@ -20,7 +20,6 @@ import abc -import code import ctypes import sys import traceback diff --git a/python/examples/README.md b/python/examples/README.md new file mode 100644 index 00000000..43af34be --- /dev/null +++ b/python/examples/README.md @@ -0,0 +1,56 @@ +# Binary Ninja Python API Examples + +The following examples demonstrate some of the Binary Ninja API. They include both stand-alone examples that directly call into the core without a GUI, as well as examples meant to be loaded as plugins available from the UI. + +## Stand-alone + +These plugins only operate when run directly outside of the UI + +* bin_info.py - general binary information +* print_syscalls.py - extract syscall numbers from IL on specified file. Can be run both headless and in Binary Ninja +* version_switcher.py - uses the update API to see raw version notes and manually downgrade or upgrade +* instruction_iterator.py - very simple plugin that iterates through functions, blocks, and instructions + +To use the stand-alone Python examples, make sure your `PYTHON_PATH` includes the API, as shown below. Please note, this is a feature that requires the "GUI-less processing" capability not available in the personal edition. In the personal edition, all scripts must by run from the integrated python console (follow the directions below under "Loading Plugins" section) + +``` +PYTHONPATH=$PYTHONPATH:/Applications/Binary\ Ninja.app/Contents/Resources/python +``` + +## GUI Plugins + +These plugins require the UI to be running + +* breakpoint.py - small example showing how to modify a file and register a GUI menu item +* jump_table.py - heuristic based jump table detection for when the data-flow based computation fails, triggered by right-clicking on the location where the jump value is computed +* angr_plugin.py - a plugin to demonstrate both background threads, the simplified plugin UI elements, and highlighting +* export_svg.py - exports the graph view of a function to an SVG file for including in reports + +## Both + +These plugins are able to operate in either the GUI or as stand-alone plugins + +* nes.py - 6502 CPU architecture including LLIL lifting and `.NES` file format parser + + +## Loading Plugins + +Plugins are meant to be loaded into a running Binary Ninja GUI and should either be copied or symlinked into the appropriate plugin folder. You'll need to then re-start Binary Ninja. + +### OSX + +``` +~/Library/Application Support/Binary Ninja/plugins +``` + +### Windows + +``` +%APPDATA%\Binary Ninja\plugins +``` + +### Linux + +``` +~/.binaryninja/plugins +``` diff --git a/python/examples/angr_plugin.py b/python/examples/angr_plugin.py new file mode 100644 index 00000000..26f8040c --- /dev/null +++ b/python/examples/angr_plugin.py @@ -0,0 +1,153 @@ +# Copyright (c) 2015-2017 Vector 35 LLC +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + + +# This plugin assumes angr is already installed and available on the system. See the angr documentation +# for information about installing angr. It should be installed using the virtualenv method. +# +# This plugin is currently only known to work on Linux using virtualenv. Switch to the virtual environment +# (using a command such as "workon angr"), then run the Binary Ninja UI from the command line. +# +# This method is known to fail on Mac OS X as the virtualenv used by angr does not appear to provide a +# way to automatically link to the correct version of Python, even when running the UI from within the +# virtual environment. A later update may allow for a manual override to link to the required version +# of Python. + +import tempfile +import logging +import os + +__name__ = "__console__" # angr looks for this, it won't load from within a UI without it + +import angr +# For the lazy instead you can just import everything 'from binaryninja import *'' +from binaryninja.binaryview import BinaryView +from binaryninja.plugin import BackgroundTaskThread, PluginCommand +from binaryninja.interaction import show_plain_text_report, show_message_box +from binaryninja.highlight import HighlightColor +from binaryninja.enums import HighlightStandardColor, MessageBoxButtonSet, MessageBoxIcon + +# Disable warning logs as they show up as errors in the UI +logging.disable(logging.WARNING) + +# Create sets in the BinaryView's data field to store the desired path for each view +BinaryView.set_default_session_data("angr_find", set()) +BinaryView.set_default_session_data("angr_avoid", set()) + + +def escaped_output(str): + return '\n'.join([s.encode("string_escape") for s in str.split('\n')]) + + +# Define a background thread object for solving in the background +class Solver(BackgroundTaskThread): + def __init__(self, find, avoid, view): + BackgroundTaskThread.__init__(self, "Solving with angr...", True) + self.find = tuple(find) + self.avoid = tuple(avoid) + self.view = view + + # Write the binary to disk so that the angr API can read it + self.binary = tempfile.NamedTemporaryFile() + self.binary.write(view.file.raw.read(0, len(view.file.raw))) + self.binary.flush() + + def run(self): + # Create an angr project and an explorer with the user's settings + p = angr.Project(self.binary.name) + e = p.surveyors.Explorer(find = self.find, avoid = self.avoid) + + # Solve loop + while not e.done: + if self.cancelled: + # Solve cancelled, show results if there were any + if len(e.found) > 0: + break + return + + # Perform the next step in the solve + e.step() + + # Update status + active_count = len(e.active) + found_count = len(e.found) + + progress = "Solving with angr (%d active path%s" % (active_count, "s" if active_count != 1 else "") + if found_count > 0: + progress += ", %d path%s found" % (found_count, "s" if found_count != 1 else "") + self.progress = progress + ")..." + + # Solve complete, show report + text_report = "Found %d path%s.\n\n" % (len(e.found), "s" if len(e.found) != 1 else "") + i = 1 + for f in e.found: + text_report += "Path %d\n" % i + "=" * 10 + "\n" + text_report += "stdin:\n" + escaped_output(f.state.posix.dumps(0)) + "\n\n" + text_report += "stdout:\n" + escaped_output(f.state.posix.dumps(1)) + "\n\n" + text_report += "stderr:\n" + escaped_output(f.state.posix.dumps(2)) + "\n\n" + i += 1 + + name = self.view.file.filename + if len(name) > 0: + show_plain_text_report("Results from angr - " + os.path.basename(self.view.file.filename), text_report) + else: + show_plain_text_report("Results from angr", text_report) + + +def find_instr(bv, addr): + # Highlight the instruction in green + blocks = bv.get_basic_blocks_at(addr) + for block in blocks: + block.set_auto_highlight(HighlightColor(HighlightStandardColor.GreenHighlightColor, alpha = 128)) + block.function.set_auto_instr_highlight(addr, HighlightStandardColor.GreenHighlightColor) + + # Add the instruction to the list associated with the current view + bv.session_data.angr_find.add(addr) + + +def avoid_instr(bv, addr): + # Highlight the instruction in red + blocks = bv.get_basic_blocks_at(addr) + for block in blocks: + block.set_auto_highlight(HighlightColor(HighlightStandardColor.RedHighlightColor, alpha = 128)) + block.function.set_auto_instr_highlight(addr, HighlightStandardColor.RedHighlightColor) + + # Add the instruction to the list associated with the current view + bv.session_data.angr_avoid.add(addr) + + +def solve(bv): + if len(bv.session_data.angr_find) == 0: + show_message_box("Angr Solve", "You have not specified a goal instruction.\n\n" + + "Please right click on the goal instruction and select \"Find Path to This Instruction\" to " + + "continue.", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon) + return + + # Start a solver thread for the path associated with the view + s = Solver(bv.session_data.angr_find, bv.session_data.angr_avoid, bv) + s.start() + + +# Register commands for the user to interact with the plugin +PluginCommand.register_for_address("Find Path to This Instruction", + "When solving, find a path that gets to this instruction", find_instr) +PluginCommand.register_for_address("Avoid This Instruction", + "When solving, avoid paths that reach this instruction", avoid_instr) +PluginCommand.register("Solve With Angr", "Attempt to solve for a path that satisfies the constraints given", solve) diff --git a/python/examples/arch_hook.py b/python/examples/arch_hook.py new file mode 100644 index 00000000..452bd2b6 --- /dev/null +++ b/python/examples/arch_hook.py @@ -0,0 +1,16 @@ +from binaryninja.architecture import Architecture, ArchitectureHook + +class X86ReturnHook(ArchitectureHook): + def get_instruction_text(self, data, addr): + # Call the original implementation's method by calling the superclass + result, length = super(X86ReturnHook, self).get_instruction_text(data, addr) + + # Patch the name of the 'retn' instruction to 'ret' + if len(result) > 0 and result[0].text == 'retn': + result[0].text = 'ret' + + return result, length + +# Install the hook by constructing it with the desired architecture to hook, then registering it +X86ReturnHook(Architecture['x86']).register() + diff --git a/python/examples/bin_info.py b/python/examples/bin_info.py new file mode 100644 index 00000000..05a72ed0 --- /dev/null +++ b/python/examples/bin_info.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# Copyright (c) 2015-2017 Vector 35 LLC +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +import sys + +import binaryninja.log as log +from binaryninja.binaryview import BinaryViewType +import binaryninja.interaction as interaction +from binaryninja.plugin import PluginCommand + +# 2-3 compatibility +from binaryninja import range + + +def get_bininfo(bv): + if bv is None: + filename = "" + if len(sys.argv) > 1: + filename = sys.argv[1] + else: + filename = interaction.get_open_filename_input("Filename:") + if filename is None: + log.log_warn("No file specified") + sys.exit(1) + + bv = BinaryViewType.get_view_of_file(filename) + log.log_to_stdout(True) + + contents = "## %s ##\n" % bv.file.filename + contents += "- START: 0x%x\n\n" % bv.start + contents += "- ENTRY: 0x%x\n\n" % bv.entry_point + contents += "- ARCH: %s\n\n" % bv.arch.name + contents += "### First 10 Functions ###\n" + + contents += "| Start | Name |\n" + contents += "|------:|:-------|\n" + for i in range(min(10, len(bv.functions))): + contents += "| 0x%x | %s |\n" % (bv.functions[i].start, bv.functions[i].symbol.full_name) + + contents += "### First 10 Strings ###\n" + contents += "| Start | Length | String |\n" + contents += "|------:|-------:|:-------|\n" + for i in range(min(10, len(bv.strings))): + start = bv.strings[i].start + length = bv.strings[i].length + string = bv.read(start, length) + contents += "| 0x%x |%d | %s |\n" % (start, length, string) + return contents + + +def display_bininfo(bv): + interaction.show_markdown_report("Binary Info Report", get_bininfo(bv)) + + +if __name__ == "__main__": + print(get_bininfo(None)) +else: + PluginCommand.register("Binary Info", "Display basic info about the binary", display_bininfo) diff --git a/python/examples/breakpoint.py b/python/examples/breakpoint.py new file mode 100644 index 00000000..cbcb86d4 --- /dev/null +++ b/python/examples/breakpoint.py @@ -0,0 +1,50 @@ +# Copyright (c) 2015-2017 Vector 35 LLC +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + + +from binaryninja.plugin import PluginCommand +from binaryninja.log import log_error + + +def write_breakpoint(view, start, length): + """Sample function to show registering a plugin menu item for a range of bytes. Also possible: + register + register_for_address + register_for_function + """ + bkpt_str = { + "x86": "int3", + "x86_64": "int3", + "armv7": "bkpt", + "aarch64": "brk #0", + "mips32": "break"} + + if view.arch.name not in bkpt_str: + log_error("Architecture %s not supported" % view.arch.name) + return + + bkpt, err = view.arch.assemble(bkpt_str[view.arch.name]) + if bkpt is None: + log_error(err) + return + view.write(start, bkpt * length // len(bkpt)) + + +PluginCommand.register_for_range("Convert to breakpoint", "Fill region with breakpoint instructions.", write_breakpoint) diff --git a/python/examples/export_svg.py b/python/examples/export_svg.py new file mode 100755 index 00000000..5bb27824 --- /dev/null +++ b/python/examples/export_svg.py @@ -0,0 +1,224 @@ +# from binaryninja import * +import os +import webbrowser +import time +try: + from urllib import pathname2url # Python 2.x +except: + from urllib.request import pathname2url # Python 3.x + +from binaryninja.interaction import get_save_filename_input, show_message_box +from binaryninja.enums import MessageBoxButtonSet, MessageBoxIcon, MessageBoxButtonResult, InstructionTextTokenType, BranchType +from binaryninja.plugin import PluginCommand + +colors = {'green': [162, 217, 175], 'red': [222, 143, 151], 'blue': [128, 198, 233], 'cyan': [142, 230, 237], 'lightCyan': [176, 221, 228], 'orange': [237, 189, 129], 'yellow': [237, 223, 179], 'magenta': [218, 196, 209], 'none': [74, 74, 74]} + +escape_table = { + "'": "'", + ">": ">", + "<": "<", + '"': """, + ' ': " " +} + + +def escape(toescape): + toescape = toescape.decode('utf-8').encode('ascii', 'xmlcharrefreplace') # handle extended unicode + return ''.join(escape_table.get(i, i) for i in toescape) # still escape the basics + + +def save_svg(bv, function): + address = hex(function.start).replace('L', '') + path = os.path.dirname(bv.file.filename) + origname = os.path.basename(bv.file.filename) + filename = os.path.join(path, 'binaryninja-{filename}-{function}.html'.format(filename=origname, function=address)) + outputfile = get_save_filename_input('File name for export_svg', 'HTML files (*.html)', filename) + if outputfile is None: + return + content = render_svg(function, origname) + output = open(outputfile, 'w') + output.write(content) + output.close() + result = show_message_box("Open SVG", "Would you like to view the exported SVG?", + buttons = MessageBoxButtonSet.YesNoButtonSet, icon = MessageBoxIcon.QuestionIcon) + if result == MessageBoxButtonResult.YesButton: + url = 'file:{}'.format(pathname2url(outputfile)) + webbrowser.open(url) + + +def instruction_data_flow(function, address): + ''' TODO: Extract data flow information ''' + length = binaryninja.function.view.get_instruction_length(address) + bytes = binaryninja.function.view.read(address, length) + hex = bytes.encode('hex') + padded = ' '.join([hex[i:i + 2] for i in range(0, len(hex), 2)]) + return 'Opcode: {bytes}'.format(bytes=padded) + + +def render_svg(function, origname): + graph = binaryninja.function.create_graph() + graph.layout_and_wait() + heightconst = 15 + ratio = 0.48 + widthconst = heightconst * ratio + + output = ''' + + + + +''' + output += ''' + + + + + + + + + + + + + + + '''.format(width=graph.width * widthconst + 20, height=graph.height * heightconst + 20) + output += ''' + Function Graph 0 + ''' + edges = '' + for i, block in enumerate(graph.blocks): + + # Calculate basic block location and coordinates + x = ((block.x) * widthconst) + y = ((block.y) * heightconst) + width = ((block.width) * widthconst) + height = ((block.height) * heightconst) + + # Render block + output += ' \n'.format(i=i) + output += ' Basic Block {i}\n'.format(i=i) + rgb = colors['none'] + try: + bb = block.basic_block + color_code = bb.highlight.color + color_str = bb.highlight._standard_color_to_str(color_code) + if color_str in colors: + rgb = colors[color_str] + except: + pass + output += ' \n'.format(x=x, y=y, width=width + 16, height=height + 12, r=rgb[0], g=rgb[1], b=rgb[2]) + + # Render instructions, unfortunately tspans don't allow copying/pasting more + # than one line at a time, need SVG 1.2 textarea tags for that it looks like + + output += ' \n'.format(x=x, y=y + (i + 1) * heightconst) + for i, line in enumerate(block.lines): + output += ' '.format(x=x + 6, y=y + 6 + (i + 0.7) * heightconst, address=hex(line.address)[:-1]) + hover = instruction_data_flow(function, line.address) + output += '{hover}'.format(hover=hover) + for token in line.tokens: + # TODO: add hover for hex, function, and reg tokens + output += '{text}'.format(text=escape(token.text), tokentype=InstructionTextTokenType(token.type).name) + output += '\n' + output += ' \n' + output += ' \n' + + # Edges are rendered in a seperate chunk so they have priority over the + # basic blocks or else they'd render below them + + for edge in block.outgoing_edges: + points = "" + x, y = edge.points[0] + points += str(x * widthconst) + "," + str(y * heightconst + 12) + " " + for x, y in edge.points[1:-1]: + points += str(x * widthconst) + "," + str(y * heightconst) + " " + x, y = edge.points[-1] + points += str(x * widthconst) + "," + str(y * heightconst + 0) + " " + if edge.back_edge: + edges += ' \n'.format(type=BranchType(edge.type).name, points=points) + else: + edges += ' \n'.format(type=BranchType(edge.type).name, points=points) + output += ' ' + edges + '\n' + output += ' \n' + output += '' + + output += '

This CFG generated by Binary Ninja from {filename} on {timestring}.

'.format(filename = origname, timestring = time.strftime("%c")) + output += '' + return output + + +PluginCommand.register_for_function("Export to SVG", "Exports an SVG of the current function", save_svg) diff --git a/python/examples/instruction_iterator.py b/python/examples/instruction_iterator.py new file mode 100644 index 00000000..f55e1c1b --- /dev/null +++ b/python/examples/instruction_iterator.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# Copyright (c) 2015-2017 Vector 35 LLC +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +import sys +import binaryninja as binja + +if len(sys.argv) > 1: + target = sys.argv[1] + +bv = binja.BinaryViewType.get_view_of_file(target) +binja.log_to_stdout(True) +binja.log_info("-------- %s --------" % target) +binja.log_info("START: 0x%x" % bv.start) +binja.log_info("ENTRY: 0x%x" % bv.entry_point) +binja.log_info("ARCH: %s" % bv.arch.name) +binja.log_info("\n-------- Function List --------") + +""" print all the functions, their basic blocks, and their il instructions """ +for func in bv.functions: + binja.log_info(repr(func)) + for block in func.low_level_il: + binja.log_info("\t{0}".format(block)) + + for insn in block: + binja.log_info("\t\t{0}".format(insn)) + + +""" print all the functions, their basic blocks, and their mc instructions """ +for func in bv.functions: + binja.log_info(repr(func)) + for block in func: + binja.log_info("\t{0}".format(block)) + + for insn in block: + binja.log_info("\t\t{0}".format(insn)) diff --git a/python/examples/jump_table.py b/python/examples/jump_table.py new file mode 100644 index 00000000..4ed8dda2 --- /dev/null +++ b/python/examples/jump_table.py @@ -0,0 +1,86 @@ +# Copyright (c) 2015-2017 Vector 35 LLC +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +# This plugin will attempt to resolve simple jump tables (an array of code pointers) and add the destinations +# as indirect branch targets so that the flow graph reflects the jump table's control flow. +from binaryninja.plugin import PluginCommand +from binaryninja.enums import InstructionTextTokenType +import struct + + +def find_jump_table(bv, addr): + for block in bv.get_basic_blocks_at(addr): + func = block.function + arch = func.arch + addrsize = arch.address_size + + # Grab the instruction tokens so that we can look for the table's starting address + tokens, length = arch.get_instruction_text(bv.read(addr, 16), addr) + + # Look for the next jump instruction, which may be the current instruction. Some jump tables will + # compute the address first then jump to the computed address as a separate instruction. + jump_addr = addr + while jump_addr < block.end: + info = arch.get_instruction_info(bv.read(jump_addr, 16), jump_addr) + if len(info.branches) != 0: + break + jump_addr += info.length + if jump_addr >= block.end: + print("Unable to find jump after instruction 0x%x" % addr) + continue + print("Jump at 0x%x" % jump_addr) + + # Collect the branch targets for any tables referenced by the clicked instruction + branches = [] + for token in tokens: + if InstructionTextTokenType(token.type) == InstructionTextTokenType.PossibleAddressToken: # Table addresses will be a "possible address" token + tbl = token.value + print("Found possible table at 0x%x" % tbl) + i = 0 + while True: + # Read the next pointer from the table + data = bv.read(tbl + (i * addrsize), addrsize) + if len(data) == addrsize: + if addrsize == 4: + ptr = struct.unpack("= bv.start) and (ptr < bv.end): + print("Found destination 0x%x" % ptr) + branches.append((arch, ptr)) + else: + # Once a value that is not a pointer is encountered, the jump table is ended + break + else: + # Reading invalid memory + break + + i += 1 + + # Set the indirect branch targets on the jump instruction to be the list of targets discovered + func.set_user_indirect_branches(jump_addr, branches) + + +# Create a plugin command so that the user can right click on an instruction referencing a jump table and +# invoke the command +PluginCommand.register_for_address("Process jump table", "Look for jump table destinations", find_jump_table) diff --git a/python/examples/nds.py b/python/examples/nds.py new file mode 100644 index 00000000..a99303cf --- /dev/null +++ b/python/examples/nds.py @@ -0,0 +1,120 @@ +# Copyright (c) 2015-2017 Vector 35 LLC +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +# from binaryninja import * +from binaryninja.binaryview import BinaryView +from binaryninja.architecture import Architecture +from binaryninja.enums import SegmentFlag +from binaryninja.log import log_error + +import struct +import traceback + +# 2-3 compatibility +from binaryninja import range + + +def crc16(data): + crc = 0xffff + for ch in data: + crc ^= ord(ch) + for bit in range(0, 8): + if (crc & 1) == 1: + crc = (crc >> 1) ^ 0xa001 + else: + crc >>= 1 + return crc + + +class DSView(BinaryView): + def __init__(self, data): + BinaryView.__init__(self, file_metadata = data.file, parent_view = data) + self.raw = data + + @classmethod + def is_valid_for_data(self, data): + hdr = data.read(0, 0x160) + if len(hdr) < 0x160: + return False + if struct.unpack("> 4) + self.ram_banks = struct.unpack("B", hdr[8])[0] + self.rom_offset = 16 + if self.rom_flags & 4: + self.rom_offset += 512 + self.rom_length = self.rom_banks * 0x4000 + + # Add mapping for RAM and hardware registers, not backed by file contents + self.add_auto_segment(0, 0x8000, 0, 0, SegmentFlag.SegmentReadable | SegmentFlag.SegmentWritable | SegmentFlag.SegmentExecutable) + + # Add ROM mappings + self.add_auto_segment(0x8000, 0x4000, self.rom_offset + (self.__class__.bank * 0x4000), 0x4000, + SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable) + self.add_auto_segment(0xc000, 0x4000, self.rom_offset + self.rom_length - 0x4000, 0x4000, + SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable) + + nmi = struct.unpack("= 0x8000: + self.add_function(addr) + + return True + except: + log_error(traceback.format_exc()) + return False + + def perform_is_executable(self): + return True + + def perform_get_entry_point(self): + return struct.unpack("'.format(sys.argv[0])) + else: + print_syscalls(sys.argv[1]) diff --git a/python/examples/version_switcher.py b/python/examples/version_switcher.py new file mode 100644 index 00000000..c990a6c0 --- /dev/null +++ b/python/examples/version_switcher.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +# Copyright (c) 2015-2017 Vector 35 LLC +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +import sys + +from binaryninja.update import UpdateChannel, are_auto_updates_enabled, set_auto_updates_enabled, is_update_installation_pending, install_pending_update +from binaryninja import core_version +import datetime + +chandefault = UpdateChannel.list[0].name +channel = None +versions = [] + + +def load_channel(newchannel): + global channel + global versions + if (channel is not None and newchannel == channel.name): + print("Same channel, not updating.") + else: + try: + print("Loading channel %s" % newchannel) + channel = UpdateChannel[newchannel] + print("Loading versions...") + versions = channel.versions + except Exception: + print("%s is not a valid channel name. Defaulting to " % chandefault) + channel = UpdateChannel[chandefault] + + +def select(version): + done = False + date = datetime.datetime.fromtimestamp(version.time).strftime('%c') + while not done: + print("Version:\t%s" % version.version) + print("Updated:\t%s" % date) + print("Notes:\n\n-----\n%s" % version.notes) + print("-----") + print("\t1)\tSwitch to version") + print("\t2)\tMain Menu") + selection = raw_input('Choice: ') + if selection.isdigit(): + selection = int(selection) + else: + selection = 0 + if (selection == 2): + done = True + elif (selection == 1): + if (version.version == channel.latest_version.version): + print("Requesting update to latest version.") + else: + print("Requesting update to prior version.") + if are_auto_updates_enabled(): + print("Disabling automatic updates.") + set_auto_updates_enabled(False) + if (version.version == core_version): + print("Already running %s" % version.version) + else: + print("version.version %s" % version.version) + print("core_version %s" % core_version) + print("Downloading...") + print(version.update()) + print("Installing...") + if is_update_installation_pending: + #note that the GUI will be launched after update but should still do the upgrade headless + install_pending_update() + # forward updating won't work without reloading + sys.exit() + else: + print("Invalid selection") + + +def list_channels(): + done = False + print("\tSelect channel:\n") + while not done: + channel_list = UpdateChannel.list + for index, item in enumerate(channel_list): + print("\t%d)\t%s" % (index + 1, item.name)) + print("\t%d)\t%s" % (len(channel_list) + 1, "Main Menu")) + selection = raw_input('Choice: ') + if selection.isdigit(): + selection = int(selection) + else: + selection = 0 + if (selection <= 0 or selection > len(channel_list) + 1): + print("%s is an invalid choice." % selection) + else: + done = True + if (selection != len(channel_list) + 1): + load_channel(channel_list[selection - 1].name) + + +def toggle_updates(): + set_auto_updates_enabled(not are_auto_updates_enabled()) + + +def main(): + global channel + done = False + load_channel(chandefault) + while not done: + print("\n\tBinary Ninja Version Switcher") + print("\t\tCurrent Channel:\t%s" % channel.name) + print("\t\tCurrent Version:\t%s" % core_version) + print("\t\tAuto-Updates On:\t%s\n" % are_auto_updates_enabled()) + for index, version in enumerate(versions): + date = datetime.datetime.fromtimestamp(version.time).strftime('%c') + print("\t%d)\t%s (%s)" % (index + 1, version.version, date)) + print("\t%d)\t%s" % (len(versions) + 1, "Switch Channel")) + print("\t%d)\t%s" % (len(versions) + 2, "Toggle Auto Updates")) + print("\t%d)\t%s" % (len(versions) + 3, "Exit")) + selection = raw_input('Choice: ') + if selection.isdigit(): + selection = int(selection) + else: + selection = 0 + if (selection <= 0 or selection > len(versions) + 3): + print("%d is an invalid choice.\n\n" % selection) + else: + if (selection == len(versions) + 3): + done = True + elif (selection == len(versions) + 2): + toggle_updates() + elif (selection == len(versions) + 1): + list_channels() + else: + select(versions[selection - 1]) + + +if __name__ == "__main__": + main() diff --git a/python/function.py b/python/function.py index 8407eece..0589a5e2 100644 --- a/python/function.py +++ b/python/function.py @@ -26,7 +26,7 @@ import ctypes # Binary Ninja components import binaryninja from binaryninja import _binaryninjacore as core -from binaryninja import associateddatastore #required in the main scope due to being an argument for _FunctionAssociatedDataStore +from binaryninja import associateddatastore # Required in the main scope due to being an argument for _FunctionAssociatedDataStore from binaryninja import highlight from binaryninja import log from binaryninja import types @@ -1631,7 +1631,7 @@ class FunctionGraphEdge(object): class FunctionGraphBlock(object): - def __init__(self, handle): + def __init__(self, handle, graph): self.handle = handle self.graph = graph @@ -1657,14 +1657,14 @@ class FunctionGraphBlock(object): core.BNFreeBasicBlock(block) return None - view = binaryview.BinaryView(handle = core.BNGetFunctionData(func_handle)) + view = binaryninja.binaryview.BinaryView(handle = core.BNGetFunctionData(func_handle)) func = Function(view, func_handle) if core.BNIsLowLevelILBasicBlock(block): - block = lowlevelil.LowLevelILBasicBlock(view, block, + block = binaryninja.lowlevelil.LowLevelILBasicBlock(view, block, lowlevelil.LowLevelILFunction(func.arch, core.BNGetBasicBlockLowLevelILFunction(block), func)) elif core.BNIsMediumLevelILBasicBlock(block): - block = mediumlevelil.MediumLevelILBasicBlock(view, block, + block = binaryninja.mediumlevelil.MediumLevelILBasicBlock(view, block, mediumlevelil.MediumLevelILFunction(func.arch, core.BNGetBasicBlockMediumLevelILFunction(block), func)) else: block = binaryninja.basicblock.BasicBlock(view, block) @@ -1942,12 +1942,12 @@ class FunctionGraph(object): il_func = core.BNGetFunctionGraphLowLevelILFunction(self.handle) if not il_func: return None - return lowlevelil.LowLevelILFunction(self.function.arch, il_func, self.function) + return binaryninja.lowlevelil.LowLevelILFunction(self.function.arch, il_func, self.function) if self.is_medium_level_il: il_func = core.BNGetFunctionGraphMediumLevelILFunction(self.handle) if not il_func: return None - return mediumlevelil.MediumLevelILFunction(self.function.arch, il_func, self.function) + return binaryninja.mediumlevelil.MediumLevelILFunction(self.function.arch, il_func, self.function) return None def __setattr__(self, name, value): diff --git a/python/plugin.py b/python/plugin.py index b8217f0b..2fc30052 100644 --- a/python/plugin.py +++ b/python/plugin.py @@ -121,7 +121,7 @@ class PluginCommand(with_metaclass(_PluginCommandMetaClass, object)): try: file_metadata = binaryninja.filemetadata.FileMetadata(handle = core.BNGetFileForView(view)) view_obj = binaryninja.binaryview.BinaryView(file_metadata = file_metadata, handle = core.BNNewViewReference(view)) - func_obj = binaryninja.function.Function(view_obj, core.BNNewFunctionReference(func)) + func_obj = function.Function(view_obj, core.BNNewFunctionReference(func)) action(view_obj, func_obj) except: log.log_error(traceback.format_exc()) @@ -132,7 +132,7 @@ class PluginCommand(with_metaclass(_PluginCommandMetaClass, object)): file_metadata = filemetadata.FileMetadata(handle = core.BNGetFileForView(view)) view_obj = binaryview.BinaryView(file_metadata = file_metadata, handle = core.BNNewViewReference(view)) owner = function.Function(view_obj, core.BNGetLowLevelILOwnerFunction(func)) - func_obj = lowlevelil.LowLevelILFunction(owner.arch, core.BNNewLowLevelILFunctionReference(func), owner) + func_obj = binaryninja.lowlevelil.LowLevelILFunction(owner.arch, core.BNNewLowLevelILFunctionReference(func), owner) action(view_obj, func_obj) except: log.log_error(traceback.format_exc()) @@ -143,7 +143,7 @@ class PluginCommand(with_metaclass(_PluginCommandMetaClass, object)): file_metadata = filemetadata.FileMetadata(handle = core.BNGetFileForView(view)) view_obj = binaryview.BinaryView(file_metadata = file_metadata, handle = core.BNNewViewReference(view)) owner = function.Function(view_obj, core.BNGetLowLevelILOwnerFunction(func)) - func_obj = lowlevelil.LowLevelILFunction(owner.arch, core.BNNewLowLevelILFunctionReference(func), owner) + func_obj = binaryninja.lowlevelil.LowLevelILFunction(owner.arch, core.BNNewLowLevelILFunctionReference(func), owner) action(view_obj, func_obj[instr]) except: log.log_error(traceback.format_exc()) @@ -154,7 +154,7 @@ class PluginCommand(with_metaclass(_PluginCommandMetaClass, object)): file_metadata = filemetadata.FileMetadata(handle = core.BNGetFileForView(view)) view_obj = binaryview.BinaryView(file_metadata = file_metadata, handle = core.BNNewViewReference(view)) owner = function.Function(view_obj, core.BNGetMediumLevelILOwnerFunction(func)) - func_obj = mediumlevelil.MediumLevelILFunction(owner.arch, core.BNNewMediumLevelILFunctionReference(func), owner) + func_obj = binaryninja.mediumlevelil.MediumLevelILFunction(owner.arch, core.BNNewMediumLevelILFunctionReference(func), owner) action(view_obj, func_obj) except: log.log_error(traceback.format_exc()) @@ -165,7 +165,7 @@ class PluginCommand(with_metaclass(_PluginCommandMetaClass, object)): file_metadata = filemetadata.FileMetadata(handle = core.BNGetFileForView(view)) view_obj = binaryview.BinaryView(file_metadata = file_metadata, handle = core.BNNewViewReference(view)) owner = function.Function(view_obj, core.BNGetMediumLevelILOwnerFunction(func)) - func_obj = mediumlevelil.MediumLevelILFunction(owner.arch, core.BNNewMediumLevelILFunctionReference(func), owner) + func_obj = binaryninja.mediumlevelil.MediumLevelILFunction(owner.arch, core.BNNewMediumLevelILFunctionReference(func), owner) action(view_obj, func_obj[instr]) except: log.log_error(traceback.format_exc()) @@ -213,7 +213,7 @@ class PluginCommand(with_metaclass(_PluginCommandMetaClass, object)): return True file_metadata = binaryninja.filemetadata.FileMetadata(handle = core.BNGetFileForView(view)) view_obj = binaryninja.binaryview.BinaryView(file_metadata = file_metadata, handle = core.BNNewViewReference(view)) - func_obj = binaryninja.function.Function(view_obj, core.BNNewFunctionReference(func)) + func_obj = function.Function(view_obj, core.BNNewFunctionReference(func)) return is_valid(view_obj, func_obj) except: log.log_error(traceback.format_exc()) @@ -227,7 +227,7 @@ class PluginCommand(with_metaclass(_PluginCommandMetaClass, object)): file_metadata = filemetadata.FileMetadata(handle = core.BNGetFileForView(view)) view_obj = binaryview.BinaryView(file_metadata = file_metadata, handle = core.BNNewViewReference(view)) owner = function.Function(view_obj, core.BNGetLowLevelILOwnerFunction(func)) - func_obj = lowlevelil.LowLevelILFunction(owner.arch, core.BNNewLowLevelILFunctionReference(func), owner) + func_obj = binaryninja.lowlevelil.LowLevelILFunction(owner.arch, core.BNNewLowLevelILFunctionReference(func), owner) return is_valid(view_obj, func_obj) except: log.log_error(traceback.format_exc()) @@ -241,7 +241,7 @@ class PluginCommand(with_metaclass(_PluginCommandMetaClass, object)): file_metadata = filemetadata.FileMetadata(handle = core.BNGetFileForView(view)) view_obj = binaryview.BinaryView(file_metadata = file_metadata, handle = core.BNNewViewReference(view)) owner = function.Function(view_obj, core.BNGetLowLevelILOwnerFunction(func)) - func_obj = lowlevelil.LowLevelILFunction(owner.arch, core.BNNewLowLevelILFunctionReference(func), owner) + func_obj = binaryninja.lowlevelil.LowLevelILFunction(owner.arch, core.BNNewLowLevelILFunctionReference(func), owner) return is_valid(view_obj, func_obj[instr]) except: log.log_error(traceback.format_exc()) @@ -255,7 +255,7 @@ class PluginCommand(with_metaclass(_PluginCommandMetaClass, object)): file_metadata = filemetadata.FileMetadata(handle = core.BNGetFileForView(view)) view_obj = binaryview.BinaryView(file_metadata = file_metadata, handle = core.BNNewViewReference(view)) owner = function.Function(view_obj, core.BNGetMediumLevelILOwnerFunction(func)) - func_obj = mediumlevelil.MediumLevelILFunction(owner.arch, core.BNNewMediumLevelILFunctionReference(func), owner) + func_obj = binaryninja.mediumlevelil.MediumLevelILFunction(owner.arch, core.BNNewMediumLevelILFunctionReference(func), owner) return is_valid(view_obj, func_obj) except: log.log_error(traceback.format_exc()) @@ -269,7 +269,7 @@ class PluginCommand(with_metaclass(_PluginCommandMetaClass, object)): file_metadata = filemetadata.FileMetadata(handle = core.BNGetFileForView(view)) view_obj = binaryview.BinaryView(file_metadata = file_metadata, handle = core.BNNewViewReference(view)) owner = function.Function(view_obj, core.BNGetMediumLevelILOwnerFunction(func)) - func_obj = mediumlevelil.MediumLevelILFunction(owner.arch, core.BNNewMediumLevelILFunctionReference(func), owner) + func_obj = binaryninja.mediumlevelil.MediumLevelILFunction(owner.arch, core.BNNewMediumLevelILFunctionReference(func), owner) return is_valid(view_obj, func_obj[instr]) except: log.log_error(traceback.format_exc()) @@ -364,7 +364,7 @@ class PluginCommand(with_metaclass(_PluginCommandMetaClass, object)): .. warning:: Calling ``register_for_low_level_il_function`` with the same function name will replace the existing function but will leak the memory of the original plugin. """ - startup._init_plugins() + binaryninja.startup._init_plugins() action_obj = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.POINTER(core.BNBinaryView), ctypes.POINTER(core.BNLowLevelILFunction))(lambda ctxt, view, func: cls._low_level_il_function_action(view, func, action)) is_valid_obj = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.POINTER(core.BNBinaryView), ctypes.POINTER(core.BNLowLevelILFunction))(lambda ctxt, view, func: cls._low_level_il_function_is_valid(view, func, is_valid)) cls._registered_commands.append((action_obj, is_valid_obj)) @@ -383,7 +383,7 @@ class PluginCommand(with_metaclass(_PluginCommandMetaClass, object)): .. warning:: Calling ``register_for_low_level_il_instruction`` with the same function name will replace the existing function but will leak the memory of the original plugin. """ - startup._init_plugins() + binaryninja.startup._init_plugins() action_obj = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.POINTER(core.BNBinaryView), ctypes.POINTER(core.BNLowLevelILFunction), ctypes.c_ulonglong)(lambda ctxt, view, func, instr: cls._low_level_il_instruction_action(view, func, instr, action)) is_valid_obj = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.POINTER(core.BNBinaryView), ctypes.POINTER(core.BNLowLevelILFunction), ctypes.c_ulonglong)(lambda ctxt, view, func, instr: cls._low_level_il_instruction_is_valid(view, func, instr, is_valid)) cls._registered_commands.append((action_obj, is_valid_obj)) @@ -402,7 +402,7 @@ class PluginCommand(with_metaclass(_PluginCommandMetaClass, object)): .. warning:: Calling ``register_for_medium_level_il_function`` with the same function name will replace the existing function but will leak the memory of the original plugin. """ - startup._init_plugins() + binaryninja.startup._init_plugins() action_obj = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.POINTER(core.BNBinaryView), ctypes.POINTER(core.BNMediumLevelILFunction))(lambda ctxt, view, func: cls._medium_level_il_function_action(view, func, action)) is_valid_obj = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.POINTER(core.BNBinaryView), ctypes.POINTER(core.BNMediumLevelILFunction))(lambda ctxt, view, func: cls._medium_level_il_function_is_valid(view, func, is_valid)) cls._registered_commands.append((action_obj, is_valid_obj)) @@ -421,7 +421,7 @@ class PluginCommand(with_metaclass(_PluginCommandMetaClass, object)): .. warning:: Calling ``register_for_medium_level_il_instruction`` with the same function name will replace the existing function but will leak the memory of the original plugin. """ - startup._init_plugins() + binaryninja.startup._init_plugins() action_obj = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.POINTER(core.BNBinaryView), ctypes.POINTER(core.BNMediumLevelILFunction), ctypes.c_ulonglong)(lambda ctxt, view, func, instr: cls._medium_level_il_instruction_action(view, func, instr, action)) is_valid_obj = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.POINTER(core.BNBinaryView), ctypes.POINTER(core.BNMediumLevelILFunction), ctypes.c_ulonglong)(lambda ctxt, view, func, instr: cls._medium_level_il_instruction_is_valid(view, func, instr, is_valid)) cls._registered_commands.append((action_obj, is_valid_obj)) @@ -469,7 +469,7 @@ class PluginCommand(with_metaclass(_PluginCommandMetaClass, object)): elif self.command.type == PluginCommandType.LowLevelILInstructionPluginCommand: if context.instruction is None: return False - if not isinstance(context.instruction, lowlevelil.LowLevelILInstruction): + if not isinstance(context.instruction, binaryninja.lowlevelil.LowLevelILInstruction): return False if not self.command.lowLevelILInstructionIsValid: return True @@ -484,7 +484,7 @@ class PluginCommand(with_metaclass(_PluginCommandMetaClass, object)): elif self.command.type == PluginCommandType.MediumLevelILInstructionPluginCommand: if context.instruction is None: return False - if not isinstance(context.instruction, mediumlevelil.MediumLevelILInstruction): + if not isinstance(context.instruction, binaryninja.mediumlevelil.MediumLevelILInstruction): return False if not self.command.mediumLevelILInstructionIsValid: return True diff --git a/suite/generator.py b/suite/generator.py index ad0d24fc..f4ee69ee 100755 --- a/suite/generator.py +++ b/suite/generator.py @@ -22,6 +22,7 @@ from collections import Counter global verbose verbose = False + class TestBinaryNinjaAPI(unittest.TestCase): # Returns a tuple of: # bool : Two lists are equal diff --git a/suite/oracle.pkl b/suite/oracle.pkl index d7ce1115..f0be17d4 100644 Binary files a/suite/oracle.pkl and b/suite/oracle.pkl differ diff --git a/suite/testcommon.py b/suite/testcommon.py index ef85c053..07ceb592 100644 --- a/suite/testcommon.py +++ b/suite/testcommon.py @@ -541,6 +541,7 @@ class TestBuilder(Builder): """Types produced different result""" file_name = os.path.join(self.test_store, "helloworld") bv = binja.BinaryViewType.get_view_of_file(file_name) + preprocessed = binja.preprocess_source(""" #ifdef nonexistant int foo = 1; @@ -550,7 +551,7 @@ class TestBuilder(Builder): long long bar1 = 2; #endif """) - source = '\n'.join([i.decode('utf-8') for i in preprocessed[0].split(b'\n') if not b'#line' in i and len(i) > 0]) + source = '\n'.join([i.decode('charmap') for i in preprocessed[0].split(b'\n') if not b'#line' in i and len(i) > 0]) typelist = bv.platform.parse_types_from_source(source) inttype = binja.Type.int(4) @@ -566,14 +567,13 @@ class TestBuilder(Builder): retinfo.append("Type equality: " + str((inttype == inttype) and not (inttype != inttype))) return retinfo - def test_Plugin_bin_info(self): """print_syscalls plugin produced different result""" file_name = os.path.join(self.test_store, "helloworld") self.unpackage_file(file_name) result = subprocess.Popen(["python", os.path.join(self.examples_dir, "bin_info.py"), file_name], stdout=subprocess.PIPE).communicate()[0] # normalize line endings and path sep - return [result.replace(b"\\", b"/").replace(b"\r\n", b"\n").decode("charmap")] + return [line for line in result.replace(b"\\", b"/").replace(b"\r\n", b"\n").decode("charmap").split("\n")] def test_linear_disassembly(self): """linear_disassembly produced different result""" diff --git a/suite/unit.py b/suite/unit.py index 9cbed511..e01b0f19 100644 --- a/suite/unit.py +++ b/suite/unit.py @@ -13,6 +13,7 @@ from collections import Counter global verbose verbose = False + class TestBinaryNinjaAPI(unittest.TestCase): # Returns a tuple of: # bool : Two lists are equal @@ -235,12 +236,6 @@ class TestBinaryNinjaAPI(unittest.TestCase): def test_binary___loop_constant_propagate(self): self.run_binary_test('suite/binaries/test_corpus/loop_constant_propagate.zip') - def test_binary___ls(self): - self.run_binary_test('suite/binaries/test_corpus/ls.zip') - - def test_binary___md5(self): - self.run_binary_test('suite/binaries/test_corpus/md5.zip') - def test_binary___partial_register_dataflow(self): self.run_binary_test('suite/binaries/test_corpus/partial_register_dataflow.zip') -- cgit v1.3.1 From 2c7f443ce0b3d92ea44acb440c1891344eacc6d4 Mon Sep 17 00:00:00 2001 From: KyleMiles Date: Fri, 13 Jul 2018 19:23:31 -0400 Subject: Python2/3 String Compatibility Fix --- .gitignore | 1 + python/__init__.py | 14 ++++++++++++++ python/binaryview.py | 4 ++-- python/databuffer.py | 8 ++++---- python/demangle.py | 8 ++++---- python/downloadprovider.py | 7 +++++-- python/generator.cpp | 14 +++++--------- python/metadata.py | 3 ++- python/setting.py | 3 ++- python/types.py | 3 ++- suite/oracle.pkl | Bin 440689 -> 0 bytes 11 files changed, 41 insertions(+), 24 deletions(-) delete mode 100644 suite/oracle.pkl (limited to 'python/downloadprovider.py') diff --git a/.gitignore b/.gitignore index 302ad960..fca3f67d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ Thumbs.db *.pdb *.ilk *.exp +*.pkl libbinaryninja* binaryninjacore.lib binaryninjacore.dll diff --git a/python/__init__.py b/python/__init__.py index 36ed4f0d..9d183465 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -51,6 +51,20 @@ def with_metaclass(meta, *bases): return type.__new__(metaclass, 'temporary_class', (), {}) +def cstr(arg): + if isinstance(arg, bytes) or arg is None: + return arg + else: + return arg.encode('charmap') + + +def pyNativeStr(arg): + if isinstance(arg, str): + return arg + else: + return arg.decode('charmap') + + # Binary Ninja components import binaryninja._binaryninjacore as core # __all__ = [ diff --git a/python/binaryview.py b/python/binaryview.py index c4fddd7c..25953b34 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -98,7 +98,7 @@ class StringReference(object): @property def value(self): - return self.view.read(self.start, self.length).decode("charmap") + return binaryninja.pyNativeStr(self.view.read(self.start, self.length)) def __repr__(self): return "<%s: %#x, len %#x>" % (self.type, self.start, self.length) @@ -3503,7 +3503,7 @@ class BinaryView(object): def get_unique_section_names(self, name_list): incoming_names = (ctypes.c_char_p * len(name_list))() for i in range(0, len(name_list)): - incoming_names[i] = name_list[i].encode('charmap') + incoming_names[i] = binaryninja.cstr(name_list[i]) outgoing_names = core.BNGetUniqueSectionNames(self.handle, incoming_names, len(name_list)) result = [] for i in range(0, len(name_list)): diff --git a/python/databuffer.py b/python/databuffer.py index 298d0002..2fb5382d 100644 --- a/python/databuffer.py +++ b/python/databuffer.py @@ -23,6 +23,9 @@ import ctypes # Binary Ninja components from binaryninja import _binaryninjacore as core +# 2-3 compatibility +from binaryninja import pyNativeStr + class DataBuffer(object): def __init__(self, contents="", handle=None): @@ -114,10 +117,7 @@ class DataBuffer(object): def __str__(self): buf = ctypes.create_string_buffer(len(self)) ctypes.memmove(buf, core.BNGetDataBufferContents(self.handle), len(self)) - if isinstance(buf.raw, str): - return buf.raw - else: - return buf.raw.decode("charmap") + return pyNativeStr(buf.raw) def __bytes__(self): buf = ctypes.create_string_buffer(len(self)) diff --git a/python/demangle.py b/python/demangle.py index 466324bc..2fa1028f 100644 --- a/python/demangle.py +++ b/python/demangle.py @@ -22,9 +22,11 @@ import ctypes # Binary Ninja components from binaryninja import _binaryninjacore as core +from binaryninja import types # 2-3 compatibility from binaryninja import range +from binaryninja import pyNativeStr def get_qualified_name(names): @@ -58,28 +60,26 @@ def demangle_ms(arch, mangled_name): (, ['Foobar', 'testf']) >>> """ - from binaryninja import types handle = ctypes.POINTER(core.BNType)() outName = ctypes.POINTER(ctypes.c_char_p)() outSize = ctypes.c_ulonglong() names = [] if core.BNDemangleMS(arch.handle, mangled_name, ctypes.byref(handle), ctypes.byref(outName), ctypes.byref(outSize)): for i in range(outSize.value): - names.append(outName[i].decode('charmap')) + names.append(pyNativeStr(outName[i])) core.BNFreeDemangledName(ctypes.byref(outName), outSize.value) return (types.Type(handle), names) return (None, mangled_name) def demangle_gnu3(arch, mangled_name): - from binaryninja import types handle = ctypes.POINTER(core.BNType)() outName = ctypes.POINTER(ctypes.c_char_p)() outSize = ctypes.c_ulonglong() names = [] if core.BNDemangleGNU3(arch.handle, mangled_name, ctypes.byref(handle), ctypes.byref(outName), ctypes.byref(outSize)): for i in range(outSize.value): - names.append(outName[i].decode('charmap')) + names.append(pyNativeStr(outName[i])) core.BNFreeDemangledName(ctypes.byref(outName), outSize.value) if not handle: return (None, names) diff --git a/python/downloadprovider.py b/python/downloadprovider.py index 09abd516..14368ee7 100644 --- a/python/downloadprovider.py +++ b/python/downloadprovider.py @@ -31,6 +31,9 @@ from binaryninja import with_metaclass from binaryninja import startup from binaryninja import log +# 2-3 compatibility +from binaryninja import pyNativeStr + class DownloadInstance(object): def __init__(self, provider, handle = None): @@ -166,7 +169,7 @@ if sys.version_info >= (2, 7, 9): opener = build_opener(ProxyHandler({'https': proxy_setting})) install_opener(opener) - r = urlopen(url.decode("charmap")) + r = urlopen(pyNativeStr(url)) total_size = int(r.headers.get('content-length', 0)) bytes_sent = 0 while True: @@ -224,7 +227,7 @@ else: else: proxies = None - r = requests.get(url.decode("charmap"), proxies=proxies) + r = requests.get(pyNativeStr(url), proxies=proxies) if not r.ok: core.BNSetErrorForDownloadInstance(self.handle, "Received error from server") return -1 diff --git a/python/generator.cpp b/python/generator.cpp index 2da856ff..afc77254 100644 --- a/python/generator.cpp +++ b/python/generator.cpp @@ -198,13 +198,9 @@ int main(int argc, char* argv[]) fprintf(out, "\t_base_path = os.path.join(os.path.dirname(__file__), \"..\", \"..\")\n"); fprintf(out, "\tcore = ctypes.CDLL(os.path.join(_base_path, \"binaryninjacore.dll\"))\n"); fprintf(out, "else:\n"); - fprintf(out, "\traise Exception(\"OS not supported\")\n\n"); + fprintf(out, "\traise Exception(\"OS not supported\")\n\n\n"); - fprintf(out, "def cstr(arg):\n"); - fprintf(out, "\tif isinstance(arg, str):\n"); - fprintf(out, "\t\treturn arg.encode('charmap')\n"); - fprintf(out, "\telse:\n"); - fprintf(out, "\t\treturn arg\n\n"); + fprintf(out, "from binaryninja import cstr, pyNativeStr\n\n\n"); // Create type objects fprintf(out, "# Type definitions\n"); @@ -226,8 +222,8 @@ int main(int argc, char* argv[]) (arg.type->GetChildType()->GetWidth() == 1) && (arg.type->GetChildType()->IsSigned())) { - fprintf(out, "\t@property\n\tdef %s(self):\n\t\treturn self._%s.decode('charmap')\n", arg.name.c_str(), arg.name.c_str()); - fprintf(out, "\t@%s.setter\n\tdef %s(self, value):\n\t\tself._%s = value.encode('charmap')\n", arg.name.c_str(), arg.name.c_str(), arg.name.c_str()); + fprintf(out, "\t@property\n\tdef %s(self):\n\t\treturn pyNativeStr(self._%s)\n", arg.name.c_str(), arg.name.c_str()); + fprintf(out, "\t@%s.setter\n\tdef %s(self, value):\n\t\tself._%s = cstr(value)\n", arg.name.c_str(), arg.name.c_str(), arg.name.c_str()); stringField = true; } } @@ -423,7 +419,7 @@ int main(int argc, char* argv[]) } else fprintf(out, "\tresult = %s(*args)\n", funcName.c_str()); - fprintf(out, "\tstring = str(ctypes.cast(result, ctypes.c_char_p).value.decode('charmap'))\n"); + fprintf(out, "\tstring = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value))\n"); fprintf(out, "\tBNFreeString(result)\n"); fprintf(out, "\treturn string\n"); } diff --git a/python/metadata.py b/python/metadata.py index b17bbf4f..606e4ceb 100644 --- a/python/metadata.py +++ b/python/metadata.py @@ -28,6 +28,7 @@ from binaryninja.enums import MetadataType # 2-3 compatibility from binaryninja import range +from binaryninja import pyNativeStr class Metadata(object): @@ -148,7 +149,7 @@ class Metadata(object): try: for i in range(result.contents.size): if isinstance(result.contents.keys[i], bytes): - yield str(result.contents.keys[i].decode('charmap')) + yield str(pyNativeStr(result.contents.keys[i])) else: yield result.contents.keys[i] finally: diff --git a/python/setting.py b/python/setting.py index 355492aa..93f6b72a 100644 --- a/python/setting.py +++ b/python/setting.py @@ -25,6 +25,7 @@ from binaryninja import _binaryninjacore as core # 2-3 compatibility from binaryninja import range +from binaryninja import pyNativeStr class Setting(object): @@ -62,7 +63,7 @@ class Setting(object): result = core.BNSettingGetStringList(self.plugin_name, name, default_list, ctypes.byref(length)) out_list = [] for i in range(length.value): - out_list.append(result[i].decode('charmap')) + out_list.append(pyNativeStr(result[i])) core.BNFreeStringList(result, length) return out_list diff --git a/python/types.py b/python/types.py index e8ce7464..12e7733f 100644 --- a/python/types.py +++ b/python/types.py @@ -30,6 +30,7 @@ from binaryninja.enums import SymbolType, TypeClass, NamedTypeReferenceClass, In # 2-3 compatibility from binaryninja import range +from binaryninja import pyNativeStr class QualifiedName(object): @@ -41,7 +42,7 @@ class QualifiedName(object): self.name = name.name self.byte_name = [n.encode('charmap') for n in name.name] else: - self.name = [i.decode('charmap') for i in name] + self.name = [pyNativeStr(i) for i in name] self.byte_name = name def __str__(self): diff --git a/suite/oracle.pkl b/suite/oracle.pkl deleted file mode 100644 index f0be17d4..00000000 Binary files a/suite/oracle.pkl and /dev/null differ -- cgit v1.3.1