Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

libafl_wizard #1344

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
cdfd37e
Initial commit for GSOC's 2023 Fuzzer Template Generator project
matheusbaptistella Jul 5, 2023
a1104de
Minor changes
matheusbaptistella Jul 5, 2023
307fe4c
Changed questions file from CSV to TOML
matheusbaptistella Jul 6, 2023
b7eabdd
Merge branch 'main' of github.com:matheusbaptistella/LibAFL into liba…
matheusbaptistella Jul 6, 2023
c1e1d5e
Merge branch 'main' of github.com:matheusbaptistella/LibAFL into liba…
matheusbaptistella Jul 17, 2023
2e8974e
Added variable number of answers/next questions
matheusbaptistella Jul 17, 2023
f146edb
Added an image overview of the questions diagram
matheusbaptistella Jul 17, 2023
0b4b4de
Merge branch 'main' of github.com:matheusbaptistella/LibAFL into liba…
matheusbaptistella Jul 24, 2023
babb7cb
Added custom errors to validate the questions.
matheusbaptistella Jul 24, 2023
9cc7077
Changed some aspects of the approach to deliver an initial working ge…
matheusbaptistella Aug 2, 2023
21ad866
Changed some questions and added file functionality
matheusbaptistella Aug 9, 2023
e6d9947
Corrected a question that was wrong
matheusbaptistella Aug 9, 2023
b1f21ab
Renamed the project to libafl_wizar. Removed unwanted features. Curre…
matheusbaptistella Aug 15, 2023
2d62c14
Updated the questions.
matheusbaptistella Aug 15, 2023
5c4669f
Added the UI and changed some logic
matheusbaptistella Aug 27, 2023
4effc52
Added flowchart image feature and the ui.
matheusbaptistella Nov 9, 2023
992da1f
Removed the UI and replaced it by a CLI interface.
matheusbaptistella Nov 14, 2023
0ee6bb5
Developed an initial set of question and the functions to write code …
matheusbaptistella Nov 22, 2023
93ae225
Corrected the methods to arrange code and write it to the file
matheusbaptistella Nov 27, 2023
b570b74
(WIP) Added functions to arrange module imports
matheusbaptistella Dec 19, 2023
b71d705
(WIP) Restructured the functions related to imports.
matheusbaptistella Dec 21, 2023
b4e68d1
Implemented functions to arrange imports.
matheusbaptistella Dec 26, 2023
ea612f0
Added documentation and begin refactoring the imports function
matheusbaptistella Jan 3, 2024
a5fb809
Finished the wizard.
matheusbaptistella Jan 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ members = [
"libafl_tinyinst",
"libafl_sugar",
"libafl_nyx",
"libafl_wizard",
"libafl_concolic/symcc_runtime",
"libafl_concolic/symcc_libafl",
"libafl_concolic/test/dump_constraints",
Expand Down
17 changes: 17 additions & 0 deletions libafl_wizard/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "libafl_wizard"
version.workspace = true
edition = "2021"
authors = ["matheus <[email protected]>"]
description = "libafl's fuzzer template generator"
documentation = "TODO"
repository = "https://github.com/AFLplusplus/LibAFL/"
readme = "../README.md"
license = "MIT OR Apache-2.0"
keywords = ["fuzzing", "testing", "security"]
categories = ["development-tools::testing"]

[dependencies]
toml = "0.7.6"
serde = { version = "1.0", features = ["derive"] }
graphviz-rust = "0.6.6"
23 changes: 23 additions & 0 deletions libafl_wizard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# libafl_wizard

<img align="right" src="./icons/libafl_wizard.png" alt="libafl_wizard logo" width="250" heigh="250">

libafl_wizard is a tool to generate fuzzers using libafl's components. By answering some questions, you can generate your own fuzzer to test programs and help with the development of more secure code, all that while learning more about libafl and how it's used!

## Usage

libafl_wizard has a cli interface and can be run with:

```
cargo run
```

## Have in mind that...

The tool makes use of [graphviz](https://graphviz.org/download/) to generate an image containing the flowchart of the questions diagram, so the users can know beforehand where the answers will take them. Make sure it's installed (and added to PATH) before running the tool.

When writing answers, the check if an input is a valid answer is such that it simply verifies if what's typed by the user has the same characters so far as the answer (check out `validate_input()`). The thing is that, e.g. if "Crash or Timeout" and "Crash" are both valid answers, if the user answers "crash", the first option will be deemed correct (even though the user wanted the second one). To avoid that, for now, one can simply reverse these answers, so "Crash" comes before "Crash or Timeout".

## Contributing

libafl_wizard uses the `questions.toml` TOML file to store and load the questions that will be asked during the generation process. Each question contains some fields, like the possible answers for that question and the Rust code associated to those answers. As libafl's components get updated or new ones introduced, the questions need to be updated as well.
Binary file added libafl_wizard/icons/libafl_wizard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
271 changes: 271 additions & 0 deletions libafl_wizard/questions.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
[[question]]
id = "intro"
title = "libafl_wizard: a tool to generate fuzzers using Libafl's components."
content = """
Before starting, make sure that you know your target very well. Choosing the right components to build a fuzzer depends on the details
of the implementation of your target.

Knowing what you want before building a fuzzer can be extremely helpful when selecting the components that best fit your options and
the restrictions of the environment.

Details such as:
*Having the source code of the target.
*Implementing a harness function to fuzz your target.
*Knowing the expected input by the target.
*Providing an initial set of inputs.
*The size of the target and the availability of memory.
*The possibility of memory corruption.

Can be helpful during this process.
"""
skipped_by = ""
previous = "intro"

[[question.answers]]
was_chosen = false
answer = "Next"
next = "source code"
code = ""
skip = []

[[question]]
id = "source code"
title = "Do you have the target's source code?"
content = """
Having the target's source code is interesting for performance. With the source code in hands, we can instrument the target, that is,
place coverage information to help our fuzzer identify new points of execution (guide itself).

Without instrumentation on the source code, we have to rely on third-party applications to provide this information for our fuzzer,
such as QEMU, FRIDA or Tiny Inst.
"""
skipped_by = ""
previous = ""

[[question.answers]]
was_chosen = false
answer = "Yes"
next = "harness"
code = ""
skip = []

[[question.answers]]
was_chosen = false
answer = "No"
next = "END"
code = ""
skip = []

[[question]]
id = "harness"
title = "Can you provide a harness function?"
content = """
A harness function bridges the gap between how the fuzzer expects input to occur and how it's delivered. Essentially, the harness will
receive the input (properly structured) and call the target/function under test with it. This allows for in-process fuzzing (when the
harness is executed inside the fuzzer process), which increases performance a lot.

Not providing a harness makes in-process fuzzing impractical. Therefore, the only option left is to fork before executing the target,
which makes use of a shared memory region to record the coverage information.
"""
skipped_by = ""
previous = ""

[[question.answers]]
was_chosen = false
answer = "Yes"
next = "corrupt memory"
code = ""
skip = []

[[question.answers]]
was_chosen = false
answer = "No"
next = "crash/timeout"
code = """
/* Forkserver Executor */
"""
skip = []

[[question]]
id = "corrupt memory"
title = "Can your target corrupt memory used by the fuzzer?"
content = """
Under some circumstances, you may find your harness pretty unstable or your harness wreaks havoc on the global states. In this case,
you want to fork it before executing the harness runs in the child process so that it doesn't break things.
"""
skipped_by = ""
previous = ""

[[question.answers]]
was_chosen = false
answer = "Yes"
next = "crash/timeout"
code = """
use libafl::InProcessForkExecutor;
use libafl_bolts::{
shmem::{unix_shmem, ShMemProvider},
tuples::tuple_list,
};

let mut shmem_provider = unix_shmem::UnixShMemProvider::new().unwrap();

let mut executor = InProcessForkExecutor::new(
&mut harness,
tuple_list!(observer),
&mut fuzzer,
&mut state,
&mut mgr,
shmem_provider,
)
.expect("Failed to create the Executor");
"""
skip = []

[[question.answers]]
was_chosen = false
answer = "No"
next = "crash/timeout"
code = """
use libafl::executors::inprocess::InProcessExecutor;
use libafl_bolts::tuples::tuple_list;

let mut executor = InProcessExecutor::new(
&mut harness,
tuple_list!(observer),
&mut fuzzer,
&mut state,
&mut mgr,
)
.expect("Failed to create the Executor");
"""
skip = []

[[question]]
id = "crash/timeout"
title = "Do you expect to cause a crash or a timeout on the target?"
content = """
Determining the objective of the fuzzing campaign is essential to identify input that triggered critical errors on the target.

Telling the fuzzer that we are looking for a crash means that a testcase, which causes a crash on the target, fullfills the objective
and, differently from the ones that e.g. reach a new execution point, this testcase is saved in a separate folder for you to check the
input that trigerred the crash.

A timeout follows the same idea: a testcase that takes longer to execute than a particular timeout.

It's even possible to join these two ideas to instruct the fuzzzer that any testcase, which causes a crash or takes long enough to
execute, is stored.
"""
skipped_by = ""
previous = ""

[[question.answers]]
was_chosen = false
answer = "Crash"
next = "default components"
code = """
use libafl::feedbacks::CrashFeedback;

let mut objective = CrashFeedback::new();
"""
skip = []

[[question.answers]]
was_chosen = false
answer = "Crash or Timeout"
next = "default components"
code = """
use libafl::{
feedback_or_fast,
feedbacks::{CrashFeedback, TimeoutFeedback},
};

let mut objective = feedback_or_fast!(
CrashFeedback::new(),
TimeoutFeedback::new()
);
Comment on lines +176 to +184
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of importing I believe it would be more robust if the full-qualified path to the struct/item is used.

    let mut objective = libafl::feedback_or_fast!(
        ...
    );

"""
skip = []

[[question.answers]]
was_chosen = false
answer = "Timeout"
next = "default components"
code = """
use libafl::feedbacks::TimeoutFeedback;

let mut objective = TimeoutFeedback::new();
"""
skip = []

[[question]]
id = "default components"
title = "Type in finsish to end the generation."
content = """
"""
skipped_by = ""
previous = ""

[[question.answers]]
was_chosen = false
answer = "Finish"
next = "END"
code = """
use std::path::PathBuf;
use libafl::{
corpus::{InMemoryCorpus, OnDiskCorpus},
events::SimpleEventManager,
feedbacks::MaxMapFeedback,
fuzzer::{Fuzzer, StdFuzzer},
monitors::SimpleMonitor,
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
schedulers::QueueScheduler,
stages::mutational::StdMutationalStage,
state::StdState,
};
use libafl_bolts::{
current_nanos,
rands::StdRand,
tuples::tuple_list,
};

let mut feedback = MaxMapFeedback::new(&observer);

let mut state = StdState::new(
StdRand::with_seed(current_nanos()),
InMemoryCorpus::new(),
OnDiskCorpus::new(PathBuf::from("./crashes")).unwrap(),
&mut feedback,
&mut objective,
)
.unwrap();

let mon = SimpleMonitor::new(|s| println!("{s}"));

let mut mgr = SimpleEventManager::new(mon);

let scheduler = QueueScheduler::new();

let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);

let mutator = StdScheduledMutator::new(havoc_mutations());

let mut stages = tuple_list!(StdMutationalStage::new(mutator));

fuzzer
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
.expect("Error in the fuzzing loop");
"""
skip = []

[[question]]
id = "END"
title = "All questions answered!"
content = ""
skipped_by = ""
previous = ""

[[question.answers]]
was_chosen = false
answer = ""
next = ""
code = ""
skip = []
55 changes: 55 additions & 0 deletions libafl_wizard/src/answer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use serde::Deserialize;

/// The Answer struct contains all the necessary information that an answer must
/// have, such as the code to be added and the next question to be asked.
#[derive(Clone, Deserialize, PartialEq, Debug)]
pub struct Answer {
was_chosen: bool,
answer: String,
next: String,
code: String,
skip: Vec<String>,
}

impl Answer {
/// Returns true if this answer was chosen between all the possibilities for
/// a given question.
pub fn was_chosen(&self) -> &bool {
&self.was_chosen
}

/// Returns true if this answer was chosen between all the possibilities for
/// a given question.
pub fn set_was_chosen(&mut self, new_value: bool) {
self.was_chosen = new_value;
}

/// Returns a String that represents the text of that answer, e.g "Yes" or
/// "No".
pub fn answer(&self) -> &String {
&self.answer
}

/// Returns the id of the next question that will be asked, considering that
/// this answer was chosen.
pub fn next(&self) -> &String {
&self.next
}

/// Returns the Rust code that will be added to the fuzzer file, considering
/// that this answer was chosen.
pub fn code(&self) -> &String {
&self.code
}

/// Returns the ids of the questions that should be skipped, considering
/// that this answer was chosen.
///
/// In some cases, depending on the answer that the user chooses for a
/// particular question, it will skip other questions that shouldn't be
/// asked, e.g. if the user doesn't have the source code of the that target
/// the wizard shouldn't ask if they can provide a harness.
pub fn skip(&self) -> &Vec<String> {
&self.skip
}
}
Loading