-
Notifications
You must be signed in to change notification settings - Fork 27
Porting kaeru to a new device
This section of the wiki covers the process of adding support for a new device to kaeru. It also explains how to set up the necessary requirements and assumes that you have already verified your eligibility to load unsigned bootloaders.
Kaeru uses a build system similar to the Linux kernel one. To build kaeru, you'll need to set up a set of prerequisites.
Warning
To build kaeru, you must use a Linux-based system. If you're using Windows, consider installing Ubuntu or Debian on WSL2.
Note
If you use this method, make sure that all the necessary files (such as the lk) are located in the kaeru directory.
You can use a Docker container to install all the dependencies in an isolated environment. This approach eliminates issues with environment setup and ensures stable operation on any system.
-
Install Docker in any convenient way
-
Run the docker container
./docker_run.sh
sudo apt-get update
sudo apt-get install gcc-arm-linux-gnueabihf python3 python3-pip git makesudo pacman -Syu python git make # Usually not required.
yay -S arm-linux-gnueabihf-gcc-stage1 arm-linux-gnueabihf-gcc-stage2 arm-linux-gnueabihf-glibc arm-linux-gnueabihf-gcc Once you've installed the necessary requirements, simply clone the repository to any location you prefer:
git clone https://github.com/R0rt1z2/kaeru.git kaeru && cd kaeruIf your device is not yet supported by kaeru, you’ll need to start by creating the required device-specific files. This can be done either manually or by using the provided setup.sh script.
The script supports two modes: interactive and command-line. As the names suggest, the interactive mode will prompt you to enter details such as the device model, platform, and more, while the command-line mode expects all necessary information to be provided up front.
To start the interactive mode, run the following command:
./setup.shThis will prompt you for all the necessary information.
For this mode, you’ll need to provide all the required information when running the script:
./setup.sh -c CODENAME -m "DEVICE MODEL" -v VENDOR -l /path/to/lk.bin -s SOC_MODELThese are the parameters the script expects you to provide:
-
-c, --codename: Device codename (e.g., penangf) -
-m, --model: Device model (e.g., Motorola G13) -
-v, --vendor: Device vendor/brand (e.g., motorola) -
-l, --lk: Path to the LK image file -
-s, --soc: MediaTek SoC model (e.g., MT6765) -
-d, --debug: Enable debug output -
-h, --help: Show help message
The setup script will create a new file in the board/ directory, inside a folder named after the vendor with board- prefixed to the device codename. This file will be used to store the device-specific code.
It will also generate a new configuration file in the config/ directory, which contains the device-specific offsets and addresses.
The script tries to automatically detect the offsets and addresses for the device, but it's highly likely that you will need to adjust them manually.
This section of the wiki will also include an example of how to add support for a new device, so you’ll need to check that to understand how to manually adjust these offsets.
This part of the section will demonstrate how to manually adjust all the addresses and identify the missing offsets. The example will use an LK image from a Nokia 2.3, codenamed ironman, which uses the MT6761 chipset.
The following output has been generated by the setup script:
r0rt1z2@r0rt1z2 $ ./setup.sh -c ironman -m "Nokia 2.3" -v Nokia -l "$HOME/lk.bin" -s MT6761 -d
[INFO] Setting up device with the following parameters:
[INFO] Codename: ironman
[INFO] Model: Nokia 2.3
[INFO] Vendor: Nokia
[INFO] LK Path: /home/r0rt1z2/lk.bin
[INFO] SoC: MT6761
[INFO] Copyright: R0rt1z2 <[email protected]>
....
CONFIG_BOOTLOADER_BASE=0x48000000
CONFIG_BOOTLOADER_SIZE=0xD4FF0
CONFIG_APP_ADDRESS="Not found, requires manual search"
CONFIG_APP_CALLER=0x4801C9CC
CONFIG_FASTBOOT_CONTINUE=0x48021154
CONFIG_FASTBOOT_FAIL=0x4801FE34
CONFIG_FASTBOOT_INFO=0x4801FDB4
CONFIG_FASTBOOT_OKAY=0x480200B0
CONFIG_FASTBOOT_PUBLISH=0x4801FA90
CONFIG_FASTBOOT_REGISTER=0x4801FA54
CONFIG_LK_LOG_STORE=0x48041088
CONFIG_MTK_DETECT_KEY=0x480045D4
CONFIG_PLATFORM_INIT=0x480028FC
CONFIG_BOOTSTRAP2=0x48049CA1
CONFIG_VIDEO_PRINTF=0x4802DD2C
CONFIG_PLATFORM_INIT_CALLER="Not found, requires manual search"
# CONFIG_BOOTMODE_ADDRESS="Go to 0x48021154 to find the bootmode value"
....
[SUCCESS] Device setup completed successfully
[INFO] You can now build using: ./build.sh ironman <lk_path>
r0rt1z2@r0rt1z2 $This will generate a config file at configs/ironman_defconfig (with ironman replaced by whatever codename you used). In the following steps, you'll need to keep editing and adjusting that file as needed.
Warning
Save this output somewhere, as you'll most likely need it when analyzing the LK image with Ghidra.
If we tried to build it as-is now, we'd encounter the build system asking us for many values. This is because the script can't automatically detect all of them, so we'll need to load the LK image into Ghidra. As you can see, the output already gives us some hints about what we need to check.
Note
Theoretically, you could use any decompiler you prefer, but I chose to use Ghidra because it's free, open source, and has a somewhat intuitive GUI.
This step requires you to have Ghidra installed on your system. You also need to have the JDK installed; otherwise, Ghidra won't open.
You can download Ghidra from the official NSA webpage: https://ghidra-sre.org/. You can also search online for instructions on how to install the JDK on your system, I won't be detailing that in this wiki.
Once you've downloaded and install Ghidra, open it and, if you haven't already, create a new project. You can name it whatever you want, it doesn't really matter.
Then, you're going to drag your LK image into the Active Project window so Ghidra includes it in the project. You can also press i and it'll prompt you to select the LK image rather than dragging it.
A new window will appear. By default, Ghidra cannot automatically detect the language, so you'll need to click the three-dot button next to the Language field.

