mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-27 19:04:15 +01:00
240 lines
10 KiB
ReStructuredText
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:
|