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
Argument | Covered in | Description |
---|---|---|
modem_file | Getting Started | The modem file FirmWire shall create an emulation environment for. Only mandatory argument(!) |
--consecutive-ports CONSECUTIVE_PORTS | Getting Started | Choose consecutive ports for the any listening sockets (e.g. QEMU’s GDB & QMP), starting with the port provided. |
-h/--help | CLI reference | Show help for for different cli flags on commandline |
-w/--workspace WORKSPACE | Workspaces | Path to the workspace to use |
--snapshot-at SNAPSHOT_AT | Workspaces | Address and name for taking a snapshot. (Syntax: address,name) |
--restore-snapshot SNAPSHOT_NAME | Workspaces | Name of snapshot to be restored |
-t/--module INJECTED_TASK | Modkit | Module / Task to be injected to the baseband modem |
-S/--stop | Interactive exploration | Stop CPU after initializing the Machine. Useful for interactive exploration. |
-s/--gdb-server | Interactive exploration | Start GDB server on TCP port. Default is 1234. NOTE: this is a minimal GDB stub. |
--console | Interactive exploration | Spawn an ipython remote kernel that can be connected to from another terminal using jupyter console --existing |
--fuzz FUZZ | Fuzzing | Inject and invoke the passed AFL fuzz task module (headless). |
--fuzz-input FUZZ_INPUT | Fuzzing | Path the AFL test case (@@ should be sufficient) or just the path to a single test file. |
--fuzz-triage FUZZ_TRIAGE | Fuzzing | Invoke the fuzzer, but without an AFL front end. Enables debug hooks and saves code coverage. |
--fuzz-persistent FUZZ_PERSISTENT | Fuzzing | Enable persistent fuzzing with a loop count as the argument. |
--fuzz-crashlog-dir FUZZ_CRASHLOG_DIR | Fuzzing | Folder to which logs of all testcases (length testcase) for a crashing run in persistent mode |
--fuzz-crashlog-replay FUZZ_CRASHLOG_REPLAY | Fuzzing | Replay a persistent-mode crash trace written with fuzz-crashcase-dir. |
--fuzz-state-addr-file FUZZ_STATE_ADDR_FILE | Fuzzing | Textfile containing the hex-addresses of state-variables |
--full-coverage | Fuzzing | Enable full coverage collection (logs every executed basic block) |
--shannon-loader-nv_data NV_DATA | TBD | (Shannon only) Specify the NV_DATA to be used |
--mtk-loader-nv_data NV_DATA | TBD | (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.
Argument | Covered in | Description |
---|---|---|
--debug | TBD | Enable FirmWire debugging |
--debug-peripheral | TBD | Enable debugging for specified peripheralas |
--avatar-debug | TBD | Enable debug logging for Avatar2 |
--avatar-debug-memory | TBD | Enable Avatar2 remote memory debugging (useful when Peripherals crash) |
--unassigned-access-log | TBD | Print log messages when memory accesses to undefined memory occur |
--raw-asm-logging | TBD | Print assembly basic blocks as QEMU executes them. Useful for determining infinite loops. |
--trace-bb-translation | TBD | Print 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
Keyword | Description |
---|---|
name | The name of the pattern and the resulting symbol (string) |
pattern | One ore more memory patterns which will create the result on match. |
lookup | Function 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_lookup | Function to be executed after successful match. Parameters are described in example above. Expected to return True on success, else False . |
required | When set to True , FirmWire will not continue execution when no match is found. |
for | Specify SoC version in case the symbol shall only be looked up for certain SoC versions. |
within | On an image with existing symbols, specify in which function to look for this pattern. |
offset | Offset between matched pattern and address of created symbol. |
offset_end | Same as offset, but caluclate from the end of matched pattern, rather than from the start. |
align | Memory 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:
Function | Purpose |
---|---|
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:
CVE | Severity | Finder | Description |
---|---|---|---|
CVE-2021-25479 | 7.2 (high) | Team FirmWire | A 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-25478 | 7.2 (high) | Team FirmWire | A 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-25279 | 9.8 (critical) | Team FirmWire | An 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-25477 | 4.9 (medium) | Team FirmWire | An improper error handling in Mediatek RRC Protocol stack prior to SMR Oct-2021 Release 1 allows modem crash and remote denial of service. |
Talks
Title | Where | Who | Links | Description |
---|---|---|---|---|
Emulating Samsung’s Baseband for Security Testing | Blackhat USA’20 | Team FirmWire (Grant & Marius) | youtube slides | Talk 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 Baseband | Hardwaer.io NL’20 | Team FirmWire (Grant & Marius) | youtube slides | Talk about the reverse engineering on Shannon-based modems which was required to build FirmWire. |
FirmWire: Transparent Dynamic Analysis for Cellular Baseband Firmware | NDSS’22 | Team FirmWire (Grant) | TBD | Academic presentation of the FirmWire paper. |
FirmWire: Taking Baseband Security Analysis to the Next Level | CanSecWest’22 | Team FirmWire (Grant, Marius & Dominik | TBD |
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