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

Code before:
        this_attack = meta.get("attack")
        if not this_attack:
            this_attack = []
        this_mbc = meta.get("mbc")
        if not this_mbc:
            this_mbc = []

        # scope match for the rule
        this_scope = meta["scopes"].get("static")
        with suppress(KeyError):
            # always grab first location of match
            this_locs.append(this_capability["matches"][0][0]["value"])
            # align node data
            this_node.append({})

        if "namespace" in meta:
            # split into list to help define child namespaces
            # this requires the correct delimiter used by Ghidra
            # Ex. 'communication/named-pipe/create' -> capa::communication::named-pipe::create
            namespace_str = Namespace.DELIMITER.join(meta["namespace"].split("/"))
            this_namespace = "capa" + Namespace.DELIMITER + namespace_str
            # lib rules via the official rules repo will not contain data
            # for the "namespaces" key, so format using rule itself
        else:
            lib_str = "capa" + Namespace.DELIMITER + "lib" + Namespace.DELIMITER
            this_namespace = lib_str + rule.replace(" ", "-")

        # recurse to find all locations
        # grab second dict, containing additional matches
        # and node data
        matches = this_capability["matches"][0][1]
        for m in get_locations(matches):
            this_locs.append(m[0])
            this_node.append(m[1])

        yield CapaMatchData(this_namespace, this_scope, rule, this_locs, this_node, this_attack, this_mbc)


def main():
    logging.basicConfig(level=logging.INFO)
    logging.getLogger().setLevel(logging.INFO)

    if isRunningHeadless():  # type: ignore [name-defined] # noqa: F821
        logger.error("unsupported ghidra execution mode")
        return capa.main.E_UNSUPPORTED_GHIDRA_EXECUTION_MODE

    if not capa.ghidra.helpers.is_supported_ghidra_version():
        logger.error("unsupported ghidra version")
        return capa.main.E_UNSUPPORTED_GHIDRA_VERSION

    if not capa.ghidra.helpers.is_supported_file_type():

Review comment (suggestion):
```suggestion
        else:
            # lib rules via the official rules repo will not contain data
            # for the "namespaces" key, so format using rule itself
            lib_str = "capa" + Namespace.DELIMITER + "lib" + Namespace.DELIMITER
            this_namespace = lib_str + rule.replace(" ", "-")
```

Code after:
                                    create_label(sub_func_addr, sub_func.getName(), capa_namespace)
                                    self.set_plate_comment(sub_func_addr)
                                    for sub_type, description in parse_node(node):
                                        self.set_pre_comment(sub_ghidra_addr, sub_type, description)
                                else:
                                    # addr is in some other file section like .data
                                    # represent this location with a label symbol under the capa namespace
                                    # Ex. See "Reference Base64 String" rule
                                    for sub_type, description in parse_node(node):
                                        # in many cases, these will be ghidra-labeled data, so just add the existing
                                        # label symbol to the capa namespace
                                        for sym in symbol_table.getSymbols(sub_ghidra_addr):
                                            if sym.getSymbolType() == SymbolType.LABEL:
                                                sym.setNamespace(capa_namespace)
                                        self.set_pre_comment(sub_ghidra_addr, sub_type, description)


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 ""  # return empty str to avoid handling both int and str types

    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):
        popup("capa explorer encountered warnings during analysis. Please check the console output for more information.")  # type: ignore [name-defined] # noqa: F821
        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"""

    for loc in match_dict.get("locations", {}):