Yet another reverse engineering blog

Thursday, December 20, 2007

Hacking the Kindle part 2: bootloader and firmware updates


The Kindle uses Das U-Boot bootloader. To break into the interactive shell, just press any key right afer reset.
"help" gives a list of available commands

KINDLE> help
? - alias for 'help'
badblocks - print OneNAND bad block info
base - print or set address offset
bbm - BBM sub-system
bdinfo - print Board Info structure
boot - boot default, i.e., run 'bootcmd'
bootd - boot default, i.e., run 'bootcmd'
bootf - boot from various options
bootm - boot application image from memory
cmp - memory compare
coninfo - print console devices and information
cp - memory copy
crc32 - checksum calculation
dcache - enable or disable data cache
diags - execute the User Diagnostics from OneNAND
dsleep - sleep USB device controller
dwake - wake USB device controller
echo - echo args to console
erase - erase FLASH memory
exit - exit script
factory - string [lock] [LLL_RR_PP]
fatinfo - print information about filesystem
fatload - load binary file from a dos filesystem
fatls - list files in a directory (default /)
fb - framebuffer subsystem
flinfo - print FLASH memory information
gain - displays/sets the gain value
go - start application at address 'addr'
help - print online help
hsuspend - suspend the 1761 USB host controller
hwake - wake USB host controller
icache - enable or disable instruction cache
iminfo - print header information for application image
itest - return true/false on integer compare
keys - prints out hex values from device keyboard until console key is pressed
kindle - print info about Kindle's revision
load - load OneNAND page into DataRAM
loadb - load binary file over serial line (kermit mode)
loads - load S-Record file over serial line
loop - infinite loop on address range
loopw - infinite write loop on address range
mbboot - boot bootloader from MMC/SC card
md - memory display
mdc - memory display cyclic
mkboot - boot kernel & initrd from MMC/SD card
mm - memory modify (auto-incrementing)
mmcinit - init mmc card
mtest - simple RAM test
mw - memory write (fill)
mwc - memory write cyclic
nand - NAND sub-system
nboot - boot from NAND device
nm - memory modify (constant address)
ohms - calculates board resistance
onenand - print OneNAND register info
opamp - displays/sets the op-amp offset value
otp - dump/read/write OneNAND OTP
printenv- print environment variables
protect - enable or disable FLASH write protection
reboot - alias of reset to match kernel
reset - perform RESET of the CPU
run - run commands in an environment variable
rve - displays/sets the rve (reference voltage error) value
saveenv - save environment variables to persistent storage
serial - set/display board serial number in OTP
setenv - set environment variables
sleep - delay execution for some time
snuz - put PXA to sleep
test - minimal test like /bin/sh
update - update sub-system (updates images from MMC/SD card to flash)
usb_init - init USB host controller
version - print monitor version
write - write DataRAM buffer to OneNAND page

Let's see what we can do with bbm command

KINDLE> ? bbm
bbm format
- format device
bbm open
- open device
bbm eraseall
- erase all blocks
bbm erase 'start' 'end'
- erase blocks from 'start' to 'end'
bbm load image 'id' ['start']
- load image from partition 'id' into RAM;
image is loaded into RAM at location 0xA2000000
or into 'start' if 'start' is specified (in hex)
bbm save image 'id' ['start'] 'size'
- save image of 'size' to partition 'id';
image should be loaded into RAM at 0xA2000000
or into 'start' if 'start' is specified (in hex)

