FirmWire : b Full-System Baseband Firmware Emulation Platform

0
24

FirmWire is a full-system baseband firmware analysis platform that supports Samsung and MediaTek. It enables fuzzing, root-cause analysis, and debugging of baseband firmware images. See the FirmWire documentation to get started!

Installation

The recommended way of using FirmWire is by using the supplied Dockerfile. To build the docker file, execute the following commands:

git clone https://github.com/FirmWire/FirmWire.git
cd FirmWire
git clone https://github.com/FirmWire/panda.git
This will take some time
docker build -t firmwire
.

Afterwards, you can obtain an interactive shell to a docker environment with FirmWire installed by executing:

docker run –rm -it -v $(pwd):/firmwire firmwire

From here, you can directly go to check out our quick start documentation to emulate your first modem!

Visual Studio Code

Alternatively to using docker from your commandline, you can also create a FirmWire environment using VScode, by using the devcontainer and docker extensions. After cloning FirmWire and FirmWire’s version of Panda, just open the corresponding directory in code and execute: > Remote-Containers: Add Development Container Configuration Files Then, select From Dockerfile, which should automatically create a .devcontainer file. Afterwards, follow code’s prompt to Reopen in container.

This will build the docker container and provide you an interactive shell inside the docker environment, with files transparently forwarded to the host directories. This is the favorite development setup for some of the FirmWire developers!

Manual Installation

The manual installation of FirmWire is a bit more tedious. Besides installing FirmWire and its requirement, you also need to:

  • Manually build Panda
  • Install PyPanda
  • Manually build the FirmWire mods

For information on how to carry out these individual steps, please refer to the Dockerfile.

Quick Start

Have you installed FirmWire and are all eager to emulate your modem FirmWire? Very good! All you have to run after installation is:

$ ./firmwire.py modem.bin

This will automatically recognize the firmware, unpack it, and select a loader and machine to run it. You can also load firmware from a URL to get started:

$ ./firmwire.py https://github.com/grant-h/ShannonFirmware/raw/master/modem_files/CP_G973FXXU3ASG8_CP13372649_CL16487963_QB24948473_REV01_user_low_ship.tar.md5.lz4

Currently, FirmWire supports a subset of MediaTek MTK and Samsung Shannon firmware images.

Please note that FirmWire requires a couple different TCP ports for its operation. If you have any restrictions on which ports can be used, please use the --consecutive-ports flag to specify which ports can be used. For instance, if ports 10000-10005 are free to use on your system, invoke FirmWire as follows:

$ ./firmwire.py –consecutive-ports 10000 modem.bin

Supported Images

MediaTek

  • Samsung A10s (MT6762)
  • Samsung A41 (MT6768)

Shannon

  • Most images for Galaxy S7, S7e (S335)
  • Moto One Vision (S337)
  • Galaxy S8, S8+ (S355)
  • Galaxy S9 (S360)
  • Galaxy S10, S10e (S5000)

Using Ghidra

We have custom patches to Ghidra which are required if you are analyzing MediaTek firmware. See https://github.com/FirmWire/ghidra for setup instructions. For Shannon firmware see https://github.com/grant-h/ShannonBaseband#getting-started-with-shannon-firmware. You will need the ShannonLoader, which can be installed on to the custom Ghidra for MediaTek (or just use the upstream Ghidra).

Technical Background

FirmWire is a baseband analysis platform. As input, it takes a baseband firmware image and tries to create an emulation environment for this image on-the-fly.

Emulation Core

The Emulation Core of FirmWire is built on top of avatar2 and PANDA. The core emulation capabilities are provided by PANDA, while avatar2 is used as middleware to orchestrate the execution state of the emulator, including spin-up, breakpoint registration, and starting/stopping of the emulation. Additionally, we use avatar2’s Python Peripherals to implement peripherals which react on Memory-Mapped I/O accesses.

