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

Code before:
    """recursively collect match addresses and associated nodes"""

    for loc in match_dict.get("locations", {}):
        # 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")

    for child in match_dict.get("children", {}):
        yield from get_locations(child)


def parse_json(capa_data):
    """Parse json produced by capa"""

    rules = capa_data["rules"]
    for rule in capa_data.get("rules", {}).keys():
        # loosely coupled location & node data lists
        this_locs = []
        this_node: List[Dict] = []

        # dict data of currently matched rule
        this_capability = rules[rule]
        meta = this_capability["meta"]

        # get Mitre ATT&CK and MBC
        # avoid passing NoneTypes
        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:

Review comment (suggestion):
```suggestion
        # get MITRE ATT&CK and MBC
```

Code after:

                # 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)
                    self.set_plate_comment(func_addr)

                # create subscope match precomments
                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 node != {}:
                            if func is not None:
                                # basic block/ insn scope under resolved function
                                for sub_type, description in parse_node(node):
                                    self.set_pre_comment(sub_ghidra_addr, sub_type, description)
                            else:
                                # this would be a global/file scoped main match
                                # try to resolve the encompassing function via the subscope match, instead
                                # Ex. "run as service" rule
                                sub_func = getFunctionContaining(sub_ghidra_addr)  # type: ignore [name-defined] # noqa: F821
                                if sub_func is not None:
                                    sub_func_addr = sub_func.getEntryPoint()
                                    # place function in capa namespace & create the subscope match label in Ghidra's global namespace
                                    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: