Microcorruption: Bangalore

Thu 13 July 2023

The Scenario

I got to relax and celebrate escaping Putin's clutches once again while on my way to India, but once I touched down in India it was time for business. I found myself staring down revision e.01 of the Lockitall LockIT Pro, equipped with a LockIT Pro HSM-2 like in Vladivostok. [1] Unlike the last lock, whose manual only hinted at the new security feature, this version's manual is loud and proud about supporting DEP. The Lockitall devs had earned my respect with their last lock, but could they keep it? I was eager to know myself.

Understanding The Code

Like many of the previous lock firmwares main() is a stub function using set_up_protection() to enable memory protection before entering the core loop in login(). set_up_protection() does what it says and enables DEP, marking the addresses 0x0000-0x00FF and 0x4400-0xFFFF executable and 0x0100-0x43FF writable before letting control move to login(). My first thought was to disable DEP and proceed with my normal shenanigans, but the manual did not indicate a way DEP could be disabled so I could do nothing but accept the code I cannot change; the stack was still writable but unless I figured out how to make it executable that wasn't worth much.

A screenshot of the disassembly of the functions related to configuring and enabling DEP

Since the HSM was supposed to unlock the door the firmware had no unlocking function I could call myself; surprisingly the firmware also lacked an interrupt function I could hijack with some stack grooming. [2] Thankfully my testing showed the address login() returns to is right after the buffer to store the password and login() informed me of the maximum password length so I knew I could kick things off by controlling the program counter.

Exploiting The Code

Obviously I began by checking if DEP was implemented correctly - unfortunately it was and my attempt to return into the stack directly failed miserably. At this point I knew I needed the ability to execute my own code, and after banging my head against a wall for half an hour I figured out babby's first ROP chain to make a stack page executable and return into it.

At this point I was sitting pretty with the program counter deep in what used to be non-executable address space; I had 18 bytes for shellcode but no idea how to trigger the lock - this is where the lack of an interrupt or door unlocking function was really apparent - but the firmware still made syscalls somehow so I started looking more closely. This is when I noticed something: The interrupt for the gets() syscall is 0x02, and the gets() function moves 0x8200 into the status register; the interrupt for the DEP marking syscall is 0x11, and the mark_page_writable() and mark_page_executable() functions move 0x9100 into the status register - it seems like you set the interrupt for syscalls by making the most significant byte of the status register 0x80 ORed with the interrupt number. Never one to waste a good pattern, I tried doing the same with the interrupt to trigger the lock; once again the Yombinator bonds were under new ownership.

As I decompressed on a plane bound for Nigeria I spared a thought for how the Lockitall devs have really stepped their game up with the modern exploit mitigations - if they could get their shit together on the basics and cut out the buffer overflows they might get somewhere. But they weren't there yet, so I reclined my seat and relaxed knowing I can likely tackle anything they have lying ahead for me.

[1]Or maybe it's revision c.01? The lock's manual uses both revision indicators twice but revision c.01 was previously used in Whitehorse so I'm guessing the engineers took over for the technical writers this time
[2]Re-reviewing the lock firmware for this post, there's no call to the HSM at all; I don't know how the engineers missed how you couldn't unlock the lock even with the correct password