Under the hood, FirmWire implements vendor specific machines which use avatar2’s PyPanda target to embed PANDA as dynamic library in the same process space as the Python Interpreter keeping the required inter-process communication for FirmWire to a bare minimum.

Emulator configuration

PANDA and avatar2 use the so-called configurable machine to enable emulation of arbitrary embedded systems with custom memory mappings. In essence, the embedded systems’ memory map (including ROM, RAM, and peripherals) is described in a JSON file, which gets automatically generated by avatar2 based on individually registered memory ranges. This JSON file is then passed on to PANDA, which uses it to register and emulate the memory ranges accordingly.

Inside FirmWire, we use the configurable machine to create the emulation environments for the target baseband images on-the-fly. In more detail, our loader is responsible for parsing a binary firmware file and automatically extracting the required memory mappings, for instance by finding pre-defined MPU tables within the binary image.

This Manual

The rest of this manual will guide you through FirmWire from a user’s perspective. In case you interested in developing or extending the core functionality of FirmWire, please stay tuned. Alternatively, you can dig through the source code, or reach out to us – we are happy to provide additional information wherever needed!

Command Line Interface Reference

This part of our documentation works as quick-reference to all the firmwire.py and firmwire_dev.py CLI arguments, and provides links about where they are covered. For more information about the single command line flags, you can also run FirmWire with the --help flag.

firmwire.py arguments

ArgumentCovered inDescription
modem_fileGetting StartedThe modem file FirmWire shall create an emulation environment for. Only mandatory argument(!)
--consecutive-ports CONSECUTIVE_PORTSGetting StartedChoose consecutive ports for the any listening sockets (e.g. QEMU’s GDB & QMP), starting with the port provided.
-h/--helpCLI referenceShow help for for different cli flags on commandline
-w/--workspace WORKSPACEWorkspacesPath to the workspace to use
--snapshot-at SNAPSHOT_ATWorkspacesAddress and name for taking a snapshot. (Syntax: address,name)
--restore-snapshot SNAPSHOT_NAMEWorkspacesName of snapshot to be restored
-t/--module INJECTED_TASKModkitModule / Task to be injected to the baseband modem
-S/--stopInteractive explorationStop CPU after initializing the Machine. Useful for interactive exploration.
-s/--gdb-serverInteractive explorationStart GDB server on TCP port. Default is 1234. NOTE: this is a minimal GDB stub.
--consoleInteractive explorationSpawn an ipython remote kernel that can be connected to from another terminal using jupyter console --existing
--fuzz FUZZFuzzingInject and invoke the passed AFL fuzz task module (headless).
--fuzz-input FUZZ_INPUTFuzzingPath the AFL test case (@@ should be sufficient) or just the path to a single test file.
--fuzz-triage FUZZ_TRIAGEFuzzingInvoke the fuzzer, but without an AFL front end. Enables debug hooks and saves code coverage.
--fuzz-persistent FUZZ_PERSISTENTFuzzingEnable persistent fuzzing with a loop count as the argument.
--fuzz-crashlog-dir FUZZ_CRASHLOG_DIRFuzzingFolder to which logs of all testcases (length testcase) for a crashing run in persistent mode
--fuzz-crashlog-replay FUZZ_CRASHLOG_REPLAYFuzzingReplay a persistent-mode crash trace written with fuzz-crashcase-dir.
--fuzz-state-addr-file FUZZ_STATE_ADDR_FILEFuzzingTextfile containing the hex-addresses of state-variables
--full-coverageFuzzingEnable full coverage collection (logs every executed basic block)
--shannon-loader-nv_data NV_DATATBD(Shannon only) Specify the NV_DATA to be used
--mtk-loader-nv_data NV_DATATBD(MediaTek only) Specify the NV_DATA to be used

Developer options

Note: These arguments are mostly useful for development and debugging. As of now, they are part of firmwire.py, but will be moved to a custom firmwire_dev.py interface to clearly distinguish developer and user features in a future iteration of FirmWire.

ArgumentCovered inDescription
--debugTBDEnable FirmWire debugging
--debug-peripheralTBDEnable debugging for specified peripheralas
--avatar-debugTBDEnable debug logging for Avatar2
--avatar-debug-memoryTBDEnable Avatar2 remote memory debugging (useful when Peripherals crash)
--unassigned-access-logTBDPrint log messages when memory accesses to undefined memory occur
--raw-asm-loggingTBDPrint assembly basic blocks as QEMU executes them. Useful for determining infinite loops.
--trace-bb-translationTBDPrint the address of each new Basic Block, useful to eval BBs reached during fuzzing.

Workspaces

FirmWire uses workspaces tied to the specific firmware file under analysis. These workspaces contain a variety of useful files, most notably logs emitted by the avatar2-orchestration, the configurable machine definition, and a qcow2-image used for FirmWire’s snapshotting mechanism, as well as vendor-specific files and directories.

By default, FirmWire creates a workspace at the very same directory where the modem file is located at, but this behavior can be overriden via the -w/--workspace command line flag.

Snapshots

One of FirmWire’s convenience features is snapshotting, which is implemented on top of QEMU. Besides storing the emulation machine state in QEMU’s qcow2 image format, FirmWire also saves the state of used python peripherals in auxiliary .snapinfo files.

To take a snapshot use the --snapshot-at commandline argument or call the snapshot() method during interactive exploration. Presume you want to take a snapshot with the name my_first_snapshot at address 0x464d5752. For taking the snapshot from commandline, simply run ./firmwire.py --snapshot-at 0x464d5752,my_first_snapshot modem_file. When using interactive exploration, you will have directly access to the python machine object via self. Make sure to stop execution at the desired address (for instance by setting a breakpoint), and then execute: self.snapshot("my_first_snapshot"). Alternatively, if you don’t want to manually steer execution, you can also use self.snapshot_state_at_address(0x464d5752, "my_first_snapshot").

For starting execution from this snapshot during the next start of FirmWire, all you will need to is ./firmwire.py --restore-snapshot my_first_snapshot modem_file. If you use interactive exploration, you can even restore snapshots on-the-fly, without the need to restart the emulator! In this case, you would need to execute self.restore_snapshot("my_first_snapshot")

PatternDB

PatternDB is a convienent way to define memory patterns which FirmWire uses to scan the binary baseband firmware during load-time. You you can think about FirmWire memory patterns as binary regexes tailored towards firmware analysis tasks. Once a pattern is found, FirmWire associates a symbol to the according pattern (in the simplest case), and, optionally executes lookup and post-lookup functions. The pattern itself are defined in the pattern.py-file present in the different vendor plugins.

PatternDB is used at various places inside FirmWire: For finding MPU-tables during load-time, automatically resolving logging functions, or exporting symbols to the Modkit, to just provide a few examples. At the time of FirmWire’s public release, we provide 18 patterns for Shannon-based modems and 9 for MediaTek-based modems, tested on a variety of firmware images.

Pattern Syntax

In our paper, we formally defined the syntax for our pattern as follows:

Pattern := {
name := string
pattern := [ PatternSyntax… ]
lookup := PatternFn?
post_lookup := PatternFn?
required := bool?
for := [ string… ]?
within := [ AddressSet… ]?
offset := integer?
offset_end := integer?
align := integer?
}
PatternSyntax :=
r”([a-fA-F0-9]{2}|([?][?+*]))+”
PatternFn := code
AddressSet := SymbolName | AddressRange
SymbolName := string
AddressRange := [integer, integer]

But what does this actually mean? Let’s consider the following pattern taken from Shannon’s pattern.py:

“boot_setup_memory” : {
“pattern” : [
“00008004 200c0000”,
“00000004 ????0100”,
],
“offset” : -0x14,
“align”: 4,
“post_lookup” : handlers.parse_memory_table,
“required” : True,
}
,

