Dumping firmware out of a Z-Wave ASIC
Recently, in our team, we had to deal with Z-Wave equipments, including the RF protocol that we handled with a Software Defined Radio (SDR) and GnuRadio. The purpose of this article is not to go into details on the radio part as it will be done on a later publication. Nevertheless, during our researches, we had to extract the firmware from the Z-Wave ASIC that was in use.
A bit of history
Z-Wave protocol is highly based on ITU-T G.9959 specification. It is mostly used for home automation and people are discouraged to use it for security purposes because it is too easy to jam the radio (plus it has some security issues too).
Z-Wave has been developped by a Danish company called Zen-Sys that still provides ASICs and communication modules that can be easily integrated in consumer devices. Even if there might be another Z-Wave chip manufacturer, we only encountered Zen-Sys components so far.
Find the datasheet
In order to protect the device under test from our possible mistakes, we bought a basic magnetic door sensor. After a quick teardown, the chip appears to be a ZW0301 and we can see a nice 6-pin header that has to be the programming interface.
As you can guess, the first thing to do after identifying the chip is to grab its datasheet because we will need two things:
- the pinout of the chip in order to find the programming interface
- the bus protocol used to read and write to the internal flash memory
After a quick Google search, we were lucky enough to find a public FTP server that is distributing all the documentation, SDKs and software related to Zen-Sys ASICs and modules, including of course the ZW0301 programming datasheet we were looking for.
The good, the bad and the ugly behind the chip
First of all, I have to admit that this is the worst datasheet I have ever read so far. As you will see, implementation choices are both unusual and inefficient.
As a quick summary, the ZW0301 chip provides:
- 32 kB flash memory organized in 128 pages of 256 bytes
- 256 bytes of SRAM used for programming the flash memory
- a 8051 core
- an information memory block that holds lock bits for read/write protection and a 8-byte zone called "infodata"
The internal memory is not directly accessible through the SPI programming interface. You have first to get the 8051 core into the programming mode and then send commands to it. That involves several drawbacks:
- you have to provide an oscillator to the chip for the 8051 to run instead of only relying the SPI bus clock signal
- you cannot use standard SPI tools because the commands are specific to Zen-Sys ASICs
- you must handle specific timing when you read/write because the 8051 core has to "prepare" the data
In addition to that, in the datasheet you can spot some hilarious details such as:
The synchronization normally takes 1-2 attempts, but may take up to 32 attempts.
You can thus expect high reliability from that interface!
Following on the protocol details, each command you can send to the ASIC is 4-bytes long and you can read flash memory 1 byte at a time (hence the protocol inneficiency we were mentionning before). It is even trickier to write to the flash because you have to follow these weird steps:
- Erase the page you want to write
- Write the content, 1 byte at a time, into the SRAM
- Send a command to write the SRAM into a given flash memory page
To tell the ASIC you want it to be in programming mode is poorly documented in my opinion and we spent hours trying to have it work because some details were hidden in the timing section of the specifications.
Here are the steps that has to be done:
- Connect RST and GND pin together
- Pull SCK, MOSI and MISO pin low
- Apply VCC to the chip
- Wait about 20 ms (you have to wait at least 2**17 oscillator clock cycles which gives you about 4.1 ms with a 32 MHz oscillator and this chip seems to need at least a 8 MHz oscillator)
- Send the programming enable command on the SPI bus
- Check that the third byte received equals the second byte you were sending (ie. 0x53)
- Redo step 5 up to 32 times until step 6 is ok
As you can see, this almost looks like a voodoo ritual to have it work :-)
Interfacing with the chip
To implement a SPI compatible interface, I choosed to rely on the GoodFET project as I am already a contributor to it, both on the firmware side and on the client side.
For those who don't know yet this project, this is an awesome opensource hardware platform provided by Travis Goodspeed to interface your computer with electronic devices.
It already has support for several busses, including JTAG, SPI, I2C, and several radio components.
The firmware is written in C language but unless you have specific bit-banging or timing sensitive things to do in it, most of the job is done through the client utilities that are written in Python. Unfortunately, as we already mentionned, we do have timing sensitive stuff so we have to extend a bit the SPI firmware.
We will let you check the firmware modifications on the GoodFET project's subversion repository but we simply added 3 internal SPI commands and added 1 pin as an output to handle specific power-up timings.
On the client side, we wrote a new client that implements all the commands described in the datasheet:
Usage: goodfet.zensys verb [objects] goodfet.zensys info goodfet.zensys infodata goodfet.zensys writeinfodata 0x$infodata [xtal_freq] goodfet.zensys lockbits goodfet.zensys protect_flash goodfet.zensys protect_page0 goodfet.zensys bootsize $size goodfet.zensys dump $foo.rom [0x$start 0x$stop] goodfet.zensys erase all [xtal_freq] goodfet.zensys erase flash [xtal_freq] goodfet.zensys erase page $page_number [xtal_freq] goodfet.zensys flash $foo.rom [xtal_freq 0x$start 0x$stop] goodfet.zensys verify $foo.rom [0x$start 0x$stop]
Dumping the firmware
Now that we have an interface to read our ASIC firmware, we still need to figure out the pinout of the programming interface header.
Well, that's very easy to do by using a multimeter and sharp probes between the ASIC and the header.
After doing all that stuff, dumping the firmware is as easy as:
$ goodfet.zensys dump magnetic-sensor.bin Dumping code from 000000 to 07ffff as magnetic-sensor.bin. Dumped 000000 -> 000100. [...] Dumped 070000 -> 080000. $
Our goal was only to read the firmware so we did not test write or erase commands. If you find any bug, feel free to report it.