def process_to_dict(p: Process, max_segments=None) -> Dict: """Get all events for this process.""" all_segments = p.get_segments() if max_segments is None: max_segments = len(all_segments) p.refresh() results = p.original_document results["captured_segments"] = {} results["all_segments"] = all_segments results["process_ancestry"] = StringIO() with redirect_stdout(results["process_ancestry"]): print_ancestry(p) results["process_ancestry"] = results["process_ancestry"].getvalue() results["process_tree"] = StringIO() with redirect_stdout(results["process_tree"]): print_process_tree(p) results["process_tree"] = results["process_tree"].getvalue() captured_segment_count = 0 if p.current_segment: # if current_segment is set, something specifically targeted this segment # and we will ensure it gets captured here results["captured_segments"][ p.current_segment] = segment_events_to_dict(p) captured_segment_count += 1 for segment in all_segments: p.current_segment = segment if segment in results["captured_segments"]: continue if captured_segment_count >= max_segments: LOGGER.info( f"hit maximum segment limit exporting process to json for {p.id}" ) break results["captured_segments"][segment] = segment_events_to_dict(p) captured_segment_count += 1 return results
def execute_response_arguments(cb: CbResponseAPI, args: argparse.Namespace) -> bool: """The logic to execute response specific command line arguments. Args: cb: CbResponseAPI args: parsed argparse namespace Returns: True or None on success, False on failure. """ if not isinstance(cb, CbResponseAPI): LOGGER.critical(f"expected CbResponseAPI but got '{type(cb)}'") return False # Sensor Quering # if args.command and (args.command == "sensor-query" or args.command == "sq"): LOGGER.info( f"searching {args.environment} environment for sensor query: {args.sensor_query}..." ) sensors = make_sensor_query(cb, args.sensor_query) if not sensors: return None # don't display large results by default print_results = True if not args.no_warnings and len(sensors) > 10: prompt = "Print all results? (y/n) [y] " print_results = input_with_timeout(prompt, default="y") print_results = True if print_results.lower() == "y" else False if len(sensors) > 0 and print_results: print( "\n------------------------- SENSOR RESULTS -------------------------" ) for sensor in sensors: if args.all_details: print() print(sensor) else: print(sensor_info(sensor)) print() return True # Watchlists # if args.command and (args.command == "response_watchlist" or args.command == "rwl"): watchlists = watchlist_names = [] if args.query_watchlists: watchlists = query_watchlists(cb, args.query_watchlists) elif args.list_watchlists: watchlists = get_all_watchlists(cb) if args.watchlist_names_from_stdin: watchlist_names = [line.strip() for line in sys.stdin] if args.watchlists_to_json: if watchlists: print( json.dumps( these_watchlists_to_list_dict( cb, [wl.name for wl in watchlists]))) if watchlist_names: print( json.dumps( these_watchlists_to_list_dict(cb, watchlist_names))) return elif len(watchlists) > 0: print( "\n------------------------- WATCHLISTS -------------------------" ) for wl in watchlists: print(wl) # Process Quering # if args.command and (args.command.startswith("q") or args.command == "pq"): LOGGER.info(f"searching {args.environment} environment..") args.start_time = (datetime.datetime.strptime(args.start_time, "%Y-%m-%d %H:%M:%S") if args.start_time else args.start_time) args.last_time = (datetime.datetime.strptime(args.last_time, "%Y-%m-%d %H:%M:%S") if args.last_time else args.last_time) processes = make_process_query(cb, args.query, start_time=args.start_time, last_time=args.last_time, raise_exceptions=False) if args.facets: LOGGER.info("getting facet data...") print_facet_histogram(processes.facets()) # don't display large results by default print_results = True if not args.no_warnings and len(processes) > 10: prompt = "Print all results? (y/n) [y] " print_results = input_with_timeout(prompt, default="y") print_results = True if print_results.lower() == "y" else False if len(processes) > 0 and print_results: print( "\n------------------------- QUERY RESULTS -------------------------" ) for proc in processes: print(" -------------------------") if args.all_details: print(proc) else: print_process_info(proc, raw_print=args.all_details, header=False) return True # Enumerations # if args.command and args.command == "enumerate": if args.logon_history: logon_history(cb, args.logon_history) return # Process Inspection # if args.command and (args.command.lower() == "inspect" or args.command.lower().startswith("proc")): process_id = args.process_guid_options process_segment = None if "/" in args.process_guid_options: if not args.process_guid_options.count("/") == 1: LOGGER.error( f"process guid/segement format error: {args.process_guid_options}" ) return False process_id, process_segment = args.process_guid_options.split("/") if not re.match("[0-9]{13}", process_segment): LOGGER.error( f"{process_segment} is not in the form of a process segment." ) return False process_segment = int(process_segment) if not is_uuid(process_id): LOGGER.error( f"{process_id} is not in the form of a globally unique process id (GUID/UUID)." ) return False try: proc = Process(cb, process_id, force_init=True) if process_segment and process_segment not in proc.get_segments(): LOGGER.warning( f"segment '{process_segment}' does not exist. Setting to first segment." ) process_segment = None proc.current_segment = process_segment except ObjectNotFoundError: LOGGER.warning( f"ObjectNotFoundError - process data does not exist.") return False except Exception as e: LOGGER.error(f"problem finding process: {e}") return False all_inspection_args = [ iarg for iarg in vars(args).keys() if iarg.startswith("inspect_") ] set_inspection_args = [ iarg for iarg, value in vars(args).items() if iarg.startswith("inspect_") and value is True ] if not set_inspection_args: LOGGER.debug(f"seting all inspection arguments.") for iarg in all_inspection_args: args.__setattr__(iarg, True) if args.json: print( json.dumps(process_to_dict(proc, max_segments=args.segment_limit), default=str)) return if args.walk_and_inspect_tree: inspect_process_tree( proc, info=args.inspect_proc_info, filemods=args.inspect_filemods, netconns=args.inspect_netconns, regmods=args.inspect_regmods, modloads=args.inspect_modloads, crossprocs=args.inspect_crossprocs, children=args.inspect_children, raw_print=args.raw_print_events, ) return True # else if args.inspect_process_ancestry: print_ancestry(proc) if args.inspect_process_tree: print_process_tree(proc) if args.inspect_proc_info: print_process_info(proc, raw_print=args.raw_print_events) if args.inspect_filemods: print_filemods(proc, current_segment_only=bool(process_segment), raw_print=args.raw_print_events) if args.inspect_netconns: print_netconns(proc, current_segment_only=bool(process_segment), raw_print=args.raw_print_events) if args.inspect_regmods: print_regmods(proc, current_segment_only=bool(process_segment), raw_print=args.raw_print_events) if args.inspect_modloads: print_modloads(proc, current_segment_only=bool(process_segment), raw_print=args.raw_print_events) if args.inspect_crossprocs: print_crossprocs(proc, current_segment_only=bool(process_segment), raw_print=args.raw_print_events) if args.inspect_children: print_childprocs(proc, current_segment_only=bool(process_segment), raw_print=args.raw_print_events) # Live Response Actions # if args.command and (args.command.lower() == "lr" or args.command.lower().startswith("live")): # create a LR session manager session_manager = CustomLiveResponseSessionManager( cb, custom_session_keepalive=True) # store a list of commands to execute on this sensor commands = [] try: sensor = Sensor(cb, args.name_or_id, force_init=True) except ObjectNotFoundError: LOGGER.info(f"searching for sensor...") sensor = find_sensor_by_hostname(cb, args.name_or_id) if not sensor: LOGGER.info(f"could not find a sensor.") return None if args.execute_command: # XXX expand this for more flexibiliy by making an execute parser # that can accept more arugments to pass to ExecuteCommand cmd = ExecuteCommand(args.execute_command) commands.append(cmd) LOGGER.info(f"recorded command: {cmd}") if args.sensor_isolation_toggle: result = None state = "isolated" if sensor.is_isolating else "unisolated" desired_state = "unisolated" if sensor.is_isolating else "isolated" LOGGER.info( f"sensor {sensor.id}:{sensor.hostname} is currently {state}. Changing state to: {desired_state}" ) if sensor.is_isolating: result = sensor.unisolate() else: result = sensor.isolate() if result: state = "isolated" if sensor.is_isolating else "unisolated" LOGGER.info( f"successfully {state} sensor {sensor.id}:{sensor.hostname}" ) else: state = "unisolate" if sensor.is_isolating else "isolate" LOGGER.error( f"failed to {state} sensor {sensor.id}:{sensor.hostname}") # Put File # if args.live_response_command and args.live_response_command.lower( ) == "put": cmd = PutFile(args.local_filepath, args.sensor_write_filepath) commands.append(cmd) LOGGER.info(f"recorded command: {cmd}") if args.create_regkey: cmd = CreateRegKey(args.create_regkey) commands.append(cmd) LOGGER.info(f"recorded command: {cmd}") if args.set_regkey_value: cmd = SetRegKeyValue(args.create_regkey, args.set_regkey_value) commands.append(cmd) LOGGER.info(f"recorded command: {cmd}") # Sensor Collection # if args.live_response_command and args.live_response_command.lower( ) == "collect": if args.sensor_info: print(sensor_info(sensor)) if args.process_list: cmd = ProcessListing() commands.append(cmd) LOGGER.info(f"recorded command: {cmd}") if args.list_directory: cmd = ListDirectory(args.list_directory) commands.append(cmd) LOGGER.info(f"recorded command: {cmd}") if args.walk_directory: cmd = WalkDirectory(args.walk_directory) commands.append(cmd) LOGGER.info(f"recorded command: {cmd}") if args.file: cmd = GetFile(args.file) commands.append(cmd) LOGGER.info(f"recorded command: {cmd}") if args.regkeypath: cmd = ListRegKeyValues(args.regkeypath) commands.append(cmd) LOGGER.info(f"recorded command: {cmd}") if args.regkeyvalue: cmd = RegKeyValue(args.regkeyvalue) commands.append(cmd) LOGGER.info(f"recorded command: {cmd}") if args.drives: cmd = LogicalDrives() commands.append(cmd) LOGGER.info(f"recorded command: {cmd}") if args.memdump: cmd = GetSystemMemoryDump() commands.append(cmd) LOGGER.info(f"recorded command: {cmd}") # Sensor Remediation # if args.live_response_command and args.live_response_command == "remediate": if args.delete_file_path: cmd = DeleteFile(args.delete_file_path) commands.append(cmd) LOGGER.info(f"recorded command: {cmd}") if args.kill_process_name: cmd = KillProcessByName(args.kill_process_name) commands.append(cmd) LOGGER.info(f"recorded command: {cmd}") if args.delete_regkeyvalue: cmd = DeleteRegistryKeyValue(args.delete_regkeyvalue) commands.append(cmd) LOGGER.info(f"recorded command: {cmd}") if args.delete_entire_regkey: cmd = DeleteRegistryKey(args.delete_entire_regkey) commands.append(cmd) LOGGER.info(f"recorded command: {cmd}") if args.remediation_script: remediation_commands = build_remediation_commands( args.remediation_script) LOGGER.info( f"created {len(remediation_commands)} remediation commands from {args.remediation_script}" ) commands.extend(remediation_commands) # Playbook execution # if args.live_response_command and ( args.live_response_command.startswith("play") or args.live_response_command == "pb"): if args.playbook_configpath: playbook_commands = build_playbook_commands( args.playbook_configpath) commands.extend(playbook_commands) LOGGER.info( f"loaded {len(playbook_commands)} playbook commands.") if args.playbook_name: playbook_data = get_playbook_map()[args.playbook_name] playbook_path = playbook_data["path"] playbook_commands = build_playbook_commands(playbook_path) commands.extend(playbook_commands) LOGGER.info( f"loaded {len(playbook_commands)} playbook commands.") # Handle LR commands # if commands: timeout = 1200 # default 20 minutes (same used by Cb) if not is_sensor_online(sensor): # Decision point: if the sensor is NOT online, give the analyst and option to wait LOGGER.warning(f"{sensor.id}:{sensor.hostname} is offline.") prompt = "Would you like to wait for the host to come online? (y/n) [y] " wait = input_with_timeout(prompt, default="y") wait = True if wait.lower() == "y" else False if not wait: return None prompt = "How many days do you want to wait? [Default is 7 days] " timeout = input_with_timeout(prompt, default=7) if isinstance(timeout, str): timeout = int(timeout) if timeout > 30: LOGGER.warning( f"{timeout} days is a long time. Restricting to max of 30 days." ) timeout = 30 # 86400 seconds in a day timeout = timeout * 86400 if not session_manager.wait_for_active_session(sensor, timeout=timeout): LOGGER.error(f"reached timeout waiting for active session.") return False # we have an active session, issue the commands. for command in commands: session_manager.submit_command(command, sensor) if session_manager.commands: # Wait for issued commands to complete and process any results. session_manager.process_completed_commands() # Direct Session Interaction # if args.command and args.command.startswith("sess"): if args.list_sensor_sessions: print( json.dumps(sensor_live_response_sessions_by_sensor_id( cb, args.list_sensor_sessions), indent=2, sort_keys=True)) if args.get_session_command_list: print( json.dumps(get_session_commands(cb, args.get_session_command_list), indent=2, sort_keys=True)) if args.list_all_sessions: print( json.dumps(all_live_response_sessions(cb), indent=2, sort_keys=True)) if args.get_session: print( json.dumps(get_session_by_id(cb, args.get_session), indent=2, sort_keys=True)) if args.close_session: session_manager = CustomLiveResponseSessionManager(cb) session_manager._close_session(args.close_session) print( json.dumps(get_session_by_id(cb, args.close_session), indent=2, sort_keys=True)) if args.get_command_result: session_id, command_id = args.get_command_result.split(":", 1) print( json.dumps(get_command_result(cb, session_id, command_id), indent=2, sort_keys=True)) if args.get_file_content: session_id, file_id = args.get_file_content.split(":", 1) get_file_content(cb, session_id, file_id) return True
def print_childprocs(p: Process, current_segment_only: bool = False, raw_print=False): """Print child process events.""" if p.current_segment is None: # avoids server error calling /api/v4/process/{guid}/{segment}/event p.current_segment = p.get_segments()[0] def _print_childproc_events(childprocs): if raw_print: for cp in childprocs: print(cp) return # group start/end childproc events together organized_childprocs = {} for cp in childprocs: guid = cp.procguid[:cp.procguid.rfind("-")] if guid in organized_childprocs: organized_childprocs[guid].append(cp) else: organized_childprocs[guid] = [cp] for cp_guid, cp_events in organized_childprocs.items(): # there should only be two events, a spawn and terminate event # however, don't make assumptions spawn = cp_events[0] terminate_cp = None if len(cp_events) > 1: for cp in cp_events: if cp.timestamp < spawn.timestamp: spawn = cp else: terminate_cp = cp status = "unknown" try: if spawn.is_suppressed: status = "suppressed" status = "terminated" if spawn.process.terminated else "running" if spawn.process.terminated and terminate_cp is None: LOGGER.debug( f"notice: no termination event found. process must have terminated elsewhere?" ) except ObjectNotFoundError: LOGGER.debug(f"child process not found. ") print( f" @{as_configured_timezone(spawn.timestamp)}: ({status}) {spawn.path} md5={spawn.md5} pid={spawn.pid} - {cp_guid}" ) print() print("------ CHILDPROCS ------") childprocs = [] if current_segment_only: childprocs = p.childprocs else: childprocs = p.all_childprocs() try: _print_childproc_events(childprocs) """ for cp in p.children: print(f" @{as_configured_timezone(cp.timestamp)}: {cp.path} md5={cp.md5} pid={cp.pid} - {cp.procguid}") print() """ except Exception as e: LOGGER.error( f"unhandled exception when enumerating childproc events: {e}") return