Mobile Station Modem (MSM) is an ongoing series of a 2G/3G/4G/5G-capable system on chips (SoC) designed by Qualcomm starting in the early 1990s. MSM has always been and will be a popular target for security research because hackers want to find a way to attack a mobile device remotely just by sending it a SMS or crafted radio packet. But 3GPP protocols are not the only entry point into the modem. Android also has an ability to communicate with the modem processor through the Qualcomm MSM Interface (QMI).
MSM is managed by the Qualcomm real-time OS (QuRT) that cannot be debugged or dumped even on rooted Android devices. QuRT’s integrity is ensured by the TrustZone. There is only one possible way to dynamically probe the modem, namely to use a vulnerability. There have been several successful attempts to patch the QuRT by exploiting vulnerabilities in the Qualcomm Trusted Execution Environment (QTEE) or Linux-kernel. The latest compromised SoC is MSM8998 (Pixel 2).
In our research, we fuzzed MSM data services so we could find a way to patch QuRT on modern SoCs directly from Android.
QMI is a proprietary protocol used to communicate between software components in the modem and other peripheral subsystems. QMI communication is based on a client-server model, where clients and servers exchange messages in QMI wire format. A module can act as a client of any number of QMI services and a QMI service can serve any number of clients. In the context of Qualcomm SoC, which includes Android smartphones, QMI ports are exposed to the Linux-running application CPU core inside the chip. There can be many different transport mechanisms, but in modern integrated chips, the primary one used is the Shared Memory Device (SMD).
QMI offers various different services which are exposed via the QMI protocol stack on one or many QMI ports. The SM8150 SoC (Pixel 4) modem exports about 40 services, including:
Wireless data service (WDS)
Device management service
Network access service (NAS)
Quality of service
Wireless message service (WMS)
Card apps toolkit service (CAT)
Phone book manager service (PBM)
Wireless data administrative service
OEMs can also add their own services to those provided by Qualcomm by default. For example, LG adds the LGE resim service in its T-Mobile phones to handle SIM unlock requests.
Note that the fact that a large number of QMI services are written by multiple authors makes them a good target for security research.
QMI communication is of the request/response type. Each service registers itself in the QuRT and then waits for requests/messages in a queue. For example, NAS supports more than 130 different messages.
Let’s take a look at a simple example of requesting a modem service from Android. In the code below, we send a QMI_WDS_GET_PROFILE_LIST (0x2A) message to WDS specifying the message parameters in the req buffer.
The Android libqmiservices.so library exports objects of common QMI services. QuRT has a copy of these same service objects in its data segments. Such a service object declares the request and response parameters of the service messages. QMI uses service objects to marshal data between processors.
In our example, we load libqmiservices.so dynamically and call the wds_get_service_object_internal_v01 function to get the WDS related object.
The qmi_client_xxx functions are implemented in Android libqmi_cci.so library. The qmi_client_send_msg_async function asynchronously sends a message to the service. This function encodes the req object to Type-Length-Value (TLV) format before it is passed on. Note that there is another qmi_client_send_raw_msg_async function which expects to receive the message parameters in TLV format. It does not touch the service object.
As the target of our research is modem data services, but not the QMI itself, we are skipping the technical details of transferring the TLV payload between processors. All we need to know is that the payload will be stored as a field in a dsm_item_s object and then passed to the appropriate request-handling function in the service.
Now, let’s research the service request handler functions in a modem binary.
Decompression of modem segments
Qualcomm modem ELF is split into several files in the same way as any QTEE trusted application. Let’s take the Pixel 2 (June 2020 security patch) as an example. Files from modem.b00 – modem.b27 can be extracted from the device or the firmware update. For manually building the modem ELF, it’s enough to concatenate all these .bXX files into one.
Two program segments of the ELF are compressed with Qualcomm proprietary algorithms. The code segment 0xC5C70000 – 0xC6702000 is compressed using the q6zip algorithm. The data segment 0xC6710000 – 0xC6730000 is compressed using the delta algorithm. The easiest way to decompress the segments is to find the decompression code in the modem ELF and then execute it on the Quick emulator (QEMU). The QDSP6 (Hexagon) instruction set support was added in QEMU at the end of 2019.
A search for the 0xC5C70000 in the modem file allowed us to find the destination addresses to unpack.
By reverse-engineering, we found that the q6zip decompression function is located at 0xC0BAC240. We then used the code below to decompress the segment to 0xD0000000.
The delta decompression function is located at 0xC0BACA10. The output address is 0xD11C7000.
These decompressed segments contain code and data related to WMS, NAS, PBM, Voice, and CAT, as well as some other services.
Request handler functions
The easiest way to find the vast majority of the request handlers in the modem ELF is to look for the string qmi_svc_hdlr_ftype. Hundreds of request handler names can be found.
A service combines the names of the request handlers into a single table, along with the associated message ID and a pointer to the handler function itself.
This is how we found the code of handler functions for fuzzing.
In the code, we see that four arguments are expected by the handler. The last one (incoming request) is a pointer to the dsm_item_s object that contains the TLV payload sent from the Android side.
The valuable parts of the dsm_item_s structure can be easily reconstructed, but for fuzzing it is enough to allocate a small chunk of memory and enter only the following fields:
Point the data field (offset 0xC, size 4 bytes) to the generated test blob in TLV format.
Put the size of the test blob in fields size (offset 0x22, size 2 bytes) and used (offset 0x2A, size 2 bytes).
Set the field references (offset 0x24, size 4 bytes) to 0x100.
Point the pool field (offset 0x34, size 4 bytes) to a memory that starts with 0x7AB1E5E7 magic.
The vast majority of handler functions use the QMI framework to convert the TLV payload to a C structure right after they receive it. Such a conversion also checks for data integrity and logical errors during decoding. All parsing is localized in one well-tested code. The rest of the functions work directly with the TLV data and do not use service objects.
As mentioned previously, QEMU Hexagon can be used to emulate a modem code. We are interested in emulating and instrumenting the service handler functions.
To execute a handler function on QEMU, we prepared a simple program (a Hexagon ELF binary) which is responsible for the following actions:
Parse a data file received as the first command line parameter into the message ID (2 bytes) and the request blob. We tried to interpret the request blob in these two ways:
As a raw TLV data, which we pass to the handler as is.
As a C structure that we need to convert to TLVs. The easiest way to convert is to call the encoding function 0xC0830DFC and provide the request blob as an argument.
Pack the prepared TLV payload into a dsm_item_s object and then call the handler function associated with the message ID.
QuRT should be loaded on the emulator before we run our program because we need syscall handling and framework support. The easiest way to do so is to use runelf.pbn QuRT, adopted by Qualcomm for execution on a Hexagon simulator and included in the Hexagon SDK. runelf.pbn initializes the internal structures and then runs our program specified as a command line argument.
The runelf.pbn does not contain the modem code. Therefore, we injected all required modem segments 0xC0000000 – 0xD2000000 into the runelf.pbn file using a simple python script.
Of course, we cannot execute most of the service handler functions from start to finish. We do not have the modem memory dump and therefore several data segments were not filled with initial values. We made some minor fixes like redirecting modem malloc to regular QuRT malloc, but it is impossible to solve all the issues. It should be noted here that our primary goal is to test the payload parsing code where the most formatting errors occur. This part of the handlers is easy to achieve because it is located at the beginning of the handler function.
We used American fuzzy lop (AFL) in combination with QEMU to fuzz the handler functions on Ubuntu PC.
As a result, the fuzzer discovered a heap overflow vulnerability in the qmi_voicei_srvcc_call_config_req handler (0x64) of the voice service.
The qmi_voicei_srvcc_call_config_req function begins its execution by parsing the TLV payload. It does not use the QMI framework to convert the payload to a C structure.
If the type of a TLV packet is equal to 1, the value is interpreted as the following:
Number of calls (1 byte).
Array of call contexts (0x160 bytes per call).
To process this packet, the handler allocates 0x5B90 bytes on the modem heap, extracts the number of calls from the payload into the allocated buffer at offset 0x10, and then loops to fetch all call contexts into the buffer starting at offset 0x12. Due to the lack of checking for the maximum number of calls, it is possible to pass the value 0xFF in the number of calls field and thus overwrite in the modem heap up to 0x12 + 0x160 * 0xFF – 0x5B90 = 0x10322 bytes.
The call context is 7 fields of 1 byte size and an array, the size of which is specified in the seventh field. This means that the attacker controls with his values 0x106 out of 0x160 bytes per call entry. Note that such a heap overwrite vulnerability allows us to bypass the modem heap canaries, because we have the ability to jump over the obstructing bytes.
In this blog, we do not consider possible ways to exploit the vulnerability. In Figure 5 below, you can see the TLV payload that overwrites the canary byte 0x5B91 to 0xFF and triggers the modem reboot.
The third-party Android applications do not have permission to use the QMI. Qualcomm protects MSM services by SELinux policy. However, radio, media and many other privileged users have access to the vulnerable voice service.
The patch timeline:
October 8, 2020
Bug report and POC sent to Qualcomm.
October 8, 2020
Qualcomm acknowledges the report and assigns it QPSIIR-1441 for tracking.
October 15, 2020
Qualcomm confirms the issue and names it a High rated vulnerability.
February 24, 2021
Check Point requests the CVE-ID for this issue and acknowledges that the disclosure date is April 2021.
February 24, 2021
Qualcomm informs Check Point that the CVE-ID will be CVE-2020-11292.
May 6, 2021
QMI is present on approximately 30% of all mobile phones in the world but little is known about its role as a possible attack vector. If a researcher wants to implement a modem debugger to explore the latest 5G code, the easiest way to do that is to exploit MSM data services through QMI. In our attempt to do so, we reverse-engineered QuRT and built a feedback fuzzer for QDSP6 processor architecture to probe MSM data services for bugs.
We discovered a vulnerability in a modem data service that can be used to control the modem and dynamically patch it from the application processor. An attacker can use such a vulnerability to inject malicious code into the modem from Android. This gives the attacker access to the user’s call history and SMS, as well as the ability to listen to the user’s conversations. A hacker can exploit the vulnerability to unlock the SIM, thereby overcoming the limitations of the service providers imposed on the mobile device.