When this post on Hofstede’s blog showed up on Lobsters, the comments treated it mostly as a neat hack. And it is. But the more interesting story is the one underneath: why does a cellular modem on a laptop need an “unlock” at all, what protocol is being spoken, and why did it take this long for someone to write it down in plain shell.
What FCC Unlock Actually Means
The Federal Communications Commission mandates that radio transmitters sold in the United States cannot be reconfigured to operate outside their licensed frequency bands or power limits. For commodity cellular modems sold to multiple OEMs, this creates a practical problem: the modem ships with its radio locked, and the OEM’s software has to send a specific command to enable it before the device will transmit.
This is not a security measure and it is not DRM. It is a regulatory compliance mechanism. The lock exists so that a modem sold for use on one band configuration cannot trivially be reprogrammed into something that interferes with emergency services or satellite uplinks. The FCC’s equipment authorization rules are the source of this requirement, and every cellular modem sold in the US market goes through the same process.
The consequence for Linux users is that the modem sitting on your M.2 slot will not register on any network until a host-side process sends the right message. On Windows, this happens transparently inside the OEM’s driver package. On Linux, you need something equivalent.
ModemManager has supported FCC unlock scripts since version 1.18. The mechanism is straightforward: drop an executable script into /usr/share/ModemManager/fcc-unlock.d/, name it after the modem’s USB vendor and product ID (for example 2cb7:0111 for a Fibocom module), and ModemManager will invoke it automatically when the modem is detected. The script receives CONTROL_PORT and MODEM_PHYSDEV_UID as environment variables and is expected to do whatever vendor-specific incantation the modem requires.
Lenovo ships a binary for this purpose in their Linux support packages. It works. It is also a compiled executable that nobody outside Lenovo can read, audit, or maintain.
The MBIM Protocol
To understand what the unlock binary is doing, you need to understand MBIM. The Mobile Broadband Interface Model is a USB standard defined by the USB Implementers Forum. It replaced the older NCM and QMI-over-USB arrangements for 4G and 5G modems and is what most modern M.2 WWAN cards expose on Linux.
At the USB level, a MBIM device presents a CDC (Communications Device Class) interface. The control channel is exposed as a character device, typically /dev/cdc-wdm0. Data flows over a separate bulk endpoint that appears as a network interface, usually wwan0 or similar. The control channel is where all configuration happens: signal queries, PIN management, APN setup, connect and disconnect commands.
MBIM messages are structured packets with a transaction ID, a service UUID, a command ID (CID), and a payload. The standard defines a set of service UUIDs: BASIC_CONNECT handles most common operations, SMS handles messaging, PHONEBOOK for contacts, and so on. Vendors add their own UUIDs for capabilities outside the spec. Qualcomm has extensions. Intel has extensions. Fibocom has extensions. When Lenovo’s binary sends an unlock command, it is sending a message with a vendor-specific UUID and a CID that neither the MBIM spec nor the Linux kernel documentation describes.
The libmbim library and its companion tool mbimcli are the standard userspace interface to all of this. They are part of the freedesktop.org stack, maintained by Aleksander Morgado and others, and they expose the full MBIM surface through a relatively ergonomic command-line interface.
# Query basic device capabilities
mbimcli -d /dev/cdc-wdm0 --query-device-caps
# Query subscriber ready state
mbimcli -d /dev/cdc-wdm0 --query-subscriber-ready-status
# Microsoft extensions
mbimcli -d /dev/cdc-wdm0 --ms-query-firmware-id
For vendor-specific commands, mbimcli exposes a --device-send-mbim flag or equivalent low-level interface that lets you construct arbitrary MBIM messages by UUID, CID, and raw payload. This is what makes the blob replacement possible: once you know the UUID and CID the unlock sequence uses, you can call mbimcli with those values directly from a shell script.
How the Replacement Works
The author’s approach, as is typical for this class of reverse engineering, combines two techniques. First, capture what the binary blob actually sends. Tools like Wireshark with the MBIM dissector or usbmon-based capture (tcpdump on usbmon0) can record the exact message exchange between the binary and the modem. Second, translate those captures into mbimcli calls.
The result is a shell script that opens the MBIM device, sends the unlock sequence as one or more vendor-specific MBIM messages, and exits. No compiled code. No ABI dependencies. No risk of the binary breaking on the next glibc update or distribution upgrade.
The ModemManager FCC unlock hook makes deployment clean. A script placed in the right directory with the right filename runs at the right moment without any additional service configuration. The entire integration looks like:
#!/bin/bash
# /usr/share/ModemManager/fcc-unlock.d/2cb7:0111
MBIM_DEVICE="${CONTROL_PORT}"
# Send vendor-specific unlock sequence via mbimcli
mbimcli \
--device="${MBIM_DEVICE}" \
--device-open-proxy \
--[vendor-specific-command] \
--[payload-specific-arguments]
The exact parameters depend on the modem. Different Fibocom modules, different Qualcomm variants, and different firmware revisions may need different sequences. But the structure is stable.
Why This Took a While
The MBIM standard has been around since 2013. libmbim has been stable for years. ModemManager’s FCC unlock hook has been documented since 1.18. The Fibocom FM350-GL has been shipping in ThinkPads since the Gen 3 generation. None of this is new.
What changed is that enough people ran into the problem, with the right combination of tooling familiarity and time, to actually dig in and write it down. The binary worked well enough that most users either installed the Lenovo package or gave up on WWAN. The people who cared about avoiding the blob were few enough that the knowledge stayed fragmented across forum posts and GitLab issues.
This is not unique to WWAN unlock. The Linux firmware ecosystem has substantial blob dependency, some of it unavoidable because it is actual device microcode, and some of it avoidable because it is a host-side tool doing something a script could do. The distinction matters. Replacing iwlwifi firmware requires Qualcomm or Intel to open-source their radio firmware, which involves spectrum licensing and competitive IP. Replacing a host-side MBIM unlock tool requires knowing which USB messages to send. The latter is a much simpler problem.
The Fibocom FM350-GL’s open Linux support has been improving incrementally. The upstream ModemManager project maintains a database of modem quirks and includes FCC unlock scripts for some configurations. The gap this bash script fills exists because Lenovo made a product decision to ship their own binary rather than contribute the unlock script upstream.
The Broader Point
A 100-line bash script is not impressive on its own. What is impressive is the auditable trail it creates. You can read every command. You can verify it is not making unexpected network calls, not logging device identifiers, not doing anything beyond the six or eight MBIM messages it was designed to send. You can diff it against future versions. You can port it to a different modem by changing the vendor UUID.
The binary blob gives you none of that. It works, but you are trusting an opaque executable from a hardware vendor’s support page to run as root on your laptop at boot time. That trust is not unreasonable for firmware that has to live inside the modem chip. It is much harder to justify for a host-side tool whose entire function is to send a known protocol message to a known endpoint.
This is the kind of work that rarely gets headlines but compounds quietly. Someone publishes a script. Someone else finds it, adapts it for a different firmware revision, and posts that variant. A few years later it gets merged somewhere official and the binary is no longer necessary. The protocol has been open the whole time; it just needed someone to write it down.