1
0
mirror of https://github.com/calebstewart/pwncat.git synced 2024-11-27 19:04:15 +01:00

Merge branch 'master' of github.com:calebstewart/pwncat

This commit is contained in:
Caleb Stewart 2020-05-10 16:12:24 -04:00
commit a2195d6575
6 changed files with 160 additions and 272 deletions

199
README.md
View File

@ -1,11 +1,12 @@
# pwncat - fancy reverse and bind shell handler
# pwncat
This is a little tool to make interacting with raw reverse and bind shells a
little nicer. `pwncat` can either connect to a remote bind shell or listen for
an incoming reverse shell. After receiving a connection, it will setup some
common configurations when working with remote shells. For example:
pwncat is a raw bind and reverse shell handler. It streamlines common red team
operations and all staging code from your own attacker machine, not the target.
- Unset the `HIST_FILE` macro to disable bash history
After receiving a connection, **pwncat** will setup some
common configurations when working with remote shells.
- Unset the `HISTFILE` environment variable to disable command history
- Normalize shell prompt
- Locate useful binaries (using `which`)
- Attempt to spawn a pseudoterminal (pty) for a full interactive session
@ -19,10 +20,37 @@ interact in a similar fashion to `ssh`.
`TERM` environment variable) with your local settings to ensure the shell
behaves correctly.
## Command and Control Features
## Install
`pwncat` has a few useful features baked in for interacting with a remote shell.
You can access a local command interpreter at any time by getting to a blank
To install **pwncat** into its own python virtual environment:
``` bash
git clone https://github.com/calebstewart/pwncat/ # get pwncat
cd pwncat
sudo apt-get install python3-devel # install dependencies
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python3 setup.py install
. .venv/bin/activate # activate the virtual environment
```
## Usage
```bash
# start a reverse shell listener on port 9999
python -m pwncat -r -p 9999
```
```bash
# access a bind shell on a given host and port
python -m pwncat -b -H 127.0.0.1 -p 9999
```
## Features and Functionality
**pwncat** allows you to local command interpreter at any time by getting to a blank
line and pressing the sequence `~C` (that's ``Shift+` `` then `Shift+c`). This new
prompt provides some basic interaction between your local host and the remote
host.
@ -31,47 +59,130 @@ When at this prompt, you can return to your shell at any time with `C-d` or the
"back" command. To get a list of available commands, you can use `help`. At the
time of writing the following commands are supported:
- `sync`: synchronize rows/columns and TERM environment.
- `set`: set local variables (such as `lhost`).
- `upload`: upload files to the remote host
## Uploading Files
The `upload` command in the local shell allows you to upload files quickly and
easily. `pwncat` can use a variety of methods to transfer the files, and will
use the best one given the executables it was able to find. If none of the
required executables were found, `pwncat` will transfer the file in chunks of
base64, and decode them on the other end. This is slower, but will work in a
pinch.
The usage is simple, but you must set the `lhost` variable first with te `set`
command so that `pwncat` knows how to instruct the remote host to connect to us.
```
localhost$ set lhost "8.8.8.8"
(local) pwncat$ help
back Exit command mode
download Download a file from the remote host
help View help for local commands
privesc Attempt privilege escalation
reset Reset the remote terminal (calls sync, reset, and sets PS1)
set Set or view the currently assigned variables
sync Synchronize the remote PTY with the local terminal settings
upload Upload a file to the remote host
```
Once that is set up, you can upload files but specifying a local file name:
### Transfering Files
```
localhost$ upload /opt/tools/linpeas.sh
Within the local prompt, you have the capability to `upload` and
`download` files to and from the target. **pwncat** will attempt to
determine a `lhost` IP address to refer to your attacker machine, but if you
need to change this, you can modify the variable like so:
```bash
# change local host IP address if you need to
(local) pwncat$ set lhost "8.8.8.8"
```
By default, the file will be written to the current working directory of your
remote shell. You can use the `--output/-o` option to direct the output to a
directory/file of your choosing. You can also select a specific method, if you
would like, however that shouldn't be necessary. The default method is to
automatically select the best available. `pwncat` even gives you a nice progress
bar while it uploads!
The logic to transfer files is defined in `pwncat/uploaders` and
`pwncat/downloaders` respectively. **pwncat** will smartly determine a usable
method to transfer files, but you can choose a specific one with the
`--method` option.
## More to come!
```bash
usage: upload [-h] [--method {nc,curl,shell,bashtcp,wget}] [--output OUTPUT] path
I wrote this in the last few days, and there's bound to be bugs or edge-cases.
Further, I want to build out the local prompt commands more. Obviously, a
download option would be ideal, but since the interaction with the remote
terminal is scriptable, the sky is the limit.
positional arguments:
path path to the file to upload
Another feature that I plan to implement soon is tab completions for the local
prompt (remote tab completions work already thanks to the pty ;). I'll be
working on that ASAP.
optional arguments:
-h, --help show this help message and exit
--method, -m {nc,curl,shell,bashtcp,wget}
set the download method (default: auto)
--output OUTPUT, -o OUTPUT
path to the output file (default: basename of input)
```
```bash
usage: download [-h] [--method {nc,curl,shell,bashtcp,raw}] [--output OUTPUT] path
positional arguments:
path path to the file to download
optional arguments:
-h, --help show this help message and exit
--method, -m {nc,curl,shell,bashtcp,raw}
set the download method (default: auto)
--output OUTPUT, -o OUTPUT
path to the output file (default: basename of input)
```
The methods that **pwncat** can transfer files with are as follows:
```
Both:
nc netcat socket with random port -- requires port to be accessible
curl HTTP request with port 80 -- requires curl on the target
shell send echo and base64 -- no requirements, but can be slow
bashtcp reuse the current socket -- no requirements
Upload specific:
wget HTTP request with port 80 -- requires wget on the target
Download specific:
raw read file contents and save to attacker -- no requirements
```
### Privilege Escalation
**pwncat** can attempt to perform privilege escalation with known techniques.
It will look for binaries on the target system that have known GTFOBins
capabilities, and perform different methods to try and reach new users and
ultimately root.
```bash
usage: privesc [-h] [--list] [--all]
[--user {root,caleb,john,sean,etc}]
[--max-depth MAX_DEPTH] [--read READ] [--write WRITE] [--data DATA] [--text]
optional arguments:
-h, --help show this help message and exit
--list, -l do not perform escalation. list potential escalation methods
--all, -a when listing methods, list for all users. when escalating, escalate to
root.
--user {root,caleb,john,sean,etc}
the target user
--max-depth MAX_DEPTH, -m MAX_DEPTH
Maximum depth for the privesc search (default: no maximum)
--read READ, -r READ remote filename to try and read
--write WRITE, -w WRITE
attempt to write to a remote file as the specified user
--data DATA, -d DATA the data to write a file. ignored if not write mode
--text, -t whether to use safe readers/writers
```
**pwncat** will try and run all known privilege escalation techniques.
The current methods that are supported by `privesc` are:
```
sudo Run available sudo commands with GTFOBins techniques
setuid Run available setuid binaries with GTFOBins techniques
screen Abuse screen-4.5.0 (CVE-2017-5618)
dirtycow Run DirtyCow exploit (CVE-2016-5195)
```
### BusyBox
If the target system does not have many useful "live-off-the-land" binaries,
**pwncat** can upload an appropriate copy of `busybox` in order to access more
commands.
## Planned Features
**pwncat** would like to be come a red team swiss army knife. Hopefully soon,
more features will be added.
* More privilege escalation methods (sudo -u#-1 CVE, LXD containers, etc.)
* More transfer file methods (FTP, SMB, DNS, ICMP, etc. )
* Persistence methods (bind shell, cronjobs, SSH access, PAM abuse, etc.)
* Aggression methods (spam randomness to terminals, flush firewall, etc.)
* Meme methods (terminal-parrot, cowsay, wall, etc.)
* Network methods (port forward, internet access through host, etc.)

View File

@ -14,6 +14,7 @@ from prompt_toolkit.completion import (
from prompt_toolkit.document import Document
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.lexers import PygmentsLexer
from functools import wraps
import subprocess
import requests
import tempfile
@ -45,6 +46,7 @@ class State(enum.Enum):
def with_parser(f):
@wraps(f)
def _decorator(self, argv):
try:
parser = getattr(self, f.__name__.split("do_")[1] + "_parser")

View File

@ -1,63 +0,0 @@
#!/usr/bin/env python3
from typing import Type, List, Tuple
from pwncat.reader.base import Method, ReaderError, Technique
from pwncat.reader.cat import CatMethod
reader_methods = [CatMethod]
class Reader:
""" Locate a privesc chain which ends with the given user. If `depth` is
supplied, stop searching at `depth` techniques. If `depth` is not supplied
or is negative, search until all techniques are exhausted or a chain is
found. If `user` is not provided, depth is forced to `1`, and all methods
to privesc to that user are returned. """
def __init__(self, pty: "pwncat.pty.PtyHandler"):
""" Create a new privesc finder """
self.pty = pty
self.methods: List[Method] = []
for m in reader_methods:
try:
m.check(self.pty)
self.methods.append(m(self.pty))
except ReaderError:
pass
def search(self, filename: str) -> List[Technique]:
""" Search for reader techniques."""
techniques = []
for method in self.methods:
try:
techniques.extend(method.enumerate(filename))
except ReaderError:
pass
return techniques
def read(self, filename: str,) -> str:
""" Read a file using any known techniques """
# Enumerate escalation options for this user
techniques = []
for method in self.methods:
try:
found_techniques = method.enumerate(filename)
for tech in found_techniques:
try:
filecontents = tech.method.execute(tech)
return filecontents
except ReaderError:
return None
techniques.extend(found_techniques)
except ReaderError:
pass
raise ReaderError(f"failed to read {filename}")

View File

@ -1,59 +0,0 @@
#!/usr/bin/env python3
from typing import Generator, Callable, List, Any
from dataclasses import dataclass
from colorama import Fore
import threading
import socket
import os
from pwncat import util
class ReaderError(Exception):
""" An error occurred while attempting a privesc technique """
@dataclass
class Technique:
# The user that this technique will move to
filename: str
# The method that will be used
method: "Method"
# The unique identifier for this method (can be anything, specific to the
# method)
ident: Any
def __str__(self):
return self.method.get_name(self)
class Method:
# Binaries which are needed on the remote host for this file read functionality
name = "unknown"
BINARIES = []
@classmethod
def check(cls, pty: "pwncat.pty.PtyHandler") -> bool:
""" Check if the given PTY connection can support this privesc """
for binary in cls.BINARIES:
if pty.which(binary) is None:
raise ReaderError(f"required remote binary not found: {binary}")
def __init__(self, pty: "pwncat.pty.PtyHandler"):
self.pty = pty
def enumerate(self) -> List[Technique]:
""" Enumerate all possible escalations to the given users """
raise NotImplementedError("no enumerate method implemented")
def execute(self, technique: Technique):
""" Execute the given technique to move laterally to the given user.
Raise a PrivescError if there was a problem. """
raise NotImplementedError("no execute method implemented")
def get_name(self, tech: Technique):
return f"{Fore.GREEN}{tech.filename}{Fore.RESET} via {Fore.RED}{self}{Fore.RED}"
def __str__(self):
return self.name

View File

@ -1,105 +0,0 @@
#!/usr/bin/env python3
from typing import Generator, List
import shlex
import sys
from time import sleep
import os
from colorama import Fore, Style
from pwncat.util import info, success, error, progress, warn
from pwncat.reader.base import Method, ReaderError, Technique
from pwncat import gtfobins
class CatMethod(Method):
name = "cat"
BINARIES = ["cat"]
def __init__(self, pty: "pwncat.pty.PtyHandler"):
super(CatMethod, self).__init__(pty)
# self.suid_paths = None
# def find_suid(self):
# # Spawn a find command to locate the setuid binaries
# delim = self.pty.process("find / -perm -4000 -print 2>/dev/null")
# files = []
# self.suid_paths = {}
# while True:
# path = self.pty.recvuntil(b"\n").strip()
# progress("searching for setuid binaries")
# if delim in path:
# break
# files.append(path.decode("utf-8"))
# for path in files:
# user = (
# self.pty.run(f"stat -c '%U' {shlex.quote(path)}")
# .strip()
# .decode("utf-8")
# )
# if user not in self.suid_paths:
# self.suid_paths[user] = []
# self.suid_paths[user].append(path)
def enumerate(self, filename: str) -> List[Technique]:
""" Find all techniques known at this time """
# if self.suid_paths is None:
# self.find_suid()
binary = self.BINARIES[0]
yield Technique(filename, self, binary)
# for user, paths in self.suid_paths.items():
# for path in paths:
# binary = gtfobins.Binary.find(path)
# if binary is not None:
def execute(self, technique: Technique):
""" Run the specified technique """
filename = technique.filename
binary = technique.ident
# enter, exit = binary.shell("/bin/bash")
info(
f"attempting read {Fore.YELLOW}{Style.BRIGHT}{filename}{Style.RESET_ALL} with {Fore.GREEN}{Style.BRIGHT}{binary}{Style.RESET_ALL}",
)
# before_shell_level = self.pty.run("echo $SHLVL").strip()
# before_shell_level = int(before_shell_level) if before_shell_level != b"" else 0
# Run the start commands
delim = self.pty.process(f"{binary} {filename}", delim=True)
content = self.pty.recvuntil(delim).split(delim)[0]
# print(content)
return content
# sleep(0.1)
# user = self.pty.run("whoami").strip().decode("utf-8")
# if user == technique.user:
# success("privesc succeeded")
# return exit
# else:
# error(f"privesc failed (still {user} looking for {technique.user})")
# after_shell_level = self.pty.run("echo $SHLVL").strip()
# after_shell_level = (
# int(after_shell_level) if after_shell_level != b"" else 0
# )
# if after_shell_level > before_shell_level:
# info("exiting spawned inner shell")
# self.pty.run(exit, wait=False) # here be dragons
# raise PrivescError(f"escalation failed for {technique}")
def get_name(self, tech: Technique):
return f"{Fore.GREEN}{tech.filename}{Fore.RESET} via {Fore.CYAN}{tech.ident}{Fore.RESET} ({Fore.RED}cat{Fore.RESET})"

View File

@ -1,3 +1,5 @@
colorama==0.4.3
prompt-toolkit==3.0.5
wcwidth==0.1.9
wcwidth==0.1.9
netifaces==0.10.9
pygments==2.6.1