Then, type v7 in the filter field and wait for the list to refresh. You should see a bunch of options. Look for the one labeled ARM v7 32 little default (where ARM is the processor, v7 is the variant, 32 is the size, little refers to endianness, and default is the compiler). Then, simply click OK to return to the previous window.

Once you're back in the main window, click the Options... button at the bottom. This will open a new window where you can define parameters for the image.

In this new window, you'll need to modify three settings. The first is the Base Address. Remember the output from the setup script? Set the Base Address to whatever your CONFIG_BOOTLOADER_BASE value was, in my case, 0x48000000. Next, enter 0x200 in the File Offset field, and clear any value in the Length field (leave it empty). Then, click OK to return to the main window.

Once you're back in the main window, click the OK button to load the image into the Ghidra project.

A new window will appear, showing a summary of the import. Just click OK to dismiss the dialog.

At this point, you'll be back in the main Ghidra project window. Now, double-click the bootloader image you just loaded.

A new dialog will appear, asking if you'd like to analyze the image now. Click Yes.

You’ll see a new window with Analysis Options. On this screen, make sure to disable the Non-Returning Functions - Discovered option and leave all other settings at their defaults. Then click Apply, and finally, Analyze.

Now you’ll need to wait for Ghidra to finish the auto-analysis. Depending on your specs, this might take a while. You’ll see a progress bar at the bottom.

Once the auto-analysis is complete, at the very top of the disassembly view, at offset 0x4 (your base address + 0x4, in my case 0x48000004), you’ll notice that Ghidra has created a data variable named something like DATA_XX00004 (the name may vary depending on your base address). Right-click on it, go to Data, and then select Default Settings.

A new window should appear, asking for the default settings. Set Mutability to constant, then click Apply, and finally OK.

Then, go to the top menu bar, click on Search, and select the For Strings... option. A new window will appear.

Once the new window appears, make sure that Memory Block Types is set to All Blocks, and then click the Search button.

This will load a large list showing all the strings Ghidra found in the image. At this stage, it doesn't matter which string you choose, but I recommend searching for starting app, as it’s commonly present in most LK images. Once you’ve found the string, click on it.

After clicking on the string, Ghidra will take you to its location. Once there, right-click on it, go to Data, and then select Default Settings....

