summaryrefslogtreecommitdiff
path: root/suite
diff options
context:
space:
mode:
authorRusty Wagner <rusty@vector35.com>2018-07-26 16:02:26 -0400
committerRusty Wagner <rusty@vector35.com>2018-07-26 16:18:02 -0400
commit6eb3234d924d870641ee30c4263437f1d8a8d5c7 (patch)
treeb64815c5e0a2c3b1a10a3e3dcab4c786fdd85c34 /suite
parentc5c93fc82b8929d04f62d241ca50228de60fa5f4 (diff)
parent1f986c2698ff9df6d42429b1b7699842223634e5 (diff)
Merge branch 'dev' into test_stack_adjust
Diffstat (limited to 'suite')
-rw-r--r--suite/api_test.py221
m---------suite/binaries0
-rwxr-xr-xsuite/generator.py328
-rw-r--r--suite/pwnadventurez.nesbin0 -> 262160 bytes
-rw-r--r--suite/testcommon.py802
5 files changed, 1351 insertions, 0 deletions
diff --git a/suite/api_test.py b/suite/api_test.py
new file mode 100644
index 00000000..740f88e3
--- /dev/null
+++ b/suite/api_test.py
@@ -0,0 +1,221 @@
+import unittest
+import platform
+import os
+from binaryninja.setting import Setting
+from binaryninja.metadata import Metadata
+from binaryninja.demangle import demangle_gnu3, get_qualified_name
+from binaryninja.architecture import Architecture
+
+
+class SettingsAPI(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ pass
+
+ @classmethod
+ def tearDownClass(cls):
+ setting = Setting("test")
+ setting.remove_setting_group("test")
+
+ def test_bool_settings(self):
+ setting = Setting("test")
+ setting.set("bool_test_true", True)
+ setting.set("bool_test_false", False)
+ assert not setting.get_bool("bool_test_false"), "bool_test_false failed"
+ assert setting.get_bool("bool_test_true"), "bool_test_true failed"
+ assert setting.get_bool("bool_test_default_True", True), "bool_test_default_True failed"
+ assert not setting.get_bool("bool_test_default_False", False), "bool_test_default_False failed"
+
+ def test_int_settings(self):
+ setting = Setting("test")
+ setting.set("int_test1", 0x100)
+ setting.set("int_test2", 0)
+ setting.set("int_test3", -1)
+ assert setting.get_integer("int_test1") == 0x100, "int_test1 failed"
+ assert setting.get_integer("int_test2") == 0, "int_test2 failed"
+ assert setting.get_integer("int_test3") == -1, "int_test3 failed"
+ assert setting.get_integer("int_test_default_1", 1) == 1, "int_test_default_1 failed"
+
+ def test_float_settings(self):
+ setting = Setting("test")
+ setting.set("float_test1", 10.5)
+ setting.set("float_test2", -0.5)
+ assert setting.get_double("float_test1") == 10.5, "float_test1 failed"
+ assert setting.get_double("float_test2") == -0.5, "float_test1 failed"
+ assert setting.get_double("float_test_default", -5.5), "float_test_default failed"
+
+ def test_str_settings(self):
+ setting = Setting("test")
+ setting.set("str_test1", "hi")
+ setting.set("str_test2", "")
+ setting.set("str_test3", "A" * 1000)
+ assert setting.get_string("str_test1") == "hi", "str_test1 failed"
+ assert setting.get_string("str_test2") == "", "str_test2 failed"
+ assert setting.get_string("str_test3") == "A" * 1000, "str_test3 failed"
+ assert setting.get_string("str_test_default", "hi") == "hi", "str_test_default failed"
+
+ def test_int_list_settings(self):
+ setting = Setting("test")
+ setting.set("int_list_test1", [0x100])
+ setting.set("int_list_test2", [1, 2])
+ setting.set("int_list_test3", [])
+ assert setting.get_integer_list("int_list_test1") == [0x100], "int_list_test1 failed"
+ assert setting.get_integer_list("int_list_test2") == [1, 2], "int_list_test2 failed"
+ assert setting.get_integer_list("int_list_test3") == [], "int_list_test3 failed"
+ assert setting.get_integer_list("int_list_test_default", [2, 3]), "int_list_test_default failed"
+
+ def test_str_list_settings(self):
+ setting = Setting("test")
+ setting.set("str_list_test1", ["hi"])
+ setting.set("str_list_test2", ["hello", "world"])
+ setting.set("str_list_test3", [])
+ assert setting.get_string_list("str_list_test1") == ["hi"], "str_list_test1 failed"
+ assert setting.get_string_list("str_list_test2") == ["hello", "world"], "str_list_test2 failed"
+ assert setting.get_string_list("str_list_test3") == [], "str_list_test3 failed"
+ assert setting.get_string_list("str_list_test_default", ["hi", "there"]), "str_list_test_default failed"
+
+
+class MetaddataAPI(unittest.TestCase):
+ def test_metadata_basic_types(self):
+ # Core is tested thoroughly through the C++ unit tests here we focus on the python api side
+ md = Metadata(1)
+ assert md.is_integer
+ assert int(md) == 1
+ assert md.value == 1
+
+ md = Metadata(-1, signed=True)
+ assert md.is_signed_integer
+ assert int(md) == -1
+ assert md.value == -1
+ md = Metadata(1, signed=False)
+ assert md.is_unsigned_integer
+ assert int(md) == 1
+ md = Metadata(3.14)
+ assert md.is_float
+ assert float(md) == 3.14
+ assert md.value == 3.14
+
+ md = Metadata("asdf")
+ assert md.is_string
+ assert str(md) == "asdf"
+ assert len(md) == 4
+ assert md.value == "asdf"
+
+ md = Metadata("\x00\x00\x41\x00", raw=True)
+ assert md.is_raw
+ assert len(md) == 4
+ assert str(md) == "\x00\x00\x41\x00"
+ assert md.value == "\x00\x00\x41\x00"
+
+ def test_metadata_compound_types(self):
+ md = Metadata([1, 2, 3])
+ assert md.is_array
+ assert md.value == [1, 2, 3]
+ assert len(md) == 3
+ assert md[0] == 1
+ assert md[1] == 2
+ assert md[2] == 3
+ assert isinstance(list(md), list)
+ md.remove(0)
+ assert len(md) == 2
+ assert md == [2, 3]
+
+ md = Metadata({"a": 1, "b": 2})
+ assert md.is_dict
+ assert len(md) == 2
+ assert md.value == {"a": 1, "b": 2}
+ assert md["a"] == 1
+ assert md["b"] == 2
+ md.remove("a")
+ assert len(md) == 1
+ assert md == {"b": 2}
+
+ def test_metadata_equality(self):
+ assert Metadata(1) == 1
+ assert Metadata(1) != 0
+ assert Metadata(1) == Metadata(1)
+ assert Metadata(1) != Metadata(0)
+
+ assert Metadata(3.14) == 3.14
+ assert Metadata(3.14) == Metadata(3.14)
+ assert Metadata(3.14) != 3.1
+ assert Metadata(3.14) != Metadata(3.1)
+
+ assert Metadata("asdf") == "asdf"
+ assert Metadata("asdf") == Metadata("asdf")
+ assert Metadata("asdf") != "qwer"
+ assert Metadata("asdf") != Metadata("qwer")
+
+ assert Metadata("as\x00df", raw=True) == "as\x00df"
+ assert Metadata("as\x00df", raw=True) == Metadata("as\x00df", raw=True)
+ assert Metadata("as\x00df", raw=True) != "qw\x00er"
+ assert Metadata("as\x00df", raw=True) != Metadata("qw\x00er", raw=True)
+
+ assert Metadata([1, 2, 3]) == [1, 2, 3]
+ assert Metadata([1, 2, 3]) == Metadata([1, 2, 3])
+ assert Metadata([1, 2, 3]) != [1, 2]
+ assert Metadata([1, 2, 3]) != Metadata([1, 2])
+
+ assert Metadata({"a": 1, "b": 2}) == {"a": 1, "b": 2}
+ assert Metadata({"a": 1, "b": 2}) == Metadata({"a": 1, "b": 2})
+ assert Metadata({"a": 1, "b": 2}) != {"a": 1}
+ assert Metadata({"a": 1, "b": 2}) != Metadata({"a": 1})
+
+
+class DemanglerTest(unittest.TestCase):
+ def get_type_string(self, t, n):
+ out = ""
+ if t is not None:
+ out = str(t.get_string_before_name())
+ if len(out) > 1 and out[-1] != ' ':
+ out += " "
+ out += get_qualified_name(n)
+ out += str(t.get_string_after_name())
+ return out
+
+ def test_demangle_gnu3(self):
+ tests = ("__ZN15BinaryNinjaCore12BinaryReader5Read8Ev",
+ "__ZN5QListIP18QAbstractAnimationE18detach_helper_growEii",
+ "__ZN13QStatePrivate22emitPropertiesAssignedEv",
+ "__ZN17QtMetaTypePrivate23QMetaTypeFunctionHelperI14QItemSelectionLb1EE9ConstructEPvPKv",
+ "__ZN18QSharedDataPointerI16QFileInfoPrivateE4dataEv",
+ "__ZN26QAbstractNativeEventFilterD2Ev",
+ "__ZN5QListIP14QAbstractStateE3endEv",
+ "__ZNK15BinaryNinjaCore19ArchitectureWrapper22GetOpcodeDisplayLengthEv",
+ "__ZN15BinaryNinjaCore17ScriptingInstance19SetCurrentSelectionEyy",
+ "__ZL32qt_meta_stringdata_QHistoryState",
+ "__ZN12_GLOBAL__N_114TypeDestructor14DestructorImplI11QStringListLb1EE8DestructEiPv",
+ "__ZN13QGb18030Codec5_nameEv",
+ "__ZN5QListIP7QObjectE6detachEv",
+ "__ZN19QBasicAtomicPointerI9QFreeListI13QMutexPrivateN12_GLOBAL__N_117FreeListConstantsEEE17testAndSetReleaseEPS4_S6_",
+ "__ZN12QJsonPrivate6Parser12reserveSpaceEi",
+ "__ZN20QStateMachinePrivate12endMacrostepEb",
+ "__ZN14QScopedPointerI20QTemporaryDirPrivate21QScopedPointerDeleterIS0_EED2Ev",
+ "__ZN14QVariantIsNullIN12_GLOBAL__N_115CoreTypesFilterEE8delegateI10QMatrix4x4EEbPKT_",
+ "__ZN26QAbstractProxyModelPrivateC2Ev",
+ "__ZNSt3__110__function6__funcIZ26BNWorkerInteractiveEnqueueE4$_16NS_9allocatorIS2_EEFvvEEclEv")
+
+ results = ("int32_t BinaryNinjaCore::BinaryReader::Read8()",
+ "int32_t QList<QAbstractAnimation*>::detach_helper_grow(int32_t, int32_t)",
+ "int32_t QStatePrivate::emitPropertiesAssigned()",
+ "int32_t QtMetaTypePrivate::QMetaTypeFunctionHelper<QItemSelection, true>::Construct(void*, void const*)",
+ "int32_t QSharedDataPointer<QFileInfoPrivate>::data()",
+ "void QAbstractNativeEventFilter::~QAbstractNativeEventFilter()",
+ "int32_t QList<QAbstractState*>::end()",
+ "int32_t BinaryNinjaCore::ArchitectureWrapper::GetOpcodeDisplayLength() const",
+ "int32_t BinaryNinjaCore::ScriptingInstance::SetCurrentSelection(uint64_t, uint64_t)",
+ "qt_meta_stringdata_QHistoryState",
+ "int32_t (anonymous namespace)::TypeDestructor::DestructorImpl<QStringList, true>::Destruct(int32_t, void*)",
+ "int32_t QGb18030Codec::_name()",
+ "int32_t QList<QObject*>::detach()",
+ "int32_t QBasicAtomicPointer<QFreeList<QMutexPrivate, (anonymous namespace)::FreeListConstants> >::testAndSetRelease(QFreeList<QMutexPrivate, (anonymous namespace)::FreeListConstants>*, QFreeList<QMutexPrivate, (anonymous namespace)::FreeListConstants>*)",
+ "int32_t QJsonPrivate::Parser::reserveSpace(int32_t)",
+ "int32_t QStateMachinePrivate::endMacrostep(bool)",
+ "void QScopedPointer<QTemporaryDirPrivate, QScopedPointerDeleter<QTemporaryDirPrivate> >::~QScopedPointer()",
+ "bool QVariantIsNull<(anonymous namespace)::CoreTypesFilter>::delegate<QMatrix4x4>(QMatrix4x4 const*)",
+ "void QAbstractProxyModelPrivate::QAbstractProxyModelPrivate()",
+ "int32_t std::__1::__function::__func<BNWorkerInteractiveEnqueue::$_16, std::__1::allocator<BNWorkerInteractiveEnqueue::$_16>, void ()>::operator()()")
+
+ for i, test in enumerate(tests):
+ t, n = demangle_gnu3(Architecture['x86'], test)
+ assert self.get_type_string(t, n) == results[i]
diff --git a/suite/binaries b/suite/binaries
new file mode 160000
+Subproject 96d08bbb4fdcf1b88c98165fcfe8474659c38d6
diff --git a/suite/generator.py b/suite/generator.py
new file mode 100755
index 00000000..09ee00dc
--- /dev/null
+++ b/suite/generator.py
@@ -0,0 +1,328 @@
+#!/usr/bin/env python2
+import pickle
+import sys
+import os
+import zipfile
+from optparse import OptionParser
+import testcommon
+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 difflib
+from collections import Counter
+
+api_suite_path = os.path.join(os.path.dirname(__file__), {4})
+sys.path.append(api_suite_path)
+import testcommon
+import api_test
+
+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}")
+ pickle_path = os.path.join(os.path.dirname(__file__), "oracle.pkl")
+ try:
+ # Python 2 does not have the encodings option
+ self.oracle_test_data = pickle.load(open(pickle_path, "rb"), encoding='charmap')
+ except TypeError:
+ self.oracle_test_data = pickle.load(open(pickle_path, "rb"))
+ self.verifybuilder = testcommon.VerifyBuilder("{3}")
+
+ def run_binary_test(self, testfile):
+ testname = None
+ with zipfile.ZipFile(os.path.join(api_suite_path, testfile), "r") as zf:
+ testname = zf.namelist()[0]
+ zf.extractall(path=api_suite_path)
+
+ pickle_path = os.path.join(os.path.dirname(__file__), testname + ".pkl")
+ self.assertTrue(pickle_path, "Test pickle doesn't exist")
+ try:
+ # Python 2 does not have the encodings option
+ binary_oracle = pickle.load(open(pickle_path, "rb"), encoding='charmap')
+ except TypeError:
+ binary_oracle = pickle.load(open(pickle_path, "rb"))
+
+ test_builder = testcommon.BinaryViewTestBuilder(testname)
+ for method in test_builder.methods():
+ test = getattr(test_builder, method)()
+ oracle = binary_oracle[method]
+ if test == oracle:
+ continue
+
+ result = getattr(test_builder, method).__doc__
+ result += ":\\n"
+ report = self.report(oracle, test, result)
+ self.assertTrue(report[0], report[1]) # Test does not agree with oracle
+ os.unlink(os.path.join(api_suite_path, 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)
+ runner.run(test_suite)
+"""
+
+
+binary_test_string = """
+ def test_binary__{0}(self):
+ self.run_binary_test('{1}')
+"""
+
+test_string = """
+ def {0}(self):
+ oracle = self.oracle_test_data['{0}']
+ test = self.builder.{0}()
+ report = self.report(oracle, test)
+ self.assertTrue(report[0], report[1]) # Test does not agree with oracle
+"""
+
+verify_string = """
+ def {0}(self):
+ self.assertTrue(self.verifybuilder.{0}(), self.{0}.__doc__)
+"""
+
+
+class OracleTestFile:
+ def __init__(self, filename):
+ self.f = open(filename + ".pkl", "wb")
+ self.pkl = pickle.Pickler(self.f, protocol=2)
+ self.filename = filename
+ self.oracle_test_data = {}
+
+ def add_entry(self, builder, test_name):
+ self.oracle_test_data[test_name] = getattr(builder, test_name)()
+
+ def close(self):
+ self.pkl.dump(self.oracle_test_data)
+ self.f.close()
+
+
+class UnitTestFile:
+ def __init__(self, filename, outdir, test_store):
+ self.filename = filename
+ self.test_store = test_store
+ self.outdir = outdir
+ self.f = open(filename, "wb")
+ self.template = unit_test_template
+ self.tests = ""
+ self.binary_tests = ""
+
+ def close(self):
+ api_path = os.path.relpath(os.path.dirname(__file__), start=self.outdir)
+ api_path = os.path.normpath(api_path)
+ api_path = map(lambda x: '"{0}"'.format(x), api_path.split(os.sep))
+ api_path = '{0}'.format(', '.join(api_path))
+ self.f.write(self.template.format(self.outdir, self.tests, self.binary_tests, self.test_store, api_path).encode('charmap'))
+ self.f.close()
+
+ def add_verify(self, test_name):
+ self.tests += verify_string.format(test_name)
+
+ def add_test(self, test_name):
+ self.tests += test_string.format(test_name)
+
+ def add_binary_test(self, test_store, binary):
+ name = binary[len(test_store):].replace(os.path.sep, "_").replace(".", "_")
+ self.binary_tests += binary_test_string.format(name, binary + ".zip")
+
+
+quiet = False
+def myprint(stuff):
+ if not quiet:
+ print(stuff)
+
+
+def update_progress(complete, total, description, done=False):
+ n = 20
+ maxdesc = 50
+ if total == 0:
+ total, complete = 10, 10
+ if len(description) > maxdesc:
+ description = description[:maxdesc]
+ elif len(description) < maxdesc:
+ description += ' ' * (maxdesc - len(description))
+
+ if not quiet:
+ sys.stdout.write('\r[{0}{1}] {2:10.0f}% - {3}'.format('#' * int(n * (float(complete) / total)), ' ' * (n - int(n * (float(complete) / total))), 100 * float(complete) / total, description))
+ if done:
+ sys.stdout.write("\n")
+
+
+class TestStoreError(Exception):
+ def __init__(self, *args, **kwargs):
+ Exception.__init__(self, *args, **kwargs)
+
+
+def generate(test_store, outdir, exclude_binaries):
+ if not os.path.isdir(os.path.join(os.path.dirname(__file__), test_store)):
+ raise TestStoreError("Specified test store is not a directory")
+
+ unittest = UnitTestFile(os.path.join(outdir, "unit.py"), outdir, test_store)
+ oracle = OracleTestFile(os.path.join(outdir, "oracle"))
+
+ # Generate the tests that don't involve binaries but do involve oracles
+ builder = testcommon.TestBuilder(test_store)
+ tests = builder.methods()
+ for progress, test_name in enumerate(tests):
+ update_progress(progress, len(tests), "Generating test data")
+ oracle.add_entry(builder, test_name)
+ unittest.add_test(test_name)
+ update_progress(len(tests), len(tests), "Generating test data", True)
+
+ # Generate the tests that just verify things work as expected
+ verify = testcommon.VerifyBuilder(test_store)
+ tests = verify.methods()
+ for progress, test_name in enumerate(tests):
+ update_progress(progress, len(tests), "Generating verify data")
+ unittest.add_verify(test_name)
+ update_progress(len(tests), len(tests), "Generating verify data", True)
+
+ # Now generate test that involve binaries
+ allfiles = sorted(testcommon.get_file_list(test_store))
+ for progress, testfile in enumerate(allfiles):
+ oraclefile = None
+ if testfile.endswith(".pkl"):
+ continue
+ elif testfile.endswith(".DS_Store"):
+ continue
+ elif testfile.endswith(".zip"):
+ # We have a zipped binary unzip it so we can rebaseline
+ with zipfile.ZipFile(testfile, "r") as zf:
+ zf.extractall(path = os.path.dirname(__file__))
+ if not os.path.exists(testfile[:-4]):
+ print("Error extracting testfile %s from zip: %s" % (testfile[:-4], testfile))
+ continue
+ oraclefile = testfile[:-4]
+ else:
+ if os.path.exists(testfile + ".zip"):
+ # We've got a binary and zip for that binary just skip it
+ continue
+ # We have a binary that isn't zipped use it as a new test case
+ oraclefile = testfile
+
+ oraclefile_rel = os.path.relpath(oraclefile, start=os.path.dirname(__file__))
+
+ # Now generate the oracle data
+ update_progress(progress, len(allfiles), oraclefile_rel)
+ unittest.add_binary_test(test_store, oraclefile_rel)
+ binary_start_time = time.time()
+ if exclude_binaries:
+ continue
+ test_data = testcommon.BinaryViewTestBuilder(oraclefile_rel)
+ binary_oracle = OracleTestFile(os.path.join(outdir, oraclefile_rel))
+ for method in test_data.methods():
+ binary_oracle.add_entry(test_data, method)
+ binary_oracle.close()
+ print("{0:.2f}".format(time.time() - binary_start_time))
+
+ if not os.path.exists(oraclefile + ".zip"):
+ with zipfile.ZipFile(oraclefile + ".zip", "w") as zf:
+ zf.write(oraclefile, os.path.relpath(oraclefile, start=os.path.dirname(__file__)))
+
+ os.unlink(oraclefile)
+
+ update_progress(len(allfiles), len(allfiles), "Generating binary unit tests complete", True)
+ unittest.close()
+ oracle.close()
+
+
+def main():
+ usage = "usage: %prog [-q] [-x] [-o <dir>] [-i <dir>]"
+ parser = OptionParser(usage=usage)
+ parser.add_option("-q", "--quiet",
+ dest="quiet", action="store_true",
+ default=False, help="Don't print anything")
+ parser.add_option("-x", "--exclude",
+ dest="exclude_binary", action="store_true",
+ default=False, help="Exclude regeneration of binaries")
+ parser.add_option("-o", "--outputdir", default="suite",
+ dest="outputdir", action="store", type="string",
+ help="output directory where the unit.py and oracle.py files will be stored (relative to cwd)")
+ parser.add_option("-i", "--inputdir", default=os.path.join("binaries", "test_corpus"),
+ dest="test_store", action="store", type="string",
+ help="input directory containing the binaries you which to generate unit tests from (relative to this file)")
+
+ options, args = parser.parse_args()
+
+ myprint("[+] INFO: Using test store: %s" % options.test_store)
+ if len(testcommon.get_file_list(options.test_store)) == 0:
+ myprint("ERROR: No files in the test store %s" % testcommon.get_file_list(options.test_store))
+ sys.exit(1)
+
+ myprint("[+] INFO: Generating test store")
+ try:
+ generate(options.test_store, options.outputdir, options.exclude_binary)
+ except TestStoreError as te:
+ myprint("[-] ERROR: Failed to generate test store: %s" % te.message)
+ sys.exit(1)
+ myprint("[+] SUCCESS: Generating test store")
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/suite/pwnadventurez.nes b/suite/pwnadventurez.nes
new file mode 100644
index 00000000..630684e3
--- /dev/null
+++ b/suite/pwnadventurez.nes
Binary files differ
diff --git a/suite/testcommon.py b/suite/testcommon.py
new file mode 100644
index 00000000..285c24b5
--- /dev/null
+++ b/suite/testcommon.py
@@ -0,0 +1,802 @@
+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 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 <in set([blah, blah, blah])>
+def fixSet(string):
+ # Apply regular expression
+ splitList = (re.split(r"((?<=<in set\(\[).*(?=\]\)>))", string))
+ if len(splitList) > 1:
+ return splitList[0] + ', '.join(sorted(splitList[1].split(', '))) + splitList[2]
+ else:
+ return string
+
+
+def get_file_list(test_store_rel):
+ test_store = os.path.join(os.path.dirname(__file__), test_store_rel)
+ all_files = []
+ 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
+ return type_string
+
+class Builder(object):
+ def __init__(self, test_store):
+ self.test_store = test_store
+ # binja.log.log_to_stdout(binja.LogLevel.DebugLog) # Uncomment for more info
+
+ def methods(self):
+ methodnames = []
+ for methodname, method in inspect.getmembers(self, predicate=inspect.ismethod):
+ if methodname.startswith("test_"):
+ methodnames.append(methodname)
+ return methodnames
+
+ def unpackage_file(self, filename):
+ path = os.path.join(os.path.dirname(__file__), self.test_store, filename)
+ if not os.path.exists(path):
+ with zipfile.ZipFile(path + ".zip", "r") as zf:
+ zf.extractall(path = os.path.dirname(__file__))
+ assert os.path.exists(path)
+ return os.path.relpath(path)
+
+
+class BinaryViewTestBuilder(Builder):
+ """ The BinaryViewTestBuilder is for test that are verified against a binary.
+ The tests are first run on your dev machine to base line then run again
+ on the build machine to verify they are correct.
+
+ - Function that are tests should start with 'test_'
+ - Function doc string used as 'on error' message
+ - Should return: list of strings
+ """
+ def __init__(self, filename):
+ self.filename = os.path.join(os.path.dirname(__file__), filename)
+ self.bv = BinaryViewType.get_view_of_file(self.filename)
+ if self.bv is None:
+ print("%s is not an executable format" % filename)
+ return
+
+ def test_available_types(self):
+ """Available types don't match"""
+ return ["Available Type: " + x.name for x in BinaryView(FileMetadata()).open(self.filename).available_view_types]
+
+ def test_function_starts(self):
+ """Function starts list doesnt match"""
+ 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"""
+ 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"""
+ 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)"""
+ bblist = []
+ for func in self.bv.functions:
+ for bb in func.basic_blocks:
+ bblist.append("basic block {} start: ".format(str(bb)) + hex(bb.start) + ' end: ' + hex(bb.end) + ' undetermined outgoing edges: ' + str(bb.has_undetermined_outgoing_edges))
+ 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 fixOutput(bblist)
+
+ def test_function_low_il_basic_blocks(self):
+ """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 fixOutput(ilbblist)
+
+ def test_function_med_il_basic_blocks(self):
+ """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 fixOutput(ilbblist)
+
+ def test_symbols(self):
+ """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 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"""
+ retinfo = []
+ for func in self.bv.functions:
+ for bb in func.low_level_il.basic_blocks:
+ for ins in bb:
+ retinfo.append("MLIL: " + str(ins.medium_level_il))
+ retinfo.append("Mapped MLIL: " + str(ins.mapped_medium_level_il))
+ retinfo.append("Value: " + str(ins.value))
+ retinfo.append("Possible Values: " + str(ins.possible_values))
+ retinfo.append("Prefix operands: " + str(ins.prefix_operands))
+ 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 fixOutput(retinfo)
+
+ def test_low_il_ssa(self):
+ """LLIL ssa produced different output"""
+ retinfo = []
+ for func in self.bv.functions:
+ func = func.low_level_il
+ for reg_name in self.bv.arch.regs:
+ reg = binja.SSARegister(reg_name, 1)
+ retinfo.append("Reg {} SSA definition: ".format(reg_name) + str(func.get_ssa_reg_definition(reg)))
+ retinfo.append("Reg {} SSA uses: ".format(reg_name) + str(func.get_ssa_reg_uses(reg)))
+ retinfo.append("Reg {} SSA value: ".format(reg_name) + str(func.get_ssa_reg_value(reg)))
+ for flag_name in self.bv.arch.flags:
+ flag = binja.SSAFlag(flag_name, 1)
+ retinfo.append("Flag {} SSA uses: ".format(flag_name) + str(func.get_ssa_flag_uses(flag)))
+ retinfo.append("Flag {} SSA value: ".format(flag_name) + str(func.get_ssa_flag_value(flag)))
+ for bb in func.basic_blocks:
+ for ins in bb:
+ tempind = func.get_non_ssa_instruction_index(ins.instr_index)
+ retinfo.append("Non-SSA instruction index: " + str(tempind))
+ 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 fixOutput(retinfo)
+
+ def test_med_il_instructions(self):
+ """MLIL instructions produced different output"""
+ retinfo = []
+ for func in self.bv.functions:
+ for bb in func.medium_level_il.basic_blocks:
+ for ins in bb:
+ retinfo.append("Expression type: " + str(ins.expr_type))
+ 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(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 fixOutput(retinfo)
+
+ def test_med_il_vars(self):
+ """Function med_il_vars doesn't match"""
+ varlist = []
+ for func in self.bv.functions:
+ func = func.medium_level_il
+ for bb in func.basic_blocks:
+ for instruction in bb:
+ instruction = instruction.ssa_form
+ for var in (instruction.vars_read + instruction.vars_written):
+ 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: " + fixSet(str(instruction.get_ssa_var_possible_values(var))))
+ varlist.append("SSA var version: " + str(instruction.get_ssa_var_version))
+ return fixOutput(varlist)
+
+ def test_function_stack(self):
+ """Function stack produced different output"""
+ funcinfo = []
+ for func in self.bv.functions:
+ func.stack_adjustment = func.stack_adjustment
+ func.reg_stack_adjustments = func.reg_stack_adjustments
+ func.create_user_stack_var(0, binja.Type.int(4), "testuservar")
+ func.create_auto_stack_var(4, binja.Type.int(4), "testautovar")
+
+ sl = func.stack_layout
+ for i in range(len(sl)):
+ funcinfo.append("Stack position {}: ".format(i) + str(sl[i]))
+
+ funcinfo.append("Stack content sample: " + str(func.get_stack_contents_at(func.start + 0x10, 0, 0x10)))
+ funcinfo.append("Stack content range sample: " + str(func.get_stack_contents_after(func.start + 0x10, 0, 0x10)))
+ 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("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"""
+ funcinfo = []
+ for func in self.bv.functions:
+ func.comment = "testcomment " + func.name
+ func.name = func.name
+ func.can_return = func.can_return
+ func.function_type = func.function_type
+ func.return_type = func.return_type
+ func.return_regs = func.return_regs
+ func.calling_convention = func.calling_convention
+ func.parameter_vars = func.parameter_vars
+ func.has_variable_arguments = func.has_variable_arguments
+ func.analysis_skipped = func.analysis_skipped
+ func.clobbered_regs = func.clobbered_regs
+ func.set_user_instr_highlight(func.start, binja.highlight.HighlightColor(red=0xff, blue=0xff, green=0))
+ func.set_auto_instr_highlight(func.start, binja.highlight.HighlightColor(red=0xff, blue=0xfe, green=0))
+
+ for var in func.vars:
+ funcinfo.append("Function {} var: ".format(func.name) + str(var))
+
+ for branch in func.indirect_branches:
+ funcinfo.append("Function {} indirect branch: ".format(func.name) + str(branch))
+ funcinfo.append("Function {} session data: ".format(func.name) + str(func.session_data))
+ funcinfo.append("Function {} analysis perf length: ".format(func.name) + str(len(func.analysis_performance_info)))
+ for cr in func.clobbered_regs:
+ funcinfo.append("Function {} clobbered reg: ".format(func.name) + str(cr))
+ funcinfo.append("Function {} explicitly defined type: ".format(func.name) + str(func.explicitly_defined_type))
+ funcinfo.append("Function {} needs update: ".format(func.name) + str(func.needs_update))
+ funcinfo.append("Function {} global pointer value: ".format(func.name) + str(func.global_pointer_value))
+ funcinfo.append("Function {} comment: ".format(func.name) + str(func.comment))
+ funcinfo.append("Function {} too large: ".format(func.name) + str(func.too_large))
+ funcinfo.append("Function {} analysis skipped: ".format(func.name) + str(func.analysis_skipped))
+ funcinfo.append("Function {} first ins LLIL: ".format(func.name) + str(func.get_low_level_il_at(func.start)))
+ funcinfo.append("Function {} LLIL exit test: ".format(func.name) + str(func.get_low_level_il_exits_at(func.start+0x100)))
+ funcinfo.append("Function {} regs read test: ".format(func.name) + str(func.get_regs_read_by(func.start)))
+ funcinfo.append("Function {} regs written test: ".format(func.name) + str(func.get_regs_written_by(func.start)))
+ funcinfo.append("Function {} stack var test: ".format(func.name) + str(func.get_stack_vars_referenced_by(func.start)))
+ funcinfo.append("Function {} constant reference test: ".format(func.name) + str(func.get_constants_referenced_by(func.start)))
+ funcinfo.append("Function {} first ins lifted IL: ".format(func.name) + str(func.get_lifted_il_at(func.start)))
+ funcinfo.append("Function {} flags read by lifted IL ins: ".format(func.name) + str(func.get_flags_read_by_lifted_il_instruction(0)))
+ funcinfo.append("Function {} flags written by lifted IL ins: ".format(func.name) + str(func.get_flags_written_by_lifted_il_instruction(0)))
+ funcinfo.append("Function {} create graph: ".format(func.name) + str(func.create_graph()))
+ funcinfo.append("Function {} indirect branches test: ".format(func.name) + str(func.get_indirect_branches_at(func.start+0x10)))
+ funcinfo.append("Function {} test instr highlight: ".format(func.name) + str(func.get_instr_highlight(func.start)))
+ for token in func.get_type_tokens():
+ token = str(token)
+ token = remove_low_confidence(token)
+ funcinfo.append("Function {} type token: ".format(func.name) + str(token))
+ return fixOutput(funcinfo)
+
+ def test_BinaryView(self):
+ """BinaryView produced different results"""
+ retinfo = []
+
+ for type in self.bv.types.items():
+ retinfo.append("BV Type: " + str(type))
+ for segment in sorted([str(i) for i in self.bv.segments]):
+ retinfo.append("BV segment: " + str(segment))
+ for section in sorted(self.bv.sections):
+ retinfo.append("BV section: " + str(section))
+ for allrange in self.bv.allocated_ranges:
+ retinfo.append("BV allocated range: " + str(allrange))
+ retinfo.append("Session Data: " + str(self.bv.session_data))
+ for var in self.bv.data_vars:
+ retinfo.append("BV data var: " + str(var))
+ retinfo.append("BV Entry function: " + str(self.bv.entry_function))
+ for i in self.bv:
+ retinfo.append("BV function: " + str(i))
+ 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 fixOutput(retinfo)
+
+
+class TestBuilder(Builder):
+ """ 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.
+
+ - Function that are tests should start with 'test_'
+ - Function doc string used as 'on error' message
+ - Should return: list of strings
+ """
+
+ def test_BinaryViewType_list(self):
+ """BinaryViewType list doesnt match"""
+ return ["BinaryViewType: " + x.name for x in binja.BinaryViewType.list]
+
+ def test_Architecture_list(self):
+ """Architecture list doesnt match"""
+ return ["Arch name: " + x.name for x in binja.Architecture.list]
+
+ def test_Assemble(self):
+ """unexpected assemble result"""
+ result = []
+ # success cases
+
+ 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
+ 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):
+ """Architecture failure"""
+ if not os.path.exists(os.path.join(os.path.expanduser("~"), '.binaryninja', 'plugins', 'nes.py')):
+ return [""]
+
+ retinfo = []
+ file_name = os.path.join(self.test_store, "..", "pwnadventurez.nes")
+ bv = binja.BinaryViewType["NES Bank 0"].open(file_name)
+
+ for i in bv.platform.arch.calling_conventions:
+ retinfo.append("Custom arch calling convention: " + str(i))
+ for i in bv.platform.arch.full_width_regs:
+ retinfo.append("Custom arch full width reg: " + str(i))
+
+ reg = binja.RegisterValue()
+ retinfo.append("Reg entry value: " + str(reg.entry_value(bv.platform.arch, 'x')))
+ retinfo.append("Reg constant: " + str(reg.constant(0xfe)))
+ retinfo.append("Reg constant pointer: " + str(reg.constant_ptr(0xcafebabe)))
+ retinfo.append("Reg stack frame offset: " + str(reg.stack_frame_offset(0x10)))
+ retinfo.append("Reg imported address: " + str(reg.imported_address(0xdeadbeef)))
+ retinfo.append("Reg return address: " + str(reg.return_address()))
+
+ bv.update_analysis_and_wait()
+ for func in bv.functions:
+ for bb in func.low_level_il.basic_blocks:
+ for ins in bb:
+ retinfo.append("Instruction info: " + str(bv.platform.arch.get_instruction_info(0x10, ins.address)))
+ retinfo.append("Instruction test: " + str(bv.platform.arch.get_instruction_text(0x10, ins.address)))
+ retinfo.append("Instruction: " + str(ins))
+ return retinfo
+
+ def test_Function(self):
+ """Function produced different result"""
+ inttype = binja.Type.int(4)
+ testfunction = binja.Type.function(inttype, [inttype, inttype, inttype])
+ return ["Test_function params: " + str(testfunction.parameters), "Test_function pointer: " + str(testfunction.pointer(binja.Architecture["x86"], testfunction))]
+
+ def test_Struct(self):
+ """Struct produced different result"""
+ retinfo = []
+ inttype = binja.Type.int(4)
+ struct = binja.Structure()
+ struct.a = 1
+ struct.insert(0, inttype)
+ struct.append(inttype)
+ struct.replace(0, inttype)
+ struct.remove(1)
+ for i in struct.members:
+ retinfo.append("Struct member: " + str(i))
+ retinfo.append("Struct width: " + str(struct.width))
+ struct.width = 16
+ retinfo.append("Struct width after adjustment: " + str(struct.width))
+ retinfo.append("Struct alignment: " + str(struct.alignment))
+ struct.alignment = 8
+ retinfo.append("Struct alignment after adjustment: " + str(struct.alignment))
+ retinfo.append("Struct packed: " + str(struct.packed))
+ struct.packed = 1
+ retinfo.append("Struct packed after adjustment: " + str(struct.packed))
+ retinfo.append("Struct type: " + str(struct.type))
+ return retinfo
+
+ def test_Enumeration(self):
+ """Enumeration produced different result"""
+ retinfo = []
+ inttype = binja.Type.int(4)
+ enum = binja.Enumeration()
+ enum.a = 1
+ enum.append("a", 1)
+ enum.append("b", 2)
+ enum.replace(0, "a", 2)
+ enum.remove(0)
+ retinfo.append(str(enum))
+ retinfo.append(str((enum == enum) and not (enum != enum)))
+ return retinfo
+
+ def test_Types(self):
+ """Types produced different result"""
+ file_name = self.unpackage_file("helloworld")
+ bv = binja.BinaryViewType.get_view_of_file(file_name)
+
+ preprocessed = binja.preprocess_source("""
+ #ifdef nonexistant
+ int foo = 1;
+ long long foo1 = 1;
+ #else
+ int bar = 2;
+ long long bar1 = 2;
+ #endif
+ """)
+ 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)
+
+ tokens = inttype.get_tokens() + inttype.get_tokens_before_name() + inttype.get_tokens_after_name()
+ namedtype = binja.NamedTypeReference()
+
+ retinfo = []
+ for i in range(len(typelist.variables)):
+ for j in typelist.variables.popitem():
+ retinfo.append("Type: " + str(j))
+ retinfo.append("Named Type: " + str(namedtype))
+
+ 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 = self.unpackage_file("helloworld")
+ bin_info_path = os.path.join(os.path.dirname(__file__), '..', 'python', 'examples', 'bin_info.py')
+ result = subprocess.Popen(["python", bin_info_path, file_name], stdout=subprocess.PIPE).communicate()[0]
+ # normalize line endings and path sep
+ 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"""
+ file_name = self.unpackage_file("helloworld")
+ bv = binja.BinaryViewType['ELF'].open(file_name)
+ disass = bv.linear_disassembly
+ retinfo = []
+ for i in disass:
+ i = str(i)
+ i = remove_low_confidence(i)
+ retinfo.append(i)
+ return retinfo
+
+ def test_partial_register_dataflow(self):
+ """partial_register_dataflow produced different results"""
+ file_name = self.unpackage_file("partial_register_dataflow")
+ result = []
+ reg_list = ['ch', 'cl', 'ah', 'edi', 'al', 'cx', 'ebp', 'ax', 'edx', 'ebx', 'esp', 'esi', 'dl', 'dh', 'di', 'bl', 'bh', 'eax', 'dx', 'bx', 'ecx', 'sp', 'si']
+ bv = binja.BinaryViewType.get_view_of_file(file_name)
+ for func in bv.functions:
+ llil = func.low_level_il
+ for i in range(0, llil.__len__()-1):
+ for x in reg_list:
+ result.append("LLIL:" + str(i).replace('L', '') + ":" + x + ":" + str(llil[i].get_reg_value(x)).replace('L', ''))
+ result.append("LLIL:" + str(i).replace('L', '') + ":" + x + ":" + str(llil[i].get_possible_reg_values(x)).replace('L', ''))
+ result.append("LLIL:" + str(i).replace('L', '') + ":" + x + ":" + str(llil[i].get_reg_value_after(x)).replace('L', ''))
+ result.append("LLIL:" + str(i).replace('L', '') + ":" + x + ":" + str(llil[i].get_possible_reg_values_after(x)).replace('L', ''))
+ bv.file.close()
+ del bv
+ return result
+
+
+ def test_low_il_stack(self):
+ """LLIL stack produced different output"""
+ file_name = self.unpackage_file("jumptable_reordered")
+ bv = binja.BinaryViewType.get_view_of_file(file_name)
+ reg_list = ['ch', 'cl', 'ah', 'edi', 'al', 'cx', 'ebp', 'ax', 'edx', 'ebx', 'esp', 'esi', 'dl', 'dh', 'di', 'bl', 'bh', 'eax', 'dx', 'bx', 'ecx', 'sp', 'si']
+ flag_list = ['c', 'p', 'a', 'z', 's', 'o']
+ retinfo = []
+ for func in bv.functions:
+ for bb in func.low_level_il.basic_blocks:
+ for ins in bb:
+ retinfo.append("LLIL first stack element: " + str(ins.get_stack_contents(0,1)))
+ 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)))
+ return fixOutput(retinfo)
+
+ def test_med_il_stack(self):
+ """MLIL stack produced different output"""
+ file_name = self.unpackage_file("jumptable_reordered")
+ bv = binja.BinaryViewType.get_view_of_file(file_name)
+ reg_list = ['ch', 'cl', 'ah', 'edi', 'al', 'cx', 'ebp', 'ax', 'edx', 'ebx', 'esp', 'esi', 'dl', 'dh', 'di', 'bl', 'bh', 'eax', 'dx', 'bx', 'ecx', 'sp', 'si']
+ flag_list = ['c', 'p', 'a', 'z', 's', 'o']
+ retinfo = []
+ for func in bv.functions:
+ for bb in func.medium_level_il.basic_blocks:
+ for ins in bb:
+ retinfo.append("MLIL stack begin var: " + str(ins.get_var_for_stack_location(0)))
+ retinfo.append("MLIL first stack element: " + str(ins.get_stack_contents(0, 1)))
+ 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)) + 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)) + 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))))
+ return fixOutput(retinfo)
+
+ def test_events(self):
+ """Event failure"""
+ file_name = self.unpackage_file("helloworld")
+ bv = binja.BinaryViewType['ELF'].open(file_name)
+ bv.update_analysis_and_wait()
+
+ results = []
+
+ def simple_complete(self):
+ results.append("analysis complete")
+ evt = binja.AnalysisCompletionEvent(bv, simple_complete)
+
+ class NotifyTest(binja.BinaryDataNotification):
+ def data_written(self, view, offset, length):
+ results.append("data written: offset {0} length {1}".format(hex(offset), hex(length)))
+
+ def data_inserted(self, view, offset, length):
+ results.append("data inserted: offset {0} length {1}".format(hex(offset), hex(length)))
+
+ def data_removed(self, view, offset, length):
+ results.append("data removed: offset {0} length {1}".format(hex(offset), hex(length)))
+
+ def function_added(self, view, func):
+ results.append("function added: {0}".format(func.name))
+
+ def function_removed(self, view, func):
+ results.append("function removed: {0}".format(func.name))
+
+ def data_var_added(self, view, var):
+ results.append("data var added: {0}".format(hex(var.address)))
+
+ def data_var_removed(self, view, var):
+ results.append("data var removed: {0}".format(hex(var.address)))
+
+ def string_found(self, view, string_type, offset, length):
+ results.append("string found: offset {0} length {1}".format(hex(offset), hex(length)))
+
+ def string_removed(self, view, string_type, offset, length):
+ results.append("string removed: offset {0} length {1}".format(hex(offset), hex(length)))
+
+ def type_defined(self, view, name, type):
+ results.append("type defined: {0}".format(name))
+
+ def type_undefined(self, view, name, type):
+ results.append("type undefined: {0}".format(name))
+
+ test = NotifyTest()
+ 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)
+ bv.undefine_type(type_id)
+
+ bv.update_analysis_and_wait()
+
+ bv.insert(sacrificial_addr, b"AAAA")
+ bv.update_analysis_and_wait()
+
+ bv.define_data_var(sacrificial_addr, binja.types.Type.int(4))
+ bv.update_analysis_and_wait()
+
+ bv.write(sacrificial_addr, b"BBBB")
+ bv.update_analysis_and_wait()
+
+ bv.add_function(sacrificial_addr)
+ bv.update_analysis_and_wait()
+
+ bv.remove_function(bv.get_function_at(sacrificial_addr))
+ bv.update_analysis_and_wait()
+
+ bv.undefine_data_var(sacrificial_addr)
+ bv.update_analysis_and_wait()
+
+ bv.remove(sacrificial_addr, 4)
+ bv.update_analysis_and_wait()
+
+ bv.unregister_notification(test)
+
+ return fixOutput(sorted(results))
+
+
+class VerifyBuilder(Builder):
+ """ The VerifyBuilder is for tests that verify
+ Binary Ninja against expected output.
+
+ - Function that are tests should start with 'test_'
+ - Function doc string used as 'on error' message
+ - Should return: boolean
+ """
+
+ def __init__(self, test_store):
+ super(VerifyBuilder, self).__init__(test_store)
+
+ def get_functions(self, bv):
+ return [x.start for x in bv.functions]
+
+ def get_comments(self, bv):
+ return bv.functions[0].comments
+
+ def test_verify_BNDB_round_trip(self):
+ """Binary Ninja Database output doesn't match its input"""
+ # This will test Binja's ability to save and restore databases
+ # By:
+ # - Creating a binary view
+ # - Make modification that impact the database
+ # - Record those modification
+ # - Save the database
+ # - Restore the datbase
+ # - Validate that the modifications are present
+ file_name = self.unpackage_file("helloworld")
+ try:
+ bv = binja.BinaryViewType['ELF'].open(file_name)
+ bv.update_analysis_and_wait()
+ # Make some modifications to the binary view
+
+ # Add a comment
+ bv.functions[0].set_comment(bv.functions[0].start, "Function start")
+ # Add a new function
+ bv.add_function(bv.functions[0].start + 4)
+ temp_name = next(tempfile._get_candidate_names()) + ".bndb"
+
+ comments = self.get_comments(bv)
+ functions = self.get_functions(bv)
+ bv.create_database(temp_name)
+ bv.file.close()
+ del bv
+
+ bv = binja.FileMetadata(temp_name).open_existing_database(temp_name).get_view_of_type('ELF')
+ bv.update_analysis_and_wait()
+ bndb_functions = self.get_functions(bv)
+ bndb_comments = self.get_comments(bv)
+ # force windows to close the handle to the bndb that we want to delete
+ bv.file.close()
+ del bv
+ return [str(functions == bndb_functions and comments == bndb_comments)]
+ finally:
+ os.unlink(temp_name)