Here, we define two patterns which are used to create the PatternDB symbol boot_setup_memory, using hexadecimal notation of the searched bytes in little-endian encoding. Note that the second pattern includes ?? symbols – these are basically wildcards, and allows us to match against arbitrary bytes. Wildcard bytes specified with ?? allow for modifiers as known from regular regexes (pun intended!). ?+ requires the presence of one or more wildcard bytes, while ?* allows for zero or more wildcard bytes at the given location to result into a match.

Going back to our example pattern, the actual address associated with the boot_setup_memory symbol will be 0x14 before the location of the found pattern, as specified by the offset parameter. Alignement defines that the search granularity should be 4-bytes aligned and required will cause FirmWire to exit immediately in case this pattern is not found, as it is crucial for the generation of the emulation environment. Lastly, the post_lookup function takes a reference to a python function to be executed after the lookup completed. The function signature for this specific postlookup function is as follows:

def parse_memory_table(self, sym, data, offset):

Here, self is a reference to the ShannonMachine, sym a reference to the PatternDB symbol, data the memory searched for, and offset the start offset for the search considering the virtual location of the data block. The patternDB symbol sym, on the other hand, contains information about address, name, and type of the symbol.

Pattern KeyWord Details

KeywordDescription
nameThe name of the pattern and the resulting symbol (string)
patternOne ore more memory patterns which will create the result on match.
lookupFunction to use instead of pattern. Parameters are the data block to be searched and the offset to start. Expected to return None or integer denoting the address.
post_lookupFunction to be executed after successful match. Parameters are described in example above. Expected to return True on success, else False.
requiredWhen set to True, FirmWire will not continue execution when no match is found.
forSpecify SoC version in case the symbol shall only be looked up for certain SoC versions.
withinOn an image with existing symbols, specify in which function to look for this pattern.
offsetOffset between matched pattern and address of created symbol.
offset_endSame as offset, but caluclate from the end of matched pattern, rather than from the start.
alignMemory aligment required for found matches.

Defining your own Pattern

You want to define your own pattern? Great! Just extend the pattern.py file in the corresponding vendor plugin to include your pattern, and it should be automatically scanned for during the next start of FirmWire.

Modkit

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

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
include
include
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 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.

Interactive Exploration

FirmWire has multiple ways to facilitate interactive exploration of the emulated baseband firmware. The reason for such exploration are various, ranging from aiding static reverse engineering over observing the baseband’s behavior when receiving custom messages to root-cause analysis for crashing inputs.

GDB

The most classic way to interact with the emulated baseband is via GDB. Simply start FirmWire with the -s/--gdb-server flag while specifying a port number to start a gdb server! Then, you can start up your local gdb build (we recommend gdb-multiarch for Ubuntu 20.04) and connect to the emulated baseband by executing from within gdb:

target remote :PORT

Alternatively, when using gdb together with gef, we suggest to run the following for better usability instead:

gef-remote –qemu-mode 127.0.0.1:PORT

Once connected, you should be able to set breakpoints, inspect and modify memory, as well as steering execution just as usual. Under the hood, FirmWire spawns a gdb server provided by the corresponding avatar2 plugin. This allows to transparently access both the memory provided by avatar-backed memory ranges (as in the case of python peripherals), and emulated memory provided by PANDA.

What’s more, via gdb’s monitor command you have directly access to the Python context of avatar2 gdb server, and allows you to execute simple Python statements. You even can access the global avatar object from gdb by executing:

monitor self.avatar

Fuzzing

One of FirmWire’s core contribution is the capability to fuzz the emulated baseband image using specialized fuzzing tasks. These tasks are created using our modkit, and use triforce-afl hypercalls to communicate with the fuzzer, AFL++.

This combination of injected tasks and hypercalls allows for transparent in-modem fuzzing: A fuzz task would get the input from the fuzzer and then send it as message to the targeted task. For the targeted task, the input received this way would look like benign input arriving over the usual channels.

