From d8457c57f38abd4bd5584be8e1dc0c3a1062a9e1 Mon Sep 17 00:00:00 2001 From: anti Date: Fri, 10 Apr 2026 16:02:00 -0400 Subject: [PATCH] docs: add OS fingerprint spoofing hardening roadmap --- development/HARDENING.md | 217 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 development/HARDENING.md diff --git a/development/HARDENING.md b/development/HARDENING.md new file mode 100644 index 0000000..8aca7af --- /dev/null +++ b/development/HARDENING.md @@ -0,0 +1,217 @@ +# OS Fingerprint Spoofing — Hardening Roadmap + +This document describes the current state of OS fingerprint spoofing in DECNET +and the planned improvements to make `nmap -O`, `p0f`, and similar passive/active +scanners see the intended OS rather than a generic Linux kernel. + +--- + +## Current State + +OS spoofing is partially implemented. Each archetype declares an `nmap_os` slug +(e.g. `"windows"`, `"linux"`, `"embedded"`). The **composer** resolves that slug +via `os_fingerprint.get_os_sysctls()` and injects the resulting kernel parameters +into the **base container** as Docker `sysctls`. Service containers inherit the +same network namespace via `network_mode: "service:"` and therefore appear +identical to outside scanners. + +### Currently tuned knobs + +| Sysctl | Purpose | +|---|---| +| `net.ipv4.ip_default_ttl` | Primary TTL discriminator (64 = Linux, 128 = Windows, 255 = Embedded) | +| `net.ipv4.tcp_syn_retries` | SYN retransmit count before giving up | + +### What this fools + +| Scanner probe | Status | +|---|---| +| ping TTL | ✅ Fully spoofed | +| TCP SYN retry count | ✅ Tuned | +| `nmap -O` OS family (Win vs Linux) | ⚠️ Partial — likely correct family, wrong version | +| `p0f` passive fingerprint | ⚠️ Partial — TTL correct, window/options wrong | +| Full `nmap -O` version/build match | ❌ Not achievable without deeper tuning | + +--- + +## Improvement Phases + +### Phase 1 — Extended Sysctls (Low effort, High impact) + +Several additional sysctls are **network-namespace-scoped** and can be safely set +per-container without `--privileged`. These directly affect nmap's SEQ, OPS, and +WIN probe groups. + +**Changes required:** extend `OS_SYSCTLS` in `decnet/os_fingerprint.py`. + +| Sysctl | nmap probe group | Windows | Linux | Embedded | +|---|---|---|---|---| +| `net.ipv4.tcp_timestamps` | SEQ/OPS — timestamp option presence | `0` | `1` | `0` | +| `net.ipv4.tcp_window_scaling` | WIN — window scale option | `1` | `1` | `0` | +| `net.ipv4.tcp_sack` | OPS — SACK permitted option | `1` | `1` | `0` | +| `net.ipv4.tcp_ecn` | ECN probe — explicit congestion notification | `0` | `2` | `0` | +| `net.ipv4.ip_no_pmtu_disc` | IE — DF bit copying in ICMP replies | `0` | `0` | `1` | +| `net.ipv4.tcp_fin_timeout` | T2–T6 — FIN_WAIT duration | `30` | `60` | `15` | + +> **Highest single-value impact:** setting `net.ipv4.tcp_timestamps = 0` for +> Windows is the strongest signal. nmap's OPS probes explicitly look for the TCP +> timestamp option; its absence is a definitive Windows discriminator. + +**Expected result after Phase 1:** `nmap -O` correctly identifies OS family in +the vast majority of scans. `p0f` passive fingerprinting becomes significantly +more convincing. + +--- + +### Phase 2 — TCP Window Size Mangling (Medium effort, Very high impact) + +nmap's WIN probes record the raw **TCP window size** in SYN-ACK replies. This +is the single most discriminating feature after TTL. It cannot be set with +per-namespace sysctls because `net.core.rmem_default` is global. + +The fix is an **iptables rule applied at base container startup** via a custom +entrypoint script. + +#### Target window sizes by OS + +| OS | TCP Window Size | Notes | +|---|---|---| +| Windows 10 / 11 | `64240` | Most common modern value | +| Windows 7 / Server 2008 | `8192` | Classic Windows signature | +| Linux 5.x / 6.x | `29200` | Default `tcp_rmem` min/4 | +| Linux 4.x | `43690` | Older default | +| FreeBSD / macOS | `65535` | BSD signature | +| Embedded / Cisco | `4128`–`8760` | Varies widely | + +#### Implementation sketch + +Add a parameterized entrypoint script (`templates/base/entrypoint.sh`) that +receives the target window size as an environment variable and applies an +`iptables` MANGLE rule before yielding to `sleep infinity`: + +```bash +#!/bin/sh +# Apply TCP window size spoofing via iptables mangle +if [ -n "$SPOOF_TCP_WINDOW" ]; then + iptables -t mangle -A POSTROUTING -p tcp \ + -j TCPMSS --set-mss 1460 + # Clamp outgoing window to the target value + # Requires xt_TCPMSS kernel module on the host +fi +exec sleep infinity +``` + +The composer would inject `SPOOF_TCP_WINDOW` as an environment variable on the +base container, sourced from the OS fingerprint profile. + +**Required changes:** +- `os_fingerprint.py` — add `tcp_window` field to each OS profile. +- `composer.py` — pass `SPOOF_TCP_WINDOW` env var to base container. +- `templates/base/entrypoint.sh` — new file, applies the iptables rule. +- `templates/base/Dockerfile` — new file, minimal image with `iptables`. + +> **Note:** requires `NET_ADMIN` capability (already granted) and the +> `xt_TCPMSS` and `xt_mangle` kernel modules loaded on the host. Both are +> present in any standard Linux distribution kernel. + +--- + +### Phase 3 — ICMP Response Tuning (Medium effort, Medium impact) + +nmap's `IE` probe group sends two ICMP echo requests with specific ToS values, +code fields, and payload sizes and inspects what the target returns. Currently +nothing in DECNET controls ICMP echo reply behavior. + +**Namespace-scoped sysctls to add per-OS:** + +| Sysctl | Effect | Windows | Linux | +|---|---|---|---| +| `net.ipv4.icmp_ratelimit` | Packets/sec rate limit on ICMP errors | `0` (none) | `100` | +| `net.ipv4.icmp_ratemask` | Which ICMP types are rate-limited | `0` | `6168` | + +**Expected result:** nmap's `IE` response classification improves from +"no response / filtered" to a correctly typed ICMP echo reply with OS-correct +rate limiting behavior. + +--- + +### Phase 4 — IP ID Sequence Behavior (Hard, Medium impact) + +nmap's SEQ probe group fires 6 TCP SYN packets in rapid succession and measures +the **IP ID increment pattern** across responses: + +| OS | IP ID pattern | nmap label | +|---|---|---| +| Windows (most) | Sequential, incrementing | `I` (incremental) | +| Linux 3.x+ | Per-socket hashed/random | `RI` or `RD` | +| Old Linux / BSD | Global counter (truly sequential) | `I` | +| Embedded | Often constant 0 or sequential | varies | + +Linux switched to per-socket hashed IDs at the kernel level (~3.x). This +**cannot be changed per network namespace** without patching the kernel or +replacing the TCP/IP stack with a userspace implementation. + +**Options:** +1. **Accept the limitation** — the IP ID pattern is one of many signals; getting + TTL + window + timestamps right is already a very strong fingerprint match. +2. **Userspace TCP proxy** (e.g., `lwIP` or a custom `nfqueue`-based responder) + that intercepts SYN packets and replies with forged ID sequences. High + complexity; requires `NFQUEUE` kernel module and `libnetfilter_queue`. + +> Phase 4 is **not recommended** for the near term. The complexity-to-realism +> ratio is poor compared to Phases 1–3. + +--- + +## Implementation Priority + +``` +Phase 1 ────────────────────────────────── (implement next) + └─ 5 new sysctls in os_fingerprint.py + └─ No new files, no Docker changes + └─ Estimated effort: 30 min + +Phase 2 ────────────────────────────────── (implement after Phase 1) + └─ templates/base/Dockerfile + entrypoint.sh + └─ os_fingerprint.py: add tcp_window field + └─ composer.py: pass env var to base container + └─ Estimated effort: 2–3 hours + tests + +Phase 3 ────────────────────────────────── (nice to have) + └─ 2 more sysctls in os_fingerprint.py + └─ Estimated effort: 15 min (after Phase 1 infra exists) + +Phase 4 ────────────────────────────────── (not recommended short-term) + └─ Requires kernel-level or userspace TCP stack work + └─ Estimated effort: days +``` + +--- + +## Testing Strategy + +After each phase, validate with: + +```bash +# Active OS fingerprint scan against a deployed decky +sudo nmap -O --osscan-guess + +# Passive fingerprinting (run on host while generating traffic to decky) +sudo p0f -i -p + +# Quick TTL + window check +sudo nmap -sS --script banner +hping3 -S -p 22 # inspect TTL and window in reply +``` + +Expected outcomes by phase: + +| Check | Pre-Phase 1 | Post-Phase 1 | Post-Phase 2 | +|---|---|---|---| +| TTL | ✅ | ✅ | ✅ | +| TCP timestamps | ❌ | ✅ | ✅ | +| TCP window size | ❌ | ❌ | ✅ | +| ICMP behavior | ❌ | ⚠️ | ⚠️ | +| IP ID sequence | ❌ | ❌ | ❌ | +| `nmap -O` family match | ⚠️ | ✅ | ✅ | +| `p0f` match | ⚠️ | ⚠️ | ✅ |