Reviving a BlackBerry KEY2 (and Finding a Bug in the Unlock Tool)
I put LineageOS on an old BlackBerry KEY2 from a Guix machine - patched binaries for a distro with no FHS, fought USBGuard, and found a real bug in the bootloader-unlock tool along the way.
I’ve had a BlackBerry KEY2 sitting in a drawer since it stopped getting updates. Physical keyboard, small screen, the kind of phone that ruins you for glass slabs - but the software underneath had been dead for years. This week I finally put LineageOS on it, with Claude Code doing most of the driving.
There’s a small community keeping this device alive (the Lineage-BBKey2 project, unofficial, actively maintained), and the overall recipe is well documented: unlock the bootloader with a tool called kibo that exploits a fastboot buffer overflow, reflash BlackBerry’s own signed firmware through their autoloader, then sideload LineageOS and GApps through recovery. None of that is new. What made it interesting is that I was doing it from a Guix machine, and Guix doesn’t play along with anything that expects a normal Linux filesystem.
Guix has no /lib64/ld-linux-x86-64.so.2
The obvious tools - Google’s platform-tools, kibo - are prebuilt Linux binaries that expect /lib64/ld-linux-x86-64.so.2 and a handful of libraries at fixed paths. None of that exists on Guix. Rather than reach for a container, patchelf fixes this directly:
$ patchelf --set-interpreter $GLIBC/lib/ld-linux-x86-64.so.2 \
--set-rpath "$GLIBC/lib:$LIBUSB/lib" kibo
Point the interpreter and rpath at the right store paths, and the binary runs natively - no FHS emulation, no container, full USB access. Worth doing over Guix’s own adb/fastboot package too, which is stuck at 7.1.2 from 2016; patching Google’s current 37.0.1 build instead meant sparse image splitting actually worked on a 3.2GB system.img against a 512MB per-transfer limit.
USBGuard doesn’t know what a BlackBerry is
The phone showed up on USB, fully enumerated, and fastboot still couldn’t see it. Turned out authorized_default on every root hub was 0, and USBGuard’s implicit policy is block-by-default; there was simply no rule for BlackBerry’s vendor ID. Fine for a laptop that shouldn’t trust random USB devices, less fine when the random USB device is the phone you’re trying to flash.
The annoying part: the phone re-enumerates under a new device ID at every single stage - MTP, fastboot, bootloader, recovery, sideload - so a one-off usbguard allow-device only gets you through the next reboot. The fix was a small loop watching for newly-blocked devices from the relevant vendor IDs and auto-allowing them, entirely through USBGuard’s IPC as a member of wheel, never touching the persisted policy file. Runtime-only, gone the moment you kill it.
kibo said the device wasn’t exploitable. It was lying.
This was the fun one. After flashing BlackBerry’s firmware twice (their A/B implementation is broken enough that the community instructions just say “do it twice”), kibo’s device check kept reporting one of the two boot-chains as INVALID and refusing to unlock - on a phone that had, by every other measure, been flashed correctly.
Rather than trust that, we wrote a tiny raw USB probe with pyusb to dump exactly what the device sends back for its info query. And there it was: kibo reads fixed-size response buffers off the wire and never null-terminates them, so its strcmp() calls occasionally read past the real bytes into whatever was left on the stack. One buffer happened to land next to a stray zero; the other didn’t. Deterministic, reproducible, and completely wrong about the device.
kibo is GPL and genuinely small - three source files. Pulled it, added three lines to null-terminate each response at its actual length, and rebuilt:
$ guix shell cmake make gcc-toolchain pkg-config libusb-next -- \
cmake -B build && cmake --build build
Second run: Your device is vulnerable. Which, for once, was exactly what I wanted to hear.
The result
Unlock went clean after that, LineageOS 22.2 and MindTheGapps sideloaded without a hitch, and the KEY2 booted into Android 15 on a Snapdragon 660 from 2018. The physical keyboard needed one more small fight - Gboard doesn’t reliably suppress itself for a hardware keyboard on this build - solved by switching to K12KB, the keyboard app the ROM ships for exactly this reason.
I’m not going to pretend it’s tidy. It’s a 2018 phone running an unofficial port, dual SIM has a known bug I sidestepped by only using one, and Google Wallet is a coin flip I decided not to chase. But the keyboard clicks like it’s new, and the phone that had been sitting in a drawer for two years is back in my pocket. Good luck :)