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

06 - Back to the Fuzzing, Part 2


This post will focus more on actual usage of different fuzzers, rather than part one which talked about fuzzing from a higher level.  I'll use the widely available vulnserver.exe as a target application for a bunch of examples, as there are a ton of posts out there that go over exploiting it.  I will also briefly talk about some typical fuzzing use-cases, such as HTTP requests, and how we may employ a fuzzer to assist in testing.  I'm not going to turn our crashes into exploits, that's outside of the scope of this post-- I'll just take a more in depth look at the available tools to expedite the process of locating bugs.

Here's the agenda for tool usage:
  1. Python Sockets
  2. SPIKE
  3. boofuzz
In part one I'll talk about some common ways to implement python sockets, such as raw sockets for a normal client / server model, fuzzing HTTP requests, and maybe a bit more if I don't get lazy half-way.  In part two I will apply SPIKE to the same test cases, and similarly for part three, but with boofuzz.  This should hopefully provide some insight into the utility of some of these tools, their application to fuzzing and maybe when it's not necessary to utilize them.  Onward!

01 - Python Sockets

Alright, even if you're not too familiar in the usage of python, it's easy enough to make rudimentary scripts to accomplish / automate common tasks.  Personally, I'm not the best programmer.  I can automate lots of stuff, but my scripts don't tend to be... pythonic, per se.  They get the job done, but I'm still learning.

Python is super portable, which is one reason I really dig it.  It's easy enough to write, and you can do an amazing amount of stuff with it.  xkcd has a pretty famous strip that jokes about it, but it's not far from the truth.  

Vulnserver.exe

Let's take a look at our vulnserver.  Start the application on a target machine, connect to it using netcat, and it sends a banner that tells us to type 'HELP' to see the available commands.  It's output is as follows:


It looks like there are 12 total commands that accept arguments, plus HELP and EXIT.  If you attempt to get more information about a specific command, for instance typing "HELP KSTAN", you'll get an error that explains that command specific help hasn't been implemented.  The server isn't going to give us any more information on the usage.

So, I now know there are 12 targets for fuzz points.  That's a lot of work to issue commands manually and really time consuming, at that.  It's pretty easy to quickly implement python sockets to take the load off, though.  I'm not going to be sending anything 'special' at the server, but if I iterate through lengths of strings, sending them at the application one after another, I can at least get the groundwork done for locating a typical buffer overflow.

To start off simply, I'll define a socket, connect to the server, and send from 1 to 1000 bytes to each command:
#!/usr/bin/env python

import socket
import sys

# Define commands to be tested
commands = ['STATS','RTIME','LTIME','SRUN','TRUN','GMON','GDOG','KSTET','GTER','HTER','LTER','KSTAN']

# Define fuzz function, accepts target host and port
def fuzz(host,port):

    # Create a TCP socket
    # WIll invoke a three-way handshake
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # Check if we can connect to the host
    # If so, receive data back
    try:
        s.connect((host,int(port)))
        r = s.recv(1024)
        print "[+] Host is up: \r\n" + r
    # Error out and print the target, if we can't connect
    except:
        print "[-] Unable to connect to %s:%d" % (host,int(port))
        sys.exit()

    # Create a for loop for each command we defined and set our counter up
    for cmd in commands:
        i = 1

        # Create a while loop to send growing amounts of "A"s, up to 1001
        while i < 1002:
            junk = "A"*i
            packet = cmd + " " + junk
            s.send(packet)
            print "[!] Sent %d bytes at %s" % (len(junk),cmd)
            s.recv(1024)
            i += 10
            
# Argument parsing for script start
if len(sys.argv) < 3:
    print "[+] Usage: vulnserver-tests.py HOST PORT"
    sys.exit()
else:
    host = sys.argv[1]
    port = sys.argv[2]
    
    fuzz(host,port)

This script wasn't just fast to write, it's fast in general.  Doing the math, it will send 100 packets to each of 12 commands, totaling 1200 packets of various sizes, assuming it gets through all the data.  When run, on my lab machines it finishes (generates a crash) in about 3 seconds.

I'm a pretty verbose scripter-- I like to know what my script is doing when it's run, so I put in lots of print statements.  This is primarily just to help me troubleshoot and make sure things are doing what I expected them to do.  The first few iterations of scripts I write usually only end up sending one byte at each command, or only sending data at one command, total.  Very easy to fix when you see what it's doing.

Opening wireshark when it runs shows exactly what I wanted to see:


Following the TCP stream, the three-way handshake is instantiated.  Next, we receive the server banner and send the first command: 'STATS A'.  The server sends an expected response 'STATS VALUE NORMAL'.  The script then sends 11 "A"s, 21, 31, etc, all the way to 1001 "A"s.

Once the first corpus (the junk data) is exhausted, the script moves on to the second command, and repeats the process until all data has been sent, or it can no longer connect to the application for whatever reason (hopefully a crash!).

In this case, I do actually get a crash...  It looks like sending even one byte at the 'KSTET' command results in bad news for the application.


Definitely interesting...  Here's an example of something that could've been easily located by hand, simply playing the the server would've likely yielded this information.  Maybe even faster than writing this fuzzer.  Hard tellin' not knowin'.

Another thing to note is that, although I have located a crash, I haven't tested all the commands.  Since the program crashed, I don't have any way to continue sending data and continue iterating.  For the moment, I'll remove the KSTAT command from the list and start sending again.  When I do that, I'll also make a slight modification to the amount of data I send, so I can define it on command line:
def fuzz(host,port,size):
....
....
    while i < int(size):
        junk = "A"*i
....
....
[truncated]
....
....
# Argument parsing for script start
if len(sys.argv) < 4:
    print "[+] Usage: vulnserver-tests.py HOST PORT SIZE"
    sys.exit()
else:
    host = sys.argv[1]
    port = sys.argv[2]
    size = sys.argv[3]
    
    fuzz(host,port,size)

Now, running the script with a new argument (10) on the end yields the following:


Remember that KSTAT has been removed from the list and noted as crashing the app at one byte.

Cool, so I've expanded the script (minimally) to now accept maximum data sizes defined via command line.  This is pretty useful as it means I don't need to keep modifying the script to send varying data sizes.  If I wanted, I could just as easily modify the amount of data I increment each send, specific positions to start fuzzing from, variables to skip and much, much more.  SPIKE and boofuzz also have these abilities built-in.

Let's fuzz again to 1002 bytes so that we can test the commands that came after KSTAT.

Another crash is located pretty quickly, sending one byte at the 'GTER' command seems to break the server, as well:


One thing I didn't talk about before was testing the crashes that are located.  Something seems fishy here.  I can confirm the fishiness by connecting to the application and sending a single byte at the GTER and KSTAT commands to test if they are stable.  Interestingly, neither crash the server:


That's odd...  The fuzzer confirmed and continues to confirm, the crash at this command.  Take a closer look at the above two images though.  What's the difference?

Well, in the first, a lot more data was sent prior to sending GTER.  The same goes for the data sent at KSTET.  Prior to the crash at the 'vulnerable' command, thousands of bytes were sent to the server.  At first, you may think that we have misidentified the vulnerable commands and the crash is actually occurring on the GDOG /  command.  That's a good theory, but the python script has a receive 'listener' after each send-- it only hangs after the KSTET or GTER commands, where it's awaiting some data back from the server.

A few quick test send confirm that there's something interesting happening:


After sending 1001 bytes at the GDOG command, any data sent to GTER or KSTET crashes the app.  Now, remember that I haven't continued to fuzz the rest of the commands.  I can now try this manually to see if the behavior for the other commands is the same.

After testing the few remaining commands by hand (sending GDOG + 1001 A's, then LTER / HTER / KSTAN, following each GDOG send) I find that the remaining commands do not exhibit the same behavior.  I'll note the discrepancies with GDOG and KSTET / GTER and continue fuzzing the few remaining commands.

I remove the GTER command from our list and fuzz again.  No crashes.  Let's modify the script to give us another variable for modifying the incremental the script multiplies the junk data by.  This modification is almost the same as prior-- add a new argument, send that argument to the fuzz script as an integer, and change the 'i += 10' to our new variable.

After some fiddling, I actually locate another crash after GDOG (again).  It seems as though sending at least 3001 bytes to GDOG, then data to HTER will also trigger a crash.  Sending data to the next two commands (after sending 3001 bytes to GDOG, each) does not trigger a crash, though.

So, a couple important things to mention here.  The crashes are interesting, they seem to depend on staged payloads, as the first large payload does not cause a crash, but following the initial data with another command crashes the server.  That means that data has likely corrupted the stack in a way that the application is unable to recover from.  It also means that the data sent is stored in memory...

As I said initially, I'm not going to be exploiting this application.  That's not the objective for the post and there are many articles already looking at vulnserver.exe.  Google for a walkthrough if you need, but if you are so inclined, think about what the behavior of the application suggests and how you might be able exploit it with what we currently know.

Where to now?


At this point, I've fuzzed and slowly expanded the capabilities of the python script.  It's still a fairly generic fuzzer, but there are some quality of life improvements, such as the ability to specify a multiplier and how many bytes to fuzz.  Issues that will constantly be run into whilst fuzzing with this script are plentiful, but primarily are attributed to constant crashing.  This puts a damper on the speed at which the script can fuzz and requires us to manually restart the application every time it crashes.

There are many ways one could deal with this.  One, I could modify the script to accept an argument that specifies which command to start fuzzing from, which will cut down on the amount of known crashes hit, since I can specify data sizes and target specific commands.  Although nice, this still doesn't solve the issue of manually restarting, just helps to cover more ground.

could write a script on the target machine (perhaps in powershell) to track when the application crashes, restart it, and modify the python script to add some basic error checking to detect the crashes and wait / continue once it's been brought back up.  This is definitely better, as it does solve the 'problem', but there's no reason to reinvent the wheel!

There are a bunch of tools already written to do this for us.  boofuzz employs PyDbg, libasm, a script called process_monitor.py, working in conjunction with PEDRPC to send and receive data between the two machines.  This does exactly what I'd be looking to do and more.  It will detect crashes, send relevant data back to the fuzzer (crash information, such as registers, a stack dump, etc), restart the process and continue fuzzing.

When adding more functionality, like error checking, restarting and sending dump data, you will find that the speed at which you can fuzz suffers.  This is expected, especially considering the application will need to restart, but it does still save time, as you're not required to manually restart it each crash.

Prior to getting into boofuzz, I'll show how SPIKE can be utilized to work with this application as well.

02 - SPIKE

SPIKE is a rather famous fuzzer.  Written in C, lacking any 'real' documentation, it's mysterious and powerful.  Dave Aitel, in the "Shellcoder's Handbook" refers to SPIKE as 'the best fuzzer ever written.'  Granted, this was in 2007 and tools have evolved since, but the utility of SPIKE is nothing to scoff at.  SPIKE can fuzz network protocols quickly and with a proven corpora.  It's also not limited in the way our python script is, to one single use case.  SPIKE offers multiple types of fuzzing, the most common falling under the 'generic_' scripts.


In the above you can see that the generic category offers some pretty common fuzzing utility.  You could fuzz TCP, UDP, web servers, send specific chunked formats, or even set up a listener to fuzz client connections (neat!).  The most common SPIKE use is the 'generic_send_tcp' command.  SPIKE accepts multiple arguments by default:
  1. Host
  2. Port
  3. A SPIKE
  4. SKIPVAR
  5. SKIPSTR
Since SPIKE has a specific corpora that it will be utilizing, there are a few different arguments than what was employed in the python script.  Obviously the first two are simple, host and port.  The next one is what is called a 'spike'.  SPIKE itself is actually a collection of tools for fuzzing.  A 'spike' is effectively a corpus of data to send at an application, that's true for all fuzzing, but with SPIKE, it uses '.spk' files.  A .spk file is the way to tell fuzz what to send.  It's essentially a way to define a packet or specific data to send at an application.  This can be broken down quite easily into many different formats, and many different fuzzing targets within a single packet.  I'll look at .spk files shortly.

Next up is the SKIPVAR argument, which enables you to skip targets defined within the .spk file.  This is useful because, as found earlier, there are times when you may want to narrow down the fuzzing locations, or skip specific 'comands'.  Lastly, is the SKIPSTR argument, which enables you to start at specific corpora locations.  Since SPIKE sends specific strings or binary data, you can define how much of it to 'skip'.  

Perhaps you had to stop fuzzing after five minutes, you've already tested quite a bit of corpora, there's no reason to send them all again.  Using the SKIPVAR and SKIPSTR, you can start SPIKE up at the exact location you stopped fuzzing (maybe: command two, string 153) and waste no time.  As prior, you can define all of these arguments in the base python script, but it's not necessary at the moment.

.spk files are the most important aspect of SPIKE.  WIthout one, the application won't be able to do anything.  Now, I'll look at a .spk and see how they work.

Under the '/usr/share/spike/audits/' directory on a Kali machine live lots of .spk files.  For simplicity, I'll look at a well understood protocol: HTTP.  SPIKE doesn't have a pre-built HTTP .spk file.  Why?  Well, once you see the files, you'll see that building an HTTP spike will be trivial; also, SPIKE has a UPNP.spk file, which follows a similar architecture. 

UPNP.spk:
s_string_variable("NOTIFY"); 
s_string(" "); 
s_string_variable("127.0.0.1"); 
s_string(" ");
s_string_variable("HTTP/1.1"); 
s_string("\r\n");

s_string("Host: ");
s_string_variable("192.168.1.100");
s_string(":");
s_string_variable("1900");
s_string("\r\n");

s_string_variable("CACHE-CONTROL");
s_string(": ");
s_string_variable("max-age");
s_string("=");
s_string_variable("10");
s_string("\r\n");

s_string("LOCATION: ");
s_string_variable("http");
s_string("://");
s_string_variable("192.168.1.103");
s_string(":");
s_string_variable("555");
s_string("/");
s_string_variable("");
s_string_variable(".xml");
s_string("\r\n");

s_string("NT: ");
s_string_variable("urn");
s_string(":schemas-upnp-org:device:InternetGatewayDevice:");
s_string_variable("1");
s_string("\r\n");

s_string("NTS: ");
s_string_variable("ssdp");
s_string(":");
s_string_variable("alive");
s_string("\r\n");

s_string("SERVER: ");
s_string_variable("BOB");
s_string("/2001 UPnP/1.0 product/1.1\r\n");

s_string("USN: ");
s_string_variable("uuid");
s_string(":");
s_string_variable("BOB");
s_string("\r\n\r\n");

Honestly, really simple.  The UPNP protocol is HTTP based, so you can use this as a template for typical HTTP spikes as well, with a few slight modifications.  Anyway, from the above spike file, you can learn quite a bit.  First off, try to mentally reconstruct how the above request might look, or look below :]
NOTIFY 127.0.0.1 HTTP/1.1
Host: 192.168.1.100
CACHE-CONTROL: max-age=10
LOCATION: http://192.168.1.103:555/.xml
NTS: ssdp:alive
SERVER: BOB/2001 UPnP/1.0 product/1.1
USN: uuid:BOB

As you can see, UPnP performs an HTTP request.  You can strip this down / modify it for your purposes if necessary.  SPIKE works by building packets based on criteria specified.  In the above .spk file, the protocol is broken down into strings that are kept static, or assigned as 'variable'.  Variable strings are the fuzzing targets-- each s_string_variable is a position at which SPIKE will fill with it's corpora, testing the server's responses.

In the first section of the .spk file, we define the HTTP method, a delimiter, a target (URI, sorta) and the HTTP version (plus line terminators, \r\n).  Within that section however, SPIKE only defines three positions as points to fuzz: method, target, version.  This means that as SPIKE runs, it will keep the expected structure of the request, but fill the defined insertion points with junk data.

SPIKE will iterate through each set of corpora at each position defined, one by one.  This means that, assuming it has ~1400 corpora to go through, at position one it will run until all in values it's set / list have been attempted.  Once exhausted, it moves on to the next position and restarts the process.  This is normal fuzzing behavior.

SPIKE has a large number of built in data structures it knows how to fuzz.  These can be viewed by looking at the 'spike.h' file, located here: '/usr/share/spike/include/spike.h'  Below, some of the available structs:
int
s_string(char * instring);

int
s_blocksize_string(char * instring, int size);

int
s_binary(char * instring);

/*
  this pushes an arbitrary byte string onto the spike stack
  and updates any listening blocksizes
*/
int
s_push(unsigned char * pushme, unsigned long size);

int
setspike(struct spike * newspike);

/*if you need a reserved source port ( less than 1024) set this to true*/
int
s_set_needreserved(int needreserved);

int
s_block_start(char *blockname);

int
s_binary_block_size_word_bigendian(char *blockname);

int
s_block_end(char * blockname);

This is pulled straight from the spike.h file.  If you look at the UPNP template from earlier, it's clear that the definitions the .spk file told SPIKE to test follow the same format as what's in the spike.h file.  This means you can utilize anything in the spike.h file as valid input types.  If you wanted, it's also possible to add new structs to the spike.h file and start defining custom data types (structs / primitives).

SPIKE does have some pretty nice features, including the ability to parse hex characters in multiple formats, which simplifies cutting and pasting binary from different sources.  It also allows one to define blocks to perform different actions on specific data within the requests, individually.  As an example, if you were writing an HTTP POST request, you'd need to dynamically generate content lengths based on data payload sizes.  Using blocks allows us to define a block for the data portion, and assign a 'size' variable after you define the 'Content-Length' header.  This size will then update the  header for each payload SPIKE sends.

There are a lot of articles on using SPIKE out there. Some of the useful ones:

Defining a SPIKE

So let's say I want to utilize SPIKE to fuzz the vulnserver executable.  If you look at some of the examples that are provided by SPIKE and think about our targets, it's really pretty simple.  Let's start out small and fuzz a single command, just to get it working and see what happens.

s_readline();
s_string("STATS ");
s_string_variable("TEST");
s_string("\r\n");
s_readline();

That's it. Seriously.

Check out what happens when I run it:


Above, a quick test run with SPIKE to make sure I have written the right .spk

Below, some of the Wireshark results, just so I can take a look at what's happening.


Alright, so there are a few important things to take note of.  First, in the template I wrote, I included 's_readline()' before and after each send.  I make this a habit for most scripts, SPIKE or python or boofuzz, etc.  This is probably unnecessary, but I like to do it so I can guarantee there's a logical separation between sends.  When I connect, I want to make sure the server is ready to receive, so I add a line to read in the buffer the server sends.  Similarly, when I send data I want to 'check', or at least pseudo-check, what happens.  In the case of a python script, adding a 's.recv(1024)' or equivalent will make the script hang rather than error out.  It's simply a preference for me.

Secondly, when I run this with the template, one defined fuzz point, SPIKE reports out that it will be testing 681 strings.  That's it's typical corpora for one defined fuzzing location for the 's_string_variable();'  SPIKE will apply different corpora to different types of fuzzing definitions.

Cool, so if I let the above run, SPIKE will continue fuzzing beyond the 681 tests it reported out.  It won't track variable sizes anymore, but it does continue sending corpora.  If I'm not mistaken, it's just looping back through it's list.


Keep that in mind.  Now, if you have multiple fuzz points defined, it will move automatically to the next, it will just take a bit, 2043 payloads in my case.

Anyway, you probably guessed how easy it is to expand this template.  Copy and paste, change the strings:

s_readline();
s_string("STATS ");
s_string_variable("TEST");
s_string("\r\n");
s_readline();

[TRUNCATED]

s_readline();
s_string("LTER ");
s_string_variable("TEST");
s_string("\r\n");
s_readline();

s_readline();
s_string("KSTAN ");
s_string_variable("TEST");
s_string("\r\n");
s_readline();

So, one important thing you'll start to notice with a template like the above is how long it starts to take.  Now, this isn't just because we've added a lot more fuzz points, but try removing the 's_readline();' lines and you'll see what I mean.  Without the readline commands, SPIKE cruises.  In fact, often times it slams through a few hundred corpora, then stops for a moment, then continues.  The readlines are totally optional and I typically only add them for initial creation to help troubleshoot, or if something actually requires it.

If you let it run, SPIKE will eventually switch from the first to the second, third, etc, fuzz points.  It'll automatically iterate through them. As shown a bit earlier, it's not the end of the world if you need to stop SPIKE.  It can be re-run it using the SKIPVAR and SKIPSTR commands to make it 'resume' from the last location.  Assuming you stop it at variable three, string 512, you can simply resume by running 'generic_send_tcp 192.168.86.202 9999 vulnserver.spk 2 512' for instance.

Note the above!  String three is not 'SKIPVAR = 3', as SPIKE starts from 0, as a proper array would ;)

But there's a catch

Although SPIKE will indeed fuzz the locations in the script above, it's actually sending all of the defined positions.  Meaning, if you only want to test what happens when we fuzz GDOG or KSTET without sending every other command, you'll need to remove the others from the template.  Take a look at the screenshot below:



SPIKE is fine for fuzzing this application, but you may find different results from using different tools.

03 - Boofuzz

It's always good to have a toolset.  SPIKE, while effective, is a fantastic fuzzer but may not fit the bill for our exact use case.  boofuzz, as alluded to in previous posts, is a powerful python fuzzing framework.  It's based on Sulley, SPIKE's successor, and is being actively maintained.  

boofuzz has a LOT going on.  It would take a lot of time to cover it's features.  Take a look at the documentation to see the capabilities.  The documentation is great, but it's still a bit confusing when you're reading technical info and you'd just like to apply it to a use case.  That's what we'll try to do here.

So, while boofuzz may not be quite as fast to set up as SPIKE, we've a lot of options. I'll create a small script to start fuzzing:
#!/usr/bin/env python

from boofuzz import *

# Create a session, create a target
session = sessions.Session()
target = sessions.Target(connection = SocketConnection("192.168.86.222", 9999, proto='tcp'))

# Initialize a request
s_initialize("packet")
s_static("STATS ")
s_string("TEST")
s_static("\r\n")

# Add target to session
# Connect and prep the request
# Start the fuzzing
session.add_target(target)
session.connect(s_get("packet"))
session.fuzz()

So, if you're at all acclimated with python, the above should be pretty straight forward.  Specific to boofuzz however, there are a few important things to mention:

  • boofuzz operates by using sessions.  A session is basically a way for boofuzz to track it's own fuzzing.
    • You can create multiple sessions within a single boofuzz script.  This allows for parallel fuzzing against an application, or multiple.
  • Each session has a target, so if you create a session, you need to tell boofuzz what to actually target (duh).
    • An initial target definition will need to happen, plus actually adding the target to the session.
  • Each fuzzing position is identified individually with various 's_string' style commands.  However, each request needs to be initialized to be used.
    • The 's_initialize' is a way to define a specific set of data for boofuzz to utilize.  You can create multiple requests within a single script and send them one at a time, simultaneously, etc.
    • In the above script, you can see the 'packet' initialize above the actual fuzz points, then the 's_get' within the connect directive.
So, when I run this script boofuzz spits out a LOT of info.  Info that can be a bit overwhelming.  Much of this will start to make sense as you start utilizing some of the more advanced aspects of boofuzz.  For now, here's a sample:


As you can see, boofuzz keeps us pretty informed on what it's doing.  If let run, this script will test the STATS command with a corpora count of 1441.

As it stands, this script is fairly rudimentary.  It tests one command, then exits.  Sure, that's effective enough and a good example of how boofuzz works, but it's also pretty boring.  A large attraction of boofuzz is how extensible it is. Let's change the script a bit and make it iterate through commands we provide.  This is similar to the python script created at the beginning of this post.
#!/usr/bin/env python

from boofuzz import *
import argparse

# Make a list of the target commands
commands = ['STATS','RTIME','LTIME','SRUN','TRUN','GMON','GDOG','KSTET','GTER','HTER','LTER','KSTAN']

parser = argparse.ArgumentParser(description='Boofuzz Script')
parser.add_argument('-t', '--target', help='Target to fuzz')
parser.add_argument('-p', '--port', help='Target port', nargs='?', const=int)

args = parser.parse_args()

host = args.target
port = args.port

# Create a session, create a target
session = sessions.Session()
target = sessions.Target(connection = SocketConnection(host, int(port), proto='tcp'))

# Initialize a request
s_initialize("packet")

