Skip to content

Commit 06ebb22

Browse files
committed
Land rapid7#8065, Zigbee Hardware Bridge Extension
2 parents 7bcd53d + 095a110 commit 06ebb22

File tree

10 files changed

+884
-1
lines changed

10 files changed

+884
-1
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
Actively scans the Zigbee channels by sending a beacon broadcast packet and listening for responses.
2+
3+
## Options
4+
5+
**DEVICE**
6+
7+
ZigBee Device ID. Defaults to the target device that is specified via the target command or if
8+
one device is presented when running 'supported_devices' it will use that device.
9+
10+
**CHANNEL**
11+
12+
The channel to scan. Setting this options will prevent the stumbler from changing channels. Range is 11-26, inclusive. Default: not set
13+
n
14+
**LOOP**
15+
16+
How many times to loop over the channels. Specifying a -1 will loop forever. Default: 1
17+
18+
**DELAY**
19+
20+
The delay in seconds to listen to each channel. Default: 2
21+
22+
## Scenarios
23+
24+
Scanning channel 11 for other ZigBee devices in the area.
25+
26+
```
27+
hwbridge > run post/hardware/zigbee/zstumbler channel=11
28+
29+
[*] Scanning Channel 11
30+
[*] New Network: PANID: 0x4724 SOURCE: 0x25D5
31+
[*] Ext PANID: 6E:03:C7:74:31:E2:74:AA Stack Profile: ZigBee Enterprise
32+
[*] Stack Version: ZigBee 2006/2007
33+
[*] Channel: 11
34+
[*] New Network: PANID: 0x4724 SOURCE: 0x7DD1
35+
[*] Ext PANID: 6E:03:C7:74:31:E2:74:AA Stack Profile: ZigBee Enterprise
36+
[*] Stack Version: ZigBee 2006/2007
37+
[*] Channel: 11
38+
```

lib/msf/base/sessions/hwbridge.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,16 @@ def load_automotive
157157
console.disable_output = original
158158
end
159159

160+
#
161+
# Loads the zigbee extension
162+
#
163+
def load_zigbee
164+
original = console.disable_output
165+
console.disable_output = true
166+
console.run_single('load zigbee')
167+
console.disable_output = original
168+
end
169+
160170
#
161171
# Load custom methods provided by the hardware
162172
#

