From 2767547019ea1cf19075d8ecf2ab6b02e598d739 Mon Sep 17 00:00:00 2001 From: Mitul16 Date: Mon, 19 Jul 2021 17:14:13 +0530 Subject: [PATCH 1/9] Updated 'verbose' in 'Platform' I have changed the logger name from 'str(channel)' to 'str(id(channel))' to create a logger unique to one 'channel'. Also, added a separate method to set verbose output and added a private variable to store the logging handler object --- pwncat/platform/__init__.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/pwncat/platform/__init__.py b/pwncat/platform/__init__.py index 74b7a5e..e1661e5 100644 --- a/pwncat/platform/__init__.py +++ b/pwncat/platform/__init__.py @@ -494,8 +494,9 @@ class Platform(ABC): self.session = session self.channel = channel - self.logger = logging.getLogger(str(channel)) + self.logger = logging.getLogger(str(id(channel))) self.logger.setLevel(logging.DEBUG) + self._verbose_logging_handler = None self.name = "unknown" self._current_user = None @@ -507,8 +508,8 @@ class Platform(ABC): handler.setFormatter(logging.Formatter("%(asctime)s - %(message)s")) self.logger.addHandler(handler) - if verbose: - self.logger.addHandler(RichHandler()) + # set the logging verbosity + self.set_verbose(verbose) base_path = self.PATH_TYPE target = self @@ -943,6 +944,24 @@ class Platform(ABC): def unlink(self, target: str): """Remove a link to a file (similar to `rm`)""" + def set_verbose(self, verbose: bool): + """Enable or disable verbose output + If enabled, commands that are executed by `pwncat` + are logged in the output for the user + otherwise `pwncat` do not show them""" + + # if `verbose` is `True` and there is no handler for it + # then we create one and add it to `self.logger` + # otherwise if there is a handler for it + # then we remove it and set it to `None` + + if verbose and self._verbose_logging_handler is None: + self._verbose_logging_handler = RichHandler() + self.logger.addHandler(self._verbose_logging_handler) + elif not verbose and self._verbose_logging_handler is not None: + self.logger.removeHandler(self._verbose_logging_handler) + self._verbose_logging_handler = None + def register(platform: Type[Platform]): """ From 7ca4760599149f3ab5e419264ae749c36ce261ec Mon Sep 17 00:00:00 2001 From: Mitul16 Date: Mon, 19 Jul 2021 17:14:21 +0530 Subject: [PATCH 2/9] Fixed disabling of verbose output Added a check for the 'set' command, so that if the user changes 'verbose' option then it is applied to every session --- pwncat/commands/set.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pwncat/commands/set.py b/pwncat/commands/set.py index 40c8ad7..73c42dd 100644 --- a/pwncat/commands/set.py +++ b/pwncat/commands/set.py @@ -84,6 +84,12 @@ class Command(CommandDefinition): if args.variable == "db": # Ensure the database is re-opened, if it was already manager.open_database() + if manager.sessions and args.variable == "verbose": + # If the user changed the verbose option + # then apply it to every `session` to take effect + for session_id in manager.sessions: + session = manager.sessions[session_id] + session.platform.set_verbose(args.value == "True") except ValueError as exc: console.log(f"[red]error[/red]: {exc}") elif args.variable is not None: From 61cf46214f7b2d6b3bd90a0c769a8f41e8632095 Mon Sep 17 00:00:00 2001 From: Mitul16 Date: Mon, 26 Jul 2021 11:15:30 +0530 Subject: [PATCH 3/9] Added 'verbose' in argument parser This will set the config variable 'verbose' to True, so we can run 'pwncat ... --verbose/-V' and have verbose output without the need to 'set vebrose True'. Because we do not have access to pwncat's local prompt if we run it as 'pwncat ...' --- pwncat/__main__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pwncat/__main__.py b/pwncat/__main__.py index 9b612f1..33c0e72 100644 --- a/pwncat/__main__.py +++ b/pwncat/__main__.py @@ -80,6 +80,12 @@ def main(): metavar="port", help="Alternative port number to support netcat-style syntax", ) + parser.add_argument( + "--verbose", + "-V", + action="store_true", + help="Enable verbose output for the remote commands executed by `pwncat`" + ) args = parser.parse_args() # Print the version number and exit. @@ -90,6 +96,12 @@ def main(): # Create the session manager with pwncat.manager.Manager(args.config) as manager: + if args.verbose: + # set the config variable `verbose` to `True` (globally) + manager.config.set( + 'verbose', True, True + ) + if args.download_plugins: for plugin_info in pwncat.platform.Windows.PLUGIN_INFO: with pwncat.platform.Windows.open_plugin( From 5477cfac667778739f330a4e7f022f4482776fcd Mon Sep 17 00:00:00 2001 From: Mitul16 Date: Mon, 26 Jul 2021 11:17:27 +0530 Subject: [PATCH 4/9] Fixed docstrings for local commands There was inconsistent use of the docstrings affecting the output while using 'help COMMAND' --- pwncat/commands/alias.py | 6 ++++-- pwncat/commands/back.py | 4 +++- pwncat/commands/bind.py | 6 ++++-- pwncat/commands/download.py | 4 +++- pwncat/commands/escalate.py | 3 ++- pwncat/commands/help.py | 4 +++- pwncat/commands/info.py | 4 +++- pwncat/commands/local.py | 4 +++- pwncat/commands/search.py | 4 +++- pwncat/commands/set.py | 4 +++- pwncat/commands/upload.py | 4 +++- pwncat/commands/use.py | 4 +++- 12 files changed, 37 insertions(+), 14 deletions(-) diff --git a/pwncat/commands/alias.py b/pwncat/commands/alias.py index f1d5ecb..77c02a8 100644 --- a/pwncat/commands/alias.py +++ b/pwncat/commands/alias.py @@ -5,9 +5,11 @@ from pwncat.commands import Complete, Parameter, CommandDefinition class Command(CommandDefinition): - """Alias an existing command with a new name. Specifying no alias or command + """ + 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.""" + alias if it exists. + """ def get_command_names(self): return [c.PROG for c in self.manager.parser.commands] diff --git a/pwncat/commands/back.py b/pwncat/commands/back.py index 574a06e..f0d35af 100644 --- a/pwncat/commands/back.py +++ b/pwncat/commands/back.py @@ -4,7 +4,9 @@ from pwncat.commands import CommandDefinition class Command(CommandDefinition): - """Return to the remote terminal""" + """ + Return to the remote terminal + """ PROG = "back" ARGS = {} diff --git a/pwncat/commands/bind.py b/pwncat/commands/bind.py index 3d07428..7c72c1e 100644 --- a/pwncat/commands/bind.py +++ b/pwncat/commands/bind.py @@ -7,8 +7,10 @@ from pwncat.commands import Complete, Parameter, CommandDefinition class Command(CommandDefinition): - """Create key aliases for when in raw mode. This only works from platforms - which provide a raw interaction (such as linux).""" + """ + Create key aliases for when in raw mode. This only works from platforms + which provide a raw interaction (such as linux). + """ PROG = "bind" ARGS = { diff --git a/pwncat/commands/download.py b/pwncat/commands/download.py index 002636c..089ee99 100644 --- a/pwncat/commands/download.py +++ b/pwncat/commands/download.py @@ -18,7 +18,9 @@ from pwncat.commands import Complete, Parameter, CommandDefinition class Command(CommandDefinition): - """Download a file from the remote host to the local host""" + """ + Download a file from the remote host to the local host + """ PROG = "download" ARGS = { diff --git a/pwncat/commands/escalate.py b/pwncat/commands/escalate.py index 0552b80..3c48903 100644 --- a/pwncat/commands/escalate.py +++ b/pwncat/commands/escalate.py @@ -66,7 +66,8 @@ class Command(CommandDefinition): The list command is simply a wrapper around enumerating "escalation.*". This makes the escalation workflow more straightforward, but is not - required.""" + required. + """ PROG = "escalate" ARGS = { diff --git a/pwncat/commands/help.py b/pwncat/commands/help.py index 2340d63..927e2f6 100644 --- a/pwncat/commands/help.py +++ b/pwncat/commands/help.py @@ -10,7 +10,9 @@ from pwncat.commands import Complete, Parameter, CommandDefinition class Command(CommandDefinition): - """List known commands and print their associated help documentation.""" + """ + List known commands and print their associated help documentation. + """ def get_command_names(self): try: diff --git a/pwncat/commands/info.py b/pwncat/commands/info.py index be26063..c5be955 100644 --- a/pwncat/commands/info.py +++ b/pwncat/commands/info.py @@ -10,7 +10,9 @@ from pwncat.commands import Complete, Parameter, CommandDefinition, get_module_c class Command(CommandDefinition): - """View info about a module""" + """ + View info about a module + """ PROG = "info" ARGS = { diff --git a/pwncat/commands/local.py b/pwncat/commands/local.py index bcb2ff0..5b450cf 100644 --- a/pwncat/commands/local.py +++ b/pwncat/commands/local.py @@ -6,7 +6,9 @@ from pwncat.commands import CommandDefinition class Command(CommandDefinition): - """Run a local shell command on your attacking machine""" + """ + Run a local shell command on your attacking machine + """ PROG = "local" ARGS = None diff --git a/pwncat/commands/search.py b/pwncat/commands/search.py index c90ce9e..ab91c1b 100644 --- a/pwncat/commands/search.py +++ b/pwncat/commands/search.py @@ -10,7 +10,9 @@ from pwncat.commands import Complete, Parameter, CommandDefinition class Command(CommandDefinition): - """View info about a module""" + """ + View info about a module + """ PROG = "search" ARGS = { diff --git a/pwncat/commands/set.py b/pwncat/commands/set.py index 73c42dd..0a5296d 100644 --- a/pwncat/commands/set.py +++ b/pwncat/commands/set.py @@ -5,7 +5,9 @@ from pwncat.commands import Complete, Parameter, CommandDefinition class Command(CommandDefinition): - """Set variable runtime variable parameters for pwncat""" + """ + Set variable runtime variable parameters for pwncat + """ def get_config_variables(self): options = ["state"] + list(self.manager.config.values) diff --git a/pwncat/commands/upload.py b/pwncat/commands/upload.py index 13e547d..badb828 100644 --- a/pwncat/commands/upload.py +++ b/pwncat/commands/upload.py @@ -17,7 +17,9 @@ from pwncat.commands import Complete, Parameter, CommandDefinition class Command(CommandDefinition): - """Upload a file from the local host to the remote host""" + """ + Upload a file from the local host to the remote host + """ PROG = "upload" ARGS = { diff --git a/pwncat/commands/use.py b/pwncat/commands/use.py index 31e668b..19afd97 100644 --- a/pwncat/commands/use.py +++ b/pwncat/commands/use.py @@ -6,7 +6,9 @@ from pwncat.commands import Complete, Parameter, CommandDefinition, get_module_c class Command(CommandDefinition): - """Set the currently used module in the config handler""" + """ + Set the currently used module in the config handler + """ PROG = "use" ARGS = { From dfb2f28f903bbc63dae61619b167b3e02ab3b1b5 Mon Sep 17 00:00:00 2001 From: Mitul16 Date: Mon, 26 Jul 2021 11:18:42 +0530 Subject: [PATCH 5/9] Added 'PlatformError' to PrivateKey implant 'trigger' method --- pwncat/facts/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pwncat/facts/__init__.py b/pwncat/facts/__init__.py index 7601294..1cf7e9e 100644 --- a/pwncat/facts/__init__.py +++ b/pwncat/facts/__init__.py @@ -17,6 +17,7 @@ import pwncat from pwncat.db import Fact from pwncat.channel import ChannelError from pwncat.modules import ModuleFailed +from pwncat.platform import PlatformError from pwncat.facts.tamper import ( # noqa: F401 Tamper, CreatedFile, @@ -369,7 +370,7 @@ class PrivateKey(Implant): user=user.name, identity=filp.name, ) - except ChannelError as exc: + except (ChannelError, PlatformError) as exc: manager.log( f"[yellow]warning[/yellow]: {self.source} implant failed; removing implant types." ) From a859007ca4bb07178e4619dc00d3348367b7875b Mon Sep 17 00:00:00 2001 From: Mitul16 Date: Mon, 26 Jul 2021 11:21:26 +0530 Subject: [PATCH 6/9] Added 'OSError' handling to bind protocol --- pwncat/channel/bind.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/pwncat/channel/bind.py b/pwncat/channel/bind.py index eaf7b18..75d0bfd 100644 --- a/pwncat/channel/bind.py +++ b/pwncat/channel/bind.py @@ -8,6 +8,7 @@ The only required argument for a bind channel is the port number. By default, the channel will listen on all interfaces (bound to ``0.0.0.0``). """ import socket +import errno from rich.progress import Progress, BarColumn @@ -34,7 +35,23 @@ class Bind(Socket): super().__init__(client=None, host=host, port=port, **kwargs) self.address = (host, port) - self.server = socket.create_server((host, port), reuse_port=True) + + try: + self.server = socket.create_server((host, port), reuse_port=True) + except OSError as exc: + error_message = str(exc) + + if exc.args[0] == errno.EACCES: + # See `/proc/sys/net/ipv4/ip_unprivileged_port_start` + error_message = "unable to listen on a privileged port" +\ + "\nusually ports in the range 0-1023 are restricted" +\ + "\n[green][TRY][/green]: try to run `pwncat` as `[red]root[/red]`" + elif exc.args[0] == errno.EADDRINUSE: + error_message = "port is already in use" + elif exc.args[0] == errno.EADDRNOTAVAIL: + error_message = "unable to bind on given address" + + raise ChannelError(self, error_message) def connect(self): From 62baba017fc6b9ca64a84d6f389631b8bde25981 Mon Sep 17 00:00:00 2001 From: Mitul16 Date: Wed, 28 Jul 2021 06:01:40 +0530 Subject: [PATCH 7/9] Pre-merge tasks completed! --- pwncat/__main__.py | 6 ++---- pwncat/channel/bind.py | 10 ++++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pwncat/__main__.py b/pwncat/__main__.py index 33c0e72..056c3b3 100644 --- a/pwncat/__main__.py +++ b/pwncat/__main__.py @@ -84,7 +84,7 @@ def main(): "--verbose", "-V", action="store_true", - help="Enable verbose output for the remote commands executed by `pwncat`" + help="Enable verbose output for the remote commands executed by `pwncat`", ) args = parser.parse_args() @@ -98,9 +98,7 @@ def main(): if args.verbose: # set the config variable `verbose` to `True` (globally) - manager.config.set( - 'verbose', True, True - ) + manager.config.set("verbose", True, True) if args.download_plugins: for plugin_info in pwncat.platform.Windows.PLUGIN_INFO: diff --git a/pwncat/channel/bind.py b/pwncat/channel/bind.py index 75d0bfd..c3c680c 100644 --- a/pwncat/channel/bind.py +++ b/pwncat/channel/bind.py @@ -7,8 +7,8 @@ would have been a reverse shell payload. The only required argument for a bind channel is the port number. By default, the channel will listen on all interfaces (bound to ``0.0.0.0``). """ -import socket import errno +import socket from rich.progress import Progress, BarColumn @@ -43,9 +43,11 @@ class Bind(Socket): if exc.args[0] == errno.EACCES: # See `/proc/sys/net/ipv4/ip_unprivileged_port_start` - error_message = "unable to listen on a privileged port" +\ - "\nusually ports in the range 0-1023 are restricted" +\ - "\n[green][TRY][/green]: try to run `pwncat` as `[red]root[/red]`" + error_message = ( + "unable to listen on a privileged port" + + "\nusually ports in the range 0-1023 are restricted" + + "\n[green][TRY][/green]: try to run `pwncat` as `[red]root[/red]`" + ) elif exc.args[0] == errno.EADDRINUSE: error_message = "port is already in use" elif exc.args[0] == errno.EADDRNOTAVAIL: From 5d2dd7078e817a6cab880d1112f9fdf5f9ac901d Mon Sep 17 00:00:00 2001 From: Mitul16 Date: Thu, 12 Aug 2021 11:11:46 +0530 Subject: [PATCH 8/9] Updated CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index faec69e..3980db0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,13 @@ and simply didn't have the time to go back and retroactively create one. ### Fixed - Possible exception due to _pre-registering_ of `session` with `manager` +- Fixed verbose logging handler to be __unique__ for every `channel` +- Fixed docstrings in `Command` modules ### Added - Added alternatives to `bash` to be used during _shell upgrade_ for a _better shell_ - Added a warning message when a `KeyboardInterrupt` is caught +- Added `--verbose/-V` for argument parser +- Added `OSError` for `bind` protocol to show appropriate error messages ### Changed - Changed some 'red' warning message color to 'yellow' From 4043e95adc7b3daa6036cee961c4e9f417ebeb95 Mon Sep 17 00:00:00 2001 From: Mitul16 Date: Thu, 12 Aug 2021 11:27:33 +0530 Subject: [PATCH 9/9] Fixed possible typo in 'id' command in 'refresh_uid' Real and effective 'gid' are interchanged --- pwncat/platform/linux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwncat/platform/linux.py b/pwncat/platform/linux.py index c9c45e7..8a7938a 100644 --- a/pwncat/platform/linux.py +++ b/pwncat/platform/linux.py @@ -821,7 +821,7 @@ class Linux(Platform): while True: try: proc = self.run( - "(id -ru;id -u;id -g;id -rg;id -G;)", + "(id -ru;id -u;id -rg;id -g;id -G;)", capture_output=True, text=True, check=True,