diff --git a/capa/features/extractors/viv/function.py b/capa/features/extractors/viv/function.py index 4f018c1c21..28098f050d 100644 --- a/capa/features/extractors/viv/function.py +++ b/capa/features/extractors/viv/function.py @@ -22,7 +22,7 @@ from capa.features.common import Feature, Characteristic from capa.features.address import Address, AbsoluteVirtualAddress from capa.features.extractors import loops -from capa.features.extractors.elf import SymTab +from capa.features.extractors.viv.helpers import get_elf_symbol_functions from capa.features.extractors.base_extractor import FunctionHandle @@ -30,24 +30,13 @@ def extract_function_symtab_names( fh: FunctionHandle, ) -> Iterator[tuple[Feature, Address]]: if fh.inner.vw.metadata["Format"] == "elf": - # the file's symbol table gets added to the metadata of the vivisect workspace. - # this is in order to eliminate the computational overhead of refetching symtab each time. - if "symtab" not in fh.ctx["cache"]: - try: - fh.ctx["cache"]["symtab"] = SymTab.from_viv(fh.inner.vw.parsedbin) - except Exception: - fh.ctx["cache"]["symtab"] = None - - symtab = fh.ctx["cache"]["symtab"] - if symtab: - for symbol in symtab.get_symbols(): - sym_name = symtab.get_name(symbol) - sym_value = symbol.value - sym_info = symbol.info - - STT_FUNC = 0x2 - if sym_value == fh.address and sym_info & STT_FUNC != 0: - yield FunctionName(sym_name), fh.address + # index the symbol table once and cache it, to avoid rescanning it for each function. + if "elf_symbol_functions" not in fh.ctx["cache"]: + fh.ctx["cache"]["elf_symbol_functions"] = get_elf_symbol_functions(fh.inner.vw.parsedbin) + + funcs = fh.ctx["cache"]["elf_symbol_functions"] + for sym_name in funcs.get(fh.address, ()): + yield FunctionName(sym_name), fh.address def extract_function_calls_to(fhandle: FunctionHandle) -> Iterator[tuple[Feature, Address]]: diff --git a/capa/features/extractors/viv/helpers.py b/capa/features/extractors/viv/helpers.py index 9442e0ebaf..81917415a8 100644 --- a/capa/features/extractors/viv/helpers.py +++ b/capa/features/extractors/viv/helpers.py @@ -12,11 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional +import struct +import logging +import collections +from typing import Any, Optional from vivisect import VivWorkspace from vivisect.const import XR_TO, REF_CODE +logger = logging.getLogger(__name__) + +STT_FUNC = 0x2 +STT_GNU_IFUNC = 0xA + def get_coderef_from(vw: VivWorkspace, va: int) -> Optional[int]: """ @@ -28,3 +36,25 @@ def get_coderef_from(vw: VivWorkspace, va: int) -> Optional[int]: return xrefs[0][XR_TO] else: return None + + +def get_elf_symbol_functions(parsedbin: Any) -> dict[int, list[str]]: + """index elf function symbol names by address.""" + symbol_functions: dict[int, list[str]] = collections.defaultdict(list) + try: + from capa.features.extractors.elf import SymTab + + symtab = SymTab.from_viv(parsedbin) + if symtab: + for symbol in symtab.get_symbols(): + if (symbol.info & 0xF) in (STT_FUNC, STT_GNU_IFUNC): + try: + name = symtab.get_name(symbol) + except (ValueError, UnicodeDecodeError): + continue + if name: + symbol_functions[symbol.value].append(name) + except (ValueError, struct.error, IndexError, AttributeError) as e: + logger.debug("failed to parse elf symbol table: %s", e) + + return symbol_functions diff --git a/capa/features/extractors/viv/insn.py b/capa/features/extractors/viv/insn.py index cd89a491d3..419eea1467 100644 --- a/capa/features/extractors/viv/insn.py +++ b/capa/features/extractors/viv/insn.py @@ -14,7 +14,6 @@ from typing import Callable, Iterator, cast -import Elf import envi import envi.exc import viv_utils @@ -30,7 +29,6 @@ from capa.features.insn import API, MAX_STRUCTURE_SIZE, Number, Offset, Mnemonic, OperandNumber, OperandOffset from capa.features.common import MAX_BYTES_FEATURE_SIZE, THUNK_CHAIN_DEPTH_DELTA, Bytes, String, Feature, Characteristic from capa.features.address import Address, AbsoluteVirtualAddress -from capa.features.extractors.elf import SymTab from capa.features.extractors.base_extractor import BBHandle, InsnHandle, FunctionHandle from capa.features.extractors.viv.indirect_calls import NotFoundError, resolve_indirect_call @@ -102,26 +100,15 @@ def extract_insn_api_features(fh: FunctionHandle, bb, ih: InsnHandle) -> Iterato return if f.vw.metadata["Format"] == "elf": - if "symtab" not in fh.ctx["cache"]: - # the symbol table gets stored as a function's attribute in order to avoid running - # this code every time the call is made, thus preventing the computational overhead. - try: - parsedbin = f.vw.parsedbin - assert isinstance(parsedbin, Elf.Elf) - fh.ctx["cache"]["symtab"] = SymTab.from_viv(parsedbin) - except Exception: - fh.ctx["cache"]["symtab"] = None - - symtab = fh.ctx["cache"]["symtab"] - if symtab: - for symbol in symtab.get_symbols(): - sym_name = symtab.get_name(symbol) - sym_value = symbol.value - sym_info = symbol.info - - STT_FUNC = 0x2 - if sym_value == target and sym_info & STT_FUNC != 0: - yield API(sym_name), ih.address + # index the symbol table once and cache it, to avoid rescanning it for each call instruction. + if "elf_symbol_functions" not in fh.ctx["cache"]: + fh.ctx["cache"]["elf_symbol_functions"] = capa.features.extractors.viv.helpers.get_elf_symbol_functions( + f.vw.parsedbin + ) + + funcs = fh.ctx["cache"]["elf_symbol_functions"] + for sym_name in funcs.get(target, ()): + yield API(sym_name), ih.address if viv_utils.flirt.is_library_function(f.vw, target): name = viv_utils.get_function_name(f.vw, target) diff --git a/tests/data b/tests/data index 4fa9a0448a..676ea7254d 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit 4fa9a0448a1453a80bdcbcefb6dff870f6333b64 +Subproject commit 676ea7254d67c0b909877e8f61d98c2cd88af621