While I’ve written a lot of code in my time, I don’t think I’ve ever firmly appreciated how complex it can be to write secure code. We go about our lives taking for granted that our apps will just work, and hopefully the programmers used the right techniques to not get us in trouble. Recently, I’ve started exploring buffer overflows (BOFs) as part of my Penetration Testing Professional (PTP) course by eLearnSecurity. I had heard the term “buffer overflow” and have actually seen it happen while using an application but never from a security angle. Generally, it appeared as an app crash that was resolved by restarting it, resolving my immediate issue and allowing me to carry on. But I always knew that there was much more happening underneath. This article is my braindump of my deeper exploration in an attempt to make reinforce this new knowledge in my own head. Hopefully it can help you, too.
Having just finished the BOF topic and having a better understanding of what actually causes them, it made me realize a few things:Building a good working BOF is non-trivial. Having a deep understanding of system architectures is a key advantage to building them. I’m grateful for my developer background, since it helped me understand the logic flow.
The premise of a BOF is to be able to identify payloads that can cause a memory corruption error in an application or process, because an invalid instruction has overwritten an important memory space (AKA a register). Hence why having an understanding of system architectures is so important. You’re working to overwrite registers at a machine code level which is basically unreadable without a good debugger like Immunity Debugger or IDA. And unless you’re an expert in assembly code (and sometimes even if your are), it can be challenging in nailing down the flow-control of a binary that you’ve opened up via one of the debuggers. I mean if you can read the following dump from Immunity Debugger , then I congratulate you.
Image 1 Immunity Debugger
What I learned, though, is that you don’t need to be able to read all of that to build and leverage a BOF (at least on a 32-bit system). The hardest part is actually finding a BOF vulnerability. This is why fuzzers are such a vital tool. A fuzzer is a software tool that can be used to flood inputs with random characters until a program crashes, recording the crash info and letting you come back to later to further investigate. Those random chars are what we call a payload.The Payload
Applications instructions are managed via a memory stack that tracks different points in the app lifecycle including the return memory address of a method caller. The main register that tracks this is the instruction pointer (EIP) and is the key target of BOF exploitation. If we’re able to change the memory address stored by the EIP, we can affect the flow of the application. So if we break it down, we want the following in our payload:Prepend junk bytes that get us to the EIP memory space Overwrite the EIP with a command that will point us to the base pointer (EBP) instead of the correct next instruction Shellcode that runs our exploit code starting at the EBP
Let’s focus on number 1 first.
The payload you’re passing to the application is comprised of junk data and is meant to be long enough to override all of the registers in the stack prior to and including the EIP. This will cause the windows (in this case) app to crash, because the EIP is now filled with junk data instead of a valid return memory address. Here’s an example of some junk data:
This was created using the Mona plugin for the Immunity Debugger and is passed to the app in some fashion like a command-line argument, a file being read into the app or even a banner from an FTP server. By sending this and getting the app to crash, you can begin to narrow down if there’s a BOF to exploit. This is where Immunity Debugger comes in.
By running the app within Immunity Debugger, you’ll be able to grab the EIP register value at the point of the crash and use it to build out the specific amount of junk data the payload will need to overwrite the EIP register. Mona is used again to figure out the offset value based on the EIP value and provide us an accurate figure of junk bytes to prepend. So, let’s say that at the point of the crash, Immunity reports the EIP as 61413761:
Image 2 EIP Value
We can then pass that value to the Mona plugin:!mona po 61413761
Mona conveniently returns the offset value letting us know that we need 22 bytes of junk data in order to create a payload to overwrite the EIP. From there, we can now write a script that will generate our junk data for us:
Image 3 Offset Value from Monapython:
payload = "xc3"*22 # Junk Bytes
Now this alone won’t exploit the BOF. It’ll only crash the app in a predictable manner, thus working as a makeshift Proof of Concept (POC).Oh Yeah, About that Base Pointer
I’ve been talking about the EIP all this time, but there’s another super important pointer we need to use and only mentioned briefly above, the Base Pointer (EBP). See the EBP always comes after the EIP in the stack and represents the bottom of the stack frame. The reason that’s so important is because we want to drop our shellcode in that location in memory and then have it called by the EIP.
In order to do this, we need to find a memory space that is fixed and makes either a “ call esp ” or “ jmp esp ”. ESP stands for stack pointer and is generally the top of the stack. Well, when you make a call to either of those commands, it sets the ESP to the same position as the EBP!
Looking at our steps again, that would be number 2:Prepend junk bytes that get us to the EIP memory space Overwrite the EIP with a command that will point us to the EBP instead of the correct next instruction Shellcode that runs our exploit code starting at the EBP
That’s exactly what we need in order to point the next instruction to our shellcode. We can use Mona again to find a fixed register that makes one of those calls:
!mona jmp -r esp
This will give you a list of possible registers to use:
Image 4 List of possible registers
I didn’t have a lot of success with Mona in this regard, so I prefer to use the findjmp tool which seems to provide better results:
findjmp kernel32.dll esp
Image 5 Findjmp
We’ll add one of those to our payload like this:Python:
payload += "xF7xF8xCDx76" # jmp espThat command address is what will get filled into the EIP register causing it to execute a “ jmp esp ” forcing the ESP to the position of the EBP and setting the next instruction to our shellcode’s position! Windows