Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

QDig

Qualcomm ships firmware in pieces. A modem.mdt holds the ELF header and the program headers, and every byte of actual code lives in modem.b00, modem.b01, and so on next to it. The kernel puts them back together at boot. Ghidra does not: open the .mdt and you get an empty database, with no warning that anything is missing.

QDig gathers the pieces, lays each segment at its real address, picks the right processor, and then digs out the debug metadata the vendor forgot to strip.

Install

git clone https://github.com/NerdTIV/QDig
cd QDig/Ghidra/loader
export GHIDRA_INSTALL_DIR=/opt/ghidra_12.1.2_PUBLIC
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
./build.sh install

Restart Ghidra. That is all: no gradle, no Eclipse, just javac and jar.

Needs Ghidra 12.x, a JDK 21, and zip (build.sh packs the extension with it, and a minimal Debian does not ship it). Older Ghidra works too, but without the Hexagon processor module, so the modem images will not open.

Use it

Drop your .mdt and its .bNN files in the same folder, then open the .mdt in Ghidra. The importer offers Qualcomm baseband image (MBN / split ELF), with two checkboxes already ticked. Take both.

On a real EG25-G modem image:

before   0 bytes, nothing to disassemble
after    20 segments, 79 MB of Hexagon, 7418 commented descriptors

That is the whole workflow. No script to run, no address to work out by hand.

One case will look like a failure and is not. If your file is a complete ELF, one that already carries its own code, QDig does not show up in the import dialog at all and Ghidra offers you its usual Executable and Linking Format (ELF). Take it: the stock loader handles relocations, sections and symbols, which this one does not, so there is nothing to gain from getting in its way. QDig only steps in where the stock loader would leave you with an empty database.

Quick way to tell which one you have, without opening Ghidra:

python3 Ghidra/scripts/qcom_img.py yourfile.mbn

A plain segments : 7 means the file is complete, so use the ELF loader. If the line reads segments : 20 (decoupe en .bNN), it is split and QDig is the one you want. The per-segment lines say the same thing: an offset 0x3000 is a byte range inside the file you opened, a .b00 is a byte range in a file next door.

What you get back

This is the part worth the trouble. Qualcomm's debug logging compiles every message against a table of descriptors, and that table ships in the firmware. Each entry names a source file, a line number, and often the printf format.

QDig scans for them and drops a comment on each one:

84000000  smd_main.c:1257
840008f0  nvim.c:3462  %s,item:%d

Let Ghidra's analysis run and the references appear, so you know which function sits in which source file at which line. On the EG25-G that is 7418 descriptors, 6126 of them with the format string still in clear, across 472 source files.

The file names belong to the module vendor, not to Qualcomm:

quectel_common_atc.c    564 messages
quectel_ds_tcpip.c      296
quectel_http_client.c   246

Some formats even carry the function name, which is a free extra:

[Max][NDIS][Quectel_Get3GPP2FlagForCDMA] imsi: %s

On a firmware everybody calls stripped.

Not just modems

This reads the Qualcomm PIL format, so it is not tied to one chip. Tested on four unrelated families:

EG25-G modem.mdt        Hexagon    20 segments, 79 MB    modem baseband
wpss.mbn   (WCN6750)    Hexagon     7 segments           WiFi
wlanmdsp.mbn (WCN3990)  Hexagon     3 segments           WLAN DSP
q6_fw0.mbn (IPQ5424)    Hexagon    11 segments           networking DSP
sbl1 / rpm / tz / mba   ARM       1 to 8 segments        bootloaders

So adsp.mdt, cdsp.mdt, venus.mdt, wcnss.mdt, and the modem of any MDM9x07 module (Quectel EC2x and EG2x, the SIMCom and Fibocom equivalents) all land in the same place. The debug scan is not a modem thing either: it finds 242 source files in a WCN6750 WiFi firmware.

AT command annotation

A separate script, and it works on any binary that drives a modem, Qualcomm or not. It comments every string carrying an AT command with its family and risk category, renames the anonymous functions, and rebuilds the dispatch tables, which recovers handlers a plain xref never reaches.

It runs through pyghidra, so it needs a venv. analyzeHeadless will not do, it refuses to run Python scripts and answers "not started with PyGhidra".

python3 -m venv ~/ghidra_venv
~/ghidra_venv/bin/pip install pyghidra

cd Ghidra
~/ghidra_venv/bin/python scripts/ghidra_at.py /usr/sbin/ModemManager --project /tmp/proj --name AT --surface data/surface.json

On ModemManager: 13928 strings read, 263 commented, 140 functions renamed, 36 dispatch entries. With surface.json around it also shows whether each command is really supported on a reference modem, because knowing a function touches +QFDWL matters mostly if the modem answers it.

What it does not do

It does not decompress anything. Real modem images pack some read-only data with q6zip or CLADE, and QDig measures the entropy of every segment so you are told which ones rather than left hunting for code that is not there. On the EG25-G the executable segments sit at 6.88 to 6.99 bits per byte and two data segments come out at 7.98 and 7.91.

If you need those unpacked, mzakocs/qualcomm_baseband_scripts does it, and also decodes QSHRINK4 when the strings really were moved out into a .qdb. Different layer, no overlap.

Beyond the split .mdt, the only other thing it claims is one oddity found on a real sbl1.mbn, where an MBN header with every field at 0xffffffff hides a complete ELF at offset 0x2800. No ELF magic at offset 0 means no ELF loader ever sees that file, so Ghidra would only offer you Raw Binary.

Checked

31 checks, no failure. Same venv as above, and the extension has to be installed and Ghidra restarted first, otherwise everything fails at once:

cd Ghidra
~/ghidra_venv/bin/python tests/test_loaders.py

Two sections run against real firmware. One takes a Hexagon image out of /usr/lib/firmware, splits it into .mdt plus .bNN the way Qualcomm does, and verifies the loader reassembles it and that Ghidra disassembles real instructions from the result. Those skip cleanly on a machine with no Qualcomm firmware installed.

The EG25-G numbers come from its published recovery image, pulled out of NON-HLOS.ubi. It is the EC25's sibling, same MDM9x07. An EC25 image itself has not been tried.

There is an IDA port of both tools, not published here. No IDA licence on this machine, so it has never run for real, and a loader nobody has executed once is how you collect bug reports you cannot reproduce.

About

Reassembles split Qualcomm firmware for Ghidra, and recovers source file and line from the debug metadata left in the binary

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages