import demistomock as demisto # noqa: F401 import plyara import plyara.exceptions import plyara.utils from CommonServerPython import * # noqa: F401 YARA_META_TO_XSOAR = { "descr": "description", "description": "description", "author": "author", "authour": "author", "id": "ruleid", "reference": "rulereference", "link": "rulereference", } # TODO - Add creation date as well RELATIONSHIPS = ["Malware", "CVE", "Attack_Pattern"] def parse_rules(rules: str, create_relationships: bool = False) -> CommandResults: """Parses a YARA rule in its string format into an XSOAR indicator Args: rules (list[str]): A string of one or more YARA rules Returns: CommandResults: Results in XSOAR format. """ parser = plyara.Plyara() indicators: list[dict] = [] successfully_created_indicators: int = 0 relationships = [] try: parsed_rules = parser.parse_string(rules) demisto.debug(f"Parsed {len(parsed_rules)} YARA Rules.") except plyara.exceptions.ParseError: demisto.debug(f"Failed to parse - {rules}") raise for parsed_rule in parsed_rules: indicators.append(build_indicator(parsed_rule)) for indicator in indicators: execute_command("createNewIndicator", indicator) successfully_created_indicators += 1 demisto.debug(f'Created new indicator {indicator["value"]} ({successfully_created_indicators}/len(parsed_rules))') if create_relationships: for indicator in indicators: relationships = build_relationships(indicator) readable_output = f'{tblToMd(f"{len(indicators)} new YARA rules created", indicators, ["value", "author", "description"])}' # Removing "type" from and MD blocks from context dict outputs = [{k: v.replace("```", "") if isinstance(v, str) else v for k, v in d.items() if k != "type"} for d in indicators] return CommandResults( outputs_prefix="ImportYARARule", outputs_key_field="Rule", outputs=outputs, readable_output=readable_output, relationships=relationships, ) def build_indicator(rule: dict[str, Any]) -> dict[str, Any]: """Builds the indicator in the correct XSOAR format to be created. Args: rule (dict[str, Any]): A given YARA rule. Returns: dict[str, Any]: An XSOAR indicator of type YARA Rule. """ indicator = { "value": rule["rule_name"], "type": "YARA Rule", "rulestrings": [{"index": entry["name"][1:], "string": entry["value"]} for entry in rule["strings"]], "rulecondition": " ".join(rule["condition_terms"]), "rawrule": f"```\n{plyara.utils.rebuild_yara_rule(rule)}\n```", } if "tags" in rule: indicator["tags"] = rule["tags"] meta: list[Any] = rule.get("metadata", []) for key in YARA_META_TO_XSOAR: # populate metadata fields if value := parse_metadata(meta, key): indicator[YARA_META_TO_XSOAR[key]] = value return indicator def parse_metadata(meta: list[dict], key: str) -> str: """ Extracts the value of a YARA rule metadata key Args: meta (list[dict]): The YARA Rule metadata key (str): The metadata key to be extracted Returns: str: The value of the metadata key """ for item in meta: lowered_dict = {key.lower(): value for key, value in item.items()} if key in lowered_dict: return lowered_dict[key] return "" def build_relationships(indicator: dict) -> list[EntityRelationship]: relationships = [] related_iocs: list[Any] = demisto.executeCommand("extractIndicators", args={"text": indicator["rawrule"]}) related_iocs = json.loads(related_iocs[0].get("Contents", {})) demisto.debug(f"Found the following indicators in the YARA Rule {related_iocs}") for indicator_type in RELATIONSHIPS: demisto.debug(f"Handling {indicator_type} relationships") if indicator_type == "Malware": # Will extract only malware with APTXXX prefix. indicators = set(re.findall(r"(?i)apt[-_]?\d{1,2}", indicator["rawrule"])) else: indicators = related_iocs.get(indicator_type, []) for related_indicator in indicators: demisto.debug(f"Creating relationship object for {related_indicator}") relationships.append( EntityRelationship( entity_a=indicator["value"], entity_a_type="YARA Rule", entity_b=related_indicator, entity_b_type=indicator_type.replace("_", " "), name=EntityRelationship.Relationships.RELATED_TO, ) ) return relationships def main(): # pragma: no cover try: # TODO: replace the invoked command function with yours args = demisto.args() if args.get("yara_signatures", ""): yara_rules: str = args["yara_signatures"] elif args.get("entry_id", ""): file_path = demisto.getFilePath(args["entry_id"])["path"] with open(file_path) as f: yara_rules = f.read() else: raise Exception("Please provide exactly one input to the script yara_signatures or entry_id.") return_results(parse_rules(yara_rules, argToBoolean(args.get("create_relationships")))) except Exception as ex: demisto.error(traceback.format_exc()) # print the traceback return_error(f"Error: {ex!s}") if __name__ in ("__main__", "__builtin__", "builtins"): main()