LibAFL_QEMU: Add redirect stdout module (#3256)
* Add redirect stdout * Review changes
This commit is contained in:
parent
0b25d723c0
commit
3a62013c85
@ -28,7 +28,7 @@ harness: libpng
|
||||
-lm -static
|
||||
|
||||
[unix]
|
||||
run: harness build
|
||||
run: build harness
|
||||
{{ FUZZER }} \
|
||||
--output ./output \
|
||||
--input ./corpus \
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! A binary-only corpus minimizer using qemu, similar to AFL++ afl-cmin
|
||||
#[cfg(feature = "i386")]
|
||||
use core::mem::size_of;
|
||||
use core::str::from_utf8;
|
||||
#[cfg(feature = "snapshot")]
|
||||
use core::time::Duration;
|
||||
use std::{env, fmt::Write, io, path::PathBuf, process, ptr::NonNull};
|
||||
@ -20,7 +21,6 @@ use libafl::{
|
||||
Error,
|
||||
};
|
||||
use libafl_bolts::{
|
||||
core_affinity::Cores,
|
||||
os::unix_signals::Signal,
|
||||
rands::StdRand,
|
||||
shmem::{ShMemProvider, StdShMemProvider},
|
||||
@ -30,8 +30,10 @@ use libafl_bolts::{
|
||||
#[cfg(feature = "fork")]
|
||||
use libafl_qemu::QemuForkExecutor;
|
||||
use libafl_qemu::{
|
||||
elf::EasyElf, modules::edges::StdEdgeCoverageChildModule, ArchExtras, Emulator, GuestAddr,
|
||||
GuestReg, MmapPerms, QemuExitError, QemuExitReason, QemuShutdownCause, Regs,
|
||||
elf::EasyElf,
|
||||
modules::{edges::StdEdgeCoverageChildModule, RedirectStdoutModule},
|
||||
ArchExtras, Emulator, GuestAddr, GuestReg, MmapPerms, QemuExitError, QemuExitReason,
|
||||
QemuShutdownCause, Regs,
|
||||
};
|
||||
#[cfg(feature = "snapshot")]
|
||||
use libafl_qemu::{modules::SnapshotModule, QemuExecutor};
|
||||
@ -86,12 +88,6 @@ pub struct FuzzerOptions {
|
||||
#[arg(long, help = "Timeout in seconds", default_value_t = 1_u64)]
|
||||
timeout: u64,
|
||||
|
||||
#[arg(long = "port", help = "Broker port", default_value_t = 1337_u16)]
|
||||
port: u16,
|
||||
|
||||
#[arg(long, help = "Cpu cores to use", default_value = "all", value_parser = Cores::from_cmdline)]
|
||||
cores: Cores,
|
||||
|
||||
#[clap(short, long, help = "Enable output from the fuzzer clients")]
|
||||
verbose: bool,
|
||||
|
||||
@ -137,17 +133,38 @@ pub fn fuzz() -> Result<(), Error> {
|
||||
))
|
||||
};
|
||||
|
||||
let stdout_callback = |buf: &[u8]| {
|
||||
if let Ok(s) = from_utf8(buf) {
|
||||
let msg = s.trim_end();
|
||||
if msg.len() != 0 {
|
||||
log::info!("{msg}");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let redirect_stdout_module = if options.verbose {
|
||||
RedirectStdoutModule::new()
|
||||
.with_stderr(stdout_callback)
|
||||
.with_stdout(stdout_callback)
|
||||
} else {
|
||||
RedirectStdoutModule::new()
|
||||
};
|
||||
|
||||
#[cfg(feature = "fork")]
|
||||
let modules = tuple_list!(StdEdgeCoverageChildModule::builder()
|
||||
.const_map_observer(edges_observer.as_mut())
|
||||
.build()?);
|
||||
let modules = tuple_list!(
|
||||
StdEdgeCoverageChildModule::builder()
|
||||
.const_map_observer(edges_observer.as_mut())
|
||||
.build()?,
|
||||
redirect_stdout_module
|
||||
);
|
||||
|
||||
#[cfg(feature = "snapshot")]
|
||||
let modules = tuple_list!(
|
||||
StdEdgeCoverageChildModule::builder()
|
||||
.const_map_observer(edges_observer.as_mut())
|
||||
.build()?,
|
||||
SnapshotModule::new()
|
||||
SnapshotModule::new(),
|
||||
redirect_stdout_module,
|
||||
);
|
||||
|
||||
let emulator = Emulator::empty()
|
||||
@ -180,9 +197,7 @@ pub fn fuzz() -> Result<(), Error> {
|
||||
|
||||
let stack_ptr: GuestAddr = qemu.read_reg(Regs::Sp).unwrap();
|
||||
|
||||
let monitor = SimpleMonitor::new(|s| {
|
||||
println!("{s}");
|
||||
});
|
||||
let monitor = SimpleMonitor::new(|s| log::info!("{s}"));
|
||||
let (state, mut mgr) = match SimpleRestartingEventManager::launch(monitor, &mut shmem_provider)
|
||||
{
|
||||
Ok(res) => res,
|
||||
@ -304,20 +319,20 @@ pub fn fuzz() -> Result<(), Error> {
|
||||
Duration::from_millis(5000),
|
||||
)?;
|
||||
|
||||
println!("Importing {} seeds...", files.len());
|
||||
log::info!("Importing {} seeds...", files.len());
|
||||
|
||||
if state.must_load_initial_inputs() {
|
||||
state
|
||||
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &files)
|
||||
.unwrap_or_else(|_| {
|
||||
println!("Failed to load initial corpus");
|
||||
log::error!("Failed to load initial corpus");
|
||||
process::exit(0);
|
||||
});
|
||||
println!("Imported {} seeds from disk.", state.corpus().count());
|
||||
log::info!("Imported {} seeds from disk.", state.corpus().count());
|
||||
}
|
||||
|
||||
let size = state.corpus().count();
|
||||
println!(
|
||||
log::info!(
|
||||
"Removed {} duplicates from {} seeds",
|
||||
files.len() - size,
|
||||
files.len()
|
||||
|
@ -156,7 +156,7 @@ pub fn fuzz() {
|
||||
qemu.entry_break(test_one_input_ptr);
|
||||
|
||||
let mappings = QemuMappingsViewer::new(&qemu);
|
||||
println!("{:#?}", mappings);
|
||||
log::info!("{:#?}", mappings);
|
||||
|
||||
let pc: GuestReg = qemu.read_reg(Regs::Pc).unwrap();
|
||||
log::info!("Break at {pc:#x}");
|
||||
|
@ -30,20 +30,18 @@ harness: libpng
|
||||
-lm -static
|
||||
|
||||
[unix]
|
||||
run_single: harness build
|
||||
run_single: build harness
|
||||
{{ FUZZER_SINGLE }} \
|
||||
--output ./output \
|
||||
--input ./corpus \
|
||||
--verbose \
|
||||
-- {{ HARNESS }}
|
||||
|
||||
[unix]
|
||||
run_multi: harness build
|
||||
run_multi: build harness
|
||||
{{ FUZZER_MULTI }} \
|
||||
--output ./output \
|
||||
--input ./corpus \
|
||||
--cores 0 \
|
||||
--verbose \
|
||||
-- {{ HARNESS }}
|
||||
|
||||
[unix]
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! A binary-only testcase minimizer using qemu, similar to AFL++ afl-tmin
|
||||
#[cfg(feature = "i386")]
|
||||
use core::mem::size_of;
|
||||
use core::str::from_utf8;
|
||||
#[cfg(feature = "snapshot")]
|
||||
use core::time::Duration;
|
||||
use std::{env, fmt::Write, io, path::PathBuf, process, ptr::NonNull};
|
||||
@ -32,8 +33,10 @@ use libafl_bolts::{
|
||||
AsSlice, AsSliceMut,
|
||||
};
|
||||
use libafl_qemu::{
|
||||
elf::EasyElf, modules::edges::StdEdgeCoverageChildModule, ArchExtras, Emulator, GuestAddr,
|
||||
GuestReg, MmapPerms, QemuExitError, QemuExitReason, QemuShutdownCause, Regs,
|
||||
elf::EasyElf,
|
||||
modules::{edges::StdEdgeCoverageChildModule, RedirectStdoutModule},
|
||||
ArchExtras, Emulator, GuestAddr, GuestReg, MmapPerms, QemuExitError, QemuExitReason,
|
||||
QemuShutdownCause, Regs,
|
||||
};
|
||||
#[cfg(feature = "snapshot")]
|
||||
use libafl_qemu::{modules::SnapshotModule, QemuExecutor};
|
||||
@ -188,13 +191,31 @@ pub fn fuzz() {
|
||||
))
|
||||
};
|
||||
|
||||
let stdout_callback = |buf: &[u8]| {
|
||||
if let Ok(s) = from_utf8(buf) {
|
||||
let msg = s.trim_end();
|
||||
if msg.len() != 0 {
|
||||
log::info!("{msg}");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let redirect_stdout_module = if options.verbose {
|
||||
RedirectStdoutModule::new()
|
||||
.with_stderr(stdout_callback)
|
||||
.with_stdout(stdout_callback)
|
||||
} else {
|
||||
RedirectStdoutModule::new()
|
||||
};
|
||||
|
||||
// In either fork/snapshot mode, we link the observer to QEMU
|
||||
#[cfg(feature = "snapshot")]
|
||||
let modules = tuple_list!(
|
||||
StdEdgeCoverageChildModule::builder()
|
||||
.const_map_observer(edges_observer.as_mut())
|
||||
.build()?,
|
||||
SnapshotModule::new()
|
||||
SnapshotModule::new(),
|
||||
redirect_stdout_module
|
||||
);
|
||||
|
||||
// Create our QEMU emulator
|
||||
@ -342,7 +363,7 @@ pub fn fuzz() {
|
||||
.shmem_provider(shmem_provider.clone())
|
||||
.broker_port(options.port)
|
||||
.configuration(EventConfig::from_build_id())
|
||||
.monitor(MultiMonitor::new(|s| println!("{s}")))
|
||||
.monitor(MultiMonitor::new(|s| log::info!("{s}")))
|
||||
.run_client(&mut run_client)
|
||||
.cores(&options.cores)
|
||||
.build()
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! A binary-only testcase minimizer using qemu, similar to AFL++ afl-tmin
|
||||
#[cfg(feature = "i386")]
|
||||
use core::mem::size_of;
|
||||
use core::str::from_utf8;
|
||||
#[cfg(feature = "snapshot")]
|
||||
use core::time::Duration;
|
||||
use std::{env, fmt::Write, io, path::PathBuf, process, ptr::NonNull};
|
||||
@ -29,8 +30,10 @@ use libafl_bolts::{
|
||||
AsSlice, AsSliceMut,
|
||||
};
|
||||
use libafl_qemu::{
|
||||
elf::EasyElf, modules::edges::StdEdgeCoverageChildModule, ArchExtras, Emulator, GuestAddr,
|
||||
GuestReg, MmapPerms, QemuExitError, QemuExitReason, QemuShutdownCause, Regs,
|
||||
elf::EasyElf,
|
||||
modules::{edges::StdEdgeCoverageChildModule, RedirectStdoutModule},
|
||||
ArchExtras, Emulator, GuestAddr, GuestReg, MmapPerms, QemuExitError, QemuExitReason,
|
||||
QemuShutdownCause, Regs,
|
||||
};
|
||||
#[cfg(feature = "snapshot")]
|
||||
use libafl_qemu::{modules::SnapshotModule, QemuExecutor};
|
||||
@ -146,12 +149,30 @@ pub fn fuzz() -> Result<(), Error> {
|
||||
))
|
||||
};
|
||||
|
||||
let stdout_callback = |buf: &[u8]| {
|
||||
if let Ok(s) = from_utf8(buf) {
|
||||
let msg = s.trim_end();
|
||||
if msg.len() != 0 {
|
||||
log::info!("{msg}");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let redirect_stdout_module = if options.verbose {
|
||||
RedirectStdoutModule::new()
|
||||
.with_stderr(stdout_callback)
|
||||
.with_stdout(stdout_callback)
|
||||
} else {
|
||||
RedirectStdoutModule::new()
|
||||
};
|
||||
|
||||
#[cfg(feature = "snapshot")]
|
||||
let modules = tuple_list!(
|
||||
StdEdgeCoverageChildModule::builder()
|
||||
.const_map_observer(edges_observer.as_mut())
|
||||
.build()?,
|
||||
SnapshotModule::new()
|
||||
SnapshotModule::new(),
|
||||
redirect_stdout_module
|
||||
);
|
||||
|
||||
// Create our QEMU emulator
|
||||
@ -223,9 +244,7 @@ pub fn fuzz() -> Result<(), Error> {
|
||||
};
|
||||
|
||||
// Set up the most basic monitor possible.
|
||||
let monitor = SimpleMonitor::new(|s| {
|
||||
println!("{s}");
|
||||
});
|
||||
let monitor = SimpleMonitor::new(|s| log::info!("{s}"));
|
||||
let (state, mut mgr) = match SimpleRestartingEventManager::launch(monitor, &mut shmem_provider)
|
||||
{
|
||||
Ok(res) => res,
|
||||
|
@ -19,3 +19,6 @@ pub mod asan_guest;
|
||||
pub use asan_guest::AsanGuestModule;
|
||||
pub mod redirect_stdin;
|
||||
pub use redirect_stdin::*;
|
||||
|
||||
pub mod redirect_stdout;
|
||||
pub use redirect_stdout::*;
|
||||
|
155
libafl_qemu/src/modules/usermode/redirect_stdout.rs
Normal file
155
libafl_qemu/src/modules/usermode/redirect_stdout.rs
Normal file
@ -0,0 +1,155 @@
|
||||
use core::{
|
||||
fmt::{self, Debug},
|
||||
slice::from_raw_parts,
|
||||
};
|
||||
|
||||
use libafl_bolts::HasLen;
|
||||
use libafl_qemu_sys::GuestAddr;
|
||||
|
||||
#[cfg(not(cpu_target = "hexagon"))]
|
||||
use crate::SYS_write;
|
||||
use crate::{
|
||||
Qemu,
|
||||
emu::EmulatorModules,
|
||||
modules::{EmulatorModule, EmulatorModuleTuple},
|
||||
qemu::{Hook, SyscallHookResult},
|
||||
};
|
||||
|
||||
#[cfg(cpu_target = "hexagon")]
|
||||
/// Hexagon syscalls are not currently supported by the `syscalls` crate, so we just paste this here for now.
|
||||
/// <https://github.com/qemu/qemu/blob/11be70677c70fdccd452a3233653949b79e97908/linux-user/hexagon/syscall_nr.h#L230>
|
||||
#[expect(non_upper_case_globals)]
|
||||
const SYS_write: u8 = 64;
|
||||
|
||||
/// This module hijacks any read to buffer from stdin, and instead fill the buffer from the specified input address
|
||||
/// This is useful when your binary target reads the input from the stdin.
|
||||
/// With this you can just fuzz more like afl++
|
||||
/// You need to use this with snapshot module!
|
||||
#[derive(Clone)]
|
||||
pub struct RedirectStdoutModule<F>
|
||||
where
|
||||
F: FnMut(&[u8]),
|
||||
{
|
||||
stdout: Option<F>,
|
||||
stderr: Option<F>,
|
||||
}
|
||||
|
||||
impl<F> Debug for RedirectStdoutModule<F>
|
||||
where
|
||||
F: FnMut(&[u8]),
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("RedirectStdoutModule")
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Default for RedirectStdoutModule<F>
|
||||
where
|
||||
F: FnMut(&[u8]),
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> RedirectStdoutModule<F>
|
||||
where
|
||||
F: FnMut(&[u8]),
|
||||
{
|
||||
#[must_use]
|
||||
/// constuctor
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
stdout: None,
|
||||
stderr: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> RedirectStdoutModule<F>
|
||||
where
|
||||
F: FnMut(&[u8]) + Clone,
|
||||
{
|
||||
#[must_use]
|
||||
/// Create with specified stdout callback
|
||||
pub fn with_stdout(&self, stdout: F) -> Self {
|
||||
Self {
|
||||
stdout: Some(stdout),
|
||||
stderr: self.stderr.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Create with specified stderr callback
|
||||
pub fn with_stderr(&self, stderr: F) -> Self {
|
||||
Self {
|
||||
stdout: self.stdout.clone(),
|
||||
stderr: Some(stderr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, I, S> EmulatorModule<I, S> for RedirectStdoutModule<F>
|
||||
where
|
||||
I: Unpin + HasLen + Debug,
|
||||
S: Unpin,
|
||||
F: FnMut(&[u8]) + 'static,
|
||||
{
|
||||
fn first_exec<ET>(
|
||||
&mut self,
|
||||
_qemu: Qemu,
|
||||
emulator_modules: &mut EmulatorModules<ET, I, S>,
|
||||
_state: &mut S,
|
||||
) where
|
||||
ET: EmulatorModuleTuple<I, S>,
|
||||
{
|
||||
emulator_modules.pre_syscalls(Hook::Function(syscall_write_hook::<F, ET, I, S>));
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn syscall_write_hook<F, ET, I, S>(
|
||||
_qemu: Qemu,
|
||||
emulator_modules: &mut EmulatorModules<ET, I, S>,
|
||||
_state: Option<&mut S>,
|
||||
syscall: i32,
|
||||
x0: GuestAddr,
|
||||
x1: GuestAddr,
|
||||
x2: GuestAddr,
|
||||
_x3: GuestAddr,
|
||||
_x4: GuestAddr,
|
||||
_x5: GuestAddr,
|
||||
_x6: GuestAddr,
|
||||
_x7: GuestAddr,
|
||||
) -> SyscallHookResult
|
||||
where
|
||||
ET: EmulatorModuleTuple<I, S>,
|
||||
I: Unpin + HasLen + Debug,
|
||||
S: Unpin,
|
||||
F: FnMut(&[u8]) + 'static,
|
||||
{
|
||||
let h = emulator_modules
|
||||
.get_mut::<RedirectStdoutModule<F>>()
|
||||
.unwrap();
|
||||
if syscall != SYS_write as i32 {
|
||||
return SyscallHookResult::Run;
|
||||
}
|
||||
|
||||
let fd = x0 as i32;
|
||||
let buf = x1 as *const u8;
|
||||
let len = x2;
|
||||
|
||||
let callback = match fd {
|
||||
libc::STDOUT_FILENO => h.stdout.as_mut(),
|
||||
libc::STDERR_FILENO => h.stderr.as_mut(),
|
||||
_ => return SyscallHookResult::Run,
|
||||
};
|
||||
|
||||
if let Some(callback) = callback {
|
||||
let buf = unsafe { from_raw_parts(buf, len as usize) };
|
||||
(callback)(buf);
|
||||
}
|
||||
|
||||
SyscallHookResult::Skip(len)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user