Read and Write SPI Flash on a ESP8266 based board
I found myself in the situation of having a proprietary firmware for an ESP8266 based chip (Wemos D1 mini pro) which was unable to escape special characters in the configuration web interface. When trying to set the local WiFi password — containing some special characters — the firmware would not accept the input. Usually ESP8266 based boards have an integrated writable flash memory for persistent configuration storage. I did not want to change the WiFi password because of one device’s inability to handle special characters. In order not to change the password on the router and therefore all devices in the network, I edited the contents of the writable flash storage on the board instead.
Table of Contents
How?
The idea is simple: Write an easily recognizable pattern to the flash memory by setting the WiFi password, dump the memory and look for the pattern, modify the memory at the pattern location, set our desired WiFi password and finally write the modified memory back to the device.
Tools
Luckily there is the esptool.py1 to read and write arbitrary memory locations of the board.
After installing, flash memory can be read with the read_flash
command:
usage: esptool read_flash [-h] [--spi-connection SPI_CONNECTION]
[--no-progress]
address size filename
The command requires the address of the flash memory we want to read from, the size of the memory region to read and the filename where to write the binary image to.
Writing a recognizable pattern
This is the easiest step of them all. Connect the board and power it up. Now make a configuration that will be recognizable later. I tend to chose a random ASCII character and type it repeatedly. Any string probably is fine, if your hex editor can search for strings that wrap over multiple rows.
For now I will just enter AAAAAAAAAAAAAAAAAA
as the WiFi password to find it later.
Looking up the memory layout
I did not find any information about the memory layout of the Wemos D1 mini pro, so I assumed the ESP’s memory layout will work too.
The ESP8266 has several memory regions, most of which are not the flash memory.2
The regions relevant to us are in the lower end of the address space.
The user-rw flash starts at the address 0x7e000
.3
The Wemos D1 mini pro’s SPI flash memory size is 16 MiB with a sector size of 4 kiB. The flash memory has to be read and written in multiples of the sector size.
Reading the memory
In the first step we want to read the entire flash, because we don’t know where the WiFi configuration will be stored.
To do so, let’s set the parameters for the read_flash
command of the esptool.
The flash starts at 0x7e000
, so this will be our starting address. We need to read 4 MiB of memory, which are \( 4 \cdot 1024^2 = \mathtt{0x400000} \) bytes.
We want our output to be saved in the file flash.bin
.
This leaves us with the following command:
esptool.py read_flash 0x7e000 0x400000 flash.bin
This is going to take a while.
Finding the relevant memory area
After dumping the whole flash memory to a file, we can inspect it using our favorite hex editor.
I use emacs to open, inspect and modify the file (hexl-find-file
).
Next we look for the recognizable string we entered in the first step. Let’s see if I can find it:
0037d000: fc00 0000 e803 0000 0000 0000 0000 0000 ................
0037d010: 0000 0000 0000 0000 0000 3c00 0000 0807 ..........<.....
0037d020: 2a00 0000 0000 0000 0000 0000 1b28 6801 *............(h.
0037d030: 6801 6801 6801 0000 0000 0000 0000 0000 h.h.h...........
0037d040: 0000 0100 0200 0000 0000 0000 0000 0000 ................
0037d050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0037d060: 0000 0000 ffff ffff ffff ffff ffff ffff ................
0037d070: ffff ffff ffff ffff ffff ffff ffff ffff ................
0037d080: ffff ffff ffff ffff ffff ffff ffff ffff ................
0037d090: ffff ffff ffff 4d79 2053 7570 6572 2047 ......My Super G
0037d0a0: 7265 6174 2057 6946 6920 426f 7804 ffff reat WiFi Box...
0037d0b0: ffff ffff ffff ffff ffff ffff ffff ffff ................
0037d0c0: ffff ffff ffff ffff 4141 4141 4141 4141 ........AAAAAAAA
0037d0d0: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
0037d0e0: 4141 04ff ffff ffff ffff ffff ffff ffff AA..............
0037d0f0: ffff ffff ffff ffff ffff 6d79 5f63 6375 ..........my_ccu
0037d100: 6970 04ff ffff ffff ffff ffff ffff 6465 ip............de
0037d110: 2e70 6f6f 6c2e 6e74 702e 6f72 6704 ffff .pool.ntp.org...
0037d120: ffff ffff ffff ffff ffff ffff ffff ffff ................
0037d130: ffff ffff ffff ffff ffff ffff ffff ffff ................
0037d140: ffff ffff ffff ffff ffff ffff ffff ffff ................
There it is! Starting at address 0x0037d090
seems to be my WiFi SSID and right below it there seems to be my recognizable password!
But wait, we shouldn’t change the password yet. If we did that now, we would have to write the whole 4 MiB to the device again. That can’t be good for our poor flash sectors and our free time.
We don’t need to read and write the entire memory again now that we know where the relevant configuration is stored.
We can start reading at the relative offset 0x0037d090
and stop a couple of bytes after that!
Our SSID and WiFi password will still be in there.
Keep in mind that we started reading at the address 0x7e000
, so we have to add our offset on top of that.
We have to pay attention to the flash memory sectors because the esptool and the underlying flash storage only support writing entire flash sectors which are multiple KiB large. The Wemos data sheet says that the flash sector size is 4 KiB, thus we are only able to read and write 4 KiB blocks of memory. Also the memory has to be aligned to a block (we can’t just read the last half of the first block and the first half of the second block).
First, let’s calculate the starting memory address by adding the physical base address to the offset we found:
\begin{equation} \mathrm{base} + \mathrm{offset} = \mathtt{0x7e000} + \mathtt{0x37d090} = \mathtt{0x3FB090} \end{equation}
Modifying and uploading the memory
Now that we know where the relevant section is, we can read the smallest possible (4 KiB = 0x1000
) area of memory that we need.
esptool.py read_flash 0x3FB090 0x1000 flash.bin
We can see that the relevant section starts right in the beginning of the file. Also the relative offset starts at 0 again.
Now we can finally make our changes to our WiFi password by overwriting the series of A
characters with our desired special characters-containing password.
After the last character of our string, we still need to place the NULL terminator, which in this case seems to be EOT (0x04
) instead of 0x00
.
After all modifications, the binary file looks like this:
00000000: fc00 0000 e803 0000 0000 0000 0000 0000 ................
00000010: 0000 0000 0000 0000 0000 3c00 0000 0807 ..........<.....
00000020: 2a00 0000 0000 0000 0000 0000 1b28 6801 *............(h.
00000030: 6801 6801 6801 0000 0000 0000 0000 0000 h.h.h...........
00000040: 0000 0100 0200 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: 0000 0000 ffff ffff ffff ffff ffff ffff ................
00000070: ffff ffff ffff ffff ffff ffff ffff ffff ................
00000080: ffff ffff ffff ffff ffff ffff ffff ffff ................
00000090: ffff ffff ffff 4d79 2053 7570 6572 2047 ......My Super G
000000a0: 7265 6174 2057 6946 6920 426f 7804 ffff reat WiFi Box...
000000b0: ffff ffff ffff ffff ffff ffff ffff ffff ................
000000c0: ffff ffff ffff ffff 5375 7065 7253 3363 ........SuperS3c
000000d0: 7223 7450 5f24 2477 6f72 6404 4141 4141 r#tP_$$word.AAAA
000000e0: 4141 04ff ffff ffff ffff ffff ffff ffff AA..............
000000f0: ffff ffff ffff ffff ffff 6d79 5f63 6375 ..........my_ccu
00000100: 6970 04ff ffff ffff ffff ffff ffff 6465 ip............de
00000110: 2e70 6f6f 6c2e 6e74 702e 6f72 6704 ffff .pool.ntp.org...
00000120: ffff ffff ffff ffff ffff ffff ffff ffff ................
00000130: ffff ffff ffff ffff ffff ffff ffff ffff ................
00000140: ffff ffff ffff ffff ffff ffff ffff ffff ................
The absolutely final step is to write the modified file back into the flash memory at the same address we read from:
esptool.py write_flash 0x3FB090 flash.bin
That’s it! We successfully modified the WiFi password on a proprietary firmware.