This is the second in a series of articles about assembly language. If you’re reading this, you should make sure you already understand how Assembly, CPUs, RAM and registers work.
This post is going to be based around the New Orleans level of microcorruption.com. You can login there and follow along if you like, but I’ll be explaining everything in this post too.
The idea is that we have a test copy of a digital lock and we’re trying to find some input to it that will make it open, even though we haven’t been given the key (in this case, a password). We have the copy of the lock, access to disassembled code, the current state of memory, the CPU’s registers, the output and a debugger.
Now with the debugger we could easily get it to run whatever code we want and open the lock. But that doesn’t really help, because we also need to be able to use our exploit on a lock in the real world that we don’t have privileged access to. We need some input that will open the lock, even though we haven’t been told the password.
The first thing to look at is the main function, to see what the lock is going to do when it runs. It starts with this:
4438 <main> 4438: 3150 9cff add #0xff9c, sp 443c: b012 7e44 call #0x447e <create_password> 4440: 3f40 e444 mov #0x44e4 "Enter the password to continue", r15 4444: b012 9445 call #0x4594 <puts> 4448: 0f41 mov sp, r15 444a: b012 b244 call #0x44b2 <get_password> 444e: 0f41 mov sp, r15 4450: b012 bc44 call #0x44bc <check_password>
main makes some room on the stack, calls a function called
create_password, asks the user to put in their password, gets the password the user types, and then checks if it the password was right.
We’re not going to look at the rest of
main for now. Instead, let’s look at what
create_password is doing:
447e <create_password> 447e: 3f40 0024 mov #0x2400, r15 4482: ff40 6a00 0000 mov.b #0x6a, 0x0(r15) 4488: ff40 2100 0100 mov.b #0x21, 0x1(r15) 448e: ff40 4000 0200 mov.b #0x40, 0x2(r15) 4494: ff40 5600 0300 mov.b #0x56, 0x3(r15) 449a: ff40 4e00 0400 mov.b #0x4e, 0x4(r15) 44a0: ff40 2f00 0500 mov.b #0x2f, 0x5(r15) 44a6: ff40 2900 0600 mov.b #0x29, 0x6(r15) 44ac: cf43 0700 mov.b #0x0, 0x7(r15) 44b0: 3041 ret
Well there must be something special about the address
0x2400. This function moves that address into register 15 then stores 7 specific bytes there, followed by a null
0x0 byte. That sounds a lot like a 7-character string: each character in a string is 1 byte, and they always end in a null byte. This function is called
create_password, so those 7 characters are almost certainly a fixed, hard-coded password.
A quick look at
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 could work through this, but line
0x44c2 stands out: that line is comparing two things, one of which is related to the address
0x2400, which is where
create_password was storing what it generated. So it’s probably comparing the input we entered with the string from
create_password. At this point, you should be pretty sure that the hunch was right, and that the string is the password.
If you put a breakpoint on the line after
create_password is called from
main (which is
0x4440), and run the code to there, you can look at the contents of the magic location where our seven character password is expected to be,
0x2400. I see
j!@VN/) but it might be different for you. That’s our password.
Is this realistic?
This was a quick and easy lock to hack into, because it had a saved, fixed password. Surely that would never happen in the real world?! Actually, it does happen often. Maybe not quite as simply as this, but a fixed password for gaining access to a system is normally called a backdoor.
Here’s a list of consumer devices with backdoors. It includes all Android and iOS phones, Windows, Samsung phones, Amazon Kindles, routers, etc. It might even be possible to build backdoors right into CPUs.
Most of the time a backdoor is written into software, it’s to prevent illegal or unlicensed use. For example, the Orwellian time Amazon covertly removed 1984 from people’s Kindles. The problem with backdoors is that they are intended for one company’s use but, once one exists, malicious hackers and other nefarious organisations will find out about the backdoor and start exploiting it for their own ends. Backdoors in the real world are harder to trigger than the one we broke into, so only the most dedicated hackers will be able to abuse them.
You just cracked your first lock, and used knowledge of disassembly to find hidden capabilities in software! In the next part, we’ll learn more assembly and solve a more complex lock.
Thanks to Tom Carver for reading a draft of this post.