Dear author,
Excellent analysis and a very valuable contribution to the community. Your exposition on the weaknesses of “all ports open” deception (fake banners, static responses) caught my attention and motivated me to prepare a practical technical contribution to complement the tool. The goal is to provide mechanisms so that findings are verifiable, resilient against banner-based traps, and manageable by evidence management infrastructures.
Below I present the full technical proposal, including example code, detailed line-by-line notes, and a signed report template that can be integrated into the tool’s pipeline.
Contribution Summary
Integrity and non-repudiation of reports: sign every report with ECDSA P-256 managed by a PKCS#11/HSM module; use canonical JSON serialization for deterministic signing.
Stateful protocol probes: complete handshakes and validate protocol states (TLS handshake + valid HTTP request; SSH banner + KEXINIT detection; valid commands for SMTP/FTP/Redis; UDP probes with payload validation).
Evidence format: canonical JSON schema for signed reports and clear result codes (real_service, probable_decoy, inconclusive).
Operational playbook: checklist for authorized tests, HSM usage, trace recording, and secure delivery of evidence.
1) Report Integrity — Technical Principles
Use ECDSA with curve P-256 and SHA-256 to sign reports. JSON serialization must be deterministic (sorted keys, compact separators) to ensure reproducibility of the signature.
Store the private signing key inside an HSM or PKCS#11 module; the key must never leave the hardware. The HSM is responsible for executing the signing operation.
Each report should include: metadata (target, timestamps in UTC), list of probes with detailed results, executive summary, and Base64 signature together with signer identifier.
For compatibility with enterprise infrastructures, the report can be encapsulated in a CMS container if required; the signed JSON should preserve a manifest with verified hashes of artifacts (pcap, logs).
2) Heuristics and Recommended Probes
2.1 Principle
Validate protocol behavior: the main criterion is not the banner but whether the entity completes the expected protocol flow.
2.2 Probes and Checks
HTTPS/TLS (e.g., port 443):
Complete TLS handshake (including SNI and ALPN where applicable).
If handshake succeeds, send GET / with a real User-Agent and parse status code and dynamic headers.
If handshake fails but a banner exists, mark as inconsistent → probable_decoy.
SSH (e.g., port 22):
Read SSH-2.0-... banner.
Send client identification string according to RFC4253.
Detect KEXINIT binary bytes (0x14) or non-printable byte ratios to confirm the server progresses in the key exchange.
If only ASCII banner and no binary progression, mark as probable_decoy.
UDP (DNS, Redis, etc.):
Send valid payloads for the service and check consistency across responses (e.g., Redis PING → PONG).
Repeat probes with slight variations to validate response stability.
2.3 Result Codes
real_service: completed handshake + at least 2 consistent probes.
probable_decoy: inconsistent banner, failed handshake, or response to only one probe.
inconclusive: timeouts or network issues — repeat the scan.
3) Example Code — PKCS#11 Signing, HTTPS Probe, SSH Probe, JSON Schema and Report Example
All scripts include the author’s signature in the file headers.
A) PKCS#11 Signing Example
file: sign_report_pkcs11.py
Author: Antonio José Socorro Marín © 2025
Usage: python sign_report_pkcs11.py report.json /lib/pkcs11.so token_label key_label PIN
import json, sys, base64
from datetime import datetime
def canonicalize(jsobj):
return json.dumps(jsobj, sort_keys=True, separators=(',',':'), ensure_ascii=False).encode('utf-8')
def sign_with_pkcs11(report_obj, pkcs11_lib_path, token_label, key_label, pin):
from pkcs11 import Lib, Mechanism
lib = Lib(pkcs11_lib_path)
with lib.get_token(token_label=token_label).open(user_pin=pin) as session:
priv = session.get_key(label=key_label, key_type='EC', object_class='PRIVATE_KEY')
data = canonicalize(report_obj)
signature = priv.sign(data, mechanism=Mechanism.ECDSA_SHA256)
return base64.b64encode(signature).decode('ascii')
def main():
if len(sys.argv) < 6:
print("Usage: sign_report_pkcs11.py report.json /lib/pkcs11.so token_label key_label PIN")
sys.exit(1)
report_path, libpath, token_label, key_label, pin = sys.argv[1:6]
report = json.load(open(report_path, "r", encoding="utf-8"))
sig = sign_with_pkcs11(report, libpath, token_label, key_label, pin)
out = {"report": report, "signature_b64": sig, "signed_at": datetime.utcnow().isoformat()+"Z", "signer": "CN="+key_label}
print(json.dumps(out, ensure_ascii=False, indent=2))
if name == "main":
main()
B) HTTPS / TLS Probe
file: probe_https.py
Author: Antonio José Socorro Marín © 2025
import socket, ssl, json, time
def probe_https(host, port=443, server_name=None, timeout=5.0):
result = {
"host": host,
"port": port,
"timestamp": time.time(),
"tls_handshake": False,
"tls_error": None,
"http_response_code": None,
"http_headers": None,
"notes": []
}
server_name = server_name or host
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
try:
sock.connect((host, port))
except Exception as e:
result["tls_error"] = f"tcp_connect_failed: {e}"
return result
context = ssl.create_default_context()
try:
ssock = context.wrap_socket(sock, server_hostname=server_name)
result["tls_handshake"] = True
except Exception as e:
result["tls_error"] = str(e)
return result
try:
http_req = ("GET / HTTP/1.1\r\n"
f"Host: {host}\r\n"
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)\r\n"
"Connection: close\r\n\r\n")
ssock.sendall(http_req.encode('utf-8'))
data = ssock.recv(65536)
if data:
header_text = data.decode('iso-8859-1').split("\r\n\r\n",1)[0]
status_line = header_text.splitlines()[0]
parts = status_line.split()
if len(parts) >= 2:
result["http_response_code"] = int(parts[1])
except Exception as e:
result["notes"].append(f"http_request_error: {e}")
finally:
ssock.close()
return result
C) SSH Stateful Probe
file: probe_ssh_stateful.py
Author: Antonio José Socorro Marín © 2025
import socket, time
def probe_ssh_stateful(host, port=22, timeout=5.0):
r = {"host": host, "port": port, "timestamp": time.time(), "banner": None,
"kex_received": False, "details": []}
try:
s = socket.create_connection((host, port), timeout=timeout)
except Exception as e:
r["details"].append(f"tcp_connect_failed: {e}")
return r
try:
data = b""
s.settimeout(2.0)
while b'\n' not in data:
piece = s.recv(1024)
if not piece:
break
data += piece
if len(data) > 4096:
break
banner_line = data.splitlines()[0] if data else b""
r["banner"] = banner_line.decode('ascii', errors='ignore').strip()
client_id = b"SSH-2.0-OpenSSH_9.0-AntonioProbe\r\n"
s.sendall(client_id)
s.settimeout(3.0)
more = s.recv(4096)
if more:
if b'\x14' in more[:64]:
r["kex_received"] = True
r["details"].append("kexinit_detected_binary")
else:
nonprint_ratio = sum(1 for b in more[:64] if b < 32 or b > 126) / max(1, min(64,len(more)))
if nonprint_ratio > 0.2:
r["kex_received"] = True
r["details"].append(f"binary_noise_ratio={nonprint_ratio:.2f}")
else:
r["details"].append("only_ascii_after_banner; likely decoy")
else:
r["details"].append("no_response_after_client_id")
except Exception as e:
r["details"].append(f"probe_error: {e}")
finally:
s.close()
return r
D) ECDSA Signing in Memory (testing only)
file: sign_report_ec.py
Author: Antonio José Socorro Marín © 2025
import json, base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
def sign_report_ec_p256(report_dict, private_key_pem):
canonical = json.dumps(report_dict, sort_keys=True, separators=(',',':')).encode('utf-8')
private_key = serialization.load_pem_private_key(private_key_pem, password=None)
signature = private_key.sign(canonical, ec.ECDSA(hashes.SHA256()))
sig_b64 = base64.b64encode(signature).decode('ascii')
return {'report': report_dict, 'signature_b64': sig_b64}
E) JSON Schema for Signed Reports
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DecoyHunter Signed Report",
"type": "object",
"required": ["report", "signature_b64", "signed_at", "signer"],
"properties": {
"report": {
"type": "object",
"required": ["target", "scans", "summary"],
"properties": {
"target": {"type":"string"},
"scans": {
"type":"array",
"items": {
"type":"object",
"required":["port","protocol","timestamp","result_code"],
"properties":{
"port":{"type":"integer"},
"protocol":{"type":"string"},
"timestamp":{"type":"string","format":"date-time"},
"result_code":{"type":"string"},
"details":{"type":"object"}
}
}
},
"summary":{"type":"object"}
}
},
"signature_b64":{"type":"string"},
"signed_at":{"type":"string","format":"date-time"},
"signer":{"type":"string"}
}
}
F) Example Signed Report
{
"report": {
"target": "192.168.1.10",
"scans": [
{
"port": 22,
"protocol": "ssh",
"timestamp": "2025-09-28T12:34:56Z",
"result_code": "real_service",
"details": {
"banner": "SSH-2.0-OpenSSH_8.9p1",
"kex_received": true
}
},
{
"port": 8080,
"protocol": "http",
"timestamp": "2025-09-28T12:35:13Z",
"result_code": "probable_decoy",
"details": {
"banner": "SSH-2.0-OpenSSH_8.9p1",
"http_response": null,
"note": "SSH banner on HTTP port; inconsistency detected"
}
}
],
"summary": {
"real_services": 1,
"probable_decoys": 1
}
},
"signature_b64": "BASE64_SIGNATURE_PLACEHOLDER",
"signed_at": "2025-09-28T12:36:00Z",
"signer": "CN=Antonio_Socorro_ScanKey"
}
4) Operational Playbook (Checklist)
Authorization: written authorization and clear scope (targets, windows, allowed IPs).
Key generation: ECDSA P-256 keys generated inside HSM; publish only the public certificate.
Initial reconnaissance: map open ports with low concurrency and human-like delays.
Stateful probes: TLS (handshake + GET), SSH (banner + KEX detection), SMTP/FTP/Redis (EHLO/USER/PING), UDP payload-based probes.
Recording: store pcap traces, session logs, and canonical JSON report.
Signing: sign report with HSM/PKCS#11; include signer identifier and UTC timestamp.
Delivery: provide signed report and hash manifest to authorized recipients.
Key rotation: documented policy for key rotation and revocation.
Metrics: in controlled tests, generate ROC metrics (false positives/negatives) to calibrate heuristics.
5) Legal and Ethical Considerations
Any probes interacting with real protocols must have explicit authorization.
Avoid unauthorized authentication attempts, data modification, or exploitation. Probes must be limited to protocol validation.
Maintain clear evidence custody procedures and retention policies.
Conclusion
I greatly appreciate the quality of the original analysis. The proposal included here aims to provide verifiable evidence, reduce ambiguity in interpreting banners, and facilitate integration of results into management infrastructures (PKI/CMS/IDMS).
The code presented is a starting point: it includes operational examples for signing and validating results, and probes that complete real protocol flows rather than banners alone.
Incorporating this approach into the tool’s pipeline would make reports audit-ready and significantly reduce the ability of banner-based deception to hide real services.
Top comments (0)