Skip to content

Commit

Permalink
Merge pull request mandiant#2501 from xusheng6/binja_database_support
Browse files Browse the repository at this point in the history
Binja database support
  • Loading branch information
mr-tz authored Dec 2, 2024
2 parents abe8084 + 28fcd10 commit 54952fe
Show file tree
Hide file tree
Showing 10 changed files with 59 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### New Features

- allow call as valid subscope for call scoped rules @mr-tz
- support loading and analyzing a Binary Ninja database #2496 @xusheng6

### Breaking Changes

Expand Down
2 changes: 2 additions & 0 deletions capa/features/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ def evaluate(self, features: "capa.engine.FeatureSet", short_circuit=True):
FORMAT_BINEXPORT2 = "binexport2"
FORMAT_FREEZE = "freeze"
FORMAT_RESULT = "result"
FORMAT_BINJA_DB = "binja_database"
STATIC_FORMATS = {
FORMAT_SC32,
FORMAT_SC64,
Expand All @@ -475,6 +476,7 @@ def evaluate(self, features: "capa.engine.FeatureSet", short_circuit=True):
FORMAT_FREEZE,
FORMAT_RESULT,
FORMAT_BINEXPORT2,
FORMAT_BINJA_DB,
}
DYNAMIC_FORMATS = {
FORMAT_CAPE,
Expand Down
4 changes: 4 additions & 0 deletions capa/features/extractors/binja/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
FORMAT_ELF,
FORMAT_SC32,
FORMAT_SC64,
FORMAT_BINJA_DB,
Format,
String,
Feature,
Expand Down Expand Up @@ -137,6 +138,9 @@ def extract_file_function_names(bv: BinaryView) -> Iterator[tuple[Feature, Addre


def extract_file_format(bv: BinaryView) -> Iterator[tuple[Feature, Address]]:
if bv.file.database is not None:
yield Format(FORMAT_BINJA_DB), NO_ADDRESS

view_type = bv.view_type
if view_type in ["PE", "COFF"]:
yield Format(FORMAT_PE), NO_ADDRESS
Expand Down
6 changes: 3 additions & 3 deletions capa/features/extractors/binja/find_binja_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ def find_binaryninja() -> Optional[Path]:
logger.debug("detected OS: linux")
elif sys.platform == "darwin":
logger.warning("unsupported platform to find Binary Ninja: %s", sys.platform)
return False
return None
elif sys.platform == "win32":
logger.warning("unsupported platform to find Binary Ninja: %s", sys.platform)
return False
return None
else:
logger.warning("unsupported platform to find Binary Ninja: %s", sys.platform)
return False
return None

desktop_entry = get_desktop_entry("com.vector35.binaryninja.desktop")
if not desktop_entry:
Expand Down
4 changes: 4 additions & 0 deletions capa/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
FORMAT_FREEZE,
FORMAT_DRAKVUF,
FORMAT_UNKNOWN,
FORMAT_BINJA_DB,
FORMAT_BINEXPORT2,
Format,
)
Expand All @@ -59,6 +60,7 @@
EXTENSIONS_BINEXPORT2 = ("BinExport", "BinExport2")
EXTENSIONS_ELF = "elf_"
EXTENSIONS_FREEZE = "frz"
EXTENSIONS_BINJA_DB = "bndb"

logger = logging.getLogger("capa")

Expand Down Expand Up @@ -232,6 +234,8 @@ def get_format_from_extension(sample: Path) -> str:
format_ = FORMAT_FREEZE
elif sample.name.endswith(EXTENSIONS_BINEXPORT2):
format_ = FORMAT_BINEXPORT2
elif sample.name.endswith(EXTENSIONS_BINJA_DB):
format_ = FORMAT_BINJA_DB
return format_


Expand Down
3 changes: 2 additions & 1 deletion capa/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
FORMAT_VMRAY,
FORMAT_DOTNET,
FORMAT_DRAKVUF,
FORMAT_BINJA_DB,
FORMAT_BINEXPORT2,
)
from capa.features.address import Address
Expand Down Expand Up @@ -251,7 +252,7 @@ def get_extractor(

import capa.features.extractors.binja.extractor

if input_format not in (FORMAT_SC32, FORMAT_SC64):
if input_format not in (FORMAT_SC32, FORMAT_SC64, FORMAT_BINJA_DB):
if not is_supported_format(input_path):
raise UnsupportedFormatError()

Expand Down
2 changes: 2 additions & 0 deletions capa/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
FORMAT_DRAKVUF,
STATIC_FORMATS,
DYNAMIC_FORMATS,
FORMAT_BINJA_DB,
FORMAT_BINEXPORT2,
)
from capa.capabilities.common import find_capabilities, has_file_limitation, find_file_capabilities
Expand Down Expand Up @@ -266,6 +267,7 @@ def install_common_args(parser, wanted=None):
(FORMAT_VMRAY, "VMRay sandbox report"),
(FORMAT_FREEZE, "features previously frozen by capa"),
(FORMAT_BINEXPORT2, "BinExport2"),
(FORMAT_BINJA_DB, "Binary Ninja Database"),
]
format_help = ", ".join([f"{f[0]}: {f[1]}" for f in formats])

Expand Down
2 changes: 1 addition & 1 deletion tests/data
39 changes: 39 additions & 0 deletions tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ def get_data_path_by_name(name) -> Path:
return CD / "data" / "Practical Malware Analysis Lab 12-04.exe_"
elif name == "pma16-01":
return CD / "data" / "Practical Malware Analysis Lab 16-01.exe_"
elif name == "pma16-01_binja_db":
return CD / "data" / "Practical Malware Analysis Lab 16-01.exe_.bndb"
elif name == "pma21-01":
return CD / "data" / "Practical Malware Analysis Lab 21-01.exe_"
elif name == "al-khaser x86":
Expand Down Expand Up @@ -1387,6 +1389,43 @@ def parametrize(params, values, **kwargs):
("mimikatz", "file", capa.features.file.Import("cabinet.FCIAddFile"), True),
]

FEATURE_BINJA_DATABASE_TESTS = sorted(
[
# insn/regex
("pma16-01_binja_db", "function=0x4021B0", capa.features.common.Regex("HTTP/1.0"), True),
(
"pma16-01_binja_db",
"function=0x402F40",
capa.features.common.Regex("www.practicalmalwareanalysis.com"),
True,
),
(
"pma16-01_binja_db",
"function=0x402F40",
capa.features.common.Substring("practicalmalwareanalysis.com"),
True,
),
("pma16-01_binja_db", "file", capa.features.file.FunctionName("__aulldiv"), True),
# os & format & arch
("pma16-01_binja_db", "file", OS(OS_WINDOWS), True),
("pma16-01_binja_db", "file", OS(OS_LINUX), False),
("pma16-01_binja_db", "function=0x404356", OS(OS_WINDOWS), True),
("pma16-01_binja_db", "function=0x404356,bb=0x4043B9", OS(OS_WINDOWS), True),
("pma16-01_binja_db", "file", Arch(ARCH_I386), True),
("pma16-01_binja_db", "file", Arch(ARCH_AMD64), False),
("pma16-01_binja_db", "function=0x404356", Arch(ARCH_I386), True),
("pma16-01_binja_db", "function=0x404356,bb=0x4043B9", Arch(ARCH_I386), True),
("pma16-01_binja_db", "file", Format(FORMAT_PE), True),
("pma16-01_binja_db", "file", Format(FORMAT_ELF), False),
# format is also a global feature
("pma16-01_binja_db", "function=0x404356", Format(FORMAT_PE), True),
],
# order tests by (file, item)
# so that our LRU cache is most effective.
key=lambda t: (t[0], t[1]),
)


FEATURE_COUNT_TESTS = [
("mimikatz", "function=0x40E5C2", capa.features.basicblock.BasicBlock(), 7),
("mimikatz", "function=0x4702FD", capa.features.common.Characteristic("calls from"), 0),
Expand Down
2 changes: 1 addition & 1 deletion tests/test_binja_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
@pytest.mark.skipif(binja_present is False, reason="Skip binja tests if the binaryninja Python API is not installed")
@fixtures.parametrize(
"sample,scope,feature,expected",
fixtures.FEATURE_PRESENCE_TESTS + fixtures.FEATURE_SYMTAB_FUNC_TESTS,
fixtures.FEATURE_PRESENCE_TESTS + fixtures.FEATURE_SYMTAB_FUNC_TESTS + fixtures.FEATURE_BINJA_DATABASE_TESTS,
indirect=["sample", "scope"],
)
def test_binja_features(sample, scope, feature, expected):
Expand Down

0 comments on commit 54952fe

Please sign in to comment.