Repository: mandiant/capa
File: capa/ghidra/capa_explorer.py (Python)

Code before:
                txt = self.recurse_node(node_to_parse)

                if not txt:
                    continue

                txt = txt.replace(" ", "-")
                createLabel(a, txt, name_space, True, SourceType.USER_DEFINED)  # type: ignore [name-defined] # noqa: F821
                continue

            # handle first address of match
            createLabel(a, txt, name_space, True, SourceType.USER_DEFINED)  # type: ignore [name-defined] # noqa: F821


def get_capabilities():
    rules_dir: str = ""
    try:
        selected_dir = askDirectory("Choose capa rules directory", "Ok")  # type: ignore [name-defined] # noqa: F821
        if selected_dir:
            rules_dir = selected_dir.getPath()
    except RuntimeError:
        # RuntimeError thrown when user selects "Cancel"
        pass

    if not rules_dir:
        logger.info("You must choose a capa rules directory before running capa.")
        return capa.main.E_MISSING_RULES

    rules_path: pathlib.Path = pathlib.Path(rules_dir)
    logger.info("running capa using rules from %s", str(rules_path))

    rules = capa.main.get_rules([rules_path])
    meta = capa.ghidra.helpers.collect_metadata([rules_path])
    extractor = capa.features.extractors.ghidra.extractor.GhidraFeatureExtractor()

    capabilities, counts = capa.main.find_capabilities(rules, extractor, True)

    if capa.main.has_file_limitation(rules, capabilities, is_standalone=False):
        logger.info("capa encountered warnings during analysis")

    return capa.render.json.render(meta, rules, capabilities)


def get_locations(match_dict):
    """recursively collect match addresses and associated nodes"""
    if "locations" in match_dict:
        for loc in match_dict.get("locations", {}):
            if "type" in loc:
                # either an rva (absolute)
                # or an offset into a file (file)
                if loc.get("type", "") in ("absolute", "file"):
                    yield loc.get("value"), match_dict.get("node")

Review comment (suggestion):
Let's instead return an empty `str` object here to avoid the caller having to handle both `int` and `str` return value types.

Code after:
        elif self.capability not in comment:
            comment = (
                comment + "capa: " + sub_type + "(" + description + ")" + ' matched in "' + self.capability + '"\n'
            )
            setPreComment(ghidra_addr, comment)  # type: ignore [name-defined] # noqa: F821
        else:
            return

    def label_matches(self):
        """label findings at function scopes and comment on subscope matches"""
        capa_namespace = create_namespace(self.namespace)
        symbol_table = currentProgram().getSymbolTable()  # type: ignore [name-defined] # noqa: F821

        # handle function main scope of matched rule
        # these will typically contain further matches within
        if self.scope == "function":
            for addr in self.matches.keys():
                ghidra_addr = toAddr(hex(addr))  # type: ignore [name-defined] # noqa: F821

                # classify new function label under capa-generated namespace
                sym = symbol_table.getPrimarySymbol(ghidra_addr)
                if sym is not None:
                    if sym.getSymbolType() == SymbolType.FUNCTION:
                        create_label(ghidra_addr, sym.getName(), capa_namespace)
                        self.set_plate_comment(ghidra_addr)

                    # parse the corresponding nodes, and pre-comment subscope matched features
                    # under the encompassing function(s)
                    for sub_match in self.matches.get(addr):
                        for loc, node in sub_match.items():
                            sub_ghidra_addr = toAddr(hex(loc))  # type: ignore [name-defined] # noqa: F821
                            if sub_ghidra_addr == ghidra_addr:
                                # skip duplicates
                                continue

                            # precomment subscope matches under the function
                            if node != {}:
                                for sub_type, description in parse_node(node):
                                    self.set_pre_comment(sub_ghidra_addr, sub_type, description)
        else:
            # resolve the encompassing function for the capa namespace
            # of non-function scoped main matches
            for addr in self.matches.keys():
                ghidra_addr = toAddr(hex(addr))  # type: ignore [name-defined] # noqa: F821

                # basic block / insn scoped main matches
                # Ex. See "Create Process on Windows" Rule
                func = getFunctionContaining(ghidra_addr)  # type: ignore [name-defined] # noqa: F821
                if func is not None:
                    func_addr = func.getEntryPoint()
                    create_label(func_addr, func.getName(), capa_namespace)