02x02 - How to maybe not be as bad at fuzzing unknown binary protocols as you were before reading this, part two

Disclaimer:  I wrote this post a while ago, and definitely before I had enough knowledge to really share accurately.  Feel free to read on, but there's probably a lot of bad info in this.

I'm still bad at fuzzing, but I've learned a lot.

0x01 - Replicating the Crash


Alright, so at this point I need to restart using Windows 7, instead of 10.  Or at least, I thought as much.  I was expecting that in Windows 7 the available buffer space would be larger, as looking that the publicly available exploits, they clearly send a lot more data than I can seem to fit in a buffer on Windows 10.  Here's the kicker though-- after getting my Win 7 VM ready to rock, I send a malformed packet meant to crash the app with a larger buffer (500h bytes), seen below:


Looks... familiar.  Crap.  There goes that idea.  I'm starting to think that perhaps the crash I am causing is indeed, not the crash that the known exploits take advantage of.  It's possible it is, but at this point, I doubt it.

So, what do we do?  Gotta step back to fuzzing and try to figure out how these folks found this vulnerability.

0x02 - Enter boofuzz


boofuzz, a python-based fuzzing framework, is the successor to the Sulley framework.  It's effectively a Python port of Sulley, and sports a lot of improvements, and some actual documentation.  The documentation is good, but lacking a bit (only for noobs, I presume, which is what I am) so it takes a bit to get the ball rolling.  Let's take a look at a basic boofuzz script, we'll use the FTP example script and break it down:

#!/usr/bin/env python
# Designed for use with boofuzz v0.0.8
from boofuzz import *


def main():
    """
    This example is a very simple FTP fuzzer. It uses no process monitory
    (procmon) and assumes that the FTP server is already running.
    """
    session = Session(
        target=Target(
            connection=SocketConnection("127.0.0.1", 21, proto='tcp')))

    s_initialize("user")
    s_string("USER")
    s_delim(" ")
    s_string("anonymous")
    s_static("\r\n")

    s_initialize("pass")
    s_string("PASS")
    s_delim(" ")
    s_string("james")
    s_static("\r\n")

    s_initialize("stor")
    s_string("STOR")
    s_delim(" ")
    s_string("AAAA")
    s_static("\r\n")

    s_initialize("retr")
    s_string("RETR")
    s_delim(" ")
    s_string("AAAA")
    s_static("\r\n")

    session.connect(s_get("user"))
    session.connect(s_get("user"), s_get("pass"))
    session.connect(s_get("pass"), s_get("stor"))
    session.connect(s_get("pass"), s_get("retr"))

    session.fuzz()


if __name__ == "__main__":
    main()

So, looking at the script, it's fairly obvious what's happening.  There's a connection created to the destination server (127.0.0.1) which sends various data at the application.  That's easy enough to understand, but there are a lot of other caveats in play, as well.  With boofuzz, everything works off of sessions.  A session is a connection made to the application, which can be used for a variety of purposes.  Not all cases of fuzzing are straightforward to the degree that you send a packet, and the application crashes.  It might be necessary to send initial requests to trigger an application to process subsequently received data, for instance, let's use the above FTP example.

We create the basic structure of an FTP connection, defining targets we want to fuzz (s_initialize).  In the above case, we want to fuzz parts of the app individually, testing each parameter for errors.  Each initialize is a 'block'.  A block is a piece of data we target for fuzzing, or send statically to the application.

Looking that the boofuzz documentation regarding primitives (data types), we learn that there are a lot of types of data that we can send.  Specifically for the above, we are using 's_string', 's_delim', and 's_static' as data types.  According to documentation, by default we will be fuzzing the 's_string' variables.  's_delim' represents a delimiter value, in this case " ", which will remain static between fuzz points, and 's_static' will be a static string that will not be modified by boofuzz during testing-- the static strings will always be sent as-is.  's_static' and 's_delim' are used keep the packets true to the RFC for FTP, and processed by the application.  We know the an FTP server expects specific data in a request, and by defining static variables and delimiters, we can guarantee that the application at least attempts to read the data properly.

After defining the blocks of data we want to target, the above script creates 'connections' to the application, sending various inputs we defined to it once we instantiate the 'session.fuzz()' method.  This stuff is pretty confusing, and the documentation, although verbose, requires a certain  level of knowledge to understand properly.  Admittedly, I'm not at that level, yet.

boofuzz is definitely easier to understand than some of the other available fuzzing frameworks, however.  Pretty quickly I was able to create a decent script that employs the 'process_monitor.py' script included with the package.  Basically, the 'process_monitor.py' script allows us to monitor the application status and detect crashes, then dump relevant information for analysis.  Basically, it takes the workload off of us by dumping the relevant assembly, stack, and registers on a crash, then attempts to restart the application and resume fuzzing.  I'm currently struggling a bit with the 'restarting process' functionality, as I'm attempting to use it to restart a service.  There are some workarounds, as boofuzz also includes the ability to interact with VMs and restart them when a crash is detected, but I'd like to avoid that (if possible).

