Realtek Wifi Firmware: Part 1

Firmware Header

In, the author mentions this:

It turns out there is a 0x20 byte header

I was curious how the author came to this conclusion. We’ll look at rtl8188efw.bin which is loaded and serviced by rtl8188ee.ko. The source code for this driver can be found here: Particularly interesting is the c file that handles loading and unloading the firmware:

Looking at the first 0x40 bytes of rtl8188efw.bin we see this:

00000000: e188 1000 0800 0000 1025 2156 b02b 0000  .........%!V.+..
00000010: a204 0000 0000 0000 0000 0000 0000 0000  ................
00000020: 0245 3500 0000 0000 0000 0000 0000 0000  .E5.............
00000030: 0000 00c1 5600 0000 0000 0000 0000 0000  ....V...........

The Ghidra disassembly for offset 0x20 looks like this:

// CODE 
// CODE:4000-CODE:6bcf
CODE:4000 02  45  35       LJMP       LAB_CODE_4535

Which looks like valid 8051 disassembly for the beginning of a binary image. So it looks like this is the correct offset, But how do we confirm 0x20 is the correct offset?

It turns out the realtek wifi firmware header structure is actually defined in the Linux kernel:

struct rtlwifi_firmware_header {
	__le16 signature;
	u8 category;
	u8 function;
	__le16 version;
	u8 subversion;
	u8 rsvd1;
	u8 month;
	u8 date;
	u8 hour;
	u8 minute;
	__le16 ramcodesize;
	__le16 rsvd2;
	__le32 svnindex;
	__le32 rsvd3;
	__le32 rsvd4;
	__le32 rsvd5;

The first two bytes are a signature. In the image we are considering, this is 0xe188, which is the 16-bit little endian value 0x881e. This signature is verified by the linux kernel driver here:

Other field values include:

Some interesting notes:

What month is the hex value 0x10? Perhaps this should be interpretted as decimal even though its embedded in binary format. The same is true for the minute value 0x56 - this would be the decimal value 86 which is more than the number of minutes in an hour. Ditto for the date - 0x25 is way past the maximum month date value of 31.

ramcodesize matches the length of the firmware file minus the header size:

>>> hex(11216 - 32)

However, to answer our original question, by adding up the size of each field’s date type, we see that the size of this struct is 32 bytes or 0x20 in hex.

>>> hex(2 + 1 + 1 + 2 + 1 + 1 + 1 + 1 + 1 + 1 + 2 + 2 + 4 + 4 + 4 + 4)

Also, I do not see anything in the header which looks like a checksum.

at51 Results

The at51 base results are as follows:

$ at51 base rtl8188efw.bin
Index by likeliness:
        1:  0x3fe0 with 139
        2:  0x2526 with 63
        3:  0x64f with 58

We take the first result and add 0x20 for the header size to it to find the base address of 0x4000.

The libfind results for this firmware file are located here: Specifically, at51 libfind reports the ?C_START address as 0x593.

If we subtract 0x20 from 0x593, we get 0x573. Add 0x4000 for the firmware base address and we receive 0x4573 which is the second jump in the binary:

RTL Firmware Graph

Taking it further

I wonder what would happen if we increased the ramcodesize field in the header on a host with one of these cards installed? The symbol ramcodesize doesn’t occur anywhere else in the Linux Kernel, leading me to believe it’s used by the firmware exclusively.

Fortunately, at least on Ubuntu, the /lib/firmware files are only writable as root:

$ ls -lah /lib/firmware/rtlwifi/rtl8188efw.bin
-rw-r--r-- 1 root root 11K Jun 11 15:19 /lib/firmware/rtlwifi/rtl8188efw.bin

I currently don’t have access to the necessary hardware to perform this experiment, but would be interested to play around with it.