Reverse Engineering the iClicker Base Station

What are iClickers?

iClickers are used in a lot of colleges in order to conduct quizzes and take attendance. The whole ecosystem operates as follows:

  1. Each student buys an iClicker device. They’ve got some buttons on them to respond to multiple choice questions.

  2. They enter the unique ID on the back of the device into their school database.

  3. Each class is equipped with a base station that connects to the instructor’s computer via USB. With iClicker’s software, they can conduct quizzes and export the answers for automatic grading.

State of the art of iClicker reverse engineering

A significant amount of work has been done as far as figuring out how the student owned remotes work. The seminal work in this area is contained in this fantastic paper conducted by some students at the University of British Columbia where they dumped out the firmware of the remote. Some key contributions they made were figuring out the exact radio transceiver used in the device as well as the obfuscation scheme used in the transmission of the IDs.

Following up to this, some students at Cornell started off a project called iSkipper where they attempted to create an open source alternative to the iClicker. By using logic analyzers and dumping out the raw communications using a software defined radio, they were able to piece together the protocol that the remotes use to send their answers over the air. They wrote their own implementation of an iClicker that can be run on an Arduino with just a 900MHz radio transceiver.

While the iSkipper project has managed to figure out most of the iClicker protocol, one missing piece is the communication from the base station back to the remotes. Upon pressing a button, the base station sends back an acknowledgement packet to indicate that the answer has been accepted. In addition, the base station can also send a welcome message to the remotes to indicate what class is currently in progress.

In order to figure out this last missing piece of the iClicker puzzle, I set out to reverse engineer the receiver.

Acquiring the firmware

The first part of reverse engineering the base station would be to obtain the firmware that runs on it. Since I didn’t own a base station and didn’t want to buy one (you can get them for anywhere between $50-$100 on eBay), I had to figure out an alternative approach to acquiring the firmware.

Searching for iClicker base station firmware led me to the “iClicker Base Firmware Utility” on the iClicker downloads page. This software claimed to be able to update the firmware on a base station so it seemed like a natural target. I initially guessed that they would package the updated firmware with the executable but searching around in the distributed files I couldn’t locate any firmware files. Next up I ended up starting the executable.

“Check for Update”, interesting. This was a massive hint that the updates were most likely downloaded over the internet. Thus, I cracked open the executable in IDA and searched away for interesting URLs.


Opening up this URL in a browser, I found that most of the files were updates the firmware utility itself, but there were two very interesting files:

  • update_v0602.txt
  • U_BASEU_V0058.txt

Here is a chunk of update_v0602.txt:


For those unfamiliar, this an Intel HEX file, Getting into a binary format was as simple as:

objcopy -I ihex update_v0602.txt -O binary firmware.bin

and with that, I had the firmware on my hands without having to install a JTAG interface or an AVR programmer into a base station.

Reverse engineering the firmware

Alright so next up we gotta disassemble the firmware. I strongly suspected that there was some Atmel chip inside the base station, just like the remote. Atmel makes some very popular programmable microcontrollers, a lot of embedded systems, IoT devices and the Arduino platform use Atmel chips.

Luckily IDA supports disassembling AVR, the architecture used by these microcontrollers. So I cracked the firmware open in IDA and went to hunt down the code that generates the acknowledgement packets.

Take the next section with a heavy grain of salt, this was my first time reverse engineering embedded software and it was very much new and uncharted territory for me. If I made any glaring mistakes, please feel free to reach out and I’ll try to amend them :)

ID Decoding

There was a lot of code and I wasn’t exactly familiar with AVR so in order to get my bearings, I set out to find a known piece of the protocol: the scrambling and de-scrambling routine for the iClicker remote ID. The iSkipper project had already figured out the algorithm to do this:

