Giter VIP home page Giter VIP logo

libafl-legacy's Introduction

LibAFL

This repository is for the C version of LibAFL. It is superseeded by LibAFL in Rust!

LibAFL is a fuzzing library/framework developed for building efficient fuzzers.

Disaclaimer: LibAFL is still very WIP, likely the API will change a lot, do not use it now. However, feel free to contribute also with ideas or requests opening an issue.

LibAFL was initially developed as GSOC project by rishi9101 GSOC mentors:

Content

  1. Introduction
  2. Elements of Fuzzing
  3. Features
  4. Getting Started with LibAFL

Introduction

LibAFL is a framework to build fuzzers, with support for mutithreading. The main concept behind LibAFL is not to build the "best" fuzzer, but to give you the tools to craft the best fuzzer for your specific target with ease.

We write this in C so you don't have to: eventually bindings for sane languages like rust will be added.

LibAFL contains all the pieces to build fuzzers, think "LLVM of fuzzers".

Elements of Fuzzing

LibAFL defines the pieces of fuzzer as follows (based on work by andreafioraldi):

  1. Executor - Structure to run the target and collecct observation (code coverage, exec time, ...). One example in ./examples uses the AFL++ forkserver, the other one an in-mem-executor.

  2. Observation Channel - Observation channel gives information about the last run of a target, depending on the context, e.g code-coverage metric and execution time.

  3. Feedback - Feedback is the structure which infers valuable information from the run result stored in the observation channel. It scores the input being fuzzed based on the observation channels, and adds new entries to the queue, if necessary.

  4. Queue - LibAFL supports two major types of queues.

    • Feedback queue - This type of queue is feedback specific i.e it is filled by entries which performed well based on a given feedback metric (say code-coverage). It also has a scheduling policy for itself which it can use to get next entries.
    • Global queue - This is the global queue for the fuzzer instance, it holds all the feedback queues, schedules one of the feedback queues for the next run. Also it has it's own queue which can also hold entries.
  5. Fuzz one - This executes the actual fuzzing - it holds all stages of the fuzz instance (see below), handles the crashes/timeouts of the target, etc.

  6. Stage - There can be many fuzzing stages in a fuzzer (e.g AFL has three stages, deterministic, havoc and splicing) and each stage can have it's own mutators. Thus a fuzzer can have multiple stages depending on if it's finding new finds in the current stage (or for how long the stage has been running).

  7. Mutators - Mutators have been defined to be as generic as possible with function pointers which can cover almost all mutators, (and if they don't, you can always extend them :) ). There are functions for mutating, trimming, post-process of the data which are called at appropriate places by the lib. Also, mutators also can define functions which decide if fuzzing a queue entry is beneficial or not. These mutator structures are largely inspired by AFL++'s custom mutator API.

  8. Engine - Finally, the engine is the central cog of the fuzzer. It binds all the other pieces(like executor, feedbacks, fuzzone) together. Every engine is also associated with a global queue, whose entries can be shared with other engine instances.

Features

As a fuzzing library, LibAFL packs with it large number of features which are very handy for fuzzing.

  1. Multithreaded by nature - Imagine that you built 2 fuzzers but want to share their results,You can define these 2 fuzzers, run the first one in a thread and run, e.g., 3 instances of the second running on 3 threads. They may even share results between process boundaries with very low overhead. There are several multithreaded fuzzers, most notably honggfuzz, but LibAFL goes even further with different configurations running in different threads and processes, not simply mutlithreading the same fuzzer. This means we are able to fuzz the same target in multiple contexts at the same time with great efficiency!

  2. Modular and pluggable - LibAFL is based on multiple "parts" that we think, consitutes a fuzzer. Did you ever write a fuzzer and want to use it's target executor? Just plug in the executor into the new fuzzer with LibAFL.

Getting Started with LibAFL

We have an example fuzzer ready at examples/executor.c so we can follow that. To try it out immediately:

cd ./examples
make test

Other examples are listed in ./examples/README.md.

If you want to build your own fuzzer, this is how to do it:

All the "Elements" (described above) in LibAFL can be extended by the user to suit their needs. to extend a strucuture, just include it in your custom struct as the first member

struct custom_executor {
    afl_executor_t base;    // We include the base executor as the first member
    int some_extra_stuff;
    void * some_other_stuff;
}

Each of this structure also has a set of function pointers assigned to them (sort of like a vtable) e.g

struct afl_executor_funcs {
 int (*run_target_cb)(
      afl_executor_t *); // The first argument of each function pointer is the executor itself.
 int (*place_input_cb)(
      afl_executor_t *,
      afl_raw_input_t *);
}

So, in order to override them, you just have to take assign that function pointer to your own implementation :) (Thus, these can be easily extended) e.g

