05 Microcorruption - Reykjavík
Let’s go home to the north, Reykjavík
Manual
Lockitall LOCKIT PRO r a.03
______________________________________________________________________
User Manual: Lockitall LockIT Pro, rev a.03
______________________________________________________________________
OVERVIEW
- Lockitall developers have implemented military-grade on-device
encryption to keep the password secure.
- This lock is not attached to any hardware security module.
DETAILS
The LockIT Pro a.03 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---upon receiving
the LockIT Pro, a new password must be set by connecting it to the
LockIT Pro App and entering a password when prompted, and then
restarting the LockIT Pro using the red button on the back.
This is Hardware Version A. It contains the Bluetooth connector
built in, and one available port to which the LockIT Pro Deadbolt
should be connected.
This is Software Revision 02. This release contains military-grade
encryption so users can be confident that the passwords they enter
can not be read from memory. We apologize for making it too easy
for the password to be recovered on prior versions. The engineers
responsible have been sacked.
(c) 2013 LOCKITALL Page 1/1
This lock is not connected to any external hardware, but they claim to have implemented a military-grade on-device encryption. This means that if we can crack the encryption, we would be able to retrieve the password
Enumeration
Let’s start from the beginning
Prologue
╭ __init_stack();
╰ 0x4400 mov #__init_stack, sp
__init_stack
sets the stack pointer as usually to 0x4400
╭ __low_level_init();
│ 0x4404 mov &0x015c, r5
│ 0x4408 and.b #-1, r5
╰ 0x440a bis #0x5a08, r5
__low_level_init
moves the data at address 0x015c
into R5
, there is nothing at this address, so it is just zero, then bitwise AND
operation is done with 0xff
yielding the same zero in R5
. The last instruction does a bitwise OR
operation with 0x5a08
, since R5
is zero the result of this is just the value 0x5a08
, so after __low_level_init
the result is that R5 = 0x5a08
╭ __do_copy_data();
│ 0x440e mov #0x007c, r15
│ 0x4412 tst r15
│ ╭─< 0x4414 jeq $+0x0010
│ ╭──> 0x4416 mov r5, &0x015c
│ ╎│ 0x441a decd r15
│ ╎│ 0x441c mov 0x4538(r15), 0x2400(r15)
╰ ╰──< 0x4422 jnz $-0x000c
__do_copy_data
is a loop, we set R15
to the value 0x7c
and check if it is zero. The value in R5
is now written to the address 0x015c
(from before this value is 0x5a08
). decd r15
does a double decrement of the R15
register, and it sets the status bits, this is essential our loop counter.
It looks like we are now copying two bytes from the address 0x4538 + R15
to the address 0x2400 + R15
, we keep decrementing R15
by two until it reach zero and the go through to __do_clear_bss
.
But let’s first look a bit on what exactly this code is copying. We start with R15 = 0x007c
, calculating the address gives us 0x45b4
and 0x247c
Source | Destination | |
---|---|---|
0x45b4 | → | 0x247c |
0x45b2 | → | 0x247a |
… | … | … |
0x4538 | → | 0x2400 |
This keeps looping until we reach 0x4538
and 0x2400
, let see what is stored at this address.
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0x4538 4c85 1bc5 80df e9bf 3864 2bc6 4277 62b8 L.......8d+.Bwb.
0x4548 c3ca d965 a40a c1a3 bbd1 a6ea b3eb 180f ...e............
0x4558 78af ea7e 5c8e c695 cb6f b8e9 333c 5aa1 x..~\....o..3<Z.
0x4568 5cee 906b d1aa a1c3 a986 8d14 08a5 a22c \..k...........,
0x4578 baa5 1957 192d abe1 66b9 028d 4a08 e95c ...W.-..f...J..\
0x4588 d919 8069 07a5 ef01 caa2 a30d f344 815e ...i.........D.^
0x4598 3e10 e765 2bc8 2837 abad ab3f 8cfa 754d >..e+.(7...?..uM
0x45a8 8ff0 b083 6b3e b3c7 aefe b409 ....k>......
This does not make too much sense, disassembling it does not give anything useful either. But the manual did state that they use encryption, where the goal is to make things look random, so let’s just note that this data is copied to address 0x2400
╭ __do_clear_bss();
│ ╰─> 0x4424 mov #0x0100, r15
│ 0x4428 tst r15
│ ╭─< 0x442a jeq $+0x000e
│ ╭──> 0x442c mov r5, &0x015c
│ ╎│ 0x4430 dec r15
│ ╎│ 0x4432 clr.b 0x247c(r15)
╰ ╰──< 0x4436 jnz $-0x000a
Here we have another loop, this time R15
is set to 0x100
, and we are just clearing bytes in 0x247c + R15
, seems like this code is creating a space after 0x247c
off size 0x100
that just has a bunch of zeros.
Main
And finally we get to main
╭ int main(int argc, char **argv, char **envp);
│ 0x4438 mov #0x4520, r14
│ 0x443c mov r14, r15
│ 0x443e mov #0x00f8, r14
│ 0x4442 mov #0x2400, r15
│ 0x4446 call #enc
│ 0x444a call #0x2400
╰ 0x444e clr r15
This seems a bit strange, 0x4520
is moved into R14
, which is then moved into R15
, then 0xF8
is moved into R14
, and 0x2400
into R15
. Then we call enc
presumable with argument R15 = 0x2400
and R14 = 0x00F8
. After the enc
call we make another call to address 0x2400
, this was the address where we copied all these presumably random bytes to.
Enc
Let’s take a look at this military-grade encryption function
╭ enc();
│ 0x4486 push r11
│ 0x4488 push r10
│ 0x448a push r9
│ 0x448c push r8
│ 0x448e clr r13
Pushing 4 registers to the stack and clearing R13
, all these already contains zero.
│ ╭─> 0x4490 mov.b r13, 0x247c(r13)
│ ╎ 0x4494 inc r13
│ ╎ 0x4496 cmp #0x0100, r13
│ ╰─< 0x449a jnz $-0x000a
Here we have a loop, we do something with R13
, increment it and then compare it.
- First the byte at address
0x247C + R13
is set to the value inR13
R13
is the incremented by oneR13
is then compared to the value0x0100
- We jump to the top if
R13
is not equal to0x0100
So this loop writes a sequence of bytes from 0x00
to 0xFF
at the address 0x247C
. Note that this was the address and the size (0x100
) that was cleared in the __do_clear_bss
│ 0x449c mov #0x247c, r12
│ 0x44a0 clr r13
│ 0x44a2 mov r13, r11
We are then getting ready for the next loop, the starting address of the memory we just created is moved into R12
, R13
and R11
is zeroed.
│ ╭─> 0x44a4 mov.b @r12, r8
│ ╎ 0x44a6 mov.b r8, r10
│ ╎ 0x44a8 add r10, r13
│ ╎ 0x44aa mov r11, r10
│ ╎ 0x44ac and #0x000f, r10
│ ╎ 0x44b0 mov.b 0x4472(r10), r10
│ ╎ 0x44b4 sxt r10
│ ╎ 0x44b6 add r10, r13
│ ╎ 0x44b8 and #0x00ff, r13
│ ╎ 0x44bc mov r13, r10
│ ╎ 0x44be add #0x247c, r10
│ ╎ 0x44c2 mov.b @r10, r9
│ ╎ 0x44c4 mov.b r8, 0x0(r10)
│ ╎ 0x44c8 mov.b r9, 0x0(r12)
│ ╎ 0x44cc inc r11
│ ╎ 0x44ce inc r12
│ ╎ 0x44d0 cmp #0x0100, r11
│ ╰─< 0x44d4 jnz $-0x0030
This loop does a bunch of memory manipulation. The interesting part is the instructions at 0x44c4
and 0x44c8
, this is where something is written to memory. It is actually quite easy to get the result of this, by using Rust, Python or any other programming language, we can load the binary file as a big array, and create some variables for the registers and then create a line of code for each instruction.
│ 0x44d6 clr r11
│ 0x44d8 mov r11, r12
│ ╭─< 0x44da jmp $+0x0032
This code is setting up for a third loop, clearing R11
and R12
, then jumping to the bottom of the loop.
│ ╭──> 0x44dc inc r12
│ ╎│ 0x44de and #0x00ff, r12
│ ╎│ 0x44e2 mov r12, r10
│ ╎│ 0x44e4 add #0x247c, r10
│ ╎│ 0x44e8 mov.b @r10, r8
│ ╎│ 0x44ea add.b r8, r11
│ ╎│ 0x44ec mov.b r11, r11
│ ╎│ 0x44ee mov r11, r13
│ ╎│ 0x44f0 add #0x247c, r13
│ ╎│ 0x44f4 mov.b @r13, r9
│ ╎│ 0x44f6 mov.b r8, 0x0(r13)
│ ╎│ 0x44fa mov.b r9, 0x0(r10)
│ ╎│ 0x44fe add.b @r13, r9
│ ╎│ 0x4500 mov.b r9, r13
│ ╎│ 0x4502 xor.b 0x247c(r13), 0x0(r15)
│ ╎│ 0x4508 inc r15
│ ╎│ 0x450a add #-1, r14
│ ╎╰─> 0x450c tst r14
│ ╰──< 0x450e jnz $-0x0032
Note that we start at address 0x450C
due to the jump from before, so we start by testing R14
. No where has register R14
been touched, so this register still holds the value that moved into R14
in main
, so R14
has the value 0x00F8
. This loop continues to do memory manipulation, so I would add this to my Rust program.
I have created a Rust program that does the same thing as all the assembly code in the prologue and in the enc
function, the resulting array is then written to a new file, that we can load in Rizin
.
│ 0x4510 pop r8
│ 0x4512 pop r9
│ 0x4514 pop r10
│ 0x4516 pop r11
╰ 0x4518 ret
At the end we are just recovering the registers again to return nicely to main
.
Decrypted
We now have a bunch of new data at address 0x2400
in the binary. To analyze this new data, we start at 0x2400
because this is the address that main
call after the call to enc
, so there must be some instructions here. Then we just go line by line, look for jumps and ret
instruction. When we see a ret
instruction we assume that this is the function end, unless there is some jump path bypassing it. Rizin make is really easy to spot jumps.
I have found to functions in the “new” code, and just called them func_01
and func_02
because I still have no idea what the do. Quickly reading over the two functions, we can see that func_01
has 4 calls to func_02
. So lets start with func_02
.
╭ func_02(); ╭ INT();
│ 0x2464 mov 0x2(sp), r14 │ 0x445a mov 0x2(sp), r14
│ 0x2468 push sr │ 0x445e push sr
│ 0x246a mov r14, r15 │ 0x4460 mov r14, r15
│ 0x246c swpb r15 │ 0x4462 swpb r15
│ 0x246e mov r15, sr │ 0x4464 mov r15, sr
│ 0x2470 bis #0x8000, sr │ 0x4466 bis #0x8000, sr
│ 0x2474 call #0x0010 │ 0x446a call #0x0010
│ 0x2478 pop sr │ 0x446e pop sr
╰ 0x247a ret ╰ 0x4470 ret
This looks familiar, I have copied the INT
function in beside it. They are exactly the same, so func_02
is just the INT
function. Let’s go back and look at func_01
, we want to figure out what interrupt values are used for the different calls to the new INT
function.
╭ func_01();
│ 0x2400 push r11
│ 0x2402 push r4
│ 0x2404 mov sp, r4
│ 0x2406 add #4, r4
│ 0x2408 add #0xffe0, sp
│ 0x240c mov #0x4520, r11
│ ╭─< 0x2410 jmp $+0x0010
│ ╭──> 0x2412 inc r11
│ ╎│ 0x2414 sxt r15
│ ╎│ 0x2416 push r15
│ ╎│ 0x2418 push #0
│ ╎│ 0x241a call #func_02
│ ╎│ 0x241e add #4, sp
│ ╎╰─> 0x2420 mov.b @r11, r15
│ ╎ 0x2422 tst.b r15
│ ╰──< 0x2424 jnz $-0x0012
Without going too much into details on this part, we can see that right before the call #func_02
a zero is pushed to the stack. If we look up the interrupts in the manual we can see that interrupt 0x00
is a putchar
so this call to func_02
is just to print something.
│ 0x2426 push #0x000a
│ 0x242a push #0
│ 0x242c call #func_02
It is the same for this second call to func_02
│ 0x2430 add #4, sp
│ 0x2432 push #0x001f
│ 0x2436 mov #0xffdc, r15
│ 0x243a add r4, r15
│ 0x243c push r15
│ 0x243e push #2
│ 0x2440 call #func_02
This time we are pushing 0x2
which according to the manual is a gets
interrupt, so this is where we are prompted for a password. Let’s see if we can figure out where the password is saved. The argument is pushed to the stack using R15
. The value 0xffdc
is moved into R15
, then R4
is added to it. R4
is originating from the stack pointer, so we need to figure out what the stack pointer is. When we come to address 0x2400
the SP
is 0x4400
, then it is moved into R4
and not touched before we use it, so the address our password will be written it 0x43dc
.
│ 0x2444 add #0x0006, sp
│ 0x2448 cmp #0x6365, 0xffdc(r4)
│ ╭─< 0x244e jnz $+0x000c
│ │ 0x2450 push #0x007f
│ │ 0x2454 call #func_02
│ │ 0x2458 incd sp
│ ╰─> 0x245a add #0x0020, sp
│ 0x245e pop r4
│ 0x2460 pop r11
╰ 0x2462 ret
Now we have something, 0x2450 push #0x007f
this is the one we want, this opens the lock. We need to avoid the jump. The compare checks the value 0x6365
and some address, whatever is in R4
(that is still the 0x4400) and offset by 0xffdc
, this is the value we moved into R15
before we added R4
, so this is just a cryptic way to say the same address of our password. But we only compare it to two bytes, so our password is two bytes long, seems a bit weak. 0x63 = 'c'
and 0x65 = 'e'
and since we use little endian the password is backwards.