def execute(cmd_tokens, syscall_filters): with open(HISTORY_PATH, 'a') as history_file: history_file.write(' '.join(cmd_tokens) + os.linesep) if cmd_tokens: # Extract command name and arguments from tokens cmd_name = cmd_tokens[0] cmd_args = cmd_tokens[1:] DIRFD_ARGUMENTS.clear() SYSCALL_ARG_DICT.clear() ARGUMENT_CALLBACK.clear() # If the command is a built-in command, # invoke its function with arguments if cmd_name in built_in_cmds: return built_in_cmds[cmd_name](cmd_args) # Wait for a kill signal signal.signal(signal.SIGINT, handler_kill) try: # Spawn a child process cmd_tokens[0] = locateProgram(cmd_tokens[0]) pid = createChild(cmd_tokens, False) except Exception as error: print("Error %s executing %s" % (error, cmd_name)) return 1 debugger = ptrace.debugger.PtraceDebugger() debugger.traceFork() debugger.traceExec() process = debugger.addProcess(pid, True) process.syscall() snapshots = None try: snapshots, exit_code = debug_process(debugger, syscall_filters) if exit_code != 0: print('Command unsuccessful - rollbacking changes') snapshots.rollback_all() except Exception as error: print("Error tracing process: %s." % error) return SHELL_STATUS_STOP except KeyboardInterrupt: print("%s terminated by keyboard interrupt." % cmd_name) return SHELL_STATUS_STOP finally: # Cut down all processes no matter what happens # to prevent them from doing any damage debugger.quit() if snapshots is not None: snapshots.clean() # Return status indicating to wait for next command in shell_loop return SHELL_STATUS_RUN
def main(): sys.argv = sys.argv[1:] filter_scopes = SYSCALL_FILTERS.keys() syscall_filters = {} for filter_scope in SYSCALL_FILTERS: if filter_scope in filter_scopes: for syscall in SYSCALL_FILTERS[filter_scope]: syscall_filters[syscall] = SYSCALL_FILTERS[filter_scope][ syscall] # Suppress logging output from python-ptrace getLogger().addHandler(NullHandler()) # Prevent python-ptrace from decoding arguments to keep raw numerical values DIRFD_ARGUMENTS.clear() SYSCALL_ARG_DICT.clear() ARGUMENT_CALLBACK.clear() try: sys.argv[0] = locateProgram(sys.argv[0]) pid = createChild(sys.argv, False) except Exception as error: print(f"Error executing {sys.argv}: {error}.") return 1 debugger = PtraceDebugger() debugger.traceFork() debugger.traceExec() process = debugger.addProcess(pid, True) process.syscall() try: operations = get_operations(debugger, syscall_filters, True) except Exception as error: print(f"Error tracing process: {error}.") return 1 finally: # Cut down all processes no matter what happens # to prevent them from doing any damage debugger.quit() if operations: for operation in operations: print(" " + operation) else: print(f"Not detected any file system operations from: {sys.argv}")
def main(argv=sys.argv[1:]): if PY2: argv = [unicode(arg, sys.getfilesystemencoding()) for arg in argv] # noqa # Insert positional argument separator, if not already present if "--" not in argv: for i, argument in enumerate(argv): if not argument.startswith("-"): argv.insert(i, "--") break arg_parser = ArgumentParser( prog="maybe", usage="%(prog)s [options] command [argument ...]", description="Run a command without the ability to make changes to your system " + "and list the changes it would have made.", epilog="For more information, to report issues or to contribute, " + "visit https://github.com/p-e-w/maybe.", ) arg_parser.add_argument("command", nargs="+", help="the command to run under maybe's control") arg_group = arg_parser.add_mutually_exclusive_group() arg_group.add_argument("-a", "--allow", nargs="+", metavar="OPERATION", help="allow the command to perform the specified operation(s). " + "all other operations will be denied. " + "possible values for %(metavar)s are: " + ", ".join(sorted(SYSCALL_FILTERS.keys())) + "; as well as any filter scopes defined by loaded plugins") arg_group.add_argument("-d", "--deny", nargs="+", metavar="OPERATION", help="deny the command the specified operation(s). " + "all other operations will be allowed. " + "see --allow for a list of possible values for %(metavar)s. " + "--allow and --deny cannot be combined") arg_parser.add_argument("-p", "--plugin", nargs="+", metavar="FILE", help="load the specified plugin script(s). " + "see the README for details and plugin API documentation") arg_parser.add_argument("-l", "--list-only", action="store_true", help="list operations without header, indentation and rerun prompt") arg_parser.add_argument("--style-output", choices=["yes", "no", "auto"], default="auto", help="colorize output using ANSI escape sequences (yes/no) " + "or automatically decide based on whether stdout is a terminal (auto, default)") arg_parser.add_argument("-v", "--verbose", action="count", help="if specified once, print every filtered syscall. " + "if specified twice, print every syscall, highlighting filtered syscalls") arg_parser.add_argument("--version", action="version", version="%(prog)s 0.4.0") args = arg_parser.parse_args(argv) initialize_terminal(args.style_output) if args.plugin is not None: for plugin_path in args.plugin: try: module_name = splitext(basename(plugin_path))[0] # Note: imp.load_source is *long* deprecated and not even documented # in Python 3 anymore, but it still seems to work and the "alternatives" # (see http://stackoverflow.com/a/67692) are simply too insane to use load_source(module_name, plugin_path) except Exception as error: print(T.red("Error loading %s: %s." % (T.bold(plugin_path) + T.red, error))) return 1 if args.allow is not None: for filter_scope in args.allow: if filter_scope not in SYSCALL_FILTERS: print(T.red("Unknown operation in --allow: %s." % (T.bold(filter_scope) + T.red))) return 1 filter_scopes = set(SYSCALL_FILTERS.keys()) - set(args.allow) elif args.deny is not None: for filter_scope in args.deny: if filter_scope not in SYSCALL_FILTERS: print(T.red("Unknown operation in --deny: %s." % (T.bold(filter_scope) + T.red))) return 1 filter_scopes = args.deny else: filter_scopes = SYSCALL_FILTERS.keys() syscall_filters = {} for filter_scope in SYSCALL_FILTERS: if filter_scope in filter_scopes: for syscall in SYSCALL_FILTERS[filter_scope]: syscall_filters[syscall] = SYSCALL_FILTERS[filter_scope][syscall] # Suppress logging output from python-ptrace getLogger().addHandler(NullHandler()) # Prevent python-ptrace from decoding arguments to keep raw numerical values DIRFD_ARGUMENTS.clear() SYSCALL_ARG_DICT.clear() ARGUMENT_CALLBACK.clear() # This is basically "shlex.join" command = " ".join([(("'%s'" % arg) if (" " in arg) else arg) for arg in args.command]) try: args.command[0] = locateProgram(args.command[0]) pid = createChild(args.command, False) except Exception as error: print(T.red("Error executing %s: %s." % (T.bold(command) + T.red, error))) return 1 debugger = PtraceDebugger() debugger.traceFork() debugger.traceExec() process = debugger.addProcess(pid, True) process.syscall() try: operations = get_operations(debugger, syscall_filters, args.verbose) except Exception as error: print(T.red("Error tracing process: %s." % error)) return 1 except KeyboardInterrupt: print(T.yellow("%s terminated by keyboard interrupt." % (T.bold(command) + T.yellow))) return 2 finally: # Cut down all processes no matter what happens # to prevent them from doing any damage debugger.quit() if operations: if not args.list_only: print("%s has prevented %s from performing %d file system operations:\n" % (T.bold("maybe"), T.bold(command), len(operations))) for operation in operations: print(("" if args.list_only else " ") + operation) if not args.list_only: print("\nDo you want to rerun %s and permit these operations? [y/N] " % T.bold(command), end="") try: choice = input() except KeyboardInterrupt: choice = "" # Ctrl+C does not print a newline automatically print("") if choice.lower() == "y": subprocess.call(args.command) else: print("%s has not detected any file system operations from %s." % (T.bold("maybe"), T.bold(command)))
def main(argv=sys.argv[1:]): filter_scopes = sorted(SYSCALL_FILTERS.keys()) # Insert positional argument separator, if not already present if "--" not in argv: for i, argument in enumerate(argv): if not argument.startswith("-"): argv.insert(i, "--") break arg_parser = ArgumentParser( prog="maybe", usage="%(prog)s [options] command [argument ...]", description= "Run a command without the ability to make changes to your system " + "and list the changes it would have made.", epilog="For more information, to report issues or to contribute, " + "visit https://github.com/p-e-w/maybe.", ) arg_parser.add_argument("command", nargs="+", help="the command to run under maybe's control") arg_group = arg_parser.add_mutually_exclusive_group() arg_group.add_argument( "-a", "--allow", nargs="+", choices=filter_scopes, metavar="OPERATION", help="allow the command to perform the specified operation(s). " + "all other operations will be denied. " + "possible values for %(metavar)s are: %(choices)s") arg_group.add_argument( "-d", "--deny", nargs="+", choices=filter_scopes, metavar="OPERATION", help="deny the command the specified operation(s). " + "all other operations will be allowed. " + "see --allow for a list of possible values for %(metavar)s. " + "--allow and --deny cannot be combined") arg_parser.add_argument( "-l", "--list-only", action="store_true", help="list operations without header, indentation and rerun prompt") arg_parser.add_argument( "--style-output", choices=["yes", "no", "auto"], default="auto", help="colorize output using ANSI escape sequences (yes/no) " + "or automatically decide based on whether stdout is a terminal (auto, default)" ) arg_parser.add_argument( "-v", "--verbose", action="count", help="if specified once, print every filtered syscall. " + "if specified twice, print every syscall, highlighting filtered syscalls" ) arg_parser.add_argument("--version", action="version", version="%(prog)s 0.4.0") args = arg_parser.parse_args(argv) initialize_terminal(args.style_output) if args.allow is not None: filter_scopes = set(filter_scopes) - set(args.allow) elif args.deny is not None: filter_scopes = args.deny syscall_filters = {} for filter_scope in filter_scopes: for syscall in SYSCALL_FILTERS[filter_scope]: syscall_filters[syscall] = SYSCALL_FILTERS[filter_scope][syscall] # Prevent python-ptrace from decoding arguments to keep raw numerical values DIRFD_ARGUMENTS.clear() SYSCALL_ARG_DICT.clear() ARGUMENT_CALLBACK.clear() # This is basically "shlex.join" command = " ".join([(("'%s'" % arg) if (" " in arg) else arg) for arg in args.command]) try: args.command[0] = locateProgram(args.command[0]) pid = createChild(args.command, False) except Exception as error: print( T.red("Error executing %s: %s." % (T.bold(command) + T.red, error))) return 1 debugger = PtraceDebugger() debugger.traceFork() debugger.traceExec() process = debugger.addProcess(pid, True) process.syscall() try: operations = get_operations(debugger, syscall_filters, args.verbose) except Exception as error: print(T.red("Error tracing process: %s." % error)) return 1 except KeyboardInterrupt: print( T.yellow("%s terminated by keyboard interrupt." % (T.bold(command) + T.yellow))) return 2 finally: # Cut down all processes no matter what happens # to prevent them from doing any damage debugger.quit() if operations: if not args.list_only: print( "%s has prevented %s from performing %d file system operations:\n" % (T.bold("maybe"), T.bold(command), len(operations))) for operation in operations: print(("" if args.list_only else " ") + operation) if not args.list_only: try: choice = input( "\nDo you want to rerun %s and permit these operations? [y/N] " % T.bold(command)) except KeyboardInterrupt: choice = "" # Ctrl+C does not print a newline automatically print("") if choice.lower() == "y": subprocess.call(args.command) else: print("%s has not detected any file system operations from %s." % (T.bold("maybe"), T.bold(command)))
def main(argv=sys.argv[1:]): filter_scopes = sorted(SYSCALL_FILTERS.keys()) # Insert positional argument separator, if not already present if "--" not in argv: for i, argument in enumerate(argv): if not argument.startswith("-"): argv.insert(i, "--") break arg_parser = ArgumentParser( prog="maybe", usage="%(prog)s [options] command [argument ...]", description="Run a command without the ability to make changes to your system " + "and list the changes it would have made.", epilog="For more information, to report issues or to contribute, " + "visit https://github.com/p-e-w/maybe.", ) arg_parser.add_argument("command", nargs="+", help="the command to run under maybe's control") arg_group = arg_parser.add_mutually_exclusive_group() arg_group.add_argument("-a", "--allow", nargs="+", choices=filter_scopes, metavar="OPERATION", help="allow the command to perform the specified operation(s). " + "all other operations will be denied. " + "possible values for %(metavar)s are: %(choices)s") arg_group.add_argument("-d", "--deny", nargs="+", choices=filter_scopes, metavar="OPERATION", help="deny the command the specified operation(s). " + "all other operations will be allowed. " + "see --allow for a list of possible values for %(metavar)s. " + "--allow and --deny cannot be combined") arg_parser.add_argument("-l", "--list-only", action="store_true", help="list operations without header, indentation and rerun prompt") arg_parser.add_argument("--style-output", choices=["yes", "no", "auto"], default="auto", help="colorize output using ANSI escape sequences (yes/no) " + "or automatically decide based on whether stdout is a terminal (auto, default)") arg_parser.add_argument("-v", "--verbose", action="count", help="if specified once, print every filtered syscall. " + "if specified twice, print every syscall, highlighting filtered syscalls") arg_parser.add_argument("--version", action="version", version="%(prog)s 0.4.0") args = arg_parser.parse_args(argv) initialize_terminal(args.style_output) if args.allow is not None: filter_scopes = set(filter_scopes) - set(args.allow) elif args.deny is not None: filter_scopes = args.deny syscall_filters = {} for filter_scope in filter_scopes: for syscall_filter in SYSCALL_FILTERS[filter_scope]: syscall_filters[syscall_filter.syscall] = syscall_filter # Prevent python-ptrace from decoding arguments to keep raw numerical values DIRFD_ARGUMENTS.clear() SYSCALL_ARG_DICT.clear() ARGUMENT_CALLBACK.clear() # This is basically "shlex.join" command = " ".join([(("'%s'" % arg) if (" " in arg) else arg) for arg in args.command]) try: args.command[0] = locateProgram(args.command[0]) pid = createChild(args.command, False) except Exception as error: print(T.red("Error executing %s: %s." % (T.bold(command) + T.red, error))) return 1 debugger = PtraceDebugger() debugger.traceExec() try: debugger.traceFork() except DebuggerError: print(T.yellow("Warning: Running without traceFork support. " + "Syscalls from subprocesses can not be intercepted.")) process = debugger.addProcess(pid, True) process.syscall() try: operations = get_operations(debugger, syscall_filters, args.verbose) except Exception as error: print(T.red("Error tracing process: %s." % error)) return 1 except KeyboardInterrupt: print(T.yellow("%s terminated by keyboard interrupt." % (T.bold(command) + T.yellow))) return 2 finally: # Cut down all processes no matter what happens # to prevent them from doing any damage debugger.quit() if operations: if not args.list_only: print("%s has prevented %s from performing %d file system operations:\n" % (T.bold("maybe"), T.bold(command), len(operations))) for operation in operations: print(("" if args.list_only else " ") + operation) if not args.list_only: try: choice = input("\nDo you want to rerun %s and permit these operations? [y/N] " % T.bold(command)) except KeyboardInterrupt: choice = "" # Ctrl+C does not print a newline automatically print("") if choice.lower() == "y": subprocess.call(args.command) else: print("%s has not detected any file system operations from %s." % (T.bold("maybe"), T.bold(command)))