Command Parser

The local pwncat prompt and scripting configuration language are powered by the CommandParser class which is responsible for parsing lines of text, extracting arguments, and dispatching them to the appropriate command.

Commands are loaded automatically through the pkgutils module in Python from the pwncat/commands directory. Every Python file from this directory is loaded as a module and checked for a Command attribute. This attribute must be a class which inherits from the pwncat.commands.base.CommandDefinition class. This class defines the structure of a command and allows the CommandParser to intelligently create argparse objects, syntax highlighting lexers, and prompt_toolkit completers for your commands.

To create a new command, simply create a python file under the pwncat/commands directory. The name can be anything that conforms to python module naming standards. As an example, the sysinfo command is fairly straightforward:

#!/usr/bin/env python3
from pwncat.commands.base import CommandDefinition, Complete, Parameter
from pwncat.util import console
import pwncat


class Command(CommandDefinition):
    """
    Display remote system information including host ID, IP address,
    architecture, kernel version, distribution and init system. This
    command also provides the capability to view installed services
    if the init system is supported by ``pwncat``.
    """

    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:
                    console.print(
                        f"[green]{service.name}[/green] - {service.description}"
                    )
                else:
                    console.print(f"[red]{service.name}[/red] - {service.description}")
        else:
            console.print(f"Host ID: [cyan]{pwncat.victim.host.hash}[/cyan]")
            console.print(
                f"Remote Address: [green]{pwncat.victim.client.getpeername()}[/green]"
            )
            console.print(f"Architecture: [red]{pwncat.victim.host.arch}[/red]")
            console.print(f"Kernel Version: [red]{pwncat.victim.host.kernel}[/red]")
            console.print(f"Distribution: [red]{pwncat.victim.host.distro}[/red]")
            console.print(f"Init System: [blue]{pwncat.victim.host.init}[/blue]")

This is a simple command that will print system information from the host database and provide the capability to view installed services, provided the init system is supported. This command also shows a basic example of interacting with the remote victim. The pwncat.victim object allows you to interact abstractly with the currently connected victim. The LOCAL property tells the CommandParser whether this command operates only on local resources. If set to true, the command will be allowed to run prior to a connected victim. In this case, we interact directly with the victim, and therefore set the LOCAL property to false.

Command Arguments

Argument parsing is achieved using the python built-in module argparse. The parser is automatically created based on the ARGS, and DEFAULTS dictionaries defined in your Command class.

DEFAULTS is a dictionary mapping argument names to default values. This is passed directly to the argparse.ArgumentParser.set_defaults method. This allows you to set defaults for values which can’t be set in the argument definition (such as values referenced in multiple arguments with dest parameter).

The ARGS property is a dictionary which matches argument names to the parameter objects. The key for this dictionary is a string representing the a comma-separated list of parameter names (e.g. “–param,-p”). The values in this dictionary are built from the parameter method imported above:

def parameter(complete, token=Name.Label, *args, **kwargs):

The first parameter is one of the pwncat.commands.base.Complete enumeration items. Which includes things like Complete.REMOTE_FILE, Complete.LOCAL_FILE, Complete.CHOICES and Complete.NONE. For parameters with no argument (“switches”), this should be Complete.NONE. This controls how the CommandParser tab-completes your command at the local prompt.

The second parameter is the Pygments token which this option should be highlighted with. Normally, you can leave this as default, but you may change it if you like. The remaining arguments are passed directly to argparse.ArgumentParser.add_argument.

Command Helpers

class pwncat.commands.base.Complete

Command argument completion options

CHOICES = 1

Complete argument from the list of choices specified in parameter

LOCAL_FILE = 2

Complete argument as a local file path

NONE = 4

Do not provide argument completions

REMOTE_FILE = 3

Complete argument as a remote file path

pwncat.commands.base.parameter(complete, token=Token.Name.Label, *args, **kwargs)

Generate a parameter definition from completer options, token definition, and argparse add_argument options.

Parameters:
  • complete (Complete) – the completion type
  • token (Pygments Token) – the Pygments token to highlight this argument with
  • args – positional arguments for add_argument
  • kwargs – keyword arguments for add_argument
Returns:

Parameter definition

class pwncat.commands.base.StoreConstOnce(option_strings, dest, nargs=None, const=None, default=None, type=None, choices=None, required=False, help=None, metavar=None)

Only allow the user to store a value in the destination once. This prevents users from selection multiple actions in the privesc parser.

pwncat.commands.base.StoreForAction(action: List[str]) → Callable

Generates a custom argparse Action subclass which verifies that the current selected “action” option is one of the provided actions in this function. If not, an error is raised.

pwncat.commands.base.RemoteFileType(file_exist=True, directory_exist=False)

CommandDefinition Object

class pwncat.commands.base.CommandDefinition

Generic structure for a local command. The docstring for your command class becomes the long-form help for your command.

ARGS = {}

A dictionary of parameter definitions created with the Parameter class. If this is None, your command will receive the raw argument string and no processing will be done except removing the leading command name.

DEFAULTS = {}

A dictionary of default values (passed directly to ArgumentParser.set_defaults)

GROUPS = {}

A dictionary mapping group definitions to group names. The parameters to Group are passed directly to either add_argument_group or add_mutually_exclusive_group with the exception of the mutex arg, which determines the group type.

LOCAL = False

Whether this command is purely local or requires an connected remote host

PROG = 'unimplemented'

The name of your new command

build_parser(parser: argparse.ArgumentParser, args: Dict[str, pwncat.commands.base.Parameter], group_defs: Dict[str, pwncat.commands.base.Group])

Parse the ARGS and DEFAULTS dictionaries to build an argparse ArgumentParser for this command. You should not need to overload this.

Parameters:
  • parser – the parser object to add arguments to
  • args – the ARGS dictionary
run(args)

This is the “main” for your new command. This should perform the action represented by your command.

Parameters:args – the argparse Namespace containing your parsed arguments