pwncat.platform package

A platform is the pwncat abstraction for an OS or specific distribution. In general, this abstraction allows pwncat to generically interact with targets at the OS level. For example, a platform provides a pathlib.Path implementation which provides seamless file access. A platform also defines ways to query environment variables, get the current user ID and name and generically start processes.

An individual platform must define a set of methods within it’s Platform class for file abstraction, process abstraction, and user abstraction. These methods are then used by the generic Path and Popen classes to abstract interaction with the target.

Normally, you can access a platform through a session. Every session has a platform property which returns a platform-specific implementation of the core methods outlined below.

pathlib-like File Abstraction

Each platform sets the Path property to a class which glues our generic Path class below to either PureWindowsPath or PureLinuxPath. You can construct a session-specific path object by utilizing the session.platform.Path property.

path = session.platform.Path("/etc/passwd")
print(path.read_text())
pwncat.platform.PLATFORM_TYPES = {'linux': <class 'pwncat.platform.linux.Linux'>, 'windows': <class 'pwncat.platform.windows.Windows'>}

A dictionary of platform names mapping to their class objects. This drives the pwncat.platform.create factory function.

class pwncat.platform.Path

Bases: object

A Concrete-Path. An instance of this class is bound to a specific victim, and supports all semantics of a standard pathlib concrete Path.

chmod(mode: int)

Modify file unix permissions

Parameters

mode (int) – unix permission bits

classmethod cwd() pwncat.platform.Path

Return a new concrete path referencing the current directory

exists() bool

Test if the path exists on the target system

expanduser() pwncat.platform.Path

Return a new path object which represents the full path to the file expanding any ~ or ~user components.

glob(pattern: str) Generator[pwncat.platform.Path, None, None]

Glob the given relative pattern in the directory represented by this path, yielding Path objects for any matching files/directories.

group() str

Returns the name of the group owning the file. KeyError is raised if the file’s GID isn’t found in the system database.

classmethod home() pwncat.platform.Path

Return a new concrete path referencing the current user home directory

is_block_device() bool

Returns True if the path points to a block device

is_char_device() bool

Returns True if the path points to a character device

is_dir() bool

Returns True if the path points to a directory (or a symbolic link pointing to a directory). False if it points to another kind of file.

is_fifo() bool

Returns True if the path points to a FIFO

is_file() bool

Returns True if the path points to a regular file

is_mount() bool

Returns True if the path is a mount point.

is_socket() bool

Returns True if the path points to a Unix socket

Returns True if the path points to a symbolic link, False otherwise

iterdir() bool

When the path points to a directory, yield path objects of the directory contents.

lchmod(mode: int)

Modify a symbolic link’s mode (same as chmod for non-symbolic links)

Create a hard link pointing to a path named target

lstat() os.stat_result

Same as stat except operate on the symbolic link file itself rather than the file it points to.

mkdir(mode: int = 511, parents: bool = False, exist_ok: bool = False)

Create a new directory at this given path.

open(mode: str = 'r', buffering: int = - 1, encoding: Optional[str] = None, errors: Optional[str] = None, newline: Optional[str] = None)

Open the file pointed to by the path, like Platform.open

owner() str

Return the name of the user owning the file. KeyError is raised if the file’s uid is not found in the System database

parts = []
read_bytes() bytes

Return the binary contents of the pointed-to file as a bytes object

read_text(encoding: Optional[str] = None, errors: Optional[str] = None) str

Return the decoded contents of the pointed-to file as a string

readable() bool

Test if this file is readable based on the stat results and the sessions’ current user/group ID.

Return the path to which the symbolic link points

rename(target) pwncat.platform.Path

Rename the file or directory to the given target (str or Path).

replace(target) pwncat.platform.Path

Same as rename for Linux

resolve(strict: bool = False)

Resolve the current path into an absolute path

rglob(pattern: str) Generator[pwncat.platform.Path, None, None]

This is like calling Path.glob() with “**/” added to in the front of the given relative pattern

rmdir()

Remove this directory. The directory must be empty.

samefile(otherpath: pwncat.platform.Path)

Return whether this path points to the same file as other_path which can be either a Path object or a string.

stat() os.stat_result

Request file stat details

Make this path a symbolic link to target.

touch(mode: int = 438, exist_ok: bool = True)

Create a file at this path. If the file already exists, function succeeds if exist_ok is true (and it’s modification time is updated). Otherwise FileExistsError is raised.

Remove the file or symbolic link.

writable() bool

Test if this file is writable based on the stat results and the sessions current user/group ID.

write_bytes(data: bytes)

Open the file pointed to in bytes mode and write data to it.

write_text(data: str, encoding: Optional[str] = None, errors: Optional[str] = None)

Open the file pointed to in text mode, and write data to it.

class pwncat.platform.Platform(session: pwncat.manager.Session, channel: pwncat.channel.Channel, log: Optional[str] = None, verbose: bool = False)

Bases: abc.ABC

Abstracts interactions with a target of a specific platform. This includes running commands, changing directories, locating binaries, etc.

During construction, the channel connect method is called to complete any outstanding requirements for connecting the channel. If the channel is not connected after this, a PlatformError is raised.

Platform’s are not created directly, but can be instantiated through the manager create_session method.

Parameters
  • session – a session object to which this platform is bound

  • channel (pwncat.channel.Channel) – an open a channel with the specified platform

  • log (str) – path to a log file which logs each command executed for this platform

Path

A concrete Path object for this platform conforming to pathlib.Path

abstract Popen(args, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, encoding=None, errors=None, text=None, env=None, universal_newlines=None, **other_popen_kwargs) pwncat.subprocess.Popen

Execute a process on the remote host with an interface similar to that of the python standard subprocess.Popen. The returned object behaves much like a standard Popen object and conforms to the interface defined by pwncat.subprocess.Popen. For an explanation of arguments, see pwncat.subprocess.Popen.

abstract abspath(path: str) str

Attempt to resolve a path to an absolute path

abstract chdir(path: Union[str, pwncat.platform.Path])

Change directories to the given path. This method returns the current working directory prior to the change.

Parameters

path (Union[str, Path]) – a relative or absolute path to change to

Returns

current working directory prior to the change

Raises

FileNotFoundError: the specified path doesn’t exist NotADirectoryError: the specified path is not a directory

abstract chmod(path: str, mode: int, link: bool = False)

Update the file permissions

compile(sources: List[Union[str, BinaryIO]], output: Optional[str] = None, suffix: Optional[str] = None, cflags: Optional[List[str]] = None, ldflags: Optional[List[str]] = None) str

Attempt to compile the given C source files into a binary suitable for the remote host. If a compiler exists on the remote host, prefer compilation locally. If no compiler exists on the remote remote host, check the cross global config variable for the path to a local compiler capable of generating binaries for the remote host. If the binary is compiled locally, it is automatically uploaded to the remote host. The path to the new binary on the victim is returned.

Parameters
  • sources (List[Union[str, io.IOBase]]) – list of source file paths or IO streams used as source files

  • output (str) – base name of the output file. If not specified, a name is randomly generated.

  • suffix (str) – a suffix to add to the output name.

  • cflags (List[str]) – a list of flags to pass to the compiler

  • ldflags (List[str]) – a list of flags to pass to the linker

Returns

str

Raises
  • NotImplementedError – this platform does not support c compilation

  • PlatformError – no local or cross compiler detected or compilation failed

abstract exit()

Exit this session

abstract get_host_hash() str

Retrieve a string which uniquely identifies this victim host. On Unix-like platforms, this retrieves the hostname and MAC addresses of any available network interfaces and computes a hash, which should be unique regardless of connection method.

Returns

a unique string (normally a hash) identifying this host

Return type

str

abstract getenv(name: str) str

Get the value of an environment variable.

Parameters

name (str) – the name of the environment variable

Return type

str

abstract getuid() Union[int, str]

Get the current user ID. This should not query the target, but should return the current cached UID as found with refresh_uid.

interactive_loop(interactive_complete: threading.Event)

Handles interactive piping of data between victim and attacker. If the platform you are implementing does not support raw mode, you must override this method to support interactivity. A working example with the python readline module exists in the windows platform. Linux uses this default implementation.

Create a filesystem hard link.

abstract listdir(path=None) Generator[str, None, None]

List the contents of a directory. If path is None, then the contents of the current directory is listed. The list is not guaranteed to be sorted in any way.

Parameters

path (str or Path-like) – the directory to list

Raises

FileNotFoundError – When the requested directory is not a directory, does not exist, or you do not have execute permissions.

abstract lstat(path: str) os.stat_result

Run stat on the symbolic link and return a stat result object. This has the same semantics as the stat method.

property manager

Shortcut to accessing the manager

abstract mkdir(path: str, mode: int = 511, parents: bool = False)

Create a new directory

name = None

Name of this platform (e.g. “linux”)

abstract open(path: Union[str, pwncat.platform.Path], mode: str)

Open a remote file for reading or writing. Normally, only one of read or write modes are allowed for a remote file, but this may change with future platforms. It is recommended to only use one mode when opening remote files. This method attempts to replicate the built-in open function and returns a file-like object. The b mode is honored and if not present, a TextIOWrapper is used to wrap the file object to ensure text data is returned.

Parameters
  • path (Union[str, Path]) – path to the file

  • mode (str) – the open-mode (see built-in open)

