Hi. Welcome to this week's Whiteboard Wednesday. I'm David Maloney, senior security researcher on the Metasploit team here at Rapid7. Today, we're going to be continuing our series on exploiting buffer overflows, the exploit techniques that you use and the mitigation strategies you use to protect against those techniques. This week, we're going to be talking about structured exception handler overwrites or SEH exploits.Show more Show less
Previously, we talked about stack cookies and how they prevent an easy saved return pointer overwrite exploit. We needed a new way of getting reliable control over execution of a program when we have a buffer overflow. Stack cookies make it difficult to successfully overwrite that saved return pointer and then just let the function exit. We needed something different.
That's where structured exception handlers (SEH) come in. A structured exception handler is something built in by the compiler into a program that will try to handle any kind of error that comes up during the course of running the program. What happens is if an error occurs inside the program while it's running, it's going to look at this chain of structured exception handler records. It's going to start at the top. It's going to look at this pointer right here. Each structured exception handler record has two parts. It's going to have a pointer to the next record in the chain so that the list stays linked, and it's going to have a pointer to the actual exception handler.
When that exception happens, it's going to look at the first one in the chain, find the pointer to the first exception handler that it should try. It's going to go and run the code at this address. That code is expected to try and do a couple of different things to find a way to handle the error condition that has happened. If the error still exists after this code has run, what happens is it looks up here at the pointer to the next SEH record and it hops down to that one. Then, it goes to the exception handler pointed to by the SEH record right here and tries the same thing. If the error condition still exists, it continues down that chain until it gets the whole way through that chain. If that exception hasn't been handled then the program will typically crash.
What we do to get around things like stack cookies is we're going to actually try and overwrite this pointer to the exception handler on one of our SEH records. If we look at our stack, it's changed a little bit from our previous example. But we still have our main function here. We've got our do something in here with the stack cookie basically protecting our saved return pointer. Somewhere in here we also have one of these SEH records including the pointer to the next SEH record and the actual pointer to the exception handler itself.
As we overflow this buffer of this foo string here, again like we did in the previous scenario, that data is going to overflow that buffer and start coming down the stack just like we did in the previous example. But in addition to overwriting all this, it's going to overwrite the address right here at the structured exception handler which the program is going to use to tell it to go jump to some piece of code, one of these handlers over here. Just overwriting this by itself is not enough to control execution. You have to create an actual error condition to trigger it to look at one of these SEH records. The most common and easy way of doing this of course, though, is to just continue writing this bad data all the way down until you hit the end of the stack. What will happen is it will generate an exception because you've tried to write past the last address of memory that it's allowed to access, creating an error condition that must immediately try to be handled by the structured exception handler chain.
When we control this value, it's going to need to get us to land back in a piece of memory that we control. Typically what you'll see in a structured exception handler overwrite is this value will point to an address to a known set of instructions that usually are what's called a POP POP RET or a POP, a POP and a Return. What happens, without getting too into the nitty-gritty here, is that instruction set will alter the stack slightly and return execution right back to where this NSEH record is, which we've also overwritten. What happens is we put a certain address in here to existing code that exists within the program that does that set of instructions we need which returns execution here. From here, depending on how much space we have either above or below, we put a set of...we overwrite this value with a set of instructions that tells it to jump either backwards or forwards past the SEH record. Then, in our buffer overflow we just put our payload shell code in that particular area.
By overwriting this SEH handler and then creating an intentional error condition, it will jump to that POP POP Return instruction which will return execution to here which we then jump into shell code that we've landed somewhere in the memory space. Because we do it this way, we never actually exit the do something function before we've altered the execution flow, so the stack cookie never gets evaluated. The program does not perform the check saying the stack cookie's been corrupted, something bad is happening here. This is how we overcame stack cookies as a protection when writing buffer flow exploits.
Tune in next week when we're going to talk about the mitigation known as SEHOP which is set up specifically to protect programs against this kind of attack. Thanks for joining us, and we'll see you next week.