mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-27 19:04:15 +01:00
230 lines
10 KiB
ReStructuredText
230 lines
10 KiB
ReStructuredText
GTFOBins Abstraction Layer
|
|
==========================
|
|
|
|
``pwncat`` implements an abstraction of the fantastic GTFOBins_ project. This project catalogs
|
|
known methods of file read, file write and shell access with commonly accessible binaries.
|
|
|
|
The ``pwncat.gtfobins`` module along with the ``data/gtfobins.json`` database provides a
|
|
programmatic way of enumerating and searching for known GTFObins techniques for performing
|
|
various capabilities. It is able to generate payloads for gaining a shell, file read, and
|
|
file write in standard, SUID or sudo modes.
|
|
|
|
For the standard mode, ``gtfobins`` provides ``pwncat`` a way to generically refer to file
|
|
read and write operations without depending on specific remote binaries being available.
|
|
The likelihood of no methods of file read being available on a remote system is very low,
|
|
however the probability of something like ``dd`` to be missing (however odd that would be)
|
|
is much higher. In this way, things like ``pwncat.victim.open`` can operate in a generic
|
|
way without resulting in dependencies on specific remote binaries.
|
|
|
|
Further, the ``gtfobins`` modules has abstracted away the idea of SUID and sudo to provide a
|
|
uniform interface for generating payloads which gain file read/write or shell with known
|
|
SUID or sudo privileges. The ``gtfobins`` module knows how and where to insert special options
|
|
to enable taking advantage of SUID binaries and also knows how to parse sudo command
|
|
specifications to enumerate available binaries and produce payloads compatible with the given
|
|
sudo specification.
|
|
|
|
Module Organization
|
|
-------------------
|
|
|
|
The GTFObins module is at it's core a database lookup. Currently, this database is a JSON
|
|
file which generically describes a large subset of the greater GTFObins project and
|
|
describes how to build payloads for each binaries different capabilities.
|
|
|
|
The top-level module (the ``GTFOBins`` class) provides access to this database. It is
|
|
initialized with a path to the database file (``data/gtfobins.json``) and callable
|
|
which represents the ``which`` application for the target system. It should resolve
|
|
binary names into their fullpaths on the remote system. It also takes a second boolean
|
|
parameter which indicates where the returned string should be quoted as with ``shlex.quote``.
|
|
|
|
Payloads are generated from individual methods, which are all an implementation of the
|
|
``pwncat.gtfobins.Method`` class. A method is an implementation of a specific capability
|
|
for a specific binary. They contain the payload, command arguments, input and exit command
|
|
needed to execute a specific capability with a specific binary. These methods are defined
|
|
in the database, which will be described further down.
|
|
|
|
A ``pwncat.gtfobins.Binary`` object is instantiated for every binary described in the
|
|
database. Each binary is described simply by it's name and a list of methods taken
|
|
from the database. At a generic level, the binary doesn't know the path on the remote
|
|
system, which it will need to build a payload with any given method.
|
|
|
|
When enumerating methods, the ``Binary`` and ``GTFOBins`` objects will both return
|
|
instances of the ``MethodWrapper`` class. This class provides the actual payload
|
|
building mechanism. It is the glue that puts a specific binary path, SUID state and
|
|
sudo specification together with a specific ``Method`` object. You will not interact
|
|
with ``Method`` objects directly when using this module.
|
|
|
|
Retrieving a Method Wrapper
|
|
---------------------------
|
|
|
|
Method wrappers are created in three two ways. They can be built automatically by the
|
|
``GTFOBins`` object by iterating through all known binaries and using the provided
|
|
``which`` callable to locate valid remote binaries. This is done through the
|
|
``iter_methods`` function:
|
|
|
|
.. code-block:: python
|
|
|
|
for method in pwncat.victim.gtfo.iter_methods(Capability.READ, Stream.ANY):
|
|
print("We could read a file with {method.binary_path}!")
|
|
|
|
This works well when you don't need any special permissions, but just need to generate
|
|
a payload for a specific capability. You have no requirements beyond your capability.
|
|
|
|
However, sometimes you know a specific binary that you can use, but you're not sure
|
|
what you can do with it. This can happen when performing privilege escalation. Perhaps
|
|
you can run a specific binary as another user, but you'd like to leverage this for
|
|
more access. In this case, you can provide the binary path to the ``iter_binary``
|
|
method to iterate methods for that specific binary. In this case, the ``GTFOBins``
|
|
module will not utilize the ``which`` callable. It trusts you that the given binary
|
|
path you provided exists, and yields method wrappers for the capabilities you requested,
|
|
if any.
|
|
|
|
.. code-block:: python
|
|
|
|
for method in pwncat.victim.gtfo.iter_binary("/bin/bash", Capability.ALL, Stream.ANY):
|
|
print(f"We could perform {method.cap} with /bin/bash!")
|
|
|
|
The last way of generating a method wrapper is used when you know that a user can
|
|
run commands via sudo with a specific specification. You'd like to know if GTFObins can
|
|
provide any useful capabilities with this command. For this, you can use the
|
|
``iter_sudo`` method which will iterator over all methods which are capable of being
|
|
executed under the given sudo specification.
|
|
|
|
.. code-block:: python
|
|
|
|
for method in pwncat.victim.gtfo.iter_sudo("/usr/bin/git log*", caps=Capability.ALL):
|
|
print(f"You could perform {method.cap} with /usr/bin/git!")
|
|
|
|
``GTFOBins`` is able to parse the sudo command specification and identify if the allowed
|
|
parameters to the command overlap with the needed parameters for different methods. If
|
|
the specification is ``ALL`` or ends with an asterisk, this is often possible. If it doesn't,
|
|
then it will try to make the parameters fit the specification and decide if the
|
|
capabilitiy is feasible.
|
|
|
|
Generating a Payload by Capability
|
|
----------------------------------
|
|
|
|
Once you have identified a specific method (and have a method wrapper), generating a payload is
|
|
easy. The ``MethodWrapper`` class provides the ``build`` function which will be all components
|
|
of the payload. Each payload consists of three items:
|
|
|
|
* The base payload
|
|
* The input sent to the application
|
|
* The command used to exit the application
|
|
|
|
The base payload is the command sent to the target host which will trigger the action specified
|
|
by the method capability. The input is the a bytes object which is sent to the standard input
|
|
of the application to trigger the action. The command used to exit is a bytes object which when
|
|
sent to the applications standard input should cleanly exit the application and return the user
|
|
to a normal shell. The last two are optional, but may be required and should always be sent
|
|
if returned from ``build``. If a method doesn't need them, they will be empty bytes objects
|
|
and you can safely send them to the application anyway.
|
|
|
|
The ``build`` function takes variable arguments because the specific parameters required
|
|
for each capability are different:
|
|
|
|
* A SHELL capability requires the following arguments:
|
|
- shell: the shell to execute
|
|
* A READ capability requires the following arguments:
|
|
- lfile: the path to the local file to read
|
|
* A WRITE capability with a RAW stream requires the following arguments:
|
|
- lfile: the path to the local file to write to
|
|
- length: the number of bytes of data which will be written
|
|
* A WRITE capability with any other stream type requires:
|
|
- lfile: the path to the local file to write to
|
|
|
|
In the case of a read payload, the content of the file is assumed to be sent to standard output
|
|
of the command executed via the base payload. For write payloads, the new content for the file
|
|
is sent to the standard input of the base payload command **after** any input data returned from
|
|
the ``build`` function and **before** sending the exit bytes.
|
|
|
|
Putting It All Together
|
|
-----------------------
|
|
|
|
There's a lot of information up above, so here's an example of using the GTFOBins module. For
|
|
file read and file write. First up, we will read the ``/etc/passwd`` file and print the name
|
|
of all users on the remote system:
|
|
|
|
.. code-block:: python
|
|
|
|
from pwncat import victim
|
|
|
|
try:
|
|
# Find a reader from GTFObins
|
|
method = next(victim.gtfo.iter_methods(caps=Capability.READ, stream=Stream.ANY))
|
|
except StopIteration:
|
|
raise RuntimeError("no available gtfobins readers!")
|
|
|
|
# Build the payload
|
|
payload, input_data, exit_cmd = method.build(lfile="/etc/passwd")
|
|
|
|
# Run the payload on the remote host.
|
|
pipe = self.subprocess(
|
|
payload,
|
|
"r",
|
|
data=input_data.encode("utf-8"),
|
|
exit_cmd=exit_cmd.encode("utf-8"),
|
|
name=path,
|
|
)
|
|
|
|
# Wrap the pipe in the decoder for this method (possible base64)
|
|
with method.wrap_stream(pipe) as pipe:
|
|
for line in pipe:
|
|
line = line.decode("utf-8").strip()
|
|
print("Found user:", line.split(":")[0])
|
|
|
|
This might seem long and laberous, but it is infinitely better than depending on a specific
|
|
file read method or attempting to account for multiple read methods each time you want to read
|
|
a file (although, luckily ``pwncat.victim.open`` already wraps this for you ;). Next, we'll
|
|
take a look at writing a file.
|
|
|
|
.. code-block:: python
|
|
|
|
from pwncat import victim
|
|
|
|
# The data we will write
|
|
data = b"Hello from a new file!"
|
|
|
|
try:
|
|
# Find a writer from GTFObins
|
|
method = next(victim.gtfo.iter_methods(caps=Capability.WRITE, stream=Stream.RAW))
|
|
except StopIteration:
|
|
raise RuntimeError("no available gtfobins readers!")
|
|
|
|
# Build the payload
|
|
payload, input_data, exit_cmd = method.build(lfile="/tmp/new-file", length=len(data))
|
|
|
|
# Run the payload on the remote host.
|
|
pipe = self.subprocess(
|
|
payload,
|
|
"w",
|
|
data=input_data.encode("utf-8"),
|
|
exit_cmd=exit_cmd.encode("utf-8"),
|
|
name=path,
|
|
)
|
|
|
|
with method.wrap_stream(pipe) as pipe:
|
|
pipe.write(data)
|
|
|
|
|
|
GTFOBins Utility Classes
|
|
------------------------
|
|
|
|
.. autoclass:: pwncat.gtfobins.Capability
|
|
:members:
|
|
|
|
.. autoclass:: pwncat.gtfobins.Stream
|
|
:members:
|
|
|
|
The GTFOBins Object
|
|
-------------------
|
|
|
|
.. autoclass:: pwncat.gtfobins.GTFOBins
|
|
:members:
|
|
|
|
The MethodWrapper Object
|
|
------------------------
|
|
|
|
.. autoclass:: pwncat.gtfobins.MethodWrapper
|
|
:members:
|
|
|
|
.. _GTFOBins: https://gtfobins.github.io |