diff --git a/portspoof_py/__pycache__/__init__.cpython-312.pyc b/portspoof_py/__pycache__/__init__.cpython-312.pyc index b43556b..f5dfe31 100644 Binary files a/portspoof_py/__pycache__/__init__.cpython-312.pyc and b/portspoof_py/__pycache__/__init__.cpython-312.pyc differ diff --git a/portspoof_py/__pycache__/__main__.cpython-312.pyc b/portspoof_py/__pycache__/__main__.cpython-312.pyc index d99fbdb..76f9571 100644 Binary files a/portspoof_py/__pycache__/__main__.cpython-312.pyc and b/portspoof_py/__pycache__/__main__.cpython-312.pyc differ diff --git a/portspoof_py/__pycache__/admin.cpython-312.pyc b/portspoof_py/__pycache__/admin.cpython-312.pyc index 1c8dd72..43c4dd7 100644 Binary files a/portspoof_py/__pycache__/admin.cpython-312.pyc and b/portspoof_py/__pycache__/admin.cpython-312.pyc differ diff --git a/portspoof_py/__pycache__/cli.cpython-312.pyc b/portspoof_py/__pycache__/cli.cpython-312.pyc index 7ad5df5..25489db 100644 Binary files a/portspoof_py/__pycache__/cli.cpython-312.pyc and b/portspoof_py/__pycache__/cli.cpython-312.pyc differ diff --git a/portspoof_py/__pycache__/config.cpython-312.pyc b/portspoof_py/__pycache__/config.cpython-312.pyc index 919573b..049f528 100644 Binary files a/portspoof_py/__pycache__/config.cpython-312.pyc and b/portspoof_py/__pycache__/config.cpython-312.pyc differ diff --git a/portspoof_py/__pycache__/geo.cpython-312.pyc b/portspoof_py/__pycache__/geo.cpython-312.pyc new file mode 100644 index 0000000..6de09a1 Binary files /dev/null and b/portspoof_py/__pycache__/geo.cpython-312.pyc differ diff --git a/portspoof_py/__pycache__/iptables.cpython-312.pyc b/portspoof_py/__pycache__/iptables.cpython-312.pyc index 0e06b10..c7910f2 100644 Binary files a/portspoof_py/__pycache__/iptables.cpython-312.pyc and b/portspoof_py/__pycache__/iptables.cpython-312.pyc differ diff --git a/portspoof_py/__pycache__/logger.cpython-312.pyc b/portspoof_py/__pycache__/logger.cpython-312.pyc index 672aa97..ea745df 100644 Binary files a/portspoof_py/__pycache__/logger.cpython-312.pyc and b/portspoof_py/__pycache__/logger.cpython-312.pyc differ diff --git a/portspoof_py/__pycache__/notifier.cpython-312.pyc b/portspoof_py/__pycache__/notifier.cpython-312.pyc index 5c5d6ec..9955743 100644 Binary files a/portspoof_py/__pycache__/notifier.cpython-312.pyc and b/portspoof_py/__pycache__/notifier.cpython-312.pyc differ diff --git a/portspoof_py/__pycache__/revregex.cpython-312.pyc b/portspoof_py/__pycache__/revregex.cpython-312.pyc index bfc2ea2..cb406a2 100644 Binary files a/portspoof_py/__pycache__/revregex.cpython-312.pyc and b/portspoof_py/__pycache__/revregex.cpython-312.pyc differ diff --git a/portspoof_py/__pycache__/server.cpython-312.pyc b/portspoof_py/__pycache__/server.cpython-312.pyc index 93397d6..60f9dae 100644 Binary files a/portspoof_py/__pycache__/server.cpython-312.pyc and b/portspoof_py/__pycache__/server.cpython-312.pyc differ diff --git a/portspoof_py/__pycache__/settings.cpython-312.pyc b/portspoof_py/__pycache__/settings.cpython-312.pyc new file mode 100644 index 0000000..b6f6fcc Binary files /dev/null and b/portspoof_py/__pycache__/settings.cpython-312.pyc differ diff --git a/portspoof_py/__pycache__/stats.cpython-312.pyc b/portspoof_py/__pycache__/stats.cpython-312.pyc index e8df5c0..e72ef4f 100644 Binary files a/portspoof_py/__pycache__/stats.cpython-312.pyc and b/portspoof_py/__pycache__/stats.cpython-312.pyc differ diff --git a/portspoof_py/cli.py b/portspoof_py/cli.py index f782cb8..25de4c0 100644 --- a/portspoof_py/cli.py +++ b/portspoof_py/cli.py @@ -42,7 +42,10 @@ def _parse_args(argv=None): p.add_argument('--no-iptables', action='store_true', help='Skip iptables rule setup/teardown') p.add_argument('-v', '--verbose', action='store_true', - help='Print each connection to stdout') + help='Print each connection to stdout') + p.add_argument('--stealth', action='store_true', + help='Send no data on connections — close immediately, ' + 'still logging each connection') p.add_argument('--admin-port', type=int, default=None, metavar='PORT', help='Enable web admin interface on this port (default: disabled)') p.add_argument('--admin-host', default='127.0.0.1', metavar='HOST', @@ -177,7 +180,8 @@ def main(argv=None) -> int: try: bind_ip: Optional[str] = args.bind_ip or None tasks.append(asyncio.create_task( - run_server(cfg, log, bind_ip, args.port, args.verbose, stop_event, settings) + run_server(cfg, log, bind_ip, args.port, args.verbose, stop_event, settings, + stealth=args.stealth) )) if args.admin_port: tasks.append(asyncio.create_task( diff --git a/portspoof_py/server.py b/portspoof_py/server.py index 0f0e280..3179318 100644 --- a/portspoof_py/server.py +++ b/portspoof_py/server.py @@ -38,6 +38,7 @@ async def _handle( log: Logger, verbose: bool, settings: Settings, + stealth: bool = False, ) -> None: sock = writer.get_extra_info('socket') peername = writer.get_extra_info('peername') or ('?.?.?.?', 0) @@ -49,19 +50,23 @@ async def _handle( sockname = writer.get_extra_info('sockname') or ('', 0) original_port = sockname[1] - banner = cfg.get_banner(original_port) + banner = b'' if stealth else cfg.get_banner(original_port) if verbose: - print( - f"[conn] {src_ip}:{src_port} → port {original_port}" - f" banner={banner[:32].hex()}{'…' if len(banner) > 32 else ''}" - ) + if stealth: + print(f"[conn] {src_ip}:{src_port} → port {original_port} banner=(none, stealth)") + else: + print( + f"[conn] {src_ip}:{src_port} → port {original_port}" + f" banner={banner[:32].hex()}{'…' if len(banner) > 32 else ''}" + ) try: - await asyncio.sleep(random.uniform(settings.delay_min, settings.delay_max)) - if banner: - writer.write(banner) - await writer.drain() + if not stealth: + await asyncio.sleep(random.uniform(settings.delay_min, settings.delay_max)) + if banner: + writer.write(banner) + await writer.drain() except (ConnectionResetError, BrokenPipeError): pass finally: @@ -82,11 +87,12 @@ async def run_server( verbose: bool, stop_event: asyncio.Event, settings: Settings, + stealth: bool = False, ) -> None: """Start the asyncio TCP server and run until stop_event is set.""" def handler(r, w): - asyncio.create_task(_handle(r, w, cfg, log, verbose, settings)) + asyncio.create_task(_handle(r, w, cfg, log, verbose, settings, stealth)) server = await asyncio.start_server( handler, @@ -98,6 +104,8 @@ async def run_server( addrs = ', '.join(str(s.getsockname()) for s in server.sockets) print(f"[portspoof] listening on {addrs}") + if stealth: + print("[portspoof] stealth mode: connections closed without sending data") async with server: await stop_event.wait()