Added module for zstumbler

This commit is contained in:
Craig Smith 2017-03-06 16:10:14 -08:00
parent dcb42a3e69
commit 60cd04bc7b
7 changed files with 505 additions and 10 deletions

View File

@ -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

View File

@ -0,0 +1,259 @@
# -*- 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
# 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
if not client.zigbee
print_error("Not a Zigbee hwbridge session")
return
end
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)
if not client.zigbee
print_error("Not a Zigbee hwbridge session")
return
end
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)
if not client.zigbee
print_error("Not a Zigbee hwbridge session")
return {}
end
device = client.zigbee.target_device if not device
if not device
print_line("No target device set, use 'target' or specify bus via the options")
return {}
end
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)
if not client.zigbee
print_error("Not a Zigbee hwbridge session")
return {}
end
device = client.zigbee.target_device if not device
if not device
print_line("No target device set, use 'target' or specify bus via the options")
return {}
end
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)
if not client.zigbee
print_error("Not a Zigbee hwbridge session")
return {}
end
device = client.zigbee.target_device if not device
if not device
print_line("No target device set, use 'target' or specify bus via the options")
return {}
end
client.zigbee.recv(device)
end
# Turn off Zigbee receiving
# @param device [String] Zigbee device ID
def sniffer_off(device)
if not client.zigbee
print_error("Not a Zigbee hwbridge session")
return {}
end
device = client.zigbee.target_device if not device
if not device
print_line("No target device set, use 'target' or specify bus via the options")
return {}
end
client.zigbee.sniffer_off(device)
end
# Turn on Zigbee receiving
# @param device [String] Zigbee device ID
def sniffer_on(device)
if not client.zigbee
print_error("Not a Zigbee hwbridge session")
return {}
end
device = client.zigbee.target_device if not device
if not device
print_line("No target device set, use 'target' or specify bus via the options")
return {}
end
client.zigbee.sniffer_on(device)
end
# Breaks up the packet into different sections. Also provides
# Some decoding information
# @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

View File

@ -28,11 +28,54 @@ class Zigbee < Extension
])
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
def inject(dev, data)
data = Base64.urlsafe_encode64(data)
client.send_request("/zigbee/#{dev}/inject?data=#{data}")
end
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
def sniffer_off(dev)
client.send_request("/zigbee/#{dev}/sniffer_off")
end
def sniffer_on(dev)
client.send_request("/zigbee/#{dev}/sniffer_on")
end
attr_accessor :target_device
end
end

View File

@ -18,12 +18,21 @@ class Console::CommandDispatcher::Zigbee
#
def commands
all = {
'supported_devices' => 'Get supported zigbee devices'
'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
#
@ -38,7 +47,7 @@ class Console::CommandDispatcher::Zigbee
print_line("none")
return
end
self.target_device = devices[0] if devices.size == 1
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}"
@ -61,12 +70,43 @@ class Console::CommandDispatcher::Zigbee
print_line(device_opts.usage)
return
when '-d'
self.target_device = val
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 not 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
#
@ -74,9 +114,7 @@ class Console::CommandDispatcher::Zigbee
'Zigbee'
end
private
attr_accessor :target_device
end
end

View File

@ -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

View File

@ -0,0 +1,102 @@
##
# 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", nil]),
OptInt.new('LOOP', [false, "How many times to loop over the channels. -1 is forever", 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
@loop_count += 1 if @channel > 26 or datastore["CHANNEL"]
@channel = 11 if @channel > 26
@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"]
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"]
if datastore["LOOP"] == -1
while(1) do
scan
end
else
while(@loop_count < datastore["LOOP"])
scan
end
end
end
end

View File

@ -22,6 +22,7 @@ packets_sent = 0
last_sent = 0
username = None
password = None
kb = None
class MSFHandler(BaseHTTPRequestHandler):
def status(self):
@ -53,6 +54,7 @@ class MSFHandler(BaseHTTPRequestHandler):
return status
def statistics(self):
global packets_sent
stats = {}
stats["uptime"] = int(time.time()) - starttime
stats["packet_stats"] = packets_sent
@ -66,6 +68,39 @@ class MSFHandler(BaseHTTPRequestHandler):
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():
@ -112,12 +147,20 @@ class MSFHandler(BaseHTTPRequestHandler):
elif self.path=="/zigbee/supported_devices":
self.send(self.supported_devices())
elif self.path.startswith("/zigbee/"):
re_idx = re.compile("/zigbee/(\w+)/")
m = re_idx.match(self.path)
re_dev = re.compile("/zigbee/([\d\w:]+)/")
m = re_dev.match(self.path)
if m:
idx = m.group(1)
if self.path.find("/set_freq?") > -1:
self.send(self.set_freq(args))
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:
@ -160,6 +203,7 @@ 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)
@ -168,6 +212,12 @@ if __name__ == "__main__":
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"