1
0
mirror of https://github.com/calebstewart/pwncat.git synced 2024-11-30 20:34:15 +01:00
pwncat/docs/source/api/victim.rst
2020-06-02 19:03:05 -04:00

240 lines
10 KiB
ReStructuredText

Interacting With The Victim
===========================
pwncat abstracts all interactions with the remote host through the ``pwncat.victim`` object. This is
a singleton of the ``pwncat.remote.Victim`` class, and is available anywhere after initialization and
conneciton of the remote host.
This object wraps common operations on the remote host such as running processes, retrieving output,
opening files, interacting with services and much more!
Working with remote processes
-----------------------------
Remote processes are started with one of four different methods. However, most of the time you will
only need to use two of them. The first is the ``process`` method:
.. code-block:: python
start_delim, end_delim = pwncat.victim.process("ls", delim=False)
The ``process`` method does not attempt to handle the output of a process or verify it's success.
The ``delim`` parameter specifies whether delimeters will be placed before and after the remote
processes output. If ``delim`` is false, this is equivalent to sending the command over the socket
directly with ``pwncat.victim.client.send("ls\n".encode("utf-8"))``. However, setting ``delim`` to
True (the default value) instructs the method to prepend and append delimeters. ``process`` will
also wait for the starting delimeter to be sent before returning. This means that with ``delim``
on, reading data from ``pwncat.victim.client`` after calling ``process`` will be the output of the process
up until the end delimeter.
The next process creation method is ``run``. This method utilizes ``process``, but automatically waits
for process completion and returns the output of the process:
.. code-block:: python
output: bytes = pwncat.victim.run("ls")
The optional ``wait`` parameter effects whether the method will wait for and return the result. If
``wait`` is false, the output will not be read from the socket and the method will return immediately.
This behaves much like calling ``process`` with ``delim=True``.
The third process creation method is ``subprocess``. With the subprocess method, a file-like object
is returned which allows for read/write access to the remote processes stdio. Writing to the remote
process will work until the end delimeter is observed on a read. Afterwich, the file object is
automatically closed. No other interaction with the remote host is possible until the file object
is closed.
.. code-block:: python
with pwncat.victim.subprocess("find / -name 'interesting'", "r") as pipe:
for line in pipe:
print("Interesting file:", line.strip().decode("utf-8"))
The last process creation method is the ``env`` method. This method acts much like the ``env`` command
on linux. It takes an argument array for a process to start. The first argument is the name of the
program to run, and it is check with the ``pwncat.victim.which`` method to ensure it exists. Keyword
arguments to the method are converted into environment variables for the new process. A ``FileNotFoundError``
is raised if the requested binary is not resolved properly with ``pwncat.victim.which``.
.. code-block:: python
pwncat.victim.env(["mkdir", "-p", "/tmp/somedir"], ENVIRONMENT_VAR="variable value")
This method also takes parameters similar to ``run`` for waiting and input, if needed.
Working with remote files
-------------------------
Remote files can be accessed and created similar to local files. The ``pwncat.victim.open`` interface
provides a method to open a remote file and interact with it like a local Python file object. Creating
a remote file can be accomplished with:
.. code-block:: python
with pwncat.victim.open("/tmp/remote-file", "w") as filp:
filp.write("hell from the other side!")
When interacting with remote files, no other interaction with the remote host is allowed. Prior to
executing any other remote interaction, you must close the remote file object. Because of this simple
interface, uploading a local file to a remote file can be accomplished with Python built-in functions.
.. code-block:: python
import os
import shutil
with open("local-file", "rb") as src:
with pwncat.victim.open("/tmp/remote-file", "wb",
length=os.path.getsize("local-file")) as dst:
shutil.copyfileobj(src, dst)
This is actually how the ``upload`` and ``download`` commands are implemented. The ``length`` parameter
to the ``pwncat.victim.open`` method allows ``pwncat`` to intelligently select remote file access options
which required a length argument. This is important because transfer of raw binary data unencoded requires
the output length to be known. If the length is not passed, the data will be automatically encoded (for
example, with base64) before uploading, and decoded automatically on the receiving end.
Working with remote services
----------------------------
``pwncat`` will attempt to figure out what type of init system is being used on the target host and provide
an abstracted interface to system services. The abstractions are available under the ``pwncat/remote/service.py`` file.
Currently, ``pwncat`` only supports SystemD, but the interface is abstracted to support other init systems
such as SysVInit or Upstart if the interface is implemented.
The ``pwncat.remote.service.service_map`` maps names of init systems to their abstract ``RemoteService``
class implementation. This is how ``pwncat`` selects the appropriate remote service backend.
Regardless of the underlying init system, ``pwncat`` provides methods for querying known services, enabling
auto-start, starting, stopping and creation of remote services.
To query a list of remote services, you can use the ``pwncat.victim.services`` property. This is an iterator
yielding each abstracted service object. Each object contains a name, description, and state as well as
methods for starting, stopping, enabling or disabling the service. This functionality obviously depends
on you having the correct permission to manage the services, however retrieving the state and list of
services should work regardless of your permission level.
.. code-block:: python
from pwncat import victim
for service in victim.services:
print(f"{service.name} is {'running' if service.running else 'stopped'}")
To find a specific service by name, there is a ``find_service`` method which returns an individual
remote service object. If the service is not found, a ``ValueError`` is raised.
.. code-block:: python
from pwncat import victim
sshd = victim.find_service("sshd")
The interface for creating services is provided through the ``create_service`` method, which allows
you to specify a target binary name which serves as the entrypoint for your service as well as a name
description, and enabled state. A ``PermissionError`` is raised if you do not have permission to create
the specified service. This method also returns a wrapped ``RemoteService`` object for the newly
created service.
.. code-block:: python
from pwncat import victim
pwncat = victim.create_service(name="pwncat",
description="a malicious service",
target="/usr/bin/pwncat_service",
runas="root",
enable=True,
user=False)
pwncat.start()
Starting, stopping or enabling a service is as easy as calling a method or setting a property:
.. code-block:: python
from pwncat import victim
try:
sshd = victim.find_service("sshd")
sshd.enabled = False
sshd.stop()
except PermissionError:
print("you don't have permission to modify sshd :(")
except ValueError:
print("sshd doesn't exist!")
Compiling Code for the Victim
-----------------------------
``pwncat`` provides an abstract capability to compile binaries in ``C`` for the victim. By setting
the ``cross`` configuration item to the path to valid C compiler on your attacking system capable
of generating compiled binaries for the victim, you can have ``pwncat`` compile exploits locally
and upload only the compiled binaries. This not only speeds up privilege escalation checks, but also
enables some methods in the case the remote host does not have a working C compiler. If no ``cross``
value is provided, ``pwncat`` will still check for an utilize a remote compiler if available.
To access, this functionality, you can use the ``pwncat.victim.compile`` method. This method takes
a list of source files, an output suffix, and a list of CFLAGS and LDFLAGS. The result is the path
to the compiled binary on the remote host. Ideally, it will utilize a local cross-compiler and upload
the binary, but it is also capable of uploading the specified source files and compiling remotely
as well.
.. code-block:: python
:caption: Compiling Local Source Files For A Victim
import pwncat
# Compile your code for the remote host
remote_path = pwncat.victim.compile(["main.c", "other.c"], cflags=["-static"], ldflags=["-lssl"])
# Run the new binary
pwncat.victim.run(remote_path)
# Track the new binary in the tamper database
pwncat.victim.tamper.created_file(remote_path)
The list of sources can also accept file objects instead of file paths. In this case, you can wrap
a literal string in a `io.StringIO` object in order to compile short source files from memory:
.. code-block:: python
:caption: Compiling Source From Memory
import pwncat
import textwrap
import io
# Simple in-memory source file
source = textwrap.dedent(r"""
#include <stdio.h>
int main(int argc, char** argv) {
printf("Hello World!\n");
return 0;
}
""")
# Compile the source file
remote_path = pwncat.victim.compile([io.StringIO(source)])
# will print b"Hello World!\n"
print(pwncat.victim.run(remote_path))
You can also utilize the CFLAGS argument to produce shared libraries if needed:
.. code-block:: python
:caption: Compiling Shared Libraries
import pwncat
# Compile the source as a shared library
remote_path = pwncat.victim.compile(["main.c"], cflags=["-fPIC", "-shared"], suffix=".so")
The Victim Object
-----------------
.. autoclass:: pwncat.remote.victim.Victim
:members:
Remote Service Object
---------------------
.. autoclass:: pwncat.remote.service.RemoteService
:members: