Working background listener API and commands
Needs more testing, but is functioning currently.
This commit is contained in:
parent
1fda11442a
commit
e3c4c12cad
|
@ -14,6 +14,7 @@ and simply didn't have the time to go back and retroactively create one.
|
||||||
- Added query-string arguments to connection strings for both the entrypoint
|
- Added query-string arguments to connection strings for both the entrypoint
|
||||||
and the `connect` command.
|
and the `connect` command.
|
||||||
- Added Enumeration States to allow session-bound enumerations
|
- Added Enumeration States to allow session-bound enumerations
|
||||||
|
- Added background listener API and commands ([#43](https://github.com/calebstewart/pwncat/issues/43))
|
||||||
|
|
||||||
## [0.4.3] - 2021-06-18
|
## [0.4.3] - 2021-06-18
|
||||||
Patch fix release. Major fixes are the correction of file IO for LinuxWriters and
|
Patch fix release. Major fixes are the correction of file IO for LinuxWriters and
|
||||||
|
|
|
@ -793,9 +793,14 @@ class CommandLexer(RegexLexer):
|
||||||
"""Build the RegexLexer token list from the command definitions"""
|
"""Build the RegexLexer token list from the command definitions"""
|
||||||
|
|
||||||
root = []
|
root = []
|
||||||
for command in commands:
|
sorted_commands = sorted(commands, key=lambda cmd: len(cmd.PROG), reverse=True)
|
||||||
|
for command in sorted_commands:
|
||||||
root.append(
|
root.append(
|
||||||
("^" + re.escape(command.PROG), token.Name.Function, command.PROG)
|
(
|
||||||
|
"^" + re.escape(command.PROG) + "( |$)",
|
||||||
|
token.Name.Function,
|
||||||
|
command.PROG,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
mode = []
|
mode = []
|
||||||
if command.ARGS is not None:
|
if command.ARGS is not None:
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from rich.prompt import Confirm
|
||||||
|
|
||||||
|
import pwncat
|
||||||
|
from pwncat.util import console
|
||||||
|
from pwncat.manager import ListenerState
|
||||||
|
from pwncat.commands import Complete, Parameter, CommandDefinition
|
||||||
|
|
||||||
|
|
||||||
|
class Command(CommandDefinition):
|
||||||
|
"""
|
||||||
|
Create a new background listener. Background listeners will continue
|
||||||
|
listening while you do other things in pwncat. When a connection is
|
||||||
|
established, the listener will either queue the new channel for
|
||||||
|
future initialization or construct a full session.
|
||||||
|
|
||||||
|
If a platform is provided, a session will automatically be established
|
||||||
|
for any new incoming connections. If no platform is provided, the
|
||||||
|
channels will be queued, and can be initialized with the 'listeners'
|
||||||
|
command.
|
||||||
|
|
||||||
|
If the drop_duplicate option is provided, sessions which connect to
|
||||||
|
a host which already has an active session with the same user will
|
||||||
|
be automatically dropped. This facilitates an infinite callback implant
|
||||||
|
which you don't want to pollute the active session list.
|
||||||
|
"""
|
||||||
|
|
||||||
|
PROG = "listen"
|
||||||
|
ARGS = {
|
||||||
|
"--count,-c": Parameter(
|
||||||
|
Complete.NONE,
|
||||||
|
type=int,
|
||||||
|
help="Number of sessions a listener should accept before automatically stopping (default: infinite)",
|
||||||
|
),
|
||||||
|
"--platform,-m": Parameter(
|
||||||
|
Complete.NONE,
|
||||||
|
type=str,
|
||||||
|
help="Name of the platform used to automatically construct a session for a new connection",
|
||||||
|
),
|
||||||
|
"--ssl": Parameter(
|
||||||
|
Complete.NONE,
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Wrap a new listener in an SSL context",
|
||||||
|
),
|
||||||
|
"--ssl-cert": Parameter(
|
||||||
|
Complete.LOCAL_FILE,
|
||||||
|
help="SSL Server Certificate for SSL wrapped listeners",
|
||||||
|
),
|
||||||
|
"--ssl-key": Parameter(
|
||||||
|
Complete.LOCAL_FILE,
|
||||||
|
help="SSL Server Private Key for SSL wrapped listeners",
|
||||||
|
),
|
||||||
|
"--host,-H": Parameter(
|
||||||
|
Complete.NONE,
|
||||||
|
help="Host address on which to bind (default: 0.0.0.0)",
|
||||||
|
default="0.0.0.0",
|
||||||
|
),
|
||||||
|
"port": Parameter(
|
||||||
|
Complete.NONE,
|
||||||
|
type=int,
|
||||||
|
help="Port on which to listen for new listeners",
|
||||||
|
),
|
||||||
|
"--drop-duplicate,-D": Parameter(
|
||||||
|
Complete.NONE,
|
||||||
|
action="store_true",
|
||||||
|
help="Automatically drop sessions with hosts that are already active",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
LOCAL = True
|
||||||
|
|
||||||
|
def _drop_duplicate(self, session: "pwncat.manager.Session"):
|
||||||
|
|
||||||
|
for other in session.manager.sessions.values():
|
||||||
|
if (
|
||||||
|
other is not session
|
||||||
|
and session.hash == other.hash
|
||||||
|
and session.platform.getuid() == other.platform.getuid()
|
||||||
|
):
|
||||||
|
session.log("dropping duplicate session")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def run(self, manager: "pwncat.manager.Manager", args):
|
||||||
|
|
||||||
|
if args.drop_duplicate:
|
||||||
|
established = self._drop_duplicate
|
||||||
|
|
||||||
|
if args.platform is None:
|
||||||
|
manager.print(
|
||||||
|
"You have not specified a platform. Connections will be queued until initialized with the 'listeners' command."
|
||||||
|
)
|
||||||
|
if not Confirm.ask("Are you sure?"):
|
||||||
|
return
|
||||||
|
|
||||||
|
with console.status("creating listener..."):
|
||||||
|
listener = manager.create_listener(
|
||||||
|
protocol="socket",
|
||||||
|
platform=args.platform,
|
||||||
|
host=args.host,
|
||||||
|
port=args.port,
|
||||||
|
ssl=args.ssl,
|
||||||
|
ssl_cert=args.ssl_cert,
|
||||||
|
ssl_key=args.ssl_key,
|
||||||
|
established=established,
|
||||||
|
count=args.count,
|
||||||
|
)
|
||||||
|
|
||||||
|
while listener.state is ListenerState.STOPPED:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if listener.state is ListenerState.FAILED:
|
||||||
|
manager.log(
|
||||||
|
f"[red]error[/red]: listener startup failed: {listener.failure_exception}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
manager.log(f"new listener created for {listener}")
|
|
@ -0,0 +1,207 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from rich import box
|
||||||
|
from rich.table import Table
|
||||||
|
from rich.prompt import Prompt
|
||||||
|
|
||||||
|
import pwncat
|
||||||
|
from pwncat.util import console
|
||||||
|
from pwncat.manager import Listener, ListenerError, ListenerState
|
||||||
|
from pwncat.commands import Complete, Parameter, CommandDefinition
|
||||||
|
|
||||||
|
|
||||||
|
class Command(CommandDefinition):
|
||||||
|
"""
|
||||||
|
Manage active or stopped background listeners. This command
|
||||||
|
is only used to interact with established listeners. For
|
||||||
|
creating new listeners, use the "listen" command instead.
|
||||||
|
"""
|
||||||
|
|
||||||
|
PROG = "listeners"
|
||||||
|
ARGS = {
|
||||||
|
"--all,-a": Parameter(
|
||||||
|
Complete.NONE,
|
||||||
|
action="store_true",
|
||||||
|
help="Show all listeners when listing (default: hide stopped)",
|
||||||
|
),
|
||||||
|
"--kill,-k": Parameter(
|
||||||
|
Complete.NONE, action="store_true", help="Stop the given listener"
|
||||||
|
),
|
||||||
|
"--init,-i": Parameter(
|
||||||
|
Complete.NONE, action="store_true", help="Initialize pending channels"
|
||||||
|
),
|
||||||
|
"id": Parameter(
|
||||||
|
Complete.NONE,
|
||||||
|
type=int,
|
||||||
|
nargs="?",
|
||||||
|
help="The specific listener to interact with",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
LOCAL = True
|
||||||
|
|
||||||
|
def _init_channel(self, manager: pwncat.manager.Manager, listener: Listener):
|
||||||
|
"""Initialize pending channel"""
|
||||||
|
|
||||||
|
# Grab list of pending channels
|
||||||
|
channels = list(listener.iter_channels())
|
||||||
|
if not channels:
|
||||||
|
manager.log("no pending channels")
|
||||||
|
return
|
||||||
|
|
||||||
|
manager.print(f"Pending Channels for {listener}:")
|
||||||
|
for ident, channel in enumerate(channels):
|
||||||
|
manager.print(f"{ident}. {channel}")
|
||||||
|
|
||||||
|
manager.print("\nPress C-c to stop initializing channels.")
|
||||||
|
|
||||||
|
platform = "linux"
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
if not any(chan is not None for chan in channels):
|
||||||
|
manager.log("all pending channels configured")
|
||||||
|
break
|
||||||
|
|
||||||
|
ident = int(
|
||||||
|
Prompt.ask(
|
||||||
|
"Channel Index",
|
||||||
|
choices=[
|
||||||
|
str(x)
|
||||||
|
for x in range(len(channels))
|
||||||
|
if channels[x] is not None
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if channels[ident] is None:
|
||||||
|
manager.print("[red]error[/red]: channel already initialized.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
platform = Prompt.ask(
|
||||||
|
"Platform Name",
|
||||||
|
default=platform,
|
||||||
|
choices=["linux", "windows", "drop"],
|
||||||
|
show_default=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if platform == "drop":
|
||||||
|
manager.log(f"dropping channel: {channels[ident]}")
|
||||||
|
channels[ident].close()
|
||||||
|
channels[ident] = None
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
listener.bootstrap_session(channels[ident], platform)
|
||||||
|
channels[ident] = None
|
||||||
|
except ListenerError as exc:
|
||||||
|
manager.log(f"channel bootstrap failed: {exc}")
|
||||||
|
channels[ident].close()
|
||||||
|
channels[ident] = None
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
manager.print("")
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
for channel in channels:
|
||||||
|
if channel is not None:
|
||||||
|
listener.bootstrap_session(channel, platform=None)
|
||||||
|
|
||||||
|
def _show_listener(self, manager: pwncat.manager.Manager, listener: Listener):
|
||||||
|
"""Show detailed information on a listener"""
|
||||||
|
|
||||||
|
# Makes printing the variables a little more straightforward
|
||||||
|
def dump_var(name, value):
|
||||||
|
manager.print(f"[yellow]{name}[/yellow] = {value}")
|
||||||
|
|
||||||
|
# Dump common state
|
||||||
|
dump_var("address", str(listener))
|
||||||
|
|
||||||
|
state_color = "green"
|
||||||
|
if listener.state is ListenerState.FAILED:
|
||||||
|
state_color = "red"
|
||||||
|
elif listener.state is ListenerState.STOPPED:
|
||||||
|
state_color = "yellow"
|
||||||
|
|
||||||
|
dump_var(
|
||||||
|
"state",
|
||||||
|
f"[{state_color}]"
|
||||||
|
+ str(listener.state).split(".")[1]
|
||||||
|
+ f"[/{state_color}]",
|
||||||
|
)
|
||||||
|
|
||||||
|
# If the listener failed, show the failure message
|
||||||
|
if listener.state is ListenerState.FAILED:
|
||||||
|
dump_var("[red]error[/red]", repr(str(listener.failure_exception)))
|
||||||
|
|
||||||
|
dump_var("protocol", repr(listener.protocol))
|
||||||
|
dump_var("platform", repr(listener.platform))
|
||||||
|
|
||||||
|
# A count of None means infinity, annotate that
|
||||||
|
if listener.count is not None:
|
||||||
|
dump_var("remaining", listener.count)
|
||||||
|
else:
|
||||||
|
dump_var("remaining", "[red]infinite[/red]")
|
||||||
|
|
||||||
|
# Number of pending channels
|
||||||
|
dump_var("pending", listener.pending)
|
||||||
|
|
||||||
|
# SSL settings
|
||||||
|
dump_var("ssl", repr(listener.ssl))
|
||||||
|
if listener.ssl:
|
||||||
|
dump_var("ssl_cert", repr(listener.ssl_cert))
|
||||||
|
dump_var("ssl_key", repr(listener.ssl_key))
|
||||||
|
|
||||||
|
def run(self, manager: "pwncat.manager.Manager", args):
|
||||||
|
|
||||||
|
if (args.kill or args.init) and args.id is None:
|
||||||
|
self.parser.error("missing argument: id")
|
||||||
|
|
||||||
|
if args.kill and args.init:
|
||||||
|
self.parser.error("cannot use both kill and init arguments")
|
||||||
|
|
||||||
|
if args.id is not None and (args.id < 0 or args.id >= len(manager.listeners)):
|
||||||
|
self.parser.error(f"invalid listener id: {args.id}")
|
||||||
|
|
||||||
|
if args.kill:
|
||||||
|
# Kill the specified listener
|
||||||
|
with console.status("stopping listener..."):
|
||||||
|
manager.listeners[args.id].stop()
|
||||||
|
manager.log(f"stopped listener on {str(manager.listeners[args.id])}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if args.init:
|
||||||
|
self._init_channel(manager, manager.listeners[args.id])
|
||||||
|
return
|
||||||
|
|
||||||
|
if args.id is not None:
|
||||||
|
self._show_listener(manager, manager.listeners[args.id])
|
||||||
|
return
|
||||||
|
|
||||||
|
table = Table(
|
||||||
|
"ID",
|
||||||
|
"State",
|
||||||
|
"Address",
|
||||||
|
"Platform",
|
||||||
|
"Remaining",
|
||||||
|
"Pending",
|
||||||
|
title="Listeners",
|
||||||
|
box=box.MINIMAL_DOUBLE_HEAD,
|
||||||
|
)
|
||||||
|
|
||||||
|
for ident, listener in enumerate(manager.listeners):
|
||||||
|
|
||||||
|
if listener.state is ListenerState.STOPPED and not args.all:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if listener.count is None:
|
||||||
|
count = "[red]inf[/red]"
|
||||||
|
else:
|
||||||
|
count = str(listener.count)
|
||||||
|
|
||||||
|
table.add_row(
|
||||||
|
str(ident),
|
||||||
|
str(listener.state).split(".")[1],
|
||||||
|
f"[blue]{listener.address[0]}[/blue]:[cyan]{listener.address[1]}[/cyan]",
|
||||||
|
str(listener.platform),
|
||||||
|
count,
|
||||||
|
str(listener.pending),
|
||||||
|
)
|
||||||
|
|
||||||
|
console.print(table)
|
|
@ -22,6 +22,8 @@ import signal
|
||||||
import socket
|
import socket
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import pkgutil
|
import pkgutil
|
||||||
|
import datetime
|
||||||
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
import contextlib
|
import contextlib
|
||||||
from io import TextIOWrapper
|
from io import TextIOWrapper
|
||||||
|
@ -32,7 +34,11 @@ import ZODB
|
||||||
import zodburi
|
import zodburi
|
||||||
import rich.progress
|
import rich.progress
|
||||||
import persistent.list
|
import persistent.list
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.x509.oid import NameOID
|
||||||
from prompt_toolkit.shortcuts import confirm
|
from prompt_toolkit.shortcuts import confirm
|
||||||
|
from cryptography.hazmat.primitives import hashes, serialization
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
|
|
||||||
import pwncat.db
|
import pwncat.db
|
||||||
import pwncat.facts
|
import pwncat.facts
|
||||||
|
@ -119,7 +125,18 @@ class Listener(threading.Thread):
|
||||||
""" Queue of channels waiting to be initialized in the case of an unidentified platform """
|
""" Queue of channels waiting to be initialized in the case of an unidentified platform """
|
||||||
self._session_lock: threading.Lock = threading.Lock()
|
self._session_lock: threading.Lock = threading.Lock()
|
||||||
|
|
||||||
def iter_sessions(count: Optional[int] = None) -> Generator["Session", None, None]:
|
def __str__(self):
|
||||||
|
return f"[blue]{self.address[0]}[/blue]:[cyan]{self.address[1]}[/cyan]"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pending(self) -> int:
|
||||||
|
"""Retrieve the number of pending channels"""
|
||||||
|
|
||||||
|
return self._channel_queue.qsize()
|
||||||
|
|
||||||
|
def iter_sessions(
|
||||||
|
self, count: Optional[int] = None
|
||||||
|
) -> Generator["Session", None, None]:
|
||||||
"""
|
"""
|
||||||
Synchronously iterate over new sessions. This generated will
|
Synchronously iterate over new sessions. This generated will
|
||||||
yield sessions until no more sessions are found on the queue.
|
yield sessions until no more sessions are found on the queue.
|
||||||
|
@ -132,14 +149,20 @@ class Listener(threading.Thread):
|
||||||
:rtype: Generator[Session, None, None]
|
:rtype: Generator[Session, None, None]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
while count:
|
while True:
|
||||||
|
if count is not None and count <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
try:
|
try:
|
||||||
yield self._session_queue.get(block=False, timeout=None)
|
yield self._session_queue.get(block=False, timeout=None)
|
||||||
count -= 1
|
if count is not None:
|
||||||
|
count -= 1
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
return
|
return
|
||||||
|
|
||||||
def iter_channels(count: Optional[int] = None) -> Generator["Channel", None, None]:
|
def iter_channels(
|
||||||
|
self, count: Optional[int] = None
|
||||||
|
) -> Generator["Channel", None, None]:
|
||||||
"""
|
"""
|
||||||
Synchronously iterate over new channels. This generated will
|
Synchronously iterate over new channels. This generated will
|
||||||
yield channels until no more channels are found on the queue.
|
yield channels until no more channels are found on the queue.
|
||||||
|
@ -152,10 +175,14 @@ class Listener(threading.Thread):
|
||||||
:rtype: Generator[Channel, None, None]
|
:rtype: Generator[Channel, None, None]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
while count:
|
while True:
|
||||||
|
if count is not None and count <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
try:
|
try:
|
||||||
yield self._channel_queue.get(block=False, timeout=None)
|
yield self._channel_queue.get(block=False, timeout=None)
|
||||||
count -= 1
|
if count is not None:
|
||||||
|
count -= 1
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -211,6 +238,7 @@ class Listener(threading.Thread):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
self._session_queue.put_nowait(session)
|
self._session_queue.put_nowait(session)
|
||||||
|
break
|
||||||
except queue.Full:
|
except queue.Full:
|
||||||
try:
|
try:
|
||||||
self._session_queue.get_nowait()
|
self._session_queue.get_nowait()
|
||||||
|
@ -237,6 +265,8 @@ class Listener(threading.Thread):
|
||||||
self.count = 0
|
self.count = 0
|
||||||
self._stop_event.set()
|
self._stop_event.set()
|
||||||
|
|
||||||
|
self.join()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Execute the listener in the background. We have to be careful not
|
"""Execute the listener in the background. We have to be careful not
|
||||||
to trip up the manager, as this is running in a background thread."""
|
to trip up the manager, as this is running in a background thread."""
|
||||||
|
@ -248,7 +278,7 @@ class Listener(threading.Thread):
|
||||||
server = self._ssl_wrap(raw_server)
|
server = self._ssl_wrap(raw_server)
|
||||||
|
|
||||||
# Set a short timeout so we don't block the thread
|
# Set a short timeout so we don't block the thread
|
||||||
server.settimeout(0.1)
|
server.settimeout(1)
|
||||||
|
|
||||||
self.state = ListenerState.RUNNING
|
self.state = ListenerState.RUNNING
|
||||||
|
|
||||||
|
@ -267,8 +297,7 @@ class Listener(threading.Thread):
|
||||||
channel = self._bootstrap_channel(client)
|
channel = self._bootstrap_channel(client)
|
||||||
|
|
||||||
# If we know the platform, create the session
|
# If we know the platform, create the session
|
||||||
if self.platform is not None:
|
self.bootstrap_session(channel, platform=self.platform)
|
||||||
self.bootstrap_session(channel, platform=self.platform)
|
|
||||||
except ListenerError as exc:
|
except ListenerError as exc:
|
||||||
# this connection didn't establish; log it
|
# this connection didn't establish; log it
|
||||||
self.manager.log(
|
self.manager.log(
|
||||||
|
@ -315,7 +344,71 @@ class Listener(threading.Thread):
|
||||||
"""Wrap the given server socket in an SSL context and return the new socket.
|
"""Wrap the given server socket in an SSL context and return the new socket.
|
||||||
If the ``ssl`` option is not set, this method simply returns the original socket."""
|
If the ``ssl`` option is not set, this method simply returns the original socket."""
|
||||||
|
|
||||||
return server
|
if not self.ssl:
|
||||||
|
return server
|
||||||
|
|
||||||
|
if self.ssl_cert is None and self.ssl_key is not None:
|
||||||
|
self.ssl_cert = self.ssl_key
|
||||||
|
if self.ssl_key is None and self.ssl_cert is not None:
|
||||||
|
self.ssl_key = self.ssl_cert
|
||||||
|
|
||||||
|
if self.ssl_cert is None or self.ssl_key is None:
|
||||||
|
with tempfile.NamedTemporaryFile("wb", delete=False) as filp:
|
||||||
|
self.manager.log(
|
||||||
|
f"generating self-signed certificate at {repr(filp.name)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
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, "US"),
|
||||||
|
x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
|
||||||
|
x509.NameAttribute(
|
||||||
|
NameOID.STATE_OR_PROVINCE_NAME, "California"
|
||||||
|
),
|
||||||
|
x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"),
|
||||||
|
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"),
|
||||||
|
x509.NameAttribute(NameOID.COMMON_NAME, "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("localhost")]),
|
||||||
|
critical=False,
|
||||||
|
)
|
||||||
|
.sign(key, hashes.SHA256())
|
||||||
|
)
|
||||||
|
|
||||||
|
filp.write(cert.public_bytes(serialization.Encoding.PEM))
|
||||||
|
|
||||||
|
self.ssl_cert = filp.name
|
||||||
|
self.ssl_key = filp.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||||
|
context.load_cert_chain(self.ssl_cert, self.ssl_key)
|
||||||
|
|
||||||
|
return context.wrap_socket(server)
|
||||||
|
except ssl.SSLError as exc:
|
||||||
|
raise ListenerError(str(exc))
|
||||||
|
|
||||||
def _close_socket(self, raw_server: socket.socket, server: socket.socket):
|
def _close_socket(self, raw_server: socket.socket, server: socket.socket):
|
||||||
"""Close the listener socket"""
|
"""Close the listener socket"""
|
||||||
|
@ -666,7 +759,7 @@ class Manager:
|
||||||
self.parser = CommandParser(self)
|
self.parser = CommandParser(self)
|
||||||
self.interactive_running = False
|
self.interactive_running = False
|
||||||
self.db: ZODB.DB = None
|
self.db: ZODB.DB = None
|
||||||
self.prompt_lock = threading.RLock()
|
self.listeners: List[Listener] = []
|
||||||
|
|
||||||
# This is needed because pwntools captures the terminal...
|
# This is needed because pwntools captures the terminal...
|
||||||
# there's no way officially to undo it, so this is a nasty
|
# there's no way officially to undo it, so this is a nasty
|
||||||
|
@ -928,7 +1021,7 @@ class Manager:
|
||||||
try:
|
try:
|
||||||
self.target.platform.interactive_loop(interactive_complete)
|
self.target.platform.interactive_loop(interactive_complete)
|
||||||
except RawModeExit:
|
except RawModeExit:
|
||||||
pass
|
interactive_complete.set()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
raise exception_queue.get(block=False)
|
raise exception_queue.get(block=False)
|
||||||
|
@ -1008,6 +1101,8 @@ class Manager:
|
||||||
|
|
||||||
listener.start()
|
listener.start()
|
||||||
|
|
||||||
|
self.listeners.append(listener)
|
||||||
|
|
||||||
return listener
|
return listener
|
||||||
|
|
||||||
def create_session(self, platform: str, channel: Channel = None, **kwargs):
|
def create_session(self, platform: str, channel: Channel = None, **kwargs):
|
||||||
|
|
|
@ -852,6 +852,9 @@ function prompt {
|
||||||
except EOFError:
|
except EOFError:
|
||||||
self.channel.send(b"\rexit\r")
|
self.channel.send(b"\rexit\r")
|
||||||
interactive_complete.wait()
|
interactive_complete.wait()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
# This should only happen during an EOFError above
|
||||||
|
pass
|
||||||
finally:
|
finally:
|
||||||
pwncat.util.pop_term_state()
|
pwncat.util.pop_term_state()
|
||||||
|
|
||||||
|
|
1
test.py
1
test.py
|
@ -29,5 +29,6 @@ with pwncat.manager.Manager("data/pwncatrc") as manager:
|
||||||
listener = manager.create_listener(
|
listener = manager.create_listener(
|
||||||
protocol="socket", host="0.0.0.0", port=4444, platform="windows"
|
protocol="socket", host="0.0.0.0", port=4444, platform="windows"
|
||||||
)
|
)
|
||||||
|
listener = manager.create_listener(protocol="socket", host="0.0.0.0", port=9999)
|
||||||
|
|
||||||
manager.interactive()
|
manager.interactive()
|
||||||
|
|
Loading…
Reference in New Issue