DJI Protocol – Day 4 – Progress Report

We dived into a specific packet-type last time, but couldn’t extract each and every byte purpose. However, we were able to identify one important thingy: 0x00-0x01. Those two bytes indicate the packet’s content. Hence, now we are capable of assigning each sent packet (operator to drone) to a specific group, based on their unique identifier. This will hopefully create a better overview and will facilitate our future reverse-engineering process. In order to group packets, we came up with a .NET 5.0 console application, capable of reading .pcap files and extracting the most important bits.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Tako.CRC;

public class NetworkPacket : IEqualityComparer<NetworkPacket>
{
    public NetworkPacket(byte[] data) => Data = data;

    public byte[] Data { get; init; }

    // 14 bytes
    public byte[] Ethernet => Data[0..14];

    // 20 bytes
    public byte[] IP => Data[14..34];

    // 8 bytes
    public byte[] UDP => Data[34..42];

    public byte[] Payload => Data[42..];

    public static string ToHexString(byte data) => ToHexString(new byte[] { data });

    public static string ToHexString(byte[] data) => string.Join(" ", data.Select(d => $"0x{d.ToString("X2")}"));

    public string Src => $"{(uint)Data[26]}.{(uint)Data[27]}.{(uint)Data[28]}.{(uint)Data[29]}";

    public bool Equals(NetworkPacket x, NetworkPacket y) => x.Data.SequenceEqual(y.Data);

    public int GetHashCode([DisallowNull] NetworkPacket obj) => HashCode.Combine(obj.Data);
}

public class DjiPacket
{
    private const EnumCRCProvider _crc_16_provider = EnumCRCProvider.CRC16Kermit;
    private static CRCManager _crc_manager = new CRCManager();

    public DjiPacket(byte[] data) : this(new NetworkPacket(data)) { }

    public DjiPacket(NetworkPacket networkPacket) => NetworkPacket = networkPacket;

    public NetworkPacket NetworkPacket { get; init; }

    public byte Id => NetworkPacket.Payload[0];

    public byte Length => NetworkPacket.Payload[0];

    public byte ProtocolVersion => NetworkPacket.Payload[1];

    public byte[] Session => NetworkPacket.Payload[2..4];

    public static byte[] CRC_16(byte[] data) => _crc_manager.CreateCRCProvider(_crc_16_provider).GetCRC(data).CrcArray;
}

Grouping packets based on their Id leads to following taxonomy:

IdentifierPayload lengthOccurrence
0x1E30177
0x2133205
0x2234104
0x233513
0x24364
0x253727
0x26386
0x284010
0x29414
0x2A4220
0x2B439
0x2D454
0x2E461
0x2F472
0x30484
0x32506
0x3856685
0x39571
0x3C601
0x3D612
0x3E624
0x4D771
0x51811
0x791216

To avoid yet another deep dive into the bits and bytes of each and every listed packet type, we try to provoke specific types of packets to be sent. E.g. moving the drone around its own axis should result in a “rotate” packet type request. As we don’t know which packet type relates to which input action, we require a statistic based approach. We setup two scenarios. 1.) Connect the drone for 20 seconds and provide zero input. 2.) Connect the drone and constantly move the drone in a circle for 20 seconds. Then we should see a higher occurance of the rotation packet type. In order to exclude unrelated packets, we apply following filter within wireshark:

ip.src == 192.168.12.139 && udp && !dns && !icmp && !mdns
IdentifierPayload lengthOccurrence
0x1E30986/351
0x213354/31
0x223492/40
0x253730/20
0x2A4238/1
0x2C442/1
0x2E466/4
0x325017/17
0x36545/1
0x3856360/1175
0x3A584/1
0x3E6214/2
0x40644/2
0x44684/2
0x46702/1
0x4C7610/3
0x54845/3
0x56861/1
0x5E942/2
0x48720/1
0x60960/1

Packets only occurring on idle control mode haven’t been listed within the table above, as they are likely not relevant. Upon a brief comparison, the packet type 0x38 seems to be of our interest. Conveniently its size is unique across all packets, such that we can investigate within wireshark directly.

Data comparison between frame number 46 and 47
AddressBytesFindings
0x001Packet Identifier / Packet Length – All packets with an ethernet frame-length of 72 bytes start with the static byte sequence of: 0x38
0x011Protocol Version
0x02 – 0x032Session IdentifierValue retrieved by Handshake at position 0x02 0x03
0x04 – 0x052Padding – Zero bits
0x06 – 0x072???
0x081Mirror – See 0x0A
0x091Mirror – See 0x0B
0x0A – 0x0B2Drone Variable – Will be updated by drone (See Fig. Drone update)
0xC0 0x85 0x6C 0x21 0x68 0x0B 0x02 0x69 0xINIT 0xINIT 0xNEW_VAL 0xNEW_VAL

Initial value obtained after first Packet 0x30
0x08 = 0x0A
0x09 = 0x0B
0x0C – 0x0D2Padding – Zero bits
0x0E – 0x114??? – Changes rarely

Variable shared by packets
0x1e, 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x30, 0x3a, 0x3c, 0x3e, 0x40, 0x42, 0x44, 0x46, 0x4a, 0x4c, 0x50, 0x52, 0x58

Initial value obtained after first Packet 0x30

0x0E = 0x08
0x0F = 0x09
0x10 = 0x08
0x11 = 0x09
0x12 – 0x132Padding – Zero bits
0x14 – 0x152Dependent Variable – Will be updated after sending Packet
0x1e, 0x21 0x22, 0x24, 0x25, 0x29, 0x2a, 0x2b, 0x2e, 0x32

0x14 = 0x08
0x15 = 0x09

Initial value obtained after first Packet 0x30

Assumption, 0x14 and 0x15 are variables carried by many packets, including 0x38
0x16 – 0x172Dependent Variable – Will be updated after sending Packet
0x1e, 0x21, 0x22, 0x24, 0x25, 0x28, 0x29, 0x2b, 0x2e, 0x30, 0x32

0x16 = 0x0A
0x17 = 0x0B

Initial value obtained after first Packet 0x24

Assumption: 0x16 and 0x17 are variables carried by many packets, including 0x38.
0x18 – 0x2312 ???* { 0x00, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x55, 0x1A, 0x04, 0xB1, 0x02, 0x09 }
0x24 – 0x252 Exclusive Counter – Little Endian
0x26 – 0x2D8???* { 0x40, 0x01, 0x02, 0x00, 0x00, 0x04, 0x20, 0x00 }
0x3E – 0x3F2Rotation direction – Left: { 0x0D, 0x05 } Right: { 0xF3, 0x0A } Idle: { 0x01 0x08 }
0x30 – 0x345Padding – Zero bits
0x351Operator -> Drone: 0x06
Drone -> Operator: 0x01
0x36 – 0x372CRC-16 KERMIT – from 0x24 to incl. 0x35, Little Endian byte order
Packet structure of the 0x38 packet payload

* Might be: ACK/CMD/Type/Encryption/..?

The hard part of the reverse-engineering was by far the CRC checksum. The last two bytes did never equal, hence it had to be some kind of CRC. However, since the CRC’s poly-function, final XOR,… could be chosen arbitrary but fixed within the handshake, we had to bruteforce our way to the result. However, the bruteforce-crc didn’t return any useable values and thus, lead to the conclusion that the CRC might not cover the full payload. As the first 8 bytes yield the same content within each rotation-control packet, the CRC might not include the whole header of the payload. Therefore, we removed bit by bit as long as the CRC hasn’t been found. We concluded, that the bytes from 0x00 – 0x23 represent the header of the packet and isn’t refelcted within the payload’s CRC function.

The drone did only respond once to a rotation packet with the 0x35 byte set to 0x01. Hence, rotation packets do not require an acknowledgement from the drone. Assumption: the byte at position n – 2 might indicate whether the packet requires an acknowledgment. (n = payload-length)

Next up: DJI Protocol – Day 5 – Progress Report

Drone update
0 0 votes
Article Rating
Subscribe
Notify of
4 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

[…] those new features we were able to extend our findings on the packet 0x38. As our findings are limited to our current knowledge, we do require to inspect other packets to […]

[…] already got some hints and clues within our last packet inspection about a possible drone-rotation command. However, the reverse-engineering process is quite intense […]

[…] Next up: DJI Protocol – Day 4 – Progress Report […]

trackback

[…] Altitude + Rotation […]