void iClickerEmulator::decodeId(uint8_t *id, uint8_t *ret) {
    ret[0] = (id[0] >> 3) | ((id[2] & 0x1) << 5) | ((id[1] & 0x1) << 6) | ((id[0] & 0x4) << 5);
    ret[1] = ((id[0] & 0x1) << 7) | (id[1] >> 1) | (id[2] >> 7);
    ret[2] = ((id[2] & 0x7c) << 1) | (id[3] >> 5);
    ret[3] = ret[0]^ret[1]^ret[2];

So a good place to start would be finding functions that do a lot of bit shifting. Searching for “lsr” (Logical Shift Right), I found a peculiar function:

  lsr     r30
  lsr     r30
  lsr     r30

In AVR, the lsr opcode shifts its argument register right by 1 bit, this looked an awful lot like the initial part of the decoding algorithm so I followed to the callers of this right_shift_3 function.

There were some cool tricks that the compiler used in this area, for example in order to shift right by 7, it didn’t emit 7 lsr instructions. Instead, the sequence of instructions was

swap    r30
andi    r30, 0xF
lsr     r30
lsr     r30
lsr     r30

The swap instruction swaps the two nibbles of the byte, so the higher order 4 bites get swapped with the lower order 4 bits. Performing an and with 0xF = 0b1111 after this essentially does the same thing as shifting right by 4.

While this approach led me to the function that decodes the ID, the rest of the calling logic was not particularly easy to follow. I needed to find more landmarks in the code to figure out what was going on.

Radio SPI Interface

As mentioned in the introduction, previous reverse-engineers had already figured out what radio chip was used in the clicker, namely the Semtech XE1203F.

Consulting the datasheets for the IC, we can see that it uses a 3-wire SPI (Serial Peripheral Interface) based protocol in order to configure the radio chip. The next logical step was to look at an SPI tutorial for AVR microcontrollers, I found a great one here with the following code sample:

ldi r16,0xAA
out SPDR,r16         ; Initiate data transfer. 

sbis SPSR,SPIF       ; Wait for transmission to complete.
rjmp Wait
in SPDR,r16	         ; The received data is placed in r16.

Perfect, so we have to look up usages of the SPDR register within the firmware. There is only one place that used this register, so I labelled the function as read_write_from_SPI. It reads one argument stored at (Y+1) and then writes it out the SPI port.

ROM:1393 read_write_from_SPI:                    ; CODE XREF: read_write_two_bytes_SPI
ROM:1393                 st      -Y, r16         ; Spill register r16
ROM:1394                 cli                     ; Disable interrupts
ROM:1395                 ldd     r30, Y+1
ROM:1396                 out     SPDR, r30       ; SPI Data Register
ROM:1397 loc_1397:                               ; CODE XREF: read_write_from_SPI+7
ROM:1397                 in      r30, SPSR       ; SPI Status Register
ROM:1398                 andi    r30, 0x80
ROM:1399                 cpi     r30, 0x80
ROM:139A                 brne    loc_1397

Here is one of the usages of the SPI writing function:

ROM:082A                 ldi     r30, 0xF
ROM:082B                 ldi     r31, 0x8A
ROM:082C                 st      -Y, r31
ROM:082D                 st      -Y, r30
ROM:082E                 call    read_write_two_bytes_SPI
ROM:0830                 ldi     r30, 0xA0
ROM:0831                 ldi     r31, 0x8B
ROM:0832                 st      -Y, r31
ROM:0833                 st      -Y, r30
ROM:0834                 call    read_write_two_bytes_SPI

Now if we consult with the XE1203F’s documentation, it mentions the following:

The timing diagram of a write sequence is illustrated in Figure 12 below. The sequence is initiated when a Start condition is detected, defined by the SI signal being set to “0” during one period of SCK. The next bit is a read/write (R/W) bit which should be “0” to indicate a write operation. The next 5 bits contain the address of the configuration/status registers A[4:0] to be accessed, MSB first (see 5.2). Then, the next 8 bits contain the data to be written into the register. The sequence ends with 2 stop bits set to “1”.

Okay cool, now if we take a closer look at the bytes being written out on the SPI interface as binary, we see the following:

0x8A          0xF

1000 1010   0000 1111

Compare this against the timing diagram from the datasheet above, looks fairly similar! If we plot it out and label the bits, we see the following:

Great, so this is code that is writing a value to a configuration register in the radio chip. Notably it’s writing the value 0xF to the register at address 0x01010. I confirmed this theory by decoding a few more SPI writes.

Register Value Description
0x01010 0x0F Frequency Adjustment MSB
0x01011 0xA0 Frequency Adjustment LSB
0x00010 0x1F Frequency Band 902–928 MHz

Following the formula in the datasheet, we can see that the frequency will be the base frequency plus 500 times the frequency adjustment registers interpreted as a 16 bit two’s compliment number.

Frequency Base = 915 Mhz
Frequency Adjustment = 0x0FA0 = 4000

Final Frequency = (915 Mhz) + (4000 * 500 Hz)
                = 917 Mhz

and if we check this against the first paper linked above, we can confirm that they experimentally figured out the default AA channel operates at 917.0 MHz.

Great, this discovery lets us figure out exactly what parameters the radio module is using and helped find the portion of the code responsible for changing frequencies.

Radio Data IO

So the SPI protocol is how the radio module is configured, but looking at the data sheet we can see that there is a separate DATAIN and DATA port used to read and write actual radio packets. My first intuition was that the firmware might be making use of the AVR USART (Universal Synchronous/Asynchronous Receiver/Transmitter) feature to exchange data with the radio chip.

However, after looking at the interrupt handlers for USART_RXC and USART_TXC which correspond to when a byte is sent or received by the USART module, it seemed clear that this is actually how the base station communicates with the instructor’s computer and NOT where radio messages were read/sent.

Within AVR, IO is primarily done using the in and out instructions. The only interesting traces I could find for the in instruction was in the INT1 interrupt handler which corresponds to a configurable external interrupt handler. The following is psuedo-C like code that corresponds to the handler:

int8_t radio_bytes_read = 0;

int8_t radio_bits_to_read = 8;
int8_t radio_bytes_to_read = n;

extern int8_t* radio_bytes;

void INT1() {
    while (radio_bytes_to_read > 0) {
        while (radio_bits_to_read > 0) {
            int8_t current_byte = radio_bytes[radio_bytes_read];

            if (PIND_5 is high) {
                current_byte |= 1;
            current_byte = current_byte << 1;

            radio_bytes[radio_bytes_read] = current_byte;
        radio_bits_to_read = 8;

And bingo, now that we know where radio_bytes array is in memory, we can look at cross references to it to find the code that processes packets sent over the radio.

Dynamic analysis

After a large portion of just statically analyzing the disassembled code, I decided to use the fantastic avrsim project that allows you to run avr binaries and even attach gdb to it.

I took the example code in examples/board_simduino/simduino.c and customized it to my needs. The first most obvious change to make is to change the MMCU to atmega16.

Next up was setting the appropriate bit and raising the external interrupts to emulate the radio module receiving bytes.

void send_byte(unsigned char b, avr_irq_t* radio_in, avr_extint_t* extint, avr_t* avr) {
    for (int i = 7; i >= 0; i--) {
        for (int j = 0; j < 200000; j++) {

        int bit = (b >> i) & 1;
        printf("Writing bit %d\n", bit);
        avr_raise_irq(radio_in, bit);
        avr_raise_interrupt(avr, &extint->eint[1].vector);

int main(int argc, char** argv) {
    int interrupted = 0;

    while (1) {
        int state = avr_run(avr);
        if ( state == cpu_Done || state == cpu_Crashed) {

        // divide by 2 to get "word" addresses like IDA uses
        unsigned int curr_pc = avr->pc / 2;
        if (curr_pc == 0x18E && !interrupted) {
            // Perform an INT0 interrupt
            avr_raise_interrupt(avr, &extint->eint[0].vector);
            printf("Raising AVR INT0 interrupt\n");

            // Raw ID = 0xA2 0x46 0x53 0xB7
            // My encoded ID = 0x14 0x8C 0x29 0x70
            send_byte(0x14, radio_in, extint, avr);
            send_byte(0x8C, radio_in, extint, avr);
            send_byte(0x29, radio_in, extint, avr);
            // Sending an answer of 'B' (0x05)
            unsigned char last_id = 0x70;
            last_id &= 0xF0;
            last_id |= 0x05;
            send_byte(last_id, radio_in, extint, avr);

            // compute the checksum
            unsigned char checksum = 0x14 + 0x8C + 0x29 + last_id;
            send_byte(checksum, radio_in, extint, avr);

            interrupted = 1;

Protocol Findings

Hopefully the last section gives you some insight on what the reverse engineering process was like, not too harp on it too much, let’s move on to the actual findings in terms of the radio protocol:

Welcome ping packet

The base station sends out this packet on regular intervals (every few seconds), it contains the welcome message as shown on the iClicker like this:

as well as the question mode being used, which can either be the standard A/B/C/D/E multiple choice mode, numeric alphanumeric or a sequence of questions.

Packet details

Byte Description
0-5 Fixed header (Radio sync bytes)
0x55 0x55 0x55 0x36 0x36 0x36
6-13 Welcome message
(note this is not ascii, see below)
14 Mode byte 1
15-16 Number of questions
(for multiple question modes)
17 Mode byte 2
18 Checksum
(sum of bytes 6-17)

The mode bytes are as follows:

Mode Mode Byte 1 Mode Byte 2
Multiple Choice 0x92 0x62
Numeric Mode 0x93 0x63
Alphanumeric Mode 0x9B 0x6B
Multiple Numeric 0x94 0x64
Multiple Alphanumeric 0x9C 0x6C

Here are some examples of a welcome packets.

This causes the iClicker to show IREVERSE on screen and puts it in the multiple choice mode.

0    1    2    3    4    5    6    7    8    9    10   11   12   13   14   15   16   17   18
0x55 0x55 0x55 0x36 0x36 0x36 0x93 0x9C 0x8F 0xA0 0x8F 0x9C 0x9D 0x8F 0x92 0xAA 0xAA 0x62 0xFD
 ^-------------------------^  ^---------- Welcome Message ----------^  ^   ^-------^  ^    ^
  Header/Radio sync bytes     'I'  'R'  'E'  'V'  'E'  'R'  'S'  'E'   │    Useless   |  Checksum
                                                                       │              |
                                                      Question Mode (Multiple Choice) ┙

This causes the iClicker to show 2+2=5 on screen and allows 8 questions to be answered in alphanumeric mode.

0    1    2    3    4    5    6    7    8    9    10   11   12   13   14   15   16   17   18
0x55 0x55 0x55 0x36 0x36 0x36 0x82 0xA6 0x82 0xA7 0x85 0x00 0x00 0x00 0x9C 0x08 0x00 0x6C 0xE6
 ^-------------------------^  ^---------- Welcome Message ----------^  ^   ^-------^  ^    ^
  Header/Radio sync bytes     '2'  '+'  '2'  '='  '5'  ' '  ' '  ' '   │  8 in little |  Checksum
                                                                       │    endian    |
                                                                       |              |
                                                Question Mode (Multiple Alphanumeric) ┙

Text encoding

As mentioned earlier, the welcome message isn’t a normal encoding like ASCII, it seems custom rolled.

  • 1 to 9 are from 0x81 to 0x89.

  • 0 is represented by 0x8A.

  • A starts at 0x8B and goes up sequentially like ASCII.

  • - is 0xA5.

  • + is 0xA6.

  • = is 0xA7.

  • ? is 0xA8.

  • _ is 0xA9.

  • Any other bytes will show a blank.

Multiple choice answer ACK packet

Sent to acknowledge an answer for a multiple choice question. These involve calculations on the encoded iClicker id, which I will refer to as an array called encodedId.


(Shows a tick on the iClicker screen)

Bytes Description
0-2 Fixed header (Radio sync bytes)
0x55 0x55 0x55
3 Byte 0 of the encoded iClicker ID (encodedId[0])
4 Byte 1 of the encoded iClicker ID (encodedId[1])
5 Bitwise negation of byte 2 of the ID (~encodedId[2])
6 (encodedId[3] & 0xF0) | 0x02
7 Constant 0x22


(Shows closed on the iClicker screen)

Bytes Description
0-2 Fixed header (Radio sync bytes)
0x55 0x55 0x55
3 Byte 0 of the encoded iClicker ID (encodedId[0])
4 Byte 1 of the encoded iClicker ID (encodedId[1])
5 Bitwise negation of byte 2 of the ID (~encodedId[2])
6 (encodedId[3] & 0xF0) | 0x06
7 Constant 0x66


My iClicker ID is A24653B7, which encodes to 0x14 0x8C 0x29 0x75 when answering B.

Let’s calculate the bitwise negation of my encodedId[2]:

0x29 = 0b00101001
    ~= 0b11010110 = 0xD6

Thus, a positive acknowledgement packet for this answer would be:

0x55 0x55 0x55  0x14 0x8C 0xD6 0x72 0x22

And a negative acknowledgement packet would be:

0x55 0x55 0x55  0x14 0x8C 0xD6 0x76 0x66

Side note: I haven’t documented the ACK packets for other question modes since I figured there’s not a lot of interest for those, please let me know if you’d like to see those.


This was my first real project reverse engineering a large real-world project, especially so in the embedded space. Compared to reverse engineering x86 a bigger chunk of time was spent reading datasheets for the hardware components, especially the Atmel processor. The lack of strings, system calls etc also make it a lot harder to orient yourself and find your way around the code.

I’ve posted my IDA database and a text dump of the firmware on Github. Feel free to reach out to me if you have any questions.

Problem Description

Deep on the web, I discovered a secret key validation. It appeared to be from the future, and it only had one sentence: “Risk speed for security”. Something seems fishy, you should try to break the key and find the secret inside!


The binary

Welp, this binary was movfuscated:

08048794 <check_element>:
 8048794:	a1 88 d2 3f 08       	mov    eax,ds:0x83fd288
 8048799:	ba 94 87 04 88       	mov    edx,0x88048794
 804879e:	a3 10 d1 1f 08       	mov    ds:0x81fd110,eax
 80487a3:	89 15 14 d1 1f 08    	mov    DWORD PTR ds:0x81fd114,edx
 80487a9:	b8 00 00 00 00       	mov    eax,0x0
 80487ae:	b9 00 00 00 00       	mov    ecx,0x0
 80487b3:	ba 00 00 00 00       	mov    edx,0x0
 80487b8:	a0 10 d1 1f 08       	mov    al,ds:0x81fd110
 80487bd:	8b 0c 85 20 77 05 08 	mov    ecx,DWORD PTR [eax*4+0x8057720]
 80487c4:	8a 15 14 d1 1f 08    	mov    dl,BYTE PTR ds:0x81fd114
 80487ca:	8a 14 11             	mov    dl,BYTE PTR [ecx+edx*1]
 80487cd:	89 15 00 d1 1f 08    	mov    DWORD PTR ds:0x81fd100,edx
 80487d3:	a0 11 d1 1f 08       	mov    al,ds:0x81fd111
 80487d8:	8b 0c 85 20 77 05 08 	mov    ecx,DWORD PTR [eax*4+0x8057720]
 80487df:	8a 15 15 d1 1f 08    	mov    dl,BYTE PTR ds:0x81fd115
 80487e5:	8a 14 11             	mov    dl,BYTE PTR [ecx+edx*1]
 80487e8:	89 15 04 d1 1f 08    	mov    DWORD PTR ds:0x81fd104,edx
 80487ee:	a0 12 d1 1f 08       	mov    al,ds:0x81fd112
 80487f3:	8b 0c 85 20 77 05 08 	mov    ecx,DWORD PTR [eax*4+0x8057720]
 80487fa:	8a 15 16 d1 1f 08    	mov    dl,BYTE PTR ds:0x81fd116

Initially we tried using demovfuscator but all this really did was turn a few movs into leas but nothing too useful.

Instead, we used demovfuscator’s -g option to generate the control flow graph of the program which looked like this:

movfuscator control flow graph

Since the program was exiting on a wrong input, I decided to experiment by adding a breakpoint at the first branch and take a look around in gdb. This breakpoint was being hit thousands of times so I used ignore <bp> 1000000 to quickly check how many times which resulted in a really interesting output:

[Inferior 1 (process 24054) exited with code 01]
pwndbg> i breakpoints
Num     Type           Disp Enb Address    What
2       breakpoint     keep y   0x0805262f <main+8901>
    breakpoint already hit 6018 times

[Inferior 1 (process 24082) exited with code 01]
pwndbg> i breakpoint
Num     Type           Disp Enb Address    What
3       breakpoint     keep y   0x0805262f <main+8901>
    breakpoint already hit 4012 times

Notice that when correct input is provided, the breakpoint is hit more often. Now that we have an indicator to know that one character of input is correct, we can go ahead and exploit this to recover the flag.

(Note: handle SIGSEGV nostop noprint pass and handle SIGILL nostop noprint pass are required to debug in gdb since movfuscator uses signal handlers on SIGSEGV and SIGILL for function calls and loops)

Exploiting the side channel

We whipped up a quick gdb script to automate the process of monitoring the breakpoints:

import gdb
import string

valid_chars = string.printable

gdb.execute("file future_fun")
gdb.execute("handle SIGSEGV nostop noprint pass")
gdb.execute("handle SIGILL nostop noprint pass")

class MyBreakpoint(gdb.Breakpoint):
    def stop(self):
        return False

bp = MyBreakpoint("*0x805262f")
bp.ignore_count = 90000000

flag = ""
while not flag.endswith("}"):
    for char in valid_chars:
        bp.hit_count = 0

        newflag = flag + char

        with open("flagfile", "w") as f:
            f.write(newflag + "%")

        exec_command = 'r < flagfile > /dev/null'
        gdb.write("Trying: " + newflag + "\n")

        if bp.hit_count > (len(newflag) + 1) * 1000:
            gdb.write("[!] Found character: " + newflag + "\n")
            flag = newflag
            gdb.write("Hits: {}\n".format(bp.hit_count))

This script tries each printable character and if we hit the breakpoint a thousand times more than the last, we know it was correct.

After running for a few minutes, the flag was recovered successfully.

Recovery in progress

Problem Description

Ever since their hella successful ICO, the crypto experts at VapeCoinIO have put developers first with their simple, intuitive, and, most importantly, secure API. Once you’ve created your account and set up your wallet, you can access it programmatically using your VapeID by sending a GET request to /api/login?key=<HASH> where <HASH> is your VapeID. Your wallet is transferred to you over TLS, so don’t worry—it’s really, really secure. In fact, it’s so secure that the founder and CEO of VapeCoinIO uses the API for his personal Brainwallet.

One of your contacts is a site-reliability engineer at VapeCoinIO. He has obtained a PCAP of a TLS session with a client originating from an IP he suspects to be used by CEO’s personal laptop. Perhaps he accessed his wallet! Can you find a way to recover its contents?


Inspecting the packet capture

As the problem description says, the first thing we notice in the PCAP is that the data is transferred exclusively over a TLS connection. Thus the first thing we need to look for is something amiss in the TLS exchange such as the use of a weak cipher.

We can see in the PCAP that the TLS connection is using: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256

DHE stands for “Diffie-Hellman ephemeral” which is where the RSA key is used to sign the server’s Diffie-Hellman public number to provide authenticity and every TLS session uses a new set of public numbers.

Looking at the Diffie-Hellman numbers in wireshark, we notice something very interesting:

Wireshark Disection TLS Exchange

Those numbers look absolutely tiny! Diffie-Hellman’s security is based on the difficulty of the Discrete Logarithm problem. Ordinarily the p (prime) for DH is a 2048 bit number, here it’s an abysmal 32 bits making it trivial to compute discrete logarithms on. SageMath has several algorithms built in to compute discrete logs so we wrote up a quick script to recover the client’s secret number.

p = 0xf661398b
g = 0x02
client_public = 0x42b2769b
server_public = 0x916ddb94

k = GF(p)
client_secret = discrete_log_lambda(k(client_public), k(g), (1,2**32))
# 0x5ec3d070

The client secret is then enough to compute the shared secret. This is known as the pre-master secret in TLS terms which is all wireshark needs to decrypt TLS traffic.

premaster_secret = pow(server_public, client_secret, p)
# 0xf5ca9f85

Decrypting with Wireshark

In order to let Wireshark utilize this, it needs a file mapping TLS sessions to the master or pre-master secrets. So we made a file called keylogfile.txt containing:

PMS_CLIENT_RANDOM 358970edf3544c1181cecf3369cd4c0e69be2c3605662ba1288b251161eba51e f5ca9f85

PMS stands for Pre-Master Secret and the giant number in the middle is the client random, sent as part of the Client Hello packet which is what wireshark uses to map the PMS to the right TLS session.

(Note: wireshark displays the timestamp and random bytes seperately if you expand the Random portion in the TLS packet, the client random is the timestamp and random bytes together.)

We set up Wireshark’s TLS protocol settings to use the log file:

Wireshark TLS Preferences

and boom, follow the TLS stream in Wireshark for the flag:

Wireshark TLS Stream

A lesson on data structures, networking protocols, data sanitization and disclosure

Around 2 years ago, I was enthusiastically working on Spigot and Bukkit along with a couple of fairly popular plugins. During my poking around within the networking internals of Minecraft, I came across a fairly substantial problem that allowed anyone to send certain malformed packets and crash a server by running it out of memory.

Following the defacto standard procedure, I responsibly and privately disclosed the problem to Mojang on 10th July, 2013. That’s nearly 2 years ago. I asked for updates in one month intervals over the course of 3 months and was ignored or given highly unsatisfactory responses. I kept my hopes up that the problem would be patched and checked the source code on new releases whenever I could.

The version of the game when the vulnerability was reported was 1.6.2, the game is now on version 1.8.3. That’s right, 2 major versions and dozens of minor versions and a critical vulnerability that allows you to crash any server, and starve the actual machines of CPU and memory was allowed to exist.

The technical details

The minecraft protocol enables the exchange of information about what an inventory slot contains. This allows you to, for example, get the items in your hotbar when you log in. Items in minecraft also contain a feature that allows them to store arbitary metadata (used for enhancements, books etc). This metadata is stored in a format known as NBT. The NBT format is essentially like JSON but in binary form. This allows it to store complex data structures with nesting and the like.

For example, the NBT metadata (known as a tag) for a book might look something like this

tag: {
    author: "ammar2",
    title: "Kitteh",
    pages: ["Kitties are cute", "I like kitties"]

The vulnerability stems from the fact that the client is allowed to send the server information about certain slots. This, coupled with the NBT format’s nesting allows us to craft a packet that is incredibly complex for the server to deserialize but trivial for us to generate.

In my case, I chose to create lists within lists, down to five levels. This is a json representation of what it looks like.

rekt: {
    list: [
        list: [
            list: [
                list: [
                    list: [
                        list: [
                        list: [
                        list: [
                        list: [

The root of the object, rekt, contains 300 lists. Each list has a list with 10 sublists, and each of those sublists has 10 of their own, up until 5 levels of recursion. That’s a total of 10^5 * 300 = 30,000,000 lists. And this isn’t even the theoretical maximum for this attack. Just the nbt data for this payload is 26.6 megabytes. But luckily minecraft implements a way to compress large packets, lucky us! zlib shrinks down our evil data to a mere 39 kilobytes.

Note: in previous versions of minecraft, there was no protocol wide compression for big packets. Previously, NBT was sent compressed with gzip and prefixed with a signed short of its length, which reduced our maximum payload size to 2^15 - 1. Now that the length is a varint capable of storing integers up to 2^28, our potential for attack has increased significantly. (for the more technically minded people, we fill the list up with TAG_ENDs, which aren’t accounted for by the accumulator Mojang attempted to implement to fix this)

When the server will decompress our data, it’ll have 27 megs in a buffer somewhere in memory, but that isn’t the bit that’ll kill it. When it attempts to parse it into NBT, it’ll create java representations of the objects meaning suddenly, the sever is having to create several million java objects including ArrayLists. This runs the server out of memory and causes tremendous cpu load.

This vulnerability exists on almost all previous and current minecraft versions as of 1.8.3, the packets used as attack vectors are the 0x08: Block Placement Packet and 0x10: Creative Inventory Action.

The fix for this vulnerability isn’t exactly that hard, the client should never really send a data structure as complex as NBT of arbitrary size and if it must, some form of recursion and size limits should be implemented. These were the fixes that I recommended to Mojang 2 years ago.

Proof of concept

A proof of concept of this exploit can be seen here on my Github repo. The code to generate the posioned nbt can be seen in The code has been tested under python 2.7, once you have connected to a server simply enter exploit in the command line and the packet will be sent to the server.


I thought a lot before writing this post, on the one hand I don’t want to expose thousands of servers to a major vulnerability, yet on the other hand Mojang has failed to act upon it. Mojang is no longer a small indie company making a little indie game, their software is used by thousands of servers, hundreds of thousands of people play on servers running their software at any given time. They have a responsibility to fix and properly work out problems like this. In addition, it should be noted that giving condescending responses to white hats who are responsibly disclosing vulnerabilities and trying to improve a product they enjoy is a sure fire way to get developers dis-interested the next time they come across a bug like this.


  1. 28th July, 2013: First contact with mojang employee about the issue, vulnerability disclosed and proof of concept provided.

  2. 19th August, 2013: Second time asking about fix, response given that its being worked on.

  3. 24th September, 2013: Third contact with employee, told that the problem has been delegated.

  4. 25th October, 2013: Fourth time I attempted to contact employee, ignored.

  5. 27th October, 2013: Another attempt to contact, ignored again. (at this point, I patiently waited for a fix)

In retrospect, yes, I should have given them a final warning sometime recently before this but I just expected to be shot down again

Update: With the release of this full disclosure I have actually made contact with mojang and they are working to fix the issue. Apparently the initial fix they tried failed which indicates a lack of proper testing.

Update 2: The exact problem that caused this bug to go unpatched has been identified. Mojang attempted to implement a fix for this problem, however they did not test their fix against the proof of concept I provided, which still crashed the server perfectly fine. This, in combination with ignoring me when I asked for status updates twice led me to believe that Mojang had attempted no fix. In retrospect, a final warning before this full disclosure more recently was propbably in order. A combination of mis-communication and lack of testing led to this situation today, hopefully it can be a good learning experience.

Update 3: This problem has been patched as of minecraft version 1.8.4

I’m happy to see that multiple other security issues have also been fixed. Once again, I feel better communication would have easily alleviated this problem. Keeping me in the loop and not ignoring me, in addition to proper testing would have easily led to this exploit being fixed long ago.

Edit: In regards to the statements that I never filed a report on their bug tracker, I’d just like to point out that nobody pointed me towards the tracker when I reported the issue, I wasn’t asked to make a ticket or told that I could follow the progress of the report on x-ticket.

Custom API being used by a javascript web page, android and php graphing program. Apologies for the slight out of sync-ness, the web page polls temperature faster causing the one on the phone and page to be different.

I was always really interested in making new projects for my arduino, the first of which was an LED music visualizer. The problem here was that arduinos aren't really popular in Pakistan, I had to get my current duemillenove shipped in from China thus getting an internet shield would be very difficult. One fine day I started thinking and said to myself, hey I nave a dedicated home server why not just hook up the arduino to that and write a program on the server to read that data through serial and use it somehow. Initially it was designed just to be able to read the current temperature, graphing was added in later. Here is how it works: The first part of the process is an arduino which has a  DS18B20 Temperature Sensor attatched to it. Using serial communication, it sends out temperature readings to my dedicated home server. The next part is a python script running in an infinite loop, it reads the values from the serial port and then proceeds to put them in a MySQL table, this occurs once every 2 minutes.
import MySQLdb
import time
import serial
import sys
conn = MySQLdb.connect (host = "localhost",
 user = "derp",
 passwd = "murp",
 db = "tempdata")

while True:
 timestamp = int(time.time())
 ser = serial.Serial('/dev/ttyUSB0');
 linebeep = ser.readline()
 line = ser.readline()
 cursor = conn.cursor()
 cursor.execute("""INSERT INTO Data(Temperature, Time)
 VALUES(%s, %s)""", (line, timestamp,))
 print timestamp
 print line
The final stage is a php script using jpgraph to plot the data from the mysql table.
require_once ('jpgraph/src/jpgraph.php');
require_once ('jpgraph/src/jpgraph_line.php');
$link = mysql_connect('localhost', 'derp', 'hurp');
if (!$link) {
    die('Could not connect: ' . mysql_error());
mysql_select_db("tempdata", $link);
$query = mysql_query("SELECT Temperature from Data");
$i = 0;
while($row = mysql_fetch_array($query)){
    $holder = (float) $row[0];
    $temparray[$i] = $holder;
$query2 = mysql_query("SELECT Time from Data");
$p = 0;
//This part of the script is used to get the dates at the bottom
while($row = mysql_fetch_array($query2)){
    $rowint = (int) $row[0];
    $kitteh = date("G:i",$rowint);
    if($kitteh == "0:01" || $kitteh == "0:02" || $kitteh == "0:00" || $kitteh == "0:03"){
        $datesarray[$p]= date("j M", $rowint);
        $datesarray[$p] = "";
$graph = new Graph(1300,700);
$theme_class=new UniversalTheme;
// Create the first line
$p1 = new LinePlot($temparray);
//$p1 = new LinePlot($newx);
// Output line