As any previous level, when you enter it, there is a pop up manual. If you were to read it, you’d notice the following info:
Lockitall developers have implemented military-grade on-device encryption to keep the password secure.
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.
Apparently they fired some engineers and introduced military-grade encryption. That sounds so wow. Let’s see what they’re up to.
As always, we start looking at
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> 444a: b012 0024 call #0x2400 444e: 0f43 clr r15
We can see it is placing
r15. Then it is calling
enc function and,
0x2400. As you can see, that second call has no label. That’s indeed strange because if you take a closer look at the code, you will notice there are no instructions starting at address
0x2400 (the memory is full of zeros).
This can only mean that at some point during the program’s execution before the actual call, at address
0x444a, somehow instructions will be placed at
0x2400 in order for the call to make sense. Before inspecting
enc function, let’s place a breakpoint at the beginning of
main and see what happens.
Execute it and inspect the memory. You will notice some changes have been taken place. All of a sudden, instructions at address
This happens because
do_copy_data gets executed before
do_copy_data copies 0x7c bytes from
main calls this region,
enc will do his job. Let’s find out what is it. Since it’s a pretty large function, I’ve splitted it.
4486 <enc> 4486: 0b12 push r11 4488: 0a12 push r10 448a: 0912 push r9 448c: 0812 push r8 448e: 0d43 clr r13 4490: cd4d 7c24 mov.b r13, 0x247c(r13) 4494: 1d53 inc r13 4496: 3d90 0001 cmp #0x100, r13 449a: fa23 jne #0x4490 <enc+0xa>
0x449a, the function is placing from address
0x247c to address
0x257c (256 bytes) bytes from 0x00 to 0xff (from 0 to 255). It is starting from 0x00 and incrementing by one each time it points to the next memory address. From
0x449a there is a loop that gets executed 256 times.
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 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
Here we have some bytes arithmetic. It is taking bytes from
0x247c and so on, where bytes from 0x00 to 0xff haven been previously placed. We can see it is also taking some bytes from address
0x4472 at instruction
0x44b0. At address
0x4472 lives the ASCII string
0x44d4 there is a loop that gets executed 256 times. All this code will be easier to understand at debugging time. It appears, at a first glance, to be enrypting or obfuscating the previosuly inserted 256 bytes.
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
There is an inconditional jump to
0x450c where we have a
r14 checking. Remember
r14 is set in
main at instruction
0x443e. The loop from
0x450e gets executed
0xf8 (248) times because it is being decremented by one at
0x450a. Also notice that
r15 is being used as an offset at instruction
r15 was set in
main at instruction address
0x4442. This appears to be XORING the previously obfuscated 256 bytes with the data wrote at
As you can see, there is some intense work going on. But
0x444a code placed into address
0x2400 must be already translated into MSP430’s assembly language (opcodes) so the processor can execute it. Let us debug!
Let’s place a breakpoint at address
0x444a, right where
call #0x2400 is made. We execute it and insert a bunch of easy recognizable chars.
Now, how could we understand what that code is actually doing? We can either learn all MSP430’s ISA and read opcode by opcode or we can use a disassembler. The latter is exactly what we’re going to do.
We must paste our bytes and click disassemble. Notice we are disassembling because we want to translate the opcodes into human readable assembly language.
We will get the output of the disassemble.
That’s way better than reading pure bytes. Please notice you’d get the same result with any other disassembler.
Now, looking at the code it appears to be extremely large.
ret as you will see.
After disassembling the bytes we’ve previously inspected, we get the following routine:
2400: 0b12 push r11 2402: 0412 push r4 2404: 0441 mov sp, r4 2406: 2452 add #0x4, r4 2408: 3150 e0ff add #0xffe0, sp 240c: 3b40 2045 mov #0x4520, r11 2410: 073c jmp $+0x10 2412: 1b53 inc r11 2414: 8f11 sxt r15 2416: 0f12 push r15 2418: 0312 push #0x0 241a: b012 6424 call #0x2464 241e: 2152 add #0x4, sp 2420: 6f4b mov.b @r11, r15 2422: 4f93 tst.b r15 2424: f623 jnz $-0x12 2426: 3012 0a00 push #0xa 242a: 0312 push #0x0 242c: b012 6424 call #0x2464 2430: 2152 add #0x4, sp 2432: 3012 1f00 push #0x1f 2436: 3f40 dcff mov #0xffdc, r15 243a: 0f54 add r4, r15 243c: 0f12 push r15 243e: 2312 push #0x2 2440: b012 6424 call #0x2464 2444: 3150 0600 add #0x6, sp 2448: b490 11ab dcff cmp #0xf0a3, -0x24(r4) 244e: 0520 jnz $+0xc 2450: 3012 7f00 push #0x7f 2454: b012 6424 call #0x2464 2458: 2153 incd sp 245a: 3150 2000 add #0x20, sp 245e: 3441 pop r4 2460: 3b41 pop r11 2462: 3041 ret 2464: 1e41 0200 mov 0x2(sp), r14 2468: 0212 push sr 246a: 0f4e mov r14, r15 246c: 8f10 swpb r15 246e: 024f mov r15, sr 2470: 32d0 0080 bis #0x8000, sr 2474: b012 1000 call #0x10 2478: 3241 pop sr 247a: 3041 ret
After a first overview we can clearly see what is of our interest in order to solve the level at addresses
0x2454. Notice how the code starting at address
0x2464 is just an implementation of
INT (interruption) function. Remember that calling INT with 0x7f parameter instantly unlocks the level.
In order to trigger the call, we must not take the conditional jump
jnz %+0xc at
0x244e. In order to not take it, the bytes
0xf0a3 must be placed at offset
We want to know what memory address
-0x24(r4) translates into . In order to know that, we will set a breakpoint at that instruction and see what
r4’s value is. Since we do not see the code section within
When we reach our breakpoint at address
0x444a, we will insert
b 2448 in order to place another breakpoint at address
breakpoints in order to see it was indeed succesfully placed.
We now know the memory being checked is
0x43fe - 0x24 = 0x43da. If we inspect the stack at this very moment, we’ll notice that
0x43da is the memory address from where our input is saved.
This means that in order to solve the level we must simply input the value
So, the solving input (hex encoded) will be:
In this level we’ve seen how to debug code that is not in our available memory at the starting point of execution. We’ve also seen how to use a disassembler in order to understand that code and know where to breakpoint it.