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:
- How big a page is
- What page(s) do we want to mark as executable, and
- 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:
- Move 0x3f into r14, then jump into the loop at address 0x44e8 to mark page 0x3f as executable. This works, but then it tries to mark page 0x40 as executable, which is our stack pointer area as mentioned earlier (where we need to keep writing values). This results in “Segmentation Fault: can not execute write-only page.”
- Okay, then can I write somewhere else using
gets
and then go back and mark that spot as executable? I couldn’t get this to work either. - If I called
turn_on_dep
again, would that turn DEP off? Nope. - Can I overwrite one of the lines that turns on DEP using
gets
and then restart the program? … no - Can I load 0x30 into r14 and then go somewhere else interesting? … also no.
- Can I move 7f into somewhere that will later be interpreted as an interrupt type?
- Can I somehow get
0x3f
from r11 into r15 or r14, and then callmark_page_executable
? Nope, I couldn’t get 0x3f into the right registers. Even if I got close, my values would be clobbered before I could callmark_page_executable
. - Could I read through the MSP430 user guides and find something that exists on the real MSP430 that isn’t mentioned in the “Lock it all” manual, but still works in Microcorruption? No.
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 bis
ing 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 41
s 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:
- We can write a buffer overflow because we’re passing 30 bytes into the
gets
call. - The code to call a 0x7f interrupt does not currently exist in the code base, we need to write it. Additionally, there aren’t enough gadgets for us to do an execute-only ROP attack.
- If we jump into an interrupt call midway, we can position values such that they are interpreted as variables that were passed into the interrupt handler. **We can do this to mark page 0x3f as executable, and then execute custom shell code that we have inserted. **
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
.