One of my former coworkers pointed me to Microcorruption, which is an online (untimed) embedded CTF. How fun!
I started this earlier in the fall, before some travel, and am now returning to it. I’m going to document my solutions for each one, and hopefully complete the CTF. 🙂
What’s this all about?
If you open up the website, you’re greeted with this prompt:
Who wouldn’t want some Cy Yombinator bonds, amirite? Each level requires that you find an exploit in increasingly-more-secure Bluetooth locks, as emulated on the website.
Get signed up for an account, walk through the tutorial, and then click the New Orleans dot on the map to get started.
The Challenge
If you’ve gone through the tutorial, some of this will look familiar. When we first get to the New Orleans challenge, we see some release notes for this particular type of lock.
Okay, so there’s no default password (like in the tutorial? I don’t remember).
In the Disassembly window, scroll down until you get to the main
function.
Broadly speaking, this program is going to:Â Â
- Set a password for the device
- Ask us to enter a password
- Check it
- If it’s invalid, we get rejected
- Otherwise, if it’s good, the door is unlocked.
If we run the program once through to see how it works by typing c
in the debugger console (to “continue”):
After you hit enter, you should see a password prompt. Let’s guess at the password, with some Midwesterner input:
We now see that the program has stopped here:
So, we type c
again in the debugger console, and see that our password was, sadly, invalid.
If you’ve used Microcorruption or a similar program/platform before, this was all obvious to you. I included it for people who are brand new, rusty, etc.
New Orleans Solution
So, ideas on how to get a valid password?
If we take a deeper look at that create_password
function, maybe we’ll see something interesting…
Let’s set a breakpoint there by clicking on that line of assembly (such that it turns the styling blue).
If you’re unfamiliar with software breakpoints, check out the Wiki page on it. Tl;dr: it’s a way of pausing program execution so we can get a better look at what’s going on.
Assuming you’ve already reset execution (by typing reset
into the debugger console and hitting enter), we can now type c
again, to run (continue) the program from the beginning, up until our breakpoint.
You’ll know your breakpoint worked because the text will now be red, in addition to the blue highlighting:
The call <create_password>
means that this line of assembly calls the create_password
function. Pretty straightforward… let’s take a look at that function.
To do so, from our current breakpoint status, we need to step into the function. Type s
(for “step”) in your debugger console, and you should see the disassembly window update to show that the program is now at the first step of the create_password
function.
We see a mov
line, followed by a bunch of mov.b
lines.
447e <create_password>
447e: 3f40 0024 mov #0x2400, r15
4482: ff40 6100 0000 mov.b #0x61, 0x0(r15)
4488: ff40 4a00 0100 mov.b #0x4a, 0x1(r15)
448e: ff40 7400 0200 mov.b #0x74, 0x2(r15)
4494: ff40 6500 0300 mov.b #0x65, 0x3(r15)
449a: ff40 2600 0400 mov.b #0x26, 0x4(r15)
44a0: ff40 4000 0500 mov.b #0x40, 0x5(r15)
44a6: ff40 2c00 0600 mov.b #0x2c, 0x6(r15)
44ac: cf43 0700 mov.b #0x0, 0x7(r15)
44b0: 3041 ret
According to the manual (page 13), mov
is:
So r15 is set to 0x2400, which is a location in memory (which you can see in the “Live Memory Dump” view). Then, we move some chars into that location, one at a time.
If we repeated step (using s
) through, we see:
We’ve written “aJte&@,” to memory location 0x2400. If you take the individual bytes (i.e. “61 4a 74 65 26 40 2c”) and convert them to ASCII, you get the same result.
Let’s hold onto “aJte&@,” and see if we need it later.
If we set a breakpoint at the check_password function (break check_password
) or by clicking on the first line of the function, and then continue (c
):
44bc <check_password>
44bc: 0e43 clr r14
44be: 0d4f mov r15, r13
44c0: 0d5e add r14, r13
44c2: ee9d 0024 cmp.b @r13, 0x2400(r14)
44c6: 0520 jne #0x44d2 <check_password+0x16>
44c8: 1e53 inc r14
44ca: 3e92 cmp #0x8, r14
44cc: f823 jne #0x44be <check_password+0x2>
44ce: 1f43 mov #0x1, r15
44d0: 3041 ret
44d2: 0f43 clr r15
44d4: 3041 ret
We can step through and see that we’re comparing the password we entered against a location in memory (0x2400), one byte at a time. This location is the same one we just wrote to!
More specifically: the password we entered during the prompt was written to location 0x439c. This is the value of r13 in this function.
Then, we look at the byte at the location of r13 (so the byte at 0x439c) and compare it to the r14-th byte of memory location 0x2400. So, for the first time through, we’re looking at the 0th byte, which is “a”.
44c2: ee9d 0024 cmp.b @r13, 0x2400(r14)
If it doesn’t match (not equal), we will jump to the 0x44d2 and return from the function. If it does match, we’ll increment r14 such that we’re looking at the next byte of 0x2400, and then loop back to the start of the function and compare again. We’re also seeing if r14 is less than 0x8.
We’ll bail out of this loop as soon as we’ve got an non-matching byte. If all is well, we set r15 to 1, indicating success, and return.
If you want to, you could test this out by having partial matches of the password, and watch as you get kicked out of the byte-compare loop. Or, you could reset the debugger, enter in the correct password, and watch as the lock is opened.
In other words: reset
, then c
to continue, enter in “aJte&@,” and then c
again.
TL; DR
The create_password
function spells out the correct password for us. We just have to grab those bytes, and enter it in when prompted. Â
To solve this level, you must first type solve
before entering in the correct password of “aJte&@,".
Ta da!