Commit 76761a1c authored by Lars Almon's avatar Lars Almon
Browse files

older version - supports friend key recover

parent fd6aa2a6
# name of your application
APPLICATION = btlemesh
APPLICATION = btlejack
# If no BOARD is found in the environment, use this default:
BOARD ?= native
......@@ -16,9 +15,6 @@ DEVELHELP ?= 1
# Change this to 0 show compiler invocation lines by default:
QUIET ?= 1
DIRS += mode
USEMODULE += mode
# Modules to include:
USEMODULE += xtimer
USEMODULE += hashes
......@@ -31,5 +27,6 @@ USEMODULE += cipher_modes
CFLAGS += -DCRYPTO_AES
CFLAGS += -DCRYPTO_THREEDES
include $(RIOTBASE)/Makefile.include
......@@ -60,10 +60,6 @@ Use -dt to set datatypes, which should be jammed. For example for mesh message a
```
btlejack -a -j -dt 0x2a -dt 0x2b
```
Use -nid to set a nid of a friendship subnetwork, to terminate this friendship by jamming the friend poll messages:
```
btlejack -a -j -nid 0x5b
```
Catch a friend establishment and recover the friend key material
```
......@@ -84,3 +80,6 @@ To specify one or more devices add
otherwise btlejack try to find them, but only search for micro:bit. For all others you must set this value.
If only the version and depending on command that cached parameters are used is displayed after execution, abort and restart.
......@@ -257,8 +257,6 @@ And to set specific datatypes, which should be jammed, use the ``-dt`` option on
::
$ btlejack -a -j -dt 0x2a -dt 0x2b
To terminate a friendship of a BT Mesh network, use the ``-nid`` option and enter the nid of the friendship subnetwork.
Catch a friend establishment and recover the friend key material
::
$ btlejack -a -fk
......
......@@ -58,6 +58,23 @@ def main():
help='Micro:Bit device serial port'
)
parser.add_argument(
'-s',
'--scan-connections',
dest='scan_aa',
action='store_true',
default=False,
help='Scan for active BLE connections'
)
parser.add_argument(
'-f',
'--follow',
dest='follow',
type=str,
help='Follow an active connection'
)
parser.add_argument(
'-a',
'--adv',
......@@ -67,6 +84,14 @@ def main():
help='Scan for packets on advertising channels'
)
parser.add_argument(
'-dt',
'--datatype',
dest='datatypes',
action='append',
type=str,
help='The data type should be jamed'
)
parser.add_argument(
'-pt',
......@@ -77,7 +102,32 @@ def main():
help='Open a prompt to send LL packets, sniff and jam on advertising channels (only performed in conjunction with -a option)'
)
parser.add_argument(
'-c',
'--connreq',
dest='connreq',
type=str,
help='Sniff new BTLE connections on multiple channels if possible'
)
parser.add_argument(
'-m',
'--channel-map',
dest='chm',
type=str,
default=None,
help='Set channel map'
)
parser.add_argument(
'-p',
'--hop-interval',
dest='hop',
type=int,
default=None,
help='Set hop interval'
)
parser.add_argument(
'-v',
'--verbose',
......@@ -112,26 +162,24 @@ def main():
help='Jam an active connection or the advertising channels (only performed in conjunction with -f or -a option)'
)
parser.add_argument(
'-dt',
'--datatype',
dest='datatypes',
action='append',
type=str,
help='The data type should be jamed'
'-t',
'--hijack',
dest='hijack',
default=False,
action='store_true',
help='Hijack an active connection (only performed in conjunction with -f option)'
)
parser.add_argument(
'-nid',
'--nid',
dest='nid',
type=str,
help='Jam friend poll messages with the entered nid.'
'-mm',
'--mesh_mitm',
dest='mesh_mitm',
default=False,
action='store_true',
help='Start a mitm attack on mesh provisioning to get the Net Key'
)
parser.add_argument(
'-fk',
'--friendship_key',
......@@ -141,13 +189,86 @@ def main():
help='Compute the friendship key of two nodes by listen for the establishment.'
)
parser.add_argument(
'-k',
'--crc',
dest='crc',
help='CRCInit value'
)
parser.add_argument(
'-z',
'--clear',
action='store_true',
dest='flush',
default=False,
help='Clear stored connections parameters'
)
parser.add_argument(
'-i',
'--install',
action='store_true',
dest='install',
help='Install latest version of firmware on every sniffer'
)
parser.add_argument(
'-n',
'--timeout',
dest='timeout',
default=0,
type=int,
help='Channel map recovery timeout'
)
args = parser.parse_args()
supervisor = None
print('BtleJack version %s' % VERSION)
print('')
# upgrade sniffers
if args.install:
# retrieve the embedded firmware version
_dir, _filename = os.path.split(__file__)
fw_path = os.path.join(_dir, "data", "btlejack-fw.hex")
if os.name == 'posix':
mount_output = check_output('mount').splitlines()
mounted_volumes = [x.split()[2] for x in mount_output]
flashed = 0
for volume in mounted_volumes:
if re.match(b'.*MICROBIT[0-9]*$', volume):
print('[i] Flashing %s ...' % volume.decode('ascii'))
path = os.path.join(volume.decode('ascii'),'fw.hex')
fw = open(fw_path,'r').read()
# copy our firmware on it
with open(path, 'wb') as output:
output.write(fw.encode('ascii'))
flashed += 1
if flashed > 0:
print('[i] Flashed %d devices' % flashed)
else:
print('[i] No sniffer found, make sure all your devices are mounted as mass storage devices before flashing.')
sys.exit(1)
else:
print('[!] This feature does not support your operating system, sorry.')
if args.flush:
try:
BtlejackSession.get_instance().clear()
BtlejackSession.get_instance().save()
print('[i] Stored connections cleared')
except BtlejackSessionError as error:
pass
else:
try:
BtlejackSession.get_instance().load()
except BtlejackSessionError as error:
print('[!] Cannot load connections cache')
# Create output if required
if args.output is not None:
if args.output_format.lower().strip() == 'nordic':
......@@ -161,8 +282,61 @@ def main():
else:
output = None
if args.scan_aa:
try:
supervisor = CLIAccessAddressSniffer(verbose=args.verbose, devices=args.devices)
except DeviceError as error:
print('[!] Please connect a compatible board or add it with the -d option in order to use BtleJack')
sys.exit(-1)
elif args.follow is not None:
aa = int(args.follow, 16)
if args.chm is not None:
chm = int(args.chm, 16)
else:
chm = None
if args.crc is not None:
crc = int(args.crc, 16)
else:
crc = None
if args.hop is not None:
hop = args.hop
else:
hop = None
try:
cached_parameters = BtlejackSession.get_instance().find_connection(aa)
if cached_parameters is not None:
# override parameters with those stored in cache
for param in cached_parameters:
if param == 'crcinit':
crc = cached_parameters[param]
creation_date = datetime.datetime.fromtimestamp(
cached_parameters['start']
).strftime('%Y-%m-%d %H:%M:%S')
print('[i] Using cached parameters (created on %s)' % creation_date)
try:
supervisor = CLIConnectionRecovery(
aa,
channel_map=chm,
hijack=args.hijack,
jamming=args.jamming,
hop_interval=hop,
crc=crc,
output=output,
verbose=args.verbose,
devices=args.devices,
timeout=args.timeout
)
except SnifferUpgradeRequired as su:
print("[i] Quitting, please upgrade your sniffer firmware (-i option if you are using a Micro:Bit)")
except DeviceError as error:
print('[!] PPlease connect a compatible board or add it with the -d option in order to use BtleJack')
sys.exit(-1)
if args.friendship_key:
elif args.friendship_key:
if args.output is not None:
output = PcapBleAdvWriter(args.output)
netkey_str = input('Insert the NetKey: ')
......@@ -187,26 +361,57 @@ def main():
output = PcapBleAdvWriter(args.output)
try:
types = []
nid = None
if args.datatypes is not None:
for datatype in args.datatypes:
types.append(int(datatype, 16))
if args.nid is not None:
nid =int(args.nid,16)
supervisor = CLIAdvertisingSniffer(
output=output,
verbose=args.verbose,
devices=args.devices,
jamming=args.jamming,
datatypes=types,
nid=nid,
prompt=args.prompt
)
except DeviceError as error:
print('[!] Please connect a compatible board or add it with the -d option in order to use BtleJack')
sys.exit(-1)
else:
elif args.mesh_mitm:
if args.output is not None:
output = PcapBleAdvWriter(args.output)
try:
supervisor = CLIMeshProvsioningMITM(
devices=args.devices,
output=output,
verbose=args.verbose
)
except SnifferUpgradeRequired as su:
print("[i] Quitting, please upgrade your sniffer firmware (-i option if you are using a Micro:Bit)")
sys.exit(-1)
elif args.connreq is not None:
if args.output is not None:
output = PcapBleAdvWriter(args.output)
# Support magic word "any" and "*" as wildcards
if args.connreq.lower() == 'any':
args.connreq = 'ff:ff:ff:ff:ff:ff'
bd_addr_int = bd_address_to_int(args.connreq)
if bd_addr_int is not None:
# address is okay, feed our sniffer
try:
supervisor = CLIConnectionSniffer(
bd_addr_int,
output=output,
verbose=args.verbose,
devices=args.devices
)
except SnifferUpgradeRequired as su:
print("[i] Quitting, please upgrade your sniffer firmware (-i option if you are using a Micro:Bit)")
else:
print('[!] Wrong Bluetooth Address format: %s' % args.connreq)
elif not args.flush and not args.install:
print('BtleJack version %s' % VERSION)
print('')
parser.print_help()
......
......@@ -122,12 +122,6 @@ class AbstractInterface(object):
"""
self.mode = self.MODE_JAMMING
def jam_poll(self):
"""
Switch the link in jamming mode.
"""
self.mode = self.MODE_JAMMING
def start_mitm(self):
"""
Switch the link in mitm on mesh provisioning mode.
......
......@@ -115,13 +115,6 @@ class SingleSnifferInterface(AbstractInterface):
self.link.jam_adv(channel, datatypes)
super().jam_adv()
def jam_poll(self, channel=37, nid=None):
"""
Jam the advertising channels.
"""
self.link.jam_poll(channel, nid)
super().jam_poll()
def start_mitm(self, channel=37):
self.link.start_mitm(channel)
super().start_mitm()
......@@ -268,7 +261,7 @@ class MultiSnifferInterface(AbstractInterface):
def jam_adv(self, datatypes=None):
"""
Jam the advertising channels.
Sniff the advertising channels.
"""
channels = [37, 38, 39]
# initialize jobs
......@@ -277,18 +270,6 @@ class MultiSnifferInterface(AbstractInterface):
link.set_timeout(0)
link.jam_adv(channels[i], datatypes)
super().jam_adv()
def jam_poll(self, nid):
"""
Jam the friend polls of network with nid
"""
channels = [37, 38, 39]
# initialize jobs
for i,link in enumerate(self.interfaces[:len(channels)]):
link.reset()
link.set_timeout(0)
link.jam_poll(channels[i], nid)
super().jam_poll()
def start_mitm(self):
channels = [37, 38, 39]
......
......@@ -2,7 +2,6 @@
Link module.
"""
import traceback
from serial import Serial
from serial.tools.list_ports import comports
from struct import pack, unpack
......@@ -13,7 +12,7 @@ from btlejack.packets import (Packet, PacketRegistry, ResetCommand,
AccessAddressNotification, SniffConnReqCommand, SniffConnReqResponse,
ConnectionRequestNotification, EnableJammingCommand, EnableJammingResponse,
EnableHijackingCommand, EnableHijackingResponse, SniffAdvCommand,
SniffAdvResponse, JamAdvCommand, JamAdvResponse, JamPollCommand, JamPollResponse,
SniffAdvResponse, JamAdvCommand, JamAdvResponse, StartMITMCommand, StartMITMResponse,
CatchFKCommand, CatchFKResponse)
class DeviceError(Exception):
......@@ -129,7 +128,6 @@ class Link(object):
if len(self.rx_buffer) >= (pkt_size + 5):
# yep, parse this packet
packet = Packet.fromBytes(self.rx_buffer[:pkt_size+5])
#traceback.print_stack()
self.rx_buffer = self.rx_buffer[pkt_size+5:]
self.lock.release()
return packet
......@@ -224,13 +222,10 @@ class Link(object):
"""
self.write(JamAdvCommand(channel, datatypes))
self.wait_packet(JamAdvResponse)
def jam_poll(self, nid, channel=37):
"""
Jam friend pll packets
"""
self.write(JamPollCommand(channel, nid))
self.wait_packet(JamPollResponse)
def start_mitm(self, channel=37):
self.write(StartMITMCommand(channel))
self.wait_packet(StartMITMResponse)
def catch_friend_key(self, channel, netkey, iv_index):
"""
......
......@@ -69,7 +69,7 @@ class Packet(object):
OP_RESET = 0x02
OP_SCAN_AA = 0x03
OP_RECOVER = 0x04
OP_JAM_POLL = 0x05
OP_MESH_MITM = 0x05
OP_CATCH_FK = 0x06
OP_SNIFF_CONREQ = 0x07
OP_ENABLE_JAMMING = 0x08
......@@ -518,17 +518,17 @@ class JamAdvResponse(Packet):
def from_raw(packet):
return JamAdvResponse()
@register_packet(Packet.OP_JAM_POLL, Packet.F_CMD)
class JamPollCommand(Packet):
@register_packet(Packet.OP_MESH_MITM, Packet.F_CMD)
class StartMITMCommand(Packet):
"""
Sniff on advertising channels command
"""
def __init__(self, nid, channel=37):
payload = pack('<IB', channel, nid)
def __init__(self, channel=37):
payload = pack('<I', channel)
super().__init__(Packet.OP_MESH_MITM, payload, Packet.F_CMD)
@register_packet(Packet.OP_JAM_POLL, Packet.F_CMD | Packet.F_RESP)
class JamPollResponse(Packet):
@register_packet(Packet.OP_MESH_MITM, Packet.F_CMD | Packet.F_RESP)
class StartMITMResponse(Packet):
"""
Sniff connection request response.
"""
......
......@@ -335,7 +335,6 @@ class SimplePromptThread(Thread):
try:
hex_value = str(parameters[0])
payload = bytes.fromhex(hex_value)
#self.supervisor.reset()
self.supervisor.send_packet(
payload
)
......
......@@ -545,14 +545,12 @@ class AdvertisingSniffer(Supervisor):
"""
self.interface.sniff_adv()
def jam(self, datatypes=None, nid=None):
def jam(self, datatypes=None):
"""
Start sniffing for advertising.
"""
if (nid == None):
self.interface.jam_adv(datatypes)
else:
self.interface.jam_poll(nid)
self.interface.jam_adv(datatypes)
def process_packets(self):
packets = self.interface.read_packet()
if len(packets) > 0:
......
......@@ -144,7 +144,7 @@ class CLIAdvertisingSniffer(AdvertisingSniffer):
"""
Advertising sniffer.
"""
def __init__(self, devices=None, output=None, verbose=False, jamming=False, datatypes=None, nid=None, prompt=False, timeout=0):
def __init__(self, devices=None, output=None, verbose=False, jamming=False, datatypes=None, prompt=False, timeout=0):
super().__init__(devices=devices)
self.verbose = verbose
self.output = output
......@@ -162,7 +162,7 @@ class CLIAdvertisingSniffer(AdvertisingSniffer):
raise SnifferUpgradeRequired()
if self._jamming:
self.jam(datatypes, nid)
self.jam(datatypes)
else:
self.sniff()
if prompt:
......
......@@ -10,23 +10,23 @@ def read(fname):
def get_version():
"""
Retrieve version from version module
Retrieve version from btlejuice.version module.
"""
version = {}
exec(read('btlejack/version.py'), version)
return '.'.join([version['VERSION'], version['RELEASE']])
setup(
name = "btlemesh",
name = "btlejack",
python_requires='>3.5.2',
version = get_version(),
author = "",
author_email = "",
description = (""),
author = "Damien Cauquil",
author_email = "damien.cauquil@digital.security",
description = ("Bluetooth Low Energy Swiss-army knife to sniff, jam and hijack connections"),
long_description = read('README.rst'),
url = 'https://github.com/virtualabs/btlejack',
license = "MIT",
keywords = "bluetooth mesh sniff jam send friendship",
keywords = "bluetooth smart low energy hijack sniff jam",
packages=find_packages(),
install_requires=[
'pyserial',
......@@ -35,7 +35,7 @@ setup(
],
entry_points= {
'console_scripts': [
'btlemesh=btlejack:main',
'btlejack=btlejack:main',
]
},
package_data = {'btlejack' : ['data/btlejack-fw.hex', 'LICENSE']}
......
This diff is collapsed.
......@@ -2,6 +2,7 @@
#include "cpu.h"
#include "board.h"
//#include "net/netdev.h"
/**
* channel_to_freq(int channel)
......@@ -46,6 +47,123 @@ void radio_disable(void) {
* Configure the nRF51822 to sniff on a specific channel.
**/
void radio_set_sniff(int channel) {
radio_disable();
// Enable the High Frequency clock on the processor. This is a pre-requisite for
// the RADIO module. Without this clock, no communication is possible.
NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
NRF_CLOCK->TASKS_HFCLKSTART = 1;
while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0);
// power should be one of: -30, -20, -16, -12, -8, -4, 0, 4
NRF_RADIO->TXPOWER = (RADIO_TXPOWER_TXPOWER_0dBm << RADIO_TXPOWER_TXPOWER_Pos);
NRF_RADIO->TXADDRESS = 0;
NRF_RADIO->RXADDRESSES = 1;
/* Listen on channel 6 (2046 => index 1 in BLE). */
NRF_RADIO->FREQUENCY = channel_to_freq(channel);