One of the core features of FirmWire is it's modkit, which allows to create and compile own modules and tasks to be injected in the emulated baseband image. The modkit serves as bases for our fuzzing modules, as well as the GuestLink interactive exploration capabilities.

In a nutshell, mods are C programs, which use the symbols created with patternDB and the vendor specific loaders to extend the functionality of the existing baseband firmware image. These C programs need to be pre-compiled by using Makefiles supplied by us. Then, FirmWire can inject these tasks during run time, automatically resolving the symbols and placing the task in an unused memory segment.

Toolchain & Compilation

To compile tasks, target specific compilation toolchains are required. For an Ubuntu 20.04 system, we had success with the following toolchains provided by the distribution's packet repository: gcc-9-mipsel-linux-gnu for MediaTek based firmware, and gcc-arm-none-eabi for Shannon baseband firmware.

After installing the toolchains, the modules can be compiled by browsing to the modkit directory and running make inside the vendor-specific subdirectories (i.e. mtk and shannon).

In case you want to extend the modkit and provide your own mod, you will need to adjust the Makefile. In particular, you need to modify the MODS line and provide the path to your mod's source. To exemplify this, let's assume you want to add mymod to the mods available for emulated Shannon modems.

Before modification, the relevant section in the Makefile should look something like this:

MODS := gsm_mm gsm_sm gsm_cc lte_rrc glink

gsm_mm_SRC := fuzzers/gsm_mm.c afl.c
gsm_cc_SRC := fuzzers/gsm_cc.c afl.c
gsm_sm_SRC := fuzzers/gsm_sm.c afl.c
lte_rrc_SRC := fuzzers/lte_rrc.c afl.c
glink_SRC := glink.c

Assuming you have your source code in mymod.c, this part of the Makefile should look as follows after modification:

MODS := gsm_mm gsm_sm gsm_cc lte_rrc glink mymod

gsm_mm_SRC := fuzzers/gsm_mm.c afl.c
gsm_cc_SRC := fuzzers/gsm_cc.c afl.c
gsm_sm_SRC := fuzzers/gsm_sm.c afl.c
lte_rrc_SRC := fuzzers/lte_rrc.c afl.c
glink_SRC := glink.c
mymod_SRC := mymod.c

After this tiny modifications, your mod should be compiled as well when running make!

Modkit format

To further exemplify how the modkit is used, let's look at a very basic task: The hello_world task for MTK basebands.

The source code for this task looks as follows:

#include <task.h>
#include <modkit.h>
#include <hello_world.h>

MODKIT_FUNCTION_SYMBOL(void, dhl_print_string, int, int, int, char *)

extern void real_main() {
    while(1) {
        dhl_print_string(2, 0, 0, "hello world\n");

There is not a lot of code, isn't it? Let's go through the lines. The first include import the MediaTek task logic, which is required to make sure our code will be embedded correctly, following the baseband-specific task structure. Similarly, the second line includes high-level modkit functionalities. The third line includes hello_world.h, whose content are:


const char TASK_NAME[] = "testtask\0";


The only important line here is the specification of the task name, which is set to testtask.

Coming back to hello_world.c, the fifth line is where things get interesting:

MODKIT_FUNCTION_SYMBOL(void, dhl_print_string, int, int, int, char *)

This directive is used to advise the modkit to "resolve" a function which is part of the original modem firmware. The general syntax for it is:

MODKIT_FUNCTION_SYMBOL(return_type, function_name, type_argument1, type_argument2, ..., type_argumentN)

After using this directive, the selected function becomes available to the C program, so in this case we can use dhl_print_string later in the code, which is used to provide logging output.

The next part of the code defines the real_main() function, which is used by the MediaTek modkit to assess where execution should start for this task (in the case of Shannon mods, the corresponding function name would be task_main). This main function does nothing else than using the resolved dhl_print_string function to print "Hello World" repeatedly to the console. Neat!

Running the task

Providing the code for the injected task is only the first step; of course, we also want to run it. Luckily, except running make, FirmWire automates the full process of injecting the task. Once build via make, we can easily invoke FirmWire with the -t/--module flag.

Taken the hello world task from above as example, this would look something like this:

$ python3 -t hello_world modem.bin
              ___            __      _                          
-.     .-.   | __|(+) _ _ _ _\ \    / /(+) _ _ ___    .-.     .-
  \   /   \  | _|  | | '_| '  \ \/\/ /  | | '_/ -_)  /   \   /  
   '-'     '-|_|   | |_| |_|_|_\_/\_/   | |_| \___|-'     '-'   
                A  baseband  analysis  platform
[INFO] firmwire.loader: Reading firmware using ShannonLoader (shannon)
[ERROR] firmwire.vendor.shannon.loader: Modem CP tarfile is missing required modem.bin
[ERROR] firmwire.loader: Loading failed!
[INFO] firmwire.loader: Reading firmware using MTKLoader (mtk)
[INFO] firmwire.vendor.mtk.loader: Found new file md1rom at 0x0/0x0 with length 0x169eca4

[...] (Loading informations)

[INFO] firmwire.vendor.mtk.machine: Resolved dynamic symbol dhl_print_string (0x90287e25) -> 0x9f4000a0 (FUNC)
No Memory range specified at 0x913d66e4
[WARN] firmwire.vendor.mtk.machine: Overwriting an existing task
[INFO] firmwire.vendor.mtk.machine: Injecting Task at 0x9f400000 (stack: 0x9f4010e0)
Injecting contents to 913d66e4: b'7000409f284b3d910001ff00001000003500409f0000000101000000ffffffff'
No Memory range specified at 0x913d66e4
After injecting task

[...] (Lots of Logs)

[49.46536][SSIPC] 0x90e353b7 [SSIPC][ILM_MSG] waiting msg

[49.46661][testtas] 0x9f400033 hello world


As we can see, FirmWire automatically resolved dhl_print_string and injected the hello world task, which then later printed it output to the commandline!

There are also other ways to invoke a mod, namely by using the --fuzz/--fuzz-triage or the --interactive commandline flag. More about these will be covered in the next Chapters!