lib/msf/core/post/hardware.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# -*- coding: binary -*-
22
module Msf::Post::Hardware
33
require 'msf/core/post/hardware/automotive/uds'
4+
require 'msf/core/post/hardware/zigbee/utils'
45
end
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
# -*- coding: binary -*-
2+
module Msf
3+
class Post
4+
module Hardware
5+
module Zigbee
6+
7+
module Utils
8+
9+
## Constants for packet decoding fields
10+
# Frame Control Field
11+
DOT154_FCF_TYPE_MASK = 0x0007 #: Frame type mask
12+
DOT154_FCF_SEC_EN = 0x0008 #: Set for encrypted payload
13+
DOT154_FCF_FRAME_PND = 0x0010 #: Frame pending
14+
DOT154_FCF_ACK_REQ = 0x0020 #: ACK request
15+
DOT154_FCF_INTRA_PAN = 0x0040 #: Intra-PAN activity
16+
DOT154_FCF_DADDR_MASK = 0x0C00 #: Destination addressing mode mask
17+
DOT154_FCF_VERSION_MASK = 0x3000 #: Frame version
18+
DOT154_FCF_SADDR_MASK = 0xC000 #: Source addressing mask mode
19+
20+
# Frame Control Field Bit Shifts
21+
DOT154_FCF_TYPE_MASK_SHIFT = 0 #: Frame type mask mode shift
22+
DOT154_FCF_DADDR_MASK_SHIFT = 10 #: Destination addressing mode mask
23+
DOT154_FCF_VERSION_MASK_SHIFT = 12 #: Frame versions mask mode shift
24+
DOT154_FCF_SADDR_MASK_SHIFT = 14 #: Source addressing mask mode shift
25+
26+
# Address Mode Definitions
27+
DOT154_FCF_ADDR_NONE = 0x0000 #: Not sure when this is used
28+
DOT154_FCF_ADDR_SHORT = 0x0002 #: 4-byte addressing
29+
DOT154_FCF_ADDR_EXT = 0x0003 #: 8-byte addressing
30+
31+
DOT154_FCF_TYPE_BEACON = 0 #: Beacon frame
32+
DOT154_FCF_TYPE_DATA = 1 #: Data frame
33+
DOT154_FCF_TYPE_ACK = 2 #: Acknowledgement frame
34+
DOT154_FCF_TYPE_MACCMD = 3 #: MAC Command frame
35+
36+
DOT154_CRYPT_NONE = 0x00 #: No encryption, no MIC
37+
DOT154_CRYPT_MIC32 = 0x01 #: No encryption, 32-bit MIC
38+
DOT154_CRYPT_MIC64 = 0x02 #: No encryption, 64-bit MIC
39+
DOT154_CRYPT_MIC128 = 0x03 #: No encryption, 128-bit MIC
40+
DOT154_CRYPT_ENC = 0x04 #: Encryption, no MIC
41+
DOT154_CRYPT_ENC_MIC32 = 0x05 #: Encryption, 32-bit MIC
42+
DOT154_CRYPT_ENC_MIC64 = 0x06 #: Encryption, 64-bit MIC
43+
DOT154_CRYPT_ENC_MIC128 = 0x07 #: Encryption, 128-bit MIC
44+
45+
# Infer if the current session is for a ZigBee device.
46+
# @return [Boolean] true if session is for a ZigBee device, false otherwise
47+
def is_zigbee_hwbridge_session?
48+
return true if client.zigbee
49+
print_error("Not a ZigBee hwbridge session")
50+
false
51+
end
52+
53+
# Verify if a device has been specified.
54+
# @return [Boolean] true if device is specified, false otherwise
55+
def verify_device(device)
56+
return true if device
57+
print_line("No target device set, use 'target' or specify bus via the options.")
58+
false
59+
end
60+
61+
# Retrieves the target Zigbee device. This is typically set by the user via the
62+
# interactive HWBridge command line
63+
# @return [String] Zigbee device ID
64+
def get_target_device
65+
return unless is_zigbee_hwbridge_session?
66+
return client.zigbee.get_target_device
67+
end
68+
69+
# Sets the target default Zigbee Device. This command typically isn't called via a script
70+
# Instead the user is expected to set this via the interactive HWBridge commandline
71+
# @param device [String] Zigbee device ID
72+
def set_target_device(device)
73+
return unless is_zigbee_hwbridge_session?
74+
client.zigbee.set_target_device device
75+
end
76+
77+
# Sets the Zigbee Channel
78+
# @param device [String] Zigbee device ID
79+
# @param channel [Integer] Channel number, typically 11-25
80+
def set_channel(device, channel)
81+
return {} unless is_zigbee_hwbridge_session?
82+
device = client.zigbee.target_device unless device
83+
return {} unless verify_device(device)
84+
client.zigbee.set_channel(device, channel)
85+
end
86+
87+
# Inject raw packets. Need firmware on the zigbee device that supports transmission.
88+
# @param device [String] Zigbee device ID
89+
# @param data [String] Raw binary data sent as a string
90+
def inject(device, data)
91+
return {} unless is_zigbee_hwbridge_session?
92+
device = client.zigbee.target_device unless device
93+
return {} unless verify_device(device)
94+
client.zigbee.inject(device, data)
95+
end
96+
97+
# Recieves data from the Zigbee device
98+
# @param device [String] Zigbee device ID
99+
# @return [String] Binary blob of returned data
100+
def recv(device)
101+
return {} unless is_zigbee_hwbridge_session?
102+
device = client.zigbee.target_device unless device
103+
return {} unless verify_device(device)
104+
client.zigbee.recv(device)
105+
end
106+
107+
# Turn off Zigbee receiving
108+
# @param device [String] Zigbee device ID
109+
def sniffer_off(device)
110+
return {} unless is_zigbee_hwbridge_session?
111+
device = client.zigbee.target_device unless device
112+
return {} unless verify_device(device)
113+
client.zigbee.sniffer_off(device)
114+
end
115+
116+
# Turn on Zigbee receiving
117+
# @param device [String] Zigbee device ID
118+
def sniffer_on(device)
119+
return {} unless is_zigbee_hwbridge_session?
120+
device = client.zigbee.target_device unless device
121+
return {} unless verify_device(device)
122+
client.zigbee.sniffer_on(device)
123+
end
124+
125+
# Breaks up the packet into different sections. Also provides
126+
# Some decoding information. This method relates to Killerbee's Pktchop method and
127+
# Returns a similar array structure PktChop. If it's a beacon data you will also have
128+
# A BEACONDATA array of raw beacon related packets. You can pull other decoded portions from
129+
# the returned hash such as
130+
# FSF
131+
# SEQ
132+
# SPAN_ID
133+
# SOURCE
134+
# SUPERFRAME
135+
# GTS
136+
# PENDING_ADDRESS_COUNT
137+
# PROTOCOL_ID
138+
# STACK_PROFILE
139+
# CAPABILITY
140+
# EXT_PAN_ID
141+
# TX_OFFSET
142+
# UPDATE_ID
143+
# @param packet [String] Raw data from recv
144+
# @return [Hash] { PktChop => [Array of data], ..
145+
def dot154_packet_decode(packet)
146+
result = {}
147+
offset = 0
148+
pktchop = ['', '', '', '', '', '', [], '']
149+
pktchop[0] = packet[0,2]
150+
# Sequence number
151+
pktchop[1] = packet[2]
152+
# Byte swap
153+
fcf = pktchop[0].reverse.unpack("H*")[0].hex
154+
result["FSF"] = fcf
155+
result["SEQ"] = pktchop[1]
156+
# Check if we are dealing with a beacon frame
157+
if (fcf & DOT154_FCF_TYPE_MASK) == DOT154_FCF_TYPE_BEACON
158+
beacondata = ["", "", "", "", "", "", "", "", "", ""]
159+
# 802.15.4 fields, SPAN and SA
160+
pktchop[4] = packet[3,2]
161+
pktchop[5] = packet[5,2]
162+
result["SPAN_ID"] = pktchop[4].reverse.unpack("H*")[0]
163+
result["SOURCE"] = pktchop[5].reverse.unpack("H*")[0]
164+
offset = 7
165+
166+
# Superframe specification
167+
beacondata[0] = packet[offset,2]
168+
result["SUPERFRAME"] = beacondata[0]
169+
offset+=2
170+
171+
# GTS data
172+
beacondata[1] = packet[offset]
173+
result["GTS"] = beacondata[1]
174+
offset+=1
175+
176+
# Pending address count
177+
beacondata[2] = packet[offset]
178+
result["PENDING_ADDRESS_COUNT"] = beacondata[2]
179+
offset+=1
180+
181+
# Protocol ID
182+
beacondata[3] = packet[offset]
183+
result["PROTOCOL_ID"] = beacondata[3]
184+
offset+=1
185+
186+
# Stack Profile version
187+
beacondata[4] = packet[offset]
188+
result["STACK_PROFILE"] = beacondata[4]
189+
offset+=1
190+
191+
# Capability information
192+
beacondata[5] = packet[offset]
193+
result["CAPABILITY"] = beacondata[5]
194+
offset+=1
195+
196+
# Extended PAN ID
197+
beacondata[6] = packet[offset,8]
198+
result["EXT_PAN_ID"] = beacondata[6].reverse.unpack("H*")[0]
199+
offset+=8
200+
201+
# TX Offset
202+
beacondata[7] = packet[offset,3]
203+
result["TX_OFFSET"] = beacondata[7]
204+
offset+=3
205+
206+
# Update ID
207+
beacondata[8] = packet[offset]
208+
result["UPDATE_ID"] = beacondata[8]
209+
offset+=1
210+
pktchop[6] = beacondata
211+
result["BEACONDATA"] = beacondata
212+
else
213+
# Not a beacon frame
214+
215+
# DPAN
216+
pktchop[2] = packet[3,2]
217+
offset = 5
218+
219+
# Examine the destination addressing mode
220+
daddr_mask = (fcf & DOT154_FCF_DADDR_MASK) >> 10
221+
if daddr_mask == DOT154_FCF_ADDR_EXT
222+
pktchop[3] = packet[offset,8]
223+
offset+=8
224+
elsif daddr_mask == DOT154_FCF_ADDR_SHORT
225+
pktchop[3] = packet[offset,2]
226+
offset+=2
227+
end
228+
229+
# Examine the Intra-PAN flag
230+
if (fcf & DOT154_FCF_INTRA_PAN) == 0
231+
pktchop[4] = packet[offset,2]
232+
offset+=2
233+
end
234+
235+
# Examine the source addressing mode
236+
saddr_mask = (fcf & DOT154_FCF_SADDR_MASK) >> 14
237+
if daddr_mask == DOT154_FCF_ADDR_EXT
238+
pktchop[5] = packet[offset,8]
239+
offset+=8
240+
elsif daddr_mask == DOT154_FCF_ADDR_SHORT
241+
pktchop[5] = packet[offset,2]
242+
offset+=2
243+
end
244+
end
245+
# Append remaining payload
246+
pktchop[7] = packet[offset,packet.size] if offset < packet.size
247+
result["PktChop"] = pktchop
248+
return result
249+
end
250+
end
251+
252+
end
253+
end
254+
end
255+
end

0 commit comments

Comments
 (0)