04 Microcorruption - Cusco
Next destination is Cusco
Manual
Lockitall LOCKIT PRO r b.02
______________________________________________________________________
User Manual: Lockitall LockIT Pro, rev b.02
______________________________________________________________________
OVERVIEW
- We have fixed issues with passwords which may be too long.
- This lock is attached the the LockIT Pro HSM-1.
DETAILS
The LockIT Pro b.02 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 02. We have improved the security of the
lock by removing a conditional flag that could accidentally get
set by passwords that were too long.
(c) 2013 LOCKITALL Page 1/1
Now the claim to have fixed the previous vulnerability, but still using the same external hardware module. So still no passwords in the code.
Enumeration
Once again, let’s check the code for bugs that we might be able to use
Main
We start in main
╭ int main(int argc, char **argv, char **envp);
╰ 0x4438 call #login
Surprisingly this time main
is even shorter than last time
login
╭ login();
│ 0x4500 add #0xfff0, sp
This time login
create some room on the stack, let try to find out how much.
From the __init_stack
in the prologue the stack pointer is set to 0x4400
, and nothing else is touching SP
before this line in login
, or is there?
The only instruction in main
is a call
instruction, but what exactly does the call instruction do?
Reading the MSP430 User manual1 we can read what call
does.
- The destination (here the address of
login
) is stored temporarily - 2 is subtracted from the stack pointer
- The Program Counter is written to the address pointed to by the stack pointer
- The destination address is the written to the Program Counter
Spelled out a bit more, the stack pointer is moved 2 bytes to make room for the address that comes after the call
instruction. The point of this is to save the return address, meaning the address that the called function needs to return to when it is reach its end. To do the actual jump, the address of the function is moved into the program counter. The program counter holds the address of the next instruction to execute and is incremented every time. So by writing to the program counter makes the program jump.
Back to the stack pointer. It is initialized to 0x4400
then the call instruction subtract 2 and writes the return address.
Address Stack Content
+---------+ Lower memory addresses
| ... |
+---------+
0x43FC | 0x0000 |
+---------+
0x43FE | 0x3C44 | (SP-2) after the call instruction
+---------+
0x4400 | 0x3140 | Initial SP
+---------+
0x4402 | 0x0044 |
+---------+
+---------+ Higher memory addresses
The above is an illustration of the stack after the call login
instruction, and now we finally come to the first instruction of login
where we add 0xFFF0
to SP
, so 0xFFF0 + 0x43FE = 0x143EE
we have seen this before, the calculation overflows, so the SP
will end up with the truncated value 0x43EE
, we could have gotten here by subtracting 0x10
or 16 from the stack pointer, the new model of the stack now looks like this
Address Stack Content
+---------+ Lower memory addresses
0x43EE | 0x0000 | (SP-18) after login moves SP
0x43F0 | 0x0000 |
0x43F2 | 0x0000 |
0x43F4 | 0x0000 |
0x43F6 | 0x0000 |
0x43F8 | 0x0000 |
0x43FA | 0x0000 |
0x43FC | 0x0000 |
+---------+
0x43FE | 0x3C44 | (SP-2) after the call instruction in main
+---------+
0x4400 | 0x3140 | Initial SP
+---------+
+---------+ Higher memory addresses
This is a very common way to work with the stack, when a call to a function is made, the return address is pushed to the stack, for the called function to know where to return to, then at the start of the called function, it will make room on the stack for local variables. At the end of the function it will move the stack pointer back to where it started, then the ret
will pop the return address of the stack and set the program counter to that address. This is called a stack frame, the idea is that each function has its own stack frame for local variables. For more info start here Call Stack
Back to login
│ 0x4504 mov #0x447c, r15
│ ; "Enter the password to continue."
│ 0x4508 call #puts
│ 0x450c mov #0x449c, r15
│ 0x4510 call #puts
│ ; "Remember: passwords are between 8 and 16 characters."
│ 0x4514 mov #0x0030, r14
│ 0x4518 mov sp, r15
│ 0x451a call #getsn
This looks familiar, we print some text and then asks for the password, no magic here. Notice that we are asking for maximum password length of 16 bytes, which fix exactly into the free space we just created on the stack. If we look closely, we see that two arguments are prepared for getsn
, R15
is where to save the input data, R14
is how many bytes to read from the user. R14
is set to the value 0x30
, but we only have space on the stack for 0x10
bytes. So the getsn
will read 32 bytes more that we actually have room for in the stack frame.
│ 0x451e mov sp, r15
│ 0x4520 call #test_password_valid
│ 0x4524 tst r15
And as in Hanoi we call the test_password_valid
on our password buffer. And test the result.
│ ╭─< 0x4526 jeq $+0x000c
│ │ 0x4528 call #unlock_door
│ │ 0x452c mov #0x44d1, r15
│ ╭──< 0x4530 jmp $+0x0006
│ │╰─> 0x4532 mov #0x44e1, r15
│ ╰──> 0x4536 call #puts
The code now branches, if the password is incorrect, we jump, put the address of the string That password is not correct.
into R15
and call puts
on it.
If the password is correct, unlock_door
is called and the address of the string Access granted.
is moved into R15
and puts
is called.
The vulnerable compare from before is gone, so we cannot exploit that, and we don’t know the password. So they did actually fix the vulnerability from before.
│ 0x453a add #0x0010, sp
╰ 0x453e ret
At the end we see that we are now adding 0x10
to the stack pointer. We are now “cleaning” up the stack frame. We then call ret
, this will move the value on the stack into the program counter, and then add 2 to the stack pointer, hence successfully returning to main
or are we?
Exploit
It’s a bit hard to solve this without knowing anything about buffer overflows beforehand, but if you play around with the password input in the online debugger, and maybe violate the rule about maximum 16 byte password length, after all the getsn
function is instructed to read 48 bytes of password data. You will notice that you start to overwrite the data that comes after the “allocated” stack frame.
Address Stack Content
+---------+ Lower memory addresses
0x43EE | 0x0000 | (SP-18) after login moves SP
0x43F0 | 0x0000 |
0x43F2 | 0x0000 |
0x43F4 | 0x0000 |
0x43F6 | 0x0000 |
0x43F8 | 0x0000 |
0x43FA | 0x0000 |
0x43FC | 0x0000 |
+---------+
0x43FE | 0x3C44 | (SP-2) after the call instruction in main
+---------+
0x4400 | 0x3140 | Initial SP
+---------+
+---------+ Higher memory addresses
Recall our stack model here. The password we input will be stored on the stack, starting at address 0x43EE
going down. We only have 16 bytes of unused space, so byte 17 and 18 will actually overwrite the value in 0x43FE
.
This value is quite interesting, because this is actually the address from where the program will begin to execute code when it returns from login
. So we can create a payload/password that will make the program jump to where ever we want and execute code there, sounds awesome.
But where do we want it to jump to? The unlock_door
of course. So we create our payload 16 ‘A’ and then the address of the unlock_door
function ‘0x4446’
When using this payload, the online debugger actually tells us that the __init_stack
get overwritten, this is because a null byte is added to our input, so even tho our input is only 18 bytes and should only overwrite the return address, we actually end up having a 0x00
at the 19 byte position, and that is at address 0x4400