Microcorruption CTF

Preface

tl;dr: Given a debugger and a device, find an input that unlocks it. Solve the level with that input.

You've been given access to a device based on TI MSP430 microcontroller that controls a lock. Your job: defeat the lock by exploiting bugs in the device's code.

TODO: Small primer about MSP430 assembly, addressing modes and shellcode generation. And bugs.

Tutorial

The CTF starts with the tutorial which explains all the interface elements and eventually steps you through the code to help you solve this level.

We are interested in the check_password subroutine:

4484 <check_password>
4484:  6e4f           mov.b @r15, r14
4486:  1f53           inc   r15
4488:  1c53           inc   r12
448a:  0e93           tst   r14
448c:  fb23           jnz   #0x4484 <check_password+0x0>
448e:  3c90 0900      cmp   #0x9, r12
4494:  0f43           clr   r15
4496:  3041           ret
4498:  1f43           mov   #0x1, r15
449a:  3041           ret

So we set the breakpoint at 0x4484, run the debugger and input 'AAAA' as our password. We eventually trigger this function with following machine state:

Registers:

pc  4484  sp  439a  sr  0000  cg  0000
r04 0000  r05 5a08  r06 0000  r07 0000
r08 0000  r09 0000  r10 0000  r11 0000
r12 0000  r13 0000  r14 0002  r15 439c

Memory:

4380:   0000 0000 0000 0000 0000 0000 0845 0000   .............E..
4390:   5245 0200 9c43 6400 8244 5044 4141 4141   RE...Cd..DPDAAAA
43a0:   0000 0000 0000 0000 0000 0000 0000 0000   ................

So we start stepping through the code:

4484:   6e4f          mov.b @r15, r14

This basically means: move byte located at the memory location pointed by r15 to r14. As r15=0x439c we end up with r14=0x0041 (or A), because the command is move byte.

4486:  1f53           inc   r15
4488:  1c53           inc   r12

Here we set the r15 to point at the next character and increment the counter which seems to be eventually used for password length.

448a:  0e93           tst   r14
448c:  fb23           jnz   #0x4484 <check_password+0x0>

We check the value of r14 (containing a character in the password) register and jump back to the beginning of the loop if the character is non-zero.

If r14 is equal to 0x00 meaning that we have reached the end of the password, and we execute the next instruction where we compare 9 with r12.

448e:  3c90 0900      cmp   #0x9, r12
4492:  0224           jeq   #0x4498 <check_password+0x14>

Hmm? If r12 is not 9 we clear r15 and return from the function:

4494:  0f43           clr   r15
4496:  3041           ret

Otherwise we set r15 to 1 and return to caller:

4498:  1f43           mov   #0x1, r15
449a:  3041           ret

And this is what the code from calling function looks like:

444c:  b012 8444      call  #0x4484 <check_password>
4450:  0f93           tst   r15
4452:  0520           jnz   #0x445e <main+0x26>
4454:  3f40 c744      mov   #0x44c7 "Invalid password; try again.", r15
4458:  b012 5845      call  #0x4558 <puts>
445c:  063c           jmp   #0x446a <main+0x32>
445e:  3f40 e444      mov   #0x44e4 "Access Granted!", r15
4462:  b012 5845      call  #0x4558 <puts>
4466:  b012 9c44      call  #0x449c <unlock_door>
446a:  0f43           clr   r15
446c:  3150 6400      add   #0x64, sp

Solved? :)

New Orleans

After completing the tutorial, we move one to our first challenge.

Like previously we set breakpoint to check_password function, enter 12345 as password and continue until breakpoint gets triggered:

44bc <check_password>
44bc:  0e43           clr   r14
44be:  0d4f           mov   r15, r13
44c0:  0d5e           add   r14, r13
44c2:  ee9d 0024      cmp.b @r13, 0x2400(r14)
44c6:  0520           jne   #0x44d2 <check_password+0x16>
44c8:  1e53           inc   r14
44ca:  3e92           cmp   #0x8, r14
44cc:  f823           jne   #0x44be <check_password+0x2>
44ce:  1f43           mov   #0x1, r15
44d0:  3041           ret
44d2:  0f43           clr   r15
44d4:  3041           ret

