Repository: mandiant/capa
File: capa/features/extractors/ghidra/helpers.py (Python)

Code before:

    import_dict: Dict[int, List[str]] = {}

    for f in currentProgram().getFunctionManager().getExternalFunctions():  # type: ignore [name-defined] # noqa: F821
        for r in f.getSymbol().getReferences():
            if r.getReferenceType().isData():
                addr = r.getFromAddress().getOffset()  # gets pointer to fake external addr

        ex_loc = f.getExternalLocation().getAddress()  # map external locations as well (offset into module files)

        fstr = f.toString().split("::")  # format: MODULE.dll::import / MODULE::Ordinal_* / <EXTERNAL>::import
        if "Ordinal_" in fstr[1]:
            fstr[1] = f"#{fstr[1].split('_')[1]}"

        # Most prominently shows up in .elf samples, Ghidra's FunctionID has a
        # hard time resolving module names for Linux imports
        if "<EXTERNAL>" in fstr[0]:
            for name in capa.features.extractors.helpers.generate_symbols("*", fstr[1]):
                import_dict.setdefault(addr, []).append(name)
                if ex_loc:
                    import_dict.setdefault(ex_loc.getOffset(), []).append(name)
        else:
            for name in capa.features.extractors.helpers.generate_symbols(fstr[0][:-4], fstr[1]):
                import_dict.setdefault(addr, []).append(name)
                if ex_loc:
                    import_dict.setdefault(ex_loc.getOffset(), []).append(name)

    return import_dict


def get_file_externs() -> Dict[int, List[str]]:
    """
    Gets function names & addresses of statically-linked library functions

    Ghidra's external namespace is mostly reserved for dynamically-linked
    imports. Statically-linked functions are part of the global namespace.
    Filtering on the type, source, and namespace of the symbols yield more
    statically-linked library functions.

    Example: (PMA Lab 16-01.exe_) 7faafc7e4a5c736ebfee6abbbc812d80:0x407490
    - __aulldiv
    - Note: See Symbol Table labels
    """

    extern_dict: Dict[int, List[str]] = {}

    for sym in currentProgram().getSymbolTable().getAllSymbols(True):  # type: ignore [name-defined] # noqa: F821
        # .isExternal() misses more than this config for the function symbols
        if sym.getSymbolType() == SymbolType.FUNCTION and sym.getSource() == SourceType.ANALYSIS and sym.isGlobal():
            name = sym.getName()  # starts to resolve names based on Ghidra's FidDB
            if name.startswith("FID_conflict:"):  # format: FID_conflict:<function-name>

Review comment (refactor):
Can we simplify the code here by setting `fstr[0]` to `*` or `fstr[:-4]` before falling into the `for` loop used to generate the symbols? Let's also add a comment describing the significance of `-4` for the latter.

Code after:
    """get all import names & addrs"""

    import_dict: Dict[int, List[str]] = {}

    for f in currentProgram().getFunctionManager().getExternalFunctions():  # type: ignore [name-defined] # noqa: F821
        for r in f.getSymbol().getReferences():
            if r.getReferenceType().isData():
                addr = r.getFromAddress().getOffset()  # gets pointer to fake external addr

        ex_loc = f.getExternalLocation().getAddress()  # map external locations as well (offset into module files)

        fstr = f.toString().split("::")  # format: MODULE.dll::import / MODULE::Ordinal_* / <EXTERNAL>::import
        if "Ordinal_" in fstr[1]:
            fstr[1] = f"#{fstr[1].split('_')[1]}"

        # <EXTERNAL> mostly shows up in ELF files, otherwise, strip '.dll' w/ [:-4]
        fstr[0] = "*" if "<EXTERNAL>" in fstr[0] else fstr[0][:-4]

        for name in capa.features.extractors.helpers.generate_symbols(fstr[0], fstr[1]):
            import_dict.setdefault(addr, []).append(name)
            if ex_loc:
                import_dict.setdefault(ex_loc.getOffset(), []).append(name)

    return import_dict


def get_file_externs() -> Dict[int, List[str]]:
    """
    Gets function names & addresses of statically-linked library functions

    Ghidra's external namespace is mostly reserved for dynamically-linked
    imports. Statically-linked functions are part of the global namespace.
    Filtering on the type, source, and namespace of the symbols yield more
    statically-linked library functions.

    Example: (PMA Lab 16-01.exe_) 7faafc7e4a5c736ebfee6abbbc812d80:0x407490
    - __aulldiv
    - Note: See Symbol Table labels
    """

    extern_dict: Dict[int, List[str]] = {}

    for sym in currentProgram().getSymbolTable().getAllSymbols(True):  # type: ignore [name-defined] # noqa: F821
        # .isExternal() misses more than this config for the function symbols
        if sym.getSymbolType() == SymbolType.FUNCTION and sym.getSource() == SourceType.ANALYSIS and sym.isGlobal():
            name = sym.getName()  # starts to resolve names based on Ghidra's FidDB
            if name.startswith("FID_conflict:"):  # format: FID_conflict:<function-name>
                name = name[13:]
            extern_dict.setdefault(sym.getAddress().getOffset(), []).append(name)
            if name.startswith("_"):
                # some linkers may prefix linked routines with a `_` to avoid name collisions.