- So what a mutation-based fuzzer,
I think it's fairly obvious,
is it takes well-formatted input
like a message, a string, maybe a file,
and it mutates the data.
And you don't need to have any structure of the input data
for mutation-based fuzzer, at least for very simple ones.
For good code coverage you need
that large collection of well-formed inputs.
This is called a corpus.
And you can get this information from the internet.
You're fuzzing against a file, or maybe you're doing fuzzing
against a certain packet format.
You can get just PCAPs that are from the internet
that are basically representations
of the data that the process would use.
And then you could mutate that data
and use it as fuzzing test cases.
And those mutations can include flipping bits.
You can duplicate certain fields.
You can randomize data that's in the message.
And then you can use your imagination.
There's many different ways you can mutate the data.
And then the result of the mutation
is to be fed into the target,
and that is your anomalous message.
And then, when you have a failure
you have to have some way of detecting that failure,
because sometimes you won't have a hard crash.
Or maybe the process is alive.
Maybe it's stuck,
maybe it is stuck in an infinite loop.
Maybe it no longer processes messages,
but the process is waiting for something, basically.
Waiting for a resource to be freed up.
So you have to have some way of monitoring this.
And typically, you would monitor the process directly
and you could just see whether or not
the process is running.
Or you can see whether or not it's consuming CPU.
If it consumes over 90% or 95% CPU
that would be indicative of an infinite loop scenario.
And then another way to detect failures,
which I would recommend,
is just to send a know good message.
Rather than sending an anomalous message
with modifications to it,
you could just send one that's good.
And if the process acts the way it should
and responds maybe the way it should,
then you know that it's fine.
You can move on.
And then if you don't have a response,
then you might have a denial-of-service condition.
Maybe it doesn't crash or anything,
maybe it just went off to lunch.
And then, so in the case of the crash,
you have to restart the process
and then note that information.
And then the anomalous message
and program crash information,
you save it, like I said before,
and then you would use that for further analysis.
Generation-based fuzzers,
these are your smart fuzzers.
And those work with a data model of the protocol,
and that data model has to be created in advance.
So it requires a lot of hard work first
before you can ever do any kind of fuzzing.
Generation-based fuzzers are typically
something you would do much later on
after you set up mutation for a little while.
You can do some generation fuzzing after you have a model.
You can actually create a message from scratch.
In other words, you're actually creating from the ground-up
a message based on the model.
And then, in other times,
you can just start from the corpus.
And then use the model to modify the corpus,
but also do those fix ups that you need
to make the protocol look the way it should
so that the parse, or parses the message.
And then modifications.
You add to the data model for each anomalous test case.
So you can start from one field
and you can go to another field.
And then the data model may include fix ups
that you have to do.
And those could be intelligently made
by the fuzzer based on the data model itself.
And those could be things
like length fields, for instance, or check sums.
And well, I found that generation-based fuzzers
do provide better results for code
that maybe is not as well mature.
If the code has been around for a long time,
you may find that generation fuzzers may not find anything.
It's kind of a, you know,
you really won't know until you try.
So to move on, on the newer generations of fuzzers,
the evolutionary fuzzers.
This was where a lot of research is happening.
And these actually generate test cases based
on looking at the program itself.
So you have a, another process that runs
alongside the program,
and it actually watches what's happening with the program.
So you can see what the code coverage is.
You usually start with something very small,
a very small seed corpus to get you into that state
to where you can start walking through the program,
and then figure out what you want to do from there.
And it aims for more complete coverage.
And you also want to eliminate redundant test cases
that are testing the exact same code.
There's really no point to running the same code
over and over again
just to look for those anomalous test cases.
And so it's much quicker to set up
and it may give you better results than both,
either mutation or generation-based fuzzing.
But there are some downsides to running
with an evolutionary fuzzer.
In some cases, the program has to be written a certain way.
It has to be written in a certain language.
It has to be that you have a source code available,
so you can recompile the program
so that you could instrument it
and actually listen to what's going on in the program.
And so you may need code available
and you may not have that code available.
So maybe that's something you can't do.
There are evolutionary fuzzers that do work on like QMU.
One of the examples is American fuzzy lop.
It's an experimental feature, but you can try it.
But this is definitely something to try,
simply, like I said,
you really want to always be fuzzing when you have a target,
and evolutionary fuzzer gives you a quick way
to get started.