The Mutiny Fuzzing Framework is a network fuzzer that operates by replaying PCAPs through a mutational fuzzer. The goal is to begin network fuzzing as quickly as possible, at the expense of being thorough.
The general workflow for Mutiny is to take a sample of legitimate traffic, such as a browser request, and feed it into a prep script to generate a .fuzzer file. Then, Mutiny can be run with this .fuzzer file to generate traffic against a target host, mutating whichever packets the user would like.
There are extensions that allow changing how Mutiny behaves, including changing messages based on input/output, changing how Mutiny responds to network errors, and monitoring the target in a separate thread.
Mutiny uses Radamsa to perform mutations.
The Decept Proxy is a multi-purpose network proxy that can forward traffic from a plaintext or TLS TCP/UDP/domain socket connection to a plaintext or TLS TCP/UDP/domain socket connection, among other features. It makes a good companion for Mutiny, as it can both generate .fuzzer files directly, particularly helpful when fuzzing TLS connections, and allow Mutiny to communicate with TLS hosts.
sample_apps give a basic idea of some things that can be done with the fuzzer, with a few different applications/clients to test with.
Written by James Spadaro (jaspadar@cisco.com) and Lilith Wyatt (liwyatt@cisco.com)
Setup
Ensure python and scapy are installed.
Untar Radamsa and
make
(You do not have to make install, unless you want it in /usr/bin - it will use the local Radamsa) Update mutiny.py
with path to Radamsa if you changed it.Basic Usage
Save pcap into a folder. Run
mutiny_prep.py
on <XYZ>.pcap
(also optionally pass the directory of a custom processor if any, more below). Answer the questions, end up with a <XYZ>.fuzzer
file in same folder as pcap.Run
mutiny.py <XYZ>.fuzzer <targetIP>
This will start fuzzing. Logs will be saved in same folder, under directory <XYZ>_logs/<time_of_session>/<seed_number>
More Detailed Usage
.fuzzer Files
The .fuzzer files are human-readable and commented. They allow changing various options on a per-fuzzer-file basis, including which message or message parts are fuzzed.
Message Formatting
Within a .fuzzer file is the message contents. These are simply lines that begin with either 'inbound' or 'outbound', signifying which direction the message goes. They are in Python string format, with '\xYY' being used for non-printable characters. These are autogenerated by 'mutiny_prep.py' and Decept, but sometimes need to be manually modified.
Message Formatting - Manual Editing
If a message has the 'fuzz' keyword after 'outbound', this indicates it is to be fuzzed through Radamsa. A given message can have line continuations, by simply putting more message data in quotes on a new line. In this case, this second line will be merged with the first.
Alternatively, the 'sub' keyword can be used to indicate a subcomponent. This allows specifying a separate component of the message, in order to fuzz only certain parts and for convenience within a Message Processor.
Here is an example arbitrary set of message data:
outbound 'say'
' hi'
sub fuzz ' and fuzz'
' this'
sub ' but not this\xde\xad\xbe\xef'
inbound 'this is the server's'
' expected response'
This will cause Mutiny to transmit say hi and fuzz this but not this(0xdeadbeef)
. 0xdeadbeef
will be transmitted as 4 hex bytes. and fuzz this
will be passed through Radamsa for fuzzing, but say hi
and but not this(0xdeadbeef)
will be left alone.Mutiny will wait for a response from the server after transmitting the single above message, due to the 'inbound' line. The server's expected response is
this is the server's expected response
. Mutiny won't do a whole lot with this data, aside from seeing if what the server actually sent matches this string. If a crash occurs, Mutiny will log both the expected output from the server and what the server actually replied with.Customization
mutiny_classes/ contains base classes for the Message Processor, Monitor, and Exception Processor. Any of these files can be copied into the same folder as the .fuzzer (by default) or into a separate subfolder specified as the 'processor_dir' within the .fuzzer file.
These three classes allow for storing server responses and changing outgoing messages, monitoring the target on a separate thread, and changing how Mutiny handles exceptions.
Customization - Message Processor
The Message Processor defines various callbacks that are called during a fuzzing run. Within these callbacks, any Python code can be run. Anecdotally, these are primarily used in three ways.
The most common is when the server sends tokens that need to be added to future outbound messages. For example, if Mutiny's first message logs in, and the server responds with a session ID, the
postReceiveProcess()
callback can be used to store that session ID. Then, in preSendProcess()
, the outgoing data can be fixed up with that session ID. An example of this is in sample_apps/session_server
.Another common use of a Message Processor is to limit or change a fuzzed message. For example, if the server always drops messages greater than 1000 bytes, it may not be worth sending any large messages. preSendProcess() can be used to shorten messages after fuzzing but before they are sent or to raise an exception.
Raising an exception brings up the final way Message Processors are commonly used. Within a callback, any custom exceptions defined in
mutiny_classes/mutiny_exceptions.py
can be raised. There are several exceptions, all commented, that will cause various behaviors from Mutiny. These generally involve either logging, retrying, or aborting the current run.Customization - Monitor
The Monitor has a
monitorTarget()
function that is run on a separate thread from the main Mutiny fuzzer. The purpose is to allow implementing a long-running process that can monitor a host in some fashion. This can be anything that can be done in Python, such as communicating with a monitor daemon running on the target, reading a long file, or even just pinging the host repeatedly, depending on the requirements of the fuzzing session.If the Monitor detects a crash, it can call
signalMain()
at any time. This will signal the main Mutiny thread that a crash has occurred, and it will log the crash. This function should generally operate in an infinite loop, as returning will cause the thread to terminate, and it will not be restarted.Customization - Exception Processor
The Exception Processor determines what Mutiny should do with a given exception during a fuzz session. In the most general sense, the
processException()
function will translate Python and OS-level exceptions into Mutiny error handling actions as best as it can.For example, if Mutiny gets 'Connection Refused', the default response is to assume that the target server has died unrecoverably, so Mutiny will log the previous run and halt. This is true in most cases, but this behavior can be changed to that of any of the exceptions in
mutiny_classes/mutiny_exceptions.py
as needed, allowing tailoring of crash detection and error correction.Quickstart: Mutiny tutorial
Blog post here:
Links to this YouTube video demo: