After solving the previous level called New Orleans, explained in detail here, our next objective in the list, and in the map, is called Sydney.
In the manual that appears at the beginning, in the last paragraph, you can read the following statement:
This is Software Revision 02. We have received reports that the prior version of the lock was bypassable without knowing the password. We have fixed this and removed the password from memory.
So, apparently, there will be no more hardcoded password. Let’s find out!
4438 <main> 4438: 3150 9cff add #0xff9c, sp 443c: 3f40 b444 mov #0x44b4 "Enter the password to continue.", r15 4440: b012 6645 call #0x4566 <puts> 4444: 0f41 mov sp, r15 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> 4454: 3f40 d444 mov #0x44d4 "Invalid password; try again.", r15 4458: b012 6645 call #0x4566 <puts> 445c: 093c jmp #0x4470 <main+0x38> 445e: 3f40 f144 mov #0x44f1 "Access Granted!", r15 4462: b012 6645 call #0x4566 <puts> 4466: 3012 7f00 push #0x7f 446a: b012 0245 call #0x4502 <INT> 446e: 2153 incd sp 4470: 0f43 clr r15 4472: 3150 6400 add #0x64, sp
main we can see there is no silly
create_password as before. We still have
check_password at instruction
0x444c and afterwards a test on
r15 that determines whether the password is correct. Once again, we must insert such an input so that
r15 at instruction
0x4450 has whatever value but zero. Let’s check what
check_password is all about.
448a <check_password> 448a: bf90 782e 0000 cmp #0x2e78, 0x0(r15) 4490: 0d20 jnz $+0x1c 4492: bf90 3d67 0200 cmp #0x673d, 0x2(r15) 4498: 0920 jnz $+0x14 449a: bf90 3d33 0400 cmp #0x333d, 0x4(r15) 44a0: 0520 jne #0x44ac <check_password+0x22> 44a2: 1e43 mov #0x1, r14 44a4: bf90 635e 0600 cmp #0x5e63, 0x6(r15) 44aa: 0124 jeq #0x44ae <check_password+0x24> 44ac: 0e43 clr r14 44ae: 0f4e mov r14, r15 44b0: 3041 ret
There are 4
cmp comparing some argument against some contents of memory addresses relatives to
r15. Remember at this point
r15 points at the address, its value is a memory address, where our input is stored because in the
main function, right before calling
check_password, there is a
mov sp, r15 at
You may notice after the comparisons there is always a jump instruction. The first two of them take as argument an
0x4490, PC has that exact value. When solving the expression
$+0x1c will become
0x4490+0x1c. The instruction will then be
jnz #0x44ac (because 0x4490+0x1c = 0x44ac). The jump after the next comparison at
0x4498 will jump to the same address. At that address,
0x44ac, there is
clr r14 and afterwards the function finishes. Remember
clr r14 is equivalent to
mov #0, r14 and that’s exactly what we do not want. You can read more bout
Instruction at address
cmp #0x2e78, 0x0(r15). Unlike the last level, this time there is no
cmp instruction. Remember that
When we talk about “
Back to the code, instruction at
0x448a is comparing two bytes.
0x2e78 with values at
r15+1. The “+1” is implicit to the
cmp instruction because that’s exactly what working with
0x2e78, when perfomring the comparison the result will be 0, the ZF will be set and
jnz won’t be taken.
0x4492 we have
cmp #0x673d, 0x2(r15). Just like before, it is checking if values at memory
r15+3 are equal to
The same rule applies to comparisons at
check_password does is checking if there is indeed:
That’s exactly 8 bytes long password. Since we already know that our input is stored at
r15 and that same
r15 is later used in
check_password, it is logic to think that providing those exact bytes as inpute we can solve the level. So let’s do it. You can input hex encoded
Well, unexpected. What happened? Let’s breakpoint it at
main, right where
check_password is called, in order to inspect memory content.
If you want to easily find where
r15 is pointing, you can write
track r15 and its marker will appear in Live Memory Dump box.
Everything looks allright. What could be happening? Lets debug it step by step using
s command. Type
s to jump to next instruction, inside
check_password function. Inspecting memory with
r commands also shows that apparently everything is right. Well, not everything is right.
We must take into account
There are tons of articles, papers, videos, etc.. addressing endianness. To summarize it, I will quote wikipedia’s article about endianness. There are several ways in which bytes can be arranged into memory. There is little-endian, big-endian (also known as netowrk-endian), middle-endian, etc… with the first 2 being the most common.
big-endianthe most significant byte, the byte containing the most significant bit (the leftmost byte), is stored first, at the lowest address. The least significant byte, then, has the higher address.
little-endianthe least significant byte, (the rightmost byte) is stored first, at the lowest address. The most significant byte, then, has the higher address.
As an example, let’s use the char sequence “RAZVI OVERFLOW”. Suppose each char is 1 byte and we are starting at address 0x00. Here is how a little-endian and big-endian architecture would store the data in memory.
Different endianness representations problems are sometimes rrefered to as the nuxi problem. Here is a very good read about endianness and the nuxi problem.
Since MSP430 is little endian, as stated here, and it has 16-bit addressing, we must convert to little-endian each pair of byte. If it’d have been a 32-bit processor, we’d have to convert each 4 bytes and so on…
So, instead of inserting
2e78 673d 333d 5e63 we must input
782e 3d67 3d33 635e (without spaces) in order for the little-endian machine to read and interpet the data as we expect to.
And we’re successfull :)
In this level we’ve seen the use of
jmp instruction, we’ve seen several data representations such as bit, nibble, byte, word… and the very important concept called endianness.