from ptrace.syscall.syscall_argument import ARGUMENT_CALLBACK from .syscall_filters import SYSCALL_FILTERS from .utilities import T, SYSCALL_REGISTER, RETURN_VALUE_REGISTER # Python 2/3 compatibility hack # Source: http://stackoverflow.com/a/7321970 try: input = raw_input except NameError: pass # Register filtered syscalls with python-ptrace so they are parsed correctly SYSCALL_PROTOTYPES.clear() FILENAME_ARGUMENTS.clear() for syscall_filter in SYSCALL_FILTERS: SYSCALL_PROTOTYPES[syscall_filter.name] = syscall_filter.signature for argument in syscall_filter.signature[1]: if argument[0] == "const char *": FILENAME_ARGUMENTS.add(argument[1]) # Turn list into dictionary indexed by syscall name for fast filter retrieval SYSCALL_FILTERS = {syscall_filter.name: syscall_filter for syscall_filter in SYSCALL_FILTERS} # Prevent python-ptrace from decoding arguments to keep raw numerical values SYSCALL_ARG_DICT.clear() ARGUMENT_CALLBACK.clear()
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_PROTOTYPES.clear() FILENAME_ARGUMENTS.clear() syscall_filters = {} for filter_scope in filter_scopes: for syscall_filter in SYSCALL_FILTERS[filter_scope]: syscall_filters[syscall_filter.name] = syscall_filter # Register filtered syscalls with python-ptrace so they are parsed correctly SYSCALL_PROTOTYPES[syscall_filter.name] = syscall_filter.signature for argument in syscall_filter.signature[1]: if argument[0] == "const char *": FILENAME_ARGUMENTS.add(argument[1]) # Prevent python-ptrace from decoding arguments to keep raw numerical values 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)))