# Create a group called 'verbs' using the 'commands' list
s_group("verbs", commands)    
# Create a block for use with groups
if s_block_start("packet", group="verbs"):
  s_delim(" ",fuzzable=0)
  s_string("AAA")
  s_string("\r\n")
s_block_end("packet") 

# Add target to session
# Connect and prep the request
# Start the fuzzing
session.add_target(target)
session.connect(s_get("packet"))
session.fuzz()

Alright, a few changes have been made to how the script works.  Namely:
  • I have initialized a list of the commands I want tested.
  • I added argparse.  This will help in the event IPs or ports change, as well as giving more control over adding more features.
  • I initialized a group and a block.

What's a group?

Here's boofuzz's documentation:

boofuzz.s_group(name, values)
This primitive represents a list of static values, stepping through each one on mutation. You can tie a block to a group primitive to specify that the block should cycle through all possible mutations for each value within the group. The group primitive is useful for example for representing a list of valid opcodes. 

Basically, this enables you to create a list that can manipulate the way a block works.  For us, this is just a way to iterate through each command.

What's a block?

A block is a way for you to create a section of data you can access and manipulate.  Blocks enable you to calculate size values of specific data you send (think about testing data in a POST request), apply checksums, define values to send (groups), various calculations and things like repeating sends.

Blocks are rather versatile and enable us to perform many transformations.  In this case, I'm just going to use it as a location I can define for the group of commands being used.

When the new script is run, a few changes are visible.  From boofuzz's output, not a lot has changed.  Primarily though, you'll note the yellow 'Test Case' has changed to a reference of packet, to 'packet.verbs.X' where X represents the position in the list.  Another large change-- the corpora increased to 34596.  That's a lot and will take a long time.



If you've noticed, there's a specific 'Check OK:' statement in the output.  Basically, boofuzz is checking after each send to see if the application sends any data back.  This can cause some issues if the application doesn't send anything back, as boofuzz may think there's a crash.  Those options can be tuned pretty easily; refer to section 6.1 'Session' in the boofuzz documentation. 

Now, I can make some more involved changes.  I have a script that will iterate through positions automatically, but what happens in a crash?  Currently, well, it's similar to our initial python script.  Nothing happens.  The script can't send any more and dies.  The application also dies, forcing me to manually restart it.  Also, if you've followed along and noticed, each time you restart boofuzz it starts back at the first fuzz point.  That sucks.  Assuming you get a crash on the fifth element in the list, it would take a long time to get back to the same position, as well as crashing at the same location unless it's removed.

There are some issues to address:
  1. Add session tracking
  2. Add a 'SKIPVAR' parameter
  3. Add some process monitoring / debugging
    • Procmon
Okay, so the changes that are being made aren't a lot of work, but they do involve some understanding of how sessions work, as well as number three above being a bit more... involved.  I'm not going to cover how to install the process monitoring functionality of boofuzz, you can find that here.

Here's an updated script:
#!/usr/bin/env python
 
from boofuzz import *
import argparse
import sys
 
# Make a list of the target commands
commands = ['STATS','RTIME','LTIME','SRUN','TRUN','GMON','GDOG','KSTET','GTER','HTER','LTER','KSTAN']
 
parser = argparse.ArgumentParser(description='Boofuzz Script')
parser.add_argument('-t', '--target', help='Target to fuzz')
parser.add_argument('-p', '--port', default=80, help='Target port, default 80', nargs='?', const=int)
parser.add_argument('-f', '--filename', help='Session filename')
parser.add_argument('-s', '--skip', default=0, help='Tests to skip', nargs='?', const=int)
 
args = parser.parse_args()
 
host = args.target
port = args.port
sessFile = args.filename
skipTests = args.skip

# Quick sanity check for args
if len(sys.argv) < 3:
        print "[-] Too few arguments.  Run with -h"
        sys.exit()

# Create a session, create a target
session = sessions.Session(
        crash_threshold="10000",            # Arbitrarily high crash threshold, prevent premature script death
        check_data_received_each_request=1, # Placeholder in case we want to disable the check
        session_filename=sessFile,          # Session filename (saving boofuzz session data)
        restart_sleep_time=1,               # Time to wait after application restart
        skip=int(skipTests)                 # The amount of tests to skip
        )
target = sessions.Target(connection = SocketConnection(host, int(port), proto='tcp'))
 
# Initialize ProcMon 
# Add stop and start commands to procmon
# On crash, procmon will restart the process and send boofuzz some debug data
target.procmon = pedrpc.Client(host, 26002)
target.procmon_options = {
        "stop_commands":['wmic process where (name="vulnserver.exe") delete'],
        "start_commands": ['C:\\Users\\Win7Pro-SP1\\Desktop\\apps\\vulnServer\\vulnserver.exe 9999']
        }
# Initialize a request
s_initialize("packet")
 
# Create a group called 'verbs' using the 'commands' list
s_group("verbs", commands)    
# Create a block for use with groups
if s_block_start("packet", group="verbs"):
  s_delim(" ",fuzzable=0)
  s_string("AAA")
  s_string("\r\n")
