================= MT1939 Bootloader ================= Notation -------- All numbers are hexadecimal. Memory accesses are denoted as Width[Address], where Width is B, H, or (blank) for Byte, Halfword, or Word respectively. C-style operators "|=", "&=", etc. are shorthand for read-modify-write operations which require a load and store. SCSI Commands ------------- Handled by the bootloader: 12 Inquiry 1B Start / Stop Unit 46 Get Configuration 4A Get Event Status Notification 5A Mode Sense (10) 3C Read Buffer 3B Write Buffer F1 Undocumented FF Undocumented Command FF has its opcode followed by a 16-bit subcommand. Unrecognized commands are ignored silently without an error response. This is the command used by the official firmware updater app from TSST. FF 00 01 00 00 xx Firmware update (x=0,ff,2) FF 00 04 00 00 0x Set [4002058] bit0 to 'x' (bootloader "app select") FF 00 05 Return packet '00 0x' with bit0 of [4002058] FF 00 FF Firmware version info, bootloader returns: 00 06 01 01 54 53 20 00 The F1 command seems to be another vendor specific undocumented thing. This one has a big switch tree of subcommands, including one that seems to invoke app firmware unconditionally? Read Buffer (3C) and Write Buffer (3B) are part of the spec for firmware updates, but TSSC clearly went their own route. Looks like these might be fully custom, or they might be some vestiges of official spec support that they decided to drop. Whatever the reason, these turn out to be super useful. 3c 02 00 aa aa aa ll ll ll Read 0xllllll bytes from address 0xaaaaaa. This is a new address space, possibly a bus address for the USB controller. The implementation of this command passes the address directly to the same DMA controller that usually receives the address of an allocated DRAM buffer. This address space has access to DRAM, but it's unclear whether there's a linear mapping. For now, I've noticed this one correspondence which seems to be the second (larger) DSP firmware image. [EXAMPLE] Correspondence between I/O address space and firmware in DRAM %sc 1000 3c 2 0 0 10 0 0 10 0 %rd 1ff2022 1000 3c 06 00 aa aa aa ll ll ll Read from ARM memory. Since the address is only 24 bits, this can't reach RAM or MMIO. It can read from flash, including the weird mapped regions like the one at 0x000. [EXAMPLE] These backdoor shell commands return identical data: %sc 100 3c 6 0 2 ab cd 0 1 0 %rd 2abcd 100 (*) Bonus! It's possible to use this command to "see" through the data cache, to see what values the firmware has been using recently. Just after erasing the flash, you can use this command to dump memory. Anything still in the cache will show up against the background of ff 3b 01 00 Much simpler 3b 02 00 Seem to be synonyms for the same complicated command? 3b 06 00 Involves a bootloader flash self-test Memory Map ---------- 00000000 - 00000fff Encrypted page? May be transparently decrypted on exec? - ARM code issues function calls into this page - Looks like part of a syscall interface 00001000 - 000013ff ARM startup code - Low level bootloader 00001400 - 00002fff ARM application code - Includes main loop, SCSI command handlers - Might be usermode - Probably compiled using a higher level SDK 00003000 - 0000dfff ARM library code - Bulk of code is here - Invoked by startup code 0000e000 - 0000ffff 8051 firmware image - Self contained, maps to 8051 code memory - Unknown how this is loaded - Implements USB Storage protocol - Interprocessor communication pipe between ARM and 8051 00010000 - 001e1fff Loadable firmware image (1863 kB) 001e2000 - 001fffff Bootloader information region (120 kB) - Not checksummed - Lots of reserved blank space - Interesting data table at 1f2000 - Might be keys at 1f2080 - Zero-padded 256-byte blob at 1fe000 Opcodes or other structured data, some aligned 16-bit values too - Signature block at 1ff000, matches 16 bytes at 10ff0 - Infonub at 1fffd0 (product/vendor/version, weak checksum) Initialization -------------- First known step: - ARM processor starts, PC=0 T=0 Mappings are already set up such that addresses 000:3ff map to 1000:13ff in flash. - Boot code 1000 - SoC main reset [4001000] |= 10 [4001000] &= 10 - Set up RAM segments First one is 6 kB, second one is 2 MB. [4020f20] = 02000000 [4020f24] = 020019ff [4020f04] |= 0x800 [4030f04] = 2 [4031004] = 8 [4031000] = 40000800 Addresses calculated based on masking off 1ffff/2 [40300f4] = 1ffff [4030f20] = 1e00000 [4030f24] = 1ffffff [4030f40] = 2000000 [4030f44] = 20019ff [4030f04] = 8802 - Something else! [4011f04] |= 0xB00 - Stack to 2000d80 - CPSR to D3 - Branch to next bootloader section, "MT1939 Boot Code" at 3010. - Boot code 3010 ... SVC 0x123456, R0=18 R1=20026 R1 could plausibly be an entry-related address (Sadly this SVC leads into the encrypted area...) - Hard to nail down exactly which checks go into running or not running the image, since it's a huge foresty state machine. Here's a running list of things that seem to matter: 10ff0 128-bit key (big endian) 10400 Table of 128-bit memory segment hashes 1ff000 Signature matches 10ff0 1ffff8 16-bit value aaaa 1ffffe 16-bit checksum over range 10000:1e0000 - Finally, if the image is good, we install it by copying 0x1000 bytes from 0x10000 to 400000, which seems like either an uncached mirror of RAM, or a special memory region where we'd keep the application IVT. This is the same length as the encrypted region, so it's possible there's a kind of specialized memory in use for that page. App-select Register ------------------- The word register at [4002058] seems to be heavily involved in the process of deciding what path the bootloader takes; whether to rn the app, and a few other things. So I called it "appselect". Just prior to the main loop: [4002058] |= 40 (Set bit 6) In the main loop: If bit6 is 0, act as if the checksum test failed. If bit6 is 1, enable an increment by 1f000 and transfer of 1000 bytes on the way out (setting ref[10] to 200000). Just prior to exit (after ref[10] >= 200000) [4002058] |= 80 (Set bit 7) Magic numbers ------------- a1b9d358 13201959 Vector Table ------------ The area from 10000 - 102ff is set aside for interrupt vectors. Differences between TS00 and TS01 seem to only be related to code addresses changing; no validity checks in this section as far as I can tell. Signature --------- Bootloader reads two 16-byte regions: 01ff000: d7a3 e98e 5a3a 2573 357d 324b 0ccb 1f53 ....Z:%s5}2K...S 0010ff0: d7a3 e98e 5a3a 2573 357d 324b 0ccb 1f53 ....Z:%s5}2K...S From near the beginning and end of the application firmware image. All 16-byte signatures were identical in the images seen so far. It looks like they're intended to be identical, but I haven't yet determined exactly how they're checked. Seems like it involves the 8051 processor as well. The one at 1ff000 is read just after the infonub checksum is validated. xxxxxxxx - 001e2000 blocksize 1000 Infonub ------- There's a 48-byte region at the end of flash (001fffd0 - 001fffff) that I've been calling the "infonub". It's a tiny nub of versioning and integrity data that the bootloader checks before invoking the application firmware. 1fff90: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................ 1fffa0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................ 1fffb0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................ 1fffc0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................ 1fffd0: 54 53 53 54 63 6f 72 70 42 44 44 56 44 57 20 53 TSSTcorpBDDVDW S 1fffe0: 45 2d 35 30 36 43 42 20 54 53 30 31 3e 22 04 00 E-506CB TS01>".. 1ffff0: 54 53 20 ff ff ff ff ff aa aa ff ff ff ff 59 18 TS ...........Y. Most of this is clear vendor, product, and revision info. A lot of it is boring. - This version info appears to be only for image identification. It isn't checksummed, and changing it doesn't result in any difference in INQURIY results from the device. The last two bytes are a really dumb 16-bit checksum: def get_stored_checksum(fw): return (ord(fw[0x1ffffe]) << 8) | ord(fw[0x1fffff]) def calculate_checksum(fw): s = 0 for i in range(0x10000, 0x1e2000): s = 0xffff & (s + ord(fw[i])) return s def file_info(name): fw = open(name, 'rb').read() print '%s stored:%04x calc:%04x' % ( name, get_stored_checksum(fw), calculate_checksum(fw)) >>> file_info('SE-506CB_TS00.bin') SE-506CB_TS00.bin stored:06e8 calc:06e8 >>> file_info('SE-506CB_TS01.bin') SE-506CB_TS01.bin stored:5918 calc:5918 *** Verified that this is not the ONLY checksum in use. A trivially hacked firmware will not boot even with this checksum fixed. Image Validity -------------- How does the bootloader decide that an application firmware image (everything from 10000 to 1fffff) is valid before executing it? If this validity check fails, the bootloader will present its own tiny USB storage interface that only supports firmware uploading. - The bootloader region from 0 through 10000 is explicitly NOT verified. It seems to be completely ignored during upload. Possibly it was included in the flashing procedure to support initial installation of the loader in th factory using the same binary image? - There's a 16-bit checksum at 1ffffe, covering memory from 10000 up to 1e2000. - Experimentally found an area that isn't validated 1e2000 - 1f2000 (64 kB). This might be reserved for runtime storage, hence the nice round size and lack of checksum. It contains all FF in the shipping firmware images. 1f2000 - 1fa07f (0x8080) The beginning contains an important-looking table that I don't want to change until I know what I'm doing, but the end of this region is zero-padded. Changes here still allow the image to boot. 1fa080 - Changes here also seem fine. This whole region that isn't CRC-protected might not be validated, but lots of it is scary data tables that will probably brick the device if it does boot the modified image. 1ff300 - 1ff307 Verified okay So far I haven't had luck in updating the checksum at 1ffffe after changing the image, BUT it seems possible to modify unused space (such as at 1083c) to adjust the checksum of the image to match the stored one. Area at 10400 is a good candidate for additional image checksumming. Possibly this is checked by the bootloader; possibly it's checked by the firmware itself after it starts up (and the cheesy 16-bit checksum might be the only check used by the bootloader itself). ROM:00010400 dword_10400 DCD 1 ; DATA XREF: ROM:0016838A ROM:00010400 ; sub_16841C+8 ROM:00010404 DCD 0x11000 ROM:00010408 DCD 0x16FFF ROM:0001040C DCD 0x3477DBC5 ROM:00010410 DCD 0xD5C4AB44 ROM:00010414 DCD 0x2A69E0AC ROM:00010418 DCD 0x7ABD7E09 ROM:0001041C DCD 1 ROM:00010420 DCD 0x17000 ROM:00010424 DCD 0x1817FF ROM:00010428 DCD 0x114CF615 ROM:0001042C DCD 0x217A4416 ROM:00010430 DCD 0x9EAC335F ROM:00010434 DCD 0x3138D3CC FFFFFFFF .... This seems to indicate two logically separate areas within the firmware image: possibly a second-stage loader, then the higher level application. The boundary at 17000 does correspond with a logical boundary in the allocation of the firmware image, and 1817ff is the very end of the 8051 firmware image (right before the two maybe-DSP-image blobs). Very likely that these are checksums! The length would be right for MD5, but it doesn't seem that simple. Looks like the checking happens in this first image. So 11000:16fff is a kind of second stage loader that does these checks. Should be able to test this theory by looking at regions that are covered by the checksum16 but not by these validations. The checksum covers 10000-1e2000. The tests above made changes in the area between 10000:11000 successfully with this technique. - Understanding this check will also tell us how the firmware can exit back to the bootloader safely! These values appear to be calculated using a 128-bit block cypher implemented in hardware. Keys are loaded by the bootloader from the firmware header. TS00 and TS01 both use the same key: 0010ff0: d7a3 e98e 5a3a 2573 357d 324b 0ccb 1f53 ....Z:%s5}2K...S This is also loaded into address 0x0000 in the ref[20] 8051 mapping area. This is plausibly just a lazy place to put the key during loading, so I'm not yet sure if the 8051 firmware actually needs it, or if that MCU even has a compatible crypto core. ... much more research follows, and it seems like the MCU has an 128-bit block ... cypher core, and there are several different parts of the boot process ... that use it. It also seems to have a way of transparently decrypting code ... from flash or remapping writable regions over the encrypted functions so ... that unencrypted versions can be substituted. The most straightforward implementation of the signature verification seems to be in the implementation of SCSI command 3B "Write Buffer" with undocumented functionality that seems to include a different unused form of firmware updater. The crypto portions of this operation are contained nicely within a critical section, so it's easy to see what needs to stay together. Beginning the critical section: ~MeS`14