diff --git a/brom-dump/README.md b/brom-dump/README.md index d4d51af..851b2ae 100644 --- a/brom-dump/README.md +++ b/brom-dump/README.md @@ -9,6 +9,8 @@ * [Reverse engineering the Download Agent](#reverse-engineering-the-download-agent) * [Patching Download Agent](#patching-download-agent) * [Hello, world!](#hello-world) + * [Figuring out I/O API](#figuring-out-io-api) + * [The usb-dump payload](#the-usb-dump-payload) # Dumping mt6589 BROM @@ -16,7 +18,7 @@ Initially this part was meant to be more of a blog post than a clear and concise Dumping BootROM on modern Mediatek family (mt67xx) SoCs is quite a trivial task because we have [mtkclient](https://github.com/bkerler/mtkclient) that works in nearly automatic mode. -For slightly older devices we can always rely on modified generic payloads from the [bypass_payloads](https://github.com/chaosmaster/bypass_payloads) repository. +For slightly older devices we can always rely on modified generic payloads from the [bypass_payloads](https://github.com/chaosmaster/bypass_payloads) repository. However, for some reason even properly coded generic UART dump payload has never worked for me on mt6589. It felt like some hardware was either not initialized at all or initialized in some wrong way. Judging by Github commits no one has publicly shared mt6589 BROM dump at the time I started working on it so I decided to take a deeper look into what could I do. @@ -34,7 +36,7 @@ My idea is to obtain the original DA for my SoC and make it execute my code righ I started by searching the oldest available SP Flash Tool build for Linux that still supported mt6589. By the time first Linux support was added to SPFT its developers already started dropping code for older platforms. For example, mt6575 and mt6577 were among the first to get their support removed from SPFT though their DAs remained in a few later versions of `MTK_AllInOne_DA.bin`. The first search result led me to the [download page at spflashtool.com](https://spflashtool.com/download/) where I got the archive with the Linux variant of SP Flash Tool v5.1648. Worth mentioning the website is tricky because it doesn't want us to access archives via direct links. Instead, it runs a script to add an event listener that appends a special request header on clicking the link. If you access the direct link without this header you will get redirected to the main page. -By the way, the Linux version is more useful than the Windows one because the `libflashtoolEx.so` has debug symbols unlike its Windows counterpart :) +By the way, the Linux version is more useful than the Windows one because the `libflashtoolEx.so` has debug symbols unlike its Windows counterpart :) ``` libflashtoolEx.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=b570d3c0871606769140884647696f12d864c9b7, with debug_info, not stripped @@ -156,3 +158,59 @@ spft-replay.py "mt6589-hello-world-uart-da-api-payload.bin" ![Hello world payload output](../images/brom-dump-009.png) Success! My next step is to implement proper code for dumping BROM. + +## Figuring out I/O API +*I'm sure there's much better way to do things I'm about to do but I lack experience. If you are experienced in Ghidra get ready to cringe.* + +Previously I found some calls in `FUN_12000ccc` for USB I/O operations. They are referenced to as an offset to an address stored in `DAT_12000ef8`. + +The address is `0x00102114`. In *MT6589 HSPA+ Smpartphone Application Processor datasheet / Version: 1.0 / Release date: 2012-12-11* on page 44 we can see the `0x0010_0000 - 0x0010_FFFF` region belongs to On-Chip SRAM. + +![DAT_00102114](../images/brom-dump-010.png) + +This pointer has only 6 references and jumping to the first one in the list reveals a function (`FUN_12000b4a`, later renamed to `setup_io_ops`) that seems to setup a variety of function pointers depending on the value of `param_1` (renamed to `io_type`). It's clear to me that `io_type` indicates either UART or USB because DA has no other way to communicate with PC. + +For me it seems like `puVar1` in the decompiler window means usage of a struct to store said I/O function pointers. I renamed `PTR_DAT_12000ef8` to `ptr_io_ops`. Each branch of `if` statement sets 15 different pointers relative to `ptr_io_iops`. + +I created a 60-byte struct called `io_ops_s` but it looks like I made a mistake somewhere because changing the type of `puVar1` from `undefined *` to `io_ops_s *` does nothing but prints some bullshit warning atop of the function in decompiler window: + +![WARNING: Unable to use type for symbol puVar1](../images/brom-dump-011.png) + +Instead, I created a `0x3c` bytes long uninitialized RW memory region at `0x102114` and set its type to `io_ops_s`. This makes it much easier to inspect usages of each pointer. It took me some time to figure out what each function in `io_ops_s` does, here's a very brief rundown: + +1. `(off)` Init transport HW. Is used only in `FUN_12000bdc` (renamed to `init_io`). +2. `(off + 0x4)` Read 1 byte and return it +3. `(off + 0x8)` Read 1 byte into a buffer +4. `(off + 0xC)` Read N bytes `read(char* dst, uint len)` +5. `(off + 0x10)` Write 1 byte +6. `(off + 0x14)` Write N bytes `write(char* data, uint len)` +7. `(off + 0x18)` Write 1 byte but unused..? +8. `(off + 0x1C)` Read 2 bytes +9. `(off + 0x20)` Write 2 bytes +10. `(off + 0x24)` Read 4 bytes +11. `(off + 0x28)` Write 4 bytes +12. `(off + 0x2C)` Read 8 bytes +13. `(off + 0x30)` Write 8 bytes +14. `(off + 0x34)` Activate transport features (ignored for USB) +15. `(off + 0x38)` Set transport baudrate (ignored for USB) + +## The usb-dump payload +After pointing out all the I/O functions I took their addresses and implemented a rather simple `usb-dump` payload that will dump hardcoded set of regions using newly found functions in DA. I chose to dump not only the BootROM but also whole SRAM and the DA itself as it now has many variables initialized. Could be useful for further reverse engineering. + +In `spft-replay` I implemented the "receive mode" to save dumped regions to disk. There's also "greedy mode" I made mainly for debugging. + +``` +[2023-03-25 02:19:01,737] -> DA: (OK) 5A +[2023-03-25 02:19:01,740] Waiting for custom payload response +[2023-03-25 02:19:01,743] Received HELLO sequence +[2023-03-25 02:19:01,749] Reading 65536 bytes +[2023-03-25 02:19:05,216] Saved to dump-1.bin +[2023-03-25 02:19:05,225] Reading 65536 bytes +[2023-03-25 02:19:08,704] Saved to dump-2.bin +[2023-03-25 02:19:08,715] Reading 262144 bytes +[2023-03-25 02:19:22,479] Saved to dump-3.bin +[2023-03-25 02:19:22,485] Received GOODBYE sequence +[2023-03-25 02:19:22,486] Closing device +``` + +Success! Now I've got the dump of mt6589 BROM. I have a few more devices to play with. The next will be mt6573. diff --git a/brom-dump/payloads/Makefile b/brom-dump/payloads/Makefile index 6f323a3..a4b3ac8 100644 --- a/brom-dump/payloads/Makefile +++ b/brom-dump/payloads/Makefile @@ -21,7 +21,7 @@ TARGET ?= unsupported TARGET_LD_SCRIPT = include/$(TARGET)/payload.ld TARGET_INIT = $(OUT_DIR)/$(TARGET)-init.o # Available payloads -PAYLOADS = hello-world-uart-da-api +PAYLOADS = hello-world-uart-da-api usb-dump PAYLOAD_BINS = $(patsubst %,$(BUILD_DIR)/$(TARGET)-%-payload.bin,$(PAYLOADS)) TARGET_DA_PATCHED = $(OUT_DIR)/$(TARGET)-da-patched.bin @@ -50,6 +50,9 @@ $(TARGET_INIT): init.s $(OUT_DIR)/$(TARGET)-hello-world-uart-da-api-piggyback.o: hello-world-uart-da-api.s | $(OUT_DIR) $(AS) $(ASFLAGS) -o "$@" "$<" +$(OUT_DIR)/$(TARGET)-usb-dump-piggyback.o: usb-dump.s | $(OUT_DIR) + $(AS) $(ASFLAGS) -o "$@" "$<" + %-piggyback.elf: %-piggyback.o $(TARGET_INIT) | $(OUT_DIR) $(LD) $(LDFLAGS) -o "$@" $^ diff --git a/brom-dump/payloads/include/mt6589/da-api.s b/brom-dump/payloads/include/mt6589/da-api.s index 4011cbf..c82f147 100644 --- a/brom-dump/payloads/include/mt6589/da-api.s +++ b/brom-dump/payloads/include/mt6589/da-api.s @@ -1,6 +1,18 @@ @ SPDX-License-Identifier: GPL-3.0-only @ SPDX-FileCopyrightText: 2023 arzamas-16 -.equ DA_print_hex_value, 0x12003F50 @ void print_hex_value(uint value, uint width) -.equ DA_putc_wrapper_uart, 0x12003F3A @ void putc_uart_wrapper(uint chr) -.equ DA_printf_uart, 0x12003F7C @ void printf_uart(char* fmt, uint* val1, uint val2, uint val3) +@ void __fastcall putc_uart_wrapper(uint8_t c) +.equ DA_putc_wrapper_uart, 0x12003F3A +@ void __fastcall print_hex_value(uint32_t val, uint32_t width) +.equ DA_print_hex_value, 0x12003F50 +@ void __fastcall printf_uart(uint8_t* fmt, uint32_t* arg1, uint32_t arg2, uint32_t arg3) +.equ DA_printf_uart, 0x12003F7C + + + +@ void __fastcall io_usb_write(uint8_t* data, uint32_t len) +.equ DA_io_usb_write, 0x12008E60 +@ uint32_t __fastcall io_usb_readl() +.equ DA_io_usb_readl, 0x12009032 +@ void __fastcall io_usb_writel(uint32_t val) +.equ DA_io_usb_writel, 0x12009060 diff --git a/brom-dump/payloads/include/mt6589/hw-api.s b/brom-dump/payloads/include/mt6589/hw-api.s index 58f8acb..ecb65b5 100644 --- a/brom-dump/payloads/include/mt6589/hw-api.s +++ b/brom-dump/payloads/include/mt6589/hw-api.s @@ -5,3 +5,10 @@ @ derived from the original mt6589 DA, see 0x120000A0 .equ MEM_stack_base, 0x10FFF0 + +.equ MEM_brom_start, 0x0 +.equ MEM_brom_length, 0x10000 +.equ MEM_sram_start, 0x100000 +.equ MEM_sram_length, 0x10000 +.equ MEM_da_start, 0x12000000 +.equ MEM_da_length, 0x40000 diff --git a/brom-dump/payloads/usb-dump.s b/brom-dump/payloads/usb-dump.s new file mode 100644 index 0000000..bc5c440 --- /dev/null +++ b/brom-dump/payloads/usb-dump.s @@ -0,0 +1,60 @@ +.include "da-api.s" +.include "hw-api.s" + + + .syntax unified + .global _main +_main: + LDR R1, =usb_seq_hello + LDR R0, [R1] + BL DA_io_usb_writel + + LDR R0, =dump_task_brom @ dump BootROM + BL usb_dump + LDR R0, =dump_task_sram @ dump SRAM + BL usb_dump + LDR R0, =dump_task_da @ dump Download Agent + BL usb_dump + + LDR R1, =usb_seq_goodbye + LDR R0, [R1] + BL DA_io_usb_writel +busy_wait: + NOP + B busy_wait @ do not go any further, must reset manually! + + + +.func usb_dump +usb_dump: + PUSH {R5, LR} + PUSH {R0} @ save dump_task pointer on stack + + MOV R5, R0 + LDMIA R5!, {R0, R1} @ offset -> R0, length -> R1 + MOV R0, R1 @ length -> R0 + BL DA_io_usb_writel @ DA_io_usb_writel(length) + + POP {R5} @ load dump_task pointer from task + LDMIA R5!, {R0, R1} @ offset -> R0, length -> R1 + BL DA_io_usb_write @ DA_io_usb_write(offset, length) + + POP {R5, PC} +.endfunc + + + +.data +usb_seq_hello: + .word 0x3E4D746B +usb_seq_goodbye: + .word 0x4D746B3C +dump_task_brom: + .word MEM_brom_start @ offset + .word MEM_brom_length @ length +dump_task_sram: + .word MEM_sram_start @ offset + .word MEM_sram_length @ length +dump_task_da: + .word MEM_da_start @ offset + .word MEM_da_length @ length diff --git a/brom-dump/spft-replay/spft-replay.py b/brom-dump/spft-replay/spft-replay.py index 1d65e1b..c48117f 100755 --- a/brom-dump/spft-replay/spft-replay.py +++ b/brom-dump/spft-replay/spft-replay.py @@ -6,7 +6,7 @@ import logging from functools import partial, partialmethod -from src.common import as_0x +from src.common import as_0x, as_hex, from_bytes from src.device import Device from src.replay import replay @@ -40,6 +40,20 @@ def main(): const=LOG_LEVEL_BROM_IO, help="Super verbose: also print all read/write operations", ) + mode_parser = parser.add_mutually_exclusive_group() + mode_parser.add_argument( + "-r", + dest="mode_receive", + action="store_true", + help="Receive mode: wait for >Mtk and Mtk + logging.info("Received HELLO sequence") + else: + logging.info(f"Received invalid data {as_hex(seq)}, expected HELLO sequence") + + idx = 1 + size = from_bytes(device.read(4), 4) + while size != 0x4D746B3C: #