The story is familiar: hardware ships with a proprietary binary that does something nobody quite understands, runs as root, and resists inspection. Then someone sits down with a packet sniffer or a serial monitor, reads what the binary is actually sending to the hardware, and finds that the magic was a handful of AT commands all along.
That is roughly what happened with Lenovo’s WWAN unlock mechanism, documented in this post on hofstede.it. The author replaced Lenovo’s proprietary unlock binary with a 100-line bash script by observing what AT commands the blob sent to the modem, then replicating that sequence directly. The replacement works, which makes the blob’s existence harder to justify in hindsight.
What “WWAN unlock” means here
ThinkPads and other Lenovo business laptops often ship with embedded cellular modems, typically from manufacturers like Fibocom, Sierra Wireless, or Quectel. These modems handle LTE and 5G connectivity independently of Wi-Fi, appearing to the OS as a USB device exposing serial interfaces, QMI endpoints, or MBIM interfaces.
The unlock in this context is not about SIM PIN codes or carrier unlocking in the consumer sense. It is about a modem initialization gate that Lenovo implemented. Some of these modems ship in a restricted state and need a host-side tool to signal that they are allowed to operate. The mechanism exists partly for carrier certification reasons, partly for enterprise control: corporate IT can configure which networks a fleet of laptops can reach. The blob is the gatekeeper.
The problem is that on Linux, this gatekeeper is a closed binary. It requires specific execution, often at boot time or when the modem powers up, and there is no source to read, audit, or port to different architectures.
The AT command protocol
AT commands are older than most developers’ careers. Hayes Microcomputer Products introduced the command set in 1981, and it became the lingua franca of modems. The “AT” prefix stands for “attention,” and every command starts with those two characters. The modem responds with “OK”, “ERROR”, or result data.
Modern cellular modems still speak AT commands, usually over a virtual serial port appearing as /dev/ttyUSB*, /dev/ttyACM*, or on newer kernels /dev/wwan*. You can open one of these with minicom, socat, or just cat and echo, and speak to the modem directly.
A basic session looks like this:
AT+CIMI # Request IMSI
860123456789012
OK
AT+CGDCONT? # List PDP contexts
+CGDCONT: 1,"IP","internet","",0,0
OK
Manufacturers layer vendor-specific extensions on top of the standard set. Qualcomm modems, which many Fibocom and Sierra modules use internally, have their own AT command namespace. Lenovo’s unlock mechanism uses one or more of these vendor extensions to transition the modem out of its restricted state. The blob was doing nothing more exotic than opening a serial port, sending a specific sequence of AT commands, reading the responses, and exiting. The magic was knowing which commands to send in what order.
Reading what the blob was saying
Observing what a binary does without source code does not require disassembly. You just need to watch the system calls.
On Linux, strace -e trace=open,write,read -f ./unlock_binary shows every file the process opens and every byte it sends. When the target is a serial device, the AT commands appear in plain text in the write syscalls. usbmon can capture raw USB traffic if the serial port sits on top of USB, which it almost always does with WWAN modules.
socat provides another approach: create a pseudo-terminal pair, interpose it between the binary and the real device, and log everything that passes through.
socat -v PTY,link=/tmp/modem_fake,rawer /dev/ttyUSB2,rawer 2>/tmp/modem_log.txt
Point the binary at /tmp/modem_fake instead of the real device via a symlink or command argument, and you have a complete transcript of the conversation between the blob and the modem. The vendor AT commands appear verbatim in that log.
Once you have the transcript, writing the replacement is mechanical. The bash reimplementation looks roughly like this:
#!/usr/bin/env bash
MODEM_PORT="${1:-/dev/ttyUSB2}"
send_at() {
local cmd="$1"
printf '%s\r\n' "$cmd" > "$MODEM_PORT"
sleep 0.2
read -t 2 response < "$MODEM_PORT"
echo "$response"
}
# Bring modem to low-power state before reconfiguring
send_at "AT+CFUN=0"
sleep 1
# Send vendor-specific unlock sequence
send_at "AT+XLOCK=..."
# Restore full functionality
send_at "AT+CFUN=1"
The real script is more careful: it validates responses before proceeding, handles timeouts, and checks that the modem acknowledged each step. But the structure is that simple.
Why bash works where a blob was supposed to be necessary
The argument for shipping a binary instead of a script usually involves security through obscurity, protecting proprietary unlock codes, or preventing misuse. None of these hold up under examination.
Security through obscurity fails the moment someone runs strace. If unlock codes are embedded in the binary, they are readable from the binary with strings or by monitoring the serial output at runtime. A determined actor who wants to misuse the unlock mechanism does not need the source code.
The concern about protecting proprietary codes is more plausible. If Lenovo negotiated unlock credentials with modem manufacturers or carriers, there might be a reason to avoid shipping them in plain text. But those credentials are being sent to the modem over a serial port at runtime, in a protocol that can be trivially intercepted. The binary format does not protect them; it just makes them slightly harder to extract without active effort.
What the blob approach does accomplish is making the unlock harder to understand for ordinary users, harder to modify, and harder to run on alternative distributions or non-x86 architectures. None of those are benefits to the user. They are friction.
The Linux modem ecosystem and where this fits
ModemManager is the canonical way Linux talks to cellular modems. It handles device detection, interface management, and exposes a D-Bus API that NetworkManager and other tools use to establish connections. The mmcli command-line tool is the day-to-day interface into ModemManager.
Proprietary unlock blobs sit outside this ecosystem. They need to run before or around ModemManager, require integration into init systems or udev rules, and create dependencies on architecture-specific binaries. A binary built for x86-64 will not run on ARM or RISC-V. Anyone running an alternative kernel configuration or a non-standard init system has to figure out the blob integration independently, with no documentation.
A bash script using standard tools, /dev/ttyUSB* access, and documented AT commands slots into the existing ecosystem cleanly. It runs anywhere bash runs, can be audited, and can be packaged by distributions without legal concerns about redistributing proprietary binaries. It can also be contributed upstream to projects like ModemManager, where vendor-specific initialization already lives as plugins.
The community around mobile-broadband-provider-info and libmbim has cataloged a significant amount of modem-specific behavior over the years. Projects like libqmi provide structured access to Qualcomm’s QMI protocol, which many of these modems also speak. The Lenovo unlock sequence fits naturally into that body of work.
The broader binary blob problem
This is a small instance of a pattern that runs through Linux hardware support. GPU firmware, Wi-Fi firmware, Bluetooth firmware, and WWAN initialization routines all ship as compiled binaries. The Linux kernel’s firmware_class subsystem loads these blobs into hardware at runtime and treats them as opaque by design.
There is a meaningful spectrum here. Firmware that runs on dedicated hardware processors with their own instruction sets is difficult to replace without detailed hardware documentation, and those blobs often represent genuinely complex code that would need to be reverse-engineered at the machine-code level. Lenovo’s WWAN blob runs on the host CPU, communicates over a protocol that has been documented since 1981, and performs operations the modem’s own specification covers. That is not firmware in any meaningful sense. It is a script wearing a binary costume.
The Linux-libre project and the Free Software Foundation have tracked the binary blob problem in Linux for years. Most modern Wi-Fi cards fail their criteria. WWAN modules almost always do. But the unlock mechanism, it turns out, was never genuinely opaque, just undocumented.
Projects like OpenWRT and postmarketOS have long grappled with the blob problem on constrained hardware where you cannot simply run an x86-64 binary. The WWAN unlock situation is a milder version of what those communities face routinely. The difference is that a cellular modem in a ThinkPad has a well-documented host interface, so the barrier to replacement was low from the start.
What this should signal to hardware vendors
The developer who wrote the 100-line replacement did the work anyway. Hiding the implementation did not prevent reverse engineering; it just made the shipped solution worse for everyone until someone took the time to look.
If an unlock sequence is sensitive for carrier certification reasons, the right approach is authentication rather than obscurity: cryptographically sign the commands, tie them to device identity, or require a server round-trip. Hiding the command sequence in a compiled binary achieves none of those goals. It just raises the cost of discovery from “run strace” to “run strace and be patient.”
If the sequence is not sensitive, and many of these turn out not to be, publish it. The modem support community on Linux is capable and has a strong record of maintaining integrations once they exist. Open initialization scripts mean distributions can ship working out-of-the-box support, security teams can audit what is running at boot, and users on ARM hardware or alternative distributions do not get left behind. The 100-line bash script on hofstede.it shows that the technical barrier to that outcome was never very high.