Land #8065, Zigbee Hardware Bridge Extension
commit
06ebb22a8f
|
@ -0,0 +1,38 @@
|
|||
Actively scans the Zigbee channels by sending a beacon broadcast packet and listening for responses.
|
||||
|
||||
## Options
|
||||
|
||||
**DEVICE**
|
||||
|
||||
ZigBee Device ID. Defaults to the target device that is specified via the target command or if
|
||||
one device is presented when running 'supported_devices' it will use that device.
|
||||
|
||||
**CHANNEL**
|
||||
|
||||
The channel to scan. Setting this options will prevent the stumbler from changing channels. Range is 11-26, inclusive. Default: not set
|
||||
n
|
||||
**LOOP**
|
||||
|
||||
How many times to loop over the channels. Specifying a -1 will loop forever. Default: 1
|
||||
|
||||
**DELAY**
|
||||
|
||||
The delay in seconds to listen to each channel. Default: 2
|
||||
|
||||
## Scenarios
|
||||
|
||||
Scanning channel 11 for other ZigBee devices in the area.
|
||||
|
||||
```
|
||||
hwbridge > run post/hardware/zigbee/zstumbler channel=11
|
||||
|
||||
[*] Scanning Channel 11
|
||||
[*] New Network: PANID: 0x4724 SOURCE: 0x25D5
|
||||
[*] Ext PANID: 6E:03:C7:74:31:E2:74:AA Stack Profile: ZigBee Enterprise
|
||||
[*] Stack Version: ZigBee 2006/2007
|
||||
[*] Channel: 11
|
||||
[*] New Network: PANID: 0x4724 SOURCE: 0x7DD1
|
||||
[*] Ext PANID: 6E:03:C7:74:31:E2:74:AA Stack Profile: ZigBee Enterprise
|
||||
[*] Stack Version: ZigBee 2006/2007
|
||||
[*] Channel: 11
|
||||
```
|
|
@ -157,6 +157,16 @@ class HWBridge < Rex::Post::HWBridge::Client
|
|||
console.disable_output = original
|
||||
end
|
||||
|
||||
#
|
||||
# Loads the zigbee extension
|
||||
#
|
||||
def load_zigbee
|
||||
original = console.disable_output
|
||||
console.disable_output = true
|
||||
console.run_single('load zigbee')
|
||||
console.disable_output = original
|
||||
end
|
||||
|
||||
#
|
||||
# Load custom methods provided by the hardware
|
||||
#
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# -*- coding: binary -*-
|
||||
module Msf::Post::Hardware
|
||||
require 'msf/core/post/hardware/automotive/uds'
|
||||
require 'msf/core/post/hardware/zigbee/utils'
|
||||
end
|
||||
|
|
|
@ -0,0 +1,255 @@
|
|||
# -*- coding: binary -*-
|
||||
module Msf
|
||||
class Post
|
||||
module Hardware
|
||||
module Zigbee
|
||||
|
||||
module Utils
|
||||
|
||||
## Constants for packet decoding fields
|
||||
# Frame Control Field
|
||||
DOT154_FCF_TYPE_MASK = 0x0007 #: Frame type mask
|
||||
DOT154_FCF_SEC_EN = 0x0008 #: Set for encrypted payload
|
||||
DOT154_FCF_FRAME_PND = 0x0010 #: Frame pending
|
||||
DOT154_FCF_ACK_REQ = 0x0020 #: ACK request
|
||||
DOT154_FCF_INTRA_PAN = 0x0040 #: Intra-PAN activity
|
||||
DOT154_FCF_DADDR_MASK = 0x0C00 #: Destination addressing mode mask
|
||||
DOT154_FCF_VERSION_MASK = 0x3000 #: Frame version
|
||||
DOT154_FCF_SADDR_MASK = 0xC000 #: Source addressing mask mode
|
||||
|
||||
# Frame Control Field Bit Shifts
|
||||
DOT154_FCF_TYPE_MASK_SHIFT = 0 #: Frame type mask mode shift
|
||||
DOT154_FCF_DADDR_MASK_SHIFT = 10 #: Destination addressing mode mask
|
||||
DOT154_FCF_VERSION_MASK_SHIFT = 12 #: Frame versions mask mode shift
|
||||
DOT154_FCF_SADDR_MASK_SHIFT = 14 #: Source addressing mask mode shift
|
||||
|
||||
# Address Mode Definitions
|
||||
DOT154_FCF_ADDR_NONE = 0x0000 #: Not sure when this is used
|
||||
DOT154_FCF_ADDR_SHORT = 0x0002 #: 4-byte addressing
|
||||
DOT154_FCF_ADDR_EXT = 0x0003 #: 8-byte addressing
|
||||
|
||||
DOT154_FCF_TYPE_BEACON = 0 #: Beacon frame
|
||||
DOT154_FCF_TYPE_DATA = 1 #: Data frame
|
||||
DOT154_FCF_TYPE_ACK = 2 #: Acknowledgement frame
|
||||
DOT154_FCF_TYPE_MACCMD = 3 #: MAC Command frame
|
||||
|
||||
DOT154_CRYPT_NONE = 0x00 #: No encryption, no MIC
|
||||
DOT154_CRYPT_MIC32 = 0x01 #: No encryption, 32-bit MIC
|
||||
DOT154_CRYPT_MIC64 = 0x02 #: No encryption, 64-bit MIC
|
||||
DOT154_CRYPT_MIC128 = 0x03 #: No encryption, 128-bit MIC
|
||||
DOT154_CRYPT_ENC = 0x04 #: Encryption, no MIC
|
||||
DOT154_CRYPT_ENC_MIC32 = 0x05 #: Encryption, 32-bit MIC
|
||||
DOT154_CRYPT_ENC_MIC64 = 0x06 #: Encryption, 64-bit MIC
|
||||
DOT154_CRYPT_ENC_MIC128 = 0x07 #: Encryption, 128-bit MIC
|
||||
|
||||
# Infer if the current session is for a ZigBee device.
|
||||
# @return [Boolean] true if session is for a ZigBee device, false otherwise
|
||||
def is_zigbee_hwbridge_session?
|
||||
return true if client.zigbee
|
||||
print_error("Not a ZigBee hwbridge session")
|
||||
false
|
||||
end
|
||||
|
||||
# Verify if a device has been specified.
|
||||
# @return [Boolean] true if device is specified, false otherwise
|
||||
def verify_device(device)
|
||||
return true if device
|
||||
print_line("No target device set, use 'target' or specify bus via the options.")
|
||||
false
|
||||
end
|
||||
|
||||
# Retrieves the target Zigbee device. This is typically set by the user via the
|
||||
# interactive HWBridge command line
|
||||
# @return [String] Zigbee device ID
|
||||
def get_target_device
|
||||
return unless is_zigbee_hwbridge_session?
|
||||
return client.zigbee.get_target_device
|
||||
end
|
||||
|
||||
# Sets the target default Zigbee Device. This command typically isn't called via a script
|
||||
# Instead the user is expected to set this via the interactive HWBridge commandline
|
||||
# @param device [String] Zigbee device ID
|
||||
def set_target_device(device)
|
||||
return unless is_zigbee_hwbridge_session?
|
||||
client.zigbee.set_target_device device
|
||||
end
|
||||
|
||||
# Sets the Zigbee Channel
|
||||
# @param device [String] Zigbee device ID
|
||||
# @param channel [Integer] Channel number, typically 11-25
|
||||
def set_channel(device, channel)
|
||||
return {} unless is_zigbee_hwbridge_session?
|
||||
device = client.zigbee.target_device unless device
|
||||
return {} unless verify_device(device)
|
||||
client.zigbee.set_channel(device, channel)
|
||||
end
|
||||
|
||||
# Inject raw packets. Need firmware on the zigbee device that supports transmission.
|
||||
# @param device [String] Zigbee device ID
|
||||
# @param data [String] Raw binary data sent as a string
|
||||
def inject(device, data)
|
||||
return {} unless is_zigbee_hwbridge_session?
|
||||
device = client.zigbee.target_device unless device
|
||||
return {} unless verify_device(device)
|
||||
client.zigbee.inject(device, data)
|
||||
end
|
||||
|
||||
# Recieves data from the Zigbee device
|
||||
# @param device [String] Zigbee device ID
|
||||
# @return [String] Binary blob of returned data
|
||||
def recv(device)
|
||||
return {} unless is_zigbee_hwbridge_session?
|
||||
device = client.zigbee.target_device unless device
|
||||
return {} unless verify_device(device)
|
||||
client.zigbee.recv(device)
|
||||
end
|
||||
|
||||
# Turn off Zigbee receiving
|
||||
# @param device [String] Zigbee device ID
|
||||
def sniffer_off(device)
|
||||
return {} unless is_zigbee_hwbridge_session?
|
||||
device = client.zigbee.target_device unless device
|
||||
return {} unless verify_device(device)
|
||||
client.zigbee.sniffer_off(device)
|
||||
end
|
||||
|
||||
# Turn on Zigbee receiving
|
||||
# @param device [String] Zigbee device ID
|
||||
def sniffer_on(device)
|
||||
return {} unless is_zigbee_hwbridge_session?
|
||||
device = client.zigbee.target_device unless device
|
||||
return {} unless verify_device(device)
|
||||
client.zigbee.sniffer_on(device)
|
||||
end
|
||||
|
||||
# Breaks up the packet into different sections. Also provides
|
||||
# Some decoding information. This method relates to Killerbee's Pktchop method and
|
||||
# Returns a similar array structure PktChop. If it's a beacon data you will also have
|
||||
# A BEACONDATA array of raw beacon related packets. You can pull other decoded portions from
|
||||
# the returned hash such as
|
||||
# FSF
|
||||
# SEQ
|
||||
# SPAN_ID
|
||||
# SOURCE
|
||||
# SUPERFRAME
|
||||
# GTS
|
||||
# PENDING_ADDRESS_COUNT
|
||||
# PROTOCOL_ID
|
||||
# STACK_PROFILE
|
||||
# CAPABILITY
|
||||
# EXT_PAN_ID
|
||||
# TX_OFFSET
|
||||
# UPDATE_ID
|
||||
# @param packet [String] Raw data from recv
|
||||
# @return [Hash] { PktChop => [Array of data], ..
|
||||
def dot154_packet_decode(packet)
|
||||
result = {}
|
||||
offset = 0
|
||||
pktchop = ['', '', '', '', '', '', [], '']
|
||||
pktchop[0] = packet[0,2]
|
||||
# Sequence number
|
||||
pktchop[1] = packet[2]
|
||||
# Byte swap
|
||||
fcf = pktchop[0].reverse.unpack("H*")[0].hex
|
||||
result["FSF"] = fcf
|
||||
result["SEQ"] = pktchop[1]
|
||||
# Check if we are dealing with a beacon frame
|
||||
if (fcf & DOT154_FCF_TYPE_MASK) == DOT154_FCF_TYPE_BEACON
|
||||
beacondata = ["", "", "", "", "", "", "", "", "", ""]
|
||||
# 802.15.4 fields, SPAN and SA
|
||||
pktchop[4] = packet[3,2]
|
||||
pktchop[5] = packet[5,2]
|
||||
result["SPAN_ID"] = pktchop[4].reverse.unpack("H*")[0]
|
||||
result["SOURCE"] = pktchop[5].reverse.unpack("H*")[0]
|
||||
offset = 7
|
||||
|
||||
# Superframe specification
|
||||
beacondata[0] = packet[offset,2]
|
||||
result["SUPERFRAME"] = beacondata[0]
|
||||
offset+=2
|
||||
|
||||
# GTS data
|
||||
beacondata[1] = packet[offset]
|
||||
result["GTS"] = beacondata[1]
|
||||
offset+=1
|
||||
|
||||
# Pending address count
|
||||
beacondata[2] = packet[offset]
|
||||
result["PENDING_ADDRESS_COUNT"] = beacondata[2]
|
||||
offset+=1
|
||||
|
||||
# Protocol ID
|
||||
beacondata[3] = packet[offset]
|
||||
result["PROTOCOL_ID"] = beacondata[3]
|
||||
offset+=1
|
||||
|
||||
# Stack Profile version
|
||||
beacondata[4] = packet[offset]
|
||||
result["STACK_PROFILE"] = beacondata[4]
|
||||
offset+=1
|
||||
|
||||
# Capability information
|
||||
beacondata[5] = packet[offset]
|
||||
result["CAPABILITY"] = beacondata[5]
|
||||
offset+=1
|
||||
|
||||
# Extended PAN ID
|
||||
beacondata[6] = packet[offset,8]
|
||||
result["EXT_PAN_ID"] = beacondata[6].reverse.unpack("H*")[0]
|
||||
offset+=8
|
||||
|
||||
# TX Offset
|
||||
beacondata[7] = packet[offset,3]
|
||||
result["TX_OFFSET"] = beacondata[7]
|
||||
offset+=3
|
||||
|
||||
# Update ID
|
||||
beacondata[8] = packet[offset]
|
||||
result["UPDATE_ID"] = beacondata[8]
|
||||
offset+=1
|
||||
pktchop[6] = beacondata
|
||||
result["BEACONDATA"] = beacondata
|
||||
else
|
||||
# Not a beacon frame
|
||||
|
||||
# DPAN
|
||||
pktchop[2] = packet[3,2]
|
||||
offset = 5
|
||||
|
||||
# Examine the destination addressing mode
|
||||
daddr_mask = (fcf & DOT154_FCF_DADDR_MASK) >> 10
|
||||
if daddr_mask == DOT154_FCF_ADDR_EXT
|
||||
pktchop[3] = packet[offset,8]
|
||||
offset+=8
|
||||
elsif daddr_mask == DOT154_FCF_ADDR_SHORT
|
||||
pktchop[3] = packet[offset,2]
|
||||
offset+=2
|
||||
end
|
||||
|
||||
# Examine the Intra-PAN flag
|
||||
if (fcf & DOT154_FCF_INTRA_PAN) == 0
|
||||
pktchop[4] = packet[offset,2]
|
||||
offset+=2
|
||||
end
|
||||
|
||||
# Examine the source addressing mode
|
||||
saddr_mask = (fcf & DOT154_FCF_SADDR_MASK) >> 14
|
||||
if daddr_mask == DOT154_FCF_ADDR_EXT
|
||||
pktchop[5] = packet[offset,8]
|
||||
offset+=8
|
||||
elsif daddr_mask == DOT154_FCF_ADDR_SHORT
|
||||
pktchop[5] = packet[offset,2]
|
||||
offset+=2
|
||||
end
|
||||
end
|
||||
# Append remaining payload
|
||||
pktchop[7] = packet[offset,packet.size] if offset < packet.size
|
||||
result["PktChop"] = pktchop
|
||||
return result
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,95 @@
|
|||
#
|
||||
# -*- coding: binary -*-
|
||||
require 'rex/post/hwbridge/client'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module HWBridge
|
||||
module Extensions
|
||||
module Zigbee
|
||||
|
||||
###
|
||||
# Zigbee extension - set of commands to be executed on zigbee compatible hw bridges
|
||||
###
|
||||
|
||||
class Zigbee < Extension
|
||||
|
||||
def initialize(client)
|
||||
super(client, 'zigbee')
|
||||
|
||||
# Alias the following things on the client object so that they
|
||||
# can be directly referenced
|
||||
client.register_extension_aliases(
|
||||
[
|
||||
{
|
||||
'name' => 'zigbee',
|
||||
'ext' => self
|
||||
}
|
||||
])
|
||||
end
|
||||
|
||||
# Sets the default target device
|
||||
# @param device [String] Target Zigbee device ID
|
||||
def set_target_device(device)
|
||||
self.target_device = device
|
||||
end
|
||||
|
||||
# Retrieves the default zigbee device ID
|
||||
# @return [String] Zigbee device ID
|
||||
def get_target_device
|
||||
self.target_device
|
||||
end
|
||||
|
||||
# Gets supported Zigbee Devices
|
||||
# @return [Array] Devices
|
||||
def supported_devices
|
||||
client.send_request("/zigbee/supported_devices")
|
||||
end
|
||||
|
||||
# Sets the channel
|
||||
# @param dev [String] Device to affect
|
||||
# @param channel [Integer] Channel number
|
||||
def set_channel(dev, channel)
|
||||
client.send_request("/zigbee/#{dev}/set_channel?chan=#{channel}")
|
||||
end
|
||||
|
||||
# Injects a raw packet
|
||||
# @param dev [String] Zigbee Device ID
|
||||
# @param data [String] Raw hex data that will be Base64 encoded
|
||||
def inject(dev, data)
|
||||
data = Base64.urlsafe_encode64(data)
|
||||
client.send_request("/zigbee/#{dev}/inject?data=#{data}")
|
||||
end
|
||||
|
||||
# Receives data from transceiver
|
||||
# @param dev [String] Zigbee Device ID
|
||||
# @return [Hash] { data: HexString, valid_crc: X, rssi: X }
|
||||
def recv(dev)
|
||||
data = client.send_request("/zigbee/#{dev}/recv")
|
||||
if data.size > 0
|
||||
data["data"] = Base64.urlsafe_decode64(data["data"]) if data.has_key? "data"
|
||||
end
|
||||
data
|
||||
end
|
||||
|
||||
# Disables sniffer and puts the device in a state that can be changed (like adujsting channel)
|
||||
# @param dev [String] Zigbee Device ID
|
||||
def sniffer_off(dev)
|
||||
client.send_request("/zigbee/#{dev}/sniffer_off")
|
||||
end
|
||||
|
||||
# Enables sniffer receive mode. Not necessary to call before calling recv
|
||||
# @param dev [String] Zigbee Device ID
|
||||
def sniffer_on(dev)
|
||||
client.send_request("/zigbee/#{dev}/sniffer_on")
|
||||
end
|
||||
|
||||
attr_accessor :target_device
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -63,7 +63,7 @@ class Console::CommandDispatcher::Automotive
|
|||
def cmd_busconfig(*args)
|
||||
bus = ''
|
||||
bus_config_opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-h' => [ false, 'Help banner' ],
|
||||
'-b' => [ true, 'Target bus']
|
||||
)
|
||||
bus_config_opts.parse(args) do |opt, _idx, val|
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'rex/post/hwbridge'
|
||||
require 'msf/core/auxiliary/report'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module HWBridge
|
||||
module Ui
|
||||
|
||||
###
|
||||
# Zigbee extension - set of commands to be executed on Zigbee compatible devices
|
||||
###
|
||||
class Console::CommandDispatcher::Zigbee
|
||||
include Console::CommandDispatcher
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
#
|
||||
# List of supported commands.
|
||||
#
|
||||
def commands
|
||||
all = {
|
||||
'supported_devices' => 'Get supported ZigBee devices',
|
||||
'target' => 'Set the target device id',
|
||||
'channel' => 'Set the channel'
|
||||
}
|
||||
|
||||
all
|
||||
end
|
||||
|
||||
# Sets the target device both in the UI class and in the base API
|
||||
# @param device [String] Device ID
|
||||
def set_target_device(device)
|
||||
self.target_device = device
|
||||
client.zigbee.set_target_device device
|
||||
end
|
||||
|
||||
#
|
||||
# Lists all thesupported devices
|
||||
#
|
||||
def cmd_supported_devices
|
||||
devices = client.zigbee.supported_devices
|
||||
if !devices or !devices.has_key? "devices"
|
||||
print_line("error retrieving list of devices")
|
||||
return
|
||||
end
|
||||
devices = devices["devices"]
|
||||
unless devices.size > 0
|
||||
print_line("none")
|
||||
return
|
||||
end
|
||||
set_target_device(devices[0]) if devices.size == 1
|
||||
str = "Supported Devices: "
|
||||
str << devices.join(', ')
|
||||
str << "\nUse device name to set your desired device, default is: #{self.target_device}"
|
||||
print_line(str)
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the default target device
|
||||
#
|
||||
def cmd_target(*args)
|
||||
self.target_device = ""
|
||||
device_opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help banner' ],
|
||||
'-d' => [ true, 'Device ID' ]
|
||||
)
|
||||
device_opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: target -d <device id>\n")
|
||||
print_line(device_opts.usage)
|
||||
return
|
||||
when '-d'
|
||||
set_target_device val
|
||||
end
|
||||
end
|
||||
print_line("set target device to #{self.target_device}")
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the channel
|
||||
#
|
||||
def cmd_channel(*args)
|
||||
chan = 11
|
||||
dev = self.target_device if self.target_device
|
||||
xopts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help banner' ],
|
||||
'-d' => [ true, 'ZigBee device' ],
|
||||
'-c' => [ true, 'Channel number' ]
|
||||
)
|
||||
xopts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: channel -c <channel number>\n")
|
||||
print_line(xopts.usage)
|
||||
return
|
||||
when '-d'
|
||||
dev = val
|
||||
when '-c'
|
||||
chan = val.to_i
|
||||
end
|
||||
end
|
||||
if !dev
|
||||
print_line("You must specify or set a target device")
|
||||
return
|
||||
end
|
||||
client.zigbee.set_channel(dev, chan)
|
||||
print_line("Device #{dev} channel set to #{chan}")
|
||||
end
|
||||
|
||||
#
|
||||
# Name for this dispatcher
|
||||
#
|
||||
def name
|
||||
'Zigbee'
|
||||
end
|
||||
|
||||
attr_accessor :target_device
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -67,6 +67,8 @@ class MetasploitModule < Msf::Auxiliary
|
|||
if (res.code == 200)
|
||||
print_status res.body if datastore["DEBUGJSON"] == true
|
||||
return JSON.parse(res.body)
|
||||
elsif res.code == 401
|
||||
print_error "Access Denied: #{res.body}"
|
||||
end
|
||||
return nil
|
||||
|
||||
|
@ -98,6 +100,9 @@ class MetasploitModule < Msf::Auxiliary
|
|||
if self.hw_specialty.has_key? "automotive"
|
||||
sess.load_automotive if self.hw_specialty["automotive"] == true
|
||||
end
|
||||
if self.hw_specialty.has_key? "zigbee"
|
||||
sess.load_zigbee if self.hw_specialty["zigbee"] == true
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'rex'
|
||||
require 'msf/core/post/hardware/zigbee/utils'
|
||||
|
||||
class MetasploitModule < Msf::Post
|
||||
|
||||
include Msf::Post::Hardware::Zigbee::Utils
|
||||
|
||||
def initialize(info={})
|
||||
super( update_info( info,
|
||||
'Name' => 'Sends Beacons to Scan for Active ZigBee Networks',
|
||||
'Description' => %q{ Post Module to send beacon signals to the broadcast address while
|
||||
channel hopping},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => ['Craig Smith'],
|
||||
'Platform' => ['hardware'],
|
||||
'SessionTypes' => ['hwbridge']
|
||||
))
|
||||
register_options([
|
||||
OptInt.new('CHANNEL', [false, "Disable channel hopping by forcing a channel (11-26)", nil]),
|
||||
OptInt.new('LOOP', [false, "How many times to loop over the channels (-1 will run in an endless loop)", 1]),
|
||||
OptInt.new('DELAY', [false, "Delay in seconds to listen on each channel", 2]),
|
||||
OptString.new('DEVICE', [false, "ZigBee device ID, defaults to target device", nil])
|
||||
], self.class)
|
||||
@seq = 0
|
||||
@channel = 11
|
||||
@stumbled = {}
|
||||
@loop_count = 0
|
||||
end
|
||||
|
||||
def display_details(routerdata)
|
||||
stackprofile_map = {0 => "Network Specific",
|
||||
1 => "ZigBee Standard",
|
||||
2 => "ZigBee Enterprise"}
|
||||
stackver_map = {0 => "ZigBee Prototype",
|
||||
1 => "ZigBee 2004",
|
||||
2 => "ZigBee 2006/2007"}
|
||||
spanid, source, extpanid, stackprofilever, channel = routerdata
|
||||
stackprofilever = stackprofilever.unpack("H*")[0].hex
|
||||
stackprofile = stackprofilever & 0x0f
|
||||
stackver = (stackprofilever & 0xf0) >> 4
|
||||
profile = "Unknown"
|
||||
profile = stackprofile_map[stackprofile] if stackprofile_map.has_key? stackprofile
|
||||
ver = "Unknown"
|
||||
ver = stackver_map[stackver] if stackver_map.has_key? stackver
|
||||
print_status("New Network: PANID: 0x#{spanid.upcase} SOURCE: 0x#{source.upcase}")
|
||||
print_status(" Ext PANID: #{extpanid.upcase.scan(/../).join(':')} Stack Profile: #{profile}")
|
||||
print_status(" Stack Version: #{ver}")
|
||||
print_status(" Channel: #{@channel}")
|
||||
end
|
||||
|
||||
def scan
|
||||
@seq = 0 if @seq > 255
|
||||
print_status("Scanning Channel #{@channel}")
|
||||
set_channel(datastore["DEVICE"], @channel)
|
||||
beacon = "\x03\x08#{@seq.chr}\xff\xff\xff\xff\x07"
|
||||
inject(datastore["DEVICE"], beacon)
|
||||
delay = Time.now + datastore["DELAY"]
|
||||
while delay > Time.now()
|
||||
pkt = recv(datastore["DEVICE"])
|
||||
if pkt and pkt.size > 0 and pkt["valid_crc"]
|
||||
pktdecode = dot154_packet_decode(pkt["data"])
|
||||
if (pktdecode["FSF"] & DOT154_FCF_TYPE_MASK) == DOT154_FCF_TYPE_BEACON
|
||||
key = "#{pktdecode["SPAN_ID"]}#{pktdecode["SOURCE"]}"
|
||||
value = [pktdecode["SPAN_ID"], pktdecode["SOURCE"], pktdecode["EXT_PAN_ID"], pktdecode["STACK_PROFILE"], @channel]
|
||||
if not @stumbled.has_key? key
|
||||
@stumbled[key] = value
|
||||
display_details(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
sniffer_off(datastore["DEVICE"]) # Needed to clear receive buffers
|
||||
@seq += 1
|
||||
@channel += 1 if not datastore["CHANNEL"]
|
||||
@loop_count += 1 if @channel > 26 or datastore["CHANNEL"]
|
||||
@channel = 11 if @channel > 26
|
||||
end
|
||||
|
||||
def run
|
||||
if not get_target_device and not datastore["DEVICE"]
|
||||
print_error "No target device set. Either set one with the 'target' command or specify the DEVICE."
|
||||
return
|
||||
end
|
||||
@channel = datastore["CHANNEL"] if datastore["CHANNEL"]
|
||||
@channel = 11 if @channel > 26
|
||||
if datastore["LOOP"] == -1
|
||||
while(1) do
|
||||
scan
|
||||
end
|
||||
else
|
||||
while(@loop_count < datastore["LOOP"])
|
||||
scan
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,251 @@
|
|||
#!/usr/bin/env python
|
||||
# KillerBee Metasploit relay server
|
||||
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import cmd
|
||||
import time
|
||||
import json
|
||||
import base64
|
||||
import socket
|
||||
import threading
|
||||
import pkg_resources # Used to get killerbee version
|
||||
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer
|
||||
from urlparse import parse_qs,urlparse
|
||||
from killerbee import *
|
||||
|
||||
last_errors = 0
|
||||
starttime = 0
|
||||
packets_sent = 0
|
||||
last_sent = 0
|
||||
username = None
|
||||
password = None
|
||||
kb = None
|
||||
|
||||
class MSFHandler(BaseHTTPRequestHandler):
|
||||
def status(self):
|
||||
status = {}
|
||||
hw_versions = []
|
||||
fw_version = pkg_resources.get_distribution("killerbee").version
|
||||
device_names = []
|
||||
for dev in kbutils.devlist():
|
||||
hw_versions.append(dev[2])
|
||||
device_names.append(dev[1])
|
||||
if len(hw_versions) > 0:
|
||||
status["operational"] = 1
|
||||
else:
|
||||
status["operational"] = 0
|
||||
status["hw_specialty"] = { "zigbee": True }
|
||||
# TODO: We should check firmware before reporting transmit capabilities
|
||||
status["hw_capabilities"] = { "transmit": True}
|
||||
status["last_10_errors"] = last_errors
|
||||
status["api_version"] = "0.0.3"
|
||||
status["fw_version"] = fw_version
|
||||
if len(hw_versions) == 1:
|
||||
status["hw_version"] = hw_versions[0]
|
||||
status["device_name"] = device_names[0]
|
||||
elif len(hw_versions) > 1:
|
||||
status["hw_version"] = ', '.join(hw_versions)
|
||||
status["device_name"] = ', '.join(device_names)
|
||||
else:
|
||||
status["hw_version"] = "Not Supported"
|
||||
return status
|
||||
|
||||
def statistics(self):
|
||||
global packets_sent
|
||||
stats = {}
|
||||
stats["uptime"] = int(time.time()) - starttime
|
||||
stats["packet_stats"] = packets_sent
|
||||
stats["last_request"] = last_sent
|
||||
stats["voltage"] = "0.0v"
|
||||
return stats
|
||||
|
||||
def datetime(self):
|
||||
return { "sytem_datetime": int(time.time()) }
|
||||
|
||||
def timezone(self):
|
||||
return { "system_timezone": time.strftime("%Z") }
|
||||
|
||||
def set_channel(self, args):
|
||||
if not "chan" in args:
|
||||
return self.not_supported()
|
||||
chan = int(args["chan"][0])
|
||||
kb.set_channel(chan)
|
||||
return { "success": True }
|
||||
|
||||
def inject(self, args):
|
||||
global packets_sent
|
||||
if not "data" in args:
|
||||
return self.not_supported()
|
||||
try:
|
||||
kb.inject(base64.urlsafe_b64decode(args["data"][0]))
|
||||
packets_sent+=1
|
||||
except Exception, e:
|
||||
print("ERROR: Unable to inject packet: {0}".format(e))
|
||||
return { "success": False }
|
||||
return { "success": True }
|
||||
|
||||
def recv(self):
|
||||
pkt = kb.pnext()
|
||||
if pkt != None and pkt[1]:
|
||||
return {"data": base64.urlsafe_b64encode(pkt[0]), "valid_crc": pkt[1], "rssi": pkt[2] }
|
||||
return {}
|
||||
|
||||
def sniffer_off(self):
|
||||
kb.sniffer_off()
|
||||
return {"success": True }
|
||||
|
||||
def sniffer_on(self):
|
||||
kb.sniffer_on()
|
||||
return {"success": True }
|
||||
|
||||
def supported_devices(self):
|
||||
devices = []
|
||||
for dev in kbutils.devlist():
|
||||
devices.append(dev[0])
|
||||
return { "devices": devices }
|
||||
|
||||
def not_supported(self):
|
||||
return { "status": "not supported" }
|
||||
|
||||
def send(self, data, resp=200):
|
||||
self.send_response(resp)
|
||||
self.send_header('Content-type','application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(data))
|
||||
return
|
||||
|
||||
def do_AUTHHEAD(self):
|
||||
self.send_response(401)
|
||||
self.send_header('WWW-Authenticate', 'Basic realm=\"Killerbee MSF Relay\"')
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write("Please Authenticate")
|
||||
|
||||
def do_GET(self):
|
||||
if not password == None:
|
||||
if self.headers.getheader('Authorization') == None:
|
||||
print("Did not authenticate")
|
||||
self.do_AUTHHEAD()
|
||||
return
|
||||
if not self.headers.getheader('Authorization') == 'Basic '+base64.b64encode(username + ":" + password):
|
||||
print("Bad Authentication")
|
||||
self.do_AUTHHEAD()
|
||||
return
|
||||
url = urlparse(self.path)
|
||||
args = parse_qs(url.query)
|
||||
if self.path=="/status":
|
||||
self.send(self.status())
|
||||
elif self.path=="/statistics":
|
||||
self.send(self.statistics())
|
||||
elif self.path=="/settings/datetime":
|
||||
self.send(self.datetime())
|
||||
elif self.path=="/settings/timezone":
|
||||
self.send(self.timezone())
|
||||
elif self.path=="/zigbee/supported_devices":
|
||||
self.send(self.supported_devices())
|
||||
elif self.path.startswith("/zigbee/"):
|
||||
re_dev = re.compile("/zigbee/([\d\w:]+)/")
|
||||
m = re_dev.match(self.path)
|
||||
if m:
|
||||
dev = m.group(1)
|
||||
if self.path.find("/set_channel?") > -1:
|
||||
self.send(self.set_channel(args))
|
||||
elif self.path.find("/inject?") > -1:
|
||||
self.send(self.inject(args))
|
||||
elif self.path.find("/recv") > -1:
|
||||
self.send(self.recv())
|
||||
elif self.path.find("/sniffer_off") > -1:
|
||||
self.send(self.sniffer_off())
|
||||
elif self.path.find("/sniffer_on") > -1:
|
||||
self.send(self.sniffer_on())
|
||||
else:
|
||||
self.send(self.not_supported(), 404)
|
||||
else:
|
||||
self.send(self.not_supported(), 404)
|
||||
else:
|
||||
self.send(self.not_supported(), 404)
|
||||
return
|
||||
|
||||
class Killerbee_MSFRelay(cmd.Cmd):
|
||||
intro = """
|
||||
KillerBee Metasploit Relay
|
||||
"""
|
||||
|
||||
def __init__(self, ip='0.0.0.0', port=8080):
|
||||
cmd.Cmd.__init__(self)
|
||||
|
||||
self._ip = ip
|
||||
self._port = port
|
||||
self._sock = None
|
||||
self._pause = False
|
||||
|
||||
self.start()
|
||||
|
||||
def start(self):
|
||||
self._go = True
|
||||
while self._go:
|
||||
# serve the NIC port
|
||||
try:
|
||||
self._sock = HTTPServer((self._ip, self._port), MSFHandler)
|
||||
starttime = int(time.time())
|
||||
print("KillerBee MSFRelay running.")
|
||||
self._sock.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
self._sock.socket.close()
|
||||
self._go = False
|
||||
except:
|
||||
sys.excepthook(*sys.exc_info())
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-i', '--iface', '--dev', action='store', dest='devstring')
|
||||
parser.add_argument('-u', '--user', default="msf_relay", help='HTTP Username', type=str)
|
||||
parser.add_argument('-p', '--password', default="rfcat_relaypass", help='HTTP Password', type=str)
|
||||
parser.add_argument('-P', '--Port', default=8080, type=int)
|
||||
parser.add_argument('--noauth', default=False, action="store_true", help='Do not require authentication')
|
||||
parser.add_argument('--localonly', default=False, action="store_true", help='Listen on localhost only')
|
||||
|
||||
ifo = parser.parse_args()
|
||||
|
||||
try:
|
||||
kb = KillerBee(device=ifo.devstring)
|
||||
except KBInterfaceError as e:
|
||||
print("Interface Error: {0}".format(e))
|
||||
sys.exit(-1)
|
||||
|
||||
username = ifo.user
|
||||
password = ifo.password
|
||||
ip = "0.0.0.0"
|
||||
port = ifo.Port
|
||||
if ifo.noauth:
|
||||
username = None
|
||||
password = None
|
||||
if ifo.localonly:
|
||||
host = "127.0.0.1"
|
||||
|
||||
wait_msg = False
|
||||
dev_found = False
|
||||
while not dev_found:
|
||||
try:
|
||||
devs = kbutils.devlist()
|
||||
if len(devs) > 0:
|
||||
dev_found = True
|
||||
elif not wait_msg:
|
||||
print("Insert KillerBee compatible ZigBee device. (You may need to add permissions)")
|
||||
wait_msg = True
|
||||
except KeyboardInterrupt:
|
||||
sys.exit()
|
||||
except:
|
||||
if not wait_msg:
|
||||
print("Insert KillerBee compatible ZigBee device. (You may need to add permissions)")
|
||||
wait_msg = True
|
||||
|
||||
beerelay = Killerbee_MSFRelay(ip, port)
|
||||
|
||||
import atexit
|
||||
atexit.register(cleanupInteractiveAtExit)
|
Loading…
Reference in New Issue