Persistence Modules

Persistence modules are simply pwncat modules which inherit from the PersistModule base class. The PersistModule base class takes care of the run method. The install and remove methods must be implemented in all persistence modules. Depending on the type, at least one of connect and escalate must be implemented.

Unlike a base module, persistence modules should raise the PersistError class when a module fails.

pwncat/modules/persist/passwd.py is a good example of a persistence module if you’d like to review a working module.

The install, remove, and escalate methods should be generators which yield status updates during operation. Status updates should be of the type pwncat.modules.Status which is a subclass of str. Any other values will be ignored.

Persistence Types

Persistence types are defined by the TYPE module class property. This property is a PersistType flags instance. There are three possible persistence types as documented below. This field describes how an installed module can be used. At least one of the listed types must be specified.

Custom Arguments

Custom arguments can be specified in the same way as a base module: the ARGUMENTS class property. The only difference is that you must include the base persistence arguments in addition to new arguments. Every persistence module takes the following arguments: “user”, “remove”, “escalate” and “connect”.

If custom arguments are used, the persistence module cannot be automatically invoked by privilege escalation. This is not required, but you should be aware during implementation/testing.

In addition to the user argument, all custom arguments are passed to all module methods as keyword arguments with the same name as in the ARGUMENTS class property. The remove, escalate and connect arguments are only received and processed by the the run method.

Simple Example Module

This serves as a baseline persistence module. It doesn’t do anything, but show the structure of a working module.

class Module(PersistModule):
    """ This docstring will be used as the information from the ``info``
    command. """

    # PersistType.LOCAL requires the ``escalate`` method
    # PersistType.REMOTE requires the ``connect`` method
    TYPE = PersistType.LOCAL | PersistType.REMOTE
    # If no custom arguments are needed, this can be ommitted
    # completely.
    ARGUMENTS = {
        **PersistModule.ARGUMENTS,
        "custom_arg": Argument(str),
    }

    def install(self, user, custom_arg):
        """ Install the module on the victim """

        yield Status("Update the progress bar by yielding Status objects")

    def remove(self, user, custom_arg):
        """ Remove any modifications from the remote victim """

        yield Status("You can also update the progress bar here")

    def escalate(self, user, custom_arg):
        """ Locally escalate privileges with this module """

        yield Status("Update the status information")
        return "exit command used to leave this new shell"

    def connect(self, user, custom_arg):
        """ Connect to the victim at pwncat.victim.host.ip """

        # Return a socket-like object connected to the victim shell
        return socket.create_connection(pwncat.victim.host.ip)

Helper Classes

class pwncat.modules.persist.PersistError

Raised when any PersistModule method fails.

class pwncat.modules.persist.PersistType

Identifies the persistence module type flags. One or more flags must be specified for a module.

ALL_USERS = 4

When installed, the persistence module allows access as any user.

LOCAL = 1

The persistence module implements the escalate method for local privilege escalation.

REMOTE = 2

The persistence module implements the connect method for remote connection.

Persistence Module Reference

class pwncat.modules.persist.PersistModule

Base class for all persistence modules.

Persistence modules should inherit from this class, and implement the install, remove, and escalate methods. All modules must take a user argument. If the module is a “system” module, and can only be installed as root, then an error should be raised for any “user” that is not root.

If you need your own arguments to a module, you can define your arguments like this:

ARGUMENTS = {
    **PersistModule.ARGUMENTS,
    "your_arg": Argument(str)
}

All arguments must be picklable. They are stored in the database as a SQLAlchemy PickleType containing a dictionary of name-value pairs.

ARGUMENTS = {'connect': Argument(type=<function Bool>, default=False, help='Connect to a remote host with this module. Only valid from the connect command.'), 'escalate': Argument(type=<function Bool>, default=False, help='Utilize this persistence module to escalate locally'), 'remove': Argument(type=<function Bool>, default=False, help='Remove an installed module with these parameters'), 'user': Argument(type=<class 'str'>, default=<class 'pwncat.modules.NoValue'>, help='The user to install persistence as')}

The default arguments for any persistence module. If other arguments are specified in sub-classes, these must also be included to ensure compatibility across persistence modules.

COLLAPSE_RESULT = True

The run method returns a single scalar value even though it utilizes a generator to provide status updates.

TYPE = 1

Defines where this persistence module is useful (either remote connection or local escalation or both). This also identifies a given persistence module as applying to “all users”

connect(**kwargs) → _socket.socket

Connect to a victim host by utilizing this persistence module. The host address can be found in the pwncat.victim.host object.

Parameters:
  • user (str) – the user to install persistence as. In the case of ALL_USERS persistence, this should be ignored.
  • kwargs – Any custom arguments defined in your ARGUMENTS dictionary.
Return type:

socket.SocketType

Returns:

An open channel to the victim

Raises:

PersistError – All errors must be PersistError or a subclass thereof.

escalate(**kwargs)

Escalate locally from the current user to another user by using this persistence module.

Parameters:
  • user (str) – the user to install persistence as. In the case of ALL_USERS persistence, this should be ignored.
  • kwargs – Any custom arguments defined in your ARGUMENTS dictionary.
Raises:

PersistError – All errors must be PersistError or a subclass thereof.

install(**kwargs)

Install this persistence module on the victim host.

Parameters:
  • user (str) – the user to install persistence as. In the case of ALL_USERS persistence, this should be ignored.
  • kwargs – Any custom arguments defined in your ARGUMENTS dictionary.
Raises:

PersistError – All errors must be PersistError or a subclass thereof.

register(**kwargs)

Register a module as installed, even if it wasn’t installed by the bundled install method. This is mainly used during escalation when a standard persistence method is installed manually through escalation file read/write.

remove(**kwargs)

Remove this persistence module from the victim host.

Parameters:
  • user (str) – the user to install persistence as. In the case of ALL_USERS persistence, this should be ignored.
  • kwargs – Any custom arguments defined in your ARGUMENTS dictionary.
Raises:

PersistError – All errors must be PersistError or a subclass thereof.

run(remove, escalate, connect, **kwargs)

This method should not be overriden by subclasses. It handles all logic for installation, escalation, connection, and removal. The standard interface of this method allows abstract interactions across all persistence modules.