blog/09-microcorruption

09 Microcorruption - Santa Cruz

Santa Cruz is a really nice place at the coast in California, with a great beer festival. But according to the map on microcorruption this is the Santa Cruz on the Spanish vacation island Tenerife

Manual
Lockitall                                            LOCKIT PRO r b.05
______________________________________________________________________

              User Manual: Lockitall LockIT Pro, rev b.05
______________________________________________________________________


OVERVIEW

    - A firmware update further rejects passwords which are too long.
    - This lock is attached the the LockIT Pro HSM-1.

DETAILS

    The LockIT Pro b.05  is the first of a new series  of locks. It is
    controlled by a  MSP430 microcontroller, and is  the most advanced
    MCU-controlled lock available on the  market. The MSP430 is a very
    low-power device which allows the LockIT  Pro to run in almost any
    environment.

    The  LockIT  Pro   contains  a  Bluetooth  chip   allowing  it  to
    communiciate with the  LockIT Pro App, allowing the  LockIT Pro to
    be inaccessable from the exterior of the building.

    There  is no  default  password  on the  LockIT  Pro HSM-1.   Upon
    receiving the  LockIT Pro,  a new  password must  be set  by first
    connecting the LockitPRO HSM to  output port two, connecting it to
    the LockIT Pro App, and entering a new password when prompted, and
    then restarting the LockIT Pro using the red button on the back.

    LockIT Pro Hardware  Security Module 1 stores  the login password,
    ensuring users  can not access  the password through  other means.
    The LockIT Pro  can send the LockIT Pro HSM-1  a password, and the
    HSM will  return if the password  is correct by setting  a flag in
    memory.

    This is Hardware  Version B.  It contains  the Bluetooth connector
    built in, and two available  ports: the LockIT Pro Deadbolt should
    be  connected to  port  1,  and the  LockIT  Pro  HSM-1 should  be
    connected to port 2.

    This is Software Revision 05.  We have added further mechanisms to
    verify that passwords which are too long will be rejected.


(c) 2013 LOCKITALL                                            Page 1/1

This lock again uses the HSM-1, and should reject passwords that are too long

Enumeration

Let’s see if they do that better this time, we start with main

Main

int main(int argc, char **argv, char **envp);
0x4438      add   #0xffce, sp
0x443c      call  #login

This is new, main now does some stack allocation, 0x4400 + 0xffce = 0x143ce, so we end up with the stack pointer having the value 0x43ce, and the call instruction subtracting 2 from it, SP = 0x43cc when we jump to login

Login

login is quite long in this lock, fortunately we know how to eat an Elephant

╭ login();
0x4550      push  r11
0x4552      push  r4

Classic saving of registers that we want to restore before we return from this function, the push instruction like the call subtract 2 from the stack pointer, so we end up with SP = 0x43c8

0x4554      mov   sp, r4
0x4556      add   #4, r4
0x4558      add   #0xffd8, sp

The current stack pointer is now saved in R4 and then 4 is added to it, restoring the initial value from the start of login (R4 = 0x43cc). Then 40 bytes is allocated on the stack, 0x43c8 + 0xffd8 = 0x143a0 giving us a stack pointer with the value 0x43a0

0x455c      clr.b 0xfffa(r4)
0x4560      mov.b #8, 0xffe7(r4)
0x4564      mov.b #0x0010, 0xffe8(r4)

Now some values are set in memory using offset on R4. Remember that R4 has the value 0x43cc, we can then calculate the offset where we are writing to memory:

  1. 0xfffa + R4 󰾞 0x43c6
  2. 0xffe7 + R4 󰾞 0x43b3
  3. 0xffe8 + R4 󰾞 0x43b4

This is interesting, because the address are all between the address in R4 and the SP, if we try to visualize the memory map, we get something like below

- offset - 0 1  2 3  4 5  6 7  8 9  A B  C D  E F
  0x43a0  SP.. .... .... .... .... .... .... ....
  0x43b0  .... ..08 10.. .... .... .... .... ....
  0x43c0  .... .... .... 00.. .... .... R4

Remember that the stack grows downwards, meaning that stack pointer is decremented when something is pushed to the stack.

0x456a      mov   #0x4484, r15
0x456e      call  #puts
0x4572      mov   #0x44b9, r15
0x4576      call  #puts
0x457a      mov   #0x44e9, r15
0x457e      call  #puts

This looks familiar, three calls to print strings, printing the strings

This is new, they now want a username and a password, and both have some length rules

0x4582      mov   #0x0063, r14
0x4586      mov   #0x2404, r15
0x458a      call  #getsn

Next we have the username input, reading 0x63 (99) bytes from the user into address 0x2404 That is a lot of bytes for a maximum of 16 characters username

0x458e      mov   #0x2404, r15
0x4592      call  #puts

Another print of a string, but this is the address of where our input was written, so we are just printing whatever we input

0x4596      mov   #0x2404, r14
0x459a      mov   r4, r15
0x459c      add   #0xffd6, r15
0x45a0      call  #strcpy

Then our input is copied to 0x43cc + 0xffd6 󰾞 0x43a2, using strcpy, this is two bytes from the start of our 40 byte stack that was allocated at the start of login, we can do another visualization of our stack with the username

- offset - 0 1  2 3  4 5  6 7  8 9  A B  C D  E F
  0x43a0  SP.. UUUU UUUU UUUU UUUU UUUU UUUU UUUU
  0x43b0  UUUU ..08 10.. .... .... .... .... ....
  0x43c0  .... .... .... 00.. .... .... R4

If we follow the rules the 16 byte maximum username would we stored where we see the U’s in the stack memory, leaving out the null byte to indicate the end of the string

0x45a4      mov   #0x4505, r15
0x45a8      call  #puts

Now the following string is printed

I would expect another getsn call

0x45ac      mov   #0x0063, r14
0x45b0      mov   #0x2404, r15
0x45b4      call  #getsn

As expected, another reading of input, identical to the reading of the username input (99 bytes at 0x2404)

0x45b8      mov   #0x2404, r15
0x45bc      call  #puts

And this input is also printed 🙃 the password that we give is printed, that seems … “smart”

0x45c0      mov   r4, r11
0x45c2      add   #0xffe9, r11
0x45c6      mov   #0x2404, r14
0x45ca      mov   r11, r15
0x45cc      call  #strcpy

It seems a bit cryptic, but the password input is also copied to the stack using strcpy. R4 still contains the end of our stack 0x43cc, giving us the following sequence of operation

  1. R11 = 0x43cc
  2. R11 = 0x43cc + 0xffe9 󰾞 0x43b5
  3. R14 = 0x2404
  4. R15 = 0x43b5

Leaving the input for strcpy in R14 and R15, copying our password input to 0x43b5 which is on our stack

- offset - 0 1  2 3  4 5  6 7  8 9  A B  C D  E F
  0x43a0  SP.. UUUU UUUU UUUU UUUU UUUU UUUU UUUU
  0x43b0  UUUU ..08 10PP PPPP PPPP PPPP PPPP PPPP
  0x43c0  PPPP PPPP PP.. 00.. .... .... R4

We can illustrate this too, here using P’s if we follow the rules.

So far no checks has been done on our input. With both the username and password we can input 99 bytes, so we can for sure overwrite a lot of things if we avoid null bytes. It does however look like our password input will overwrite our username input if it is too long.

0x45d0      mov   r11, r15
0x45d2      mov   r4, r14
0x45d4      add   #0xffe8, r14
│       ╭─> 0x45d8      inc   r14
│       ╎   0x45da      tst.b 0x0(r14)
│       ╰─< 0x45de      jnz   $-0x0006

Moving on, R15 still contains the address of where we just copied the password to, this value is moved into R11. R14 gets the end of our stack from R4, and then the value 0xffe8 is added.

R14 = R14 + 0xffe8 󰾞 0x43b4 this is one byte before the password, then we count R14 up in a loop, and test if it contains a null byte. After this loop R14 will contain the end address of our password.

0x45e0      mov   r14, r11
0x45e2      sub   r15, r11
0x45e4      mov.b 0xffe8(r4), r15
0x45e8      sxt   r15
0x45ea      cmp   r15, r11
│       ╭─< 0x45ec      jnc   $+0x000e
│       │   0x45ee      mov   &0x2400, r15
│       │   0x45f2      call  #puts
│       │   0x45f6      br    #__stop_progExec__

The password end address is moved into R11, and the R15 containing the start address of the password is subtracted, resulting in the length of the password in R11.

