Merge pull request #2501 from xusheng6/binja_database_support

Binja database support
This commit is contained in:
Moritz
2024-12-02 17:32:24 +01:00
committed by GitHub
10 changed files with 59 additions and 6 deletions

View File

@@ -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

View File

@@ -466,6 +466,7 @@ FORMAT_VMRAY = "vmray"
FORMAT_BINEXPORT2 = "binexport2"
FORMAT_FREEZE = "freeze"
FORMAT_RESULT = "result"
FORMAT_BINJA_DB = "binja_database"
STATIC_FORMATS = {
FORMAT_SC32,
FORMAT_SC64,
@@ -475,6 +476,7 @@ STATIC_FORMATS = {
FORMAT_FREEZE,
FORMAT_RESULT,
FORMAT_BINEXPORT2,
FORMAT_BINJA_DB,
}
DYNAMIC_FORMATS = {
FORMAT_CAPE,

View File

@@ -18,6 +18,7 @@ from capa.features.common import (
FORMAT_ELF,
FORMAT_SC32,
FORMAT_SC64,
FORMAT_BINJA_DB,
Format,
String,
Feature,
@@ -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

View File

@@ -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:

View File

@@ -46,6 +46,7 @@ from capa.features.common import (
FORMAT_FREEZE,
FORMAT_DRAKVUF,
FORMAT_UNKNOWN,
FORMAT_BINJA_DB,
FORMAT_BINEXPORT2,
Format,
)
@@ -59,6 +60,7 @@ EXTENSIONS_DYNAMIC = ("json", "json_", "json.gz", "log", ".log.gz", ".zip")
EXTENSIONS_BINEXPORT2 = ("BinExport", "BinExport2")
EXTENSIONS_ELF = "elf_"
EXTENSIONS_FREEZE = "frz"
EXTENSIONS_BINJA_DB = "bndb"
logger = logging.getLogger("capa")
@@ -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_

View File

@@ -48,6 +48,7 @@ from capa.features.common import (
FORMAT_VMRAY,
FORMAT_DOTNET,
FORMAT_DRAKVUF,
FORMAT_BINJA_DB,
FORMAT_BINEXPORT2,
)
from capa.features.address import Address
@@ -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()

View File

@@ -92,6 +92,7 @@ from capa.features.common import (
FORMAT_DRAKVUF,
STATIC_FORMATS,
DYNAMIC_FORMATS,
FORMAT_BINJA_DB,
FORMAT_BINEXPORT2,
)
from capa.capabilities.common import find_capabilities, has_file_limitation, find_file_capabilities
@@ -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])

View File

@@ -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":
@@ -1387,6 +1389,43 @@ FEATURE_PRESENCE_TESTS_IDA = [
("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),

View File

@@ -36,7 +36,7 @@ except ImportError:
@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):