int custom_run_target(afl_executor_t * executor) {
    /* Some Custom stuff, like a forkserver for AFL */
}

executor->funcs.run_target_cb = custom_run_target;
// We override the basic definition for run_target

It is not recommended to change the function signatures for these methods, as many of these function pointers are called by the library internally during fuzzing. So, if you do need some extra information for any function, it's always better to put it in an extended struct :)

The basic workflow for writing a fuzzer with LibAFL is as follows:

  1. Let's decide on the input structure now. Now, this is not necessary, if raw_bytes and a length are all we need, no problem. But if your target takes a structured input (like a PNG, PDF etc), you can extend the afl_raw_input_t structure to include those

  2. Build a simple executor, which runs the target, places the input for the target to read. A very simple example would be an executor which forks and executes the target as a child process, also placing the input correctly (in say, stdin or a file) for target to read. AFL's forkserver is another example of an executor.

// Let's build an executor.
executor_t example_executor = afl_executor_new(); // This function allocates memory for a "base" executor AND initializes it.

u8 place_input( afl_executor_t * executor, afl_raw_input_t * input ) {
    // We write to a file simply, this is totally user dependent
    int fd = open("some_file");
    write(fd, input->bytes, input->len);
}
exit_type_t run_target_cb(afl_executor_t *executor) {
    pid_t child = fork();

    if (!child) {
        exec("target"); // We execute the target
    }

    return 0;

};

executor_t example_executor = afl_executor_new();
example_executor->funcs.place_input_cb = place_input
example_executor->funcs.run_target_cb = run_target;
  1. If you're writing a greybox fuzzer, you probably want an observation channel which gains "insights" from the target's execution. An example would be the map based channel, which records the coverage (a simple map based channel is included in the library). Another observation channel could be the execution time of the target.
// Let's extend the base observation channel struct to make a timeout channel

struct timeout_channel {
    afl_observer_t base;
    u64 last_run_time;
    u64 avg_exec_time;
}

// It is assumed that the observation channel is updated by the target itself (in case of coverage recording) or by the executor after running the target.

// Since we extended the structure, We allocate memory for the structure ourselves;

struct timeout_channel tmout_channel = malloc(sizeof(struct timeout_channel));

// Every structure, apart from the create function, has an init function too, which initializes the structure. You do have to initialize the rest of your extended structure yourself though.
afl_observer_init(&tmout_channel->base, size_t unique_tag);

// Let's add it to the executor now, it gets added to an array of channels
executor->funcs.add_observer(executor, &tmout_base);
  1. You probably want to build a simple feedback for the observation channel, which reduces the observation channels input to a float (0.0 to 1.0) to decide the "score" of input in the context of that observation channel (e.g more code coverage, greater execution time means higher score). This feedback also decides if an input should be put in a feedback specific queue, global queue or both.
feedback_t * example feedback = afl_feedback_new(NULL, timeout_channel_id);  // We can add the feedback queue instead of NULL here, but we'll add them later.

float is_interesting(afl_feedback_t * feedback, afl_executor_t * executor) {
    
    afl_observer_t * obs_channel = feedback->channel;
    // Every feedback "should" store the correct idx of the observation channel in the array. 
    
    //We can use the channel's unique tag to identify them. See example/executor.c for this.

    // Again completely user dependent
    if (first_condition) {
        return 0.0 
    }   else if (second_condition) {

        feedback->queue->funcs.add_to_queue(feedback->queue, executor->current_input)
        return 1.0;
    }

    return 0.5;
}
  1. Now we create the queues for the fuzzer. If we have any feedback, and we want any feedback specific fuzzing, we can create a feedback queue and add it to the global queue. But we need a global queue always.

In case of queues, we don't expect the user to extend or do much to the queue structure itself, but the scheduling, culling etc are totally on user's choice

// Let's create a global queue, one for each fuzzing "instance" we have.
afl_queue_global_t *global_queue = afl_queue_global_new(NULL); // NULL is for the engine, if present pass a ptr to it.

// Let's create a feedback queue, for the feedback we create above.
afl_queue_feedback_t * feedback_queue = afl_queue_feedback_new(feedback, "Timeout feedback queue");

// Let's add it to the global queue
global_queue->funcs.add_feedback_queue(feedback_queue);   // Notice how we actually use funcs instead of funcs, this is because global_queue is extended from base_queue and required a few extra function pointers, thus this. 

It's totally upto the user to redefine the queue's scheduling algorithms (both global and feedback queue's) according to their needs

  1. Let's get a few mutators running now. Each mutator is part of a fuzzing stage (like AFL has three stages, deterministic, havoc and spilcing). So, every stage has it's own mutator.
afl_mutator_t * mutator = afl_mutator_new(NULL);
// We'll add it to the stage later.

void mutate(afl_mutator_t * mutator, afl_raw_input_t * input) {

    // Some mutation operator, bit-flip, byte flip etc.

    // We actually make a copy of the original input before sending it to mutate, so no need to worry about chaning the given input
}

void trim(afl_mutator_t * mutator, afl_raw_input_t * input) {
    // Trimming function for the mutator
}

mutator->funcs.mutate= mutate;
mutator->funcs.trim = trim;

fuzzing_stage_t * stage  = afl_fuzzing_stage(NULL);
// This is a fuzzing stage(mutations and stuff) so we use fuzzing_stage structure, there can be stages without mutation and mutators. 

stage->funcs.add_mutator_to_stage(stage, mutator);
// We add the mutator
  1. Let's create a few cogs for the fuzzer, like engine and the fuzz_one. Engine is the central part of the fuzzer which holds everything else together.
afl_fuzz_one_t * fuzz_one = afl_fuzz_one_new(NULL);
// Let's add the stage to the fuzzone

fuzz_one->funcs.add_stage(fuzz_one, stage);

afl_engine_t * engine = afl_engine_new(executor, fuzz_one, global_queue);
  1. We're done here. Now, we can just run the fuzzer :)
engine->funcs.load_testcases_from_dir(engine, dirpath); // Load the initial corpus

engine->funcs.loop()    // Fuzzing starts :)

Enjoy!

libafl-legacy's People

Contributors

andreafioraldi avatar domenukk avatar hexcoder- avatar rish9101 avatar skylined avatar vanhauser-thc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

libafl-legacy's Issues

When we should go public

Rishi did good work, but ofc libafl is a greater thing than GSoC and it will not be ready for the end of August.

Should we make it public to highlight the contribution of Rishi? IIRC this is not needed for GSoC, and if he will do a blogpost this should be fine.

We can publish it with a WIP disclaimer while slowing migrate pieces of AFL++ here. My main concern is that other people can "steal" good ideas from here before we finish because maintaining compatibility with AFL will take more time than rewriting a fuzzer from scratch.

What should we do?

Directory format

We have entities (eg executor) and for each entity we will provide some implementations into libafl (eg inmemoryexecutor and forkserverexecutor). These implementations are part of the library, they should not be in the example folder.

I propose to create a subfolder in include and src for each entity and place into them a main file with the asbtract class and the other implementations.

For instance.

include/executor/executor.h contains only the abstract class
include/executor/in_memory_executor.h contains the implementation

same for src/

Btw just look at FFF

Build system

If libafl has to be multi platform, we cannot really use just GNU makefiles.

I propose meson as it was recently adopted by QEMU it and seems a sane build system, more than cmake for sure.

We can maintain both meson files and raw makefiles for linux/bsd if needed.

AFL++ Custom Mutator Support

Right now LibAFL only supports its own, internal, mutators at build time. It may be beneficial to add a wrapper around the current mutators that can interface with existing Custom Mutators (at least those in C).

Crash analysis - more info about crash in/from LibAFL

Wrote a harness to fuzz a lib using LibAFL.

I get a crash within LibAFL, but cannot reproduce it outside LibAFL (almost identical harness ... outside LibAFL I added just main() function)

Would be great if LibAFL could save stacktrace, registers along the crashfile etc during the observed crash to debug it.

Could be that it is an issue in my harness or LibAFL.

Having this info would help to debug issues with harnesses, also for other projects and other users.

Thanks,

P.S Cool project

Order of implementation of features

So there is as little work needed to push changes in afl++ into libafl I think we should define the order in which the library features are to be implemented.

So using mostly the names from: https://github.com/AFLplusplus/Website/blob/master/content/aflpp_fuzzing_framework_proposal.md

this starts with queue and then mutator as these are the least like to change. the further down the more likely there will be changes in either afl++ or how we envision what features are useful on the way.

queue : Structure & Handling (Struct; Get, Add, Remove)
queue : Generic seed scheduler (-p stuff)
queue : Generic seed scheduler : custom
queue : Trim (for new new entries)
queue : Trim : custom
Mutator : structure
Mutator : determinstic
Mutator : havoc
Mutator : mopt
Mutator : custom
Executor (Forkserver, Fauxserver, Network Connector): Structure
Executor : Input Channel (A way to send a new testcase to the target (multiple can be stacked)) > andrea: why multiple? marc: maybe so this can be threaded? one controller creating the input and putting them in a qeue, and the executor passes them on one by one. but: IMHO this would not be good for performance, too much complexity, too much code)
Executor: Observation Channel (shared mem, data from a tcp connection)
Feedback : Structure
Feedback : Converter (Reduce input channel information to something useful)
Feedback : Analysis crash or new path, etc.

