For the past few weeks, I’ve been working on a research project that includes radio frequency (RF) nodes with a proprietary protocol running on top of Nordic Semiconductor (Nordic)[1] chips, specifically nrf52840. While it’s been quite challenging (no strings at all and of course no symbols), it’s been interesting and satisfying at the same time. As part of this work, I uncovered the code that handles encryption and decryption of RF packets. I wanted to share my findings in the hope that it will serve as a guide to anyone working on a similar project.
Let’s jump straight to the technical part. These chips use a couple of different crypto peripherals depending on the protocol they implement. In this case, the code used the AESCCM[2][3] engine, which is quite simple. It just takes three main parameters, key /nonce, input buffer, and output buffer. Once these are configured, the system can encrypt or decrypt data.
One nice feature of these chips is the ability to chain peripherals using the Programmable Peripheral Interconnect (PPI)[4] subsystem. This allows embedded developers to connect the RADIO peripheral and the crypto engine, causing the AESCCM to immediately trigger encryption or decryption, as can be seen in Figure 1 and Figure 2.


With this information in mind, let’s jump into the disassembled binary and see what the code looks like. All of the function names, variables, and registers are my best guess based on the reverse-engineered functionality.

The first function, shown in Figure 3, configures the AESCCM registers. As you know, all the peripherals are memory mapped, so each register instructs the peripheral about what needs to be done or configured. Figure 3 is an example of a register for this specific peripheral. It’s a good starting point for reverse engineering the functionality, since all of the code references in these areas are related to the actual peripheral and its features.

The specific function in Figure 3 sets argument a2 to the output buffer, a3 to the input buffer, and a1 to the configuration register that points to the key and nonce. This information will assist in further reverse engineering, since we now know which buffer goes to which register.
Later we will see how, depending on whether the code is decrypting or encrypting, it will be used to hold the ciphertext or the plaintext.

As seen in Figure 5, the firmware includes an option where the device does not implement encryption, which is executed in the else statement. This option just sets the buffer to the corresponding RADIO pointer so the peripheral writes the packets as soon as they are demodulated.
However, we are focusing on the first part of the branch where the code deals with the decryption of the RADIO stream. The code sets the encrypted_packet buffer to both the RADIO packet and PACKETPTR (using the set_PACKETPTR function) and, using the function in Figure 3, sets encrypted_packet to INPTR and unencrypted_pkt to OUTPTR, which is the buffer where the AESCCM module will write the decrypted block.
As part of the process, the AESCCM engine needs some additional values (e.g. the key and nonce) in order to correctly decrypt the stream. These are passed as the first argument, as discussed earlier.
Once everything is set, the code instructs the SoC to begin operating using the defined RADIO-AESCCM relationship. This is achieved through further configuration that tells the SoC to make the RADIO and EES peripheral work together using the PPI subsystem.
Similar to the decryption path, the firmware implements the ability to send RADIO packets; Figure 6 shows how this is configured. As its counterpart, the code can work with or without encryption/decryption enabled, which is also dealt with in the branch.

Focusing on the second part where the key/nonce parameters are set, the output and input buffers are swapped, meaning that the RADIO packet points to the encrypted packet (which corresponds to the OUTPTR) and the plaintext is the INPTR.
The firmware very clearly shows how these features are configured and what kind of patterns we can look for when reverse engineering similar functionality on the same chips or others supporting similar peripherals, since these operations are typically quite similar across different vendors.
[1] https://www.nordicsemi.com/About-us
[2] https://nvlpubs.nist.gov/nistpubs/legacy/sp/nistspecialpublication800-38c.pdf
[3] https://docs.nordicsemi.com/bundle/ps_nrf52840/page/ccm.html
[4] https://docs.nordicsemi.com/bundle/ps_nrf52840/page/ppi.html
