Monday, November 14, 2011

Man vs. ROP - Overcoming Adversity One Gadget at a Time

Introduction

I recently discovered a rather simple stack-based buffer overflow in a legacy application that shall remain unnamed. With DEP disabled, exploiting the vulnerability was trivial. It’s no longer 1999, however. If you want to write any exploit these days you have to at least be proficient in return-oriented programming techniques to bypass data execution prevention. This article will detail some of the nonorthodox techniques that were required to get code execution for this particular vulnerability.

Prerequisites

Before reading this, you should be rather comfortable with return-oriented programming techniques. I highly recommend checking out the following presentations/articles before diving in to the techniques I’ll describe:

Also, if you enjoy Peter’s article, please be sure to donate to his fight against plagiarism in the infosec community.

Vulnerability Synopsis

The application was vulnerable to a simple stack buffer overflow when it read in a particular value from a config file. An excessively long value overwrote the stack cookie, saved frame pointer, return address, SE handler, and a virtual function pointer on the stack. Because the app was compiled with the /GS flag, overwriting the saved EIP wasn't going to cut it since the stack cookie would have been validated and the process terminate upon detecting the clobbered cookie value. No problem. Fortunately, the SE handler was overwritten and the app crashed upon calling the virtual function pointer - both valid avenues to exploitation. It seemed all too simple until I started trying to find valid ROP gadgets. This is where the fun/frustration began.

Here are some additional details worth noting:

All but three modules (the executable and two DLLs) were compiled with ASLR. However, only one of the DLLs could be used for building a ROP chain since the other DLL contained a bad character, thus truly limiting the amount of potential ROP gadgets.

The following were bad characters: '\x00\x0a\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x28\x29\x2a'

The stack was always mapped to the same base address – 0x0017xxxx

Let’s just say that the virtual function pointer was overwritten at offset 1400 and the SE handler pointer was overwritten at offset 1700

Exploitation

As stated before, I had two angles of attack at my disposal – SEH overwrite and a call to a virtual function pointer. I chose to explore the SEH route first.

When writing a ROP exploit for an SEH overwrite, you need specific gadgets to perform the stack pivot (i.e. get ESP to point to your shellcode). The traditional gadgets that would perform this task are as follows:

XCHG reg, ESP; RETN

MOV reg, ESP; RETN

CALL reg

POP reg; JMP reg

ADD ESP, offset; RETN

Unfortunately, upon passing control to the SE handler, there were no registers under my control nor were there any registers that pointed to my buffer. The only option left for me was MOV reg, ESP; RETN. The ROP compiler I used found no such gadget, however aside from a lone ADD ESP, 0x0C; RETN which just wasn’t going to cut it since my buffer was approximately 0x800 bytes away from ESP. Originally, I thought that the ADD ESP, 0x0C; RETN gadget might work because it would essentially “walk” down the stack returning to invalid memory addresses along the way until it hit the beginning of my buffer. Then, upon attempting to return to an invalid memory address the SE handler that I overwrote would then kick in and return to itself, continuing to walk the stack. I was naive however and it didn’t work that way. The unhandled exception handler kicked in and I got nowhere.

I tried to get creative with gadgets. For example, I thought that I might be able to find a ROP variation to the classic pop-pop-ret instruction sequence:

POP regPOP regPOP ESP <-- this would point to the beginning of my ROP chainRETN

Alas, I found no such gadget. So I decided to shift my attention to the call to the virtual function pointer – CALL DWORD PTR [EAX+A0].

At the time of the crash, I had full control over EAX. Also, ESI and EDI contained pointers to my buffer in the stack. [EAX+A0] is a pointer to a function pointer, however so you would need to find an interesting pointer to a pointer within the DLL that I was targeting. I found no address in the DLL. I needed a pointer to a pointer that I could control. Solution: the buffer that I controlled on the stack. The problem was that the stack was mapped to 0x0017xxxx which contains a null – one of the bad characters. I mentioned that the function pointer was overwritten at offset 1400. So the solution to getting EAX to point to the stack was a partial EAX overwrite.

After running the process in WinDbg and tracing calls to CALL [EAX+A0], I observed that all calls were being made from the stack – 0x0017xxxx. Due to the little-endian nature of the Intel architecture, that means that overwriting EAX with just three bytes would allow EAX to point to any address with a null high-order byte – i.e. 0x00414141.

Upon crashing, I noticed that my buffer reliably resided within a range of approximately 0x40 bytes. So at this point, I was dealing with the following:

Ability to point EAX to the stack using a partial EAX overwrite

A buffer that shifted slightly at each crash but only within a range of about 0x40 bytes.

So now I potentially had a range of addresses that I could “spray” with pointers to a stack pivot gadget. Since the sprayed addresses reside in the stack, I guess you could call this method stack-spraying ;D. I found a few XCHG EAX, ESP; RETN gadgets and got them to execute reliably but there was a problem, however. Executing a stack pivot twice “unpivots” the stack and you’re back to where you were when you started. After many hours of banging my head on the desk I realized that I could use a series of alternating gadgets to only execute the stack pivot once then jump to a series of ROP-NOPs that jumped over the remaining stack pivot gadgets. I tried my best to illustrate this concept in the following diagram:

Fortunately, upon executing CALL [EAX+A0], the program reliably landed on one of the stack pivot gadgets – Gadget1. Gadget 1 then returned to Gadget 2. The purpose of Gadget 2 is to walk the stack while jumping over Gadget 1. I then added a few ROP-NOPs - &RETN followed by the primary ROP chain, which eventually called VirtualProtect(). Utilizing these techniques ultimately resulted in an extremely reliable exploit.

Conclusion

Just because the solution isn’t staring you in the face doesn’t mean it’s not there. Finding specific ROP gadgets can be like trying to find a needle in the haystack. With enough patience, persistence, and a good ROP compiler at your disposal, you’ll be well on your way to popping shells in the modern world.

Future Work

I would really like to get the ADD ESP, 0x0C; RETN gadget to work. I thought it might work if I overwrote the pointer to the next SEH handler with 0xFFFFFFFF, marking my crafted handler as the last in the chain and thus the handler of last resort. This wasn’t the case though. If anyone has successfully used this method to walk the stack down the attacker-controlled buffer I would love to hear how you accomplished it.

9 comments:

Nice post.Would have been better to see the actual sploit and software though ;) (unless you're bound by an NDA :s).

So basically if I understood well, you're using the vcall:CALL DWORD PTR [EAX+A0].

And the EAX+A0 pointing to the stack which contain a retslide?Are you sure there were no others gadgets like this:ADD ESP, offset; RETN?Since you can overwrite EAX with prolly anything you want at the vcall, maybe digging in the code might tell you why the EAX register can be overwritten (and so do it in the SEH :)).

Anyway, if you want a ROP exploit that overflow the SEH structure and using the ADD ESP, offset; RET as a gadget, you could go see there: http://binholic.blogspot.com/2011/06/ndh2011-demo-virtuosa-full-rop.html ;).It was a long time ago, it was quite hardcore to write since it was the 2nd time ever i did ROP ><.

There were several other ADD ESP, offset; RETN gadgets but none of which had a large enough offset to point to my buffer. So I thought that because I overwrote the SE handler, that the ADD ESP, 0C; RETN gadget would suffice because if it tried to return to say a bunch of nulls or other random data along the way that it would redirect execution right back to my SE handler. This was not the case.

I know someone who claims to have successfully used this technique but I think he may be keeping it to himself. ;D

I didn't see the ADD ESP, offset; RETN you were referring to from your blog but I assume you used it as a stack pivot, right? My goal was to use it as a way to walk down the stack until it hit my buffer. Does that make sense?

I do not see the point of "walking the stack" as you say. If you do "walk the stack", it already means that you are inside the buffer that contain your ROP payload :).

The stack pivot is in the ropstack_before_seh function in the exploit ;).The sploit has a pretty advanced ROP payload as it encode/decode pointers, do import resolution, allocation, memcpy, etc.

For the add esp, 0xc # ret ... what is the 4th dword? (as it would be the ret address).It would probably not work as we are in nx/dep memory ;) unless it is an address you can control (and that it allows you to pivot to your buffer).In any case, you have to pivot to your buffer by any means necessary ...

I'm not in the portion of the stack that I control. I want to 'walk' past portions of other stack frames that I don't control - approximately 800 bytes. The majority of the data in between the stack frame of the SE handler and my buffer consists primarily of nulls. So obviously, If it was to return to an address on the stack somewhere in between, it wouldn't work because of DEP.

I see the stack pivot you're referring to in your exploit. It would have been nice to have had an offset that large. As I said, there was no such gadget with a large enough offset though. :(

As you said, "you have to pivot to your buffer by any means necessary." I couldn't agree with you more. Regardless, I eventually got everything to point to my buffer and the exploit works rather reliably using the method I described in the blog post.

Hey man. nice blog :) Can you please explain again how you got the addresses of the ROP gadgets on the stack even though the addresses have null bytes? I overflowed a buffer but as you know, strcpy will not let me write null bytes(part of the gadget addresses) on the stack.

Whether or not you're able to use addresses with nulls depends upon what function triggers the vulnerability. What triggered the vuln in this case wasn't a strcpy. I mistakenly listed 0x00 as a bad char. Sorry if that was misleading.

It's always too bad when the program is teasing you. I've found an exploit and finally realized I need to create a ROP chain to successfully exploit it. the buffer is like 900 something bytes away from ESP (and EBP points into the buffer, only about 10 bytes away from SEH) and an exception _usually_ gets triggered during the SEH overwrite but sometimes I can randomly get control of EIP just out of nowhere and I'm still not sure why that happens exactly, usually happens once or twice in a row every 5 or 6 times I crash it I've gotten it to execute a short chain of ROP NOPS one time, but tried expanding it much larger and couldn't get it to execute at all. The annoying part is that the main() function where the crash takes place doesn't have SafeSEH and it has tons of pivots throughout it of every size imaginable quite a few between 400 - 5,000! including a few that are almost exactly the size to land at the very top of my buffer I plan to place my ROP chain but every single one starts with a null byte.