From register state we can see that r15=(0x439c) is pointing at our password in memory:

4380:   0000 0000 0000 0000 0000 0000 4445 0000   ............DE..
4390:   8e45 0200 9c43 6400 ba44 5444 3132 3334   .E...Cd..DTD1234
43a0:   3500 0000 0000 0000 0000 0000 0000 0000   5...............

Step through the function we can see that comparison is actually being done against specific location in the memory address starting from 0x2400:

44c2:  ee9d 0024      cmp.b @r13, 0x2400(r14)

And each time r14 is increased by one starting from 0. And this actually means that the password is hardcoded into that specific memory location..

Solved?! :)

Sydney

As usual we start with setting the breakpoint, entering 12345 as password and see where it triggers:

448a <check_password>
448a:  bf90 6a3b 0000 cmp   #0x3b6a, 0x0(r15)
4490:  0d20           jnz   $+0x1c
4492:  bf90 5c43 0200 cmp   #0x435c, 0x2(r15)
4498:  0920           jnz   $+0x14
449a:  bf90 694c 0400 cmp   #0x4c69, 0x4(r15)
44a0:  0520           jne   #0x44ac <check_password+0x22>
44a2:  1e43           mov   #0x1, r14
44a4:  bf90 3028 0600 cmp   #0x2830, 0x6(r15)
44aa:  0124           jeq   #0x44ae <check_password+0x24>
44ac:  0e43           clr   r14
44ae:  0f4e           mov   r14, r15
44b0:  3041           ret

The check_password subroutine seems to contain 4 comparison instructions all of which compare various offsets of memory pointed by r15 register:

$ read r15
439c:   3132 3334 3500 0000  12345...

So what happens if we try 3b6a435c4c692830 as password (as hex-encoded input)? Well, nothing, because we fail at the first comparison.

Let's investigate the memory again:

$ r r15
439c:   3b6a 435c 4c69 2830  ;jC\Li(0

So? What's wrong? Apparently the MSP430 memory is byte-addressed, but pairs of bytes are combined little-endian. And that's basically it. Solved? :)

Hanoi

We start as usual, although, the password checking function is now test_password_valid.

4454 <test_password_valid>
4454:  0412           push  r4
4456:  0441           mov   sp, r4
4458:  2453           incd  r4
445a:  2183           decd  sp
445c:  c443 fcff      mov.b #0x0, -0x4(r4)
4460:  3e40 fcff      mov   #0xfffc, r14
4464:  0e54           add   r4, r14
4466:  0e12           push  r14
4468:  0f12           push  r15
446a:  3012 7d00      push  #0x7d
446e:  b012 7a45      call  #0x457a <INT>
4472:  5f44 fcff      mov.b -0x4(r4), r15
4476:  8f11           sxt   r15
4478:  3152           add   #0x8, sp
447a:  3441           pop   r4
447c:  3041           ret

Well, nothing suspicious there, except the fact that the password prompt contains warning: "Remember: passwords are between 8 and 16 characters.".

And we actually do not see any password length checks. Anyway, let's analyze the main function (called login in this case):

4520 <login>
4520:  c243 1024      mov.b #0x0, &0x2410
4524:  3f40 7e44      mov   #0x447e "Enter the password to continue.", r15
4528:  b012 de45      call  #0x45de <puts>
452c:  3f40 9e44      mov   #0x449e "Remember: passwords are between 8 and 16 characters.", r15
4530:  b012 de45      call  #0x45de <puts>
4534:  3e40 1c00      mov   #0x1c, r14
4538:  3f40 0024      mov   #0x2400, r15
453c:  b012 ce45      call  #0x45ce <getsn>
4540:  3f40 0024      mov   #0x2400, r15
4544:  b012 5444      call  #0x4454 <test_password_valid>
4548:  0f93           tst   r15
454a:  0324           jz    $+0x8
454c:  f240 0900 1024 mov.b #0x9, &0x2410
4552:  3f40 d344      mov   #0x44d3 "Testing if password is valid.", r15
4556:  b012 de45      call  #0x45de <puts>
455a:  f290 6400 1024 cmp.b #0x64, &0x2410
4560:  0720           jne   #0x4570 <login+0x50>
4562:  3f40 f144      mov   #0x44f1 "Access granted.", r15
4566:  b012 de45      call  #0x45de <puts>
456a:  b012 4844      call  #0x4448 <unlock_door>
456e:  3041           ret
4570:  3f40 0145      mov   #0x4501 "That password is not correct.", r15
4574:  b012 de45      call  #0x45de <puts>
4578:  3041           ret

It seems that the memory location 0x2410 is used as a place where the 'access granted' flag will be written (although, there seems to be a bug) and it has to be equal to 0x64 or d:

4552:  3f40 d344      mov   #0x44d3 "Testing if password is valid.", r15
4556:  b012 de45      call  #0x45de <puts>
455a:  f290 6400 1024 cmp.b #0x64, &0x2410
4560:  0720           jne   #0x4570 <login+0x50>
4562:  3f40 f144      mov   #0x44f1 "Access granted.", r15

Password itself is read into location 0x2400:

4534:  3e40 1c00      mov   #0x1c, r14
4538:  3f40 0024      mov   #0x2400, r15
453c:  b012 ce45      call  #0x45ce <getsn>

Hmm.. 0x2410 - 0x2400 = 0x16 = 10. Remember, we didn't see any password length checks?

So to solve it let's try with 16 character random password + 'd'.

Cusco

Ok, another level:

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.

Let's check what has changed:

4500: <login>
...
4514:  3e40 3000      mov   #0x30, r14
4518:  0f41           mov   sp, r15
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
...

Apparently we are now using stack to store the entered password. Let's try again with a password a bit too big - now with 20 characters: aAaAaAaAaAaAaAaAaAaA.

Well, nothing: That password is not correct. :(

Well, no really ;)

When we let the program to continue after the password failure we run into following crash:

insn address unaligned

And pc = 0x4161. Wait a minute! This is Aa, which means that we have actually managed to cause a stack overflow!

So, lets analyze the login function which is used to read the password from user. Let's set the breakpoint at location 0x4518 and 0x453e. First one is to see the value of the sp (stack pointer) and second one is to see what happens with sp after the login function returns.

As expected 0x4518 is triggered first:

$ read sp
43ee:   0000 0000 0000 0000  ........
43f6:   0000 0000 0000 0000  ........
43fe:   3c44 3140 0044 1542  <D1@.D.B

We step through the next function to see where the password gets stored:

$ read sp
43ee:   3132 3334 3536 0000  123456..
43f6:   0000 0000 0000 0000  ........
43fe:   3c44 3140 0044 1542  <D1@.D.B

Let's continue until we trigger the ret of the login function @ 0x453e:

$ read sp
43fe:   3c44 3140 0044 1542  <D1@.D.B
4406:   5c01 75f3 35d0 085a  \.u.5..Z

When we step into next function, we end up at location 0x443c.

So let's try again with a bit longer password this time, lets enter ABCDABCDABCDABCDABCD and set breakpoint at 0x453e:

$ read sp
43fe:   4142 4344 0044 1542  ABCD.D.B
4406:   5c01 75f3 35d0 085a  \.u.5..Z

YES! pc = 0x4241 and insn address unaligned. All we now have to do is take the address of unlock_door function and add it to the correct location in our password ;)

Reykjavik

Looks like we have a crypto challenge :)

Anyway - we have a main function that immediately calls the enc function containing multiple loops:

4486 <enc>
4486:  0b12           push  r11
4488:  0a12           push  r10
448a:  0912           push  r9
448c:  0812           push  r8
448e:  0d43           clr   r13                     ;; Start of loop 1
4490:  cd4d 7c24      mov.b r13, 0x247c(r13)
4494:  1d53           inc   r13
4496:  3d90 0001      cmp   #0x100, r13
449a:  fa23           jne   #0x4490 <enc+0xa>
449c:  3c40 7c24      mov   #0x247c, r12
44a0:  0d43           clr   r13                     ;; End of loop 1
44a2:  0b4d           mov   r13, r11
44a4:  684c           mov.b @r12, r8                ;; Start of loop 2
44a6:  4a48           mov.b r8, r10
44a8:  0d5a           add   r10, r13
44aa:  0a4b           mov   r11, r10
44ac:  3af0 0f00      and   #0xf, r10
44b0:  5a4a 7244      mov.b 0x4472(r10), r10
44b4:  8a11           sxt   r10
44b6:  0d5a           add   r10, r13
44b8:  3df0 ff00      and   #0xff, r13
44bc:  0a4d           mov   r13, r10
44be:  3a50 7c24      add   #0x247c, r10
44c2:  694a           mov.b @r10, r9
44c4:  ca48 0000      mov.b r8, 0x0(r10)
44c8:  cc49 0000      mov.b r9, 0x0(r12)
44cc:  1b53           inc   r11
44ce:  1c53           inc   r12
44d0:  3b90 0001      cmp   #0x100, r11
44d4:  e723           jne   #0x44a4 <enc+0x1e>
44d6:  0b43           clr   r11                     ;; End of loop 2
44d8:  0c4b           mov   r11, r12
44da:  183c           jmp   #0x450c <enc+0x86>
44dc:  1c53           inc   r12
44de:  3cf0 ff00      and   #0xff, r12
44e2:  0a4c           mov   r12, r10
44e4:  3a50 7c24      add   #0x247c, r10
44e8:  684a           mov.b @r10, r8
44ea:  4b58           add.b r8, r11
44ec:  4b4b           mov.b r11, r11
44ee:  0d4b           mov   r11, r13
44f0:  3d50 7c24      add   #0x247c, r13
44f4:  694d           mov.b @r13, r9
44f6:  cd48 0000      mov.b r8, 0x0(r13)
44fa:  ca49 0000      mov.b r9, 0x0(r10)
44fe:  695d           add.b @r13, r9
4500:  4d49           mov.b r9, r13
4502:  dfed 7c24 0000 xor.b 0x247c(r13), 0x0(r15)
4508:  1f53           inc   r15
450a:  3e53           add   #-0x1, r14
450c:  0e93           tst   r14
450e:  e623           jnz   #0x44dc <enc+0x56>
4510:  3841           pop   r8
4512:  3941           pop   r9
4514:  3a41           pop   r10
4516:  3b41           pop   r11
4518:  3041           ret

First, we set up the breakpoint just after the first loop at 0x44a0. When it triggers, let's examine 0x247c:

$ read 247c
247c:   0001 0203 0405 0607  ........
2484:   0809 0a0b 0c0d 0e0f  ........
248c:   1011 1213 1415 1617  ........
2494:   1819 1a1b 1c1d 1e1f  ........
...
256c:   f0f1 f2f3 f4f5 f6f7  ........
2574:   f8f9 fafb fcfd feff  ........
257c:   0000 0000 0000 0000  ........

Look's like a lookup table. Time to investigate the second loop. First we step it through for a few cycles and then let it to run until its end (set breakpoint @ 0x44d6). During the first few steps we discover that it copies certain bytes starting from this location:

$ read 4472
4472:   5468 6973 4973 5365  ThisIsSe
447a:   6375 7265 5269 6768  cureRigh
4482:   743f 0000 0b12 0a12  t?......
448a:   0912 0812 0d43 cd4d  .....C.M

Well, seems like we have the key. What happens next is that the whole table @ 0x247c get scrambled using the key.

We currently skip the third loop to actually see what's going on after this function ends and calls 0x2400. As things are getting complicated, this function is not viewable in the 'Disassembly' section, so we have to extract it from live memory dump and disassemble ourself:

2400:   0b12           push r11
        0412           push r4
        0441           mov  sp, r4
        2452           add  #0x4, r4
        3150 e0ff      add  #0xffe0, sp
        3b40 2045      mov  #0x4520, r11    ;; 0x4520 is location for 'what's the password?' string
        073c           jmp  $+0x10          ;; jump to 0x2420
