tests: cleanup tests and fixtures

This commit is contained in:
Willi Ballenthin
2026-04-17 12:31:22 +02:00
parent 44509a3cb0
commit 72fab63e0b
29 changed files with 1428 additions and 2020 deletions

View File

@@ -16,20 +16,19 @@ from typing import Iterator
import idaapi
import capa.ida.helpers
import capa.features.extractors.elf
import capa.features.extractors.ida.file
import capa.features.extractors.ida.insn
import capa.features.extractors.ida.global_
import capa.features.extractors.ida.function
import capa.features.extractors.ida.basicblock
import capa.features.extractors.ida.file
import capa.features.extractors.ida.function
import capa.features.extractors.ida.global_
import capa.features.extractors.ida.insn
import capa.ida.helpers
from capa.features.address import AbsoluteVirtualAddress, Address
from capa.features.common import Feature
from capa.features.address import Address, AbsoluteVirtualAddress
from capa.features.extractors.base_extractor import (
BBHandle,
FunctionHandle,
InsnHandle,
SampleHashes,
FunctionHandle,
StaticFeatureExtractor,
)
@@ -44,7 +43,9 @@ class IdaFeatureExtractor(StaticFeatureExtractor):
)
)
self.global_features: list[tuple[Feature, Address]] = []
self.global_features.extend(capa.features.extractors.ida.file.extract_file_format())
self.global_features.extend(
capa.features.extractors.ida.file.extract_file_format()
)
self.global_features.extend(capa.features.extractors.ida.global_.extract_os())
self.global_features.extend(capa.features.extractors.ida.global_.extract_arch())
@@ -68,7 +69,9 @@ class IdaFeatureExtractor(StaticFeatureExtractor):
f = idaapi.get_func(ea)
return FunctionHandle(address=AbsoluteVirtualAddress(f.start_ea), inner=f)
def extract_function_features(self, fh: FunctionHandle) -> Iterator[tuple[Feature, Address]]:
def extract_function_features(
self, fh: FunctionHandle
) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.ida.function.extract_features(fh)
def get_basic_blocks(self, fh: FunctionHandle) -> Iterator[BBHandle]:
@@ -77,13 +80,19 @@ class IdaFeatureExtractor(StaticFeatureExtractor):
for bb in ida_helpers.get_function_blocks(fh.inner):
yield BBHandle(address=AbsoluteVirtualAddress(bb.start_ea), inner=bb)
def extract_basic_block_features(self, fh: FunctionHandle, bbh: BBHandle) -> Iterator[tuple[Feature, Address]]:
def extract_basic_block_features(
self, fh: FunctionHandle, bbh: BBHandle
) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.ida.basicblock.extract_features(fh, bbh)
def get_instructions(self, fh: FunctionHandle, bbh: BBHandle) -> Iterator[InsnHandle]:
def get_instructions(
self, fh: FunctionHandle, bbh: BBHandle
) -> Iterator[InsnHandle]:
import capa.features.extractors.ida.helpers as ida_helpers
for insn in ida_helpers.get_instructions_in_range(bbh.inner.start_ea, bbh.inner.end_ea):
for insn in ida_helpers.get_instructions_in_range(
bbh.inner.start_ea, bbh.inner.end_ea
):
yield InsnHandle(address=AbsoluteVirtualAddress(insn.ea), inner=insn)
def extract_insn_features(self, fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle):

View File

@@ -21,7 +21,3 @@
# https://www.revsys.com/tidbits/pytest-fixtures-are-magic/
# https://lobste.rs/s/j8xgym/pytest_fixtures_are_magic
from fixtures import * # noqa: F403 [unable to detect undefined names]
from fixtures import _692f_dotnetfile_extractor # noqa: F401 [imported but unused]
from fixtures import _1c444_dotnetfile_extractor # noqa: F401 [imported but unused]
from fixtures import _039a6_dotnetfile_extractor # noqa: F401 [imported but unused]
from fixtures import _0953c_dotnetfile_extractor # noqa: F401 [imported but unused]

File diff suppressed because it is too large Load Diff

View File

@@ -126,16 +126,17 @@ For example:
```python
import fixtures
BACKEND = fixtures.BackendFeaturePolicy(
name="viv",
get_extractor=fixtures.get_viv_extractor,
exclude_tags={"dotnet"},
@fixtures.parametrize_backend_feature_fixtures(
fixtures.BackendFeaturePolicy(
name="viv",
include_tags={"static"},
exclude_tags={"dotnet", "ghidra"},
)
)
@fixtures.parametrize_backend_feature_fixtures(BACKEND)
def test_viv_features(feature_fixture):
fixtures.run_feature_fixture(BACKEND, feature_fixture)
extractor = fixtures.get_viv_extractor(feature_fixture.sample_path)
fixtures.run_feature_fixture(extractor, feature_fixture)
```
Module-level availability checks are still allowed. runtime-specific hooks are allowed only when they depend on the installed backend or tool version and cannot be represented declaratively in the fixture manifests.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -15,11 +15,13 @@
import re
import logging
from typing import Any
from pathlib import Path
from typing import Any
import pytest
import fixtures
CD = Path(__file__).resolve().parent
from google.protobuf.json_format import ParseDict
import capa.features.extractors.binexport2.helpers
@@ -39,7 +41,6 @@ from capa.features.extractors.binexport2.arch.arm.helpers import is_stack_regist
logger = logging.getLogger(__name__)
CD = Path(__file__).resolve().parent
# found via https://www.virustotal.com/gui/search/type%253Aelf%2520and%2520size%253A1.2kb%252B%2520and%2520size%253A1.4kb-%2520and%2520tag%253Aarm%2520and%2520not%2520tag%253Arelocatable%2520and%2520tag%253A64bits/files
@@ -583,7 +584,7 @@ def test_pattern_matching_not_stack():
assert match_address_with_be2(BE2_EXTRACTOR_687, queries, 0x107918) is None
BE2_EXTRACTOR_MIMI = fixtures.get_binexport_extractor(CD / "data" / "binexport2" / "mimikatz.exe_.ghidra.BinExport")
BE2_EXTRACTOR_MIMI = fixtures.get_binexport_extractor(fixtures.CD / "data" / "binexport2" / "mimikatz.exe_.ghidra.BinExport")
def test_pattern_matching_x86():

