From 352d6f26fc4ecc7b7ed2f4b732a63b6b57879f3d Mon Sep 17 00:00:00 2001 From: William Ballenthin Date: Sat, 25 Jul 2020 10:10:25 -0600 Subject: [PATCH] tests: ida: ensure they all pass closes #202 --- tests/test_ida_features.py | 43 +++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/tests/test_ida_features.py b/tests/test_ida_features.py index 06c64111..5624b060 100644 --- a/tests/test_ida_features.py +++ b/tests/test_ida_features.py @@ -14,9 +14,23 @@ import capa.features.basicblock logger = logging.getLogger("test_ida_features") +def check_input_file(): + import idautils + + wanted = "5f66b82558ca92e54e77f216ef4c066c" + # some versions of IDA return a truncated version of the MD5. + # https://github.com/idapython/bin/issues/11 + found = idautils.GetInputFileMD5().rstrip(b"\x00").decode("ascii").lower() + if not wanted.startswith(found): + raise RuntimeError("please run the tests against `mimikatz.exe`") + + def get_extractor(): + check_input_file() + # have to import import this inline so pytest doesn't bail outside of IDA import capa.features.extractors.ida + return capa.features.extractors.ida.IdaFeatureExtractor() @@ -57,6 +71,7 @@ def extract_basic_block_features(f, bb): def test_api_features(): # have to import import this inline so pytest doesn't bail outside of IDA import idaapi + f = idaapi.get_func(0x403BAC) features = extract_function_features(f) assert capa.features.insn.API("advapi32.CryptAcquireContextW") in features @@ -74,6 +89,7 @@ def test_api_features(): @pytest.mark.skip(reason="IDA Pro tests must be run within IDA") def test_string_features(): import idaapi + f = idaapi.get_func(0x40105D) features = extract_function_features(f) assert capa.features.String("SCardControl") in features @@ -86,9 +102,10 @@ def test_string_features(): @pytest.mark.skip(reason="IDA Pro tests must be run within IDA") def test_byte_features(): import idaapi + f = idaapi.get_func(0x40105D) features = extract_function_features(f) - wanted = capa.features.Bytes("SCardControl".encode('utf-16le')) + wanted = capa.features.Bytes("SCardControl".encode("utf-16le")) # use `==` rather than `is` because the result is not `True` but a truthy value. assert wanted.evaluate(features) == True @@ -96,6 +113,7 @@ def test_byte_features(): @pytest.mark.skip(reason="IDA Pro tests must be run within IDA") def test_number_features(): import idaapi + f = idaapi.get_func(0x40105D) features = extract_function_features(f) assert capa.features.insn.Number(0xFF) in features @@ -108,6 +126,7 @@ def test_number_features(): @pytest.mark.skip(reason="IDA Pro tests must be run within IDA") def test_offset_features(): import idaapi + f = idaapi.get_func(0x40105D) features = extract_function_features(f) assert capa.features.insn.Offset(0x0) in features @@ -129,6 +148,7 @@ def test_offset_features(): @pytest.mark.skip(reason="IDA Pro tests must be run within IDA") def test_nzxor_features(): import idaapi + f = idaapi.get_func(0x410DFC) features = extract_function_features(f) assert capa.features.Characteristic("nzxor") in features # 0x0410F0B @@ -137,6 +157,7 @@ def test_nzxor_features(): @pytest.mark.skip(reason="IDA Pro tests must be run within IDA") def test_mnemonic_features(): import idaapi + f = idaapi.get_func(0x40105D) features = extract_function_features(f) assert capa.features.insn.Mnemonic("push") in features @@ -158,6 +179,7 @@ def test_file_section_name_features(): @pytest.mark.skip(reason="IDA Pro tests must be run within IDA") def test_tight_loop_features(): import idaapi + extractor = get_extractor() f = idaapi.get_func(0x402EC4) for bb in extractor.get_basic_blocks(f): @@ -171,6 +193,7 @@ def test_tight_loop_features(): @pytest.mark.skip(reason="IDA Pro tests must be run within IDA") def test_tight_loop_bb_features(): import idaapi + extractor = get_extractor() f = idaapi.get_func(0x402EC4) for bb in extractor.get_basic_blocks(f): @@ -195,6 +218,7 @@ def test_file_import_name_features(): @pytest.mark.skip(reason="IDA Pro tests must be run within IDA") def test_stackstring_features(): import idaapi + f = idaapi.get_func(0x4556E5) features = extract_function_features(f) assert capa.features.Characteristic("stack string") in features @@ -203,6 +227,7 @@ def test_stackstring_features(): @pytest.mark.skip(reason="IDA Pro tests must be run within IDA") def test_switch_features(): import idaapi + f = idaapi.get_func(0x409411) features = extract_function_features(f) assert capa.features.Characteristic("switch") in features @@ -215,15 +240,22 @@ def test_switch_features(): @pytest.mark.skip(reason="IDA Pro tests must be run within IDA") def test_function_calls_to(): import idaapi + + # this function is used in a function pointer f = idaapi.get_func(0x4011FB) features = extract_function_features(f) - assert capa.features.Characteristic("calls to") in features + assert capa.features.Characteristic("calls to") not in features + + # __FindPESection is called once + f = idaapi.get_func(0x470360) + features = extract_function_features(f) assert len(features[capa.features.Characteristic("calls to")]) == 1 @pytest.mark.skip(reason="IDA Pro tests must be run within IDA") def test_function_calls_from(): import idaapi + f = idaapi.get_func(0x4011FB) features = extract_function_features(f) assert capa.features.Characteristic("calls from") in features @@ -233,6 +265,7 @@ def test_function_calls_from(): @pytest.mark.skip(reason="IDA Pro tests must be run within IDA") def test_basic_block_count(): import idaapi + f = idaapi.get_func(0x4011FB) features = extract_function_features(f) assert len(features[capa.features.basicblock.BasicBlock()]) == 15 @@ -243,11 +276,11 @@ if __name__ == "__main__": # invoke all functions in this module that start with `parse_` for name in dir(sys.modules[__name__]): - if not name.startswith('test_'): + if not name.startswith("test_"): continue test = getattr(sys.modules[__name__], name) - logger.debug('invoking test: %s', name) + logger.debug("invoking test: %s", name) sys.stderr.flush() try: test() @@ -255,4 +288,4 @@ if __name__ == "__main__": print("FAIL %s" % (name)) traceback.print_exc() else: - print("OK %s" % (name)) \ No newline at end of file + print("OK %s" % (name))