mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-24 01:25:37 +01:00
Merge branch 'master' of github.com:calebstewart/pwncat
This commit is contained in:
commit
a2195d6575
199
README.md
199
README.md
@ -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
|
pwncat is a raw bind and reverse shell handler. It streamlines common red team
|
||||||
little nicer. `pwncat` can either connect to a remote bind shell or listen for
|
operations and all staging code from your own attacker machine, not the target.
|
||||||
an incoming reverse shell. After receiving a connection, it will setup some
|
|
||||||
common configurations when working with remote shells. For example:
|
|
||||||
|
|
||||||
- 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
|
- Normalize shell prompt
|
||||||
- Locate useful binaries (using `which`)
|
- Locate useful binaries (using `which`)
|
||||||
- Attempt to spawn a pseudoterminal (pty) for a full interactive session
|
- 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
|
`TERM` environment variable) with your local settings to ensure the shell
|
||||||
behaves correctly.
|
behaves correctly.
|
||||||
|
|
||||||
## Command and Control Features
|
## Install
|
||||||
|
|
||||||
`pwncat` has a few useful features baked in for interacting with a remote shell.
|
To install **pwncat** into its own python virtual environment:
|
||||||
You can access a local command interpreter at any time by getting to a blank
|
|
||||||
|
``` 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
|
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
|
prompt provides some basic interaction between your local host and the remote
|
||||||
host.
|
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
|
"back" command. To get a list of available commands, you can use `help`. At the
|
||||||
time of writing the following commands are supported:
|
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
|
||||||
|
|
||||||
```
|
Within the local prompt, you have the capability to `upload` and
|
||||||
localhost$ upload /opt/tools/linpeas.sh
|
`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
|
The logic to transfer files is defined in `pwncat/uploaders` and
|
||||||
remote shell. You can use the `--output/-o` option to direct the output to a
|
`pwncat/downloaders` respectively. **pwncat** will smartly determine a usable
|
||||||
directory/file of your choosing. You can also select a specific method, if you
|
method to transfer files, but you can choose a specific one with the
|
||||||
would like, however that shouldn't be necessary. The default method is to
|
`--method` option.
|
||||||
automatically select the best available. `pwncat` even gives you a nice progress
|
|
||||||
bar while it uploads!
|
|
||||||
|
|
||||||
## 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.
|
positional arguments:
|
||||||
Further, I want to build out the local prompt commands more. Obviously, a
|
path path to the file to upload
|
||||||
download option would be ideal, but since the interaction with the remote
|
|
||||||
terminal is scriptable, the sky is the limit.
|
|
||||||
|
|
||||||
Another feature that I plan to implement soon is tab completions for the local
|
optional arguments:
|
||||||
prompt (remote tab completions work already thanks to the pty ;). I'll be
|
-h, --help show this help message and exit
|
||||||
working on that ASAP.
|
--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.)
|
@ -14,6 +14,7 @@ from prompt_toolkit.completion import (
|
|||||||
from prompt_toolkit.document import Document
|
from prompt_toolkit.document import Document
|
||||||
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
||||||
from prompt_toolkit.lexers import PygmentsLexer
|
from prompt_toolkit.lexers import PygmentsLexer
|
||||||
|
from functools import wraps
|
||||||
import subprocess
|
import subprocess
|
||||||
import requests
|
import requests
|
||||||
import tempfile
|
import tempfile
|
||||||
@ -45,6 +46,7 @@ class State(enum.Enum):
|
|||||||
|
|
||||||
|
|
||||||
def with_parser(f):
|
def with_parser(f):
|
||||||
|
@wraps(f)
|
||||||
def _decorator(self, argv):
|
def _decorator(self, argv):
|
||||||
try:
|
try:
|
||||||
parser = getattr(self, f.__name__.split("do_")[1] + "_parser")
|
parser = getattr(self, f.__name__.split("do_")[1] + "_parser")
|
||||||
|
@ -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}")
|
|
@ -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
|
|
@ -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})"
|
|
@ -1,3 +1,5 @@
|
|||||||
colorama==0.4.3
|
colorama==0.4.3
|
||||||
prompt-toolkit==3.0.5
|
prompt-toolkit==3.0.5
|
||||||
wcwidth==0.1.9
|
wcwidth==0.1.9
|
||||||
|
netifaces==0.10.9
|
||||||
|
pygments==2.6.1
|
||||||
|
Loading…
Reference in New Issue
Block a user