The Software

Another way to grab the flash of the device is by taking a look at the software updates. Luckily,
Coolermaster seems to be actively working on the firmware, killing bugs and adding lighting modes, and
you can download the latest version from their website. The website actually
has two versions: seemingly there's an EU and an US version of the firmware. That's because the
hardware between the two is actually a tad different: the EU version has a key extra.

The firmware updater is a single executable, with the firmware itself tucked into it somehow.
Before I could take a look at that, I needed to separate it from the rest of the executable. I
used the fact that there are two firmwares available to my advantage here. My line of thought was that
the UI and upgrade functions of both versions of the keyboard should be the same; there probably
aren't any differences in the PC code that handles the upgrade. That means most or all of the differences
in the two firmware executables should be in the firmware that's sent to the keyboard. To test this,
I ran a binary diff over the two files and indeed, all differences were concentrated in an 16K block at
the end of the executable, neatly separated from the rest of the file by zero padding.

I cut out this section of the executable, and took a better look at it. It seemed indeed to be
firmware-related: for example it started with the characters '1.1.7' which is the version number of
the firmware update I downloaded.

The rest didn't really strike me as something that belonged in the flash of a microcontroller. For
example, this is an USB keyboard and you'd expect the USB descriptor strings to be in the firmware
somewhere. The USB descriptor strings are UTF-16 strings indicating the name, manufacturer etc of the
device to the OS. Here, no UTF-16-string could be found at all, though. If this actually was the firmware,
it would either be compressed or encrypted.

To see what was going on, I took a better look at the actual bytes using a hex-editor. Here's a pretty
telling bit of it:

Two things can be seen here. First of all, there's a repeating string of 'A5 CA 88 A5' going on. No
compression worth its salt would leave those repetitions here; they can be compressed even with a
trivial compression system like RLE. Secondly, the byte 'A5' seems to be occurring an awful lot in
this firmware, and not only in the bit I've shown. That's not a byte you'd expect to occur that much.
Normally, you'd expect the byte '00' to do this, mostly because it's used as padding, when you e.g.
store an 8-bit value into a 32-bit variable.

So there seems to be something going on that as one of the effects replaces 00 by A5. There's only one
widely-used encryption that's byte-granular and has that effect, and that is XOR-encryption.
So I tried reversing the encryption by XORring every byte with 0xA5 and lo and behold, USB-16
description strings show up! (I later found out that apart from the XOR encoding, also a few of the first 1K blocks
were swapped.)

Now I had a working firmware dump, right? Well, throwing the decrypted firmware into a disassembler
indeed gave me a fair amount of valid-looking ARM code. It's not the entire flash contents,
though: there are some jumps that jump outside of the bit of code I had here. Also, there's a large
block that seems to be corrupted somehow: there are code jumps into it, but no valid ARM-code. All in
all, while this helps, it's no solid base to start programming Snake on yet.

There is another thing you can do with an executable file, and that's to actually execute it. With an
USB sniffer (in this case USBPcap) in the background, I could
then review the packets going back and forth and see if I could glance something from that. Also, my own
keyboard firmware still was an old version, so updating would probably be useful anyway.

After looking at the packet captures, I found out that the firmware update handles the communication
to the keyboard by sending packets to USB endpoint 3 and receiving them from endpoint 4. One of those
packets actually seems to switch the keyboard into a dedicated flash mode: the keyboard re-enumerates
with a different PID. The update packets have this format:

Byte 1-2: Command/subcommand, tells keyboard what to do

Byte 3-4: CRC16 of entire packet

Byte 5-8: 32-bit 'from'-address for command

Byte 9-12: 32-bit 'to'-address for command

Byte 13-63: Data

How are those packets used? Well, this is what happened during a firmware upgrade, plus what I
deduced the packets could mean:

Cmd

Addr

Data

1:2

0x2800-0x2808

(kb->pc) "1.1.5"

<- Read version

4:1

0

(Device re-enumerates)

<- Enter flash mode

0:8

0x2800-0x2808

-

<- Erase version

0:8

0x2c00-0x6ad4

-

<- Erase firmware

1:1

0x2c00-0x6ad4

Multiple, uploads fw

<- Write fw

1:0

0x2c00-0x6ad4

Multiple, uploads fw

<- Verify fw?

1:1

0x2800-0x2808

(pc->kb) "1.1.7"

<- Write new version

4:0

1

(Device re-enumerates)

<- Exit flash mode

Now, there's something interesting going on here. To erase the old and write the new firmware version
number, the firmware update sends the same commands as used to erase and flash the firmware itself, but
to a different region: 0x2800 instead of 0x2c00. From what, I could
conclude that the firmware version is just stored in a separate sector in the flash, simplifying the
management of it: the firmware wouln't need to implement a completely different set of commands just to
manage the version number.

But wait, the same 0x2800 address is is used to read the firmware version. If the firmware version
actually isn't more than data that happens to be in flash, it would make sense that the firmware version
read command actually isn't anything more than a general flash read command, useful for the entirety
of the flash. That would be incredible useful to me: by running that command over the entire flash, I
could essentially grab all the data that's in there.

I whipped up a small program using libusb that would do exactly that. The program ran without a hitch,
but what I got back wasn't exactly what I expected: aside from the sector the version was in, the
reads returned all zeroes. I didn't seem wrong in my assumption that the command to read the version
was a generalized flash read command; for example, when I increased the address by one, I would get all the data
one byte shifted. It looked like something else was keeping me from reading the interesting bits.
Well, a big bunch of the firmware update I decoded earlier was still corrupted, but maybe I could
find some info in the bits that were readable?

And after a bit of searching, I found some relevant code. This little chunk is called every time a byte is read
from the flash. It basically checks if the byte is read from an allowed region, and if so it jumps out
to the code that sends it. If not, the last line in the bit of code will replace it with zero. This
code would indeed result in the behaviour I saw. In theory, this is easily fixable: removing the
final line and replacing it with a NOP would mean the read byte would be passed as-is.

The replacement of the code with a NOP means a modification of exactly one byte, and after some
re-encrypting and twiddling with offsets, I knew exactly what byte I had to change in the original
firmware in order to have it flashed to the keyboard. By changing this byte, however, I made what's
essentially a corrupted firmware update. I didn't see any CRC checks that I could easily spot that
would stop the keyboard from running the code, but I might have missed a check that would make the
keyboard reject the upgrade. And then? Would it still accept a new firmware or would I have a dead
keyboard on my hands? Only one way to find out...

Well, after starting up Windows and running the executable, nothing special happened: it looked like
the firmware updater at least doesn't check itself. Plugging in the keyboard and starting the update
also worked without a hitch. After a while, my fears were relieved: the keyboard did actually light
up again after the firmware update was done. But did the new firmware actually take hold? I ran my
program again and this time the dumped data looked much better:

After running the dumped image through the disassembler, I could indeed confirm that this looked very
much like it was a complete flash dump. No weird corrupted bits, no links into the middle of nowhere,
just everything you'd expect from an unencrypted firmware dump. Seems like I had a backup of the
keyboard! Apart from me now being able to reverse engineer everything, this also meant that if I
ever were to make a boo-boo while developing Snake which bricks the keyboard, I could just use the ROM
bootloader to revert the flash to a working state. It also meant my JTAG/SWD jig got a lot more usable:
by doing a bulk erase of the microcontroller and then restoring the backup I had, I could disable the
nasty protection bit and e.g. single-step through the flash code again.