View File

@@ -11,57 +11,15 @@
# 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.
from typing import cast
import fixtures
import pytest
import capa.features.common
BACKEND = fixtures.BackendFeaturePolicy(
name="binexport",
get_extractor=fixtures.get_binexport_extractor,
include_tags={"binexport"},
)
@fixtures.parametrize_backend_feature_fixtures(BACKEND)
def test_binexport_features_elf_aarch64(feature_fixture):
fixtures.run_feature_fixture(BACKEND, feature_fixture)
@fixtures.parametrize(
"sample,scope,feature,expected",
fixtures.FEATURE_PRESENCE_TESTS,
indirect=["sample", "scope"],
)
def test_binexport_features_pe_x86(sample, scope, feature, expected):
if "mimikatz.exe_" not in sample.name:
pytest.skip("for now only testing mimikatz.exe_ Ghidra BinExport file")
if isinstance(
feature, capa.features.common.Characteristic
) and "stack string" in cast(str, feature.value):
pytest.skip("for now only testing basic features")
sample = sample.parent / "binexport2" / (sample.name + ".ghidra.BinExport")
assert sample.exists()
fixtures.do_test_feature_presence(
fixtures.get_binexport_extractor, sample, scope, feature, expected
@fixtures.parametrize_backend_feature_fixtures(
fixtures.BackendFeaturePolicy(
name="binexport",
include_tags={"binexport"},
)
@fixtures.parametrize(
"sample,scope,feature,expected",
fixtures.FEATURE_COUNT_TESTS_GHIDRA,
indirect=["sample", "scope"],
)
def test_binexport_feature_counts_ghidra(sample, scope, feature, expected):
if "mimikatz.exe_" not in sample.name:
pytest.skip("for now only testing mimikatz.exe_ Ghidra BinExport file")
sample = sample.parent / "binexport2" / (sample.name + ".ghidra.BinExport")
assert sample.exists()
fixtures.do_test_feature_count(
fixtures.get_binexport_extractor, sample, scope, feature, expected
)
def test_binexport_features(feature_fixture):
extractor = fixtures.get_binexport_extractor(feature_fixture.sample_path)
fixtures.run_feature_fixture(extractor, feature_fixture)

View File

@@ -13,12 +13,10 @@
# limitations under the License.
import logging
from pathlib import Path
import fixtures
import pytest
import capa.features.common
import capa.main
logger = logging.getLogger(__file__)
@@ -41,23 +39,22 @@ except ImportError:
pass
BACKEND = fixtures.BackendFeaturePolicy(
name="binja",
# binja also loads .bndb database files natively, so include `binja-db`
# alongside the regular static-binary fixtures.
get_extractor=fixtures.get_binja_extractor,
include_tags={"static", "binja-db"},
exclude_tags={"dotnet", "ghidra"},
)
@pytest.mark.skipif(
binja_present is False,
reason="Skip binja tests if the binaryninja Python API is not installed",
)
@fixtures.parametrize_backend_feature_fixtures(BACKEND)
@fixtures.parametrize_backend_feature_fixtures(
fixtures.BackendFeaturePolicy(
name="binja",
# binja also loads .bndb database files natively, so include `binja-db`
# alongside the regular static-binary fixtures.
include_tags={"static", "binja-db"},
exclude_tags={"dotnet", "ghidra"},
)
)
def test_binja_features(feature_fixture):
fixtures.run_feature_fixture(BACKEND, feature_fixture)
extractor = fixtures.get_binja_extractor(feature_fixture.sample_path)
fixtures.run_feature_fixture(extractor, feature_fixture)
@pytest.mark.skipif(
@@ -65,10 +62,7 @@ def test_binja_features(feature_fixture):
reason="Skip binja tests if the binaryninja Python API is not installed",
)
def test_standalone_binja_backend():
CD = Path(__file__).resolve().parent
test_path = (
CD / ".." / "tests" / "data" / "Practical Malware Analysis Lab 01-01.exe_"
)
test_path = fixtures.CD / "data" / "Practical Malware Analysis Lab 01-01.exe_"
assert capa.main.main([str(test_path), "-b", capa.main.BACKEND_BINJA]) == 0

View File

@@ -16,6 +16,7 @@ import textwrap
import capa.rules
import capa.features.common
import fixtures
import capa.capabilities.common
import capa.features.extractors.null
from capa.features.address import AbsoluteVirtualAddress
@@ -212,7 +213,8 @@ def test_byte_matching(z9324d_extractor):
assert "byte match test" in capabilities.matches
def test_com_feature_matching(z395eb_extractor):
def test_com_feature_matching():
extractor = fixtures.get_viv_extractor(fixtures.CD / "data" / "395eb0ddd99d2c9e37b6d0b73485ee9c.exe_")
rules = capa.rules.RuleSet([
capa.rules.Rule.from_yaml(
textwrap.dedent("""
@@ -230,7 +232,7 @@ def test_com_feature_matching(z395eb_extractor):
""")
)
])
capabilities = capa.capabilities.common.find_capabilities(rules, z395eb_extractor)
capabilities = capa.capabilities.common.find_capabilities(rules, extractor)
assert "initialize IWebBrowser2" in capabilities.matches

View File

@@ -11,17 +11,15 @@
# 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 fixtures
BACKEND = fixtures.BackendFeaturePolicy(
name="cape",
get_extractor=fixtures.get_cape_extractor,
include_tags={"cape"},
@fixtures.parametrize_backend_feature_fixtures(
fixtures.BackendFeaturePolicy(
name="cape",
include_tags={"cape"},
)
)
@fixtures.parametrize_backend_feature_fixtures(BACKEND)
def test_cape_features(feature_fixture):
fixtures.run_feature_fixture(BACKEND, feature_fixture)
extractor = fixtures.get_cape_extractor(feature_fixture.sample_path)
fixtures.run_feature_fixture(extractor, feature_fixture)

View File

@@ -14,7 +14,6 @@
import gzip
from typing import Type
from pathlib import Path
import pytest
import fixtures
@@ -22,8 +21,7 @@ import fixtures
from capa.exceptions import EmptyReportError, UnsupportedFormatError
from capa.features.extractors.cape.models import Call, CapeReport
CD = Path(__file__).resolve().parent
CAPE_DIR = CD / "data" / "dynamic" / "cape"
CAPE_DIR = fixtures.CD / "data" / "dynamic" / "cape"
@fixtures.parametrize(

View File

@@ -11,16 +11,15 @@
# 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 fixtures
BACKEND = fixtures.BackendFeaturePolicy(
name="dnfile",
get_extractor=fixtures.get_dnfile_extractor,
include_tags={"dotnet"},
@fixtures.parametrize_backend_feature_fixtures(
fixtures.BackendFeaturePolicy(
name="dnfile",
include_tags={"dotnet"},
)
)
@fixtures.parametrize_backend_feature_fixtures(BACKEND)
def test_dnfile_features(feature_fixture):
fixtures.run_feature_fixture(BACKEND, feature_fixture)
extractor = fixtures.get_dnfile_extractor(feature_fixture.sample_path)
fixtures.run_feature_fixture(extractor, feature_fixture)

View File

@@ -14,37 +14,53 @@
import fixtures
BACKEND = fixtures.BackendFeaturePolicy(
name="dotnetfile",
get_extractor=fixtures.get_dotnetfile_extractor,
include_tags={"dotnet"},
exclude_tags={
# dotnetfile is a file-scope extractor; drop non-file scopes
"function",
"basic-block",
"instruction",
# and drop feature types dotnetfile doesn't produce
"function-name",
},
@fixtures.parametrize_backend_feature_fixtures(
fixtures.BackendFeaturePolicy(
name="dotnetfile",
include_tags={"dotnet"},
exclude_tags={
# dotnetfile is a file-scope extractor; drop non-file scopes
"function",
"basic-block",
"instruction",
# and drop feature types dotnetfile doesn't produce
"function-name",
},
)
)
@fixtures.parametrize_backend_feature_fixtures(BACKEND)
def test_dotnetfile_features(feature_fixture):
fixtures.run_feature_fixture(BACKEND, feature_fixture)
extractor = fixtures.get_dotnetfile_extractor(feature_fixture.sample_path)
fixtures.run_feature_fixture(extractor, feature_fixture)
@fixtures.parametrize(
"extractor,function,expected",
[
("b9f5b_dotnetfile_extractor", "is_dotnet_file", True),
("b9f5b_dotnetfile_extractor", "is_mixed_mode", False),
("mixed_mode_64_dotnetfile_extractor", "is_mixed_mode", True),
("b9f5b_dotnetfile_extractor", "get_entry_point", 0x6000007),
("b9f5b_dotnetfile_extractor", "get_runtime_version", (2, 5)),
("b9f5b_dotnetfile_extractor", "get_meta_version_string", "v2.0.50727"),
],
)
def test_dotnetfile_extractor(request, extractor, function, expected):
extractor_function = getattr(request.getfixturevalue(extractor), function)
assert extractor_function() == expected
def test_dotnetfile_extractor_is_dotnet_file():
extractor = fixtures.get_dotnetfile_extractor(fixtures.CD / "data" / "b9f5bd514485fb06da39beff051b9fdc.exe_")
assert extractor.is_dotnet_file() is True
def test_dotnetfile_extractor_is_not_mixed_mode():
extractor = fixtures.get_dotnetfile_extractor(fixtures.CD / "data" / "b9f5bd514485fb06da39beff051b9fdc.exe_")
assert extractor.is_mixed_mode() is False
def test_dotnetfile_extractor_mixed_mode_64_is_mixed_mode():
extractor = fixtures.get_dotnetfile_extractor(
fixtures.DNFILE_TESTFILES / "mixed-mode" / "ModuleCode" / "bin" / "ModuleCode_amd64.exe"
)
assert extractor.is_mixed_mode() is True
def test_dotnetfile_extractor_get_entry_point():
extractor = fixtures.get_dotnetfile_extractor(fixtures.CD / "data" / "b9f5bd514485fb06da39beff051b9fdc.exe_")
assert extractor.get_entry_point() == 0x6000007
def test_dotnetfile_extractor_get_runtime_version():
extractor = fixtures.get_dotnetfile_extractor(fixtures.CD / "data" / "b9f5bd514485fb06da39beff051b9fdc.exe_")
assert extractor.get_runtime_version() == (2, 5)
def test_dotnetfile_extractor_get_meta_version_string():
extractor = fixtures.get_dotnetfile_extractor(fixtures.CD / "data" / "b9f5bd514485fb06da39beff051b9fdc.exe_")
assert extractor.get_meta_version_string() == "v2.0.50727"

View File

@@ -11,17 +11,15 @@
# 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 fixtures
BACKEND = fixtures.BackendFeaturePolicy(
name="drakvuf",
get_extractor=fixtures.get_drakvuf_extractor,
include_tags={"drakvuf"},
@fixtures.parametrize_backend_feature_fixtures(
fixtures.BackendFeaturePolicy(
name="drakvuf",
include_tags={"drakvuf"},
)
)
@fixtures.parametrize_backend_feature_fixtures(BACKEND)
def test_drakvuf_features(feature_fixture):
fixtures.run_feature_fixture(BACKEND, feature_fixture)
extractor = fixtures.get_drakvuf_extractor(feature_fixture.sample_path)
fixtures.run_feature_fixture(extractor, feature_fixture)

View File

@@ -65,7 +65,7 @@ def filter_threads(extractor: DynamicFeatureExtractor, ppid: int, pid: int, tid:
@lru_cache(maxsize=1)
def get_0000a657_thread3064():
extractor = fixtures.get_cape_extractor(fixtures.get_data_path_by_name("0000a657"))
extractor = fixtures.get_cape_extractor(fixtures.CD / "data" / "dynamic" / "cape" / "v2.2" / "0000a65749f5902c4d82ffa701198038f0b4870b00a27cfca109f8f933476d82.json.gz")
extractor = filter_threads(extractor, 2456, 3052, 3064)
return extractor

View File

@@ -15,13 +15,14 @@
import io
from pathlib import Path
import fixtures
from elftools.elf.elffile import ELFFile
from capa.features.extractors.elffile import extract_file_export_names, extract_file_import_names
CD = Path(__file__).resolve().parent
SAMPLE_PATH = CD / "data" / "055da8e6ccfe5a9380231ea04b850e18.elf_"
STRIPPED_SAMPLE_PATH = CD / "data" / "bb38149ff4b5c95722b83f24ca27a42b.elf_"
SAMPLE_PATH = fixtures.CD / "data" / "055da8e6ccfe5a9380231ea04b850e18.elf_"
STRIPPED_SAMPLE_PATH = fixtures.CD / "data" / "bb38149ff4b5c95722b83f24ca27a42b.elf_"
def check_import_features(sample_path, expected_imports):

View File

@@ -24,7 +24,7 @@ logger = logging.getLogger(__name__)
def test_viv_hash_extraction():
assert fixtures.get_viv_extractor(fixtures.get_data_path_by_name("mimikatz")).get_sample_hashes() == SampleHashes(
assert fixtures.get_viv_extractor(fixtures.CD / "data" / "mimikatz.exe_").get_sample_hashes() == SampleHashes(
md5="5f66b82558ca92e54e77f216ef4c066c",
sha1="e4f82e4d7f22938dc0a0ff8a4a7ad2a763643d38",
sha256="131314a6f6d1d263c75b9909586b3e1bd837036329ace5e69241749e861ac01d",
@@ -33,7 +33,7 @@ def test_viv_hash_extraction():
def test_pefile_hash_extraction():
assert fixtures.get_pefile_extractor(
fixtures.get_data_path_by_name("mimikatz")
fixtures.CD / "data" / "mimikatz.exe_"
).get_sample_hashes() == SampleHashes(
md5="5f66b82558ca92e54e77f216ef4c066c",
sha1="e4f82e4d7f22938dc0a0ff8a4a7ad2a763643d38",
@@ -42,7 +42,7 @@ def test_pefile_hash_extraction():
def test_dnfile_hash_extraction():
assert fixtures.get_dnfile_extractor(fixtures.get_data_path_by_name("b9f5b")).get_sample_hashes() == SampleHashes(
assert fixtures.get_dnfile_extractor(fixtures.CD / "data" / "b9f5bd514485fb06da39beff051b9fdc.exe_").get_sample_hashes() == SampleHashes(
md5="b9f5bd514485fb06da39beff051b9fdc",
sha1="c72a2e50410475a51d897d29ffbbaf2103754d53",
sha256="34acc4c0b61b5ce0b37c3589f97d1f23e6d84011a241e6f85683ee517ce786f1",
@@ -51,7 +51,7 @@ def test_dnfile_hash_extraction():
def test_dotnetfile_hash_extraction():
assert fixtures.get_dotnetfile_extractor(
fixtures.get_data_path_by_name("b9f5b")
fixtures.CD / "data" / "b9f5bd514485fb06da39beff051b9fdc.exe_"
).get_sample_hashes() == SampleHashes(
md5="b9f5bd514485fb06da39beff051b9fdc",
sha1="c72a2e50410475a51d897d29ffbbaf2103754d53",
@@ -60,7 +60,7 @@ def test_dotnetfile_hash_extraction():
def test_cape_hash_extraction():
assert fixtures.get_cape_extractor(fixtures.get_data_path_by_name("0000a657")).get_sample_hashes() == SampleHashes(
assert fixtures.get_cape_extractor(fixtures.CD / "data" / "dynamic" / "cape" / "v2.2" / "0000a65749f5902c4d82ffa701198038f0b4870b00a27cfca109f8f933476d82.json.gz").get_sample_hashes() == SampleHashes(
md5="e2147b5333879f98d515cd9aa905d489",
sha1="ad4d520fb7792b4a5701df973d6bd8a6cbfbb57f",
sha256="0000a65749f5902c4d82ffa701198038f0b4870b00a27cfca109f8f933476d82",
@@ -84,7 +84,7 @@ except ImportError:
@pytest.mark.skipif(binja_present is False, reason="Skip binja tests if the binaryninja Python API is not installed")
def test_binja_hash_extraction():
extractor = fixtures.get_binja_extractor(fixtures.get_data_path_by_name("mimikatz"))
extractor = fixtures.get_binja_extractor(fixtures.CD / "data" / "mimikatz.exe_")
hashes = SampleHashes(
md5="5f66b82558ca92e54e77f216ef4c066c",
sha1="e4f82e4d7f22938dc0a0ff8a4a7ad2a763643d38",

View File

@@ -160,7 +160,7 @@ def test_freeze_bytes_roundtrip():
def test_freeze_load_sample(tmpdir):
o = tmpdir.mkdir("capa").join("test.frz")
extractor = fixtures.get_cape_extractor(fixtures.get_data_path_by_name("d46900"))
extractor = fixtures.get_cape_extractor(fixtures.CD / "data" / "dynamic" / "cape" / "v2.2" / "d46900384c78863420fb3e297d0a2f743cd2b6b3f7f82bf64059a168e07aceb7.json.gz")
Path(o.strpath).write_bytes(capa.features.freeze.dump(extractor))

View File

@@ -15,11 +15,10 @@
import textwrap
from pathlib import Path
import pytest
import fixtures
import capa.main
import capa.rules
import capa.helpers
import capa.features.file
import capa.features.insn
import capa.features.common
@@ -192,26 +191,15 @@ def test_no_address_lt_irreflexivity():
assert not (no_addr < no_addr)
def test_freeze_sample(tmpdir, z9324d_extractor):
def test_freeze_sample(tmpdir):
# tmpdir fixture handles cleanup
o = tmpdir.mkdir("capa").join("test.frz").strpath
path = z9324d_extractor.path
path = str(fixtures.CD / "data" / "9324d1a8ae37a36ae560c37448c9705a.exe_")
assert capa.features.freeze.main([path, o, "-v"]) == 0
@pytest.mark.parametrize(
"extractor",
[
pytest.param("z9324d_extractor"),
],
)
def test_freeze_load_sample(tmpdir, request, extractor):
def test_freeze_load_sample(tmpdir, z9324d_extractor):
o = tmpdir.mkdir("capa").join("test.frz")
extractor = request.getfixturevalue(extractor)
Path(o.strpath).write_bytes(capa.features.freeze.dump(extractor))
Path(o.strpath).write_bytes(capa.features.freeze.dump(z9324d_extractor))
null_extractor = capa.features.freeze.load(Path(o.strpath).read_bytes())
compare_extractors(extractor, null_extractor)
compare_extractors(z9324d_extractor, null_extractor)

View File

@@ -23,15 +23,14 @@ ghidra_present = (
)
BACKEND = fixtures.BackendFeaturePolicy(
name="ghidra",
get_extractor=fixtures.get_ghidra_extractor,
include_tags={"static"},
exclude_tags={"dotnet"},
)
@pytest.mark.skipif(ghidra_present is False, reason="PyGhidra not installed")
@fixtures.parametrize_backend_feature_fixtures(BACKEND)
@fixtures.parametrize_backend_feature_fixtures(
fixtures.BackendFeaturePolicy(
name="ghidra",
include_tags={"static"},
exclude_tags={"dotnet"},
)
)
def test_ghidra_features(feature_fixture):
fixtures.run_feature_fixture(BACKEND, feature_fixture)
extractor = fixtures.get_ghidra_extractor(feature_fixture.sample_path)
fixtures.run_feature_fixture(extractor, feature_fixture)

View File

@@ -16,18 +16,19 @@ import logging
import fixtures
import pytest
import capa.features.extractors.ida.idalib
import capa.features.extractors.ida.idalib as idalib
from capa.features.common import Characteristic
from capa.features.file import FunctionName
from capa.features.insn import API
logger = logging.getLogger(__name__)
idalib_present = capa.features.extractors.ida.idalib.has_idalib()
idalib_present = idalib.has_idalib()
if idalib_present:
try:
if True:
import idapro # noqa: F401 [imported but unused]
import ida_kernwin
import idapro # noqa: F401 [imported but unused]
kernel_version: str = ida_kernwin.get_kernel_version()
except ImportError:
@@ -37,19 +38,17 @@ else:
kernel_version = "0.0"
BACKEND = fixtures.BackendFeaturePolicy(
name="idalib",
get_extractor=fixtures.get_idalib_extractor,
include_tags={"static"},
exclude_tags={"dotnet", "ghidra"},
)
@pytest.mark.skipif(
idalib_present is False,
reason="Skip idalib tests if the idalib Python API is not installed",
)
@fixtures.parametrize_backend_feature_fixtures(BACKEND)
@fixtures.parametrize_backend_feature_fixtures(
fixtures.BackendFeaturePolicy(
name="idalib",
include_tags={"static"},
exclude_tags={"dotnet", "ghidra"},
)
)
def test_idalib_features(feature_fixture):
# apply runtime-conditional xfails for specific IDA versions.
# version-specific behavior stays in the test body because it
@@ -79,7 +78,8 @@ def test_idalib_features(feature_fixture):
pytest.xfail("idalib 9.0 does not support loading resource segments")
try:
fixtures.run_feature_fixture(BACKEND, feature_fixture)
extractor = fixtures.get_idalib_extractor(feature_fixture.sample_path)
fixtures.run_feature_fixture(extractor, feature_fixture)
finally:
import idapro

View File

@@ -21,13 +21,11 @@ import fixtures
import capa.main
import capa.rules
import capa.engine
import capa.features
def test_main(z9324d_extractor):
def test_main():
# tests rules can be loaded successfully and all output modes
path = z9324d_extractor.path
path = str(fixtures.PMA1601)
assert capa.main.main([path, "-vv"]) == 0
assert capa.main.main([path, "-v"]) == 0
assert capa.main.main([path, "-j"]) == 0
@@ -35,7 +33,7 @@ def test_main(z9324d_extractor):
assert capa.main.main([path]) == 0
def test_main_single_rule(z9324d_extractor, tmpdir):
def test_main_single_rule(tmpdir):
# tests a single rule can be loaded successfully
RULE_CONTENT = textwrap.dedent("""
rule:
@@ -49,12 +47,11 @@ def test_main_single_rule(z9324d_extractor, tmpdir):
features:
- string: test
""")
path = z9324d_extractor.path
rule_file = tmpdir.mkdir("capa").join("rule.yml")
rule_file.write(RULE_CONTENT)
assert (
capa.main.main([
path,
str(fixtures.PMA1601),
"-v",
"-r",
rule_file.strpath,
@@ -63,16 +60,17 @@ def test_main_single_rule(z9324d_extractor, tmpdir):
)
def test_main_non_ascii_filename(pingtaest_extractor, tmpdir, capsys):
def test_main_non_ascii_filename(tmpdir, capsys):
# here we print a string with unicode characters in it
# (specifically, a byte string with utf-8 bytes in it, see file encoding)
# only use one rule to speed up analysis
assert capa.main.main(["-q", pingtaest_extractor.path, "-r", "rules/communication/icmp"]) == 0
path = str(fixtures.CD / "./data/ping_täst.exe_")
assert capa.main.main(["-q", path, "-r", "rules/communication/icmp"]) == 0
std = capsys.readouterr()
# but here, we have to use a unicode instance,
# because capsys has decoded the output for us.
assert pingtaest_extractor.path in std.out
assert path in std.out
def test_main_non_ascii_filename_nonexistent(tmpdir, caplog):
@@ -82,8 +80,9 @@ def test_main_non_ascii_filename_nonexistent(tmpdir, caplog):
assert NON_ASCII_FILENAME in caplog.text
def test_main_shellcode(z499c2_extractor):
path = z499c2_extractor.path
def test_main_shellcode():
path = str(fixtures.CD / "./data/499c2a85f6e8142c3f48d4251c9c7cd6.raw32")
assert capa.main.main([path, "-vv", "-f", "sc32"]) == 0
assert capa.main.main([path, "-v", "-f", "sc32"]) == 0
assert capa.main.main([path, "-j", "-f", "sc32"]) == 0
@@ -200,8 +199,8 @@ def test_ruleset():
assert len(rules.call_rules) == 2
def test_fix262(pma16_01_extractor, capsys):
path = pma16_01_extractor.path
def test_fix262(capsys):
path = str(fixtures.CD / "./data/Practical Malware Analysis Lab 16-01.exe_")
assert capa.main.main([path, "-vv", "-t", "send HTTP request", "-q"]) == 0
std = capsys.readouterr()
@@ -209,11 +208,11 @@ def test_fix262(pma16_01_extractor, capsys):
assert "www.practicalmalwareanalysis.com" not in std.out
def test_not_render_rules_also_matched(z9324d_extractor, capsys):
def test_not_render_rules_also_matched(capsys):
# rules that are also matched by other rules should not get rendered by default.
# this cuts down on the amount of output while giving approx the same detail.
# see #224
path = z9324d_extractor.path
path = str(fixtures.CD / "./data/9324d1a8ae37a36ae560c37448c9705a.exe_")
# `act as TCP client` matches on
# `connect TCP client` matches on
@@ -236,7 +235,7 @@ def test_not_render_rules_also_matched(z9324d_extractor, capsys):
def test_json_meta(capsys):
path = str(fixtures.get_data_path_by_name("pma01-01"))
path = str(fixtures.CD / "./data/Practical Malware Analysis Lab 01-01.dll_")
assert capa.main.main([path, "-j"]) == 0
std = capsys.readouterr()
std_json = json.loads(std.out)
@@ -250,9 +249,9 @@ def test_json_meta(capsys):
assert {"address": ["absolute", 0x10001179]} in info["matched_basic_blocks"]
def test_main_dotnet(_1c444_dotnetfile_extractor):
def test_main_dotnet():
# tests successful execution and all output modes
path = _1c444_dotnetfile_extractor.path
path = str(fixtures.CD / "./data/dotnet/1c444ebeba24dcba8628b7dfe5fec7c6.exe_")
assert capa.main.main([path, "-vv"]) == 0
assert capa.main.main([path, "-v"]) == 0
assert capa.main.main([path, "-j"]) == 0
@@ -260,27 +259,27 @@ def test_main_dotnet(_1c444_dotnetfile_extractor):
assert capa.main.main([path]) == 0
def test_main_dotnet2(_692f_dotnetfile_extractor):
def test_main_dotnet2():
# tests successful execution and one rendering
# above covers all output modes
path = _692f_dotnetfile_extractor.path
path = str(fixtures.CD / "./data/dotnet/692f7fd6d198e804d6af98eb9e390d61.exe_")
assert capa.main.main([path, "-vv"]) == 0
def test_main_dotnet3(_0953c_dotnetfile_extractor):
def test_main_dotnet3():
# tests successful execution and one rendering
path = _0953c_dotnetfile_extractor.path
path = str(fixtures.CD / "./data/0953cc3b77ed2974b09e3a00708f88de931d681e2d0cb64afbaf714610beabe6.exe_")
assert capa.main.main([path, "-vv"]) == 0
def test_main_dotnet4(_039a6_dotnetfile_extractor):
def test_main_dotnet4():
# tests successful execution and one rendering
path = _039a6_dotnetfile_extractor.path
path = str(fixtures.CD / "./data/039a6336d0802a2255669e6867a5679c7eb83313dbc61fb1c7232147379bd304.exe_")
assert capa.main.main([path, "-vv"]) == 0
def test_main_rd():
path = str(fixtures.get_data_path_by_name("pma01-01-rd"))
path = str(fixtures.CD / "./data/rd/Practical Malware Analysis Lab 01-01.dll_.json")
assert capa.main.main([path, "-vv"]) == 0
assert capa.main.main([path, "-v"]) == 0
assert capa.main.main([path, "-j"]) == 0
@@ -295,7 +294,7 @@ def extract_cape_report(tmp_path: Path, gz: Path) -> Path:
def test_main_cape1(tmp_path):
path = extract_cape_report(tmp_path, fixtures.get_data_path_by_name("0000a657"))
path = extract_cape_report(tmp_path, fixtures.CD / "./data/dynamic/cape/v2.2/0000a65749f5902c4d82ffa701198038f0b4870b00a27cfca109f8f933476d82.json.gz")
# TODO(williballenthin): use default rules set
# https://github.com/mandiant/capa/pull/1696
@@ -344,5 +343,5 @@ def test_main_cape1(tmp_path):
def test_main_cape_gzip():
# tests successful execution of .json.gz
path = str(fixtures.get_data_path_by_name("0000a657"))
path = str(fixtures.CD / "./data/dynamic/cape/v2.2/0000a65749f5902c4d82ffa701198038f0b4870b00a27cfca109f8f933476d82.json.gz")
assert capa.main.main([path]) == 0

View File

@@ -14,9 +14,8 @@
import io
import zlib
from pathlib import Path
from fixtures import get_data_path_by_name
import fixtures
import capa.features.extractors.elf
import capa.features.extractors.common
@@ -31,8 +30,8 @@ def test_elf_sh_notes():
# guess: ABI versions needed: None
# guess: symtab: None
# guess: needed dependencies: None
path = get_data_path_by_name("2f7f5f")
with Path(path).open("rb") as f:
path = fixtures.CD / "data" / "2f7f5fb5de175e770d7eae87666f9831.elf_"
with path.open("rb") as f:
assert capa.features.extractors.elf.detect_elf_os(f) == "linux"
@@ -44,8 +43,8 @@ def test_elf_pt_notes():
# guess: ABI versions needed: OS.LINUX
# guess: symtab: None
# guess: needed dependencies: None
path = get_data_path_by_name("7351f.elf")
with Path(path).open("rb") as f:
path = fixtures.CD / "data" / "7351f8a40c5450557b24622417fc478d.elf_"
with path.open("rb") as f:
assert capa.features.extractors.elf.detect_elf_os(f) == "linux"
@@ -57,8 +56,8 @@ def test_elf_so_needed():
# guess: ABI versions needed: OS.HURD
# guess: symtab: None
# guess: needed dependencies: OS.HURD
path = get_data_path_by_name("b5f052")
with Path(path).open("rb") as f:
path = fixtures.CD / "data" / "b5f0524e69b3a3cf636c7ac366ca57bf5e3a8fdc8a9f01caf196c611a7918a87.elf_"
with path.open("rb") as f:
assert capa.features.extractors.elf.detect_elf_os(f) == "hurd"
@@ -70,8 +69,8 @@ def test_elf_abi_version_hurd():
# guess: ABI versions needed: OS.HURD
# guess: symtab: None
# guess: needed dependencies: None
path = get_data_path_by_name("bf7a9c")
with Path(path).open("rb") as f:
path = fixtures.CD / "data" / "bf7a9c8bdfa6d47e01ad2b056264acc3fd90cf43fe0ed8deec93ab46b47d76cb.elf_"
with path.open("rb") as f:
assert capa.features.extractors.elf.detect_elf_os(f) == "hurd"
@@ -83,8 +82,8 @@ def test_elf_symbol_table():
# guess: ABI versions needed: None
# guess: symtab: OS.LINUX
# guess: needed dependencies: None
path = get_data_path_by_name("2bf18d")
with Path(path).open("rb") as f:
path = fixtures.CD / "data" / "2bf18d0403677378adad9001b1243211.elf_"
with path.open("rb") as f:
assert capa.features.extractors.elf.detect_elf_os(f) == "linux"
@@ -95,14 +94,14 @@ def test_elf_android_notes():
# DEBUG:capa.features.extractors.elf:guess: linker: None
# DEBUG:capa.features.extractors.elf:guess: ABI versions needed: None
# DEBUG:capa.features.extractors.elf:guess: needed dependencies: OS.ANDROID
path = get_data_path_by_name("1038a2")
with Path(path).open("rb") as f:
path = fixtures.CD / "data" / "1038a23daad86042c66bfe6c9d052d27048de9653bde5750dc0f240c792d9ac8.elf_"
with path.open("rb") as f:
assert capa.features.extractors.elf.detect_elf_os(f) == "android"
def test_elf_go_buildinfo():
path = get_data_path_by_name("3da7c")
with Path(path).open("rb") as f:
path = fixtures.CD / "data" / "3da7c2c70a2d93ac4643f20339d5c7d61388bddd77a4a5fd732311efad78e535.elf_"
with path.open("rb") as f:
assert capa.features.extractors.elf.detect_elf_os(f) == "linux"

View File

@@ -11,26 +11,25 @@
# 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 fixtures
BACKEND = fixtures.BackendFeaturePolicy(
name="pefile",
get_extractor=fixtures.get_pefile_extractor,
include_tags={"static"},
exclude_tags={
"dotnet",
"elf",
# pefile is a file-scope extractor; drop non-file scopes
"function",
"basic-block",
"instruction",
# and drop feature types pefile doesn't produce
"function-name",
},
@fixtures.parametrize_backend_feature_fixtures(
fixtures.BackendFeaturePolicy(
name="pefile",
include_tags={"static"},
exclude_tags={
"dotnet",
"elf",
# pefile is a file-scope extractor; drop non-file scopes
"function",
"basic block",
"instruction",
# and drop feature types pefile doesn't produce
"function-name",
},
)
)
@fixtures.parametrize_backend_feature_fixtures(BACKEND)
def test_pefile_features(feature_fixture):
fixtures.run_feature_fixture(BACKEND, feature_fixture)
extractor = fixtures.get_pefile_extractor(feature_fixture.sample_path)
fixtures.run_feature_fixture(extractor, feature_fixture)

View File

@@ -284,12 +284,12 @@ def test_round_trip(request, rd_file):
def test_json_to_rdoc():
path = fixtures.get_data_path_by_name("pma01-01-rd")
path = fixtures.CD / "data" / "rd" / "Practical Malware Analysis Lab 01-01.dll_.json"
assert isinstance(rdoc.ResultDocument.from_file(path), rdoc.ResultDocument)
def test_rdoc_to_capa():
path = fixtures.get_data_path_by_name("pma01-01-rd")
path = fixtures.CD / "data" / "rd" / "Practical Malware Analysis Lab 01-01.dll_.json"
rd = rdoc.ResultDocument.from_file(path)

View File

@@ -22,22 +22,22 @@ from pathlib import Path
import pytest
logger = logging.getLogger(__name__)
import fixtures
CD = Path(__file__).resolve().parent
logger = logging.getLogger(__name__)
def get_script_path(s: str):
return str(CD / ".." / "scripts" / s)
return str(fixtures.CD /".." / "scripts" / s)
def get_binary_file_path():
return str(CD / "data" / "9324d1a8ae37a36ae560c37448c9705a.exe_")
return str(fixtures.CD /"data" / "9324d1a8ae37a36ae560c37448c9705a.exe_")
def get_cape_report_file_path():
return str(
CD
fixtures.CD
/ "data"
/ "dynamic"
/ "cape"
@@ -47,11 +47,11 @@ def get_cape_report_file_path():
def get_binexport2_file_path():
return str(CD / "data" / "binexport2" / "mimikatz.exe_.ghidra.BinExport")
return str(fixtures.CD /"data" / "binexport2" / "mimikatz.exe_.ghidra.BinExport")
def get_rules_path():
return str(CD / ".." / "rules")
return str(fixtures.CD /".." / "rules")
def get_rule_path():
@@ -65,7 +65,7 @@ def get_rule_path():
pytest.param("capafmt.py", [get_rule_path()]),
pytest.param(
"capa2sarif.py",
[Path(__file__).resolve().parent / "data" / "rd" / "Practical Malware Analysis Lab 01-01.dll_.json"],
[fixtures.CD /"data" / "rd" / "Practical Malware Analysis Lab 01-01.dll_.json"],
),
# testing some variations of linter script
pytest.param("lint.py", ["-t", "create directory", get_rules_path()]),
@@ -96,7 +96,7 @@ def test_scripts(script, args):
)
def test_binexport_scripts(script, args):
# define sample bytes location
os.environ["CAPA_SAMPLES_DIR"] = str(Path(CD / "data"))
os.environ["CAPA_SAMPLES_DIR"] = str(fixtures.CD / "data")
script_path = get_script_path(script)
p = run_program(script_path, args)
@@ -108,7 +108,7 @@ def test_bulk_process(tmp_path):
t = tmp_path / "test"
t.mkdir()
source_file = Path(__file__).resolve().parent / "data" / "ping_täst.exe_"
source_file = fixtures.CD /"data" / "ping_täst.exe_"
dest_file = t / "test.exe_"
dest_file.write_bytes(source_file.read_bytes())
@@ -127,7 +127,7 @@ def run_program(script_path, args):
def test_proto_conversion(tmp_path):
t = tmp_path / "proto-test"
t.mkdir()
json_file = Path(__file__).resolve().parent / "data" / "rd" / "Practical Malware Analysis Lab 01-01.dll_.json"
json_file = fixtures.CD /"data" / "rd" / "Practical Malware Analysis Lab 01-01.dll_.json"
p = run_program(get_script_path("proto-from-results.py"), [json_file])
assert p.returncode == 0

View File

@@ -11,17 +11,17 @@
# 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 fixtures
BACKEND = fixtures.BackendFeaturePolicy(
name="viv",
get_extractor=fixtures.get_viv_extractor,
include_tags={"static"},
exclude_tags={"dotnet", "ghidra"},
@fixtures.parametrize_backend_feature_fixtures(
fixtures.BackendFeaturePolicy(
name="viv",
include_tags={"static"},
exclude_tags={"dotnet", "ghidra"},
)
)
@fixtures.parametrize_backend_feature_fixtures(BACKEND)
def test_viv_features(feature_fixture):
fixtures.run_feature_fixture(BACKEND, feature_fixture)
extractor = fixtures.get_viv_extractor(feature_fixture.sample_path)
fixtures.run_feature_fixture(extractor, feature_fixture)

View File

@@ -11,24 +11,22 @@
# 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 fixtures
BACKEND = fixtures.BackendFeaturePolicy(
name="vmray",
get_extractor=fixtures.get_vmray_extractor,
include_tags={"vmray"},
@fixtures.parametrize_backend_feature_fixtures(
fixtures.BackendFeaturePolicy(
name="vmray",
include_tags={"vmray"},
)
)
@fixtures.parametrize_backend_feature_fixtures(BACKEND)
def test_vmray_features(feature_fixture):
fixtures.run_feature_fixture(BACKEND, feature_fixture)
extractor = fixtures.get_vmray_extractor(feature_fixture.sample_path)
fixtures.run_feature_fixture(extractor, feature_fixture)
def test_vmray_processes():
# see #2394
path = fixtures.get_data_path_by_name("2f8a79-vmray")
path = fixtures.CD / "data" / "dynamic" / "vmray" / "2f8a79b12a7a989ac7e5f6ec65050036588a92e65aeb6841e08dc228ff0e21b4_min_archive.zip"
vmre = fixtures.get_vmray_extractor(path)
assert len(vmre.analysis.monitor_processes) == 9