def parse_operator_and_operand(search_string: str) -> Union[Tuple[Any, Any], None]: """Parse a string in the format of "operator operand" and returns the callable and operand string. :param search_string: The search string to parse. :type search_string: str :return: The operator callable and operand parsed from the search string. :rtype: Union[Tuple[Any, Any], None] """ NONE_TYPES = ["None", "none", "null", "''", '""', "NULL"] try: s = search_string.split(" ") except AttributeError: ERROR( "search string '{}' appears to be the wrong data type.".format( search_string ) ) return try: operator = OPERATOR_MAP(s[0]) operand = " ".join(s[1:]) except IndexError: ERROR("Did not receive a parseable string! '{}'".format(search_string)) return if operand in NONE_TYPES: operand = "" return (operator, operand)
async def run(indicators: dict) -> None: """Accept a dict containing events indicators and writes out to the OUTPUT_DIR specified by chirp.common. :param indicators: A dict containing parsed network indicator files. :type indicators: dict """ if not indicators: return hits = 0 CONSOLE("[cyan][NETWORK][/cyan] Entered network plugin.") saved_ns = parse_netstat(grab_netstat()) saved_dns = parse_dns(grab_dns()) report = { indicator["name"]: build_report(indicator) for indicator in indicators } for indicator in indicators: try: for ioc in indicator["indicator"]["ips"].splitlines(): if await hunter(saved_ns + ["\n"] + saved_dns, ioc): report[indicator["name"]]["matches"].append(ioc) hits += 1 except KeyError: ERROR("{} appears to be malformed.".format(indicator)) CONSOLE( "[cyan][NETWORK][/cyan] Read {} records, found {} IoC hits.".format( len(saved_dns) + len(saved_ns), hits)) with open(os.path.join(OUTPUT_DIR, "network.json"), "w+") as writeout: writeout.write( json.dumps({r: report[r] for r in report if report[r]["matches"]}))
def _str_load(name: str, pkg: Callable, discovered_plugins: Dict[str, Callable]) -> None: """Load a plugin given the REQUIRED_OS attribute is a string.""" if OS.lower() in pkg.REQUIRED_OS.lower(): if _verify_privilege(pkg): discovered_plugins[name] = pkg.entrypoint else: ERROR("{} must be ran on {}".format(name, pkg.REQUIRED_OS))
def _iter_load(name: str, pkg: Callable, discovered_plugins: Dict[str, Callable]) -> None: """Load a plugin given the REQUIRED_OS attribute is a list or tuple.""" if any(OS.lower() in operating_system.lower() for operating_system in pkg.REQUIRED_OS): if _verify_privilege(pkg): discovered_plugins[name] = pkg.entrypoint else: ERROR("{} must be ran on {}".format(name, " or ".join(pkg.REQUIRED_OS)))
def _loader(name: str, discovered_plugins: Dict[str, Callable]) -> None: """Load discovered plugins in the ./plugins directory.""" INFO("Found {}".format(name)) pkg = importlib.import_module("chirp.plugins.{}".format(name)) try: if not hasattr(pkg.entrypoint, "__call__"): raise AttributeError try: if isinstance(pkg.REQUIRED_OS, str): _str_load(name, pkg, discovered_plugins) elif isinstance(pkg.REQUIRED_OS, (tuple, list)): _iter_load(name, pkg, discovered_plugins) else: ERROR("Not sure how to interpret REQUIRED_OS for plugin {}". format(name)) except AttributeError: if _verify_privilege(pkg): discovered_plugins[name] = pkg.entrypoint except AttributeError: ERROR("{} does not have a valid entrypoint".format(name))
def _verify_privilege(plugin: Callable) -> bool: """Verify proper privilege for plugin.""" try: admin = plugin.REQUIRED_ADMIN if ADMIN or not admin: INFO("Loaded {}".format(_parse_name(plugin))) return True else: ERROR("{} must be ran from an admin console.".format( _parse_name(plugin))) return False except AttributeError: INFO("Loaded {}".format(_parse_name(plugin))) return True
def OPERATOR_MAP(symbol: str) -> Union[Callable, None]: """Map a string to a function for an operator. :param symbol: The symbol for an operator. :type symbol: str :return: The function related to the symbol. :rtype: Callable """ d = { "==": equals, "!=": notequals, "~=": regular_expression, } try: return d[symbol] except KeyError: ERROR("Unknown symbol '{}'.".format(symbol))
def check_valid_indicator_types(indicator_generator: Iterable[dict], plugins: List[str]) -> Iterator[dict]: """Check that an indicator file has a matching plugin and if so yields the indicator. :param indicator_generator: A generator to yield parsed indicator files. :type indicator_generator: Iterable[dict] :param plugins: Names of valid plugins. :type plugins: List[str] :yield: Valid parsed indicators. :rtype: Iterator[dict] """ failed_types = [] for indicator in indicator_generator: if indicator["ioc_type"] in plugins: yield indicator DEBUG("Loaded {}".format(indicator["name"])) else: if indicator["ioc_type"] not in failed_types: ERROR( """Can't locate plugin "{}". It is possible it has not loaded due to an error.""" .format(indicator["ioc_type"])) failed_types.append(indicator["ioc_type"]) continue
"""Main method for CHIRP (Used when compiled).""" # Standard Python Libraries from multiprocessing import freeze_support import os # cisagov Libraries from chirp.common import CONSOLE, ERROR, OUTPUT_DIR, save_log import chirp.run if __name__ == "__main__": try: freeze_support() chirp.run.run() CONSOLE( "[green][+][/green] DONE! Your results can be found in {}.".format( os.path.abspath(OUTPUT_DIR) ) ) except KeyboardInterrupt: ERROR("Received an escape sequence. Goodbye.") finally: save_log() input()