6.1 KiB
LoRa Craft
LoRa Craft is a small set of tools to receive signals with SDR, decode et craft LoRaWAN packets on top of a gr-lora GNU Radio module.
This repository will be completed with other tools soon, depending on needs during assessments :)
Dependencies
- Python 2 or 3
- Scapy
- GNU Radio 3.8
- gr-lora from rpp0: link here
- Software-Defined Radio equipment (USRP, bladeRF, RTL-SDR dongle, etc.)
Receive signal and decode its data
Receive
To receive a signal, an example of a GRC schema is available in the folder grc_examples/usrp_LoRa_decode_to_UDP.grc
for USRP, as shown as follows:
The channel frequency, as well as the spreading factor and the bandwidth, must be set correctly to valid values with the help of the FFT and waterfall sinks:
Note: Multiple frequencies can be used by targets. This would imply to include multiple receivers in GRC.
For more information on how to detect LoRa signal, please take a look at the following post: https://penthertz.com/blog/testing-LoRa-with-SDR-and-handy-tools.html.
Decode
Once the receiver is running with the SDR equipment, we use the script LoRa_PHYDecode.py
:
$ python LoRa_PHYDecode.py -h 1 ↵
usage: LoRa_PHYDecode.py [-h] [-p PORT] [-i IFACE]
Monitor and decode MAC PHY packets.
optional arguments:
-h, --help show this help message and exit
-p PORT, --port PORT TAP PORT to listen on (default: UDP 40868)
-i IFACE, --iface IFACE
Interface to monitor (default: local)
By default, the script can be run as follows to decode received LoRa frames:
$ sudo python LoRa_PHYDecode.py
<LoRa Preamble=0x1 PHDR=0x631e PHDR_CRC=0x0 MType=Unconfirmed Data Up RFU=0 Major=0 DevAddr=[<DevAddrElem NwkID=0xad NwkAddr=0x600015 |>] FCtrl=[<FCtrl_UpLink ADR=0 ADRACKReq=0 ACK=0 ClassB=0 FOptsLen=0 |>] FCnt=0 FPort=2 DataPayload='i\x06D\x94\x97\x08\xce!\xd9' MIC=0x4b516899 CRC=0x96e1 |>
...
<LoRa Preamble=0x1 PHDR=0x631e PHDR_CRC=0x0 MType=Unconfirmed Data Up RFU=0 Major=0 DevAddr=[<DevAddrElem NwkID=0xad NwkAddr=0x600015 |>] FCtrl=[<FCtrl_UpLink ADR=0 ADRACKReq=0 ACK=0 ClassB=0 FOptsLen=0 |>] FCnt=0 FPort=2 DataPayload='penthertz' MIC=0x20a5fcba CRC=0xcdc |>
<LoRa Preamble=0x0 PHDR=0xd30c PHDR_CRC=0x0 MType=Confirmed Data Up RFU=0 Major=0 DevAddr=[<DevAddrElem NwkID=0xad NwkAddr=0x600015 |>] FCtrl=[<FCtrl_UpLink ADR=0 ADRACKReq=0 ACK=0 ClassB=0 FOptsLen=1 |>] FCnt=0 FOpts_up=[<MACCommand_up CID=LinkCheckReq LinkCheck=[''] |>] FOpts_down=[<MACCommand_down CID=222 |>] FPort=92 DataPayload='' MIC=0x31c753f |>
<LoRa Preamble=0x0 PHDR=0xd30c PHDR_CRC=0x0 MType=Confirmed Data Up RFU=0 Major=0 DevAddr=[<DevAddrElem NwkID=0xad NwkAddr=0x600015 |>] FCtrl=[<FCtrl_UpLink ADR=0 ADRACKReq=0 ACK=0 ClassB=0 FOptsLen=1 |>] FCnt=0 FOpts_up=[<MACCommand_up CID=LinkCheckReq LinkCheck=[''] |>] FOpts_down=[<MACCommand_down CID=222 |>] FPort=92 DataPayload='' MIC=0x31c753f |
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 packet:
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.
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), str(pkt))
True
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
TODO
- More helpers for other types of payloads
- Implement helpers to transmit signal with dongles more easily
- Transmit packets with SDR
- Support gr-lora from Bastille: link here
Feel free to contribute if you have cool scripts/tools to share :)!