microcorruption

New Orleans

The brief for this level is as follows:

    - This is the first LockIT Pro Lock.
    - This lock is not attached to any hardware security module.

Let’s start by commenting the assembly. Once we understand how the program flows it will be much easier to exploit.

Looking at main, we see this interesting snippet:

444a:  b012 b244      call  #0x44b2 <get_password>
444e:  0f41           mov sp, r15                   # sp contains the address of our input on the stack
4450:  b012 bc44      call  #0x44bc <check_password>
4454:  0f93           tst r15
4456:  0520           jnz #0x4462 <main+0x2a>       # we want this test to succeed - that is, for r15 to be non-zero

The other interesting function is check_password:

44bc <check_password>
44bc:  0e43           clr r14                 # clear r14 - r14 = 0
44be:  0d4f           mov r15, r13            # r13 = r15, which is the address of our password on the stack
44c0:  0d5e           add r14, r13            # adds 0 - effectively a no-op
44c2:  ee9d 0024      cmp.b @r13, 0x2400(r14) # compares the lower byte (first character of our input) stored at r13
                                              # with what is stored at r14 = 0x2400 - so 0x2400 since r14 is 0
44c6:  0520           jne #0x44d2 <check_password+0x16> # exit if not equal
44c8:  1e53           inc r14                 # increment r14
44ca:  3e92           cmp #0x8, r14           # check if r14 is equal to 8
44cc:  f823           jne #0x44be <check_password+0x2>  # if it isn't jump back up to 44be
44ce:  1f43           mov #0x1, r15           # this is where we want to get at - it's the return value
44d0:  3041           ret
44d2:  0f43           clr r15                 # this returns 0 - which is no good
44d4:  3041           ret

check_password is essentially a loop that will iterate through each character of our input and compare it against what is stored at 0x2400. Using the Live Memory Dump window we see:

2400: 5966 3841 2d46 6e00 0000 0000 0000 0000 Yf8A-Fn………

You’ll note this is 7 characters + the null byte - matching the 0x8 above. Could this be the password? : )

Sydney

    - We have revised the software in revision 02.
    - This lock is not attached to any hardware security module.

main looks very similar:

4446:  b012 8044      call  #0x4480 <get_password>
444a:  0f41           mov sp, r15
444c:  b012 8a44      call  #0x448a <check_password>
4450:  0f93           tst r15
4452:  0520           jnz #0x445e <main+0x26>

But check_password looks very different! Again r15 holds the address of our input.

448a <check_password>
448a:  bf90 4831 0000 cmp #0x3148, 0x0(r15) # compare 0x3148 with the first word at r15
4490:  0d20           jnz $+0x1c            # jump to 44ac if we didn't match
4492:  bf90 3261 0200 cmp #0x6132, 0x2(r15) # compare 0x6132 with [r15+2]
4498:  0920           jnz $+0x14            # jump to 44ac if we didn't match
449a:  bf90 336b 0400 cmp #0x6b33, 0x4(r15) # compare 0x6b33 with [r15+4]
44a0:  0520           jne #0x44ac <check_password+0x22> # jump to 44ac if we didn't match
44a2:  1e43           mov #0x1, r14         # r14 = 1
44a4:  bf90 6d79 0600 cmp #0x796d, 0x6(r15) # compare 0x796d with [r15+6]
44aa:  0124           jeq #0x44ae <check_password+0x24> # jump to 44ae if we did match
44ac:  0e43           clr r14               # set r14 to 0
44ae:  0f4e           mov r14, r15          # copy r14 to r15, our return value
44b0:  3041           ret

We are comparing each word (2 bytes) consecutivley. If we match all the way, the jeq at 44aa will bypass clr r14 - allowing us to return 1 instead of 0.

With this in mind, the password should be easy to guess. Just remember the system is little-endian, meaning an input of 0xaabb is stored as bbaa.

Hanoi

    - This lock is attached the the LockIT Pro HSM-1.
    - We have updated  the lock firmware  to connect with the hardware
      security module.

This time the lock is attached to a security module - so the password comparison should be hidden from sight - meaning it won’t be availble to us in the live memory dump (or in code).

The section of interest in main is:

4534:  3e40 1c00      mov #0x1c, r14        # r14 = 0x1c (which is 28 in decimal)
4538:  3f40 0024      mov #0x2400, r15      # r15 = 0x2400
453c:  b012 ce45      call  #0x45ce <getsn>
4538:  3f40 0024      mov #0x2400, r15      # r15 = 0x2400
453c:  b012 ce45      call  #0x45ce <getsn>
4540:  3f40 0024      mov #0x2400, r15      # r15 = 0x2400
4544:  b012 5444      call  #0x4454 <test_password_valid>
4548:  0f93           tst r15               # set sr, the status register
454a:  0324           jz  $+0x8             # jump to 4552 if it is 0
454c:  f240 9500 1024 mov.b #0x95, &0x2410  # [0x2410] = 0x95 - we only move a byte
4552:  3f40 d344      mov #0x44d3 "Testing if password is valid.", r15
4556:  b012 de45      call  #0x45de <puts>
455a:  f290 b000 1024 cmp.b #0xb0, &0x2410  # compare the byte at [0x2410] with 0xb0
4560:  0720           jne #0x4570 <login+0x50>

It’s a little weird. If test_password_valid returns 0 (presumably if the password is incorrect), we compare the value at 0x2410 with 0xb0 - and if that matches the login is approved.

The plan is to set that memory address - but how? As per the above our input is stored at 0x2400. We’re told the password should only be 16 characters but who’s checking?

The code to for getsn is as follows:

45ce:  0e12           push  r14   # the number of bytes we're reading
45d0:  0f12           push  r15   # the address to store those at
45d2:  2312           push  #0x2  # for INT 0x02
45d4:  b012 7a45      call  #0x457a <INT>

The description for INT 0x02 is in the manual. As per the above, r14 = 0x1c which is 28 bytes - despite the instructions stating passwords should have a maximum of 16 letters. This means we can provide more characters and overflow to 0x2410. All in all that’ll be 16 bytes of garbage + 0xb0.

Cusco

    - We have fixed issues with passwords which may be too long.
    - This lock is attached the the LockIT Pro HSM-1.

It looks like our previous overflow trick might not longer be applicable. But is it really?

4514:  3e40 3000      mov #0x30, r14        # r13 = 48 bytes - how much input we'll read
4518:  0f41           mov sp, r15           # r15 = sp - where input will be stored
451a:  b012 9645      call  #0x4596 <getsn>
451e:  0f41           mov sp, r15
4520:  b012 5244      call  #0x4452 <test_password_valid>
4524:  0f93           tst r15
4526:  0524           jz  #0x4532 <login+0x32>
4528:  b012 4644      call  #0x4446 <unlock_door>
452c:  3f40 d144      mov #0x44d1 "Access granted.", r15
4530:  023c           jmp #0x4536 <login+0x36>
4532:  3f40 e144      mov #0x44e1 "That password is not correct.", r15
4536:  b012 a645      call  #0x45a6 <puts>
453a:  3150 1000      add #0x10, sp
453e:  3041           ret

We see we will read in 0x30 bytes, or 48 bytes, for our input. But if we look at the instructions before ret, we see we only pop 16 bytes off the stack.

As per the documentation, ret will take the return address off the stack and pc will be set to it.

Let’s break on 0x451e and check out the memory with ‘password’ as our input:

> r sp 16
   43ee:   7061 7373 776f 7264  password
   43f6:   0000 0000 0000 0000  ........
   43fe:   3c44 3140 0044 1542  <D1@.D.B

We see at at 0x43fe there’s 0443c - which is the address main will will return to (__stop_progExec__). If we can override that we can control the program flow regardless of the validity of the password.

The return address we’ll override with is 0x4528 - the call to unlock_door in main. Our input will look like this: 16 bytes of garbage + 0x4528. And voila!