The write up for this project is split up into 4 parts – part 1: hacking the controller, part 2: decoding the protocol, part 3: the protocol definition, and part 4: hacks using the protocol. That way you can just jump to whatever interests you, or follow the whole thing first start to finish. There’s also a bonus post on achieving a similar goal by emulating the controller joysticks using an Arduino Uno.
- 1 Introduction
- 2 Creating A Test Regime
- 3 Decoding The Data
- 4 Step 1: Transceiver Setup
- 5 Step 2: Binding Choreography
- 6 Step 3: Reversing the checksum algorithm
- 7 Step 4: Identifying the flight control data
- 8 Conclusion
In Part 1 we were able to reverse engineer how the controller’s MCU and the Transceiver communicate, and successfully sniff and decode the SPI commands being sent between the two. Now we need to reverse engineer the protocol and configuration required to communicate with the Hubsan X4. Using some basic tools like Microsoft Excel, key information from the data sheet, and a methodical testing approach, we can completely identify the Hubsan protocol and all the configuration parameters we need to set up the Transceiver properly.
Creating A Test Regime
Before we jump into pulling apart the data we need to design a test regime – this will help us save time and give us an empirical basis for understanding the changes in the data. All it really boils down to is having a documented order of control operations so we can understand what any changes in the data are attributable to. Here’s the test regime I used for the Hubsan X4:
1. Ensure all trim values are zeroed out. 2. Start Logic Analyzer (500MS @ 24MHz). 3. Power On Controller 4. Power On Quadcopter (check binding) 5. Throttle up to 100% and back to 0% 6. Yaw LEFT 100% and back to center (+ Throttle to ~50%) 7. Yaw RIGHT 100% and back to center (+ Throttle to ~50%) 8. Pitch FORWARD 100% and back to center (+ Throttle to ~50%) 9. Pitch BACKWARD 100% and back to center (+ Throttle to ~50%) 10. Roll LEFT 100% and back to center (+ Throttle to ~50%) 11. Roll RIGHT 100% and back to center (+ Throttle to ~50%) 12. Stop Logic capture.
Additionally, the Hubsan has an “Expert” flying mode that appears to make it more agile and able to do acrobatic tricks like flips – since this mode appears to affect all the normal flight controls, I also performed a second test of all of the above, but added the step of putting the quad into Expert mode between step 4 and 5. A final test was done to specifically test things like turning the LEDs on the X4 on and off, and whether expert mode is communicated or not.
Decoding The Data
At the end of Part 1 we had successfully configured the Saleae Logic software to decode the SPI commands from the debug port. Now we need to take the next step and look at the capture in an easier to read way. Let’s start by exporting the decoded SPI byte values: This gives us the raw data in a comma-separated text format. Importing this into Microsoft Excel (or your favorite open-source alternative…) gives us a nice set of tabular data to work with:
From here we can use a bunch of Excel/spreadsheet tricks to pick apart the data. Spreadsheet software might not immediately spring to mind when you think about reverse-engineering, but it often has a ton of very powerful tools like formulas and macros that make our job a lot easier. To make it easier to explain, I’ll talk about the rationale for the decoding and then how to use Excel formulas to scale the analysis section by section.
The A7105 SPI Communication Format
Section 10 (starting page 30) of the AMIC A7105 Datasheet is the key to pulling out all the interesting details in our capture. The transceiver has 2 main types of command: control register commands, and strobe commands.
Control Register commands have 2 modes: Read and Write. The way Control Register commands are sent is an Address byte, followed by at least one Data byte. The Address byte looks like this:
- Bit 0 – “CMD” – this bit tells the A7105 whether it’s a Control Register command (a “0”), or a Strobe command (a “1”).
- Bit 1 – “R/W” – Read the register, or write to it.
- Bits 2-7 – “Address” – the address of the register to read or write, between x00 and x32 (0-50).
An example would be:
- CMD = Control Register Command 
- R/W = Write 
- Address = ID DATA Register [06h]
So this command would tell the A7105 to write the next set of bytes to the ID DATA register.
Strobe commands are special messages that tell the A7105 to switch modes in its state machine. There are 8 different strobes, and they are signaled with a “1” in the CMD position of the Address byte:
Here’s an example of a TX mode strobe:
Transmitting and Receiving Packets
The register and strobe commands are the building blocks for interacting with the transceiver, so it’s worth talking about how they come together to allow us to send and receive whole data packets. There are a number of transmission modes supported by the A7105, but I’ll just talk about the simplest one (that the Hubsan X4 happens to use) – “easy FIFO”.
The A7105 has a First-In, First-Out (FIFO) buffer that is used to store data either TO be sent, or just RECEIVED. Once the transceiver is set up, to send a packet of data you have to first write the bytes to be sent to register 05h – the FIFO buffer. We do this by first sending a Control Register Address Byte of 0x05 followed by 16 data bytes. Once the data is in the buffer, sending a Transmit (TX) Strobe will make the Transceiver broadcast the contents of the FIFO buffers. Similarly, to receive data you send a RX strobe then read from register 05h.
We can use this pattern of activity surrounding the FIFO (05h) register to find the packets that are being sent and received within our data captures.
A guide to the tabs:
SPI Sniffing – Register Decode: a step-by-step breakdown of the SPI commands – the first step in decoding the protocol.
Checksum Decoding: the steps taken to decode the frame checksum algorithm.
Control Decode – Raw SPI: Default controls test regime.
Control Decode – Packets: Default controls test regime – data packets.
Expert – Raw SPI: Expert controls test regime.
Expert – Packets: Expert controls test regime – data packets.
Addtl Controls – Raw SPI: Miscellaneous control decoding
Addtl Controls – Packets: Miscellaneous control decoding – packets.
Rather than explain the ins and outs of every formula, I’m just going to put them here and you can use the spreadsheet above to see how they work in practice.
Raw SPI Data Formulas
These formulas are used on the data exported straight out of Logic.Expand the formulas!
Convert Hex to Binary:
Convert the byte to padded 8 bits, ignoring the “0x”.
Human-Readable Address Byte Type:
Identify if the byte is a Control Register, RegisterDATA, or Strobe message.
Human-Readable Read/Write flag:
If the byte is a Control Register message, identify if the operation is Read or Write, or DATA/Strobe.
Control Register Address:
If the byte is a Control Register Address byte, extract the hex Address.
Build TX/RX Packet Byte Sequences:
=IF(G2="05",SUBSTITUTE(CONCATENATE(A2," - ",F2," - ",C3,C4,C5,C6,C7 ,C8,C9,C10,C11,C12,C13,C14,C15,C16,C17,C18),"0x",""),"")
If the message is a Read/Write to the FIFO buffer register (05h), build a text string out of the next 16 bytes, plus time stamp and read/write.
Data Packet Formulas
These formulas are used to deconstruct the data packets built from the SPI reads/writes to the FIFO buffer.Expand the Formulas!
Break up Packet into component numbered hex byte values:
For byte 1: =MID(C2,1,2) For byte's 2: =MID($C2,E$1+D$1,2) [...]
These formulas use the numbered column headers for the bytes 1-16 to index along the Packet data and chop out the correct hex byte.
Convert the identified control bytes into decimals:
Throttle: =HEX2DEC(F2) Yaw: =HEX2DEC(H2) Pitch: =HEX2DEC(J2) Roll: =HEX2DEC(L2)
Once we identified the byte values that correspond to the changes, decode them into more human-readable control values.
Using these formulas we can quickly see what the command register is, whether it’s a read/write command, and even the data frames to be transmitted. Time to figure out what it all means…
Step 1: Transceiver Setup
If we’re going to fully understand how the X4 communicates, the handset’s setup of its A7105 radio is the best place to look for all the fine details – especially if we plan to re-implement the protocol later (we do). Time to decode the SPI register commands using the A7105 datasheet… (this is a lot of manual effort, but you can make it fun if you use a lot of nice colours 🙂 ).
[See “SPI Sniffing – Register Decode tab of the spreadsheet]
Once we’ve decoded the commands and colour-coded them, clear patterns emerge that are quickly recognisable as the different stages of the setup procedure:
Stage (1) – The first stage consists of configuring most of the radio parameters including the I/O pins, frequency and channel configuration, and the initial ID code. Many of the commands are simply resetting the register values and initialising the registers ready for operation.
Stage (2) – This section appears to consist of the IF Filter Bank Calibration procedure (as described on page 63 of the A7105 datasheet).
Stage (3) – This section appears to consist of the VCO Bank Calibration procedure (as described on page 64 of the A7105 datasheet).
Stage (4) – This section also appears to consist of the VCO Bank Calibration procedure, but this time setting the auto-calibration register. Not entirely sure what this bit is about.
Stage (5) – This section appears to be the preparation for Stage 6 (the channel selection procedure). It consists of setting the channel to the first available channel (14h) and setting the Received Signal Strength Indicator (RSSI) detection settings.
Stage (6) – This section is the channel selection procedure’s listen phase. It appears that in order to choose the appropriate channel, the Hubsan X4 monitors the 12 available channels (14, 1E, 28, 32, 3C, 46, 50, 5A, 64, 6E, 78, and 82) to detect which has the strongest signal strength. This is done by switching to the channel under test, sampling the RSSI value 15 times, then calculating the average (mean) of the results. Once all 12 channels have been tested, the channel with the highest RSSI is selected for good, the transmit power is set, and the receiver gain is configured.
Stage (7) – Stage 7 is the commencement of the handset discovery announcement transmissions. From here the controller broadcasts announce packets as the first step in the binding handshake protocol.
Step 2: Binding Choreography
Once the transceiver is set up, the handset begins the binding sequence. This consists of an escalating handshake between the handset and the quadcopter as follows:
- Discovery: The handset broadcasts discovery packets until an X4 quadcopter responds. The discovery packets contain a random 4-byte session ID in bytes 3-6. Byte 2 is the channel selected during the set-up stage. Byte 1 is the binding sequence number. Byte 16 is a checksum – more on this later.
- Handshake 1: The handset and quadcopter exchange handshake packets with an incrementing sequence number.
- Session ID: The handset changes it’s transceiver ID Code to the random session ID.
- Handshake 2: The handset and quadcopter exchange handshake packets with an incrementing sequence number (again). This time Bytes 7-8 contain \x03\x07, but I’m not sure why.
- Tx/Rx Power: Transmit power and Receive gain is set.
- Handshake 3: The handset and quadcopter exchange handshake packets with an incrementing sequence number (again – a different structure this time). The controller uses \x09 in Byte 1, whilst the quad uses \x0A. The alternating incrementing sequence numbers are sent in Bytes 2-3. Once the controller has incremented all the way to \x09 the binding is complete and flight control packets start flowing.
To illustrate this, here’s an example binding sequence:
Step 3: Reversing the checksum algorithm
The control packets are terminated with a 1-byte checksum value at Byte 16. For those who are unfamiliar, checksums are regularly used to determine if a packet has been corrupted in-flight in some way (due to noise, etc…). They are computed when the packet is constructed and sent, then recomputed by the receiver to see if the checksum matches. There are a huge number of algorithms that can be used to solve this problem, ranging from the simple parity check to cryptographic hashing. In embedded systems and simple RF protocols where space and computing power is an absolute premium, we can expect that quite a simple algorithm is used here.
Testing a basic XOR parity algorithm doesn’t have much success, so lets take a look at the data en-masse and try to spot some clues – time for a spreadsheet!
[See “Checksum Decoding” tab of the spreadsheet]
The aim of the game here is to try and look for trends that hint at the nature of the algorithm – kind of like imagining you have an algebra problem where the answer is “8”, x = 2, y = 4, so we can guess the equation is probably x * y. By breaking out the likely building blocks of the checksum algorithm into their own columns, we can quickly see trends appearing:
– The sum of every byte in the packet (including the checksum) always equals a multiple of 256 (e.g. 512, 768, 1024). In particular, the checksum is always the number that needs to be added to make a multiple of 256.
– Incrementing one byte by a single bit (such as in the binding handshakes) decreases the checksum value by exactly 1.
From these observations we can reasonably guess that the value of the checksum is simply the difference between the sum of the data bytes (1-15) and the next nearest multiple of 256. We can work out this difference by dividing the sum of the data bytes by 256 and subtracting the remainder (the Modulo) from 256.
In other words:
checksum = 256 - ([sum of packet bytes 1-15] % 256)
Step 4: Identifying the flight control data
Once the handset and quad are bound, the handset starts sending the flight control packets from there on out. The basic initialized values for the packets look like this:
Now all that is left is to identify how the values for the flight controls are encoded in the packets – here’s where the test plan is crucial to spotting the changes in the packets throughout the capture. Here are the relevant parts of the plan:
5. Throttle up to 100% and back to 0% 6. Yaw LEFT 100% and back to center (+ Throttle to ~50%) 7. Yaw RIGHT 100% and back to center (+ Throttle to ~50%) 8. Pitch FORWARD 100% and back to center (+ Throttle to ~50%) 9. Pitch BACKWARD 100% and back to center (+ Throttle to ~50%) 10. Roll LEFT 100% and back to center (+ Throttle to ~50%) 11. Roll RIGHT 100% and back to center (+ Throttle to ~50%)
By isolating each control (with the exception of Throttle, which we can guess has to be applied all the time for the other controls to work), it will make it much easier to spot which bytes change in response to our tests.
[See “Control Decode – Packets” tab of the spreadsheet]
Scrolling through the data, we see the following:
Throttle up to 100% and back to 0%[Rows 390-850] Byte 3 appears to go from \x00 (dec 0) to \xFF (dec 255), then back down to \x00.
Yaw LEFT 100% and back to center (+ Throttle to ~50%)[Rows 1250-1420] Byte 5 has an initialized value of \x80 (dec 128), descends to \x44 (dec 68), and then back to \x80.
Yaw RIGHT 100% and back to center (+ Throttle to ~50%)[Rows 1562-1719] Byte 5 has an initialized value of \x80 (dec 128), ascends to \xBF (dec 191), and then back to \x80.
Pitch FORWARD 100% and back to center (+ Throttle to ~50%)[Rows 2344-2480] Byte 7 has an initialized value of \x80 (dec 128), descends to \x41 (dec 65), and then back to \x80.
Pitch BACKWARD 100% and back to center (+ Throttle to ~50%)[Rows 2616-2777] Byte 7 has an initialized value of \x80 (dec 128), ascends to \xBF (dec 191), and then back to \x80.
Roll LEFT 100% and back to center (+ Throttle to ~50%)[Rows 2923-3068] Byte 9 has an initialized value of \x80 (dec 128), ascends to \xBF (dec 191), and then back to \x80.
Roll RIGHT 100% and back to center (+ Throttle to ~50%)[Rows 3279-3392] Byte 9 has an initialized value of \x80 (dec 128), descends to \x41 (dec 65), and then back to \x80.
So it looks like Throttle is Byte 3, Yaw is Byte 5, Pitch is Byte 7, and Roll is Byte 9. Plotting the decimal values of these bytes for the whole capture gives us a great visual of the control values:
This makes analysing the Expert mode controls easier too – enabling Expert Mode and repeating the tests gives us this graph (I threw a flip in there to see how it looked):
We can clearly see the difference here between regular mode and expert mode – it looks like regular mode limits Yaw, Pitch and Roll to half their maximum values (+/- 63 in each direction), while expert mode unlocks the full range (+/- 128). We can also see that no special messages get sent when you execute a flip, it’s just the acute shift in the pitch or roll that that does it – neat! Using similar test techniques we can decode the remaining controls like the LED toggle and switching to Expert mode.
I hope this walkthrough helped illuminate some of the ideas and techniques that I used to reverse engineer the X4’s control protocol – it’s a really fascinating bit of gear and for the price it’s absolutely perfect for hackers looking to have a play with outside-in flight control systems on the cheap. If you have any questions about the decoding, feel free to ask in the comments!
Tune in to Part 3 for the full protocol description.