0xffe8 + R4 󰾞 0x43b4, this is the address where the values 0x10 was written in the start of login, so R15 = 0x10 (16 byte maximum length of our username and password input). We then compare R15 with R11 essentially doing 0x10 == password length, and then we jump if R11 was lower than R15 meaning if the password was shorter than 0x10

We can see that if we do not jump we exit the program. So here we actually have a length check of the password, and exit if the password is longer than the 16 bytes.

│       ╰─> 0x45fa      mov.b 0xffe7(r4), r15
0x45fe      sxt   r15
0x4600      cmp   r15, r11
│       ╭─< 0x4602      jc    $+0x000e
│       │   0x4604      mov   &0x2402, r15
│       │   0x4608      call  #puts
│       │   0x460c      br    #__stop_progExec__

Now the byte before the 0x10 is moved into R15, here we have the value 0x08, so R15 = 0x08 (the minimum length of the password). And the password length it now compares against this value, and we jump if the length is larger than the 0x08.

So the password is verified to be between 8 and 16 characters long

│       ╰─> 0x4610      clr.b 0xffd4(r4)
0x4614      mov   #0xffd4, r15
0x4618      add   r4, r15
0x461a      push  r15

If we successfully passed the two length checks we get to here. The address R4 + 0xffd4 󰾞 0x43a0 is cleared, and then moved into R15 with the next two instructions. This address is at the top of our stack, before where the username is saved. This address is then pushed to the stack

0x461c      mov   r4, r15
0x461e      add   #0xffe9, r15
0x4622      push  r15

Now we move the value 0x43cc + 0xffe9 󰾞 0x43b5 into R15 and push it to the stack. This address is where our password starts

0x4624      add   #0xffed, r15
0x4628      push  r15
0x462a      push  #0x007d
0x462e      call  #INT

The value 0x43b5 + 0xffed 󰾞 0x43a2 is now also pushed to the stack, this is the address of where our username starts. And then the interrupt 0x7d is pushed to the stack before the call to INT.

The interrupt 0x7d is for the HSM-1, so all this pushing to the stack is to prepare the input for the HSM-1, to validate the username and password, and store the result in the byte before our username input

0x4632      add   #8, sp
0x4634      tst.b 0xffd4(r4)
│       ╭─< 0x4638      jeq   $+0x000c
│       │   0x463a      call  #unlock_door
│       │   0x463e      mov   #0x4521, r15
│      ╭──< 0x4642      jmp   $+0x0006
│      │╰─> 0x4644      mov   #0x4531, r15
│      ╰──> 0x4648      call  #puts

This part checks the result of the HSM-1 check. If success, the unlock_door is called and a string is prepared to be printed, if it failed, we skip the call to unlock_door and a different string is prepared

0x464c      tst.b 0xfffa(r4)
│       ╭─< 0x4650      jeq   $+0x000e
│       │   0x4652      mov   &0x2400, r15
│       │   0x4656      call  #puts
│       │   0x465a      br    #__stop_progExec__

Here the address 0x43cc + 0xfffa 󰾞 0x43c6 is checked, this is the address that was cleared in the start of login. If it is not zero the program print a string and exits.

│       ╰─> 0x465e      add   #0x0028, sp
0x4662      pop   r4
0x4664      pop   r11
0x4666      ret

Usual stack clean up and return

Exploit

Let’s try to map out the stack memory our input is ending up in

- offset - 0 1  2 3  4 5  6 7  8 9  A B  C D  E F
  0x43a0  .... UUUU UUUU UUUU UUUU UUUU UUUU UUUU
  0x43b0  UUUU ..08 10PP PPPP PPPP PPPP PPPP PPPP
  0x43c0  PPPP PPPP PP.. 00.. .... .... 4044 ....

The 0x08 is the minimum length of the password and the 010 is the max length, only the password length is checked. So we can actually overwrite the whole memory with the username input. This means we can also change the minimum and maximum length that the password is checked against.

If we use the username to overwrite the minimum and maximum values, plus the return address, we can make the login function return to an address we decide.

But the null byte at 0x43c6 we cannot avoid overwriting, so our username payload will make the program exit before login returns to our address

This can be fixed using the password input, if we make the password 17 bytes, the strcpy function will write a null byte at 0x43c6, and then we can avoid that the program exits before returning to our address.

So far so good, where do we want to return to?

With this lock we have the unlock_door function, so let’s just use that as in the old days

Door unlocked

/microcorruption/