diff options
| author | Rusty Wagner <rusty@vector35.com> | 2018-07-26 16:02:26 -0400 |
|---|---|---|
| committer | Rusty Wagner <rusty@vector35.com> | 2018-07-26 16:18:02 -0400 |
| commit | 6eb3234d924d870641ee30c4263437f1d8a8d5c7 (patch) | |
| tree | b64815c5e0a2c3b1a10a3e3dcab4c786fdd85c34 /suite | |
| parent | c5c93fc82b8929d04f62d241ca50228de60fa5f4 (diff) | |
| parent | 1f986c2698ff9df6d42429b1b7699842223634e5 (diff) | |
Merge branch 'dev' into test_stack_adjust
Diffstat (limited to 'suite')
| -rw-r--r-- | suite/api_test.py | 221 | ||||
| m--------- | suite/binaries | 0 | ||||
| -rwxr-xr-x | suite/generator.py | 328 | ||||
| -rw-r--r-- | suite/pwnadventurez.nes | bin | 0 -> 262160 bytes | |||
| -rw-r--r-- | suite/testcommon.py | 802 |
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 Binary files differnew file mode 100644 index 00000000..630684e3 --- /dev/null +++ b/suite/pwnadventurez.nes 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) |
