Repository: mandiant/capa
File: capa/features/extractors/viv/insn.py (Python)

Code before:
    #   14005AAF8 jmp     ElfClearEventLogFileW (14005E196)
    #     14005E196 jmp     cs:__imp_ElfClearEventLogFileW

    elif isinstance(insn.opers[0], envi.archs.i386.disasm.i386PcRelOper):
        imports = get_imports(f.vw)
        target = capa.features.extractors.viv.helpers.get_coderef_from(f.vw, insn.va)
        if not target:
            return

        if viv_utils.flirt.is_library_function(f.vw, target):
            name = viv_utils.get_function_name(f.vw, target)
            yield API(name), ih.address
            if name.startswith("_"):
                # some linkers may prefix linked routines with a `_` to avoid name collisions.
                # extract features for both the mangled and un-mangled representations.
                # e.g. `_fwrite` -> `fwrite`
                # see: https://stackoverflow.com/a/2628384/87207
                yield API(name[1:]), ih.address
            return

        if imports == {}:
            # no imports implies the binary was likely statically linked.
            # therefore, we try to use the symbol's table to fetch the api names
            name = f.vw.name_by_va.get(target)
            if not name:
                return

            name = name.split('.')[-1]
            prefixes = [
                    "__GI_",
                    "__libc_",
            ]

            for prefix in prefixes:
                if name.startswith(prefix):
                    yield API(name[len(prefix):]), ih.address
                    return

            yield API(name), ih.address
            return

        for _ in range(THUNK_CHAIN_DEPTH_DELTA):
            if target in imports:
                dll, symbol = imports[target]
                for name in capa.features.extractors.helpers.generate_symbols(dll, symbol):
                    yield API(name), ih.address

            # if jump leads to an ENDBRANCH instruction, skip it
            if f.vw.getByteDef(target)[1].startswith(b"\xf3\x0f\x1e"):
                target += 4


Review comment (security):
i dont think we should rely on vivisect's naming here, primarily because vivisect can only associate a single name with any location, even if the symtab has multiple names. if vivisect provides access to the symtab entries directly its fine to use that; alternatively, we can use the code that you contributed to the simple ELF parser we have in capa.

and, it looks like IDA has the same limitation as viv (single name per location) and doesn't give easy access to symtab (via functions list nor names list) so probably we should prefer our ELF parser.

anyways, when the symtab has multiple names for a location, we should emit each of them verbatim as their own API feature. this way rule authors have the most flexibility and power (do they want to match the weak names? or only the libc names? it's up to them). also, we don't have to invent too much logic about stemming the prefix - we can just use the data as its present. of course, we emit a few more features than before, but i dont think it's too many nor will affect performance much.

creating the mapping from VA -> list of names should probably happen once and then the results should be re-used on each API extraction. we can use the `ctx` to store the cached data structure.

Code after:
    # 0x140059342 (viv) / 0x14005E0C0 (IDA)
    # 14005E0FF call    j_ElfClearEventLogFileW (14005AAF8)
    #   14005AAF8 jmp     ElfClearEventLogFileW (14005E196)
    #     14005E196 jmp     cs:__imp_ElfClearEventLogFileW

    elif isinstance(insn.opers[0], envi.archs.i386.disasm.i386PcRelOper):
        imports = get_imports(f.vw)
        target = capa.features.extractors.viv.helpers.get_coderef_from(f.vw, insn.va)
        if not target:
            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 everytime the call is made, thus preventing the computational overhead.
                try:
                    fh.ctx["cache"]["symtab"] = SymTab.from_Elf(f.vw.parsedbin)
                except:
                    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

        if viv_utils.flirt.is_library_function(f.vw, target):
            name = viv_utils.get_function_name(f.vw, target)
            yield API(name), ih.address
            if name.startswith("_"):
                # some linkers may prefix linked routines with a `_` to avoid name collisions.
                # extract features for both the mangled and un-mangled representations.
                # e.g. `_fwrite` -> `fwrite`
                # see: https://stackoverflow.com/a/2628384/87207
                yield API(name[1:]), ih.address
            return

        for _ in range(THUNK_CHAIN_DEPTH_DELTA):
            if target in imports:
                dll, symbol = imports[target]
                for name in capa.features.extractors.helpers.generate_symbols(dll, symbol):
                    yield API(name), ih.address

            # if jump leads to an ENDBRANCH instruction, skip it
            if f.vw.getByteDef(target)[1].startswith(b"\xf3\x0f\x1e"):
                target += 4