Module: PWN::SDR::Decoder::ZigBee

Defined in:
lib/pwn/sdr/decoder/zigbee.rb

Overview

IEEE 802.15.4 O-QPSK (2.4 GHz ZigBee/Thread) true-air decoder.

2 Mchip/s half-sine O-QPSK ≡ MSK, so I/Q → PWN::FFI::Liquid gmskdem (BT=0.5) at 2 Msps → chip stream. Each 4-bit symbol maps to a 32-chip PN sequence (Table 73, IEEE 802.15.4-2011); soft- correlate every 32 chips against the 16 sequences → symbols → nibbles → bytes. Hunt SHR (4×0x00 preamble + SFD 0xA7) → PHR len → MHR (FCF/seq/PAN/addr) → FCS (CRC-16-KERMIT). Emits per-frame src:, dst:, frame_type:, len:, fcs_ok:.

Defined Under Namespace

Classes: DemodIQ

Constant Summary collapse

CHIP_RATE =
2_000_000
PN32 =

16 × 32-chip PN sequences (symbol 0..15). Each row is one 32-bit word; chips are LSB-first (c0 = bit0).

[
  0xD9C3522E, 0xED9C3522, 0x2ED9C352, 0x22ED9C35,
  0x522ED9C3, 0x3522ED9C, 0xC3522ED9, 0x9C3522ED,
  0x8C96077B, 0xB8C96077, 0x7B8C9607, 0x77B8C960,
  0x077B8C96, 0x6077B8C9, 0x96077B8C, 0xC96077B8
].freeze
PN_CHIPS =
PN32.map { |w| Array.new(32) { |i| (w >> i) & 1 } }.freeze
SFD =
0xA7
FRAME_TYPE =
{ 0 => 'Beacon', 1 => 'Data', 2 => 'ACK', 3 => 'MAC-Cmd' }.freeze

Class Method Summary collapse

Class Method Details

.authorsObject

Author(s)

0day Inc. support@0dayinc.com



182
183
184
# File 'lib/pwn/sdr/decoder/zigbee.rb', line 182

public_class_method def self.authors
  "AUTHOR(S):\n  0day Inc. <support@0dayinc.com>\n"
end

.decode(opts = {}) ⇒ Object

Supported Method Parameters

PWN::SDR::Decoder::ZigBee.decode( freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq' )



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/pwn/sdr/decoder/zigbee.rb', line 152

public_class_method def self.decode(opts = {})
  freq_obj = opts[:freq_obj]
  hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
  ch = ((hz - 2_405_000_000) / 5_000_000).round + 11
  rate = (opts[:sample_rate] || freq_obj[:iq_rate] || 4_000_000).to_i
  PWN::SDR::Decoder::Base.run_iq(
    freq_obj: freq_obj,
    protocol: 'ZigBee',
    sample_rate: rate,
    source: opts[:source],
    file: opts[:file],
    demod: DemodIQ.new(rate: rate, channel: ch),
    note: 'O-QPSK 2 Mcps ≡ MSK — I/Q→gmskdem→32-chip PN correlate→SHR/SFD→PHR/MHR/FCS.',
    describe: proc { |b| { modulation: 'O-QPSK', channel: ch, classification: b[:duration_ms] < 5 ? 'ACK' : 'MAC-frame' } }
  )
end

.helpObject



186
187
188
189
190
191
192
193
194
195
196
# File 'lib/pwn/sdr/decoder/zigbee.rb', line 186

public_class_method def self.help
  puts "USAGE (true-air I/Q via PWN::FFI + detector fallback):
    #{self}.decode(
      freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq',
      source:   'optional - :auto|:rtlsdr|:adalm_pluto|:file',
      file:     'optional - .cu8/.cs16 capture (≥4 Msps)'
    )

    #{self}.authors
  "
end

.parse_line(opts = {}) ⇒ Object



169
170
171
172
173
174
175
176
177
178
# File 'lib/pwn/sdr/decoder/zigbee.rb', line 169

public_class_method def self.parse_line(opts = {})
  line = opts[:line].to_s
  out  = { protocol: 'ZigBee' }
  out[:pan] = ::Regexp.last_match(1) if line =~ /PAN[:= ]+([0-9A-Fa-f]+)/i
  out[:src] = ::Regexp.last_match(1) if line =~ /src[:= ]+([0-9A-Fa-f:]+)/i
  out[:dst] = ::Regexp.last_match(1) if line =~ /dst[:= ]+([0-9A-Fa-f:]+)/i
  out[:cmd] = ::Regexp.last_match(1) if line =~ /\b(Beacon|Data|ACK|Cmd)\b/i
  out[:summary] = "ZigBee #{out[:cmd]} PAN=#{out[:pan]} #{out[:src]}#{out[:dst]}"
  out.compact
end