Skip to content

Some Scapy layers and tools to study LoRa PHY and LoRaWAN

License

Notifications You must be signed in to change notification settings

PentHertz/LoRa_Craft

Repository files navigation

LoRa Craft

LoRa Craft is a small set of tools that aims to provide tools to assess LoRAPHY and LoRaWAN communications.

Available features:

  • Capture packet as PCAP and read them
  • Parses LoRaPHY and LoRaWAN 1.0 + 1.1 packets
  • Supports UpLink as well as DownLink
  • Can bruteforce Join-Request and Join-Accept MIC as well as Data Payload MIC
  • Can decipher payloads => not tested with real LoRaWAN 1.1 devices yet
  • Possible to generate packet at low LoRaPHY layer

Dependencies

  • Python 2 or 3
  • Scapy
  • GNU Radio 3.8
  • gr-lora from rpp0: link here
  • or gr-lorasdr (for TX & RX): link here
  • Software-Defined Radio equipment (USRP, bladeRF, RTL-SDR dongle, etc.)

Receive and decode packets packets

First you need to generate GNU Radio hierachical blocks lora_txrxdecode.grc, and lora_rechan.grc before running LoRa_MultiSF_decode_to_UDP.grc flowgraph located in the grc directory.

After that we can run the LoRa_MultiSF_decode_to_UDP.grc by connecting one the supported SDR device by the osmocom Source block:

alt text

Then we can run the decoder script that will automatically parse packet from the socket used by gr-lora and display it on the console as follows:

# python3 LoRa_PHYDecode-NG.py   

------------------------------>
<LoRa  Preamble=0x1 PHDR=0xe312 PHDR_CRC=0x0 MType=Unconfirmed Data Up RFU=0 Major=0 DevAddr=[<DevAddrElem  NwkID=0x6e NwkAddr=0x260117 |>] FCtrl=[<FCtrl_Link  ADR=1 ADRACKReq=0 ACK=0 UpClassB_DownFPending=0 FOptsLen=0 |>] FCnt=0 FPort=1 ULDataPayload="M\x93'\tT\xd6\xa4\x02\x8e\x0e9f\xdc\xfd\xec\x898" MIC=0x8ce72a63 CRC=0x978e |>

------------------------------>
<LoRa  Preamble=0x1 PHDR=0xe312 PHDR_CRC=0x0 MType=Unconfirmed Data Up RFU=0 Major=0 DevAddr=[<DevAddrElem  NwkID=0x6e NwkAddr=0x260117 |>] FCtrl=[<FCtrl_Link  ADR=1 ADRACKReq=0 ACK=0 UpClassB_DownFPending=0 FOptsLen=0 |>] FCnt=1 FPort=1 ULDataPayload='w\xf96\x98\x9f\x1a\x1e\x14\xa3\xac\xb4\xbe_X&\xa1\x81' MIC=0x43f31d41 CRC=0x6b0 |>

<------------------------------
<LoRa  Preamble=0x1 PHDR=0x3219 PHDR_CRC=0x0 MType=Unconfirmed Data Down RFU=0 Major=0 DevAddr=[<DevAddrElem  NwkID=0x6e NwkAddr=0x260117 |>] FCtrl=[<FCtrl_Link  ADR=0 ADRACKReq=0 ACK=0 UpClassB_DownFPending=0 FOptsLen=0 |>] FCnt=0 FPort=1 DLDataPayload="\xb9d\x8c\xf90'" MIC=0xd395a01e |>

Note we can see 2 uplink packets and 1 downling packet that got parsed by the tool

Generate packets

To generate packets, you can instantiate a Scapy packet as follows:

>>> from layers.loraphy2wan import *
>>> pkt = LoRa()
>>> pkt
<LoRa  Join_Request_Field=[''] |>

And start to fill it.

After crafting your packet, you can use python-loranode as follows:

>>> from binascii
>>> from loranode import RN2483Controller
>>> to_send = binascii.hexlify(str(pkt))[3:]
>>> c = RN2483Controller("/dev/ttyACM0")  # Choose the correct /dev device here
>>> c.set_sf(7)  # choose your spreading factor here
>>> c.set_bw(150) # choose the bandwidth here
>>> c.set_cr("4/8")  # Set 4/8 coding for example
>>> c.send_p2p(to_send)

Note that you should skip the first three bytes (Preamble, PHDR, PHDR_CRC), before sending it with send_p2p method.

LoRa crypto helpers

Few helpers have been implemented to calculate MIC field, encrypt and decrypt packets:

  • JoinAcceptPayload_decrypt: decrypt Join-accept payloads;
  • JoinAcceptPayload_encrypt: encrypt Join-accept payloads;
  • getPHY_CMAC: compute MIC field of a packet using a provided key;
  • checkMIC: check MIC of a packet against a provided key.
  • checkDATAMIC_1x: check MIC for FRMPayloads
  • bruteforceDATAMIC_10: bruteforce MIC for UL/DL FRMPayloads

Checking MIC for 'Join-request' packets

As an example, to check if the key 000102030405060708090A0B0C0D0E0F is used to compute MIC on the following Join-request, we can write a little script as follows:

>>> from layers.loraphy2wan import *
>>> from lutil.crypto import *
>>> key = "000102030405060708090A0B0C0D0E0F"
>>> p = '000000006c6f7665636166656d656565746f6f00696953024c49'
>>> pkt = LoRa(binascii.unhexlify(p))
>>> pkt
<LoRa  Preamble=0x0 PHDR=0x0 PHDR_CRC=0x0 MType=Join-request RFU=0 Major=0 Join_Request_Field=[<Join_Request  AppEUI='lovecafe' DevEUI='meeetoo' DevNonce=26985 |>] MIC=0x53024c49 |>
>>> checkMIC(binascii.unhexlify(key), bytes(pkt))
True

Deciphering a 'Join-accept' message

To check if 000102030405060708090A0B0C0D0E0F key is used to encrypt a Join-accept message, we can combine JoinAcceptPayload_decrypt and checkMIC as follows:

>>> pkt = "000000200836e287a9805cb7ee9e5fff7c9ee97a"
>>> ja = JoinAcceptPayload_decrypt(binascii.unhexlify(key), binascii.unhexlify(pkt))
>>> ja
'ghi#\x01\x00\xb2\\C\x03\x00\x00{\x06O\x8a'
>>> Join_Accept(ja)
<Join_Accept  JoinAppNonce=0x6fe14a NetID=0x10203 DevAddr=0x68e8cb1 OptNeg=0 RX1DRoffset=0x0 RX2_Data_rate=0x0 RxDelay=0x0 |<Padding  load='\xbejsu' |>>
>>> p = b"\x00\x00\x00\x20"+ja # adding headers
>>> checkMIC(binascii.unhexlify(key), p)
>>> True

Check and bruteforce the MIC of a FRMPayload

We want to check the MIC of the following captured packet containing an UL/DL data:

~>>> pkt
<LoRa  Preamble=0x1 PHDR=0xe312 PHDR_CRC=0x0 MType=Unconfirmed Data Up RFU=0 Major=0 DevAddr=[<DevAddrElem  NwkID=0x6e NwkAddr=0x260117 |>] FCtrl=[<FCtrl_Link  ADR=1 ADRACKReq=0 ACK=0 UpClassB_DownFPending=0 FOptsLen=0 |>] FCnt=0 FPort=1 ULDataPayload="M\x93'\tT\xd6\xa4\x02\x8e\x0e9f\xdc\xfd\xec\x898" MIC=0x8ce72a63 CRC=0x978e |>

If we already have the NwkSkey that is 2B7E151628AED2A6ABF7158809CF4F3C for example, we can use one of the checkDATAMIC_1x function (depending of LoRaWAN version) to check it:

~>>> checkDATAMIC_10(binascii.unhexlify("2B7E151628AED2A6ABF7158809CF4F3C"), bytes(pkt))
True

And see that decoded MIC (MIC=0x8ce72a63) with the Scapy layer matches the one processed by checkDATAMIC_1x function.

But in case we want to bruteforce this key, we can actually do it using the bruteforceDATAMIC_1x function by providing a list of key dictionnary path:

~>>> bruteforceDATAMIC_10(bytes(pkt), "/home/fluxius/Projects/LoRa/tools/LoRa_Craft/resources/keydict.lst")
Testing:  00000000000000000000000000000000

Testing:  00010101010101010101010101010101

Testing:  01234567890123456789012345678901

Testing:  000102030405060708090a0b0c0d0e0f

Testing:  00020202020202020202020202020202

Testing:  00030303030303030303030303030303

Testing:  00040404040404040404040404040404

Testing:  00050505050505050505050505050505

Testing:  00060606060606060606060606060606

Testing:  2B7E151628AED2A6ABF7158809CF4F3C

('Found NwkSKey: ', b'2b7e151628aed2a6abf7158809cf4f3c')

Warning: the check function also takes a 3rd argument that is the direction of the packet (UL or DL)

And we found the correct key! :) => so we can mess with packet's integrity now.

Decipher a FRMPayload

If we have been able to retrieve the key used to decipher the FRMPayload, we can try it using the decryptFRMPayload function as follows:

~>>> pkt
<LoRa  Preamble=0x1 PHDR=0xe312 PHDR_CRC=0x0 MType=Unconfirmed Data Up RFU=0 Major=0 DevAddr=[<DevAddrElem  NwkID=0x6e NwkAddr=0x260117 |>] FCtrl=[<FCtrl_Link  ADR=1 ADRACKReq=0 ACK=0 UpClassB_DownFPending=0 FOptsLen=0 |>] FCnt=0 FPort=1 ULDataPayload="M\x93'\tT\xd6\xa4\x02\x8e\x0e9f\xdc\xfd\xec\x898" MIC=0x8ce72a63 CRC=0x978e |>
~>>> decryptFRMPayload(binascii.unhexlify("2b7e151628aed2a6abf7158809cf4f3c"), bytes(pkt))
b'<3Trend with Love\xed@W`f/;\xafL\xff\x04\xd0\xb5\xb83'

Warning: the check function also takes a 3rd argument that is the direction of the packet (UL or DL)

Further work

  • Test MIC bruteforcing and deciphering on LoRaWAN 1.1

About

Some Scapy layers and tools to study LoRa PHY and LoRaWAN

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages