mirror of
https://github.com/mandiant/capa.git
synced 2025-12-24 12:03:24 -08:00
Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#322
This commit is contained in:
@@ -180,6 +180,16 @@ def get_binja_extractor(path: Path):
|
||||
return extractor
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def get_ghidra_extractor(path: Path):
|
||||
import capa.features.extractors.ghidra.extractor
|
||||
|
||||
extractor = capa.features.extractors.ghidra.extractor.GhidraFeatureExtractor()
|
||||
setattr(extractor, "path", path.as_posix())
|
||||
|
||||
return extractor
|
||||
|
||||
|
||||
def extract_global_features(extractor):
|
||||
features = collections.defaultdict(set)
|
||||
for feature, va in extractor.extract_global_features():
|
||||
@@ -359,7 +369,7 @@ def get_sample_md5_by_name(name):
|
||||
elif name.startswith("3b13b"):
|
||||
# file name is SHA256 hash
|
||||
return "56a6ffe6a02941028cc8235204eef31d"
|
||||
elif name == "7351f.elf":
|
||||
elif name.startswith("7351f"):
|
||||
return "7351f8a40c5450557b24622417fc478d"
|
||||
elif name.startswith("79abd"):
|
||||
return "79abd17391adc6251ecdc58d13d76baf"
|
||||
@@ -1055,6 +1065,14 @@ FEATURE_COUNT_TESTS_DOTNET = [
|
||||
]
|
||||
|
||||
|
||||
FEATURE_COUNT_TESTS_GHIDRA = [
|
||||
# Ghidra may render functions as labels, as well as provide differing amounts of call references
|
||||
# (Colton) TODO: Add more test cases
|
||||
("mimikatz", "function=0x4702FD", capa.features.common.Characteristic("calls from"), 0),
|
||||
("mimikatz", "function=0x4556E5", capa.features.common.Characteristic("calls to"), 0),
|
||||
]
|
||||
|
||||
|
||||
def do_test_feature_presence(get_extractor, sample, scope, feature, expected):
|
||||
extractor = get_extractor(sample)
|
||||
features = scope(extractor)
|
||||
|
||||
98
tests/test_ghidra_features.py
Normal file
98
tests/test_ghidra_features.py
Normal file
@@ -0,0 +1,98 @@
|
||||
# Copyright (C) 2023 Mandiant, 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.
|
||||
"""
|
||||
Must invoke this script from within the Ghidra Runtime Enviornment
|
||||
"""
|
||||
import sys
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
try:
|
||||
sys.path.append(str(Path(__file__).parent))
|
||||
import fixtures
|
||||
finally:
|
||||
sys.path.pop()
|
||||
|
||||
|
||||
logger = logging.getLogger("test_ghidra_features")
|
||||
|
||||
ghidra_present: bool = False
|
||||
try:
|
||||
import ghidra # noqa: F401
|
||||
|
||||
ghidra_present = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def standardize_posix_str(psx_str):
|
||||
"""fixture test passes the PosixPath to the test data
|
||||
|
||||
params: psx_str - PosixPath() to the test data
|
||||
return: string that matches test-id sample name
|
||||
"""
|
||||
|
||||
if "Practical Malware Analysis Lab" in str(psx_str):
|
||||
# <PosixPath>/'Practical Malware Analysis Lab 16-01.exe_' -> 'pma16-01'
|
||||
wanted_str = "pma" + str(psx_str).split("/")[-1][len("Practical Malware Analysis Lab ") : -5]
|
||||
else:
|
||||
# <PosixPath>/mimikatz.exe_ -> mimikatz
|
||||
wanted_str = str(psx_str).split("/")[-1][:-5]
|
||||
|
||||
if "_" in wanted_str:
|
||||
# al-khaser_x86 -> al-khaser x86
|
||||
wanted_str = wanted_str.replace("_", " ")
|
||||
|
||||
return wanted_str
|
||||
|
||||
|
||||
def check_input_file(wanted):
|
||||
"""check that test is running on the loaded sample
|
||||
|
||||
params: wanted - PosixPath() passed from test arg
|
||||
"""
|
||||
|
||||
import capa.ghidra.helpers as ghidra_helpers
|
||||
|
||||
found = ghidra_helpers.get_file_md5()
|
||||
sample_name = standardize_posix_str(wanted)
|
||||
|
||||
if not found.startswith(fixtures.get_sample_md5_by_name(sample_name)):
|
||||
raise RuntimeError(f"please run the tests against sample with MD5: `{found}`")
|
||||
|
||||
|
||||
@pytest.mark.skipif(ghidra_present is False, reason="Ghidra tests must be ran within Ghidra")
|
||||
@fixtures.parametrize("sample,scope,feature,expected", fixtures.FEATURE_PRESENCE_TESTS, indirect=["sample", "scope"])
|
||||
def test_ghidra_features(sample, scope, feature, expected):
|
||||
try:
|
||||
check_input_file(sample)
|
||||
except RuntimeError:
|
||||
pytest.skip(reason="Test must be ran against sample loaded in Ghidra")
|
||||
|
||||
fixtures.do_test_feature_presence(fixtures.get_ghidra_extractor, sample, scope, feature, expected)
|
||||
|
||||
|
||||
@pytest.mark.skipif(ghidra_present is False, reason="Ghidra tests must be ran within Ghidra")
|
||||
@fixtures.parametrize(
|
||||
"sample,scope,feature,expected", fixtures.FEATURE_COUNT_TESTS_GHIDRA, indirect=["sample", "scope"]
|
||||
)
|
||||
def test_ghidra_feature_counts(sample, scope, feature, expected):
|
||||
try:
|
||||
check_input_file(sample)
|
||||
except RuntimeError:
|
||||
pytest.skip(reason="Test must be ran against sample loaded in Ghidra")
|
||||
|
||||
fixtures.do_test_feature_count(fixtures.get_ghidra_extractor, sample, scope, feature, expected)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# No support for faulthandler module in Ghidrathon, see:
|
||||
# https://github.com/mandiant/Ghidrathon/issues/70
|
||||
sys.exit(pytest.main(["--pyargs", "-p no:faulthandler", "test_ghidra_features"]))
|
||||
Reference in New Issue
Block a user