Skip to content

Commit

Permalink
brom-dump: payloads: implement usb-dump payload for mt6589
Browse files Browse the repository at this point in the history
  • Loading branch information
arzam16 committed Mar 24, 2023
1 parent a3dea7e commit 524c7ff
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 7 deletions.
62 changes: 60 additions & 2 deletions brom-dump/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@
* [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)
<!--te-->

# Dumping mt6589 BROM
Initially this part was meant to be more of a blog post than a clear and concise guide. I will eventually publish everything I used but please do not expect any common sense to be present here especially if you are actually experienced in reverse engineering and baremetal programming.

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.

Expand All @@ -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
Expand Down Expand Up @@ -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] <REPLAY> -> DA: (OK) 5A
[2023-03-25 02:19:01,740] <INFO> Waiting for custom payload response
[2023-03-25 02:19:01,743] <INFO> Received HELLO sequence
[2023-03-25 02:19:01,749] <INFO> Reading 65536 bytes
[2023-03-25 02:19:05,216] <INFO> Saved to dump-1.bin
[2023-03-25 02:19:05,225] <INFO> Reading 65536 bytes
[2023-03-25 02:19:08,704] <INFO> Saved to dump-2.bin
[2023-03-25 02:19:08,715] <INFO> Reading 262144 bytes
[2023-03-25 02:19:22,479] <INFO> Saved to dump-3.bin
[2023-03-25 02:19:22,485] <INFO> Received GOODBYE sequence
[2023-03-25 02:19:22,486] <INFO> 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.
5 changes: 4 additions & 1 deletion brom-dump/payloads/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 "$@" $^

Expand Down
18 changes: 15 additions & 3 deletions brom-dump/payloads/include/mt6589/da-api.s
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
@ SPDX-License-Identifier: GPL-3.0-only
@ SPDX-FileCopyrightText: 2023 arzamas-16 <https://github.com/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
7 changes: 7 additions & 0 deletions brom-dump/payloads/include/mt6589/hw-api.s
Original file line number Diff line number Diff line change
Expand Up @@ -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
60 changes: 60 additions & 0 deletions brom-dump/payloads/usb-dump.s
Original file line number Diff line number Diff line change
@@ -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
64 changes: 63 additions & 1 deletion brom-dump/spft-replay/spft-replay.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 magics and save data to files",
)
mode_parser.add_argument(
"-g",
dest="mode_greedy",
action="store_true",
help="Greedy mode: receive and print all data after jumping to "
"payload (4 bytes at a time)",
)
args = parser.parse_args()

init_logging(args)
Expand All @@ -62,6 +76,11 @@ def main():
# Don't exit, try to close the device
logging.critical("Replay error!", exc_info=True)

if args.mode_greedy:
handle_greedy(device)
elif args.mode_receive:
handle_receive(device)

logging.info("Closing device")
device.close()

Expand Down Expand Up @@ -90,5 +109,48 @@ def init_logging(args):
)


def handle_greedy(device):
logging.info("Greedy mode! Waiting for incoming data... :)")
logging.info("Hit Ctrl+C to stop waiting")
try:
data = None
while True:
data = device.read(4)
if not data:
logging.error("Cannot receive data!")
break
logging.info(f"<- DA: {as_hex(data)}")
except KeyboardInterrupt:
logging.info("Stopped reading")


def handle_receive(device):
logging.info("Waiting for custom payload response")

# This function is prone to errors.
# TODO: add more try-except!

seq = from_bytes(device.read(4), 4)
if seq == 0x3E4D746B: # >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: # <Mtk
logging.info(f"Reading {size} bytes")
data = device.read(size)
filename = f"dump-{idx}.bin"
with open(filename, "wb") as fos:
fos.write(data)
logging.info(f"Saved to {filename}")

idx += 1
size = from_bytes(device.read(4), 4)

logging.info("Received GOODBYE sequence")


if __name__ == "__main__":
main()
Binary file added images/brom-dump-010.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/brom-dump-011.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 524c7ff

Please sign in to comment.