Not sure what this is:
Virtual Input (hold input buffer and associated metadata (e.g. structure))
Feedback
Feedback specific queue
Feedback specific seed scheduler
Feedback specific seed energy

feel free to change or discuss

Compile problem under Debian Buster 32-Bit

When compiling examples/libpng-1.6.37/contrib/libtests/pngvalid.c, I get
clang -DHAVE_CONFIG_H -I. -g -fPIC -Iinclude -Wall -Wextra -Werror -Wshadow -fstack-protector-strong -D_FORTIFY_SOURCE=2 -O3 -MT contrib/libtests/pngvalid.o -MD -MP -MF $depbase.Tpo -c -o contrib/libtests/pngvalid.o contrib/libtests/pngvalid.c &&
mv -f $depbase.Tpo $depbase.Po
In file included from contrib/libtests/pngvalid.c:26:
In file included from /usr/include/signal.h:25:
/usr/include/features.h:185:3: error: "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE" [-Werror,-W#warnings]
# warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE"
^
1 error generated.
make[3]: *** [Makefile:1196: contrib/libtests/pngvalid.o] Error 1

pngvalid.c line 24 has
#define _BSD_SOURCE 1 /* For the floating point exception extension */

The problem is in the Makefile which exports CFLAGS instead of setting CFLAGS as make argument.

Status update

@rish9101 as I not one of your GSoC mentors, but your work is closely related to my specification for a fuzzing framewok, can you update me on the status of your project?

I guess, at this point, your reimplemented my FFF poc in C and added things from AFL++. Can I build a fuzzer with the current API?

Add to Fuzzbench

This is a longer-reaching issue:
We should eventually add LibAFL to Fuzzbench, potentially even the in-mem fuzzer (crushing all other fuzzers :) ).

C++ Bindings

We need a nice way to interface with LibAFL from C++.

Autogenerate Docs & more Documentation

Right now all doc is inside the code.
We should set up a sphinx instance or some other nice way to make the API browseable.
To get started with sphinx, there is this project (not super actively maintained) which may just be enough or this blog post that seems to be a bit more work, going through doxygen first.
I didn't find a way to get the sphinx thing going directly, without additional tools, but maybe there's an easy way.

Once we decided on a documentation format, we can start writing more docu.

Possible PoC Usecases

We can collect some use cases here that would be nice to make possible using libAFL.

I'll start with a "proper" untracer support
https://twitter.com/buherator/status/1269396963998543872?s=19
Taking away breakpoints as they are hit also means that subsequent runs are not reproducible.
Subsequent runs with the same input should be considered successful on "no new coverage". Instead of "same hash".