A new window (similar to one you've seen before) will appear. As before, click on Mutability and change it to Constant. Then click Apply, and finally, OK.

Finally, on the top menu bar, click on Edit, then select Tool Options, and wait for the Options window to appear.

Once there, in the Filter section, type unreachable and click on the Analysis entry. Then, disable the Eliminate unreachable code option, click Apply, and finally OK.

Congratulations, you’ve successfully loaded your LK image into Ghidra!
Now that you have the LK image loaded into Ghidra, you can start searching for the missing addresses. The script will provide you with some hints, but it won't be able to find all of them automatically.
This address will always be missing and needs to be adjusted manually. Thankfully, the script helps by telling us exactly where to look. In my case, it's 0x48021154. We can quickly navigate there using the Navigation > Go To option.

Once we click that option, a new pop-up appears, asking for an address. Simply paste the address provided by the script and press the OK button.

If we decompiled the LK image correctly, we should see an undefined function at that address. For those curious, this is the function that's executed when you run fastboot continue. It appears as undefined because its only reference is an indirect one, it's registered via fastboot_register(), not directly called.
The first thing this function does is set the boot mode to BOOTMODE_NORMAL (0), which makes it easy to determine the address of the boot mode variable. In my case, CONFIG_BOOTMODE_ADDRESS will be 0x480d75e4.

This function tends to vary significantly across different devices, so the script rarely identifies the correct one. Fortunately, that's not the case for the app caller address.
The caller is the address where the branch jump to app() is located. Since we know that, simply navigate to the address defined by CONFIG_APP_CALLER and examine the assembly view.
You'll find a blx call. It will probably look something like blx r3=>FUN_XXXXXXXX. This the address you're looking for. In my case CONFIG_APP_CALLER will be 0x4801c9cc, while CONFIG_APP_ADDRESS will be 0x4801f2bc.

Tip
If you're still having trouble locating app(), another option is to search for the string starting app %s\n using the same string search feature we used earlier. Then, click on the function that references this string, that function should contain the blx app() call we mentioned before.
These are additional addresses that can sometimes be difficult to locate. The script usually finds the address itself, but not the caller.
If that's the case, use the Go To feature to navigate to the CONFIG_PLATFORM_INIT address. In the assembly view, you should see one XREF, this will contain the address of the platform_init() caller. In my case, CONFIG_PLATFORM_INIT_CALLER would be 0x4801ba98.

If neither of the addresses was found, you'll need to locate CONFIG_PLATFORM_INIT first, and then find the caller using the method we just discussed. The easiest way to find platform_init() is by using the string search feature to look for platform_init()\n. Then, click on the reference, that will be the function you're looking for.

Warning
This step is CRUCIAL, as the script can sometimes produce false positives.
For all function-related addresses, you need to double-check that they actually point to valid functions and not to random locations within the LK image.
The following list contains the values you should verify:
CONFIG_APP_ADDRESSCONFIG_FASTBOOT_CONTINUE_ADDRESSCONFIG_FASTBOOT_FAIL_ADDRESSCONFIG_FASTBOOT_INFO_ADDRESSCONFIG_FASTBOOT_OKAY_ADDRESSCONFIG_FASTBOOT_PUBLISH_ADDRESSCONFIG_FASTBOOT_REGISTER_ADDRESSCONFIG_MTK_DETECT_KEY_ADDRESSCONFIG_PLATFORM_INIT_ADDRESSCONFIG_VIDEO_PRINTF_ADDRESS
Basically, for each address, you need to navigate to it and verify that it points to valid function. If it doesn’t, there’s no universal solution, you’ll need to manually compare it with Nokia’s LK.
Use string references within the functions to guide you, and apply everything you've learned here to identify the correct values. You can also open a new issue, and I’ll be happy to help you :)
If you’ve done everything correctly, you should now be able to build kaeru using the build.sh script with the command ./build.sh ironman lk.bin (where ironman is your device’s codename).
This will generate a file named ironman-kaeru.bin (where ironman is your device’s codename) inside the out directory. This file contains your LK, patched to jump into its newly embedded payload.
In order to test whether it works or not, you should flash this image into your LK partition with either fastboot, mtkclient or SP Flash Tools (or any other flashing method you're aware of).
To verify if kaeru is up and running, boot into fastboot mode and execute the fastboot oem kaeru-version. If it worked, the bootloader should report the kaeru version and the screen should show the disclaimer.
r0rt1z2@r0rt1z2 $ fastboot oem kaeru-version
(bootloader) kaeru v1.0.0
OKAY [ 0.076s]
Finished. Total time: 0.076s
r0rt1z2@r0rt1z2 $

The next page explains how to customize board-specific files and showcases most of kaeru's APIs.
Continue: Customization and kaeru APIs