The 'process_monitor.py', which we'll refer to as procmon, can be used multiple ways.  Basically, you need to have a server, and a client.  You can use it on one machine, or start the server on one, and fuzz remotely.  Both scenarios utilize the 'pedrpc.py' script to make RPC connections to the server to trigger restarts, regardless if it's local or remote, so procmon needs to me running on the target machine.

Here's a rudimentary script I came up with for fuzzing Disk Savvy (which I apparently will forever refer to as Saavy, and Saavy, interchangeably):

#!/usr/bin/python

# boofuzz DiskSaavy 10.4.18 template

from boofuzz import *
import sys

def main():
    if len(sys.argv) < 3:
        print "[-] Usage: ./boofuzz-diskSaavy.py  <dst> <port>"
        sys.exit()
    else:  
 # VARIABLES
 dst = sys.argv[1]
 dport = int(sys.argv[2]) [dst-ip] [port]        
 start_cmd = ["C:\\Windows\\System32\\net.exe start Disk Savvy Enterprise"]
 
 fuzz(dst, dport, start_cmd)

def fuzz(dst, dport, start_c):

    # Create session, specify high-arbitrary crash_threshold, low restart_sleep_time, check_data_received_each_request=False
    #session = Session(ignore_connection_reset=1, check_data_received_each_request=False, web_port=8181, crash_threshold="10000", restart_sleep_time=0.1, sleep_time=0.1, target = Target(connection = SocketConnection(dst, dport, proto='tcp')))
    session = sessions.Session(
        crash_threshold="10000", 
        check_data_received_each_request=0,
        restart_sleep_time=0.1, 
        sleep_time=0.1,
    )
    target = sessions.Target(
        connection = SocketConnection(dst, dport, proto='tcp')
    )
    target.procmon = pedrpc.Client(dst, 26002)
    target.procmon_options = {"start_commands": [start_c]}

    # Define packet block
    s_initialize("packet")
    s_dword(0x7519baab, endian='>', fuzzable=0)
    s_dword(0x03000000, endian='>', fuzzable=0)
    s_dword(0x00000000, endian='>', fuzzable=0)
    
    #s_dword(0x1A000000, endian='>', fuzzable=1, name="Potential ECX overwrite")
    s_byte(0x1A, fuzzable=0, name="ECX Prefix")
    s_bit_field(000000, width=24, endian='>')

    s_dword(0x20000000, endian='>', fuzzable=0, name="post-ecx");
    s_string("SERVER_GET_INFO", name="SC 1")
    s_byte(0x02, endian='>', name="Unknown binary 1")
    s_string("2", name="Unknown string 1")
    s_byte(0x01, endian='>', name="Unknown binary 2")
    s_string("DATA", name="SC 2")
    s_byte(0x01, endian='>', name="Unknown binary 3")
    s_string("0", name="Unknown string 2")
    s_dword(0x01000000, endian='>', name="Unknown dword 1")
    s_dword(0x60C0F102, endian='>')
    
    #s_string("`", name="Unknown string 3")
    #s_byte(0xC0, endian='>')
    #s_byte(0xF1, endian='>')
    #s_byte(0x02, endian='>')
    
    session.add_target(target)
    #session.connect()
    session.connect(s_get("packet"))
    session.fuzz()

if __name__ == "__main__":
    main()

There's a lot of redundancy in the above script, such as all the 'endian' calls, but I wanted to become acclimated to using boofuzz and it's functionality, so I kept them in.  There are a lot of different ways to structure boofuzz scripts.  You can define a session simply, as previously seen in the FTP script, or break down the session, target, etc, into separate definitions and combine them later.  I like to define them separately, as it helps me stay organized in a script.

Anyway, as we can see, I define a 'block' (packet) just to have a target to fuzz.  I've then broken down the packet into separate areas that seem like good points to fuzz.  Starting out simply, I don't want to necessarily fuzz every possible way, but want to have a working foundation.  The above script will make an RPC connection to my server machine which is hosting Disk Savvy, and attempt to dump out crash data, then trigger a restart of the process.  On my server machine, I run the following script prior to fuzzing:

@echo off
cd C:\Python27\Lib\site-packages\boofuzz
python process_monitor.py --proc_name "disksvs.exe" --port 26002 --crash_bin diskSaavy_Crashes.txt

This will load up procmon, attach to disksvs.exe, await a connection from our fuzzer on 26002/tcp and dump crash data to a file we've specified..  Here's both in action:

Server:

Client:


END.  Sorry.



This post has been reworked into the DiskSavvy specific post, focusing only on the BoF, but utilizing knowledge gleaned via these two.  I realized a bit later on that this post should have strayed a little less and remained more focused on fuzzing unknown binary protocols, rather than tuning in on one particular app.  I'm going to create another fuzzing post shortly, where I'll stay more on topic of the intricacies of dissecting these protocols, and where to spend time.






Comments

Popular posts from this blog

06 - How to maybe not be so bad at fuzzing, Part 2

07 - Just Another OSCE Review

05 - How to maybe not be so bad at fuzzing, Part 1