def main(): # Default log-level is "INFO" logging.getLogger().setLevel(logging.INFO) # Build the victim object pwncat.victim = Victim() # Arguments to `pwncat` are considered arguments to `connect` # We use the `prog_name` argument to make the help for "connect" # display "pwncat" in the usage. This is just a visual fix, and # isn't used anywhere else. pwncat.victim.command_parser.dispatch_line(shlex.join(["connect"] + sys.argv[1:]), prog_name="pwncat") # Only continue if we successfully connected if not pwncat.victim.connected: exit(0) # Setup the selector to wait for data asynchronously from both streams selector = selectors.DefaultSelector() selector.register(sys.stdin, selectors.EVENT_READ, None) selector.register(pwncat.victim.client, selectors.EVENT_READ, "read") # Initialize our state done = False try: # This loop is only used to funnel data between the local # and remote hosts when in raw mode. During the `pwncat` # prompt, the main loop is handled by the CommandParser # class `run` method. while not done: for k, _ in selector.select(): if k.fileobj is sys.stdin: data = sys.stdin.buffer.read(8) pwncat.victim.process_input(data) else: data = pwncat.victim.recv() if data is None or len(data) == 0: done = True break sys.stdout.buffer.write(data) sys.stdout.flush() except ConnectionResetError: pwncat.victim.restore_local_term() console.log( "[yellow]warning[/yellow]: connection reset by remote host") except SystemExit: console.log("closing connection") finally: # Restore the shell pwncat.victim.restore_local_term() try: # Make sure everything was committed pwncat.victim.session.commit() except InvalidRequestError: pass console.log("local terminal restored")
def main(): params = inspect.signature(BufferedPipe.read).parameters if "flags" not in params: console.log( f"[red]error[/red]: pwncat requires a custom fork of paramiko. This can be installed with `pip install -U git+https://github.com/calebstewart/paramiko`" ) sys.exit(1) # Ignore SQL Alchemy warnings with warnings.catch_warnings(): warnings.simplefilter("ignore", category=sa_exc.SAWarning) # Default log-level is "INFO" logging.getLogger().setLevel(logging.INFO) # Build the victim object pwncat.victim = Victim() # Find the user configuration config_path = (Path(os.environ.get("XDG_CONFIG_HOME", "~/.config/")) / "pwncat" / "pwncatrc") config_path = config_path.expanduser() try: # Read the config script with config_path.open("r") as filp: script = filp.read() # Run the script pwncat.victim.command_parser.eval(script, str(config_path)) except (FileNotFoundError, PermissionError): # The config doesn't exist pass # Arguments to `pwncat` are considered arguments to `connect` # We use the `prog_name` argument to make the help for "connect" # display "pwncat" in the usage. This is just a visual fix, and # isn't used anywhere else. pwncat.victim.command_parser.dispatch_line(shlex.join(["connect"] + sys.argv[1:]), prog_name="pwncat") # Only continue if we successfully connected if not pwncat.victim.connected: sys.exit(0) # Make stdin unbuffered. Without doing this, some key sequences # which are multi-byte don't get sent properly (e.g. up and left # arrow keys) sys.stdin = TextIOWrapper( os.fdopen(sys.stdin.fileno(), "br", buffering=0), write_through=True, line_buffering=False, ) # Setup the selector to wait for data asynchronously from both streams selector = selectors.DefaultSelector() selector.register(sys.stdin, selectors.EVENT_READ, None) selector.register(pwncat.victim.client, selectors.EVENT_READ, "read") # Initialize our state done = False try: # This loop is only used to funnel data between the local # and remote hosts when in raw mode. During the `pwncat` # prompt, the main loop is handled by the CommandParser # class `run` method. while not done: for k, _ in selector.select(): if k.fileobj is sys.stdin: data = sys.stdin.buffer.read(64) pwncat.victim.process_input(data) else: data = pwncat.victim.recv() if data is None or len(data) == 0: done = True break sys.stdout.buffer.write(data) sys.stdout.flush() except ConnectionResetError: pwncat.victim.restore_local_term() console.log( "[yellow]warning[/yellow]: connection reset by remote host") except SystemExit: console.log("closing connection") finally: # Restore the shell pwncat.victim.restore_local_term() try: # Make sure everything was committed pwncat.victim.session.commit() except InvalidRequestError: pass
def main(): # Default log-level is "INFO" logging.getLogger().setLevel(logging.INFO) parser = argparse.ArgumentParser(prog="pwncat") mutex_group = parser.add_mutually_exclusive_group(required=True) mutex_group.add_argument( "--reverse", "-r", action="store_const", dest="type", const="reverse", help="Listen on the specified port for connections from a remote host", ) mutex_group.add_argument( "--bind", "-b", action="store_const", dest="type", const="bind", help="Connect to a remote host", ) parser.add_argument( "--host", "-H", type=str, help= ("Bind address for reverse connections. Remote host for bind connections (default: 0.0.0.0)" ), default="0.0.0.0", ) parser.add_argument( "--port", "-p", type=int, help= "Bind port for reverse connections. Remote port for bind connections", required=True, ) parser.add_argument( "--method", "-m", choices=[*Victim.OPEN_METHODS.keys()], help="Method to create a pty on the remote host (default: script)", default="script", ) parser.add_argument("--config", "-c", help="Configuration script", default=None) args = parser.parse_args() # Build the victim object pwncat.victim = Victim(args.config) # Run the configuration script if args.config: with open(args.config, "r") as filp: config_script = filp.read() pwncat.victim.command_parser.eval(config_script, args.config) if args.type == "reverse": # Listen on a socket for connections util.info(f"binding to {args.host}:{args.port}", overlay=True) server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind((args.host, args.port)) # After the first connection, drop further attempts server.listen(1) # Wait for a single connection try: (client, address) = server.accept() except KeyboardInterrupt: util.warn(f"aborting listener...") sys.exit(0) elif args.type == "bind": util.info(f"connecting to {args.host}:{args.port}", overlay=True) # client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # client.connect((args.host, args.port)) client = socket.create_connection((args.host, args.port)) address = (args.host, args.port) else: parser.error("must specify a valid connection type!") sys.exit(1) util.info(f"connection to {address[0]}:{address[1]} established", overlay=True) # Connect and initialize the remote victim pwncat.victim.connect(client) # Setup the selector to wait for data asynchronously from both streams selector = selectors.DefaultSelector() selector.register(sys.stdin, selectors.EVENT_READ, None) selector.register(client, selectors.EVENT_READ, "read") # Initialize our state done = False try: while not done: for k, _ in selector.select(): if k.fileobj is sys.stdin: data = sys.stdin.buffer.read(8) pwncat.victim.process_input(data) else: data = pwncat.victim.recv() if data is None or len(data) == 0: done = True break sys.stdout.buffer.write(data) sys.stdout.flush() except ConnectionResetError: pwncat.victim.restore_local_term() util.warn("connection reset by remote host") finally: # Restore the shell pwncat.victim.restore_local_term() try: # Make sure everything was committed pwncat.victim.session.commit() except InvalidRequestError: pass util.success("local terminal restored")