FirmWire comes with some example fuzzing tasks, which were used in the evaluation of our paper.

First, shannon.h is included to provide shannon specific convenience functions (e.g. uart_puts and pal_MemAlloc). Then, afl.h is included, which provides the main functionality and API for fuzzing. The API is a slightly modified version as the one given by TriforceAFL and provides following four functions:

FunctionPurpose
char * getWork(unsigned int *sizep)Returns a buffer with fuzzing input and stores the input size into sizep.
int startWork(unsigned int start, unsigned int end)Start a fuzzing execution, while collecting coverage for code residing between start and end.
int doneWork(int val)Mark the end of a fuzzing iteration, providing val as return code to the fuzzer.
int startForkserver(int ticks)Starts AFL forkserver. ticks controls whether qemu ticks should be enabled or not.

As we can see, this logic requires two additional functions: fuzz_single_setup and fuzz_single, which both need to be provided by our harness. The first function is responsible for all task-specific setup. In the case of gsm_cc, this means (1) resolving the queueID for CC, (2) creating a qitem_cc memory chunk containing the correct msgGroup ID to initiate task initialization, and (3) sending the memory chunk as message to the according queue.

Trophy Wall

FirmWire is intended as tool to find security critical bugs and to ease baseband specific research. As such, we are happy to showcase how FirmWire is used! On this page, you can find details to vulnerabilties found with FirmWire, talks about the framework, and blogposts describing its usage.

Vulnerabilities

So far, FirmWire was involved in finding the following vulnerabilities:

CVESeverityFinderDescription
CVE-2021-254797.2 (high)Team FirmWireA possible heap-based buffer overflow vulnerability in Exynos CP Chipset prior to SMR Oct-2021 Release 1 allows arbitrary memory write and code execution.
CVE-2021-254787.2 (high)Team FirmWireA possible stack-based buffer overflow vulnerability in Exynos CP Chipset prior to SMR Oct-2021 Release 1 allows arbitrary memory write and code execution.
CVE-2020-252799.8 (critical)Team FirmWireAn issue was discovered on Samsung mobile devices with O(8.x), P(9.0), and Q(10.0) (Exynos chipsets) software. The baseband component has a buffer overflow via an abnormal SETUP message, leading to execution of arbitrary code. The Samsung ID is SVE-2020-18098 (September 2020).
CVE-2021-254774.9 (medium)Team FirmWireAn improper error handling in Mediatek RRC Protocol stack prior to SMR Oct-2021 Release 1 allows modem crash and remote denial of service.

Talks

TitleWhereWhoLinksDescription
Emulating Samsung’s Baseband for Security TestingBlackhat USA’20Team FirmWire (Grant & Marius)youtube slidesTalk about FirmWire’s first steps (back then, it had the working title ShannonEE). Discusses the fundamental architecture of the framework.
Reversing & Emulating Samsung’s Shannon BasebandHardwaer.io NL’20Team FirmWire (Grant & Marius)youtube slidesTalk about the reverse engineering on Shannon-based modems which was required to build FirmWire.
FirmWire: Transparent Dynamic Analysis for Cellular Baseband FirmwareNDSS’22Team FirmWire (Grant)TBDAcademic presentation of the FirmWire paper.
FirmWire: Taking Baseband Security Analysis to the Next LevelCanSecWest’22Team FirmWire (Grant, Marius & DominikTBD

Blog posts

So far, we are not aware of any blog posts about FirmWire, but this may change in the future. 😉

Adding your Vulnerability, Talk, or Blogpost to this Trophy Wall

We are happy to hear about your FirmWire usage! If you want to include it into this trophy wall, create first a fork of the FirmWire repository on the GitHub UI. Then, clone the docs branch of your forked FirmWire repository:

$ git clone -b docs git@github.com:your_username/FirmWire.git

LEAVE A REPLY

Please enter your comment!
Please enter your name here