How to multicore by dom

My concept:

  • The main thread parses commandline flags, loads all test cases from disk, spawns all threads ("engines"). All engines have unique seeds (like main seed + engine id).
  • The main thread makes sure there are more queue elements than threads, worst case it clones them.
  • Afterwards, the main thread just sleeps, shows some stats, sleeps, writes to sync dir/ disk, tears down the workers on ctrl+c
  • The queue(s) (linked lists for now) live in a shared map, each queue element includes a in_use flag
  • If an engine looks for the next queue element to fuzz, it walks the queue (or starts from the beginning), keeping an internal ptr, does a CAS on in_use, if it doesn't work, it increases it and walks to the next element, repeat.
  • After fuzzing, in_use is reset.
  • If a new queue entry needs to be added, a global lock for this queue is acquired (spinlock), then the new element is added.

Downsides:

  1. All threads work on the same shared map
  2. inserts can be slow (however that will only be a problem in the first minutes)

AFL-Style Testcase Support

Right now, in the example, each thread outputs lines to its own little file.
Instead, the broker or a background thread could write new queue entries to disk, whenever a new queue message flys by.
On top, we should add the option to resume a longer-running fuzzing campaign, and maybe even sync with AFL workers. This could also be done in the background thread.

How to multicore by marc

Goals:

  • no locks because its a bottleneck.
  • no or very little heap, because a target running havor could trash it
  • even if the threads all die the data is still there

There is one primary process which is the started process.
It forks of a single secondary, which is responsible for spawning the fuzzing childs.
It also creates a central shmem where all fuzzers register (a unique id, shmem IDs of queue, dictionary, ...)
it also occasionally (like every 30 minutes) persists the data to disk, and besides recovering if all threads die does (so far) nothing.

The secondary spawns off as many fuzzing threads as required and is itself also a fuzzer.

Every fuzzing thread first registers itself in the register. this needs a lock, once.
If a thread crashes it will not update a timing field in its main map anymore, the primary can do an alive check occasionally and tell the secondary to respawn, or forks of another secondary which does that.

Every fuzzer writes into his maps (e.g. info map, queue map, dict map, service request map, ...) and visits all other fuzzers occasionally (shmem IDs from the register) for stuff it is interested in. only read access to other fuzzer maps, no writing.
e.g. selecting a queue entry could be rand_below(number_of_fuzzers) -> rand_below(selected_fuzzer->queue->no_of_entries).
(needs more glue, as the selection should be based on a calibration and weighting the entries)

and that is already it. except for the central register no locks and no writing into foreign memory.
no performance problems.

and with the knowledge of the shmemID of the register you can even start different fuzzers that join in.

other idea I had: a fuzzer can perform services for other fuzzers.
e.g. if fuzzer A needs a path to be solved where it has no finds,and thenwrites into his service request map to please handle queue entry X for cmplog and laf.
A fuzzer B that has cmplog visits occasionally the service request map of others and if it sees something it can perform and has nothing better todo can then perform that and writes that it was completed into his own service request map.

Cleanup Makefiles

The Makefiles right now are far from ideal...
It would be great to have them cleaned up a little.

Get some CI going

Right now, we don't have any CI for LibAFL.
However, we do have unittests and examples that can pose as test cases. These should be added to Travis, similar to AFL++.

Oracle class

A fuzzer can look for something different than crashes. Think about timeouts for instance.

The oracle class looks at observation channels, similarly to feedback, but it decides if the testcase is crashing, not if it is interesting.
For regular fuzzing, you should define the exit code of a process as an observation channel. To make the AFL oracle, you need two observation channels that are the exit code and the coverage map.

The dedup (you can choose also that dedup is part of oracle, not a separate entity) take a crashing input, look at observation channels and say of a crash worth saving (not a duplicate). in AFL this just uses the feedback to see if the crash triggers new coverage.

For timeout, the oracle will use the coverage and the timing observation channels. I hope it is clear.

In-Mem Crash Recovery

Right now, a Crashing input in the In-Memory Fuzzer will be lost.
This is rather unfortunate. As we use shared maps/llmp for fuzzing, the input is still around after a crash, and a new process, or even the broker, should be able to recover it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.