s_block_end("packet") 
 
 
# Add target to session (will hang if PEDRPC is not running)
# Connect and prep the request
# Start the fuzzing
session.add_target(target)
session.restart_target(target)
session.connect(s_get("packet"))
session.fuzz()

Note: After doing a bit of testing, I've noticed that the script will hang when it adds the target to the session if procmon isn't running on the target machine.  Go ahead and expand upon it if you want and add some more quality of life stuff.

Double Note: The above script is working in boofuzz 0.0.12  Thanks to Erfan in the comments for pointing out this issue, but as of version 0.1, the current boofuzz iteration, the 'skip' parameter of the API call to 'session' no longer exists.  Currently looking for a workaround, but for all intents and purposes, simply removing the 'skip=int(skipTests)' line and the comma right before it should allow the script to function.

So, prior to running this script, as it employs the use of process_monitor.py, you'll need to run the process_monitor.py script on the target machine.  While running this, a few new things happen in boofuzz, and some extra output on procmon is visible, too:


The above is fairly similar to the last usage, though now I've assigned a file to track session status in case it's exited early, as well as some tests to skip (assigned as zero for the above).

First, see that boofuzz sends the start and stop commands to procmon and the process restarts and pydbg attaches to it. The target machine output is below:


Pretty neat, huh?

The real action happens when the application crashes.  Previously, it was discussed that a large issue with fuzzers like these is the lack of automation at crash time.  boofuzz makes that a thing of the past.  Check out what happens on the boofuzz side when the application finally crashes:


It also collects a bunch of information from pydbg, running on the target machine and displays it back in a great format.  The above includes the exact issue (Access violation when attempting to read 0x31313131, EIP) a register dump, a stack dump, and a SEH unwind.

Awesome.  The best part though, is that right after the above, the application is automatically restarted (as 'start' commands were sent) and the fuzzing continues.  Check out process_monitor.py on the target machine, below:


In that screenshot you can see two crashes were generated-- both result in an EIP overwrite, but the whole point is that procmon then restarts the app, gives it a few seconds to 'settle in' and boofuzz resumes operation.

This enables much more freedom whilst fuzzing.  I'm no longer tied to the keyboard in that I'd have to stay present for the fuzzing.  By utilizing the process monitoring capabilities of boofuzz, it now collects information on crashes automatically.  I will still need to make sure the fuzzer is tuned in an acceptable fashion, but overall the set up isn't too hard.

That is merely scratching the surface on what boofuzz is capable of.  Go look at the documentation to see some of the other options that you may want to use.  One that immediately comes to mind is the amount of information it writes to STDOUT and the potential to quiet it down.  Although, yes, this is a nice feature for troubleshooting as there's no longer a need to open wireshark to see what boofuzz is sending, / receiving-- but once the fuzzer is set up acceptably, I could really do without the gratuitous amounts of information.  It becomes superfluos.

Anyway, another thing to mention about the script above is the amount of fuzz points.  I've utilized the 's_groups' functionality in that script to show the capabilities of block manipulation in a way that's applicable and easily understood.  However, if you run this script you'll note that crashes aren't generated until around test-case 20,000.  That's a lot of values to go through.  While thorough, it's usually better to start off simple and increase the complexity when necessary-- this is the real point of this post.

You'll certainly want to go through all the possible values while fuzzing, but once it starts crashing, try noting the value responsible and removing it from the list.  Perhaps create another list, only including values that caused unanticipated behavior.

Yet another thing that can help with quality of life in boofuzz is naming variables.  In the event you decide not to use a group, you can apply names and attributes to variables.  This will cause boofuzz output to include the variable name it's currently working on.  Not a big deal, just a little something to help readability when the script's running.

Other attributes that can be applied to the available primitives are all view able within the documentation.  I often use the following attributes for strings, i.e. 's_string'
  • padding
    • Padding to apply around string values, such as \x00
  • max_len
    • Max length for the value
  • encoding
    • Encoding scheme to use, default is ascii
and I frequently use the following for dealing with binary values, i.e. 's_binary'
  • endian
    • Defines big or little endian, default is the latter
  • signed
    • Defines whether or not the value is signed (only applied with ascii values)
  • full_range
    • If enabled, iterates through all possible values
A rather important attribute that can be applied to (primitives, minus s_static) is the 'fuzzable' directive.  By default, most values will be assumed fuzzable by boofuzz.  This 'fuzzable' attribute is similar to how one would define a fuzzable string in SPIKE, by writing 's_string_variable'.

boofuzz becomes a lot of fun when it's used for binary fuzzing as well.  You really get hands on with the data that's being sent, and breaking it down into different sections, like headers, blocks, static values, length fields, etc.  At some point I'll delve deeper into some of the intricacies involved in binary fuzzing.  I did write an article a while back about it, but it didn't keep it's focus.  I'm still learning, too :]

Takeaways

I've gone over a lot in this post-- typically this would entail many takeaways, but I think it can be kept it pretty simple on this one.
  • Remember to not over-complicate the task.  Try to break down a task into parts, or ask yourself objective questions to try to figure out where you should focus.
  • Don't try to reinvent the wheel.  There may be times where it's necessary, but start simple.
    • The bane of my existence lies here
  • Fuzzing takes time.  Learning new tools takes time.  Don't be afraid to try other tools if something doesn't appear to be working, but don't jump around too much either-- try to learn why something may not be working before switching to another method.
  • Lastly, it's not always so cut and dry as sending data one after another.  Be creative!  The term 'think outside the box' gets used a lot in this field, so I completely understand how that may not seem helpful.  Really though, it comes down to experimenting.  Try weird shit and look at the results.  
    • Remain curious.
Thanks for sticking with me on this one, it's not meant to be all-inclusive, and it's clearly not, but hopefully some folks get some ideas.





Comments

  1. That was an incredibly helpful series of articles. Thank you so very much for sharing.

    ReplyDelete
  2. Howard I'm trying to repliccate the last script in this post but seem to getting the error below?

    Traceback (most recent call last):
    File "test.py", line 27, in
    skip=int(skipTests) # The amount of tests to skip
    TypeError: __init__() got an unexpected keyword argument 'skip'

    ReplyDelete
    Replies
    1. Hey Erfan, thanks for the feedback.

      As for the error you're getting, it's hard to tell without looking at the script (which I also just made a minor but unrelated change to after looking at). Did you copy the script out, or are you writing a similar one?

      I just copied it out and it appears to be working correctly. Do you have argparse installed?

      Delete
  3. Hello Howard,

    Thank you for coming back to me :)

    As it stands, I copied the last script on this page directly into Sublime > Saved as test.py on a Kali box.

    Then configured a Windows XP host with process_monitor.py and vulnserver.

    I have checked for argparse and it seems to be installed. It is worth noting that all the previous scripts on this post do work without issues.

    ReplyDelete
    Replies
    1. I assume these scripts aren't for Python 3.x, right?

      Delete
    2. Not a problem, hopefully I can help. You're correct in that these are not for Python 3.

      First, make sure the whitespace isn't getting messed up on copy-paste, everything needs to be indented properly.. If that doesn't work, make sure there's not a comma missing on the line prior to the one it's erroring on. Looks like the error is in the section that's adding parameters to the boofuzz session variable.

      If it's still not working, try pulling down another copy of boofuzz and see if that addresses the issue, as we are trying to use the boofuzz API to set the 'skip' parameter of a session, which should exist.

      Delete
    3. Thank you very much. I have checked for indentations. re-installed Boofuzz but still the same.

      Interestingly, if I remove the lines related to the skip function, the script runs fines.

      Out of interest, what is your version of boofuzz? mine is 0.1.0

      Delete
    4. Nice, okay, so I was running 0.0.12, the newest release is 0.1.0, good find. Upon further investigation, skip has been removed from the session API call, and is no longer valid. The documentation has been updated TODAY:

      https://media.readthedocs.org/pdf/boofuzz/latest/boofuzz.pdf

      Looks like that functionality is gone. Not sure if it will be added back, I've inquired on the gitter, but who knows. Anyway, thanks for the heads up. I'll make a note in the post.

      Delete
    5. haha, thank you! at least we got to the bottom of it. I'll download Boofuzz version 0.0.12 for the time being. Appreciate your awesome work ;)

      Delete
    6. Good news Howard. Installing Boofuzz 0.0.12 sorted the issue. Scripts run perfectly.

      I feel like this is a stupid question so please bear with me. Where does boofuzz save the logs after fuzzing an application? I assume all application crashes are stored in a file somewhere?

      Delete
    7. Hi Erfan,

      Great question! This perhaps can be another blog post for some more advanced boofuzz usage.

      So process_monitor.py actually saves a file out, by default called 'crash.bin'. You can see that when you run it. This file will contain crash data. It's in binary format, so it's a little tricky to read normally, so boofuzz actually contains a tool called 'crash_explorer.py' within it's utils directory. You might need pgraph installed (pip install pgraph) but you should be able to 'explore' crashes by something akin to the following:

      C:\boofuzz\utils\crash_explorer.py

      Then, you'll see the test cases listed that caused crashes and were logged. From there, you can tack on the '-t' flag and list out the crash info for the specific test case. For example, if the previous command lists out a crash at test case 13, I can run:

      C:\boofuzz\utils\crash_explorer.py -t 13

      and be shown the relevant dump. boofuzz also has a 'print_session.py' script in it's utils that will print out relevant information from your session file, assuming you use one. This will print out the session data, but also the crash data from dumps it receives. It's not super pretty in the console, but it's good enough!

      Delete
    8. Ah that is excellent! Thank you very much for all the help. Looking forward to see more of your work on fuzzing :)

      Delete

Post a Comment

Popular posts from this blog

07 - Just Another OSCE Review

04 - DiskSavvy Enterprise 10.4.18 BoF SEH (and how I finally became not so bad at it)