- Partition Info -
bbm show partition
- show partition information
bbm save partition
- save partition information
bbm del partition
- delete last partition
bbm add partition 'id 'attr' 'blocks'
- add partition 'id' of type 'attr' and of size 'blocks'
KINDLE> bbm show partition
id : Bootloaders, Diagnostics (3)
attr : RW (1)
first_blk : 0 (0x00000000)
no_blks : 12 (1.5 MB)
id : Standard Kernel (17)
attr : RW (1)
first_blk : 12 (0x00180000)
no_blks : 12 (1.5 MB)
id : Recovery Kernel (16)
attr : RW (1)
first_blk : 24 (0x00300000)
no_blks : 12 (1.5 MB)
id : Standard Initrd (15)
attr : RW (1)
first_blk : 36 (0x00480000)
no_blks : 10 (1.3 MB)
id : Recovery Initrd (14)
attr : RW (1)
first_blk : 46 (0x005C0000)
no_blks : 10 (1.3 MB)
id : Read-only Root Filesystem (8)
attr : RW (1)
first_blk : 56 (0x00700000)
no_blks : 96 (12 MB)
id : Default Content (9)
attr : RW (1)
first_blk : 152 (0x01300000)
no_blks : 120 (15 MB)
id : Read/Write Root Filesystem (10)
attr : RW (1)
first_blk : 272 (0x02200000)
no_blks : 144 (18 MB)
id : Userstore (11)
attr : RW (1)
first_blk : 416 (0x03400000)
no_blks : 1584 (198 MB)
id : Environment Variables (4)
attr : RW (1)
first_blk : 2000 (0x0FA00000)
no_blks : 2 (256 KB)

The diagnostics U-Boot image is stored in the first partition.
There are two kernels, standard and recovery with corresponding ramdisks. Recovery kernel is used for firmware update.
There is a read-only root filesystem and read-write part. There is a partition with default content used for factory reset.
Then there is a userstore partition, which is visible as mass storage drive over USB.

There were no commands to copy data from flash to SD/MMC, but I could load flash partitions into memory and dump that.

KINDLE> bbm load image 3
Loading partition "Bootloaders, Diagnostics" into 0xA2000000... Success
KINDLE> base a2000000
KINDLE> md.b 0 100
a2000000: 4e 69 63 6b ff ff ff ff ff ff ff ff ff ff ff ff Nick............
a2000010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
a2000020: 0e 00 00 ea 18 f0 9f e5 2c f0 9f e5 18 f0 9f e5 ........,.......
a2000030: 18 f0 9f e5 18 f0 9f e5 18 f0 9f e5 18 f0 9f e5 ................
a2000040: 60 00 00 a2 20 01 00 a2 80 01 00 a2 e0 01 00 a2 `... ...........
a2000050: 40 02 00 a2 a0 02 00 a2 00 03 00 a2 40 03 00 a2 @...........@...
a2000060: 00 00 0f e1 80 00 c0 e3 00 f0 29 e1 7c 00 9f e5 ..........).|...
a2000070: 21 0a 40 e2 02 0c 40 e2 02 0a 40 e2 0c d0 40 e2 !.@...@...@...@.
a2000080: 70 00 9f e5 70 10 9f e5 00 20 a0 e3 00 20 80 e5 p...p.... ... ..
a2000090: 04 00 80 e2 01 00 50 e1 fb ff ff 1a e6 80 00 ea ......P.........
a20000a0: 00 00 a0 40 00 00 00 00 00 00 00 00 00 00 00 00 ...@............
a20000b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
a20000c0: 28 00 1f e5 18 10 90 e5 01 10 81 e3 18 10 80 e5 (...............
a20000d0: 10 10 90 e5 02 1b 81 e2 02 1b 81 e2 0c 10 80 e5 ................
a20000e0: fe ff ff ea ef be ad de ef be ad de ef be ad de ................
a20000f0: 00 00 00 a2 20 00 00 a2 1c 56 06 a2 98 52 4a a3 .... ....V...RJ.

(Nick probaly refers to Nick Vaccaro of Lab126, who apparently was one of the main Kindle software developers. Hi Nick!)

Due to either my cable or inconsistent terminal settings, any dump of over 256 bytes was returning only some bytes in the beginning and some in the end. So I had to conjure up a little Python script to send dump commands in chunks of 256 bytes, parse the output and write it to a file. It took a few hours per partition, but in the end I was able to dump what I needed. In the initrd image of the recovery kernel I found scripts that performed firmware update and so I could reverse the update file format.

Firmware updates

Firmware update can be performed both over-the-air and from the SD card or mass storage partition.

The firmware update file should match the mask "update*.bin" and reside in the root of the userstore partition (Kindle's USB drive) or SD/MMC card. There should be only one such file present. The file consists of a header and scrambled .tar.gz file with update files.

offset size  value
0 4 signature (OTA: "FC02", manual: "FB01")
4 4 fromVersion (minimal version to update)
8 4 toVersion (maximal version to update)
0C 2 deviceCode (number in 3rd and 4th characters of the serial, i.e. 01)
0E 1 updateOptional (seems unused)
0F 1
10 32 scrambled md5 hash string of the tgz
20000 ? scrambled tgz with update files

Version value is made from the kindle version string. In my unit, it's 292-Kindle-012138 and the version value is 121380292 (12138*10000 + 292).

The manual update doesn't check any fields except signature and md5.

Scramble algorithm:
byte = rol(byte,4)^0x7A;

byte = rol(byte^0x7A,4);

The tgz should contain a text file matching the mask "update*.dat". Its lines have the following format:
id md5 filename block_count display_name

id is the ID of the flash partition to write to. I know the following numbers:
6 base RO fs (squashfs image)
7 default content (squashfs)

block_count is the number of 128K blocks to flash (see bbm show partition output above).

If id is 129, the file is considered a shell script and is executed.
If id is 128, the md5 is checked but nothing is done with the file.

I made a small Python script to assemble an update file with all checksums calculated automagically. It will be included in the next post.

To do a manual update, first put the update bin in the root of Kindle's flash drive or SD card. Then hold Home button while resetting the Kindle. In a while you will see the following menu:
Service menu
2 "Firmware Reset" clears all user-specific data and settings and returns the Kindle to factory state. I'm not sure if it will be usable without extra initialization by Amazon technicians.
3 "Exit" starts normal boot process.
0 (not shown) starts the diagnostics bootloader. Interaction is done mostly over the console, you won't see much on the Kindle screen.
1 "Firmware update" starts the manual update process.


Anonymous said...

You know, this is the best pre-Christmas reading EVER! I am enjoying your posts re your findings, and wish you the very best of luck in this.

Keep up the good work and have a very merry christmas!

Fiona Hackworth said...

igor, thank you for the fantastic work, as always!

Will you be posting the filesystem images somewhere? I seem to have misplaced my soldering iron...

Vlad said...

Hmm, that firmware header format doesn't seem to be valid with the OTA update (header FC02), which seems to be the only one downloadable. Any idea what the format is for the OTA updates? Even after decoding the entire file I don't see anything that looks like a gzip header in any reasonable location...

vlad said...

Scratch that, had a bug. With the OTA updates, the tgz seems to start at offset 0x40.

Anonymous said...

If you download the update*.bin from Amazon, the header in that file starts at 0x40. If that decoded .TGZ file is unpacked, there is another update*.bin inside that one, with the actual Linux images on it. This second bin file has a header which starts at 0x20000.
Unpacking the second time gives a .TGZ with the Linux .IMG files, and some other stuff.

wz2b said...

Do you think it would be reasonable to implement the screen saver hack by making a symlink between /opt and /mnt, so that you can see the "real" screen saver directory from a normal USB mount, and delete/add as you wish? said...


Anonymous said...

Kindle source code is posted on Amazon website
Help > Digital Products Help > Amazon Kindle Wireless Reading Device > Amazon Kindle Terms, Warranties, & Notices > Source Code Notice

Matthew said...

Apologies for being a bit thick but I got a bit confused when I read:

"To break into the interactive shell, just press any key right afer reset."

Does that mean that it will give me an interactive shell on the actual Kindle's screen or is this only via the pin-outs?

Thanks for your time.