Monday, 28 March 2011

Shellcode Tutorial - Part 3

So far I have made references to 'flow control data', in this post I want to explain and clarify what this really means.

A program, like our authentication example, is not just a big list of instructions with one following after the other. A program makes decisions, it compares data and performs different tasks according to the result. This is achieved through what are called jumps. A jump in a program is just a decision (based on some program data) as to whether or not the program should 'jump' to another part of the program, or carry on as normal. These jumps are sometimes called branches.

A certain type of branch that is very common is called a function call. When writing a program is is often useful to compartmentalise the the symantic purpose of the program into little chunks. These little chunks are called functions. For example, you might have a function that calculates a Christmas bonus based on salary and performance of your employees. Rather than writing the same calculation over and over for each employee, you would simply write a Christmas bonus function, and call that function with information from each employee. That way you only have to write the calculation once.

These function calls happen all the time in pretty much every program ever written. We are interested in exactly how a function is called in terms of a running process. When a program reaches a function call, the first thing it does is make a note of what is called a return address. This is necessary as when the function completes (in our example; when the Christmas bonus has been calculated) the process needs to return control to whatever the program was doing just before the function call.

So when the function completes, it checks the return address and jumps back to the calling procedure, just after it's own function call, so that the program can continue on as it was. This return address is the target of the majority of buffer overflow attacks. When we overflow our buffer as in Part 2, it is often the case that a return address (of some kind) is overwritten by the overflowing data. It is overwritten with whichever part of our input data happens to end up over the location in memory when the return address was stored


When the function completes, and attempts to jump back up to the calling procedure, it reads the (now incorrect) value stored in the return address, and jumps to this new memory location. This is why the program crashes with a segfault; the program tries to jump to an address that is outside it's allocated memory and the operating system will not allow it (accidentally overwriting a return address has a very very small chance of overwriting it with information that will not cause this kind of crash, as the memory allocated to a process is comparitively tiny in relation to the total potential address space).

So what we can now do, as a hacker, is supply a long username (taking our authentication example from part 2) that causes a carefully chosen address to be overwritten into the return address, whatever we point the address to is what the program will run once it jumps to that address. But what do we jump to? This is the tricky part.

We now have the power to run any set of instructions we like, provided they can already be found somewhere in the memory available to the process. So we could jump to some other random procedure and possibly cause some harm, but we are still confined to just executing instructions that were already present in the program.

 Or are we? Remember that we have just written a great big long username into memory, and only the tail end of it is actually doing anything useful (the part that has been carefully crafted with our desired return address). We can use the rest of it to store a sequence of instructions, and then point the return address to the beginning of our sequence of instructions, now we have total control of the process!

The Shellcoder's Handbook: Discovering and Exploiting Security HolesThe sequence of instructions is called the shellcode, because classically it has always been used to spawn a local or remote shell. The process of writing a shellcode from scratch is quite complex and involved and I plan to address it in a later part to this tutorial (I highly recommend The Shellcoders Handbook for those who want to learn more, link to the side). For now just be convinced that we can A) Cause the remote process to jump to any location in memory that we choose, and B) write a sequence of instructions to memory to execute any (albeit small) program we like.

13 comments:

  1. freakin win, and good job explaining it straighforwardly

    ReplyDelete
  2. Ah, part three, finally. Awesome tutorials!

    ReplyDelete
  3. Getting slightly less confusing...

    ReplyDelete
  4. you sir, are the proud owner of an awesome blog.
    followed

    ReplyDelete
  5. awesome blog.. dont do a lot of this kind of programming, but still good reading

    ReplyDelete
  6. You know what, I really am having trouble getting this. Can you make it more clear?

    ReplyDelete
  7. Haven't touched programming in years, brings back a lot of memories

    ReplyDelete
  8. havent seen this yet, might check it out though

    ReplyDelete
  9. Nice man, following for sure.

    ReplyDelete
  10. Hmmm, can shell-code overwrite occupied memory-space?

    ReplyDelete
  11. Its nice to have tutorial like these at hand when we need them. Great tips and guides you share man. Will certainly follow.

    ReplyDelete