The next post in the series of solving the microcorruption.com ctf challenges continues from the previous challenge called New Orleans. This challenge is titled Sydney.
If you were to read the description when you enter the challenge, one would see the following right at the bottom:
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.
Lol. Lets take a closer look.
sydney
Performing a static analysis of the code, one can see that this time round there is no silly create_password
routine or something similar.
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
In fact, just a simple get_password
and check_password
routine before the tst r15
call at 0x4450
. The call to get_password
just ends up with a syscall, prompting you for a password, so that is not really interesting to us. What is interesting though is check_password
:
448a <check_password>
448a: bf90 4c7e 0000 cmp #0x7e4c, 0x0(r15)
4490: 0d20 jnz $+0x1c
4492: bf90 2142 0200 cmp #0x4221, 0x2(r15)
4498: 0920 jnz $+0x14
449a: bf90 4522 0400 cmp #0x2245, 0x4(r15)
44a0: 0520 jne #0x44ac <check_password+0x22>
44a2: 1e43 mov #0x1, r14
44a4: bf90 587d 0600 cmp #0x7d58, 0x6(r15)
44aa: 0124 jeq #0x44ae <check_password+0x24>
44ac: 0e43 clr r14
44ae: 0f4e mov r14, r15
44b0: 3041 ret
At first sight it looks like the code does a number of compares to values at an offset from the memory address at r15
. Could these be parts of the actual password? Lets inspect with the debugger. Setting a breakpoint at 0x448a
and continuing the CPU (entering a password of test when prompted) until we reach it should help in revealing what is happening.
Hah, so after the first cmp
instruction, the status register is 0x4
(N), meaning the jump 14 bytes onwards to 0x44ac
will be taken, effectively ending the check_password
routine prematurely. The bytes in r15
at the time of the first cmp
instruction was 0x439c
, which in turn pointed to 0x7465
in the memory dump (visualised with read r15
in the debugger). The bytes in memory is clearly the password (test in this case) that I entered when I was prompted.
>>> ' '.join([hex(ord(x)) for x in 'test'])
'0x74 0x65 0x73 0x74'
So, lets take the bytes in the three cmp
calls and enter that as the password, keeping our breakpoints and seeing what the CPU does then. The six bytes of interest are: 0x7e 0x4c 0x42 0x21 0x22 0x45
.
>>> '7e4c42212245'.decode('hex')
'~LB!"E'
Resetting the CPU, entering ~LB!"E
as password and continuing untill we hit the breakpoint at 0x448a
and then stepping past the first cmp
and jnz
instructions should now look like this:
Hmm. The value 0x7e4c
was at 0x439c
(the address r15
points to), but the cmp
call set the status register to 0x4
(N), ending the check_password
function again. :|
What I think this challenge is supposed to teach you is about the endianness of the CPU which means it stores values in little endian format in memory. What that means for us is that that password values should be provided as 0x4c7e
instead of as 0x7e4c
like we did. So, lets re-arrange the password we enter, and continue to the breakpoint again.
>>> '4c7e21424522'.decode('hex')
'L~!BE"'
Woohoo. This time the jnz
instruction is not taken as the status register is now 0x3
(CZ) and the next values provided as part of the password checked. By now you might think you have won and decide to just continue the CPU to unlock the lock.
Well, no. See, we missed the part where another cmp
happens just after 0x1
is moved into r14
.
This means that when cmp #0x7d58, 0x6(r15)
at 0x44a4
is called, we will be comparing to 0x0
(the bytes at r15+6
), resulting in the jump at 0x44aa
not being taken, clearing r14
before the routine finishes.
So, to prevent that adn as a final password, we need to enter those the bytes 0x58
and 0x7d
too to unlock the lock.
>>> '4c7e21424522587d'.decode('hex')
'L~!BE"X}'
Continue the CPU and unlock the lock!
solution
Enter L~!BE"X}
as ASCII or 4c7e21424522587d
as hex encoded input.
other challenges
For my other write ups in the microcorruption series, checkout this link.