-
-
Notifications
You must be signed in to change notification settings - Fork 366
/
Copy pathfuzzer.rs
291 lines (241 loc) · 9.56 KB
/
fuzzer.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
//! A qemu test case runner to generate drcov coverage outputs
#[cfg(feature = "i386")]
use core::mem::size_of;
use core::time::Duration;
use std::{env, fmt::Write, fs::DirEntry, io, path::PathBuf, process};
use clap::{builder::Str, Parser};
use libafl::{
corpus::{Corpus, InMemoryCorpus},
events::{
launcher::Launcher, ClientDescription, EventConfig, LlmpRestartingEventManager, SendExiting,
},
executors::ExitKind,
fuzzer::StdFuzzer,
inputs::{BytesInput, HasTargetBytes},
monitors::MultiMonitor,
schedulers::QueueScheduler,
state::{HasCorpus, StdState},
Error,
};
use libafl_bolts::{
core_affinity::Cores,
os::unix_signals::Signal,
rands::StdRand,
shmem::{ShMemProvider, StdShMemProvider},
tuples::tuple_list,
AsSlice,
};
use libafl_qemu::{
elf::EasyElf,
modules::{drcov::DrCovModule, SnapshotModule},
ArchExtras, Emulator, GuestAddr, GuestReg, MmapPerms, Qemu, QemuExecutor, QemuExitReason,
QemuMappingsViewer, QemuRWError, QemuShutdownCause, Regs,
};
#[derive(Default)]
pub struct Version;
/// Parse a millis string to a [`Duration`]. Used for arg parsing.
fn timeout_from_millis_str(time: &str) -> Result<Duration, Error> {
Ok(Duration::from_millis(time.parse()?))
}
impl From<Version> for Str {
fn from(_: Version) -> Str {
let version = [
("Architecture:", env!("CPU_TARGET")),
("Build Timestamp:", env!("VERGEN_BUILD_TIMESTAMP")),
("Describe:", env!("VERGEN_GIT_DESCRIBE")),
("Commit SHA:", env!("VERGEN_GIT_SHA")),
("Commit Date:", env!("VERGEN_RUSTC_COMMIT_DATE")),
("Commit Branch:", env!("VERGEN_GIT_BRANCH")),
("Rustc Version:", env!("VERGEN_RUSTC_SEMVER")),
("Rustc Channel:", env!("VERGEN_RUSTC_CHANNEL")),
("Rustc Host Triple:", env!("VERGEN_RUSTC_HOST_TRIPLE")),
("Rustc Commit SHA:", env!("VERGEN_RUSTC_COMMIT_HASH")),
("Cargo Target Triple", env!("VERGEN_CARGO_TARGET_TRIPLE")),
]
.iter()
.fold(String::new(), |mut output, (k, v)| {
let _ = writeln!(output, "{k:25}: {v}");
output
});
format!("\n{version:}").into()
}
}
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
#[command(
name = format!("qemu_coverage-{}",env!("CPU_TARGET")),
version = Version::default(),
about,
long_about = "Module for generating DrCov coverage data using QEMU instrumentation"
)]
pub struct FuzzerOptions {
#[arg(long, help = "Coverage file")]
coverage_path: PathBuf,
#[arg(long, help = "Input directory")]
input_dir: PathBuf,
#[arg(long, help = "Timeout in seconds", default_value = "5000", value_parser = timeout_from_millis_str)]
timeout: Duration,
#[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,
#[arg(last = true, help = "Arguments passed to the target")]
args: Vec<String>,
}
pub const MAX_INPUT_SIZE: usize = 1048576; // 1MB
pub fn fuzz() {
env_logger::init();
let mut options = FuzzerOptions::parse();
let corpus_files = options
.input_dir
.read_dir()
.expect("Failed to read corpus dir")
.collect::<Result<Vec<DirEntry>, io::Error>>()
.expect("Failed to read dir entry");
let num_files = corpus_files.len();
let num_cores = options.cores.ids.len();
let files_per_core = (num_files as f64 / num_cores as f64).ceil() as usize;
let program = env::args().next().unwrap();
log::info!("Program: {program:}");
options.args.insert(0, program);
log::info!("ARGS: {:#?}", options.args);
env::remove_var("LD_LIBRARY_PATH");
let mut run_client = |state: Option<_>,
mut mgr: LlmpRestartingEventManager<_, _, _, _, _>,
client_description: ClientDescription| {
let mut cov_path = options.coverage_path.clone();
let core_id = client_description.core_id();
let coverage_name = cov_path.file_stem().unwrap().to_str().unwrap();
let coverage_extension = cov_path.extension().unwrap_or_default().to_str().unwrap();
let core = core_id.0;
cov_path.set_file_name(format!("{coverage_name}-{core:03}.{coverage_extension}"));
let emulator_modules = tuple_list!(
DrCovModule::builder().filename(cov_path.clone()).build(),
SnapshotModule::new(),
);
let emulator = Emulator::empty()
.qemu_parameters(options.args.clone())
.modules(emulator_modules)
.build()
.expect("QEMU initialization failed");
let qemu = emulator.qemu();
let mut elf_buffer = Vec::new();
let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer).unwrap();
let test_one_input_ptr = elf
.resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr())
.expect("Symbol LLVMFuzzerTestOneInput not found");
log::info!("LLVMFuzzerTestOneInput @ {test_one_input_ptr:#x}");
qemu.entry_break(test_one_input_ptr);
let mappings = QemuMappingsViewer::new(&qemu);
println!("{:#?}", mappings);
let pc: GuestReg = qemu.read_reg(Regs::Pc).unwrap();
log::info!("Break at {pc:#x}");
let ret_addr: GuestAddr = qemu.read_return_address().unwrap();
log::info!("Return address = {ret_addr:#x}");
qemu.set_breakpoint(ret_addr);
let input_addr = qemu
.map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite)
.unwrap();
log::info!("Placing input at {input_addr:#x}");
let stack_ptr: GuestAddr = qemu.read_reg(Regs::Sp).unwrap();
let reset = |qemu: Qemu, buf: &[u8], len: GuestReg| -> Result<(), QemuRWError> {
unsafe {
qemu.write_mem(input_addr, buf)?;
qemu.write_reg(Regs::Pc, test_one_input_ptr)?;
qemu.write_reg(Regs::Sp, stack_ptr)?;
qemu.write_return_address(ret_addr)?;
qemu.write_function_argument(0, input_addr)?;
qemu.write_function_argument(1, len)?;
match qemu.run() {
Ok(QemuExitReason::Breakpoint(_)) => {}
Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(
Signal::SigInterrupt,
))) => process::exit(0),
_ => panic!("Unexpected QEMU exit."),
}
Ok(())
}
};
let mut harness =
|emulator: &mut Emulator<_, _, _, _, _, _, _>, _state: &mut _, input: &BytesInput| {
let qemu = emulator.qemu();
let target = input.target_bytes();
let mut buf = target.as_slice();
let mut len = buf.len();
if len > MAX_INPUT_SIZE {
buf = &buf[0..MAX_INPUT_SIZE];
len = MAX_INPUT_SIZE;
}
let len = len as GuestReg;
reset(qemu, buf, len).unwrap();
ExitKind::Ok
};
let core_id = client_description.core_id();
let core_idx = options
.cores
.position(core_id)
.expect("Failed to get core index");
let files = corpus_files
.iter()
.skip(files_per_core * core_idx)
.take(files_per_core)
.map(|x| x.path())
.collect::<Vec<PathBuf>>();
if files.is_empty() {
mgr.send_exiting()?;
Err(Error::ShuttingDown)?
}
let mut feedback = ();
let mut objective = ();
let mut state = state.unwrap_or_else(|| {
StdState::new(
StdRand::new(),
InMemoryCorpus::new(),
InMemoryCorpus::new(),
&mut feedback,
&mut objective,
)
.unwrap()
});
let scheduler = QueueScheduler::new();
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
let mut executor = QemuExecutor::new(
emulator,
&mut harness,
(),
&mut fuzzer,
&mut state,
&mut mgr,
options.timeout,
)
.expect("Failed to create QemuExecutor");
if state.must_load_initial_inputs() {
state
.load_initial_inputs_by_filenames(&mut fuzzer, &mut executor, &mut mgr, &files)
.unwrap_or_else(|_| {
println!("Failed to load initial corpus at {:?}", &options.input_dir);
process::exit(0);
});
log::info!("We imported {} inputs from disk.", state.corpus().count());
}
log::info!("Processed {} inputs from disk.", files.len());
mgr.send_exiting()?;
Err(Error::ShuttingDown)?
};
match Launcher::builder()
.shmem_provider(StdShMemProvider::new().expect("Failed to init shared memory"))
.broker_port(options.port)
.configuration(EventConfig::from_build_id())
.monitor(MultiMonitor::new(|s| println!("{s}")))
.run_client(&mut run_client)
.cores(&options.cores)
.build()
.launch()
{
Ok(()) => (),
Err(Error::ShuttingDown) => println!("Run finished successfully."),
Err(err) => panic!("Failed to run launcher: {err:?}"),
}
}