2412:   1b53           inc  r11
        8f11           sxt  r15
        0f12           push r15
        0312           push #0x0
        b012 6424      call #0x2464
        2152           add  #0x4, sp
2420:   6f4b           mov.b    @r11, r15
        4f93           tst.b    r15
        f623           jnz  $-0x12          ;; jump to 0x2412 if not-zero
2426:   3012 0a00      push #0xa
        0312           push #0x0
        b012 6424      call #0x2464
        2152           add  #0x4, sp
        3012 1f00      push #0x1f
        3f40 dcff      mov  #0xffdc, r15
        0f54           add  r4, r15
        0f12           push r15
        2312           push #0x2
        b012 6424      call #0x2464
        3150 0600      add  #0x6, sp
        b490 b5a3 dcff cmp  #0xa3b5, -0x24(r4)
        0520           jnz  $+0xc
        3012 7f00      push #0x7f
        b012 6424      call #0x2464
        2153           incd sp
        3150 2000      add  #0x20, sp
        3441           pop  r4
        3b41           pop  r11
        3041           ret
2464:   1e41 0200      mov  0x2(sp), r14    ;; INT function
        0212           push sr
        0f4e           mov  r14, r15
        8f10           swpb r15
        024f           mov  r15, sr
        32d0 0080      bis  #0x8000, sr
        b012 1000      call #0x10
        3241           pop  sr
        3041           ret
        d21a 189a      call &0x9a18
        22dc           bis  @r12, sr
        45b9           bit.b    r9, r5
        4279           subc.b   r9, sr
        2d55           add  @r5, r13
        858e a4a2      sub  r14, -0x5d5c(r5)
        67d7           bis.b    @r7, r7

So after disassembly bunch of memory, we better set the breakpoint to 0x2400 and see what happens:

2400:   0b12           push r11
        0412           push r4
        0441           mov  sp, r4
        2452           add  #0x4, r4
        3150 e0ff      add  #0xffe0, sp
        3b40 2045      mov  #0x4520, r11    ;; 0x4520 is location for 'what's the password?' string
        073c           jmp  $+0x10          ;; jump to 0x2420

Ok, so far so good - we saw that we set up a pointer to password prompt text. Let's continue tracing:

2420:   6f4b           mov.b @r11, r15
        4f93           tst.b r15
        f623           jnz  $-0x12          ;; jump to 0x2412 if not-zero

We end up at 0x2412:

2412:   1b53           inc  r11
        8f11           sxt  r15
        0f12           push r15
        0312           push #0x0
        b012 6424      call #0x2464         ;;

And we jump to subroutine located at0x2464 which prints a single character to the console when it returns. Apparently if you continue stepping we'll see the whole string located at 0x4520 appearing on console and then continue on to 0x2426 which will be our next breakpoint.

2426:   3012 0a00      push #0xa            ;; ??
        0312           push #0x0            ;; 'putchar' interrupt ??
        b012 6424      call #0x2464         ;; ??
        2152           add  #0x4, sp
        3012 1f00      push #0x1f
        3f40 dcff      mov  #0xffdc, r15
        0f54           add  r4, r15
        0f12           push r15
        2312           push #0x2            ;; 'gets' interrupt
        b012 6424      call #0x2464         ;; call IRQ

So after we input our password ('aabb' as a test) we end up at location 0x2444 so we continue on:

2444:   3150 0600      add  #0x6, sp
        b490 b5a3 dcff cmp  #0xa3b5, -0x24(r4)  ;; This looks suspicious!?!
        0520           jnz  $+0xc
        3012 7f00      push #0x7f           ;; HSM-1 interface
        b012 6424      call #0x2464         ;; call IRQ

That compare function certainly looks sucpicious. Let's see what's behind -0x24(r4) = 0x43fe - 0x24 = 0x43da:

$ read 43da
   43da:   6161 6262 0000 0000  aabb....

Oops! I think we found a way to open this lock :)

Whitehorse

Montevideo

Johannesburg

Santa Cruz

Jakarta

Addis Ababa

...