- So your strategy when you're fuzzing.
If you don't know a whole lot
about the target that you're fuzzing,
don't wait until you have a complete suite
of tests with the fuzzer.
Always fuzz.
That's my recommendation to you.
Fuzzing takes a long time.
And so you really don't want to be waiting
until the very end when you have a collection of tests.
Just start fuzzing.
And it could be just a simple mutational fuzzer.
When some cases you're doing an evolutionary fuzzer,
you can set up something really quickly.
So a fuzzing can take really a long time
if you want it to be complete.
It can take weeks, and so if you can,
try to fuzz in parallel.
If you know a target, if you know the version
of software that's being run,
run as many instances of that as you can.
You can even run them in a cloud
like in Amazon Web Services or Azure.
Just have multiple instances of it,
and you're fuzzing at the same time.
Maybe starting from a different test case
and that way you finish things a lot faster
and you can get better results.
So when you're looking for fuzzing,
you're looking for code coverage.
In other words, your one exercise is much
of the program's parser and code that's based on the parser.
Information that comes from the parser,
you want to execute that as much as possible.
So you can get as much coverage.
Some of the basic fuzzers that you're dealing with,
like mutational fuzzers, they really only find results
for bugs that are what are called shallow.
In other words, you're not going very far
into the parser itself.
You're doing maybe just a little bit
of things on the outside.
And really to get any further than that,
you either need to do the generational fuzzer
where you have information about
how the protocol is supposed to work.
Or the evolutionary fuzzers which actually monitor
the programs so that you can get
more deep into the program.
So that's what you need to have.
A complex and sometimes even a custom-written fuzzer.
So in other words, you're writing everything either
from scratch or you're using some kind of fuzzing framework,
and I'll go through some of those later on.
And there are certain kinds of vulnerabilities and bugs
that you will not find with fuzzing.
This is an example.
There are some vulnerabilities
that will not crash your program
because they are basically just a logic error,
or they allow you some additional access
to do things that you shouldn't be doing
like injecting a command.
That's a very common vulnerability
especially on network equipment
is a command injection vulnerability.
And what that means is that normally
you would issue a command on the command line interface,
and it would run some program in the operating system.
And then you may be able to tack on something
like if you do a semicolon,
you could maybe tack on a shell command to that
and be able to do things like executing a Netcat
and calling home to you and then getting that shell.
Another thing you can do is maybe directory traversal.
You can access part of the file system
that you shouldn't be able to access.
These are things that fuzzing will not uncover
because they do not cause the program to crash,
and there's really no way of knowing
what it is that it was looking at.
So in these cases, you may able to do some kind
of instrumentation and analysis after the fact
to find out what the device is responding with.
But in other cases, you just won't find them.
So here's how you choose what to fuzz.
When I approach a target, and I wanna know
what I should work on first,
first I wanna know what user inputs are used by the program.
What actual user inputs can I supply,
and which of those are from an untrusted source?
In other words, are they coming from a session
that is already authenticated?
If I'm already logged in with SSH,
it may not be as important as if I had actually been able
to send this as a packet over the internet, for instance.
So if you have those kinds of inputs that are untrusted,
you would want to prioritize those above all others.
What features are mature versus immature?
If it's a new feature, it may have
been implemented just recently.
And the code may not have been tested as well.
So if you know a part of the feature
or you know a feature of the code that is newer,
you may fuzz that first because
the code has not been as well vetted.
And then there are other things you need
to worry about when you're choosing what to fuzz.
Are there input constraints which the parser checks first?
In other words, does the input have to be null terminated?
In other words, are they doing something
like we did earlier with a string copier?
Are they using a strcpy?
So the input must be null terminated.
Otherwise it just continues to copy.
You have certain packets and payloads
that you need that have a checksum in them,
and that checksum has to be correct.
Before it'll look any further into the packet,
you need to know that that checksum
has to be fixed up first.
Otherwise the parser will just see that
that's no good and just throw the packet away.
And same thing if you're doing
a secure connection using TLS.
Maybe you have to have that set up first.
You have to actually have a connection set up first.
The input may have length fields.
It has count fields, and those have
to match some kind of payload.
Those are all fix ups that you would have
to do when you're choosing what to fuzz.
And so here is a drawing of program input.
Some of these may be fairly obvious to you.
Like maybe a socket that you're
listening on the network for.
And then there's others that may not be very obvious to you.
You have environment which are values
that are stored with the program when the program executes.
Those can be like the path variable
that allows you to look in the file system
for certain programs.
You may be able to modify that environment
before you invoke a program.
You have file systems and files
that are accessible from the application.
Maybe when you're running the application,
it has files that it uses on the
file system for doing sockets.
That's called a Unix domain socket.
You have memory.
So memory could be mapped into an
application from somewhere else.
It could be shared memory based on a file.
Also if you write into that file
and that memory is executable,
then you might be able to run things
that are directly in that shared memory.
You have positive message queues,
and you have libraries that are mapped in.
And those libraries can either be libraries
that are on disk like the libc,
and there's also libraries called
the virtual dynamic shared object which are libraries
that the kernel itself provides to each process.
If you ever heard of an attack recently called Dirty Cow,
one of the ways to be able to exploit a process was
to overwrite the virtual dynamic shared object.
So that when something got called in there from the kernel,
you're actually calling some attacker code,
and you've actually taken the process over.
So this is the fuzzing process.
We're gonna start from the top and work our way down.
So what you'll do first is you'll set up
the test environment so that it's
in the state that you want.
You'll pick a random seed.
Typically when you're doing some kind of fuzzing,
you'll have a random number generator
that will determine what inputs you actually fuzz
and then the values that you might use.
So you want to have a random seed
that is available that you know,
so that when you're fuzzing and you cause a crash later on,
you want to be able to start from that random seed.
So if you're not recording that anywhere,
if you fuzz again, you use a different seed,
you may have different test cases.
So then you also want to choose the input.
You wanna choose what value you wanna fuzz.
And then the fuzzer will create a test case.
In other words, it creates an
anomalous message based on that field.
And then you run the target
with that test case as the input.
And then you instrument the target and check for a crash.
And if you have a crash, then you would record
the test case inputs and the crash data.
And then you would move on and go back
to choose the input again.
And if you don't crash, then I guess it was okay.
There wasn't anything that happened.
So you may kill the target and restart it again
just to get you back into that original state.