· 7 min read ·

What Lenovo's WWAN Unlock Blob Was Actually Doing All Along

Source: lobsters

Every now and then, someone reverse engineers a proprietary binary and discovers it was doing something embarrassingly simple. This is one of those times.

Lenovo ships certain ThinkPads with an integrated WWAN modem, a cellular radio that lets the laptop connect to LTE or 5G networks without a hotspot. On Linux, that modem does not simply work. Lenovo ships a proprietary binary blob, triggered by a udev rule or systemd service on boot, that sends an unlock sequence to the modem before it will register on a network. The blob is closed-source, ships as a pre-compiled ELF binary, and carries no documentation. It just runs, silently, and until recently you took it on faith that it was doing something essential.

It was doing something essential. It just did not need 10,000 lines of compiled C to do it.

A developer traced the blob’s behavior, identified the exact commands it was sending, and rewrote the entire thing in about 100 lines of bash. The replacement works. The modem connects. The blob is gone.

What WWAN Unlock Actually Means

The WWAN modules in ThinkPads (Fibocom L850-GL, Quectel EM05-G, Sierra Wireless EM7455, and their successors) are full cellular modems on a USB or PCIe interface. On top of Lenovo’s BIOS whitelist, which only allows approved card models to initialize at all, some of these modems also require an “FCC unlock” before they will transmit. This is a regulatory requirement in the United States: modems must be locked to their tested RF configuration until an authorized unlock sequence is received. On Windows, the driver handles this. On Linux, something else has to.

The Linux kernel has understood this problem for a while. Since around kernel 5.14, the kernel WWAN subsystem and ModemManager together provide an FCC unlock plugin architecture. ModemManager, when it detects a modem that needs FCC auth, looks in /usr/share/ModemManager/fcc-unlock.d/ for a script matching the modem’s USB vendor and product ID. If it finds one, it runs it. The whole system was designed precisely so that modem vendors and OEMs could ship these unlock sequences as simple scripts instead of opaque binaries.

Lenovo did not use it.

Instead, Lenovo shipped a compiled binary that runs outside ModemManager’s lifecycle, communicates directly with the modem, and gives users no insight into what it is doing or whether it succeeded. This is not unusual behavior for OEM software, but it is unnecessary, and it creates real problems: the blob only ships for certain distributions, breaks when library versions change, and cannot be audited.

How Modems Talk: MBIM in Practice

To understand what the blob was doing, you need to understand how Linux talks to modern mobile broadband modems. Most WWAN modules since around 2013 use MBIM, the Mobile Broadband Interface Model, a USB device class specification from the USB Implementers Forum. MBIM replaced the older approach of layering PPP over a CDC-ACM serial port. It is cleaner, faster, and natively supports IPv6 and multiple bearers.

When an MBIM modem is connected, the kernel presents it as a character device, typically /dev/cdc-wdm0 or (with the newer kernel WWAN framework) /dev/wwan0mbim0. All MBIM communication happens through this device using a binary message format.

Every MBIM message targets a “service”, identified by a UUID. The specification defines several standard services:

  • Basic Connect (a289cc33-bcbb-8b4f-b6b0-133ec2aae6df): radio state, registration, packet attach, connect/disconnect
  • SMS (533fbeeb-14fe-4467-9f90-33a223e56c3f): read and send SMS
  • Authentication (1d2b5ff7-0aa1-5ec8-ba5c-79d7e0...): EAP authentication

But the spec explicitly allows vendor-defined service UUIDs. This is where things get interesting. Any modem vendor, and any OEM layered on top of them, can define a proprietary service UUID and use it to send arbitrary vendor commands. The MBIM message structure wraps these as MBIM_COMMAND_MSG with InformationBuffer payloads whose contents are completely opaque to everything except the modem firmware and the software that knows the UUID.

This is what Lenovo’s blob was using: a vendor-specific MBIM service UUID to send a structured unlock command to the modem. The modem firmware recognizes the UUID, processes the payload, and transitions to an unlocked state.

mbimcli, the command-line client from libmbim, can send these messages:

mbimcli -d /dev/cdc-wdm0 --no-open=3 \
  --ms-set-device-service-subscribe-list="..."

The --no-open=3 flag is particularly useful here: it passes an already-open file descriptor to the device, skipping the MBIM OpenMessage handshake. This matters when ModemManager already has the device open, since MBIM only allows one manager at a time.

For cases where the modem also exposes an AT command port (usually at /dev/ttyUSB2 or similar, detectable by checking bInterfaceProtocol in the USB descriptor), the script can send AT commands directly:

exec 3<>/dev/ttyUSB2
echo -e 'AT+COMMAND\r' >&3
read -t 2 response <&3
exec 3>&-

Or with socat for cleaner handling:

socat - /dev/ttyUSB2,crnl,rawer,echo=0,b115200 <<< 'AT+COMMAND'

Tracing What the Blob Was Doing

The reverse engineering approach for this class of problem is fairly standard. The two main techniques are strace and USB capture.

strace -e trace=read,write,open,ioctl -p $(pgrep blob) will show every file descriptor interaction the blob has, including the raw bytes it writes to the MBIM device. The output is verbose, but MBIM messages have a recognizable structure: a 4-byte message type, 4-byte total length, 4-byte transaction ID, followed by the payload. With the spec open alongside the strace output, the messages decode quickly.

Alternatively, usbmon, the kernel’s USB bus monitor, captures raw USB traffic at the host controller level. Load the module with modprobe usbmon, then capture with Wireshark on the usbmon0 interface (or whichever bus the modem is on, found via lsusb -t). Wireshark has a built-in MBIM dissector that will decode the messages automatically, showing service UUIDs, CIDs (command IDs), and payloads in a readable tree view.

Once you have the UUID and command structure, you can replay it with mbimcli using --device-open-proxy if needed, or write raw bytes to the character device if mbimcli does not have a built-in for that specific vendor extension.

The resulting bash script uses the standard tools already available on most Linux distributions: mbimcli from libmbim (packaged in every major distro), possibly socat or direct file descriptor manipulation for AT commands, and a udev rule to trigger the whole thing at the right moment:

ACTION=="add", SUBSYSTEM=="usb", \
  ATTRS{idVendor}=="105b", ATTRS{idProduct}=="e0ab", \
  RUN+="/usr/local/bin/wwan-unlock.sh"

The RUN directive in udev executes the script synchronously when the device appears, with the full environment needed to find the device node and send the commands.

The FCC Unlock Plugin System That Should Have Been Used

ModemManager’s FCC unlock plugin system is worth understanding, because it represents the right way to solve this problem at the infrastructure level. When ModemManager detects a modem that reports itself as needing FCC auth (via the MBIM MBIMSubscriberReadyState or via QMI’s DMS Get Operating Mode), it checks /usr/share/ModemManager/fcc-unlock.d/ for a file named after the USB IDs, like 105b:e0ab. If found, ModemManager runs that file as a script, passing the modem path and index as arguments.

Several modem vendors already ship these scripts, including entries for Fibocom and Quectel modems. The scripts themselves are often just a few mbimcli or qmicli invocations.

By bypassing this system, Lenovo created a dependency on their proprietary binary that is entirely unnecessary. The Linux modem ecosystem already had the abstraction. A vendor-provided shell script in the right directory would have been auditable, updatable, and distribution-agnostic.

What This Pattern Reveals

The gap between what proprietary blobs do and what they look like they do is almost always significant. A compiled binary with no documentation implies complexity, security sensitivity, or trade secrets. In reality, a large fraction of these blobs are doing things that could be expressed in two or three function calls against a well-documented interface.

This is not unique to Lenovo. Wi-Fi firmware blobs, GPU microcode, embedded controller firmware: some of these are genuinely complex and legitimately proprietary. But the “unlock” category, where a blob’s sole purpose is to send a vendor-specific command sequence to a piece of hardware, almost never needs to be closed source. The command sequence itself is not a trade secret once the device is in the field. Anyone with a USB analyzer and 30 minutes can recover it, as this blog post demonstrates.

The practical benefits of the bash replacement go beyond transparency. The script can be modified to add logging, debugging output, or retry logic. It can be packaged by distributions without requiring Lenovo’s involvement. It works on any kernel version and any glibc version, because it has no compiled dependencies beyond mbimcli. It can be inspected before being trusted with ring-3 access to a cellular radio.

For ThinkPad users running Linux without the official Lenovo software stack, this kind of community reverse engineering is often the only path to a working cellular connection. The ThinkWiki and the ThinkPad subreddit communities have maintained working configurations for dozens of modem variants over the years, entirely through this kind of documentation and scripting effort.

The 100-line bash script is not a hack in the pejorative sense. It is what the blob should have been from the start: a transparent, maintainable sequence of documented commands that does exactly what it says and nothing else. The fact that it took reverse engineering to produce it says more about OEM software culture than it does about the complexity of the underlying problem.

Was this interesting?