microcorruption - reykjavik

This post is part of the series of solving microcorruption.com ctf challenges which continues from the previous challenge called Cusco. This challenge is titled Reykjavik.

This challenge has the following description towards the bottom:

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.

Rough. But ok, time to see of its better this time. Also, “military-grade encryption”, hah! :P


The main routine this time is a quite a bit different when compared to the previous challenges:

4438 <main>
4438:  3e40 2045      mov   #0x4520, r14
443c:  0f4e           mov   r14, r15
443e:  3e40 f800      mov   #0xf8, r14
4442:  3f40 0024      mov   #0x2400, r15
4446:  b012 8644      call  #0x4486 <enc>

; once enc is done, call something without a label?
444a:  b012 0024      call  #0x2400
444e:  0f43           clr   r15

Only two call’s are made in main, of which the second has no label.

The enc routine has quite a bit of logic though. Taking a closer, annotated look at the enc routine as I was performing a static analysis resulted in the following:

4486 <enc>
4486:  0b12           push  r11
4488:  0a12           push  r10
448a:  0912           push  r9
448c:  0812           push  r8

; clearing r13, might mean that the instruction at
; 4490 will move a 0x0 byte into 0x247c, indicating
; the start of a loop

448e:  0d43           clr   r13
4490:  cd4d 7c24      mov.b r13, 0x247c(r13)
4494:  1d53           inc   r13

; this looks like a loop that will continue until
; r13 is incremented up to 0x0100 (256). interestingly
; that is bytes 0x00 to 0xff...

4496:  3d90 0001      cmp   #0x100, r13
449a:  fa23           jne   #0x4490 <enc+0xa>

; not sure what this is preparing for yet, but it looks
; like r11 is being setup for a loop here.

449c:  3c40 7c24      mov   #0x247c, r12
44a0:  0d43           clr   r13
44a2:  0b4d           mov   r13, r11
44a4:  684c           mov.b @r12, r8
44a6:  4a48           mov.b r8, r10
44a8:  0d5a           add   r10, r13
44aa:  0a4b           mov   r11, r10

; some arithmetic. maybe an indication of a decrypt of
; some sorts or a simple byte swap happening here, needs
; debugger

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

; the loop with r11 for another 256 bytes

44d0:  3b90 0001      cmp   #0x100, r11
44d4:  e723           jne   #0x44a4 <enc+0x1e>

44d6:  0b43           clr   r11
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

; probably the actual decryption taking place here, as
; r15 (which was set in main) is used as an offset.

4502:  dfed 7c24 0000 xor.b 0x247c(r13), 0x0(r15)
4508:  1f53           inc   r15

; decrement r14 until we get to 0, which will prevent the
; jmp back up from being taken, ending the routine.

; r14 was set to to 0xf8 (248) in main, so that might mean
; that the decrypted bytes is 248 long.

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

As you can see, this is a busy routine. There is very clearly some form of byte write, swapping and xor occurring as expected. Some key points to note though is that the memory location 0x2400 that was written to r15 in main is used as a starting offset for an xor instruction occurring within enc. So, that is definitely an interesting location to watch as we debug this routine.


To gain a better understanding of what is happening in enc, I set a breakpoint at the entry point of this routine with break 4486. Then continuing the CPU and manually stepping through the instructions while watching what happens around memory address 0x2400 revealed the following:

It looks like a payload already lives from 0x2400 and a range of bytes from 0x00 to 0xff is written from 0x247c onwards.

This memory state is achieved right after this loop has completed and the jump at 0x449a is not taken. Alright. Lets move on. The next loop also does a whole bunch of manipulations on the byte range that was just written to memory (and a few other things). There are some weird instructions like the mov.b r11,r11 at 0x44ec that I couldn’t quite understand, but nonetheless.

The most important thing to see in this loop is that the bytes at 0x2400 are XOR’d with those at 0x24f9 when the xor.b 0x247c(r13), 0x0(r15) instruction is called the first time. The second time, 0x247c(r13) calculates to 0x24b6, which is the next XOR.

This loop continues until r14 reaches zero, and the registers before the call to enc is restored and the function returns. At this stage, the memory contents at 0x2400 looks as follows (which should now be the decrypted contents):

At this stage its pretty clear that the call to jump to 0x2400 in main was because then the encrypted opcodes would be available there and further processing should take place. Now, reading the opcodes like that from a memory dump sucks. So, to make it a little more digestible, I slapped the dump into https://onlinedisassembler.com. The process was pretty simple; select the memory dump from microcorruption, paste it into a file, cleanup the dump with cat dump | cut -d" " -f2-11 and paste that result into the RAW section in the disassembler. Finally, set the architecture to MSP430 and profit!

After pasting that into the disassembler, I quickly realised that the whole payload might not be valid code for this section. After the ret the instructions became pretty crazy, so I just removed those. The final opcodes I used (which was ~256 bytes when counted with cat dump.raw | tr -d ' ' | wc -c) were:

0b12 0412 0441 2452 3150 e0ff 3b40 2045
073c 1b53 8f11 0f12 0312 b012 6424 2152
6f4b 4f93 f623 3012 0a00 0312 b012 6424
2152 3012 1f00 3f40 dcff 0f54 0f12 2312
b012 6424 3150 0600 b490 db01 dcff 0520
3012 7f00 b012 6424 2153 3150 2000 3441
3b41 3041 1e41 0200 0212 0f4e 8f10 024f
32d0 0080 b012 1000 3241 3041

Anyways, I could now step through these instructions in the debugger, and have a disassembled reference I could refer to and see what is happening. The opcodes in the online disassembler would also correspond with those on microcorruption at the top right section which helped confirm that I was on the right track.

Stepping through each instruction, annotating what I thought was happening helped me understand what was going on. After a few rounds of running the routine, the following instructions were the most interesting as they were the instructions called just before it would jump away from the unlocking logic.

cmp #0x1db, -0x24(r4)
jnz $+0xc

I needed to see what is happening at -0x24(r4) and see if I can control that value. To help me locate the memory location of the data used for the cmp, I had to do a little offset calculation. First, the cmp instruction was at 0x2448, found by simply looking at the program counter (pc) as I debugged the routine. A breakpoint here let me inspect the registers and perform the offset calculations needed to know what data was used in the cmp. Register r4 contained 0x43fe which is 17406, so taking 0x24 (36) from that ends you up with 0x43fe.

>>> 0x43fe
>>> 0x43fe-36
>>> hex(0x43fe-36)

The data at 0x43da was the start of my password buffer, which is effectively what is being used in the cmp instruction. Making sure my password entered started with 0xdb01 (remember, little endian) would be enough for this compare to result in the status flag being set to not have the next conditional jump taken, resulting in a value of 0x7f being pushed to the stack and a syscall being made to unlock the lock!

My final, annotated version of the dissasembled opcodes looked as follows:


Enter db01 as hex encoded input.

other challenges

For my other write ups in the microcorruption series, checkout this link.

comments powered by Disqus