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:
Stephen Sims - "SANS Security 660 Series: Return-Oriented Programming and Exploitation"
Dino Dai Zovi (@dinodaizovi) - "Practical Return-Oriented Programming"
Peter Van Eeckhoutte (@corelanc0d3r) - "Exploit writing tutorial part 10 : Chaining DEP with ROP – the Rubik’s[TM] Cube"
Stephen Sims - "SANS Security 660 Series: Return-Oriented Programming and Exploitation"
Dino Dai Zovi (@dinodaizovi) - "Practical Return-Oriented Programming"
Peter Van Eeckhoutte (@corelanc0d3r) - "Exploit writing tutorial part 10 : Chaining DEP with ROP – the Rubik’s[TM] Cube"
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:
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
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:
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:
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 reg
POP reg
POP ESP <-- this would point to the beginning of my ROP chain
RETN
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:
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.