Returns

a file-like object

Raises

FileNotFoundError: the specified file does not exist IsADirectoryError: the specified path refers to a directory

process_output(data)

Called during interactivity to handle output from the victim. It can mutate the output and return a changed value if needed. It does nothing by default.

Attempt to read the target of a link

abstract refresh_uid() Union[int, str]

Refresh the cached UID of the current session.

abstract rename(source: str, target: str)

Rename a file from the source to the target. This should replace the target if it exists.

abstract rmdir(target: str)

Remove the specified directory. It must be empty.

run(args, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None, popen_class=None, **other_popen_kwargs) pwncat.subprocess.Popen

Run the given command utilizing the self.popen method and return a pwncat.subprocess.CompletedProcess instance.

set_verbose(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

abstract stat(path: str) os.stat_result

Run stat on a path on the remote system and return a stat result This is mainly used by the concrete Path type to fill in a majority of it’s methods. If the specified path does not exist or cannot be accessed, a FileNotFoundError or PermissionError is raised respectively

Parameters

path (str) – path to a remote file

su(user: str, password: Optional[str] = None)

Attempt to switch users in the running shell. This normally executes a new sub-shell as the requested user. On unix-like systems, this is simply a wrapper for the su command. Implementations may differ on other systems. If a password isn’t provided, the database will be consulted for a matching username and password.

Parameters
  • user (str) – the name of the new user

  • password (str) – the password for the new user

Raises

PermissionError: the provided password was incorrect

sudo(command: Union[str, List[str]], user: Optional[str] = None, group: Optional[str] = None, **popen_kwargs)

Run the specified command as the specified user and group. On unix-like systems the normally translates to the sudo command. The command is executed using the self.popen method. All arguments not documented here are passed directly to self.popen. The process is executed and if a password is required, it is sent from the database. If a password is not available, the process is killed and a PermissionError is raised. If the password is incorrect, a PermissionError is also raised.

Create a symbolic link to source from target

abstract tempfile(mode: str, length: Optional[int] = None, suffix: Optional[str] = None)

Create a temporary file on the remote host and open it with the specified mode. Creating a new temporary file with a mode other than “w” is mostly useless, however mode can be used to specify a binary or text-mode file. The length argument is useful if you know the length of file you are about to read. This alleviates some situations which could be complicated on some platforms by not knowing the intended file length prior to opening. Optionally, a suffix can be added to the random file name. A file-like object is returned. The temporary file is not removed by pwncat itself. Unless explicitly removed, it will continue to exist until the remote operating system cleans up temporary files (possible at the next reboot).

Parameters
  • mode (str) – the open-mode for the new file-like object

  • length (int) – the intended length for the new file

  • suffix (str) – a suffix for the filename

Returns

a file-like object

abstract touch(path: str)

Update a file modification time and possibly create it

abstract umask(mask: Optional[int] = None)

Set or retrieve the current umask value

Remove a link to a file (similar to rm)

which(name: str, **kwargs) str

Locate the specified binary on the remote host. Normally, this is done through the local which command on the remote host (for unix-like hosts), but can be located by any means. The returned path string is guaranteed to exist on the remote host and provide the capabilities of the requested binary.

Parameters

name (Union[list, str]) – name of the binary (e.g. “tar” or “dd”)

Returns

full path to the requested binary

Return type

str

Raises

FileNotFoundError: the requested binary does not exist on this host

abstract whoami()

Retrieve’s only name of the current user (may be faster depending on platform)

exception pwncat.platform.PlatformError

Bases: Exception

Generic platform error.

pwncat.platform.create(platform: str, log: Optional[str] = None, channel: Optional[pwncat.channel.Channel] = None, **kwargs)

Create a new platform object with a registered platform type. If no channel is specified, then this will attempt to utilize the pwncat.channel.create factory function to create a channel. In this case, all keyword arguments are passed to the channel creation function and a platform is created around the channel.

Parameters
  • platform (str) – the name of the platform to construct

  • channel (pwncat.channel.Channel) – the C2 channel to use for communication

Returns

A newly created platform around the specified channel

Return type

Platform

Raises

KeyError: if the specified platform does not exist ChannelError: if a channel could not be created

pwncat.platform.find(name: str) Type[pwncat.platform.Platform]

Retrieve the platform class for the specified name

Parameters

name (str) – name of the platform

Returns

the platform class

Return type

Type[Platform]

Raises

KeyError: if the specified platform does not exist

pwncat.platform.register(platform: Type[pwncat.platform.Platform])

Register a platform class to be automatically constructed with the create factory function with the given name. This can be used to register new custom platforms in plugins.

Parameters
  • name (str) – the name of the new platform

  • platform (Type[Platform]) – the platform class