diff options
Diffstat (limited to 'suite')
| -rw-r--r-- | suite/api_test.py | 43 | ||||
| m--------- | suite/binaries | 0 | ||||
| -rwxr-xr-x | suite/generator.py | 143 | ||||
| -rw-r--r-- | suite/oracle.pkl | bin | 1969636 -> 440689 bytes | |||
| -rw-r--r-- | suite/testcommon.py | 748 | ||||
| -rw-r--r-- | suite/unit.py | 206 |
6 files changed, 502 insertions, 638 deletions
diff --git a/suite/api_test.py b/suite/api_test.py index a162d26a..740f88e3 100644 --- a/suite/api_test.py +++ b/suite/api_test.py @@ -1,17 +1,10 @@ import unittest -import time import platform import os -import tempfile -import zipfile -import urllib -import subprocess -import getpass from binaryninja.setting import Setting from binaryninja.metadata import Metadata from binaryninja.demangle import demangle_gnu3, get_qualified_name from binaryninja.architecture import Architecture -from binaryninja.binaryview import BinaryViewType class SettingsAPI(unittest.TestCase): @@ -226,39 +219,3 @@ class DemanglerTest(unittest.TestCase): for i, test in enumerate(tests): t, n = demangle_gnu3(Architecture['x86'], test) assert self.get_type_string(t, n) == results[i] - - -class TimingTest(unittest.TestCase): - def unpackage_file(self, file): - if not os.path.exists(file): - with zipfile.ZipFile(file + ".zip", "r") as zf: - zf.extractall() - assert os.path.exists(file) - - def test_analysis_time(self): - if platform.system() != "Linux" or getpass.getuser() != "jenkins": - return - - start_time = time.time() - file_names = [os.path.join(os.path.dirname(__file__), "binaries", "quick3dcoreplugin.dll"), - os.path.join(os.path.dirname(__file__), "binaries", "md5"), - os.path.join(os.path.dirname(__file__), "binaries", "ls")] - - for file_name in file_names: - temp_name = next(tempfile._get_candidate_names()) + ".bndb" - self.unpackage_file(file_name) - try: - bv = BinaryViewType.get_view_of_file(file_name) - bv.create_database(temp_name) - bv.file.close() - bv = BinaryViewType.get_view_of_file(temp_name) - bv.file.close() - finally: - if os.path.exists(file_name): - os.unlink(file_name) - if os.path.exists(temp_name): - os.unlink(temp_name) - - time_s = time.time() - start_time - commit = subprocess.check_output(["git", "rev-parse", "HEAD"])[:-1] - conn = urllib.urlopen("https://script.google.com/macros/s/AKfycbxrZtlgLaWt3l95_7RyH9ceDdFIBm0VaR6jG1-4UDt6CFFCFzQ/exec?BuildID=%s&Test1=%.2f" % (commit, time_s)) diff --git a/suite/binaries b/suite/binaries -Subproject 7d5d207de812ccd7c4648331b00fa936ef0a82c +Subproject 96d08bbb4fdcf1b88c98165fcfe8474659c38d6 diff --git a/suite/generator.py b/suite/generator.py index 347d0d84..09ee00dc 100755 --- a/suite/generator.py +++ b/suite/generator.py @@ -10,41 +10,99 @@ 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 binaryninja import api_test -import difflib +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(os.path.join("{0}", "oracle.pkl"), "rUb"), errors="ignore") + # 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(os.path.join("{0}", "oracle.pkl"), "rU")) + 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(testfile, "r") as zf: + with zipfile.ZipFile(os.path.join(api_suite_path, testfile), "r") as zf: testname = zf.namelist()[0] - zf.extractall() + zf.extractall(path=api_suite_path) - self.assertTrue(os.path.exists(testname + ".pkl"), "Test pickle doesn't exist") + 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(testname + ".pkl", "rUb"), errors="ignore") + # 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(testname + ".pkl", "rU")) + binary_oracle = pickle.load(open(pickle_path, "rb")) - test_builder = testcommon.BinaryViewTestBuilder(testname, "{3}") + test_builder = testcommon.BinaryViewTestBuilder(testname) for method in test_builder.methods(): test = getattr(test_builder, method)() oracle = binary_oracle[method] @@ -53,22 +111,15 @@ class TestBinaryNinjaAPI(unittest.TestCase): result = getattr(test_builder, method).__doc__ result += ":\\n" - d = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in d.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\\n' - skipped_lines = 0 - delta = delta.replace('\\n', '') - result += delta + '\\n' - self.assertTrue(False, result) - os.unlink(testname) + 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)) @@ -81,11 +132,13 @@ 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}() - self.assertTrue(oracle == test, "%s:\\n'%s' does not equal\\n'%s'" % (self.builder.{0}.__doc__, oracle, test)) + report = self.report(oracle, test) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle """ verify_string = """ @@ -97,7 +150,7 @@ verify_string = """ class OracleTestFile: def __init__(self, filename): self.f = open(filename + ".pkl", "wb") - self.pkl = pickle.Pickler(self.f) + self.pkl = pickle.Pickler(self.f, protocol=2) self.filename = filename self.oracle_test_data = {} @@ -120,7 +173,11 @@ class UnitTestFile: self.binary_tests = "" def close(self): - self.f.write(self.template.format(self.outdir, self.tests, self.binary_tests, self.test_store).encode('utf-8')) + 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): @@ -135,8 +192,6 @@ class UnitTestFile: quiet = False - - def myprint(stuff): if not quiet: print(stuff) @@ -164,7 +219,7 @@ class TestStoreError(Exception): def generate(test_store, outdir, exclude_binaries): - if not os.path.isdir(test_store): + 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) @@ -193,10 +248,12 @@ def generate(test_store, outdir, exclude_binaries): 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() + 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 @@ -208,14 +265,16 @@ def generate(test_store, outdir, exclude_binaries): # 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) - unittest.add_binary_test(test_store, oraclefile) + 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, test_store) - binary_oracle = OracleTestFile(oraclefile) + 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() @@ -223,7 +282,7 @@ def generate(test_store, outdir, exclude_binaries): if not os.path.exists(oraclefile + ".zip"): with zipfile.ZipFile(oraclefile + ".zip", "w") as zf: - zf.write(oraclefile) + zf.write(oraclefile, os.path.relpath(oraclefile, start=os.path.dirname(__file__))) os.unlink(oraclefile) @@ -243,17 +302,13 @@ def main(): 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") - parser.add_option("-i", "--inputdir", default=os.path.join("suite", "binaries", "test_corpus"), + 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") + help="input directory containing the binaries you which to generate unit tests from (relative to this file)") options, args = parser.parse_args() - if not os.path.exists(os.path.join(os.getcwd(), 'suite')): - print("Error: Please run this script from the binaryninja-api root directory") - sys.exit(1) - 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)) diff --git a/suite/oracle.pkl b/suite/oracle.pkl Binary files differindex bf1a4b3b..f0be17d4 100644 --- a/suite/oracle.pkl +++ b/suite/oracle.pkl diff --git a/suite/testcommon.py b/suite/testcommon.py index 4e004630..285c24b5 100644 --- a/suite/testcommon.py +++ b/suite/testcommon.py @@ -1,29 +1,74 @@ import tempfile import pickle import os +import sys import zipfile import inspect import binaryninja as binja from binaryninja.binaryview import BinaryViewType, BinaryView from binaryninja.filemetadata import FileMetadata import subprocess -import traceback -import types +import re -def get_file_list(test_store): +# 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 - self.examples_dir = os.path.join(self.test_store, "..", "..", "..", "python", "examples") + # binja.log.log_to_stdout(binja.LogLevel.DebugLog) # Uncomment for more info def methods(self): methodnames = [] @@ -32,11 +77,13 @@ class Builder(object): methodnames.append(methodname) return methodnames - def unpackage_file(self, file): - if not os.path.exists(file): - with zipfile.ZipFile(file + ".zip", "r") as zf: - zf.extractall() - assert os.path.exists(file) + 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): @@ -48,61 +95,72 @@ class BinaryViewTestBuilder(Builder): - Function doc string used as 'on error' message - Should return: list of strings """ - def __init__(self, filename, test_store): - self.filename = filename - self.bv = BinaryViewType.get_view_of_file(filename) + 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 [x.name for x in BinaryView(FileMetadata()).open(self.filename).available_view_types] + 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""" - return [hex(x.start) for x in self.bv.functions] + result = [] + for x in self.bv.functions: + result.append("Function start: " + hex(x.start)) + return fixOutput(result) def test_function_symbol_names(self): """Function.symbol.name list doesnt match""" - return [x.symbol.name + ' ' + str(x.symbol.type) + ' ' + hex(x.symbol.address) for x in self.bv.functions] + result = [] + for x in self.bv.functions: + result.append("Symbol: " + x.symbol.name + ' ' + str(x.symbol.type) + ' ' + hex(x.symbol.address)) + return fixOutput(result) def test_function_can_return(self): """Function.can_return list doesnt match""" - return [x.symbol.name + ' ' + str(x.symbol.type) + ' ' + hex(x.symbol.address) + ' ' + str(bool(x.can_return)) for x in self.bv.functions] + result = [] + for x in self.bv.functions: + result.append("function name: " + x.symbol.name + ' type: ' + str(x.symbol.type) + ' address: ' + hex(x.symbol.address) + ' can_return: ' + str(bool(x.can_return))) + return fixOutput(result) def test_function_basic_blocks(self): """Function basic_block list doesnt match (start, end, has_undetermined_outgoing_edges)""" bblist = [] for func in self.bv.functions: for bb in func.basic_blocks: - bblist.append(hex(bb.start) + ' ' + hex(bb.end) + ' ' + str(bb.has_undetermined_outgoing_edges)) - return bblist + 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""" + """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(hex(bb.start) + ' ' + hex(bb.end) + ' ' + str(len(bb.outgoing_edges))) - return ilbblist - + 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""" + """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(hex(bb.start) + ' ' + hex(bb.end) + ' ' + str(len(bb.outgoing_edges))) - return ilbblist + 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 [str(i) for i in sorted(self.bv.symbols)] + """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 [x.value + ' ' + str(x.type) + ' ' + hex(x.start) for x in self.bv.strings] - + return fixOutput(["String: " + str(x.value) + ' type: ' + str(x.type) + ' at: ' + hex(x.start) for x in self.bv.strings]) def test_low_il_instructions(self): """LLIL instructions produced different output""" @@ -110,42 +168,38 @@ class BinaryViewTestBuilder(Builder): for func in self.bv.functions: for bb in func.low_level_il.basic_blocks: for ins in bb: - retinfo.append(str(ins.medium_level_il)) - retinfo.append(str(ins.mapped_medium_level_il)) - retinfo.append(str(ins.value)) - retinfo.append(str(ins.possible_values)) - retinfo.append(str(ins.prefix_operands)) - retinfo.append(str(ins.postfix_operands)) - retinfo.append(str(ins.ssa_form)) - retinfo.append(str(ins.non_ssa_form)) - - return retinfo + 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 = [] - reg_list = [binja.SSARegister(i,1) for i in self.bv.arch.regs] - flag_list = [binja.SSAFlag(i,1) for i in self.bv.arch.flags] for func in self.bv.functions: func = func.low_level_il - for reg in reg_list: - retinfo.append(str(func.get_ssa_reg_definition(reg))) - retinfo.append(str(func.get_ssa_reg_uses(reg))) - retinfo.append(str(func.get_ssa_reg_value(reg))) - for flag in flag_list: - retinfo.append(str(func.get_ssa_flag_uses(flag))) - retinfo.append(str(func.get_ssa_flag_value(flag))) + 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(str(tempind)) - retinfo.append(str(func.get_ssa_instruction_index(tempind))) - retinfo.append(str(func.get_medium_level_il_instruction_index(ins.instr_index))) - retinfo.append(str(func.get_mapped_medium_level_il_instruction_index(ins.instr_index))) - - return retinfo - + 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""" @@ -153,22 +207,37 @@ class BinaryViewTestBuilder(Builder): for func in self.bv.functions: for bb in func.medium_level_il.basic_blocks: for ins in bb: - retinfo.append(str(ins.expr_type)) - retinfo.append(str(ins.low_level_il)) - retinfo.append(str(ins.value)) - retinfo.append(str(ins.possible_values)) - retinfo.append(str(ins.branch_dependence)) - retinfo.append(str(sorted([str(i) for i in ins.prefix_operands]))) - retinfo.append(str(sorted([str(i) for i in ins.postfix_operands]))) - retinfo.append(str(ins.ssa_form)) - retinfo.append(str(ins.non_ssa_form)) + 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()))) - return retinfo + 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""" + """Function med_il_vars doesn't match""" varlist = [] for func in self.bv.functions: func = func.medium_level_il @@ -176,14 +245,13 @@ class BinaryViewTestBuilder(Builder): for instruction in bb: instruction = instruction.ssa_form for var in (instruction.vars_read + instruction.vars_written): - #varlist.append((func.get_var_uses(var), func.get_var_definitions(var))) if hasattr(var, "var"): - varlist.append(str(func.get_ssa_var_definition(var)) + ' ' + \ - str(func.get_ssa_var_uses(var)) + ' ' + \ - str(func.get_ssa_var_value(var)) + ' ' + \ - str(instruction.get_ssa_var_possible_values(var)) + ' ' + \ - str(instruction.get_ssa_var_version)) - return varlist + 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""" @@ -194,42 +262,35 @@ class BinaryViewTestBuilder(Builder): func.create_user_stack_var(0, binja.Type.int(4), "testuservar") func.create_auto_stack_var(4, binja.Type.int(4), "testautovar") - temp = [] - for i in func.stack_layout: - temp.append(str(i)) - funcinfo.append(str(temp)) + sl = func.stack_layout + for i in range(len(sl)): + funcinfo.append("Stack position {}: ".format(i) + str(sl[i])) - funcinfo.append(str(func.get_stack_contents_at(func.start + 0x10, 0, 0x10))) - funcinfo.append(str(func.get_stack_contents_after(func.start + 0x10, 0, 0x10))) - funcinfo.append(str(func.get_stack_var_at_frame_offset(0, 0))) + 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(str(llilbb)) + retinfo.append("LLIL basic block: " + str(llilbb)) for llilins in func.llil_instructions: - retinfo.append(str(llilins)) - + retinfo.append("LLIL instruction: " + str(llilins)) for mlilbb in func.mlil_basic_blocks: - retinfo.append(str(mlilbb)) + retinfo.append("MLIL basic block: " + str(mlilbb)) for mlilins in func.mlil_instructions: - retinfo.append(str(mlilins)) - + retinfo.append("MLIL instruction: " + str(mlilins)) for ins in func.instructions: - retinfo.append(str(ins)) - - return retinfo - + retinfo.append("Instruction: {}: ".format(hex(ins[1])) + ''.join([str(i) for i in ins[0]])) + return fixOutput(retinfo) def test_functions_attributes(self): - """"Function attributes don't match""" + """Function attributes don't match""" funcinfo = [] for func in self.bv.functions: func.comment = "testcomment " + func.name @@ -245,62 +306,67 @@ class BinaryViewTestBuilder(Builder): 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)) - - funcinfo.append(str([str(i) for i in func.vars])) - funcinfo.append(str(func.indirect_branches)) - funcinfo.append(str(func.session_data)) - funcinfo.append(len(func.analysis_performance_info)) - funcinfo.append(str(func.clobbered_regs)) - funcinfo.append(str(func.explicitly_defined_type)) - funcinfo.append(str(func.needs_update)) - funcinfo.append(len(func.lifted_il)) - funcinfo.append(str(func.global_pointer_value)) - funcinfo.append(str(func.comment)) - funcinfo.append(str(func.too_large)) - funcinfo.append(str(func.analysis_skipped)) - funcinfo.append(str(func.get_low_level_il_at(func.start))) - funcinfo.append(str(func.get_low_level_il_exits_at(func.start+0x100))) - funcinfo.append(str(func.get_regs_read_by(func.start))) - funcinfo.append(str(func.get_regs_written_by(func.start))) - funcinfo.append(str(func.get_stack_vars_referenced_by(func.start))) - funcinfo.append(str(func.get_constants_referenced_by(func.start))) - funcinfo.append(str(func.get_lifted_il_at(func.start))) - funcinfo.append(str(func.get_flags_read_by_lifted_il_instruction(0))) - funcinfo.append(str(func.get_flags_written_by_lifted_il_instruction(0))) - funcinfo.append(str(func.create_graph())) - funcinfo.append(str(func.get_indirect_branches_at(func.start+0x10))) - funcinfo.append(str(func.get_block_annotations(func.start))) - funcinfo.append(str(func.get_basic_block_at(func.start))) - funcinfo.append(str(func.get_instr_highlight(func.start))) - funcinfo.append(str(func.get_type_tokens())) + for var in func.vars: + funcinfo.append("Function {} var: ".format(func.name) + str(var)) - return funcinfo + 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 = [] - retinfo += [str(i[1]) for i in self.bv.types.items()] - retinfo.append(str(self.bv.segments)) - retinfo.append(str(self.bv.sections)) - retinfo.append(str(self.bv.allocated_ranges)) - retinfo.append(str(self.bv.session_data)) + 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(str(var)) - retinfo.append(str(self.bv.entry_function)) + retinfo.append("BV data var: " + str(var)) + retinfo.append("BV Entry function: " + str(self.bv.entry_function)) for i in self.bv: - retinfo.append(str(i)) - retinfo.append(hex(self.bv.entry_point)) - retinfo.append(hex(self.bv.start)) - retinfo.append("length: " + hex(len(self.bv))) - return retinfo - + 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 against + """ The TestBuilder is for tests that need to be checked againsttest_BinaryView stored oracle data that isn't from a binary. These test are generated on your local machine then run again on the build machine to verify correctness. @@ -312,74 +378,133 @@ class TestBuilder(Builder): def test_BinaryViewType_list(self): """BinaryViewType list doesnt match""" - return [x.name for x in binja.BinaryViewType.list] + return ["BinaryViewType: " + x.name for x in binja.BinaryViewType.list] def test_Architecture_list(self): """Architecture list doesnt match""" - return [x.name for x in binja.Architecture.list] + return ["Arch name: " + x.name for x in binja.Architecture.list] def test_Assemble(self): """unexpected assemble result""" result = [] # success cases - result.append(binja.Architecture["x86"].assemble("xor eax, eax")) - result.append(binja.Architecture["x86_64"].assemble("xor rax, rax")) - result.append(binja.Architecture["mips32"].assemble("move $ra, $zero")) - result.append(binja.Architecture["mipsel32"].assemble("move $ra, $zero")) - result.append(binja.Architecture["armv7"].assemble("str r2, [sp, #-0x4]!")) - result.append(binja.Architecture["aarch64"].assemble("mov x0, x0")) - result.append(binja.Architecture["thumb2"].assemble("ldr r4, [r4]")) - result.append(binja.Architecture["thumb2eb"].assemble("ldr r4, [r4]")) + + strResult = binja.Architecture["x86"].assemble("xor eax, eax") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("x86 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("x86 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["x86_64"].assemble("xor rax, rax") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("x86_64 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("x86_64 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["mips32"].assemble("move $ra, $zero") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("mips32 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("mips32 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["mipsel32"].assemble("move $ra, $zero") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("mipsel32 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("mipsel32 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["armv7"].assemble("str r2, [sp, #-0x4]!") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("armv7 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("armv7 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["aarch64"].assemble("mov x0, x0") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("aarch64 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("aarch64 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["thumb2"].assemble("ldr r4, [r4]") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("thumb2 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("thumb2 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["thumb2eb"].assemble("ldr r4, [r4]") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("thumb2eb assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("thumb2eb assembly: " + repr(str(strResult))) + # fail cases - result.append(binja.Architecture["x86"].assemble("thisisnotaninstruction")) - result.append(binja.Architecture["x86_64"].assemble("thisisnotaninstruction")) - result.append(binja.Architecture["mips32"].assemble("thisisnotaninstruction")) - result.append(binja.Architecture["mipsel32"].assemble("thisisnotaninstruction")) - result.append(binja.Architecture["armv7"].assemble("thisisnotaninstruction")) - result.append(binja.Architecture["aarch64"].assemble("thisisnotaninstruction")) - result.append(binja.Architecture["thumb2"].assemble("thisisnotaninstruction")) - result.append(binja.Architecture["thumb2eb"].assemble("thisisnotaninstruction")) + try: + strResult = binja.Architecture["x86"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'x86'") + try: + strResult = binja.Architecture["x86_64"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'x86_64'") + try: + strResult = binja.Architecture["mips32"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'mips32'") + try: + strResult = binja.Architecture["mipsel32"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'mipsel32'") + try: + strResult = binja.Architecture["armv7"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'armv7'") + try: + strResult = binja.Architecture["aarch64"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'aarch64'") + try: + strResult = binja.Architecture["thumb2"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'thumb2'") + try: + strResult = binja.Architecture["thumb2eb"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'thumb2eb'") return result def test_Architecture(self): """Architecture failure""" if not os.path.exists(os.path.join(os.path.expanduser("~"), '.binaryninja', 'plugins', 'nes.py')): - return + return [""] retinfo = [] file_name = os.path.join(self.test_store, "..", "pwnadventurez.nes") bv = binja.BinaryViewType["NES Bank 0"].open(file_name) - - retinfo.append(str([str(i) for i in bv.platform.arch.calling_conventions])) - retinfo.append(str(bv.platform.arch.full_width_regs)) + + 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(str(reg.entry_value(bv.platform.arch, 'x'))) - retinfo.append(str(reg.constant(0xfe))) - retinfo.append(str(reg.constant_ptr(0xcafebabe))) - retinfo.append(str(reg.stack_frame_offset(0x10))) - retinfo.append(str(reg.imported_address(0xdeadbeef))) - retinfo.append(str(reg.return_address())) + 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(str(bv.platform.arch.get_instruction_info(0x10, ins.address))) - retinfo.append(str(bv.platform.arch.get_instruction_text(0x10, ins.address))) - retinfo.append(str(ins)) + 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 [str(testfunction.parameters), str(testfunction.pointer(binja.Architecture["x86"], testfunction))] + 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 @@ -387,22 +512,23 @@ class TestBuilder(Builder): struct.append(inttype) struct.replace(0, inttype) struct.remove(1) - retinfo = [str(i) for i in struct.members] - retinfo += [struct.width] + for i in struct.members: + retinfo.append("Struct member: " + str(i)) + retinfo.append("Struct width: " + str(struct.width)) struct.width = 16 - retinfo += [struct.width] - retinfo += [struct.alignment] + retinfo.append("Struct width after adjustment: " + str(struct.width)) + retinfo.append("Struct alignment: " + str(struct.alignment)) struct.alignment = 8 - retinfo += [struct.alignment] - retinfo += [struct.packed] + retinfo.append("Struct alignment after adjustment: " + str(struct.alignment)) + retinfo.append("Struct packed: " + str(struct.packed)) struct.packed = 1 - retinfo += [struct.packed] - retinfo += [struct.type] - retinfo = [str(i) for i in retinfo] + 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 @@ -410,16 +536,15 @@ class TestBuilder(Builder): enum.append("b", 2) enum.replace(0, "a", 2) enum.remove(0) - retinfo = [str(enum)] - retinfo += [str((enum == enum) and not (enum != enum))] + 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 = os.path.join(self.test_store, "helloworld") + file_name = self.unpackage_file("helloworld") bv = binja.BinaryViewType.get_view_of_file(file_name) - + preprocessed = binja.preprocess_source(""" #ifdef nonexistant int foo = 1; @@ -429,70 +554,64 @@ class TestBuilder(Builder): long long bar1 = 2; #endif """) - source = '\n'.join([i.decode("utf-8") for i in preprocessed[0].split('\n') if not b"#line" in i and len(i) > 0]) + source = '\n'.join([i.decode('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 = [[str(i) for i in typelist.variables.popitem()][::-1] for i in range(len(typelist.variables))] - retinfo += [str(namedtype)] - - equalcheck = (inttype == inttype) and not (inttype != inttype) - retinfo += [str(equalcheck)] - return retinfo + 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 = os.path.join(self.test_store, "helloworld") - self.unpackage_file(file_name) - result = subprocess.Popen(["python", os.path.join(self.examples_dir, "bin_info.py"), file_name], stdout=subprocess.PIPE).communicate()[0] + 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 [result.replace(b"\\", b"/").replace(b"\r\n", b"\n")] + 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 = os.path.join(self.test_store, "helloworld") - self.unpackage_file(file) - bv = binja.BinaryViewType['ELF'].open(file) - settings = binja.DisassemblySettings() + file_name = self.unpackage_file("helloworld") + bv = binja.BinaryViewType['ELF'].open(file_name) disass = bv.linear_disassembly - res = [] + retinfo = [] for i in disass: - res.append(str(i)) - #return res + 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 = os.path.join(self.test_store, "partial_register_dataflow") - self.unpackage_file(file_name) + file_name = self.unpackage_file("partial_register_dataflow") result = [] - try: - 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): - result.append(["LLIL:" + str(i).replace('L', '') + ":" + x + ":" + str(llil[i].get_reg_value(x)).replace('L', '') for x in reg_list]) - result.append(["LLIL:" + str(i).replace('L', '') + ":" + x + ":" + str(llil[i].get_possible_reg_values(x)).replace('L', '') for x in reg_list]) - result.append(["LLIL:" + str(i).replace('L', '') + ":" + x + ":" + str(llil[i].get_reg_value_after(x)).replace('L', '') for x in reg_list]) - result.append(["LLIL:" + str(i).replace('L', '') + ":" + x + ":" + str(llil[i].get_possible_reg_values_after(x)).replace('L', '') for x in reg_list]) - bv.file.close() - del bv - finally: - os.unlink(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'] + 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 = os.path.join(self.test_store, "jumptable_reordered") - self.unpackage_file(file_name) + 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'] @@ -500,26 +619,20 @@ class TestBuilder(Builder): for func in bv.functions: for bb in func.low_level_il.basic_blocks: for ins in bb: - retinfo.append(str(ins.get_stack_contents(0,1))) - retinfo.append(str(ins.get_stack_contents_after(0,1))) - retinfo.append(str(ins.get_possible_stack_contents(0,1))) - retinfo.append(str(ins.get_possible_stack_contents_after(0,1))) - - + 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(str(ins.get_flag_value(flag))) - retinfo.append(str(ins.get_flag_value_after(flag))) - retinfo.append(str(ins.get_possible_flag_values(flag))) - retinfo.append(str(ins.get_possible_flag_values_after(flag))) - - os.unlink(file_name) - - return retinfo + 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 = os.path.join(self.test_store, "jumptable_reordered") - self.unpackage_file(file_name) + 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'] @@ -527,159 +640,108 @@ class TestBuilder(Builder): for func in bv.functions: for bb in func.medium_level_il.basic_blocks: for ins in bb: - retinfo.append(str(ins.get_var_for_stack_location(0))) - retinfo.append(str(ins.get_stack_contents(0, 0x10))) - retinfo.append(str(ins.get_stack_contents_after(0, 0x10))) - retinfo.append(str(ins.get_possible_stack_contents(0, 0x10))) - retinfo.append(str(ins.get_possible_stack_contents_after(0, 0x10))) - - for reg in reg_list: - retinfo.append(str(ins.get_var_for_reg(reg))) - retinfo.append(str(ins.get_reg_value(reg))) - retinfo.append(str(ins.get_reg_value_after(reg))) - retinfo.append(str(ins.get_possible_reg_values(reg))) - retinfo.append(str(ins.get_possible_reg_values_after(reg))) + 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(str(ins.get_flag_value(flag))) - retinfo.append(str(ins.get_flag_value_after(flag))) - retinfo.append(str(ins.get_possible_flag_values(flag))) - retinfo.append(str(ins.get_possible_flag_values_after(flag))) - - os.unlink(file_name) - - return retinfo - + 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 = os.path.join(self.test_store, "helloworld") - self.unpackage_file(file_name) + 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): - def data_written_complete(self): - results.append("data written {0} {1}".format(offset, length)) - evt = binja.AnalysisCompletionEvent(bv, data_written_complete) + results.append("data written: offset {0} length {1}".format(hex(offset), hex(length))) def data_inserted(self, view, offset, length): - def data_inserted_complete(self): - results.append("data inserted {0} {1}".format(offset, length)) - evt = binja.AnalysisCompletionEvent(bv, data_inserted_complete) + results.append("data inserted: offset {0} length {1}".format(hex(offset), hex(length))) def data_removed(self, view, offset, length): - def data_removed_complete(self): - results.append("data removed {0} {1}".format(offset, length)) - evt = binja.AnalysisCompletionEvent(bv, data_removed_complete) + results.append("data removed: offset {0} length {1}".format(hex(offset), hex(length))) def function_added(self, view, func): - def function_added_complete(self): - results.append("function added {0}".format(func.name)) - evt = binja.AnalysisCompletionEvent(bv, function_added_complete) + results.append("function added: {0}".format(func.name)) def function_removed(self, view, func): - def function_removed_complete(self): - results.append("function removed {0}".format(func.name)) - evt = binja.AnalysisCompletionEvent(bv, function_removed_complete) - - def function_updated(self, view, func): - def function_updated_complete(self): - results.append("function updated {0}".format(func.name)) - evt = binja.AnalysisCompletionEvent(bv, function_updated_complete) - - def function_update_requested(self, view, func): - def function_update_requested_complete(self): - results.append("function update requested {0}".format(func.name)) - evt = binja.AnalysisCompletionEvent(bv, function_update_requested_complete) + results.append("function removed: {0}".format(func.name)) def data_var_added(self, view, var): - def data_var_added_complete(self): - results.append("data var added {0}".format(var.name)) - evt = binja.AnalysisCompletionEvent(bv, data_var_added_complete) + results.append("data var added: {0}".format(hex(var.address))) def data_var_removed(self, view, var): - def data_var_removed_complete(self): - results.append("data var removed {0}".format(var.name)) - evt = binja.AnalysisCompletionEvent(bv, data_var_removed_complete) - - def data_var_updated(self, view, var): - def data_var_updated_complete(self): - results.append("data var updated {0}".format(var.name)) - evt = binja.AnalysisCompletionEvent(bv, data_var_updated_complete) + results.append("data var removed: {0}".format(hex(var.address))) def string_found(self, view, string_type, offset, length): - def string_found_complete(self): - results.append("string found {0} {1}".format(offset, length)) - evt = binja.AnalysisCompletionEvent(bv, string_found_complete) + results.append("string found: offset {0} length {1}".format(hex(offset), hex(length))) def string_removed(self, view, string_type, offset, length): - def string_removed_complete(self): - results.append("string removed {0} {1}".format(offset, length)) - evt = binja.AnalysisCompletionEvent(bv, string_removed_complete) + results.append("string removed: offset {0} length {1}".format(hex(offset), hex(length))) def type_defined(self, view, name, type): - def type_defined_complete(self): - results.append("type defined {0} {1}".format(name)) - evt = binja.AnalysisCompletionEvent(bv, type_defined_complete) + results.append("type defined: {0}".format(name)) def type_undefined(self, view, name, type): - def type_undefined_complete(self): - results.append("type undefined {0} {1}".format(name)) - evt = binja.AnalysisCompletionEvent(bv, type_undefined_complete) - + 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.insert(sacrificial_addr, "AAAA") + 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, "BBBB") + 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.remove(sacrificial_addr, 4) - bv.update_analysis_and_wait() - - bv.unregister_notification(test) - - return str(sorted(results)) - def unpackage(self, fileName): - testname = None - with zipfile.ZipFile(fileName, "r") as zf: - testname = zf.namelist()[0] - zf.extractall() + bv.remove(sacrificial_addr, 4) + bv.update_analysis_and_wait() - if not os.path.exists(testname + ".pkl"): - return None, None - binary_oracle = pickle.load(open(testname + ".pkl", "rb")) - return binary_oracle.oracle_test_data, testname + bv.unregister_notification(test) - def cleanup_package(self, fileName): - if fileName.endswith(".zip"): - os.unlink(fileName[:-4]) + return fixOutput(sorted(results)) class VerifyBuilder(Builder): @@ -690,6 +752,7 @@ class VerifyBuilder(Builder): - Function doc string used as 'on error' message - Should return: boolean """ + def __init__(self, test_store): super(VerifyBuilder, self).__init__(test_store) @@ -709,10 +772,9 @@ class VerifyBuilder(Builder): # - Save the database # - Restore the datbase # - Validate that the modifications are present - file = os.path.join(self.test_store, "helloworld") - self.unpackage_file(file) + file_name = self.unpackage_file("helloworld") try: - bv = binja.BinaryViewType['ELF'].open(file) + bv = binja.BinaryViewType['ELF'].open(file_name) bv.update_analysis_and_wait() # Make some modifications to the binary view @@ -727,10 +789,7 @@ class VerifyBuilder(Builder): bv.create_database(temp_name) bv.file.close() del bv - finally: - os.unlink(file) - try: 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) @@ -741,4 +800,3 @@ class VerifyBuilder(Builder): return [str(functions == bndb_functions and comments == bndb_comments)] finally: os.unlink(temp_name) - diff --git a/suite/unit.py b/suite/unit.py deleted file mode 100644 index 993daf41..00000000 --- a/suite/unit.py +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/env python -# This is an auto generated unit test file do not edit directly -import os -import unittest -import pickle -import zipfile -import testcommon -import binaryninja -import api_test -import difflib - - - -class TestBinaryNinjaAPI(unittest.TestCase): - @classmethod - def setUpClass(self): - self.builder = testcommon.TestBuilder("suite/binaries/test_corpus") - try: - #Python 2 does not have the encodings option - self.oracle_test_data = pickle.load(open(os.path.join("suite", "oracle.pkl"), "rUb"), errors="ignore") - except TypeError: - self.oracle_test_data = pickle.load(open(os.path.join("suite", "oracle.pkl"), "rU")) - self.verifybuilder = testcommon.VerifyBuilder("suite/binaries/test_corpus") - - def run_binary_test(self, testfile): - testname = None - with zipfile.ZipFile(testfile, "r") as zf: - testname = zf.namelist()[0] - zf.extractall() - - self.assertTrue(os.path.exists(testname + ".pkl"), "Test pickle doesn't exist") - try: - #Python 2 does not have the encodings option - binary_oracle = pickle.load(open(testname + ".pkl", "rUb"), errors="ignore") - except TypeError: - binary_oracle = pickle.load(open(testname + ".pkl", "rU")) - - test_builder = testcommon.BinaryViewTestBuilder(testname, "suite/binaries/test_corpus") - 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" - d = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in d.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\n' - skipped_lines = 0 - delta = delta.replace('\n', '') - result += delta + '\n' - self.assertTrue(False, result) - os.unlink(testname) - - def test_Architecture(self): - oracle = self.oracle_test_data['test_Architecture'] - test = self.builder.test_Architecture() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_Architecture.__doc__, oracle, test)) - - def test_Architecture_list(self): - oracle = self.oracle_test_data['test_Architecture_list'] - test = self.builder.test_Architecture_list() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_Architecture_list.__doc__, oracle, test)) - - def test_Assemble(self): - oracle = self.oracle_test_data['test_Assemble'] - test = self.builder.test_Assemble() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_Assemble.__doc__, oracle, test)) - - def test_BinaryViewType_list(self): - oracle = self.oracle_test_data['test_BinaryViewType_list'] - test = self.builder.test_BinaryViewType_list() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_BinaryViewType_list.__doc__, oracle, test)) - - def test_Enumeration(self): - oracle = self.oracle_test_data['test_Enumeration'] - test = self.builder.test_Enumeration() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_Enumeration.__doc__, oracle, test)) - - def test_Function(self): - oracle = self.oracle_test_data['test_Function'] - test = self.builder.test_Function() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_Function.__doc__, oracle, test)) - - def test_Plugin_bin_info(self): - oracle = self.oracle_test_data['test_Plugin_bin_info'] - test = self.builder.test_Plugin_bin_info() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_Plugin_bin_info.__doc__, oracle, test)) - - def test_Struct(self): - oracle = self.oracle_test_data['test_Struct'] - test = self.builder.test_Struct() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_Struct.__doc__, oracle, test)) - - def test_Types(self): - oracle = self.oracle_test_data['test_Types'] - test = self.builder.test_Types() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_Types.__doc__, oracle, test)) - - def test_events(self): - oracle = self.oracle_test_data['test_events'] - test = self.builder.test_events() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_events.__doc__, oracle, test)) - - def test_linear_disassembly(self): - oracle = self.oracle_test_data['test_linear_disassembly'] - test = self.builder.test_linear_disassembly() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_linear_disassembly.__doc__, oracle, test)) - - def test_low_il_stack(self): - oracle = self.oracle_test_data['test_low_il_stack'] - test = self.builder.test_low_il_stack() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_low_il_stack.__doc__, oracle, test)) - - def test_med_il_stack(self): - oracle = self.oracle_test_data['test_med_il_stack'] - test = self.builder.test_med_il_stack() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_med_il_stack.__doc__, oracle, test)) - - def test_partial_register_dataflow(self): - oracle = self.oracle_test_data['test_partial_register_dataflow'] - test = self.builder.test_partial_register_dataflow() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_partial_register_dataflow.__doc__, oracle, test)) - - def test_verify_BNDB_round_trip(self): - self.assertTrue(self.verifybuilder.test_verify_BNDB_round_trip(), self.test_verify_BNDB_round_trip.__doc__) - - def test_binary___aliased_jumptable(self): - self.run_binary_test('suite/binaries/test_corpus/aliased_jumptable.zip') - - def test_binary___byte_jump_table(self): - self.run_binary_test('suite/binaries/test_corpus/byte_jump_table.zip') - - def test_binary___duff(self): - self.run_binary_test('suite/binaries/test_corpus/duff.zip') - - def test_binary___helloworld(self): - self.run_binary_test('suite/binaries/test_corpus/helloworld.zip') - - def test_binary___helloworld_armeb(self): - self.run_binary_test('suite/binaries/test_corpus/helloworld_armeb.zip') - - def test_binary___integer_test(self): - self.run_binary_test('suite/binaries/test_corpus/integer_test.zip') - - def test_binary___interprocedural_alias(self): - self.run_binary_test('suite/binaries/test_corpus/interprocedural_alias.zip') - - def test_binary___jump_loop(self): - self.run_binary_test('suite/binaries/test_corpus/jump_loop.zip') - - def test_binary___jumptable_aarch64(self): - self.run_binary_test('suite/binaries/test_corpus/jumptable_aarch64.zip') - - def test_binary___jumptable_mips32(self): - self.run_binary_test('suite/binaries/test_corpus/jumptable_mips32.zip') - - def test_binary___jumptable_multiple_indirect(self): - self.run_binary_test('suite/binaries/test_corpus/jumptable_multiple_indirect.zip') - - def test_binary___jumptable_no_range_check(self): - self.run_binary_test('suite/binaries/test_corpus/jumptable_no_range_check.zip') - - def test_binary___jumptable_reordered(self): - self.run_binary_test('suite/binaries/test_corpus/jumptable_reordered.zip') - - def test_binary___jumptable_x86(self): - self.run_binary_test('suite/binaries/test_corpus/jumptable_x86.zip') - - def test_binary___jumptable_x86_64(self): - self.run_binary_test('suite/binaries/test_corpus/jumptable_x86_64.zip') - - def test_binary___loop_constant_propagate(self): - self.run_binary_test('suite/binaries/test_corpus/loop_constant_propagate.zip') - - def test_binary___partial_register_dataflow(self): - self.run_binary_test('suite/binaries/test_corpus/partial_register_dataflow.zip') - - def test_binary___pe_thumb(self): - self.run_binary_test('suite/binaries/test_corpus/pe_thumb.zip') - - def test_binary___quick3dcoreplugin_dll(self): - self.run_binary_test('suite/binaries/test_corpus/quick3dcoreplugin.dll.zip') - - def test_binary___rangecheck(self): - self.run_binary_test('suite/binaries/test_corpus/rangecheck.zip') - - def test_binary___switch_linux_ppc_le_32(self): - self.run_binary_test('suite/binaries/test_corpus/switch_linux_ppc_le_32.zip') - - def test_binary___x87(self): - self.run_binary_test('suite/binaries/test_corpus/x87.zip') - - -if __name__ == "__main__": - - test_suite = unittest.defaultTestLoader.loadTestsFromModule(api_test) - test_suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestBinaryNinjaAPI)) - runner = unittest.TextTestRunner(verbosity=2) - runner.run(test_suite) |
