Modules

pwncat is extended primarily by implementing modules. This concept is similar in theory to Metasploit modules. Modules are organized by their purpose, so modules for persistence are under the persist package while modules for enumeration are under the enumerate package.

At their core, modules are simply classes which implement a run function to perform some task. Modules can have arguments which are passed as normal keyword arguments to the run method. Argument names, types and documentation is stored in the class property ARGUMENTS which is a dictionary mapping argument names to Argument instances.

When a user executes the following commands at the local prompt:

pwncat will first lookup the Python module pwncat.modules.persist.cron_reverse. This module must implement a class named Module which inherits from the BaseModule class. Next, each set call will cross-reference the parameter name with the module arguments and ensure type-checking is performed. Lastly, the run method will be executed.

Return values from modules are displayed in two ways. First, if the return value conforms to the Result interface, it is formatted and displayed with a title and description under the appropriate category. Otherwise, a list of uncategorized results will be displayed. If no return value is found, a simple “Module completed successfully” message will be displayed.

Creating Base Modules

Specilized categories such as enumerate, persist, and escalate have their own module base classes which all inherit from the BaseModule class. If you’d like to create a generic module which does not fit into these categories, you can subclass the BaseModule class itself.

A basic module is placed anywhere under the pwncat/modules/ package. It will be automatically loaded upon opening pwncat. A basic module named “random_string.py” could be placed under “pwncat/modules” and may look like this:

class Module(BaseModule):
    """
    Module documentation. This is shown with the `info` command.
    """

    ARGUMENTS = {
        "length": Argument(type=int, default=10, help="How long to make the string"),
        "alphabet": Argument(type=str, default=string.ascii_printable, help="The characters to choose from")
    }

    def run(self, length, alphabet):
        return "".join([random.choice(alphabet) for _ in range(length)])

This module simply generates a random string of a given length and can be executed in a few different ways at the pwncat prompt:

This module can also be used from within pwncat by using the pwncat.modules helper functions to locate and run modules by name:

import pwncat.modules
result = pwncat.modules.run("random_string", length=5, alphabet="0123456789abcdef")
result = list(pwncat.modules.match("random_.*"))[0].run(length=2, alphabet="hello")
result = pwncat.modules.find("random_string").run(length=2, alphabet="hello")

Module Results

Module run methods should return result objects which are compatible with the pwncat.modules.Result object. How those values are returned can happen in a few ways. For simple modules, the run method can simply return the result with the return statement. In this case, no progress bar will be created and until the module finishes there will be no status output. Alternatively, if the run method is a generator, a progress bar is automatically created using rich and the status is updated with each of the yield’d values. The results should always have a __str__ operator defined so that status results can be printed properly. The result of the run method will never be a generator when called externally, however. The base module class wraps the subclass run method, creates a progress bar, collects the output and returns an interable object containing all of the results.

If a module would like to update the current progress status without returning any data, it can do so using the pwncat.modules.Status type. If an object of this type is yield’d, it will not be added to the resulting return value of run, and will only update the progress bar. The Status class is simply a subclass of str, therefore issuing yield Status(“new module status”) will update the progress bar accordingly.

If you need to implement a custom result class to encapsulate your results, you can do so either by directly inheriting from the Result class or by simply implementing the required methods. The only strictly required methods are either the title or __str__ methods. By default, the title method will simply return str(self), so overriding __str__ is normally enough. This controls the single-line output of this result on the terminal.

For objects which require larger output, you can utilize the description method. This method returns a string with a long-description of your object. For example, the private_key enumeration data implements this method to show the entire content of the private key while the title just indicates that it is a private key and where it was found.

Lastly, there is a category method which specifies how to categorize this result. The affects how the data is separated and displayed when run from the prompt.

Other methods or properties can be added at will to this object. The above methods are not meant to obstruct the programmatic use of the data returned by a module so that you can organically return results from modules while still having properly formatted output from the prompt.

Recursively Calling Modules

If your new module needs to call another module (through any of the interfaces above), you should be sure to pass the current progress bar down to the sub-modules. This is done like so:

class Module(BaseModule):
    def run(self):
        result = pwncat.modules.run("some.other.module", progress=self.progress)

This ensures that multiple progress bars are not created and fighting over the terminal lock.

Exceptions

class pwncat.modules.ModuleNotFound

The specified module was not found

class pwncat.modules.ArgumentFormatError

Format of one of the arguments was incorrect

class pwncat.modules.MissingArgument

A required argument is missing

class pwncat.modules.InvalidArgument

