diff --git a/tests/fixtures.py b/tests/fixtures.py index c71a181cc..108cebf15 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -142,6 +142,13 @@ def get_dotnetfile_extractor(path): return capa.features.extractors.dotnetfile.DotnetFileFeatureExtractor(path) +@lru_cache(maxsize=1) +def get_dnfile_extractor(path): + import capa.features.extractors.dnfile.extractor + + return capa.features.extractors.dnfile.extractor.DnfileFeatureExtractor(path) + + def extract_global_features(extractor): features = collections.defaultdict(set) for feature, va in extractor.extract_global_features(): @@ -244,6 +251,10 @@ def get_data_path_by_name(name): return os.path.join(CD, "data", "b9f5bd514485fb06da39beff051b9fdc.exe_") elif name.startswith("mixed-mode-64"): return os.path.join(DNFILE_TESTFILES, "mixed-mode", "ModuleCode", "bin", "ModuleCode_amd64.exe") + elif name.startswith("hello-world"): + return os.path.join(DNFILE_TESTFILES, "hello-world", "hello-world.exe") + elif name.startswith("_1c444"): + return os.path.join(CD, "data", "dotnet", "1c444ebeba24dcba8628b7dfe5fec7c6.exe_") else: raise ValueError("unexpected sample fixture: %s" % name) @@ -660,6 +671,25 @@ def parametrize(params, values, **kwargs): ("mixed-mode-64", "file", Arch(ARCH_I386), False), ("b9f5b", "file", OS(OS_ANY), True), ("b9f5b", "file", Format(FORMAT_DOTNET), True), + ("hello-world", "function=0x250", capa.features.common.String("Hello World!"), True), + ("hello-world", "function=0x250, bb=0x250, insn=0x252", capa.features.common.String("Hello World!"), True), + ("hello-world", "function=0x250", capa.features.insn.API("System.Console::WriteLine"), True), + ("hello-world", "file", capa.features.file.Import("System.Console::WriteLine"), True), + ("_1c444", "file", capa.features.file.Import("gdi32.CreateCompatibleBitmap"), True), + ("_1c444", "file", capa.features.file.Import("CreateCompatibleBitmap"), True), + ("_1c444", "file", capa.features.file.Import("gdi32::CreateCompatibleBitmap"), False), + ("_1c444", "function=0x1F68", capa.features.insn.API("GetWindowDC"), True), + ("_1c444", "function=0x1F68", capa.features.insn.API("user32.GetWindowDC"), True), + ("_1c444", "function=0x1F68", capa.features.insn.Number(0xCC0020), True), + ("_1c444", "function=0x1F68", capa.features.insn.Number(0x0), True), + ("_1c444", "function=0x1F68", capa.features.insn.Number(0x1), False), + ( + "_1c444", + "function=0x1F68, bb=0x1F68, insn=0x1FF9", + capa.features.insn.API("System.Drawing.Image::FromHbitmap"), + True, + ), + ("_1c444", "function=0x1F68, bb=0x1F68, insn=0x1FF9", capa.features.insn.API("FromHbitmap"), False), ], # order tests by (file, item) # so that our LRU cache is most effective. @@ -681,6 +711,9 @@ def parametrize(params, values, **kwargs): ] +FEATURE_COUNT_TESTS_DOTNET = [] + + def do_test_feature_presence(get_extractor, sample, scope, feature, expected): extractor = get_extractor(sample) features = scope(extractor) @@ -788,3 +821,13 @@ def b9f5b_dotnetfile_extractor(): @pytest.fixture def mixed_mode_64_dotnetfile_extractor(): return get_dotnetfile_extractor(get_data_path_by_name("mixed-mode-64")) + + +@pytest.fixture +def hello_world_dnfile_extractor(): + return get_dnfile_extractor(get_data_path_by_name("hello-world")) + + +@pytest.fixture +def _1c444_dnfile_extractor(): + return get_dnfile_extractor(get_data_path_by_name("1c444...")) diff --git a/tests/test_dnfile_features.py b/tests/test_dnfile_features.py new file mode 100644 index 000000000..0ae391702 --- /dev/null +++ b/tests/test_dnfile_features.py @@ -0,0 +1,30 @@ +# Copyright (C) 2020 FireEye, Inc. All Rights Reserved. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: [package root]/LICENSE.txt +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and limitations under the License. + +import pytest +import fixtures +from fixtures import * +from fixtures import parametrize + + +@parametrize( + "sample,scope,feature,expected", + fixtures.FEATURE_PRESENCE_TESTS_DOTNET, + indirect=["sample", "scope"], +) +def test_dnfile_features(sample, scope, feature, expected): + fixtures.do_test_feature_presence(fixtures.get_dnfile_extractor, sample, scope, feature, expected) + + +@parametrize( + "sample,scope,feature,expected", + fixtures.FEATURE_COUNT_TESTS_DOTNET, + indirect=["sample", "scope"], +) +def test_dnfile_feature_counts(sample, scope, feature, expected): + fixtures.do_test_feature_count(fixtures.get_dnfile_extractor, sample, scope, feature, expected) diff --git a/tests/test_dotnetfile_features.py b/tests/test_dotnetfile_features.py index 15677c875..01d2e8a96 100644 --- a/tests/test_dotnetfile_features.py +++ b/tests/test_dotnetfile_features.py @@ -18,6 +18,12 @@ indirect=["sample", "scope"], ) def test_dotnetfile_features(sample, scope, feature, expected): + if scope.__name__ != "file": + pytest.xfail("pefile only extracts file scope features") + + if isinstance(feature, capa.features.file.FunctionName): + pytest.xfail("pefile doesn't extract function names") + fixtures.do_test_feature_presence(fixtures.get_dotnetfile_extractor, sample, scope, feature, expected)