1
0
mirror of https://github.com/calebstewart/pwncat.git synced 2024-11-24 01:25:37 +01:00

Merge pull request #139 from calebstewart/issue-118-ssl-bind

- Added `ssl-bind` and `ssl-connect` channel protocols for encrypted shells
- Added `ncat`-style arguments for the entrypoint and `connect` command (e.g. `--ssl` and `--ssl-cert`/`--ssl-key`)
- Added query-string arguments to connection strings for both the entrypoint
  and the `connect` command.
This commit is contained in:
Caleb Stewart 2021-06-17 00:01:26 -04:00 committed by GitHub
commit d8a566a51d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 412 additions and 112 deletions

View File

@ -14,6 +14,11 @@ and simply didn't have the time to go back and retroactively create one.
### Changed
- Changed session tracking so session IDs aren't reused
- Changed zsh prompt to match CWD of other shell prompts
### Added
- Added `ssl-bind` and `ssl-connect` channel protocols for encrypted shells
- Added `ncat`-style ssl arguments to entrypoint and `connect` command
- Added query-string arguments to connection strings for both the entrypoint
and the `connect` command.
## [0.4.2] - 2021-06-15
Quick patch release due to corrected bug in `ChannelFile` which caused command

View File

@ -23,34 +23,34 @@ After installation, you can use pwncat via the installed script:
.. code-block:: bash
$ pwncat --help
usage: pwncat [-h] [--config CONFIG] [--identity IDENTITY] [--listen]
[--platform PLATFORM] [--port PORT] [--list]
usage: pwncat [-h] [--version] [--download-plugins] [--config CONFIG] [--ssl] [--ssl-cert SSL_CERT]
[--ssl-key SSL_KEY] [--identity IDENTITY] [--listen] [--platform PLATFORM] [--port PORT] [--list]
[[protocol://][user[:password]@][host][:port]] [port]
Start interactive pwncat session and optionally connect to existing victim
via a known platform and channel type. This entrypoint can also be used to
list known implants on previous targets.
Start interactive pwncat session and optionally connect to existing victim via a known platform and channel type. This
entrypoint can also be used to list known implants on previous targets.
positional arguments:
[protocol://][user[:password]@][host][:port]
Connection string describing victim
port Alternative port number to support netcat-style
syntax
port Alternative port number to support netcat-style syntax
optional arguments:
-h, --help show this help message and exit
--version, -v Show version number and exit
--download-plugins Pre-download all Windows builtin plugins and exit immediately
--config CONFIG, -c CONFIG
Custom configuration file (default: ./pwncatrc)
--ssl Connect or listen with SSL
--ssl-cert SSL_CERT Certificate for SSL-encrypted listeners (PEM)
--ssl-key SSL_KEY Key for SSL-encrypted listeners (PEM)
--identity IDENTITY, -i IDENTITY
Private key for SSH authentication
--listen, -l Enable the `bind` protocol (supports netcat-style
syntax)
--listen, -l Enable the `bind` protocol (supports netcat-style syntax)
--platform PLATFORM, -m PLATFORM
Name of the platform to use (default: linux)
--port PORT, -p PORT Alternative way to specify port to support netcat-
style syntax
--list List installed implants with remote connection
capability
--port PORT, -p PORT Alternative way to specify port to support netcat-style syntax
--list List installed implants with remote connection capability
Windows Plugin Binaries
-----------------------

View File

@ -34,6 +34,11 @@ with three different C2 protocols: ``bind``, ``connect``, and ``ssh``. The first
modes simply open a raw socket and assume there is a shell on the other end. In SSH mode, we legitimately
authenticate to the victim host with provided credentials and utilize the SSH shell channel as our C2 channel.
pwncat also implements SSL-wrapped versions of ``bind`` and ``connect`` protocols aptly named ``ssl-bind``
and ``ssl-connect``. These protocols function largely the same as bind/connect, except that they operate
over an encrypted SSL tunnel. You must use an encrypted bind or reverse shell on the victim side such
as ``ncat --ssl`` or `socat OPENSSL-LISTEN:`.
pwncat exposes these different C2 channel protocols via the ``protocol`` field of the connection string
discussed below.
@ -42,22 +47,30 @@ Connecting to a Victim
Connecting to a victim is accomplished through a connection string. Connection strings are versatile ways
to describe the parameters to a specific C2 Channel/Protocol. This looks something like:
``[protocol://][user[:password]]@[host:][port]``
``[protocol://][user[:password]]@[host:][port][?arg1=value&arg2=value]``
Each field in the connection string translates to a parameter passed to the C2 channel. Some channels don't
require all the parameters. For example, a ``bind`` or ``connect`` channel doesn't required a username or
a password.
a password. If there is not an explicit argument or parsed value within the above format, you can use the
query string arguments to specify arbitrary channel arguments. You cannot specify the same argument twice
(e.g. ``connect://hostname:1111?port=4444``).
If the ``protocol`` field is not specified, pwncat will attempt to figure out the correct protocol
contextually. The following rules apply:
- If a user and host are provided, assume ``ssh`` protocol
- If no user is provided but a host and port are provided, assume protocol is ``connect``
- If no user is provided but a host, port and the ``--ssl`` argument, assume protocol is ``ssl-connect``
- If no user is provided but a host and port are provided and no ``--ssl``, assume protocol is ``connect``
- If no user or host is provided (or host is ``0.0.0.0``) and the ``certfile``, ``keyfile``, or
``--ssl`` arguments are provided, protocol is assumed to be ``ssl-bind``
- If no user or host is provided (or host is ``0.0.0.0``), protocol is assumed to be ``bind``
- If a second positional integer parameter is specified, the protocol is assumed to be ``connect``
- This is the ``netcat`` syntax seen in the below examples for the ``connect`` protocol.
- If the ``-l`` parameter is used, the protocol is assumed to be ``bind``.
- This is the ``netcat`` syntax seen in the below examples for the ``bind`` protocol.
- If a second positional integer parameter is specified and ``--ssl`` is not, the protocol is assumed
to be ``connect``
- If a second positional integer parameter is specified and ``--ssl`` is provided, the protocol is
assumed to be ``ssl-connect``
- If the ``-l`` parameter is used and the ``certfile``, ``keyfile``, or ``--ssl`` arguments are
provided, the protocol is assumed to be ``ssl-bind``.
- If the ``-l`` parameter is used alone, then the protocol is assumed to be ``bind``
Connecting to a victim bind shell
---------------------------------
@ -75,6 +88,21 @@ address which is routable (e.g. not NAT'd). The ``connect`` protocol provides th
# Connection string with assumed protocol
pwncat 192.168.1.1:4444
Connecting to a victim encrypted bind shell
-------------------------------------------
In this case, the victim is running a ssl-wrapped bind shell on an open port. The victim must be available at an
address which is routable (e.g. not NAT'd). The ``ssl-connect`` protocol provides this capability.
.. code-block:: bash
:caption: Connecting to a bind shell at 1.1.1.1:4444
# Full connection string
pwncat connect://192.168.1.1:4444
# ncat style syntax
pwncat --ssl 192.168.1.1 4444
pwncat --ssl 192.168.1.1:4444
Catching a victim reverse shell
-------------------------------
@ -94,6 +122,29 @@ victim machine. This mode is accessed via the ``bind`` protocol.
# Assumed protocol, assumed bind address
pwncat :4444
Catching a victim encrypted reverse shell
-----------------------------------------
In this case, the victim was exploited in such a way that they open an ssl connection to your attacking host
on a specific port with a raw shell open on the other end. Your attacking host must be routable from the
victim machine. This mode is accessed via the ``ssl-bind`` protocol.
If the explicit ``ssl-bind`` protocol or the ``--ssl`` argument is provided without an explicit certfile
or keyfile, a self-signed certificate is generated with dummy attributes. The certfile and keyfile can
both point to the same bundled PEM file if both the key and certificate are present.
.. code-block:: bash
:caption: Catching a reverse shell
# ncat style syntax
pwncat --ssl --ssl-cert cert.pem --ssl-key cert.pem -lp 4444
# Full connection string
pwncat ssl-bind://0.0.0.0:4444?certfile=/path/to/cert.pem&keyfile=/path/to/key.pem
# Auto-generated self-signed certificate
pwncat --ssl -lp 4444
# Auto-generated self-signed certificate with explicit protocol
pwncat ssl-bind://0.0.0.0:4444
Connecting to a Remote SSH Server
---------------------------------

View File

@ -39,6 +39,17 @@ def main():
default=None,
help="Custom configuration file (default: ./pwncatrc)",
)
parser.add_argument("--ssl", action="store_true", help="Connect or listen with SSL")
parser.add_argument(
"--ssl-cert",
default=None,
help="Certificate for SSL-encrypted listeners (PEM)",
)
parser.add_argument(
"--ssl-key",
default=None,
help="Key for SSL-encrypted listeners (PEM)",
)
parser.add_argument(
"--identity",
"-i",
@ -149,36 +160,74 @@ def main():
or args.listen
or args.identity is not None
):
protocol = None
user = None
password = None
host = None
port = None
query_args = {}
query_args["protocol"] = None
query_args["user"] = None
query_args["password"] = None
query_args["host"] = None
query_args["port"] = None
query_args["platform"] = args.platform
query_args["identity"] = args.identity
query_args["certfile"] = args.ssl_cert
query_args["keyfile"] = args.ssl_key
query_args["ssl"] = args.ssl
querystring = None
if args.connection_string:
m = connect.Command.CONNECTION_PATTERN.match(args.connection_string)
protocol = m.group("protocol")
user = m.group("user")
password = m.group("password")
host = m.group("host")
port = m.group("port")
query_args["protocol"] = m.group("protocol")
query_args["user"] = m.group("user")
query_args["password"] = m.group("password")
query_args["host"] = m.group("host")
query_args["port"] = m.group("port")
querystring = m.group("querystring")
if protocol is not None:
protocol = protocol.removesuffix("://")
if query_args["protocol"] is not None:
query_args["protocol"] = query_args["protocol"].removesuffix("://")
if host is not None and host == "":
host = None
if querystring is not None:
for arg in querystring.split("&"):
if arg.find("=") == -1:
continue
if protocol is not None and args.listen:
key, *value = arg.split("=")
if key in query_args and query_args[key] is not None:
console.log(f"[red]error[/red]: multiple values for {key}")
return
query_args[key] = "=".join(value)
if query_args["host"] is not None and query_args["host"] == "":
query_args["host"] = None
if query_args["protocol"] is not None and args.listen:
console.log(
"[red]error[/red]: --listen is not compatible with an explicit connection string"
)
return
if (
query_args["certfile"] is None and query_args["keyfile"] is not None
) or (query_args["certfile"] is not None and query_args["keyfile"] is None):
console.log(
"[red]error[/red]: both a ssl certificate and key file are required"
)
return
if query_args["certfile"] is not None or query_args["keyfile"] is not None:
query_args["ssl"] = True
if query_args["protocol"] is not None and args.ssl:
console.log(
"[red]error[/red]: --ssl is incompatible with an explicit protocol"
)
return
if (
sum(
[
port is not None,
query_args["port"] is not None,
args.port is not None,
args.pos_port is not None,
]
@ -189,22 +238,24 @@ def main():
return
if args.port is not None:
port = args.port
query_args["port"] = args.port
if args.pos_port is not None:
port = args.pos_port
query_args["port"] = args.pos_port
if port is not None:
if query_args["port"] is not None:
try:
port = int(port.lstrip(":"))
query_args["port"] = int(query_args["port"].lstrip(":"))
except ValueError:
console.log(f"[red]error[/red]: {port}: invalid port number")
console.log(
f"[red]error[/red]: {query_args['port'].lstrip(':')}: invalid port number"
)
return
# Attempt to reconnect via installed implants
if (
protocol is None
and password is None
and port is None
query_args["protocol"] is None
and query_args["password"] is None
and query_args["port"] is None
and args.identity is None
):
db = manager.db.open()
@ -213,7 +264,10 @@ def main():
# Locate all installed implants
for target in db.root.targets:
if target.guid != host and target.public_address[0] != host:
if (
target.guid != query_args["host"]
and target.public_address[0] != query_args["host"]
):
continue
# Collect users
@ -236,13 +290,16 @@ def main():
) as progress:
task = progress.add_task("", status="...")
for target, implant_user, implant in implants:
# Check correct user
if user is not None and implant_user.name != user:
# Check correct query_args["user"]
if (
query_args["user"] is not None
and implant_user.name != query_args["user"]
):
continue
# Check correct platform
if (
args.platform is not None
and target.platform != args.platform
query_args["platform"] is not None
and target.platform != query_args["platform"]
):
continue
@ -267,13 +324,7 @@ def main():
else:
try:
manager.create_session(
platform=args.platform,
protocol=protocol,
user=user,
password=password,
host=host,
port=port,
identity=args.identity,
**query_args,
)
except (ChannelError, PlatformError) as exc:
manager.log(f"connection failed: {exc}")

View File

@ -581,9 +581,19 @@ def create(protocol: Optional[str] = None, **kwargs) -> Channel:
or kwargs["host"] == "0.0.0.0"
or kwargs["host"] is None
):
protocols.append("bind")
if (
kwargs.get("certfile") is not None
or kwargs.get("keyfile") is not None
or kwargs.get("ssl")
):
protocols.append("ssl-bind")
else:
protocols.append("bind")
else:
protocols.append("connect")
if kwargs.get("ssl"):
protocols.append("ssl-connect")
else:
protocols.append("connect")
else:
protocols = [protocol]
@ -600,8 +610,12 @@ from pwncat.channel.ssh import Ssh # noqa: E402
from pwncat.channel.bind import Bind # noqa: E402
from pwncat.channel.socket import Socket # noqa: E402
from pwncat.channel.connect import Connect # noqa: E402
from pwncat.channel.ssl_bind import SSLBind # noqa: E402
from pwncat.channel.ssl_connect import SSLConnect # noqa: E402
register("socket", Socket)
register("bind", Bind)
register("connect", Connect)
register("ssh", Ssh)
register("ssl-bind", SSLBind)
register("ssl-connect", SSLConnect)

View File

@ -28,6 +28,12 @@ class Bind(Socket):
if not host or host == "":
host = "0.0.0.0"
if isinstance(port, str):
try:
port = int(port)
except ValueError:
raise ChannelError(self, "invalid port number")
if port is None:
raise ChannelError(self, "no port specified")
@ -51,6 +57,8 @@ class Bind(Socket):
self._socket_connected(client)
except KeyboardInterrupt:
raise ChannelError(self, "listener aborted")
except socket.error as exc:
raise ChannelError(self, str(exc))
finally:
self.server.close()

View File

@ -24,10 +24,16 @@ class Connect(Socket):
def __init__(self, host: str, port: int, **kwargs):
if not host:
raise ChannelError("no host address provided")
raise ChannelError(self, "no host address provided")
if port is None:
raise ChannelError("no port provided")
raise ChannelError(self, "no port provided")
if isinstance(port, str):
try:
port = int(port)
except ValueError:
raise ChannelError(self, "invalid port")
with Progress(
f"connecting to [blue]{host}[/blue]:[cyan]{port}[/cyan]",

View File

@ -15,6 +15,7 @@ utilize this class to instantiate a session via an established socket.
manager.interactive()
"""
import os
import ssl
import errno
import fcntl
import socket
@ -48,6 +49,9 @@ class Socket(Channel):
def __init__(self, client: socket.socket = None, **kwargs):
if isinstance(client, str):
raise ChannelError(self, f"expected socket object not {repr(type(client))}")
if client is not None:
# Report host and port number to base channel
host, port = client.getpeername()
@ -91,11 +95,14 @@ class Socket(Channel):
while written < len(data):
try:
written += self.client.send(data[written:])
except BlockingIOError:
except (BlockingIOError, ssl.SSLWantWriteError, ssl.SSLWantReadError):
pass
except BrokenPipeError as exc:
self._connected = False
raise ChannelClosed(self) from exc
except (ssl.SSLEOFError, ssl.SSLSyscallError, ssl.SSLZeroReturnError) as exc:
self._connected = False
raise ChannelClosed(self) from exc
return len(data)
@ -124,6 +131,11 @@ class Socket(Channel):
try:
data = data + self.client.recv(count)
return data
except ssl.SSLWantReadError:
return data
except (ssl.SSLEOFError, ssl.SSLSyscallError, ssl.SSLZeroReturnError) as exc:
self._connected = False
raise ChannelClosed(self) from exc
except socket.error as exc:
if exc.args[0] == errno.EAGAIN or exc.args[0] == errno.EWOULDBLOCK:
return data

View File

@ -33,8 +33,14 @@ class Ssh(Channel):
if port is None:
port = 22
if isinstance(port, str):
try:
port = int(port)
except ValueError:
raise ChannelError(self, "invalid port")
if not user or user is None:
raise ChannelError("you must specify a user")
raise ChannelError(self, "you must specify a user")
if password is None and identity is None:
password = prompt("Password: ", is_password=True)
@ -51,7 +57,7 @@ class Ssh(Channel):
t.start_client()
except paramiko.SSHException:
sock.close()
raise ChannelError("ssh negotiation failed")
raise ChannelError(self, "ssh negotiation failed")
if identity is not None:
try:
@ -67,23 +73,23 @@ class Ssh(Channel):
try:
key = paramiko.RSAKey.from_private_key_file(identity, password)
except paramiko.ssh_exception.SSHException:
raise ChannelError("invalid private key or passphrase")
raise ChannelError(self, "invalid private key or passphrase")
# Attempt authentication
try:
t.auth_publickey(user, key)
except paramiko.ssh_exception.AuthenticationException as exc:
raise ChannelError(str(exc))
raise ChannelError(self, str(exc))
else:
try:
t.auth_password(user, password)
except paramiko.ssh_exception.AuthenticationException as exc:
raise ChannelError(str(exc))
raise ChannelError(self, str(exc))
if not t.is_authenticated():
t.close()
sock.close()
raise ChannelError("authentication failed")
raise ChannelError(self, "authentication failed")
# Open an interactive session
chan = t.open_session()

View File

@ -0,0 +1,78 @@
#!/usr/bin/env python3
import ssl
import datetime
import tempfile
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from pwncat.channel import ChannelError
from pwncat.channel.bind import Bind
class SSLBind(Bind):
def __init__(self, certfile: str = None, keyfile: str = None, **kwargs):
super().__init__(**kwargs)
self.context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
if certfile is None and keyfile is None:
certfile = keyfile = self._generate_self_signed_cert()
self.context.load_cert_chain(certfile, keyfile)
self.server = self.context.wrap_socket(self.server)
def connect(self):
try:
super().connect()
except ssl.SSLError as exc:
raise ChannelError(self, str(exc))
def _generate_self_signed_cert(self):
"""Generate a self-signed certificate"""
with tempfile.NamedTemporaryFile("wb", delete=False) as filp:
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
filp.write(
key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
)
# Literally taken from: https://cryptography.io/en/latest/x509/tutorial/
subject = issuer = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"California"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My Company"),
x509.NameAttribute(NameOID.COMMON_NAME, u"mysite.com"),
]
)
cert = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.public_key(key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=365)
)
.add_extension(
x509.SubjectAlternativeName([x509.DNSName(u"localhost")]),
critical=False,
)
.sign(key, hashes.SHA256())
)
filp.write(cert.public_bytes(serialization.Encoding.PEM))
return filp.name

View File

@ -0,0 +1,22 @@
#!/usr/bin/env python3
import ssl
from pwncat.channel import ChannelError
from pwncat.channel.connect import Connect
class SSLConnect(Connect):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def _socket_connected(self, client):
try:
self.context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
self.context.check_hostname = False
self.context.verify_mode = ssl.VerifyMode.CERT_NONE
client = self.context.wrap_socket(client)
except ssl.SSLError as exc:
raise ChannelError(str(exc))
super()._socket_connected(client)

View File

@ -58,6 +58,16 @@ class Command(CommandDefinition):
action="store_true",
help="List installed implants with remote connection capability",
),
"--ssl-cert": Parameter(
Complete.LOCAL_FILE,
help="Certificate for SSL-encrypted listeners (PEM)",
),
"--ssl-key": Parameter(
Complete.LOCAL_FILE, help="Key for SSL-encrypted listeners (PEM)"
),
"--ssl": Parameter(
Complete.NONE, action="store_true", help="Connect or listen with SSL"
),
"connection_string": Parameter(
Complete.NONE,
metavar="[protocol://][user[:password]@][host][:port]",
@ -73,16 +83,23 @@ class Command(CommandDefinition):
}
LOCAL = True
CONNECTION_PATTERN = re.compile(
r"""^(?P<protocol>[-a-zA-Z0-9_]*://)?((?P<user>[^:@]*)?(?P<password>:(\\@|[^@])*)?@)?(?P<host>[^:]*)?(?P<port>:[0-9]*)?$"""
r"""^(?P<protocol>[-a-zA-Z0-9_]*://)?((?P<user>[^:@]*)?(?P<password>:(\\@|[^@])*)?@)?(?P<host>[^:]*)?(?P<port>:[0-9]*)?(\?(?P<querystring>.*))?$"""
)
def run(self, manager: "pwncat.manager.Manager", args):
protocol = None
user = None
password = None
host = None
port = None
query_args = {}
query_args["protocol"] = None
query_args["user"] = None
query_args["password"] = None
query_args["host"] = None
query_args["port"] = None
query_args["platform"] = args.platform
query_args["identity"] = args.identity
query_args["certfile"] = args.ssl_cert
query_args["keyfile"] = args.ssl_key
query_args["ssl"] = args.ssl
querystring = None
used_implant = None
if args.list:
@ -128,28 +145,53 @@ class Command(CommandDefinition):
if args.connection_string:
m = self.CONNECTION_PATTERN.match(args.connection_string)
protocol = m.group("protocol")
user = m.group("user")
password = m.group("password")
host = m.group("host")
port = m.group("port")
query_args["protocol"] = m.group("protocol")
query_args["user"] = m.group("user")
query_args["password"] = m.group("password")
query_args["host"] = m.group("host")
query_args["port"] = m.group("port")
querystring = m.group("querystring")
if protocol is not None:
protocol = protocol.removesuffix("://")
if query_args["protocol"] is not None:
query_args["protocol"] = query_args["protocol"].removesuffix("://")
if host is not None and host == "":
host = None
if querystring is not None:
for arg in querystring.split("&"):
if arg.find("=") == -1:
continue
if protocol is not None and args.listen:
key, *value = arg.split("=")
if key in query_args and query_args[key] is not None:
console.log(f"[red]error[/red]: multiple values for {key}")
return
query_args[key] = "=".join(value)
if query_args["host"] is not None and query_args["host"] == "":
query_args["host"] = None
if query_args["protocol"] is not None and args.listen:
console.log(
"[red]error[/red]: --listen is not compatible with an explicit connection string"
)
return
if (query_args["certfile"] is None and query_args["keyfile"] is not None) or (
query_args["certfile"] is not None and query_args["keyfile"] is None
):
console.log(
"[red]error[/red]: both a ssl certificate and key file are required"
)
return
if query_args["certfile"] is not None or query_args["keyfile"] is not None:
query_args["ssl"] = True
if (
sum(
[
port is not None,
query_args["port"] is not None,
args.port is not None,
args.pos_port is not None,
]
@ -159,23 +201,27 @@ class Command(CommandDefinition):
console.log("[red]error[/red]: multiple ports specified")
return
if args.port is not None:
port = args.port
if args.pos_port is not None:
port = args.pos_port
console.log(args.pos_port)
if port is not None:
if args.port is not None:
query_args["port"] = args.port
if args.pos_port is not None:
query_args["port"] = args.pos_port
if query_args["port"] is not None:
try:
port = int(port.lstrip(":"))
query_args["port"] = int(query_args["port"].lstrip(":"))
except ValueError:
console.log(f"[red]error[/red]: {port}: invalid port number")
console.log(
f"[red]error[/red]: {query_args['port'].lstrip(':')}: invalid port number"
)
return
# Attempt to reconnect via installed implants
if (
protocol is None
and password is None
and port is None
query_args["protocol"] is None
and query_args["password"] is None
and query_args["port"] is None
and args.identity is None
):
db = manager.db.open()
@ -184,7 +230,10 @@ class Command(CommandDefinition):
# Locate all installed implants
for target in db.root.targets:
if target.guid != host and target.public_address[0] != host:
if (
target.guid != query_args["host"]
and target.public_address[0] != query_args["host"]
):
continue
# Collect users
@ -207,11 +256,17 @@ class Command(CommandDefinition):
) as progress:
task = progress.add_task("", status="...")
for target, implant_user, implant in implants:
# Check correct user
if user is not None and implant_user.name != user:
# Check correct query_args["user"]
if (
query_args["user"] is not None
and implant_user.name != query_args["user"]
):
continue
# Check correct platform
if args.platform is not None and target.platform != args.platform:
if (
query_args["platform"] is not None
and target.platform != query_args["platform"]
):
continue
progress.update(
@ -225,17 +280,10 @@ class Command(CommandDefinition):
used_implant = implant
break
except ModuleFailed:
db.transaction_manager.commit()
continue
if used_implant is not None:
manager.target.log(f"connected via {used_implant.title(manager.target)}")
else:
manager.create_session(
platform=args.platform,
protocol=protocol,
user=user,
password=password,
host=host,
port=port,
identity=args.identity,
)
manager.create_session(**query_args)

View File

@ -19,12 +19,11 @@ with pwncat.manager.Manager("data/pwncatrc") as manager:
# session = manager.create_session("windows", host="192.168.56.10", port=4444)
# session = manager.create_session("windows", host="192.168.122.11", port=4444)
# session = manager.create_session("linux", host="pwncat-ubuntu", port=4444)
session = manager.create_session("linux", host="127.0.0.1", port=4444)
# session = manager.create_session("linux", host="127.0.0.1", port=4444)
session = manager.create_session(
"linux", certfile="/tmp/cert.pem", keyfile="/tmp/cert.pem", port=4444
)
# session.platform.powershell("amsiutils")
with open("/tmp/random", "rb") as source:
with session.platform.open("/tmp/random", "wb") as destination:
shutil.copyfileobj(source, destination)
manager.interactive()