blog/05-microcorruption

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

SourceDestination
0x45b40x247c
0x45b20x247a
0x45380x2400

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.

  1. First the byte at address 0x247C + R13 is set to the value in R13
  2. R13 is the incremented by one
  3. R13 is then compared to the value 0x0100
  4. We jump to the top if R13 is not equal to 0x0100

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.

Door unlocked

/microcorruption/