In this article we document a complete reverse engineering exercise applied to the RG600 Pro drone, a model that uses Wi-Fi and RF communication to receive commands from a mobile application and remote control. Without official documentation of the protocol, we carry out a practical analysis that goes from capturing UDP packets in real time, to reconstructing the commands used by the original controller.
Initial recognition
Our first step is to recognize in the air valuable information that we can capture from the drone, for that we start by performing a scan of wireless networks and recognize the name of the network of the device.
We start by placing our network card in monitor mode and scanning the networks.
sudo airmon-ng start wlan0

sudo airodump-ng wlan0
CH 1 ][ Elapsed: 24 s ][ 2025-07-15 16:41
BSSID PWR Beacons #Data, #/s CH MB ENC CIPHER AUTH ESSID
28:BB:ED:FD:60:D0 -11 9 0 0 4 65 OPN FLOW_8K_FD60D0
BSSID STATION PWR Rate Lost Frames Notes Probes
E4:84:2B:14:EF:8A 4C:57:39:85:37:DE -1 1e- 0 0 7
We have our drone with the following information BSSID 28:BB:ED:FD:60:D0
with ESSID FLOW_8K_FD60D0
and the communication channel 4
Drone Wi-Fi channel selection
sudo iwconfig wlan0 channel 4
This command forces the Wi-Fi interface (wlan0) to specifically tune to channel 4.
In multi-channel environments, selecting the correct channel ensures that we will not lose packets from the drone.
Live capture of packets filtering only the drone
sudo tcpdump -i wlan0 ether host 28:bb:ed:fd:60:d0 -s 0 -w - | wireshark -k -i -
We capture specific packets from the drone using its MAC address, We analyzed these packets in Wireshark to identify the proprietary protocol used by the drone.
UDP packet analysis
Once we are capturing the traffic from our drone, the first thing we notice is that there is a lot of traffic with packet sizes 124, 88, 1080.
After making manual adjustments to the RG600 Pro drone application – such as lever up, capture photo or stop propellers – it was observed that commands are transmitted via UDP packets directed to the drone’s port 8800.

General Packet Structure

Packets have a fixed length of 124 bytes, and can be divided into three main segments:
- Protocol header (Bytes 0-11).
- Control block (Bytes 12-31)
- Filling/Padding and additional status (Bytes 32-123)
Protocol Header
Bytes: ef 02 7c 00 02 02 00 01 02 00 00 00
Byte(s) | Description |
---|---|
ef 02 | Protocol identifier |
7c 00 | Possible length or type of frame |
02 02 00 | Subtype or Transaction ID |
01 02 | Flags or version |
00 00 00 | Padding |
This segment is constant in all packages and serves as a start marker and synchronization structure with the drone.
Control Block (bytes 12 to 31)
It starts with byte 0x66, which acts as the delimiter of the control block.
66 14 80 80 80 80 02 02 00 00 00 99
Offset | Byte | Function |
---|---|---|
12 | 66 | Start of the control block |
13 | 14 | Version / Flag (fixed value) |
14–17 | 80 80 80 80 | RC values (roll, pitch, throttle, yaw) centered (0x80) |
18 | 02 | Command sent |
19 | 02 | Headless mode” status |
20–22 | 00 00 00 | Filled or reserved |
23 | 99 | End of control block |
Identified Commands (Byte 18)
Value | Action |
---|---|
01 | Takeoff |
02 | Emergency stop |
03 | Landing |
04 | Calibrate gyroscope |
Headless Mode (Byte 19)
Value | Status |
---|---|
02 | Deactivated |
03 | On (headless mode) |
Terminator (Byte 23)
Byte 99
acts as the end of the control block, probably to delimit the packet for the drone parser.
Checksum (to be confirmed)
In some packages a byte appears immediately after 0x99
, which appears to be an XOR of the bytes from RC to headless mode. It still requires full validation.
In that vein, the key observations
- The packets have an extremely consistent format.
- It has been validated that the drone control signal does not require authentication or encryption.
- Direct manipulation of the control block allows commands such as takeoff, landing or emergency stop to be sent.
Another important point is the following:

The application also sends a packet to the drone to start the video transmission. The packet is always the same and has only 4
bytes 0xef 0x00 0x04 0x04 0x00
. This will open a port on the drone and the video stream will be sent to that port (the port ) :1234
.
Once the transmission has been activated, the drone starts sending its video signal to the mobile application through UDP port 1234.

Although it has not yet been possible to fully decode the content, everything indicates that the stream may be encoded in H.264, according to the pattern of the captured packets. Investigations are still ongoing to ensure correct display and extraction.
Detailed sections
Finally, after a little more research, there is a detailed section on how packages should travel.
Section | Bytes | Description |
---|---|---|
HEADER | 12 bytes | Identifier and base protocol. Fixed: ef 02 7c 00 02 02 00 01 02 00 00 00 . |
COUNTER | 2 bytes | Counter that increments with each shipment. Helps the drone to detect lost packages. |
CONST1 | 6 bytes | Fixed part: 00 00 14 00 66 14 |
CONTROL | 6 bytes | Critical data: ROLL, PITCH, THROTTLE, YAW, COMMAND, HEADLESS |
CONTROL_SUFFIX | 10 bytes | Control suffix: always 00 |
CHECKSUM | 1 byte | XOR of the 6 bytes of the CONTROL block |
CHECKSUM_SUFFIX | 28 bytes | Security or validation suffix. Contains part of the visual handshake. |
COUNTER_2 | 2 bytes | Same counter as above but in a different position. Reinforces reliability. |
COUNTER_SUFFIX | ~26 bytes | Padding and completion. It can have fixed values such as ff ff ff ff ff ff ff |
Checksum calculation
One thing we were not clear about was the checksum calculation.
The checksum is the XOR
of these 6 bytes
of the control block:
checksum = roll ^ pitch ^ throttle ^ yaw ^ command ^ headless
This value is placed just after the CONTROL_SUFFIX block, and before the CHECKSUM_SUFFIX block.
Initial Handshake
Before sending commands, it is necessary to perform a visual handshake for the drone to enable communication. This is accomplished by sending repeatedly (for ~1 second):
bytearray([0xef, 0x00, 0x04, 0x00])
This message is sent by UDP to port 8800
and establishes the session. If this step is not done, the drone will ignore the packets.
The process followed was:
- Connection to the drone via its open Wi-Fi network.
- Traffic capture using Wireshark on the cell phone while using the app.
- UDP packet filtering to port 8800.
- Observation of commands while calibrating, taking off, etc., and byte comparison.
- Static and dynamic pattern confirmation (e.g., counter and checksum).
- Development of script that reproduces valid messages.
- Empirical validation with the real drone (ran
calibrate
,takeoff
,land
, etc.).
The code
Finally, the code that reflects all of the above is the following.
ﲵ tree
.
├── drone
│ └── core.py
└── main.py
2 directories, 2 files
import socket
import time
class Drone:
def __init__(self, ip="192.168.169.1", port=8800):
self.ip = ip
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.header = bytearray([0xef, 0x02, 0x7c, 0x00, 0x02, 0x02, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00])
self.control_suffix = bytearray([0x00] * 10)
self.checksum_suffix = bytearray([0x99] + [0x00]*43 + [0x32, 0x4b, 0x14, 0x2d, 0x00, 0x00])
self.counter_suffix = bytearray([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xff, 0xff,
0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00,
0x00, 0x00
])
self.counter = 0
def initialize_image(self):
init_msg = bytearray([0xef, 0x00, 0x04, 0x00])
self.sock.sendto(init_msg, (self.ip, self.port))
def _build_message(self, command):
roll = pitch = throttle = yaw = 128
headless = 2
control = bytearray([roll, pitch, throttle, yaw, command, headless])
checksum = bytearray([roll ^ pitch ^ throttle ^ yaw ^ command ^ headless])
counter = bytearray([self.counter % 256, (self.counter // 256) % 256])
self.counter += 1
return (
self.header +
counter +
bytearray([0x00, 0x00, 0x14, 0x00, 0x66, 0x14]) +
control +
self.control_suffix +
checksum +
self.checksum_suffix +
counter +
self.counter_suffix
)
def send_command(self, command):
msg = self._build_message(command)
self.sock.sendto(msg, (self.ip, self.port))
print("\n=== Sending command 0x{:02X} ===".format(command))
print(f"[+] Payload complete ({len(msg)} bytes): {msg.hex(' ')}")
# Desglose por secciones
print("\n--- Breakdown of the message ---")
print("HEADER :", self.header.hex(' '))
print("COUNTER :", msg[12:14].hex(' '))
print("CONST1 :", msg[14:20].hex(' '))
print("CONTROL (6B) :", msg[20:26].hex(' '))
print("CONTROL_SUFFIX :", msg[26:36].hex(' '))
print("CHECKSUM :", msg[36:37].hex(' '))
print("CHECKSUM_SUFFIX :", msg[37:65].hex(' '))
print("COUNTER (rep) :", msg[65:67].hex(' '))
print("COUNTER_SUFFIX :", msg[67:].hex(' '))
print("-----------------------------\n")
import time
from drone.core import Drone
drone = Drone()
print("[*] Sending handshake...")
for _ in range(10):
drone.initialize_image()
time.sleep(0.1)
while True:
print("\nAvailable commands:")
print("1. Calibrate")
print("2. Take off")
print("3. Land")
print("4. exit")
option = input("Select an option: ")
if option == "1":
drone.send_command(0x04) # calibrate
print("[*] Calibrating...")
elif option == "2":
drone.send_command(0x01) # takeoff
print("[*] Taking off...")
elif option == "3":
drone.send_command(0x03) # land
print("[*] Landing...")
elif option == "4":
print("Leaving...")
break
else:
print("Invalid option.")
You can see the code in action.
Conclusion
This research demonstrated how, through traffic analysis and reverse engineering techniques, it is possible to intercept and replicate the control commands of a commercial drone such as the RG600 Pro. Through the unencrypted UDP protocol, we were able to take complete control from a Python script, evidencing the absence of security measures in its communication channel.
Leave a Comment