Fun with Assembly 2: Backdoors and Master Passwords

Posted by Hywel Carver on January 10, 2015

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.

The setup

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.

Diving in

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>

So, 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 check_password shows:

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.

Congratulations!

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.