November 3, 2019 · Basic Pen-Testing Adv. Pen-Testing

6.2 : Classic buffer overflow - Fuzzing & Win32 buffer overflow (Part II)

Background

Practical example - win32 buffer overflow

In order to learn buffer overflow in a educational way, we can use vulserver from Stephen Bradshaw as an example.

You can download it from
http://www.thegreycorner.com/2010/12/introducing-vulnserver.html
or via my github
https://github.com/j0eii/vul-server

vulserver is a vulnerable server for educational purpose & running on Windows.

The other tools we need is the immunity debugger.
In this case, we are using this as a memory inspection tool to look for strangle buffer return address.

So here is the steps to produce a buffer overflow to vulserver.

  1. Recon, look for the attack surface.
  2. Prepare fuzzer
  3. Submit junk data to vulsever and look for the maximum buffer it can hold.
  4. Look for the EIP changes on how many bytes can it barely take to achieve changing the buffer.
  5. Check enough space for your shell code
  6. Look for the bad character that vulserver to avoid
  7. Generate a rootkit from metasploit msfvenom, append it to the payload without exploding the buffer
  8. Submit the payload again
  9. try the rootkit

Let's do this together in this lab.

1. Recon

Use nmap to do the recon.

[email protected]:~# nmap -T4 -n -Pn -p 1-65535 192.168.56.100-102 --exclude 192.168.56.101
Starting Nmap 7.80 ( https://nmap.org ) at 2019-11-03 03:23 PST
Nmap scan report for 192.168.56.100
Host is up (0.000024s latency).
All 65535 scanned ports on 192.168.56.100 are filtered
MAC Address: 08:00:27:CA:DF:12 (Oracle VirtualBox virtual NIC)

Nmap scan report for 192.168.56.102
Host is up (0.00026s latency).
Not shown: 65529 filtered ports
PORT     STATE SERVICE
22/tcp   open  ssh
135/tcp  open  msrpc
139/tcp  open  netbios-ssn
445/tcp  open  microsoft-ds
5357/tcp open  wsdapi
9999/tcp open  abyss
MAC Address: 08:00:27:21:0F:B2 (Oracle VirtualBox virtual NIC)

Nmap done: 2 IP addresses (2 hosts up) scanned in 87.57 seconds

Now we have our target, 192.168.56.102 on port 9999
It is a TCP service, lets use nc try to connect it and grab the banner.

[email protected]:~# nc 192.168.56.102 9999
Welcome to Vulnerable Server! Enter HELP for help.

> HELP

Valid Commands:
HELP
STATS [stat_value]
RTIME [rtime_value]
LTIME [ltime_value]
SRUN [srun_value]
TRUN [trun_value]
GMON [gmon_value]
GDOG [gdog_value]
KSTET [kstet_value]
GTER [gter_value]
HTER [hter_value]
LTER [lter_value]
KSTAN [lstan_value]
EXIT

So right now we have our attack surface, which are the commands

STATS [stat_value]
RTIME [rtime_value]
LTIME [ltime_value]
SRUN [srun_value]
TRUN [trun_value]
GMON [gmon_value]
GDOG [gdog_value]
KSTET [kstet_value]
GTER [gter_value]
HTER [hter_value]
LTER [lter_value]
KSTAN [lstan_value]

& the command itself.

Let's create a python3 script & try to prope the function, see what can be fuzzed.

2. Prope & Fuzz

first attempt

Let's script a prope.

import socket

def get_message():
    """
    prope server

    construct request message
    :return: string
    """
    return "empty"

def prope(message):
    """
    prope server

    :param message: request message
    :return: void
    """
    host = "192.168.56.102"
    port = 9999
    res = None
    try:
        my_socket = socket.socket()
        my_socket.connect((host, port))
        my_socket.recv(2048).decode()
        # print(res)
        my_socket.send(message.encode())
        res = my_socket.recv(2048).decode().replace("\n", "").replace("\r", "")
        # print(res)
        my_socket.close()
        print("[success] %s:%s %s => %s" % (host, port, message, res))
    except:
        print("[error] %s:%s %s => None" % (host, port, message))

def main():
    prope(get_message())


if __name__ == "__main__":
    main()

Try & Execute

> python3 exploit.py 
[success] 192.168.56.102:9999 empty => UNKNOWN COMMAND

Success for the prope script. Right now, let's try to modify the get_message function, create a adjustable buffer generator for each prope.


second attempt

import socket


def get_message(length):
    """
    prope server

    construct request message
    :param length:integer total byte
    :return: string
    """
    ret = ""
    if length > 0:
        ret = "A" * length
    return ret


def prope(message):
    """
    prope server

    :param message: request message
    :return: void
    """
    host = "192.168.56.102"
    port = 9999
    res = None
    try:
        my_socket = socket.socket()
        my_socket.connect((host, port))
        my_socket.settimeout(1) # add this to prevent freeze the connection
        my_socket.recv(2048).decode()
        # print(res)
        my_socket.send(message.encode())
        res = my_socket.recv(2048).decode().replace("\n", "").replace("\r", "")
        # print(res)
        my_socket.close()
        print("[success] %s:%s %s => %s" % (host, port, message, res))
    except:
        print("[error] %s:%s %s => None" % (host, port, message))


def main():
    prope(get_message(0))
    prope(get_message(1))
    prope(get_message(10))
    prope(get_message(30))


if __name__ == "__main__":
    main()

Try & Execute

python3 exploit.py
[error] 192.168.56.102:9999  => None
[success] 192.168.56.102:9999 A => UNKNOWN COMMAND
[success] 192.168.56.102:9999 AAAAAAAAAA => UNKNOWN COMMAND
[success] 192.168.56.102:9999 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA => UNKNOWN COMMAND

Success. Right now we can modify it as a auto prope bot.


thrid attempt

Lets try to auto prope it.

import socket, sys


def get_message(length, prepend=None):
    """
    prope server

    construct request message
    :param length:integer total byte
    :return: string
    """
    ret = ""
    if length > 0:
        ret = "A" * length
    return prepend + " " + ret if prepend is not None else ret


def prope(message):
    """
    prope server

    :param message: request message
    :return: void
    """
    host = "192.168.56.102"
    port = 9999
    res = None
    try:
        my_socket = socket.socket()
        my_socket.connect((host, port))
        my_socket.settimeout(1)  # add this to prevent freeze the connection
        my_socket.recv(2048).decode()
        # print(res)
        my_socket.send(message.encode())
        res = my_socket.recv(2048).decode().replace("\n", "").replace("\r", "")
        # print(res)
        my_socket.close()
        # hide the success , look for failure, we need crash. we need failure.
        print("[success] %s:%s %s => %s" % (host, port, message, res))
    except:
        print("[error] %s:%s %s => None" % (host, port, len(message)))  # modify the output for testing ttl length
        # sys.exit()


def main():
    # prope(get_message(0))
    # prope(get_message(1))
    # prope(get_message(10))
    # prope(get_message(30))

    # try 10 kb prope by 50/incremental , see when crash

    max=3
    each=1

    print("testing cmd itself")
    i = 0
    while i < max:
        prope(get_message(i))
        i += each

    print("testing cmds")
    cmds = [
        "STATS"
        , "RTIME"
        , "LTIME"
        , "SRUN"
        , "TRUN"
        , "GMON"
        , "GDOG"
        , "KSTET"
        , "GTER"
        , "HTER"
        , "LTER"
        , "KSTAN"
    ]

    for cmd in cmds:
        print("testing %s" % cmd)
        i = 0
        while i < max:
            prope(get_message(i, cmd))
            i += each

    print("completed test")


if __name__ == "__main__":
    main()

try & execute

>python3 exploit.py
testing cmd itself
[error] 192.168.56.102:9999 0 => None
[success] 192.168.56.102:9999 A => UNKNOWN COMMAND
[success] 192.168.56.102:9999 AA => UNKNOWN COMMAND
testing cmds
testing STATS
[success] 192.168.56.102:9999 STATS  => STATS VALUE NORMAL
[success] 192.168.56.102:9999 STATS A => STATS VALUE NORMAL
[success] 192.168.56.102:9999 STATS AA => STATS VALUE NORMAL
testing RTIME
[success] 192.168.56.102:9999 RTIME  => RTIME VALUE WITHIN LIMITS
[success] 192.168.56.102:9999 RTIME A => RTIME VALUE WITHIN LIMITS
[success] 192.168.56.102:9999 RTIME AA => RTIME VALUE WITHIN LIMITS
testing LTIME
[success] 192.168.56.102:9999 LTIME  => LTIME VALUE HIGH, BUT OK
[success] 192.168.56.102:9999 LTIME A => LTIME VALUE HIGH, BUT OK
[success] 192.168.56.102:9999 LTIME AA => LTIME VALUE HIGH, BUT OK
testing SRUN
...

The exploit seems promising.


Lets try to crash the server by increasing the max byte we are trying to send.