Patch MediaTek bootloader images (LK)
Posted on Mon 04 December 2023 in tools
This post introduces a tool to patch MediaTek bootloader images (LK) and a guide to create custom patches for your device.
The tool was originally developed for BBK (Vivo, Oppo, Realme) devices in order to unlock fastboot access but it can be used for any MediaTek device.
The tool is available on GitHub and also onlined on this website.
MediaTek bootloaders
Before exploring the tool, let's first get a grasp of bootloaders, particularly in the context of MediaTek devices.
MediaTek has adopted the widely-used open-source bootloader, Little Kernel, or LK, and has significantly modified it to align with the specific requirements of their devices.
This second-stage bootloader plays a critical role in their booting process. As explained in this article, LK is responsible for the following tasks:
- Multithreaded Design: LK operates with a multithreaded design, prioritizing tasks efficiently right from the initial stages.
- Early Initialization: The initial step involves setting up hardware essentials like caches, the MMU, flash storage, and the display, using minimal drivers.
- Boot Mode Selection: LK can start in various modes (e.g., normal, recovery, fastboot) depending on different scenarios like device reboot or charger connection. These modes are defined in an enumerated type BOOTMODE and can be altered through specific key combinations or commands.
- Further Initialization: After the basic setup, LK continues to initialize more hardware components like the battery controller and RTC, and displays the boot logo.
- Running Multiple Apps: Being multithreaded, LK can run multiple applications simultaneously, such as the Android bootloader (aboot), MediaTek bootloader (mt_boot), and a flexible shell accessible via UART.
- Android Boot Image Loading: Depending on the boot mode, LK proceeds to load the appropriate Android boot image from either the internal storage or an SD card.
- Preparing for Kernel Launch: Before handing over control to the Linux kernel, LK loads the kernel image and initrd into memory, prepares a tag block with necessary information for the kernel, and then makes the jump to start the Linux kernel.
- Fastboot Mode: In fastboot mode, LK powers the USB port and starts the Fastboot interpreter, which is a part of its functionality.
Unfortunately, unlike Qualcomm, MediaTek isn't well-known for sharing its source code. So... what does this mean for us? Well, this will just make our lives a bit more difficult when it comes to understanding the bootloader's structure.
Thankfully, BSP leaks are a thing, and, as such, we can use them to our advantage. In my case, I was able to use this Volla Phone DroidBoot repository in order to get a better understanding of how LK works.
So... how does this tool work?
The tool, written in Python, uses my liblk library to work with LK images. The patching process is quite straightforward, focusing on altering select parts of the image to modify the instruction flow. This method, known as byte patching, is only efficient for precise adjustments, which is exactly what we need.
As I've mentioned before, the tool was originally developed specifically for certain devices so by default, it includes a set of patches that are primarily used to unlock fastboot access. These patches are stored in a JSON file which gets loaded by the tool.
{
"fastboot": {
"2de9f04fadf5ac5d": "00207047",
"f0b5adf5925d": "00207047"
},
"dm_verity": {
"30b583b002ab0022": "00207047"
},
"orange_state": {
"08b50a4b7b441b681b68022b": "00207047"
},
"red_state": {
"f0b5002489b0": "00207047"
}
}
Porting patches to other devices
Generally, patches are device-specific, so if you're looking to use the tool for a different device, you'll need to create your own patches. Doing so requires a bit of reverse engineering, but... don't worry, it isn't as hard as it sounds :)
Prerequisites
- A dump of your device's LK image. You can use mtkclient to dump the image from your device.
- A disassembler. I recommend using Ghidra, but you can use any other disassembler of your choice.
Step 1: Load the image into Ghidra
To load the image into Ghidra, you'll need to figure out the image's load address. In order to do so, you have two options:
- Run
lkpatcher
with the-d
flag to dump thelk
partition. - Find some leaked source code of LK for your platform and look for the
MEMBASE
definition.
In this example, I will use the LK image of a Realme 8 (MT6785) device. After using the first method, I was able to find the load address of the image, which turned out to be 0x4c400000
.
... so, that's all? Well, not quite. We still need to find the proper entry point (or offset) of the image. To do so, we can simply use hexdump and look where the headers end. In this particular case, the actual image starts at 0x200
(512 bytes).
We're finally done. We now have all the information we need to load the image into Ghidra:
- If you haven't already, create a new project in Ghidra.
- Go to
File > Import File...
and select the LK image. - Select
ARMv7 32 Little Default
as the language. - Go to
Options...
and configure the following parameters: - Base Address:
0x4c400000
- File Offset:
0x200
- Length: Leave this field empty.
- Click
OK
and double-click thelk
file in the project explorer. - You'll be prompted to analyze the file. Click
Yes
and use default settings.
Step 2: Find what to patch
The next step is to find the code we want to patch. In this example, we'll be patching the function that checks whether fastboot is accessible or not depending on the lock state of the device.
Typically, if you try to boot into fastboot mode on a Realme device and you haven't used their in-depth test app to unlock fastboot, you'll be greeted with a message saying fastboot_unlock_verify fail!
.
Since this is a constant, we can use it to identify the function that determines the accessibility of fastboot:
- Go to
Search > For Strings...
, selectAll Blocks
and filter forfastboot_unlock_verify
. - You should see only one result. Double-click it, and you'll be taken to the string's location in memory. In my case, it's located at
0x4c4bda60
. - To change the string's mutability, right-click it and select
Settings > Default Settings...
and setMutability
toconstant
. - Once you've done that, simply press the reference to the string, and you'll find the function we're interested in. In my case, it's located at
FUN_4c46c2e0
.
The function may initially seem complex, but with a thorough examination, we can simplify and clarify the way it works:
void is_fastboot_enabled(void) {
// Perform basic screen initialization.
video_clean_screen();
video_set_cursor(video_get_rows() / 2,0);
// Call fastboot_unlock_verify to check
// whether fastboot is accessible or not.
if (fastboot_unlock_verify() == 0) {
// .. if it is, return to the parent function.
video_printf("fastboot_unlock_verify ok\n\n");
return;
}
// .. otherwise, print the error message
// and trigger a reboot.
video_printf("fastboot_unlock_verify fail\n\n");
mdelay(1000);
mtk_wdt_reset(1);
// Technically, this return is unreachable.
return;
}
Step 3: Patch the function
We want the function to always return to the caller no matter what, so we'll just need to place a bx lr
right after the register initialization. We do this by right-clicking the second instruction and using the Patch Instruction
option.
--- a/is_fastboot_enabled.asm
+++ b/is_fastboot_enabled.asm
@@ -1,6 +1,6 @@
-08 b5 push {r3,lr}
-c9 f7 6f f8 bl video_clean_screen
-c9 f7 67 f8 bl video_get_rows
+08 b5 push {r3,lr}
+70 47 bx lr
+00 bf nop
00 eb d0 70 add.w rows,rows,rows, lsr #0x1f
00 21 movs r1,#0x0
...
If you want to test the patch, you can use this script to apply it to the image and then use mtkclient
to flash it to your device.
Step 4: Create the patch file
If you verified that the patch works, you can now either create a new patch file or add it to an existing one. In this case, I'll be adding it to the fastboot
patch set.
--- a/patches.json
+++ b/patches.json
@@ -1,10 +1,11 @@
{
"fastboot": {
"2de9f04fadf5ac5d": "00207047",
"f0b5adf5925d": "00207047",
+ "08b5c9f76ff8": "704700bf"
},
"dm_verity": {
"30b583b002ab0022": "00207047"
},
"orange_state": {
"08b50a4b7b441b681b68022b": "00207047"
},
"red_state": {
"f0b5002489b0": "00207047"
}
}
And that's it! You can now use the tool to patch your LK image and flash it to your device.
Conclusion
Even though MediaTek bootloaders are closed-source and typically mysterious, with careful analysis and help from various BSP leaks, it becomes feasible to comprehend and alter its functionality.
Given that some devices don't verify the integrity of the bootloader, it's possible to patch it to our liking and flash it to the device.
My lkpatcher
tool aims to make this process easier by providing an automated way to apply patches to LK images.