100 Lines of Bash vs. a Vendor Binary: What Lenovo's WWAN Blob Was Actually Doing
Source: lobsters
When someone publishes a post titled “Replacing Lenovo’s WWAN Unlock Blob with a 100-Line Bash Script”, the interesting story is not really the script. It’s what the existence of the script reveals about the binary it replaced.
A 100-line bash script can only substitute for a closed binary if that binary was doing something simple. When a vendor ships a proprietary blob for a task that turns out to be a handful of AT commands over a serial port, it raises questions about why the blob existed in the first place, and what the broader pattern says about vendor lock-in tactics on commodity hardware.
WWAN Modems and the Authorization Problem
ThinkPad laptops have shipped with embedded WWAN modems for years. Common modules include Sierra Wireless EM7455, EM7565, Fibocom L850-GL, and Quectel EM05/EM20, depending on the generation. These are M.2 cards that sit on an internal PCIe or USB bus, and they present themselves to the OS as USB composite devices with several endpoints: a QMI or MBIM interface for data, a GPS NMEA port, and one or more AT command ports.
The AT command ports are where the interesting behavior lives. Lenovo, like several other OEMs, has historically implemented a scheme where the modem firmware checks whether it is authorized to operate in the host machine. If the blob is absent or fails, the modem may power on and be visible to the OS but refuse to register on any cellular network. From the user’s perspective, the SIM is in, signal is present, and nothing works.
The blob’s job is to perform that authorization handshake. It reads some identifier from the host (typically the machine UUID or a platform-specific credential), transforms or signs it, and sends the result to the modem over the AT command interface. The modem firmware validates the response and unlocks network registration. This is not a SIM unlock; it is a host-to-modem pairing mechanism.
The AT Command Protocol, Briefly
AT commands are older than most of the hardware they now run on. The Hayes command set dates to 1977 and was designed for dial-up modems. The basic structure is simple: commands start with AT (attention), followed by a command code, followed by optional parameters, terminated with a carriage return. Responses come back as text, often OK or ERROR, with vendor-specific extended responses for complex queries.
Modern WWAN modems extend this base with vendor-specific prefixes. Sierra Wireless uses AT! for its extended commands; Quectel uses AT+QCFG and related namespaces; Fibocom has its own set. The modem exposes these over the serial interface regardless of whether the higher-level QMI/MBIM data path is active.
In Linux, these ports typically appear as /dev/ttyUSB1, /dev/ttyUSB2, or /dev/ttyACM0, depending on how the driver enumerates the composite USB device. The usb-serial driver handles most of them; option is the generic kernel module that covers the majority of WWAN USB serial endpoints.
Communicating with a modem from a shell script requires only a configured serial port and a way to write bytes to it:
exec 3<>/dev/ttyUSB2
stty -F /dev/ttyUSB2 9600 cs8 -cstopb -parenb raw
send_at() {
printf '%s\r\n' "AT$1" >&3
sleep 0.1
cat <&3
}
send_at 'I' # ATI: identify modem
send_at '+CIMI' # AT+CIMI: get IMSI
For more robust handling, tools like socat, minicom, or chat (from the ppp package) are commonly used. The chat utility in particular is designed for scripted AT command exchanges and handles response matching without requiring external expect dependencies.
One complication: ModemManager, the D-Bus daemon that manages modems in most modern Linux distributions, holds the AT ports open and will interfere with direct access. Scripts that talk directly to the modem usually need to either stop ModemManager temporarily, or use the mmcli command-line interface to put the modem into a state that permits the needed commands.
How You Figure Out What the Blob Was Sending
The approach for reverse-engineering what a proprietary binary sends over a serial port is straightforward. strace on the blob process reveals every write() system call to the modem’s file descriptor, showing the raw AT command strings. Alternatively, socat can be used to create a relay that logs all traffic:
socat -v /dev/ttyUSB2 PTY,link=/tmp/modem-pty
Point the blob at /tmp/modem-pty (by substituting the symlink or patching the path), and socat logs every byte in both directions to stderr. Running the binary once against this relay gives you the complete transcript of the authorization exchange.
For AT-command-based protocols, this transcript is usually enough. The commands are human-readable strings. The responses are structured text. Once you have the sequence, writing a bash equivalent is mechanical work.
This is the fundamental weakness of using a binary blob for something that ultimately speaks an open text protocol. The binary has no cryptographic advantage over the bash script if the modem’s AT interface is accessible. The obfuscation is shallow.
Blob Bit-Rot and Long-Term Maintenance
Beyond the philosophical issue of vendor lock-in, there is a practical maintenance problem with binary blobs that the bash script approach sidesteps entirely.
Binaries are compiled for specific architectures and kernel ABIs. A blob built for a glibc version from 2019 may fail silently on a modern distribution. Blobs distributed through vendor-specific package repositories get abandoned when the product reaches end-of-life. The Lenovo system-update and LVFS ecosystems have improved matters significantly compared to a decade ago, but modem-specific utility binaries have a poor track record of staying current.
A bash script that sends AT commands has no such dependencies. It runs on any architecture with a shell and a serial port. It breaks only if the AT command interface changes, which is far less common than distribution-level ABI churn. Users on Fedora, Debian, Arch, NixOS, and exotic ARM builds all run the same script without modification.
The Lenovo BIOS whitelist problem is a related but distinct issue. Earlier ThinkPad generations enforced a firmware-level whitelist that rejected any wireless card not on an approved list, requiring users to flash modified BIOSes via projects like 1vyrain or coreboot to remove the restriction. The WWAN blob is a software-layer analogue: same goal, different enforcement point. Both have been defeated by the community.
The Broader Pattern
This is not an isolated case. The Linux community has a long history of replacing vendor blobs with scripts or small open-source tools when the underlying protocol is accessible:
- NVIDIA’s persistence daemon was eventually replaced by proper kernel and X server integration after the community understood what it was doing.
- Various printer firmware blobs have been replaced once the underlying PostScript or PCL sequences were captured.
- libmbim and libqmi replaced vendor-provided data-connection managers for a large class of WWAN devices.
The pattern is consistent: a vendor ships a binary that speaks a documented or reverse-engineerable protocol, the community captures the protocol traffic, and a clean open implementation replaces the blob within months or years.
What slows this down is usually not protocol complexity. It is the time cost of setting up a capture environment and the tooling familiarity required to read strace output or interpret raw serial traffic. The Hofstede post compresses that work into a reusable script that others can read, modify, and package.
What This Actually Requires of Vendors
The argument for shipping a blob for this kind of task is usually one of security or IP protection. Neither holds up well here. If the modem’s authorization can be replicated by a bash script, the security model was never strong. If the IP being protected is a handful of AT command strings, that IP was already exposed the moment any user ran strace.
The better path would have been either an open specification for the host-to-modem handshake, so any OS or distribution could implement it natively, or a keystore-backed mechanism that does not rely on the AT command interface at all. What shipped instead was a closed binary that provided the appearance of a security mechanism without the substance of one.
A 100-line bash script is not an impressive technical artifact. The interesting part is that it was sufficient.