1
0
mirror of https://github.com/calebstewart/pwncat.git synced 2024-11-27 19:04:15 +01:00

Added persistence documentation

This commit is contained in:
Caleb Stewart 2020-05-21 00:04:59 -04:00
parent fce965c0c8
commit a1e819d06d
3 changed files with 156 additions and 32 deletions

View File

@ -12,4 +12,5 @@ prompt commands or more complicated privilege escalation or persistence methods.
commandparser.rst commandparser.rst
privesc.rst privesc.rst
persist.rst
victim.rst victim.rst

View File

@ -0,0 +1,78 @@
Persistence Methods
===================
Persistence methods are implemented through an abstract ``PersistenceMethod`` base class which defines methods
for installing and removing various persistence methods. The persistence module and associated base classes
are defined in the ``pwncat/persist/__init__.py`` script.
Persistence methods are loaded using the ``pkgutil`` python module automatically from the ``pwncat/persist``
subdirectory. Any module implementing a ``Method`` class which inherits from the ``PersistenceMethod``
base class will be imported as an available persistence method.
Implementing Persistence Methods
--------------------------------
A persistence method is implemented by creating a new script under the ``pwncat/persist`` directory
which implements a ``Method`` class. This class must inherit from the ``PersistenceMethod`` base
class and implement the ``install`` and ``remove`` functions.
A privilege escalation method also defines a few properties as class variables which govern how
the method is utilized by ``pwncat``. The ``system`` variable is a boolean defining whether this
persistence method allows access only as ``root`` or is installed on a per-user basis. If this
item is true, all ``user`` options in further methods are ignored.
The ``name`` variable is used to create user-readable formatted strings representing this persistence
method. Lastly, the ``local`` variable is a boolean defining whether the persistence method allows
local escalation to the specified user.
Three methods can be overridden within the ``PersistenceMethod`` base class. The first is the ``install``
method. This method takes a ``user`` parameter (if not a system method), and should install persistence
as the specified user. If there is a problem or error during installation, ``PersistenceError`` should
be raised with a description of the error.
Next, the ``remove`` method must be implemented to undo the actions of the ``install`` method. It takes
the same ``user`` argument, and upon error should also raise ``PersistenceError``.
Lastly, the ``escalate`` method is only required if ``local`` is true. It should leverage this
persistence method to gain access shell access as the specified user (again, user should be ignored
for system methods). This is used as a shortcut in the implementation of the ``privesc`` command
to utilize local persistence methods to escalate to different users.
Locating and Installing Persistence Methods
-------------------------------------------
If you would like to programmatically install, remove or locate privilege escalation methods,
you can use the ``pwncat.victim.persist`` module. This module provides a generic interface
to enumerate available methods, list installed methods, and locate methods by name.
The ``install`` method takes a method name and an optional user. This will locate the identified
method and call it's ``install`` routine. If installation is successful, it will register the
method in the database as installed and also register a corresponding tamper object to track
the installation. If the method does not exist or failed to install, a ``PersistenceError``
exception is raised.
The ``register`` method takes the same parameters as the ``install`` method. It will register the
specified method as being installed but not perform the installation routine. This is useful
when a module installs a persistence method in a non-standard way and needs to register this
installation with the framework. For example, the ``privesc`` module may install SSH authorized
keys via a privesc file writer. If this happens, it will register this persistence with the ``persist``
module for tracking.
The ``remove`` is the inverse of the ``install`` method, and will completely remove the given
persistence method.
To find a persistence method by name, you can use the ``find`` method which returns an iterator
of known persistence methods. The ``persist`` module is also an iterator which will yield all
known persistence methods.
The Persistence Module Class
----------------------------
.. autoclass:: pwncat.persist.Persistence
:members:
The Base Persistence Method Class
---------------------------------
.. autoclass:: pwncat.persist.PersistenceMethod
:members:

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import functools import functools
import pkgutil import pkgutil
from typing import Optional, Dict, Iterator from typing import Optional, Dict, Iterator, Tuple
from colorama import Fore from colorama import Fore
import pwncat import pwncat
@ -20,6 +20,13 @@ def persistence_tamper_removal(name: str, user: Optional[str] = None):
class Persistence: class Persistence:
"""
This class abstracts the management of persistence methods and is accessible at runtime
via ``pwncat.victim.persist``. It provides methods of enumerating available persistence
methods, enumerating installed persistence methods, installing methods, and removing
methods.
"""
def __init__(self): def __init__(self):
self.methods: Dict[str, "PersistenceMethod"] = {} self.methods: Dict[str, "PersistenceMethod"] = {}
@ -29,8 +36,17 @@ class Persistence:
self.methods[method.name] = method self.methods[method.name] = method
def install(self, name: str, user: Optional[str] = None): def install(self, name: str, user: Optional[str] = None):
""" Add persistence as the specified user. If the specified persistence """
method is system method, the "user" argument is ignored. """ Install the specified method by name. If the specified method is not a system
method, ``user`` specifies the user to install this method as. Otherwise, the
``user`` parameter is ignored.
This method raises a PersistenceError if installation failed or the given method
does not exist.
:param name: the name of the persistence method to install
:param user: the user to install persistence as
"""
try: try:
method = next(self.find(name)) method = next(self.find(name))
except StopIteration: except StopIteration:
@ -45,9 +61,17 @@ class Persistence:
self.register(name, user) self.register(name, user)
def register(self, name: str, user: Optional[str] = None): def register(self, name: str, user: Optional[str] = None):
""" Register a persistence method as pre-installed. This is useful for some privilege escalation """
Register a persistence method as pre-installed. This is useful for some privilege escalation
which automatically adds things equivalent to persistent, but without the which automatically adds things equivalent to persistent, but without the
persistence module itself (e.g. backdooring /etc/passwd or SSH keys). """ persistence module itself (e.g. backdooring /etc/passwd or SSH keys).
This method raises a PersistenceError if the given persistence method
does not exist.
:param name: the method to register as pre-installed
:param user: the user the method was installed as
"""
method = next(self.find(name)) method = next(self.find(name))
@ -61,47 +85,54 @@ class Persistence:
) )
@property @property
def installed(self) -> Iterator["PersistenceMethod"]: def installed(self) -> Iterator[Tuple[str, "PersistenceMethod"]]:
""" Retrieve a list of installed persistence methods """ """
Enumerate all installed persistence methods.
:return: An iterator of tuples of (username,PeristenceMethod)
"""
for persist in pwncat.victim.host.persistence: for persist in pwncat.victim.host.persistence:
yield persist.user, self.methods[persist.method] yield persist.user, self.methods[persist.method]
@property @property
def available(self) -> Iterator[str]: def available(self) -> Iterator["PersistenceMethod"]:
""" Yield all the known methods """ """
Enumerate all available persistence methods
:return: Iterator of available persistence methods
"""
yield from self.methods.values() yield from self.methods.values()
def find( def find(self, name: Optional[str] = None,) -> Iterator["PersistenceMethod"]:
self, """
name: Optional[str] = None, Locate persistence methods matching the given name.
user: Optional[str] = None,
installed: bool = False, :param name: the name of the persistence module to locate
local: Optional[bool] = None, :return: Iterator of persistence methods matching the name
system: Optional[bool] = None, """
) -> Iterator["PersistenceMethod"]:
for method in self.methods.values(): for method in self.methods.values():
if name is not None and method.name != name: if name is not None and method.name != name:
# not the requested method # not the requested method
continue continue
if installed:
if user is not None or system is None or method.system == system:
if not method.installed(user):
continue
else:
# the user was not specified and this module is not a
# system module. We can't check install state, so we
# err on the side of caution here.
continue
if local is not None and method.local != local:
continue
# All checks passed. Yield the method. # All checks passed. Yield the method.
yield method yield method
def remove(self, name: str, user: Optional[str] = None, from_tamper: bool = False): def remove(self, name: str, user: Optional[str] = None, from_tamper: bool = False):
""" Remove the specified persistence method from the remote victim """
Remove the specified persistence method from the remote victim
if the given persistence method is a system method, the "user" if the given persistence method is a system method, the "user"
argument is ignored. """ argument is ignored.
Raises a ``PersistenceError`` if the given method doesn't exist or removal
failed.
The ``from_tamper`` parameter should not be used and is only used
for internal removal from within the tamper subsystem.
:param name: the name of the method to remove
:param user: the user which was used to install this method
:param from_tamper: whether we are removing from the tamper removal system
"""
try: try:
method = next(self.find(name)) method = next(self.find(name))
except StopIteration: except StopIteration:
@ -138,7 +169,9 @@ class Persistence:
class PersistenceMethod: class PersistenceMethod:
""" Base persistence method class """ """ Base persistence method class. The docstring for your method class will
become the long-form help for this method (viewable with ``persist -l -m {method-name}``)
"""
def __init__(self): def __init__(self):
pass pass
@ -162,9 +195,21 @@ class PersistenceMethod:
raise NotImplementedError raise NotImplementedError
def install(self, user: Optional[str] = None): def install(self, user: Optional[str] = None):
"""
Install this method of persistence as the given user. Raise a
``PersistenceError`` if installation fails.
:param user: the user to install persistence as
"""
raise NotImplementedError raise NotImplementedError
def remove(self, user: Optional[str] = None): def remove(self, user: Optional[str] = None):
"""
Remove this method of persistence as the given user. Raise a
``PersistenceError`` if removal fails.
:param user: the user to remove persistence as
"""
raise NotImplementedError raise NotImplementedError
def installed(self, user: Optional[str] = None) -> bool: def installed(self, user: Optional[str] = None) -> bool: