Wednesday, September 10, 2014

Microcorruption - Cusco

As before we should go on the assumption that we aren't going to get the password - and so we don't concern ourselves with what the actual password is but only with details about how the program processes the password that you give it.

The kind of things that we might want to consider are:
* how long of a password can we put in there?
   - does a long password blow out of the stack?
   - what is the starting address of the password relative to the code?
* how does the program handle special characters?
   - unicode?
   - special delimiters?
* what functions does the program use to deal with the input that you provide it?
* are there any data structures being used to store that data that we can exploit to our advantage?
* does register under/overflow have a use?

These are just a few of the kinds of things we can begin to think about. For this level we will really only need to consider the first couple involving length of the password - more on that in a minute.

We have to have a good understanding of the code to see where the weakness is. Since this level represents an incremental update from the previous version, it seems that a reasonable place to start looking is where we looked last time... in login:


4520:  b012 5244      call #0x4452 <test_password_valid>
4524:  0f93           tst r15
4526:  0524           jz #0x4532 <login+0x32>
4528:  b012 4644      call #0x4446 <unlock_door>
452c:  3f40 d144      mov #0x44d1 "Access granted.", r15
4530:  023c           jmp #0x4536 <login+0x36>
4532:  3f40 e144      mov #0x44e1 "That password is not correct.", r15
4536:  b012 a645      call #0x45a6 <puts>
453a:  3150 1000      add #0x10, sp
453e:  3041           ret


At line 4520 the program calls "test_password_valid". It might seem like looking in test_password_valid is a good idea - after all we have previously found the password or some variant of it there. No such luck this time, there is a hardware call to do that comparison and that hardware black box puts some value into memory and program flow eventually looks at that value.

That value ends up in R15 and is then examined and if the zero bit is set based on the results of the test in line 4524 (meaning the password was wrong) then we jump to 4532 where we get the address of a string and at 4536, we 'puts' that message to stdout add 10 to the stack pointer and get out.

It merits looking at program flow for a right password also.  If the zero bit is not set when we get to 4526 (meaning the password was right) we fall through to unlock_door get the address of a message and JMP to 4536 where we 'puts' that message to stdout, add 10 to the stack pointer and get out.

In both cases, success or fail, the stack pointer is incremented by 10 to 0x43FE whereupon the instruction pointer returns to that address and executes WHATEVER CODE IS THERE...

The idea is of course that the stack pointer is incremented beyond the user password to an address for program execution which in both cases is 0x443C which is stop_prog_execution.

But what if some other address was inserted into 0x43FE? Well, the instruction pointer would return to that address and start executing that code instead. What if someone put the starting address for unlock_door instead of stop_prog_execution into 0x43FE? Then, whether or not the password was right the door would be unlocked.

It is a simple matter to find the starting address of unlock_door and (remembering that we are little endian) fill the password with 16 characters of garbage and overwrite memory location 0x43FE with 4446. It is also convenient that 44 corresponds to ascii D and 46 to ascii F.

Our winning entry is then:

1234567890abcdefFD

Friday, September 5, 2014

Microcorruption - Hanoi

These next few levels, if you aren't familiar with delving into assembly code, are a great place to start stepping through the code to get an understanding of program flow.

From looking at the code we can see that there are some basic structures that we will see over and over again in the coming levels.

trap


The trap interrupt is a software call to a hardware interrupt that results in some action being taken based on the status of certain registers - in this case the contents of the status register are used. The action taken is either to unlock the lock or set some value in another register (like r15) indicating that the supplied password was correct.

We will use this trap interrupt whenever we can. Page 8 of the manual starts the listing of valid status register contents. While some of the beginning entries seem unusable, the last entry is clearly gold: "interface with deadbolt trigger to unlock door..."

If you can set sr to 0x7F and call the trap, you win.

low level init, do copy data, do clear bss

These are some housekeeping routines called at the start of the program that aren't useful for our purposes.

main


Mostly we can expect that this is where the action would begin. Throughout the various levels we will see a variety of program styles. Some will have main filled and very few function calls, whereas this particular level the only purpose of main is to call login and then drop through to:

stop_prog_exec


Terminal point for program execution. If you get here, you failed.

unlock_door


As you can see from the code the action of unlocking the door is exactly as described above: Get 0x7F into the sr and (eventually via INT) call the trap. This doesn't always occur - hardware implementations generally don't have this function in any meaningful fashion.  But if it exists and you can get the code to call it, you win.

INT


Putting this out of order because it goes with unlock door nicely, this performs some housekeeping of preserving register state and calling the trap.

**note** As I mentioned in another posting the game changes over time to prevent people from cutting and pasting entries. In the version I played, 0x7F isn't actually the status that needs to be in the sr. I needed 0xFF to be in the sr - even though the manual said 0x7F. Generally I will refer to 0x7F as being required, but my code will always change to 0xFF.

putchar, getchar, getsn, puts


assembly implementation of C standard io lib functions. Used for user i/o. We will take advantage of their weaknesses and the code that uses them unwisely. There will come a time when the program asks for a 16 character limit password, but will ultimately accept any length which allows us to overflow the buffer. The combination of that limited expectation without following through on i/o limitations allows this kind of buffer overflow exploit. This is how we will be getting our desired value into the sr.


test_password_valid


we can expect to have to endure this function every time, but nothing of any real note happens in here.

login


The start and end of the action generally speaking. There is some point usually in this function where a branch is taken or not the ultimately allows the weakness. It varies by level, but ultimately occurs here - not in test_password as one might expect.

Having an understanding of program flow and the parts each of these functions is important. Knowing that login generally contains the meat allows us to focus on that for our weakness. Let's look at the code:We see that the true comparison for whether or not the door will be unlocked happens at line 455a and has nothing to do with test_password_valid function. The true comparison checks for a hardcoded value (0x9B) at a fixed address (0x2410). If that value is at that address code execution falls through the jne branch to "access granted" followed by a call to the unlock_door function.

4544:  b012 5444      call #0x4454 <test_password_valid>
4548:  0f93           tst r15
454a:  0324           jz $+0x8
454c:  f240 6100 1024 mov.b #0x61, &0x2410
4552:  3f40 d344      mov #0x44d3 "Testing if password is valid.", r15
4556:  b012 de45      call #0x45de <puts>
455a:  f290 9b00 1024 cmp.b #0x9b, &0x2410
4560:  0720           jne #0x4570 <login+0x50>
4562:  3f40 f144      mov #0x44f1 "Access granted.", r15
4566:  b012 de45      call #0x45de <puts>
456a:  b012 4844      call #0x4448 <unlock_door>


We also know that the starting address of where our input string occurs is 2400. Because the code doesn't actually prevent us from entering more than 16 characters (despite using getsn which can take a limiting size) we can fill addresses 2400-240F with garbage and put 9b at 2410, which will result in unlock door being called.

Here is a sample winning string (don't forget to check the box 'hex encoded') which does exactly that.


414243444546474849505152535455569b