feat: DECNET-PROBER standalone JARM fingerprinting service

Add active TLS probing via JARM to identify C2 frameworks (Cobalt Strike,
Sliver, Metasploit) by their TLS server implementation quirks. Runs as a
detached host-level process — no container dependency.

- decnet/prober/jarm.py: pure-stdlib JARM implementation (10 crafted probes)
- decnet/prober/worker.py: standalone async worker with RFC 5424 + JSON output
- CLI: `decnet probe --targets ip:port` and `--probe-targets` on deploy
- Ingester: JARM bounty extraction (fingerprint type)
- 68 new tests covering JARM logic and bounty extraction
This commit is contained in:
2026-04-14 12:14:32 -04:00
parent df3f04c10e
commit ce2699455b
7 changed files with 1210 additions and 0 deletions

View File

@@ -120,6 +120,8 @@ def deploy(
config_file: Optional[str] = typer.Option(None, "--config", "-c", help="Path to INI config file"),
api: bool = typer.Option(False, "--api", help="Start the FastAPI backend to ingest and serve logs"),
api_port: int = typer.Option(8000, "--api-port", help="Port for the backend API"),
probe_targets: Optional[str] = typer.Option(None, "--probe-targets", help="Comma-separated ip:port pairs for JARM active probing (e.g. 10.0.0.1:443,10.0.0.2:8443)"),
probe_interval: int = typer.Option(300, "--probe-interval", help="Seconds between JARM probe cycles (default: 300)"),
) -> None:
"""Deploy deckies to the LAN."""
import os
@@ -296,6 +298,43 @@ def deploy(
except (FileNotFoundError, subprocess.SubprocessError):
console.print("[red]Failed to start API. Ensure 'uvicorn' is installed in the current environment.[/]")
if probe_targets and not dry_run:
import subprocess # nosec B404
import sys
console.print(f"[bold cyan]Starting DECNET-PROBER[/] → targets: {probe_targets}")
try:
_prober_args = [
sys.executable, "-m", "decnet.cli", "probe",
"--targets", probe_targets,
"--interval", str(probe_interval),
]
if effective_log_file:
_prober_args.extend(["--log-file", str(effective_log_file)])
subprocess.Popen( # nosec B603
_prober_args,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT,
start_new_session=True,
)
except (FileNotFoundError, subprocess.SubprocessError):
console.print("[red]Failed to start DECNET-PROBER.[/]")
@app.command()
def probe(
targets: str = typer.Option(..., "--targets", "-t", help="Comma-separated ip:port pairs to JARM fingerprint"),
log_file: str = typer.Option(DECNET_INGEST_LOG_FILE, "--log-file", "-f", help="Path for RFC 5424 syslog + .json output"),
interval: int = typer.Option(300, "--interval", "-i", help="Seconds between probe cycles (default: 300)"),
timeout: float = typer.Option(5.0, "--timeout", help="Per-probe TCP timeout in seconds"),
) -> None:
"""Run JARM active fingerprinting against target hosts."""
import asyncio
from decnet.prober import prober_worker
log.info("probe command invoked targets=%s interval=%d", targets, interval)
console.print(f"[bold cyan]DECNET-PROBER starting[/] → {targets}")
asyncio.run(prober_worker(log_file, targets, interval=interval, timeout=timeout))
@app.command()
def collect(