This argument does not exist and ALLOW_KWARGS was false

class pwncat.modules.ModuleFailed

Base class for module failure

Module Helper Classes

class pwncat.modules.Argument(type: Callable[[str], Any] = <class 'str'>, default: Any = <class 'pwncat.modules.NoValue'>, help: str = '')

Argument information for a module

default

The default value if none is specified in run. If this is NoValue, then the argument is required.

alias of NoValue

help = ''

The help text displayed in the info output.

type

alias of builtins.str

pwncat.modules.List(_type=<class 'str'>)

Argument list type, which accepts a list of the provided type.

pwncat.modules.Bool(value: str)

Argument of type “bool”. Accepts true/false (case-insensitive) as well as 1/0. The presence of an argument of type “Bool” with no assignment (e.g. run module arg) is equivalent to run module arg=true.

class pwncat.modules.Status

A result which isn’t actually returned, but simply updates the progress bar. It is equivalent to a string, so this is valid: yield Status("module status update")

class pwncat.modules.Result

This is a module result. Modules can return standard python objects, but if they need to be formatted when displayed, each result should implement this interface.

category

Return a “categry” of object. Categories will be grouped. If this returns None or is not defined, this result will be “uncategorized”

description

Returns a long-form description. If not defined, the result is assumed to not be a long-form result.

is_long_form() → bool

Check if this is a long form result

title

Return a short-form description/title of the object. If not defined, this defaults to the object converted to a string.

Locating and Using Modules

pwncat.modules.reload(where: Optional[List[str]] = None)

Reload modules from the given directory. If no directory is specified, then the default modules are reloaded. This function will not remove or un-load any existing modules, but may overwrite existing modules with conflicting names.

Parameters:where (List[str]) – Directories which contain pwncat modules
pwncat.modules.find(name: str, base=<class 'pwncat.modules.BaseModule'>, ignore_platform: bool = False)

Locate a module with this exact name. Optionally filter modules based on their class type. By default, this will search for any module implementing BaseModule which is applicable to the current platform.

Parameters:
  • name (str) – Name of the module to locate
  • base (type) – Base class which the module must implement
  • ignore_platform (bool) – Whether to ignore the victim’s platform in the search
Raises:

ModuleNotFoundError – Raised if the module does not exist or the platform/base class do not match.

pwncat.modules.match(pattern: str, base=<class 'pwncat.modules.BaseModule'>)

Locate modules who’s name matches the given glob pattern. This function will only return modules which implement a subclass of the given base class and which are applicable to the current target’s platform.

Parameters:
  • pattern (str) – A Unix glob-like pattern for the module name
  • base (type) – The base class for modules you are looking for (defaults to BaseModule)
Returns:

A generator yielding module objects which at least implement base

Return type:

Generator[base, None, None]

pwncat.modules.run(name: str, **kwargs)

Locate a module by name and execute it. The module can be of any type and is guaranteed to match the current platform. If no module can be found which matches those criteria, an exception is thrown.

Parameters:
  • name (str) – The name of the module to run
  • kwargs (Dict[str, Any]) – Keyword arguments for the module
Returns:

The result from the module’s run method.

Raises:

ModuleNotFoundError – If no module with that name matches the required criteria

Base Module Class

class pwncat.modules.BaseModule

Generic module class. This class allows to easily create new modules. Any new module must inherit from this class. The run method is guaranteed to receive as key-word arguments any arguments specified in the ARGUMENTS dictionary.

ALLOW_KWARGS = False

Allow other kwargs parameters outside of what is specified by the arguments dictionary. This allows arbitrary arguments which are not type-checked to be passed. You should use **kwargs in your run method if this is set to True.

ARGUMENTS = {}

Arguments which can be provided to the run method. This maps argument names to Argument instances describing the type, default value, and requirements for an individual argument.

COLLAPSE_RESULT = False

If you want to use yield Status(…) to update the progress bar but only return one scalar value, setting this to true will collapse an array with only a single object to it’s scalar value.

PLATFORM = 1

The platform this module is compatibile with (can be multiple)

run(progress=None, **kwargs)

The run method is called via keyword-arguments with all the parameters specified in the ARGUMENTS dictionary. If ALLOW_KWARGS was True, then other keyword arguments may also be passed. Any types specified in ARGUMENTS will already have been checked.

If there are any errors while processing a module, the module should raise ModuleError or a subclass in order to enable pwncat to automatically and gracefully handle a failed module execution.

Parameters:progress (rich.progress.Progress) – A python-rich Progress instance