https://github.com/PentHertz/LoRa_Craft
Some Scapy layers and tools to study LoRa PHY and LoRaWAN
https://github.com/PentHertz/LoRa_Craft
Last synced: 4 months ago
JSON representation
Some Scapy layers and tools to study LoRa PHY and LoRaWAN
- Host: GitHub
- URL: https://github.com/PentHertz/LoRa_Craft
- Owner: PentHertz
- License: gpl-3.0
- Created: 2020-01-15T09:16:59.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2025-11-13T18:12:11.000Z (7 months ago)
- Last Synced: 2026-02-09T13:36:22.296Z (4 months ago)
- Language: Python
- Size: 813 KB
- Stars: 120
- Watchers: 12
- Forks: 19
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-connected-things-sec - LoRa Craft - Packet Interception
- awesome-telco - LoRa_Craft - 11]` - Some Scapy layers and tools to study LoRa PHY and LoRaWAN. (Satellite Communication / SMPP / SMS Gateways)
README
# 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](https://github.com/rpp0): [link here](https://github.com/rpp0/gr-lora)
* or gr-lorasdr (for TX & RX): [link here](https://github.com/tapparelj/gr-lora_sdr)
* 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:

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:
```bash
# python3 LoRa_PHYDecode-NG.py
------------------------------>
] FCtrl=[] FCnt=0 FPort=1 ULDataPayload="M\x93'\tT\xd6\xa4\x02\x8e\x0e9f\xdc\xfd\xec\x898" MIC=0x8ce72a63 CRC=0x978e |>
------------------------------>
] FCtrl=[] FCnt=1 FPort=1 ULDataPayload='w\xf96\x98\x9f\x1a\x1e\x14\xa3\xac\xb4\xbe_X&\xa1\x81' MIC=0x43f31d41 CRC=0x6b0 |>
<------------------------------
] FCtrl=[] 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:
```python
>>> from layers.loraphy2wan import *
>>> pkt = LoRa()
>>> pkt
```
And start to fill it.
After crafting your packet, you can use [python-loranode](https://github.com/rpp0/python-loranode) as follows:
```python
>>> import 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:
```python
>>> from layers.loraphy2wan import *
>>> from lutil.crypto import *
>>> key = "000102030405060708090A0B0C0D0E0F"
>>> p = '000000006c6f7665636166656d656565746f6f00696953024c49'
>>> pkt = LoRa(binascii.unhexlify(p))
>>> pkt
] 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:
```python
>>> 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)
>
>>> 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:
```python
~>>> pkt
] FCtrl=[] 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:
```python
~>>> 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:
```python
~>>> 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:
```python
~>>> pkt
] FCtrl=[] 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