Sunday, July 4, 2010

Reversing an RF Clicker

by Travis Goodspeed <travis at>
concerning the Turning Point ResponseCard RF,
having FCC ID R4WRCRF01,
patented as USA 7,330,716.

Turning Point Clicker

In this article, I describe in detail the methods by which I have reverse engineered the TurningPoint ResponseCard RF, casually known among students as a "Clicker". This 2.4GHz radio transceiver is used in undergraduate university classrooms for automated roll-call and in-class quizzing or voting. By dumping and analyzing its firmware, one can determine the radio protocol necessary to intercept and forge packets, as well as to build a custom base station. The radio hardware that I have used is a reprogrammed Next HOPE Badge running the GoodFET firmware.

A follow-up article will likely describe the writing of replacement firmware, but that can be easily enough discovered by an enterprising reader. My purpose instead is to provide the information necessary to build compatible products, as well as to teach the technique of reverse engineering these products to find such information when none is available.


The Clicker's keypad is attached only with adhesive, and it can be pulled off after lifting an edge with a knife blade. Beneath the keypad, there are four screws holding the board in place, plus a fifth from the rear of the device. If you are lucky, these will be small Phillips screws, but the unlucky will find tri-wing "Nintendo" screws. I was lucky to have one of each type, but those with neither a Phillips-screwed Clicker nor a tri-wing screwdriver can buy one or try one of these tricks.
Devil Screws

In either case, it isn't strictly necessary to open your clicker, as test-points for dumping and replacing its firmware are accessible from the battery compartment. Further, the radio communications are accessible with no hardware access whatsoever.


The Clicker is built upon a Nordic nRF24E1 chip, which combines an 8051 microcontroller with an nRF2401 radio transceiver. Although the two cores have been combined into a single package, the 8051 core speaks to the radio through a few bit-field registers and an internal SPI bus, which is shared with the external SPI bus.

As the nRF24E1 lacks internal non-volatile storage, a CAT25C32 (pdf) SPI EEPROM is used for program and configuration storage. Within the microcontroller, there is a masked ROM bootloader from 8000h to 81FFh that loads executable code from the EEPROM into executable RAM from 0000h to 0FFFh.

Hacked Clicker Board

Dumping Firmware

At the base of the circuit board's primary side, there are test points for the SPI EEPROM. As the default firmware only uses the SPI bus when buttons are pressed, this EEPROM may be dumped at any point after the device has booted. The test points are as follows, which should be matched to those of equivalent names in the GoodFET SPI Table. They were determined by use of a continuity tester.

In order to dump the firmware, I quickly wrote a GoodFET client for the 25C32 using its datasheet. A read is performed by sending {0x02, AL, AH, 0} as a SPI transaction, with the result coming back as the fourth byte. Doing it this way with the GoodFET's SPI driver is slower than having C code within the GoodFET dump the whole ROM, but it's fast enough for a dump and takes very little code.
Quick and Dirty 25C32 Driver

From this point, I dumped the firmware with 'goodfet.spi25c dump image.hex', converted the Intel Hex file to binary, and popped it open in Emacs/hexl. The result looks something like the following, whose format is described in the nRF24E1 datasheet. The opening passage is {u8 config, u8 entry offset, u8 blockcount}. Here {0x0B, 0x07, 0x0B} means that executable code begins at byte 0x07, and that the total image length is 0x0B*256==2,816 bytes. (Additional space within the SPI ROM is unused and left as 0xFF.)
Clicker ROM

To produce an image suitable for a disassembler, I cut the bytes before 0x07 to make an image beginning with {0x02, 0x0A, 0xB7, ...}. The extra bytes in this region are the serial number and default frequency, but we'll get back to that later.
Clicker Dev Kit

Firmware Analysis

As the firmware is only three kilobytes, it doesn't take terribly long to reverse engineer. First, the Special Function Registers (SFR) which are defined on pages 79 and 81 of the nRF24E1 datasheet are fed to the disassembler.

(I'm using IDA Pro here, but any 8051 disassembler with a decent text editor could suffice. All of the following function labels are from my imagination, while Special Function Registers (SFRs) come from the nRF24E1 datasheet.)

For example, "MOV 0xA0, #0x80" is rather opaque, but "MOV RADIO, #0x80" makes it clear that the immediate value 0x80 is being placed into the RADIO register. Page 89 of the datasheet will then explain that the high bit of the radio register is power control, so this instruction is powering up the radio for use. Similarly, "SETB RADIO.3" is setting the fourth bit of the RADIO register, which the datasheet describes as raising the CS signal.

Once the SFR addresses are known, it becomes useful to search for them in order to identify the I/O routines. In the nRF24E1, the radio is accessed across a SPI bus, so a good first step is to identify the SPI routine. The function containing this code will always include a MOV involving the SPI_DATA register.
SPIRXTX for 8051

Having this, a list of cross-references quickly shows that while few functions call the SPIRXTX function, each calls it many times. This is because the author has chosen to repeatedly call that function with immediate values, rather than to dump an array of bytes with a for(){} loop.
Functions calling SPITXRX

While the disassembler can automatically identify the function entry points in the table above, it is not capable of giving them English names or descriptions. To understand how this is done, it is necessary to read the datasheets of the SPI devices.

The SPI EEPROM chip, a CAT25C32, is used by dropping the !CS line then writing an opcode byte followed by its parameters or results. Opcodes include WREN/WRDI for write protection, RDSR/WRSR for accessing a status register, and READ/WRITE for reading and writing bytes. A WRITE may only be performed when the external !WP pin is low and the software write protect has been disabled by opcode. A transaction begins when !CS drops low and ends when it drops high.

To identify the function which reads a byte from the 25C32, a few things can be safely assumed: (1) The function will begin by dropping some I/O pin (!CS). (2) The function will then broadcast the READ opcode, 0x03. (3) It will then broadcast a sixteen bit parameter; that is, DPL followed by DPH. (4) Finally, it will return the result of a fourth SPIRXTX call. In pseudocode, that would be something like
return SPIRXTX();

Sure enough, one of the few functions calling SPIRXTX does exactly this. The constant pushing and popping of the parameters is a quirk of the compiler, which might possibly allow it to be identified. From the code below, it is clear that P0.0 is the !CS line of the CAT25C32.

The SPIROMPOKE function looks similar, except that two transactions are performed. First the WREN (0x06) opcode is sent to enable writing, then WRITE (0x02) is used to perform the actual write.

The other SPI operations concern the nRF2401 radio core, which behaves differently from the EEPROM. Rather than transactions being an opcode followed by parameters, there is only a single SPI register that must be completely written during a transaction. A second register, selected by the CE line, contains the packets.

The configuration is set by one big register, sent MSBit first. If fewer than the needed bytes are sent, the value is right-aligned into the lower bytes of the register. That is, the last byte sent is always (CHAN<<1)|RXMODE and the second to last always describes the radio configuration.
nRF2401 Config Register

Searching around a bit yields the RADIOWRCONFIG function, the tail of which is below. It can be seen from the code that the 0x1A IRAM byte holds the channel number. That is, if 0x20 is stored at 0x1A, the radio will be configured to 2,432 MHz. The other configuration bytes reveal that the MAC addresses are 24 bits, the checksum is 16 bits, and the device broadcasts at maximum power sourced from a 16MHz crystal. (That the configured crystal is identical to the one on the board is very important. Some enterprising coders will lie to a chip about its crystal in order to access an unsupported radio frequency.)
Clicker RF Config

At this point, it still remains to sniff traffic is to find the target address to which packets are broadcast as well as the frequency. We'll start with the address, because that's a bit easier.

The TXPACKET function involves a lot of PUSH and POP instructions, but it otherwise looks very similar to the RADIOWRCONFIG function, in that a series of bytes are written in order with repeated function calls to SPIRXTX. In pseudocode, this function becomes the following. From the radio documentation and configuration, it is clear that the first three bytes will be the target MAC address. From the RADIOWRCONFIG() function, it is equally clear that the three bytes at 0x1B are the receiving MAC address of the unit. (The parameter of the function happens to be the button press, as can be determined by tracking the keyboard I/O routines or viewing a few packets.)
void TXPACKET(u8 button){
RADIOHOP(); //set channel

//Target MAC address

//Source MAC address

//Data value

The radio itself will append a 16-bit CRC; therefore, the full packet then becomes {u24 tmac, u24 smac, u8 button}.

To determine the value of the target MAC address, just grep the disassembly for "mov" and one of 0x1E, 0x1F, 0x20. The relevant instructions are as follows, setting the target MAC address to 0x123456. (In 8051 notation, the first instruction moves the immediate constant #0x12 into byte 0x1E of IRAM.)
mov 0x1E, #0x12
mov 0x1F, #0x34
mov 0x20, #0x56

As this point, it would be possible to scan each channel for a few seconds, listening for packets sent to that address, but it's classier to find the value by static analysis. Acting on the hunch that the configuration is held in EEPROM and looking for references to the SPIROMPEEK() function, the READIDFREQ() function can be found. As can be seen in the fragment below, EEPROM[6] holds the channel number while the MAC address is at EEPROM[3,4,5].
Clicker Config

As the EEPROM begins with "0b 07 0b 15 79 1b 29", it's clear that the MAC address of the unit from which it came is 0x15791B and that it is broadcasting on 2400+0x29=2441MHz. This can be double-checked by the serial number "15791B" being printed on the label.


Knowing the modulation scheme, target address, and packet contents, it becomes possible to sniff traffic from a Clicker. This is performed by use of the GoodFET firmware on a Next Hope badge, my prior tutorial for which describes the process of packet sniffing.

The NHBadge board contains an nRF24L01+ radio, which differs dramatically from the nRF2401 in terms of how it is configured. Still, the radios are sufficiently compatible. The following hack of the goodfet.nrf client allows packets to be sniffed from the air with proper checksumming.
Sniffing TurningPoint Traffic

Sure enough, here are some packets of the 5 button being pressed on unit 1F8760. The keypress is the final byte in ASCII.
Clicker Sniffing

Response Codes

Now that it is clear how to receive and recognize button presses, it becomes necessary to reverse engineer the response codes which might be sent from the access point. Without hearing a reply of at least an ACK, the Clicker will continue to broadcast each message more than three hundred times. This takes more than ten seconds, during which all other key presses are ignored.

The broadcast loop within the MAIN() function would look a little like this in C.
for(count=0;count< MAXCOUNT && !reply;count++){

This region is easy enough to find, but there's another command mode. An easier target is the channel hopping routine, which constantly broadcasts 0x3F while incrementing the channel, sticking with the last one on which a reply of 0x18 was received. Channels 1 through 83 are attempted; that is, 2,401 MHz to 2,483MHz at 1MHz steps.
Clicker SYN/ACK

Checking this code within the MAIN() function reveals that its effect is to blink the green LED (P1.1) six times, exiting the broadcast loop. Other commands include 0x04 (LED Off), 0x06 (LED Green), 0x15 (LED Red), 0x11 (Blink Green), 0x14 (Blink Red), and 0x18 (Blink Green, Channel Lock). All undefined opcodes set the red LED.


By sniffing traffic within a classroom, it is possible to watch votes as they are being cast by students. Similarly, packets could be broadcast by a reprogrammed Clicker or NHBadge to make a student in virtual attendance, automatically voting with the majority so as to gain perfect attendance and a solid C quiz average. Where instant feedback is available, this might even allow for a solid A quiz average. Without taking advantage of the masked-ROM option of the nRF24E1, the code cannot be even slightly protected from extraction and reverse engineering.

Less adventurous users can jam the network by running 'goodfet.nrf carrier 2441000000' to hold a carrier wave on the channel. The only attempt at a frequency change is made when pressing the GO button, at which point the new channel can be discovered and similarly jammed.

Since performing this work, it has come to my attention that a USRP plugin for doing this to the competing 900MHz iClicker product is available as Additionally, the infrared Clicker units were broken with a little tool called Survey Says. I have ordered more sophisticated Clicker models from CPS and Turning Point, and proper descriptions of them will soon follow.