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:
0xfffa
+R4
0x43c6
0xffe7
+R4
0x43b3
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
0x4484
- “Authentication now requires a username and password.”0x44b9
- “Remember: both are between 8 and 16 characters.”0x44e9
- “Please enter your username:”
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
0x4505
- “Please enter your password:”
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
- R11 =
0x43cc
- R11 =
0x43cc
+0xffe9
0x43b5
- R14 =
0x2404
- 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
0x43a0
= SP and where the result of password check is saved0x43b3
=0x08
0x43b4
=0x10
0x43c6
=0x00
0x43cc
= return address frommain
- Our username input starts in
0x43a2
, and they expect a max of 16 bytes (symbolized with U’s) - Our password input starts in
0x43b5
, and they expect a max of 16 bytes (symbolized with P’s)
- 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