🔑The license was tied to the motherboard

Contents

A hospital’s nurse-call system was dying. Not the software — the hardware. A single physical server from around 2010, running Debian 6, quietly routing every call button, wall panel and pager in the building. The disks were old, the box was old, and when hardware that old fails it doesn’t ask permission.

The plan was the obvious one: pull a full image of the disk, turn the physical machine into a virtual one (a P2V conversion), and run it on modern hardware under KVM. Get it off the failing iron, keep it alive indefinitely. Standard rescue work.

I imaged the disk, built the VM, booted it. Everything came up — Asterisk, the telephony stack, the lot. Except the nurse-call application itself, which refused to start. No useful error. Just… no.

Following the refusal

The application was Java, so I pulled it apart with a decompiler and started reading. Buried in the startup path was a license check — and the way it worked stopped me for a second, because it was actually clever.

The app shelled out to:

dmidecode -s system-uuid

dmidecode reads the machine’s DMI/SMBIOS tables — the firmware-level description of the physical hardware. system-uuid is a unique identifier baked into the motherboard. The app took that UUID and used it as the key to decrypt its license file. Get the right UUID, the license decrypts, the software runs. Wrong UUID, the decryption produces garbage, and the app bails.

It’s a tidy scheme: the license is mathematically bound to one specific motherboard. You can copy the software anywhere you like, but it only comes to life on the machine it was sold for. No phone-home, no license server, nothing to crack in the traditional sense — the hardware is the key.

Which is exactly why my VM was dead in the water. The moment I virtualized the box, the motherboard it expected no longer existed. The hypervisor handed the guest a brand-new, randomly-generated system UUID. Wrong key. Garbage license. Silent failure.

The one piece of luck: I’d read the original UUID straight off the physical machine — dmidecode -s system-uuid — while it was still alive and I was imaging it. So I had the magic number. The only question was how to make the VM present it.

The fix is dumber than the lock

The clean way would be to make the VM genuinely claim the original UUID. KVM lets you set the guest’s SMBIOS system UUID in the domain XML, so in principle you pin the recovered value there and dmidecode inside the guest reports the right thing all on its own.

That would have worked. But it’s also the boring answer — pin a config value, reboot, done. And the more I looked at how the check actually worked, the more a far more fun route opened up.

Because the application doesn’t read the motherboard. It reads the output of a command named dmidecode. It has no idea where that binary lives or what it really does — it just trusts whatever comes back on stdout. So why fight QEMU’s SMBIOS tables at all, when I can just lie to the one program that’s asking?

mv /usr/bin/dmidecode /usr/bin/dmidecode.bak

cat > /usr/bin/dmidecode <<'EOF'
#!/bin/bash
if [ "$1" = "-s" ] && [ "$2" = "system-uuid" ]; then
    echo "THE-ORIGINAL-SYSTEM-UUID"
else
    exec /usr/bin/dmidecode.bak "$@"
fi
EOF

chmod +x /usr/bin/dmidecode

A tiny shell script that stands in front of the real binary. The original is parked next to it with a .bak suffix, and the shim only tells one lie: when something asks for -s system-uuid — exactly what the license check calls — it returns the blessed UUID. Every other invocation is passed straight through to the genuine dmidecode with its arguments intact, so anything else on the box that legitimately needs DMI data still gets real answers. The license decrypts, the nurse-call software starts like nothing ever happened, and the rest of the system is none the wiser.

The elaborate cryptographic hardware-binding scheme was defeated by a file with the same name sitting earlier in $PATH.

Why I like this one

There’s a lesson in here that keeps showing up in my work: a security mechanism is only as strong as its weakest assumption, and the weakest assumption is usually about trust, not math. The license encryption was genuinely fine. The decryption was fine. The fatal assumption was that the string returned by a command called dmidecode actually came from the hardware. On a box where I’m root, that assumption costs a few lines to break.

It’s the same shape as a lot of reverse-engineering: you don’t attack the strong part. You find the seam where the strong part talks to something it trusts, and you stand in the middle.

The nurse-call system has been humming along as a VM ever since — no failing disks, snapshot-able, restorable in minutes. Same decade-old software, same license, now running on hardware that won’t die on a Tuesday night. The motherboard it was married to is gone. It just doesn’t know it.