From ffa1059a43e939f3ed6179279df8342c4795c096 Mon Sep 17 00:00:00 2001 From: Caleb Stewart Date: Tue, 2 Jun 2020 19:03:05 -0400 Subject: [PATCH] Added documentation for new compile method --- docs/source/api/victim.rst | 62 ++++++++++++++++++++++++++++++++++++ pwncat/commands/busybox.py | 6 ++-- pwncat/commands/connect.py | 2 +- pwncat/commands/enumerate.py | 11 ++----- 4 files changed, 68 insertions(+), 13 deletions(-) diff --git a/docs/source/api/victim.rst b/docs/source/api/victim.rst index 5c99bc9..938ab30 100644 --- a/docs/source/api/victim.rst +++ b/docs/source/api/victim.rst @@ -164,6 +164,68 @@ Starting, stopping or enabling a service is as easy as calling a method or setti 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 + + 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 ----------------- diff --git a/pwncat/commands/busybox.py b/pwncat/commands/busybox.py index 862880d..1aefb83 100644 --- a/pwncat/commands/busybox.py +++ b/pwncat/commands/busybox.py @@ -60,7 +60,9 @@ class Command(CommandDefinition): def run(self, args): - if args.action == "list": + if args.action == "install": + pwncat.victim.bootstrap_busybox(args.url) + elif args.action == "list": if pwncat.victim.host.busybox is None: util.error( "busybox hasn't been installed yet (hint: run 'busybox --install'" @@ -95,5 +97,3 @@ class Command(CommandDefinition): .scalar() ) util.info(f"busybox provides {Fore.GREEN}{nprovides}{Fore.RESET} applets") - elif args.action == "install": - pwncat.victim.bootstrap_busybox(args.url) diff --git a/pwncat/commands/connect.py b/pwncat/commands/connect.py index 4293b9b..5df3736 100644 --- a/pwncat/commands/connect.py +++ b/pwncat/commands/connect.py @@ -159,7 +159,7 @@ class Command(CommandDefinition): if not args.user: self.parser.error("you must specify a user") - if not args.password and not args.identity: + if not (args.password or args.identity): self.parser.error("either a password or identity file is required") try: diff --git a/pwncat/commands/enumerate.py b/pwncat/commands/enumerate.py index a4514bc..ae66f01 100644 --- a/pwncat/commands/enumerate.py +++ b/pwncat/commands/enumerate.py @@ -397,10 +397,7 @@ class Command(CommandDefinition): data: Dict[str, Dict[str, List[pwncat.db.Fact]]] = {} - if isinstance(typ, list): - types = typ - else: - types = [typ] + types = typ if isinstance(typ, list) else [typ] util.progress("enumerating facts") for typ in types: @@ -429,10 +426,6 @@ class Command(CommandDefinition): def flush_facts(self, typ: str, provider: str): """ Flush all facts that match criteria """ - if isinstance(typ, list): - types = typ - else: - types = [typ] - + types = typ if isinstance(typ, list) else [typ] for typ in types: pwncat.victim.enumerate.flush(typ, provider)