diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cccb2222..c6de93bc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ ### Bug Fixes - do some imports closer to where they are used #1810 @williballenthin +- binja: fix and simplify stack string detection code after binja 4.0 @xusheng6 +- binja: add support for forwarded export #1646 @xusheng6 ### capa explorer IDA Pro plugin @@ -34,7 +36,9 @@ - ci: Fix PR review in the changelog check GH action #2004 @Ana06 - ci: use rules number badge stored in our bot gist and generated using `schneegans/dynamic-badges-action` #2001 capa-rules#882 @Ana06 - ci: update github workflows to use latest version of actions that were using a deprecated version of node #1967 #2003 capa-rules#883 @sjha2048 @Ana06 +- ci: update binja version to stable 4.0 #2016 @xusheng6 - ci: update github workflows to reflect the latest ghidrathon installation and bumped up jep, ghidra versions #2020 @psahithireddy + ### Raw diffs - [capa v7.0.1...master](https://github.com/mandiant/capa/compare/v7.0.1...master) - [capa-rules v7.0.1...master](https://github.com/mandiant/capa-rules/compare/v7.0.1...master) diff --git a/capa/features/extractors/binja/basicblock.py b/capa/features/extractors/binja/basicblock.py index 568ecc7ad..e74c9f486 100644 --- a/capa/features/extractors/binja/basicblock.py +++ b/capa/features/extractors/binja/basicblock.py @@ -7,17 +7,15 @@ # See the License for the specific language governing permissions and limitations under the License. import string -import struct from typing import Tuple, Iterator -from binaryninja import Function, Settings +from binaryninja import Function from binaryninja import BasicBlock as BinjaBasicBlock from binaryninja import ( BinaryView, SymbolType, RegisterValueType, VariableSourceType, - MediumLevelILSetVar, MediumLevelILOperation, MediumLevelILBasicBlock, MediumLevelILInstruction, @@ -29,11 +27,6 @@ from capa.features.extractors.helpers import MIN_STACKSTRING_LEN from capa.features.extractors.base_extractor import BBHandle, FunctionHandle -use_const_outline: bool = False -settings: Settings = Settings() -if settings.contains("analysis.outlining.builtins") and settings.get_bool("analysis.outlining.builtins"): - use_const_outline = True - def get_printable_len_ascii(s: bytes) -> int: """Return string length if all operand bytes are ascii or utf16-le printable""" @@ -65,7 +58,7 @@ def get_stack_string_len(f: Function, il: MediumLevelILInstruction) -> int: addr = target.value.value sym = bv.get_symbol_at(addr) - if not sym or sym.type != SymbolType.LibraryFunctionSymbol: + if not sym or sym.type not in [SymbolType.LibraryFunctionSymbol, SymbolType.SymbolicFunctionSymbol]: return 0 if sym.name not in ["__builtin_strncpy", "__builtin_strcpy", "__builtin_wcscpy"]: @@ -91,52 +84,6 @@ def get_stack_string_len(f: Function, il: MediumLevelILInstruction) -> int: return max(get_printable_len_ascii(bytes(s)), get_printable_len_wide(bytes(s))) -def get_printable_len(il: MediumLevelILSetVar) -> int: - """Return string length if all operand bytes are ascii or utf16-le printable""" - width = il.dest.type.width - value = il.src.value.value - - if width == 1: - chars = struct.pack(" bool: - """verify instruction moves immediate onto stack""" - if il.operation != MediumLevelILOperation.MLIL_SET_VAR: - return False - - if il.src.operation != MediumLevelILOperation.MLIL_CONST: - return False - - if il.dest.source_type != VariableSourceType.StackVariableSourceType: - return False - - return True - - def bb_contains_stackstring(f: Function, bb: MediumLevelILBasicBlock) -> bool: """check basic block for stackstring indicators @@ -144,14 +91,10 @@ def bb_contains_stackstring(f: Function, bb: MediumLevelILBasicBlock) -> bool: """ count = 0 for il in bb: - if use_const_outline: - count += get_stack_string_len(f, il) - else: - if is_mov_imm_to_stack(il): - count += get_printable_len(il) - - if count > MIN_STACKSTRING_LEN: - return True + count += get_stack_string_len(f, il) + if count > MIN_STACKSTRING_LEN: + return True + return False diff --git a/capa/features/extractors/binja/file.py b/capa/features/extractors/binja/file.py index 0054e62b1..cd340e77d 100644 --- a/capa/features/extractors/binja/file.py +++ b/capa/features/extractors/binja/file.py @@ -74,13 +74,18 @@ def extract_file_embedded_pe(bv: BinaryView) -> Iterator[Tuple[Feature, Address] def extract_file_export_names(bv: BinaryView) -> Iterator[Tuple[Feature, Address]]: """extract function exports""" - for sym in bv.get_symbols_of_type(SymbolType.FunctionSymbol): + for sym in bv.get_symbols_of_type(SymbolType.FunctionSymbol) + bv.get_symbols_of_type(SymbolType.DataSymbol): if sym.binding in [SymbolBinding.GlobalBinding, SymbolBinding.WeakBinding]: name = sym.short_name - yield Export(name), AbsoluteVirtualAddress(sym.address) - unmangled_name = unmangle_c_name(name) - if name != unmangled_name: - yield Export(unmangled_name), AbsoluteVirtualAddress(sym.address) + if name.startswith("__forwarder_name(") and name.endswith(")"): + yield Export(name[17:-1]), AbsoluteVirtualAddress(sym.address) + yield Characteristic("forwarded export"), AbsoluteVirtualAddress(sym.address) + else: + yield Export(name), AbsoluteVirtualAddress(sym.address) + + unmangled_name = unmangle_c_name(name) + if name != unmangled_name: + yield Export(unmangled_name), AbsoluteVirtualAddress(sym.address) for sym in bv.get_symbols_of_type(SymbolType.DataSymbol): if sym.binding not in [SymbolBinding.GlobalBinding]: diff --git a/tests/test_binja_features.py b/tests/test_binja_features.py index 78addff7c..c226d2ee6 100644 --- a/tests/test_binja_features.py +++ b/tests/test_binja_features.py @@ -63,4 +63,4 @@ def test_standalone_binja_backend(): @pytest.mark.skipif(binja_present is False, reason="Skip binja tests if the binaryninja Python API is not installed") def test_binja_version(): version = binaryninja.core_version_info() - assert version.major == 3 and version.minor == 5 + assert version.major == 4 and version.minor == 0