Move all target-specific code to harness.rs (#2605)
This commit is contained in:
parent
830941ce3a
commit
2bfed2d488
@ -1,9 +1,13 @@
|
|||||||
# qemu_launcher_
|
# qemu_launcher_
|
||||||
|
|
||||||
This folder contains an example fuzzer for libpng, using LLMP for fast multi-process fuzzing and crash detection. It has been tested on Linux.
|
This folder contains an example fuzzer that will fuzz binary-only targets, cross-architecture, on Linux.
|
||||||
This automatically spawns n child processes, and binds them to a free core.
|
It's using LLMP for fast multi-process fuzzing and crash detection.
|
||||||
|
This automatically spawns `n` child processes, and binds them to a free core.
|
||||||
|
|
||||||
|
To adapt the fuzzer to your custom target, change [`harness.rs`](./src/harness.rs).
|
||||||
|
|
||||||
The following architectures are supported:
|
The following architectures are supported:
|
||||||
|
|
||||||
* arm
|
* arm
|
||||||
* aarch64
|
* aarch64
|
||||||
* i386
|
* i386
|
||||||
@ -11,10 +15,10 @@ The following architectures are supported:
|
|||||||
* mips
|
* mips
|
||||||
* ppc
|
* ppc
|
||||||
|
|
||||||
Note that the injection feature `-j` is currently only supported on x86_64
|
For usermode, this fuzzer supports injection fuzzing with `-j`.
|
||||||
and aarch64.
|
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt install \
|
sudo apt install \
|
||||||
gcc-arm-linux-gnueabi \
|
gcc-arm-linux-gnueabi \
|
||||||
@ -32,7 +36,8 @@ sudo apt install \
|
|||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
Defaults to `x86_64` architecture
|
Defaults to `x86_64` architecture. Change the architecture by
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo make run
|
cargo make run
|
||||||
```
|
```
|
||||||
|
@ -11,16 +11,16 @@ use libafl_bolts::{core_affinity::CoreId, rands::StdRand, tuples::tuple_list};
|
|||||||
#[cfg(feature = "injections")]
|
#[cfg(feature = "injections")]
|
||||||
use libafl_qemu::modules::injections::InjectionModule;
|
use libafl_qemu::modules::injections::InjectionModule;
|
||||||
use libafl_qemu::{
|
use libafl_qemu::{
|
||||||
elf::EasyElf,
|
|
||||||
modules::{
|
modules::{
|
||||||
asan::{init_qemu_with_asan, AsanModule},
|
asan::{init_qemu_with_asan, AsanModule},
|
||||||
asan_guest::{init_qemu_with_asan_guest, AsanGuestModule},
|
asan_guest::{init_qemu_with_asan_guest, AsanGuestModule},
|
||||||
cmplog::CmpLogModule,
|
cmplog::CmpLogModule,
|
||||||
},
|
},
|
||||||
ArchExtras, GuestAddr, Qemu,
|
Qemu,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
harness::Harness,
|
||||||
instance::{ClientMgr, Instance},
|
instance::{ClientMgr, Instance},
|
||||||
options::FuzzerOptions,
|
options::FuzzerOptions,
|
||||||
};
|
};
|
||||||
@ -55,16 +55,7 @@ impl<'a> Client<'a> {
|
|||||||
.collect::<Vec<(String, String)>>()
|
.collect::<Vec<(String, String)>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_pc(qemu: Qemu) -> Result<GuestAddr, Error> {
|
#[allow(clippy::too_many_lines)]
|
||||||
let mut elf_buffer = Vec::new();
|
|
||||||
let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer)?;
|
|
||||||
|
|
||||||
let start_pc = elf
|
|
||||||
.resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr())
|
|
||||||
.ok_or_else(|| Error::empty_optional("Symbol LLVMFuzzerTestOneInput not found"))?;
|
|
||||||
Ok(start_pc)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run<M: Monitor>(
|
pub fn run<M: Monitor>(
|
||||||
&self,
|
&self,
|
||||||
state: Option<ClientState>,
|
state: Option<ClientState>,
|
||||||
@ -72,9 +63,11 @@ impl<'a> Client<'a> {
|
|||||||
core_id: CoreId,
|
core_id: CoreId,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut args = self.args()?;
|
let mut args = self.args()?;
|
||||||
|
Harness::edit_args(&mut args);
|
||||||
log::debug!("ARGS: {:#?}", args);
|
log::debug!("ARGS: {:#?}", args);
|
||||||
|
|
||||||
let mut env = self.env();
|
let mut env = self.env();
|
||||||
|
Harness::edit_env(&mut env);
|
||||||
log::debug!("ENV: {:#?}", env);
|
log::debug!("ENV: {:#?}", env);
|
||||||
|
|
||||||
let is_asan = self.options.is_asan_core(core_id);
|
let is_asan = self.options.is_asan_core(core_id);
|
||||||
@ -96,9 +89,6 @@ impl<'a> Client<'a> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let start_pc = Self::start_pc(qemu)?;
|
|
||||||
log::debug!("start_pc @ {start_pc:#x}");
|
|
||||||
|
|
||||||
#[cfg(not(feature = "injections"))]
|
#[cfg(not(feature = "injections"))]
|
||||||
let injection_module = None;
|
let injection_module = None;
|
||||||
|
|
||||||
@ -118,13 +108,7 @@ impl<'a> Client<'a> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
qemu.entry_break(start_pc);
|
let harness = Harness::init(qemu).expect("Error setting up harness.");
|
||||||
|
|
||||||
let ret_addr: GuestAddr = qemu
|
|
||||||
.read_return_address()
|
|
||||||
.map_err(|e| Error::unknown(format!("Failed to read return address: {e:?}")))?;
|
|
||||||
log::debug!("ret_addr = {ret_addr:#x}");
|
|
||||||
qemu.set_breakpoint(ret_addr);
|
|
||||||
|
|
||||||
let is_cmplog = self.options.is_cmplog_core(core_id);
|
let is_cmplog = self.options.is_cmplog_core(core_id);
|
||||||
|
|
||||||
@ -136,6 +120,7 @@ impl<'a> Client<'a> {
|
|||||||
let instance_builder = Instance::builder()
|
let instance_builder = Instance::builder()
|
||||||
.options(self.options)
|
.options(self.options)
|
||||||
.qemu(qemu)
|
.qemu(qemu)
|
||||||
|
.harness(harness)
|
||||||
.mgr(mgr)
|
.mgr(mgr)
|
||||||
.core_id(core_id)
|
.core_id(core_id)
|
||||||
.extra_tokens(extra_tokens);
|
.extra_tokens(extra_tokens);
|
||||||
|
@ -4,7 +4,9 @@ use libafl::{
|
|||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
use libafl_bolts::AsSlice;
|
use libafl_bolts::AsSlice;
|
||||||
use libafl_qemu::{ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, Qemu, Regs};
|
use libafl_qemu::{
|
||||||
|
elf::EasyElf, ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, Qemu, Regs,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Harness {
|
pub struct Harness {
|
||||||
qemu: Qemu,
|
qemu: Qemu,
|
||||||
@ -17,7 +19,40 @@ pub struct Harness {
|
|||||||
pub const MAX_INPUT_SIZE: usize = 1_048_576; // 1MB
|
pub const MAX_INPUT_SIZE: usize = 1_048_576; // 1MB
|
||||||
|
|
||||||
impl Harness {
|
impl Harness {
|
||||||
pub fn new(qemu: Qemu) -> Result<Harness, Error> {
|
/// Change environment
|
||||||
|
#[inline]
|
||||||
|
#[allow(clippy::ptr_arg)]
|
||||||
|
pub fn edit_env(_env: &mut Vec<(String, String)>) {}
|
||||||
|
|
||||||
|
/// Change arguments
|
||||||
|
#[inline]
|
||||||
|
#[allow(clippy::ptr_arg)]
|
||||||
|
pub fn edit_args(_args: &mut Vec<String>) {}
|
||||||
|
|
||||||
|
/// Helper function to find the function we want to fuzz.
|
||||||
|
fn start_pc(qemu: Qemu) -> Result<GuestAddr, Error> {
|
||||||
|
let mut elf_buffer = Vec::new();
|
||||||
|
let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer)?;
|
||||||
|
|
||||||
|
let start_pc = elf
|
||||||
|
.resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr())
|
||||||
|
.ok_or_else(|| Error::empty_optional("Symbol LLVMFuzzerTestOneInput not found"))?;
|
||||||
|
Ok(start_pc)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the emulator, run to the entrypoint (or jump there) and return the [`Harness`] struct
|
||||||
|
pub fn init(qemu: Qemu) -> Result<Harness, Error> {
|
||||||
|
let start_pc = Self::start_pc(qemu)?;
|
||||||
|
log::debug!("start_pc @ {start_pc:#x}");
|
||||||
|
|
||||||
|
qemu.entry_break(start_pc);
|
||||||
|
|
||||||
|
let ret_addr: GuestAddr = qemu
|
||||||
|
.read_return_address()
|
||||||
|
.map_err(|e| Error::unknown(format!("Failed to read return address: {e:?}")))?;
|
||||||
|
log::debug!("ret_addr = {ret_addr:#x}");
|
||||||
|
qemu.set_breakpoint(ret_addr);
|
||||||
|
|
||||||
let input_addr = qemu
|
let input_addr = qemu
|
||||||
.map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite)
|
.map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite)
|
||||||
.map_err(|e| Error::unknown(format!("Failed to map input buffer: {e:}")))?;
|
.map_err(|e| Error::unknown(format!("Failed to map input buffer: {e:}")))?;
|
||||||
@ -43,6 +78,11 @@ impl Harness {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If we need to do extra work after forking, we can do that here.
|
||||||
|
#[inline]
|
||||||
|
#[allow(clippy::unused_self)]
|
||||||
|
pub fn post_fork(&self) {}
|
||||||
|
|
||||||
pub fn run(&self, input: &BytesInput) -> ExitKind {
|
pub fn run(&self, input: &BytesInput) -> ExitKind {
|
||||||
self.reset(input).unwrap();
|
self.reset(input).unwrap();
|
||||||
ExitKind::Ok
|
ExitKind::Ok
|
||||||
|
@ -61,6 +61,9 @@ pub type ClientMgr<M> =
|
|||||||
#[derive(TypedBuilder)]
|
#[derive(TypedBuilder)]
|
||||||
pub struct Instance<'a, M: Monitor> {
|
pub struct Instance<'a, M: Monitor> {
|
||||||
options: &'a FuzzerOptions,
|
options: &'a FuzzerOptions,
|
||||||
|
/// The harness. We create it before forking, then `take()` it inside the client.
|
||||||
|
#[builder(setter(strip_option))]
|
||||||
|
harness: Option<Harness>,
|
||||||
qemu: Qemu,
|
qemu: Qemu,
|
||||||
mgr: ClientMgr<M>,
|
mgr: ClientMgr<M>,
|
||||||
core_id: CoreId,
|
core_id: CoreId,
|
||||||
@ -186,7 +189,12 @@ impl<'a, M: Monitor> Instance<'a, M> {
|
|||||||
|
|
||||||
state.add_metadata(tokens);
|
state.add_metadata(tokens);
|
||||||
|
|
||||||
let harness = Harness::new(self.qemu)?;
|
let harness = self
|
||||||
|
.harness
|
||||||
|
.take()
|
||||||
|
.expect("The harness can never be None here!");
|
||||||
|
harness.post_fork();
|
||||||
|
|
||||||
let mut harness = |_emulator: &mut Emulator<_, _, _, _, _>,
|
let mut harness = |_emulator: &mut Emulator<_, _, _, _, _>,
|
||||||
_state: &mut _,
|
_state: &mut _,
|
||||||
input: &BytesInput| harness.run(input);
|
input: &BytesInput| harness.run(input);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user