-
-
Notifications
You must be signed in to change notification settings - Fork 327
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
matheusbaptistella
wants to merge
24
commits into
AFLplusplus:main
from
matheusbaptistella:libafl_ftg
+1,401
−0
Closed
libafl_wizard #1344
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 a1104de
Minor changes
matheusbaptistella 307fe4c
Changed questions file from CSV to TOML
matheusbaptistella b7eabdd
Merge branch 'main' of github.com:matheusbaptistella/LibAFL into liba…
matheusbaptistella c1e1d5e
Merge branch 'main' of github.com:matheusbaptistella/LibAFL into liba…
matheusbaptistella 2e8974e
Added variable number of answers/next questions
matheusbaptistella f146edb
Added an image overview of the questions diagram
matheusbaptistella 0b4b4de
Merge branch 'main' of github.com:matheusbaptistella/LibAFL into liba…
matheusbaptistella babb7cb
Added custom errors to validate the questions.
matheusbaptistella 9cc7077
Changed some aspects of the approach to deliver an initial working ge…
matheusbaptistella 21ad866
Changed some questions and added file functionality
matheusbaptistella e6d9947
Corrected a question that was wrong
matheusbaptistella b1f21ab
Renamed the project to libafl_wizar. Removed unwanted features. Curre…
matheusbaptistella 2d62c14
Updated the questions.
matheusbaptistella 5c4669f
Added the UI and changed some logic
matheusbaptistella 4effc52
Added flowchart image feature and the ui.
matheusbaptistella 992da1f
Removed the UI and replaced it by a CLI interface.
matheusbaptistella 0ee6bb5
Developed an initial set of question and the functions to write code …
matheusbaptistella 93ae225
Corrected the methods to arrange code and write it to the file
matheusbaptistella b570b74
(WIP) Added functions to arrange module imports
matheusbaptistella b71d705
(WIP) Restructured the functions related to imports.
matheusbaptistella b4e68d1
Implemented functions to arrange imports.
matheusbaptistella ea612f0
Added documentation and begin refactoring the imports function
matheusbaptistella a5fb809
Finished the wizard.
matheusbaptistella File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
); | ||
""" | ||
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 = [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.