Microcorruption (Embedded Security CTF): Bangalore

Wow, the Bangalore level of Microcorruption was a good challenge for me.  Like a few of the previous levels, I explored all the dead ends before finally getting it, although I learned a lot from all the things I tried.

That’s not to say that that extra exploration was a waste.  I learned a lot of important concepts from the things that didn’t work, in addition to the things that did work.  I also picked up some ideas from all the ROP tutorials I read; hopefully bits of that will be useful in future CTFs.

This write-up will walk you through the solution and why it works, and also discuss the other things I tried (and why they didn’t work).

Bangalore Release Notes

First lock to have memory protection!

Further down, the release notes say:

Lockitall engineers  have worked for  over a year to  bring memory
protection to  the MSP430---a  truly amazing achievement.  Each of
the 256  pages can  either be executable  or writeable,  but never
both, finally  bringing to  a close  some of  the issues  in prior
versions.

Yikes, this blog post is going to be rough for them.  😬 [Narrator voice:  it would be rough for the author, first]

Bangalore Software

Quickly scanning through the code, we see that main calls some kind of memory protection function, and then calls login.

The login function looks like this (with the curiously cut-off looking conditional_door_unlock above it):

We see that the gets call is limited to 0x30 characters.

There’s puts, getsn, putchar (no printf).

Then, there are the memory protection functions.

Memory Protection

Okay, because I’m a geek, I already read through most (all?) of the lock manual and I know that there are interrupts that relate to the read/write functionality of pages.

In the code, there’s the set_up_protection function:

Its helper functions include mark_page_executable and mark_page_writable.  These correspond to the interrupt information that I showed earlier (from the manual).

Finally, there’s turn_on_dep, which also calls an interrupt as specified by the manual.  

Buffer Overflow

We know that the program is trying to prevent us from writing or executing code where we shouldn’t.  We also have a blueprint for how to turn that on (or off).  In fact, we’ve got functions that already implement that for us.

And we can write 0x30 bytes worth of data and possibly overwrite something.  Let’s give it a try.

0x30 = decimal 48, so I inputted AAAABBBBCCCCDDDDEEEEFFFFaaaabbbbccccddddeeeefffff

It looks like the entire thing gets put into memory.

Since we overwrote the return address, we get insn address unaligned because we’re trying to return to address 0x4545.

It looks like we’ve got quite a bit of room to try some bad things.  Maybe we can put code there that prepares a 0x7F interrupt and calls it.  But it’s likely that we can’t “just” execute code from here (try it out, and you’ll get “Segmentation Fault: can not execute write-only page.")

First, we’ll have to branch to a function that makes this section executable, and then return from that, and then do our unlock-door code.

Digging Into DEP

Before we go any further with that idea, I want to step through the memory protection code.  DEP, or data execution prevention, is also called executable space protection.  This means that certain regions can be marked as non-executable so that buffer overflowed code will throw an exception when execution is attempted.

In order for this plan to work, we have to know:

  1. How big a page is
  2. What page(s) do we want to mark as executable, and
  3. The address of the mark_page_executable function (which should be easy to get)

Pages

According to Wikipedia, a page is “is a fixed-length contiguous block of virtual memory, described by a single entry in the page table.”  I’m not really sure that was very helpful, and searching for “msp430 page size” isn’t getting me anything.

If we look at the manual again, we see this:

So there are 256 pages.  The memory addresses go from 0x0000 to 0xffff, which is 65535 in decimal.  This works out to 256 page sizes of 256 bytes each (if my math is correct).  Decimal 256 is 0x100.  That should make the visualizations a little bit easier.

Relevant page(s)

From this earlier picture, we can see that we might have our entire content (almost) within the 0x100 page border:

If we assume that the pages are all the same size, and that they start at 0x0000, 0x0100, 0x0200, and so on, then we’ll have a page ending at 0x3ff0, and a new one starting at 0x4000.  I changed the input again, this time with patterns of 2 letters instead of 4.  This will let me see where the return address is called.  Previously, I knew it was one (of the two) 0x4545 values.

Lucky for us, it looks like the return address is at the end of one page (0x3fff), which means if we reroute to an interrupt and then come back, we’ll be in the 0x4000 page.  So, we should only have to set one page as executable.  But which one?

The options are either page 0x3f or page 0x40.  If you try a few inputs, you’ll find that page 0x40 is also where our stack pointer returns to, meaning that we’re popping and pushing values on and off the stack in that area.

Since we’ll be regularly writing to addresses in page 0x40, let’s try to make page 0x3f executable, and put our code in the beginning of our input string.

But… how will we do that?  Clearly, we want to call mark_page_executable with the right values in r13 and r14… but how will we get those values there?

ROP

A quick google search for “bypass DEP” will give us some hits for ROP, or return-oriented programming.  From this blog post, I learned some basic ideas about ROP:

Instead of executing the code that we have injected into a process, ROP works by chaining together gadgets (i.e. sets of instructions and functions) that are already embedded in the process’ memory space4.

later on:

ROP based buffer overflows work the same as normal buffer overflows in the sense that the attacker is overflowing the Extended Instruction Pointer (“EIP”). But instead of injecting shellcode into the location of EIP, the attacker’s goal is to input the address of a function they would like called. Further, if the function requires arguments, the attacker must place them (or memory addresses to them) on the stack as well.

Does this mean that we don’t try to change the write/execute status of pages?  It seemed so, and this confusion lead to a lot of dead end ideas.  Before I get to those, let’s talk about ROP gadgets.

ROP gadgets are little snippets of code that we can chain together to move values into or out of memory, do some simple arithmetic operations, and so on.  They end in ret… we’ll use our buffer overflow to control where the ret value takes us, and thus, chain a bunch of instructions together.

Available Gadgets

So if ROP is based on gadgets, what gadgets do we have at our disposal?

// move contents of r14 into r15
445e:  0f4e           mov	r14, r15
4460:  3041           ret

// increase stack pointer by 0xa bytes
4474:  3150 0a00      add	#0xa, sp
4478:  3041           ret

// move a value (that we wrote) into r11
4498:  3b41           pop	r11
449a:  3041           ret

// increase stack pointer by 0x6
44d8:  3150 0600      add	#0x6, sp
44dc:  3041           ret

// clear r15
450e:  0f43           clr	r15
4510:  3041           ret

As you can see, there aren’t a ton of gadgets here.  In larger programs (DEP is a Windows invention), you have enough gadgets that you can essentially make your own (nearly) Turing-complete language.  Here, not so much.  Still, that didn’t keep me from trying.

Dead End Ideas

Like I said earlier, I had a lot of ideas that didn’t pan out.  If I wrote about all of them in-depth, this blog post would be 10k words long, so I’ll give you the cliff notes version:

I wrote up a ton of notes, rubber ducked my idea to various people (and my dog) and didn’t get anywhere.  So what did work?

Bangalore Constraints

All of the ROP tutorials that I read were for Windows or other large instruction sets.  Additionally, because of these large instruction sets, they didn’t bother trying to turn DEP off, because they didn’t have to.  They had enough options to work with already.  So, they only executed existing code.  They never executed their own shell code.

Since we have only a few gadgets, we might have to try and turn DEP off.  As you can see in the previous section, I exhausted all my other options.

There’s one other thing that more or less proves that you have to execute your own shell code.

Previous Interrupts

In previous levels, the interrupt calls looked something like this:

44da <unlock_door>
44da:  3012 7f00      push    #0x7f
44de:  b012 3845      call    #0x4538 <INT>
44e2:  2153           incd    sp
44e4:  3041           ret

4538 <INT>
4538:  1e41 0200      mov    0x2(sp), r14
453c:  0212           push    sr
453e:  0f4e           mov    r14, r15
4540:  8f10           swpb    r15
4542:  024f           mov    r15, sr
4544:  32d0 0080      bis    #0x8000, sr
4548:  b012 1000      call    #0x10
454c:  3241           pop    sr
454e:  3041           ret

Of course, interrupts are used for gets, puts, and so on.  The idea is the same though… you push the interrupt code (i.e. 0x7f) onto the stack and then call the interrupt.  The interrupt then ORs (bis) that value with 0x8000.  The resultant value of sr is 0xff00.  Once that is set, it calls 0x10 and the interrupt happens.

Bangalore Interrupts

In this level, that won’t work.  Why?  Because all of the sr values are mov instructions where the new value is hard-coded.  There’s no bit setting, bitwise ORing, or whatever you want to call it.

Here are some examples:

4454:  3240 0080      mov	#0x8000, sr
4458:  b012 1000      call	#0x10
...
446c:  3240 0082      mov	#0x8200, sr
4470:  b012 1000      call	#0x10
...
44a6:  3240 0091      mov	#0x9100, sr
44aa:  b012 1000      call	#0x10
...
44be:  3240 0091      mov	#0x9100, sr
44c2:  b012 1000      call	#0x10
...
44d0:  3240 0090      mov	#0x9000, sr
44d4:  b012 1000      call	#0x10

You get the idea.  Instead of passing 0x10 in, and then bising that with 0x8000 to get 0x9000 (as in the final example)… instead, we just directly move 0x9000 into sr.

That means less opportunity for us.  There is nowhere in the codebase that 0xff00 gets moved into sr (for a 0x7f interrupt).  There’s also nowhere in the codebase where we can pass our own value into sr.

In short:  the code we need to execute does not currently exist in the code base.  We need to write it, and then we need to be able to execute it.

Shell Code

That begs the question… what code do we need to write, exactly?  Since we want to get 0xff00 into sr, our instructions will look like this:

mov #0x9000, sr
call #0x10

With the help of the assembler, we can see that the op codes for that are:

3240 00ff        // mov #0xff00, sr
b012 1000        // call #0x10

So, we’ll need to put 324000ffb0121000 somewhere in our payload.

How to Mark a Page as Executable

One of the ideas that showed up many times in my “dead end ideas” section was the hope of marking a single page as executable (yet never being able to get that to work).  Obviously, I finally figured it out, or I wouldn’t be writing this blog post.

I spent a lot of time on the idea of getting the right values into r13, r14, and r15.  As it turns out, I didn’t have to do that.

I gave a lot of examples earlier of interrupt calls.  I only included two lines from each, but if you look at the preceding line, you’ll notice that all of the interrupts have this line before the mov value into sr and call 0x10 lines:

44ba:  3180 0600      sub	#0x6, sp

How can this help us?  Let’s look at an example first.

Mark-as-executable Example

We can restart the program and look at one of the times that we mark a page as executable, and use that to guide us.

This is a screenshot of when we’re trying to mark page 0x44 as executable.

You can see the argument type (0x0) in blue, and the page number (0x44) in green.

Then, we subtract 0x6 from the stack pointer, which looks like this:

This picture is what we need to recreate, but with our values.

The pattern is:

[addr that sp is pointing to]  [doesn't matter]  [doesn't matter]  [3f00]  [0000]

Partial Solution

We have 16 bytes worth of filler in our buffer overflow before we add our first return address.  If we put our 324000ffb0121000 value at the beginning, we have:

324000ffb0121000 + [remaining 8 bytes of filler] + [first return address]

For this partial solution, let’s throw a bunch of 41s in for filler, and have our return address be 0x44ba, which is the sub 0x6, sp instruction:

324000ffb01210004141414141414141ba44

Here’s a view of what memory looks like if we step to 0x44ba and then step one more time.  I’m continuing with our same “green is where the page number goes” and “blue is where 0000 goes” coloring.

Luckily for us, the interrupt is not being smart about the values passed in.  It simply moves the stack pointer by 6 bytes, and then grabs the values that are in the “correct” spots.  As a reminder, here’s the pattern we’re following:

[addr that sp is pointing to]  [doesn't matter]  [doesn't matter]  [3f00]  [0000]

Also lucky for us, the 4141 and ba44 values are kind of in the middle of it all… but that doesn’t matter.  We just need 3f00 and 0000 in the green and blue box areas.

So, let’s add that to our attack string.

324000ffb01210004141414141414141ba443f000000

Our attack string is missing one final thing… after we’re done with the interrupt and have marked page 0x3f as executable… we need to actually go and execute page 0x3f.

We need to add one final return address, to the start of our attack string (to execute the mov and call 0x10 instructions we wrote).

That address is 0x3fee, which will get added to our attack string as ee3f

324000ffb01210004141414141414141ba443f000000ee3f

Bangalore Solution

To recap:

Our attack string looks like:

[shell code] [8 bytes of filler] [addr 0x44ba] [page value of 0x3f] [value 0x0] [addr of start of attack string]

The values for each section are:

[3240 00ff b012 1000]   [4141414141414141]   [ba44]  [3f00]  [0000]  [ee3f]

All together, that’s:

324000ffb01210004141414141414141ba443f000000ee3f

To solve this level, type solve, and then enter in the attack string of 324000ffb01210004141414141414141ba443f000000ee3f.