Initial Release of Nyx

Co-authored-by: Cornelius Aschermann <cornelius@hexgolems.com>
This commit is contained in:
Sergej Schumilo 2021-11-14 21:59:03 +01:00
commit 34679b90dc
31 changed files with 2560 additions and 0 deletions

21
README.md Normal file
View File

@ -0,0 +1,21 @@
# libnyx
<p>
<img align="right" width="200" src="logo.png">
</p>
libnyx is a library that allows you to simply build hypervisor based snapshot fuzzers. Using libnyx, managing multiple vms and snapshots as well as the communication with the code running in the VM becomes a matter of a handful of lines of code. At the moment, libnyx can be used via a simple C interface or from a rust library.
## Bug Reports and Contributions
If you found and fixed a bug on your own: We are very open to patches, please create a pull request!
### License
This library is provided under **AGPL license**.
**Free Software Hell Yeah!**
Proudly provided by:
* [Sergej Schumilo](http://schumilo.de) - sergej@schumilo.de / [@ms_s3c](https://twitter.com/ms_s3c)
* [Cornelius Aschermann](https://hexgolems.com) - cornelius@hexgolems.com / [@is_eqv](https://twitter.com/is_eqv)

4
acat/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
debug/
target/
Cargo.lock

12
acat/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "acat"
version = "0.1.0"
authors = ["Sergej Schumilo <sergej@schumilo.de>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
fuzz_runner={path="../fuzz_runner"}
colored = "2.0.0"
clap="2.33.0"

149
acat/src/main.rs Normal file
View File

@ -0,0 +1,149 @@
use fuzz_runner::nyx::aux_buffer;
use clap::{App, Arg, AppSettings};
use std::fs::{OpenOptions};
use std::str;
extern crate colored;
use colored::*;
fn print_aux_buffer(aux_buffer: &aux_buffer::AuxBuffer, target_file: &String, show_header: bool, show_cap: bool, show_config: bool, show_result: bool, show_misc: bool, colored_output: bool){
println!("\n{} {} {}", "**************", target_file.green().bold(), "**************");
colored::control::set_override(colored_output);
if show_header{
println!("\n{} {}", "=>", "HEADER".blue().bold());
print!("{}", format!("{:#?}", aux_buffer.header).yellow());
}
if show_cap{
println!("\n{} {}", "=>", "CAP".blue().bold());
print!("{}", format!("{:#?}", aux_buffer.cap).yellow());
}
if show_config{
println!("\n{} {}", "=>", "CONFIG".blue().bold());
print!("{}", format!("{:#?}", aux_buffer.config).yellow());
}
if show_result{
println!("\n{} {}", "=>", "RESULT".blue().bold());
print!("{}", format!("{:#?}", aux_buffer.result).yellow());
}
if show_misc{
if aux_buffer.misc.len != 0 {
println!("\n{} {}", "=>", "MISC".blue().bold());
let len = aux_buffer.misc.len;
println!("{}", str::from_utf8(&aux_buffer.misc.data[0..len as usize]).unwrap().red());
}
}
}
fn main() {
let matches = App::new("acat")
.about("Fancy tool to debug aux buffers!")
.arg(
Arg::with_name("target_file")
.short("f")
.long("target_file")
.value_name("TARGET")
.takes_value(true)
.help("specifies target file (aux buffer)"),
)
.arg(
Arg::with_name("show_header")
.long("show_header")
.value_name("SHOW_HEADER")
.required(false)
.takes_value(false)
.help("show header section"),
)
.arg(
Arg::with_name("show_cap")
.long("show_cap")
.value_name("SHOW_CAP")
.required(false)
.takes_value(false)
.help("show capabilities section"),
)
.arg(
Arg::with_name("show_config")
.long("show_config")
.value_name("SHOW_CONFIG")
.required(false)
.takes_value(false)
.help("show config section"),
)
.arg(
Arg::with_name("ignore_result")
.long("ignore_result")
.value_name("IGNORE_RESULT")
.required(false)
.takes_value(false)
.help("dont't show result section"),
)
.arg(
Arg::with_name("show_misc")
.long("show_misc")
.value_name("SHOW_MISC")
.required(false)
.takes_value(false)
.help("show misc section"),
)
.arg(
Arg::with_name("show_all")
.short("a")
.long("show_all")
.value_name("SHOW_ALL")
.required(false)
.takes_value(false)
.help("show all sections"),
)
.arg(
Arg::with_name("disable_color")
.short("c")
.long("disable_color")
.value_name("SHOW_ALL")
.required(false)
.takes_value(false)
.help("show all sections"),
)
.setting(AppSettings::ArgRequiredElseHelp)
.get_matches();
let show_header: bool = matches.is_present("show_header");
let show_cap: bool = matches.is_present("show_cap");
let show_config: bool = matches.is_present("show_config");
let show_result: bool = !matches.is_present("ignore_result");
let show_misc: bool = matches.is_present("show_misc");
let colered_output: bool = !matches.is_present("disable_color");
let aux_buffer_file = matches.value_of("target_file").expect("file not found");
let aux_shm_f = OpenOptions::new()
.write(false)
.read(true)
.open(aux_buffer_file)
.expect("couldn't open aux buffer file");
let aux_buffer = aux_buffer::AuxBuffer::new_readonly(aux_shm_f, true);
aux_buffer.validate_header();
if matches.is_present("show_all"){
print_aux_buffer(&aux_buffer, &aux_buffer_file.to_string(), true, true, true, true, true, colered_output);
}
else{
print_aux_buffer(&aux_buffer, &aux_buffer_file.to_string(), show_header, show_cap, show_config, show_result, show_misc, colered_output);
}
}

4
config/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
debug/
target/
Cargo.lock

12
config/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "config"
version = "0.1.0"
authors = ["coco <cornelius@hexgolems.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde ="1.0.104"
serde_derive ="1.0.104"
ron="0.6.2"

192
config/src/config.rs Normal file
View File

@ -0,0 +1,192 @@
use std::time::Duration;
use serde_derive::Serialize;
use serde_derive::Deserialize;
use std::fs::File;
use std::path::{Path};
use crate::loader::*;
fn into_absolute_path(path_to_sharedir: &str, path_to_file: String) -> String {
let path_to_default_config = Path::new(&path_to_file);
if path_to_default_config.is_relative(){
let path = &format!("{}/{}", path_to_sharedir, path_to_file);
let absolute_path = Path::new(&path);
return absolute_path.canonicalize().unwrap().to_str().unwrap().to_string();
}
else{
return path_to_default_config.to_str().unwrap().to_string();
}
}
#[derive(Clone)]
pub struct QemuKernelConfig {
pub qemu_binary: String,
pub kernel: String,
pub ramfs: String,
pub debug: bool,
}
impl QemuKernelConfig{
pub fn new_from_loader(default_config_folder: &str, default: QemuKernelConfigLoader, config: QemuKernelConfigLoader) -> Self {
let mut qemu_binary = config.qemu_binary.or(default.qemu_binary).expect("no qemu_binary specified");
let mut kernel = config.kernel.or(default.kernel).expect("no kernel specified");
let mut ramfs = config.ramfs.or(default.ramfs).expect("no ramfs specified");
qemu_binary = into_absolute_path(default_config_folder, qemu_binary);
kernel = into_absolute_path(default_config_folder, kernel);
ramfs = into_absolute_path(default_config_folder, ramfs);
Self{
qemu_binary: qemu_binary,
kernel: kernel,
ramfs: ramfs,
debug: config.debug.or(default.debug).expect("no debug specified"),
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub enum SnapshotPath {
Reuse(String),
Create(String),
DefaultPath,
}
#[derive(Clone)]
pub struct QemuSnapshotConfig {
pub qemu_binary: String,
pub hda: String,
pub presnapshot: String,
pub snapshot_path: SnapshotPath,
pub debug: bool,
}
impl QemuSnapshotConfig{
pub fn new_from_loader(default_config_folder: &str, default: QemuSnapshotConfigLoader, config: QemuSnapshotConfigLoader) -> Self {
let mut qemu_binary = config.qemu_binary.or(default.qemu_binary).expect("no qemu_binary specified");
let mut hda = config.hda.or(default.hda).expect("no hda specified");
let mut presnapshot = config.presnapshot.or(default.presnapshot).expect("no presnapshot specified");
qemu_binary = into_absolute_path(default_config_folder, qemu_binary);
hda = into_absolute_path(default_config_folder, hda);
presnapshot = into_absolute_path(default_config_folder, presnapshot);
Self{
qemu_binary: qemu_binary,
hda: hda,
presnapshot: presnapshot,
snapshot_path: config.snapshot_path.or(default.snapshot_path).expect("no snapshot_path specified"),
debug: config.debug.or(default.debug).expect("no debug specified"),
}
}
}
#[derive(Clone)]
pub enum FuzzRunnerConfig {
QemuKernel(QemuKernelConfig),
QemuSnapshot(QemuSnapshotConfig),
}
impl FuzzRunnerConfig{
pub fn new_from_loader(default_config_folder: &str, default: FuzzRunnerConfigLoader, config: FuzzRunnerConfigLoader) -> Self {
match (default, config){
(FuzzRunnerConfigLoader::QemuKernel(d),
FuzzRunnerConfigLoader::QemuKernel(c)) => { Self::QemuKernel(QemuKernelConfig::new_from_loader(default_config_folder, d, c))},
(FuzzRunnerConfigLoader::QemuSnapshot(d),
FuzzRunnerConfigLoader::QemuSnapshot(c)) => { Self::QemuSnapshot(QemuSnapshotConfig::new_from_loader(default_config_folder, d, c))},
_ => panic!("conflicting FuzzRunner configs"),
}
}
}
#[derive(Copy, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SnapshotPlacement {
None,
Balanced,
Aggressive,
}
impl std::str::FromStr for SnapshotPlacement {
type Err = ron::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
ron::de::from_str(s)
}
}
#[derive(Clone)]
pub struct FuzzerConfig {
pub spec_path: String,
pub workdir_path: String,
pub bitmap_size: usize,
pub mem_limit: usize,
pub time_limit: Duration,
pub target_binary: Option<String>,
pub threads: usize,
pub thread_id: usize,
pub cpu_pin_start_at: usize,
pub seed_path: Option<String>,
pub dict: Vec<Vec<u8>>,
pub snapshot_placement: SnapshotPlacement,
pub dump_python_code_for_inputs: Option<bool>,
pub exit_after_first_crash: bool
}
impl FuzzerConfig{
pub fn new_from_loader(sharedir: &str, default: FuzzerConfigLoader, config: FuzzerConfigLoader) -> Self {
let seed_path = config.seed_path.or(default.seed_path).unwrap();
let seed_path_value = if seed_path.is_empty() {
None
}
else{
Some(into_absolute_path(&sharedir, seed_path))
};
Self{
spec_path: format!("{}/spec.msgp",sharedir),
workdir_path: config.workdir_path.or(default.workdir_path).expect("no workdir_path specified"),
bitmap_size: config.bitmap_size.or(default.bitmap_size).expect("no bitmap_size specified"),
mem_limit: config.mem_limit.or(default.mem_limit).expect("no mem_limit specified"),
time_limit: config.time_limit.or(default.time_limit).expect("no time_limit specified"),
target_binary: config.target_binary.or(default.target_binary),
threads: config.threads.or(default.threads).expect("no threads specified"),
thread_id: config.thread_id.or(default.thread_id).expect("no thread_id specified"),
cpu_pin_start_at: config.cpu_pin_start_at.or(default.cpu_pin_start_at).expect("no cpu_pin_start_at specified"),
seed_path: seed_path_value,
dict: config.dict.or(default.dict).expect("no dict specified"),
snapshot_placement: config.snapshot_placement.or(default.snapshot_placement).expect("no snapshot_placement specified"),
dump_python_code_for_inputs: config.dump_python_code_for_inputs.or(default.dump_python_code_for_inputs),
exit_after_first_crash: config.exit_after_first_crash.unwrap_or(default.exit_after_first_crash.unwrap_or(false)),
}
}
}
#[derive(Clone)]
pub struct Config {
pub runner: FuzzRunnerConfig,
pub fuzz: FuzzerConfig,
}
impl Config{
pub fn new_from_loader(sharedir: &str, default_config_folder: &str, default: ConfigLoader, config: ConfigLoader) -> Self{
Self{
runner: FuzzRunnerConfig::new_from_loader(&default_config_folder, default.runner, config.runner),
fuzz: FuzzerConfig::new_from_loader(&sharedir, default.fuzz, config.fuzz),
}
}
pub fn new_from_sharedir(sharedir: &str) -> Self {
let path_to_config = format!("{}/config.ron", sharedir);
let cfg_file = File::open(&path_to_config).expect("could not open config file");
let mut cfg: ConfigLoader = ron::de::from_reader(cfg_file).unwrap();
let default_path = into_absolute_path(sharedir, cfg.include_default_config_path.unwrap());
let default_config_folder = Path::new(&default_path).parent().unwrap().to_str().unwrap();
cfg.include_default_config_path = Some(default_path.clone());
let default_file = File::open(cfg.include_default_config_path.as_ref().expect("no default config given")).expect("could not open config file");
let default: ConfigLoader = ron::de::from_reader(default_file).unwrap();
Self::new_from_loader(&sharedir, &default_config_folder, default, cfg)
}
}

7
config/src/lib.rs Normal file
View File

@ -0,0 +1,7 @@
extern crate serde;
extern crate serde_derive;
extern crate ron;
mod loader;
mod config;
pub use config::*;

59
config/src/loader.rs Normal file
View File

@ -0,0 +1,59 @@
use crate::config::*;
use serde_derive::Serialize;
use serde_derive::Deserialize;
use std::time::Duration;
#[derive(Clone, Serialize, Deserialize)]
pub struct QemuKernelConfigLoader {
pub qemu_binary: Option<String>,
pub kernel: Option<String>,
pub ramfs: Option<String>,
pub debug: Option<bool>,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct QemuSnapshotConfigLoader {
pub qemu_binary: Option<String>,
pub hda: Option<String>,
pub presnapshot: Option<String>,
pub snapshot_path: Option<SnapshotPath>,
pub debug: Option<bool>,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct ForkServerConfigLoader {
pub args: Option<Vec<String>>,
pub hide_output: Option<bool>,
pub input_size: Option<usize>,
pub env: Option<Vec<String>>,
}
#[derive(Clone, Serialize, Deserialize)]
pub enum FuzzRunnerConfigLoader {
QemuKernel(QemuKernelConfigLoader),
QemuSnapshot(QemuSnapshotConfigLoader),
}
#[derive(Clone, Serialize, Deserialize)]
pub struct FuzzerConfigLoader {
pub workdir_path: Option<String>,
pub bitmap_size: Option<usize>,
pub mem_limit: Option<usize>,
pub time_limit: Option<Duration>,
pub target_binary: Option<String>,
pub threads: Option<usize>,
pub thread_id: Option<usize>,
pub cpu_pin_start_at: Option<usize>,
pub seed_path: Option<String>,
pub dict: Option<Vec<Vec<u8>>>,
pub snapshot_placement: Option<SnapshotPlacement>,
pub dump_python_code_for_inputs: Option<bool>,
pub exit_after_first_crash: Option<bool>,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct ConfigLoader {
pub include_default_config_path: Option<String>,
pub runner: FuzzRunnerConfigLoader,
pub fuzz: FuzzerConfigLoader,
}

3
fuzz_runner/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
target/
debug/
Cargo.lock

28
fuzz_runner/Cargo.toml Normal file
View File

@ -0,0 +1,28 @@
[package]
name = "fuzz_runner"
version = "0.1.0"
authors = ["coco <cornelius@hexgolems.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nix = "0.17.0"
time = "0.2.9"
subprocess = "0.2.4"
regex = "1.3.6"
lazy_static = "1.4.0"
libc = "0.2.68"
quick-error = "*"
tempfile = "3.1.0"
rand = "0.7.3"
serde_derive = "1.0"
serde = "1.0"
byteorder = "*"
snafu = "0.6.3"
timeout-readwrite = "0.3.1"
glob="0.3.0"
hex="0.4.2"
config={path="../config"}
colored = "2.0.0"
derivative = "2.1.1"

View File

@ -0,0 +1,46 @@
use nix::sys::wait::WaitStatus;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ExitReason {
Normal(i32),
Timeout,
Signaled(i32),
Crash(Vec<u8>),
Asan,
Stopped(i32),
FuzzerError,
InvalidWriteToPayload(Vec<u8>),
}
impl ExitReason {
pub fn from_wait_status(status: WaitStatus) -> ExitReason {
return match status {
WaitStatus::Exited(_, return_value) => ExitReason::Normal(return_value),
WaitStatus::Signaled(_, signal, _) => ExitReason::Signaled(signal as i32),
WaitStatus::Stopped(_, signal) => ExitReason::Stopped(signal as i32),
_ => panic!("Unknown WaitStatus: {:?}", status),
};
}
pub fn is_normal(&self) -> bool{
use ExitReason::*;
match self {
Normal(_) => return true,
_ => return false,
}
}
pub fn name(&self) -> &str{
use ExitReason::*;
match self {
Normal(_) => return "normal",
Timeout => return "timeout",
Signaled(_) => return "signal",
Crash(_) => return "crash",
Asan => return "asan",
Stopped(_) => return "stop",
InvalidWriteToPayload(_) => return "invalid_write_to_payload_buffer",
FuzzerError => unreachable!(),
}
}
}

View File

@ -0,0 +1,66 @@
use nix;
use std;
quick_error! {
#[derive(Debug, Clone)]
pub enum SpawnError {
Fork(err: nix::Error) {
from()
description("execve failed")
display("execve error: {}", err)
cause(err)
}
Path(desc: String){
description("Invalid Path")
display("Problem with binary path: {}", desc)
}
Exec(desc: String){
description("Execution Failed")
display("Execution failed: {}", desc)
}
FFINull(err: std::ffi::NulError) {
from()
description("argument/path contained Null byte")
display("Null error: {}", err)
cause(err)
}
DevNull(desc: String){
description("failed to open /dev/null")
display("failed to open /dev/null: {}", desc)
}
}
}
pub fn path_err<T>(desc: &str) -> Result<T, SpawnError> {
return Err(SpawnError::Path(desc.into()));
}
quick_error! {
#[derive(Debug)]
pub enum SubprocessError {
Spawn(err: SpawnError) {
from()
description("spawning failed")
display("spawning failed: {}", err)
cause(err)
}
Unspecific(desc: String){
description("Subprocess Failed")
display("Subprocess failed: {}", desc)
}
Io(err: std::io::Error){
from()
cause(err)
}
Unix(err: nix::Error){
from()
cause(err)
}
}
}
pub fn descr_err<T>(desc: &str) -> Result<T, SubprocessError> {
return Err(SubprocessError::Unspecific(desc.into()));
}

View File

@ -0,0 +1,246 @@
/*
pub mod newtypes;
use nix::fcntl;
use nix::libc::{
__errno_location, shmat, shmctl, shmget, strerror, IPC_CREAT, IPC_EXCL, IPC_PRIVATE, IPC_RMID,
};
use nix::sys::signal::{self, Signal};
use nix::sys::stat;
use nix::sys::wait::WaitStatus;
use nix::unistd;
use nix::unistd::Pid;
use nix::unistd::{fork, ForkResult};
use std::ffi::{CStr, CString};
use std::os::unix::io::AsRawFd;
use std::os::unix::io::RawFd;
use std::io::BufReader;
use std::ptr;
use timeout_readwrite::TimeoutReader;
use byteorder::{LittleEndian, ReadBytesExt};
use std::fs::File;
use std::os::unix::io::FromRawFd;
use crate::exitreason::ExitReason;
use newtypes::*;
use snafu::ResultExt;
extern crate config;
use crate::config::{ForkServerConfig, FuzzerConfig};
pub struct ForkServer {
inp_file: File,
ctl_in: File,
shared_data: *mut [u8],
input_data: Vec<u8>,
input_size: usize,
st_out: std::io::BufReader<TimeoutReader<File>>,
}
impl ForkServer {
pub fn new(cfg: &ForkServerConfig, fuzz_cfg: &FuzzerConfig) -> Self {
let inp_file = tempfile::NamedTempFile::new().expect("couldn't create temp file");
let (inp_file, in_path) = inp_file
.keep()
.expect("couldn't persists temp file for input");
let inp_file_path = in_path
.to_str()
.expect("temp path should be unicode!")
.to_string();
let args = cfg
.args
.iter()
.map(|s| {
if s == "@@" {
inp_file_path.clone()
} else {
s.to_string()
}
})
.collect::<Vec<_>>();
let (ctl_out, ctl_in) = nix::unistd::pipe().expect("failed to create ctl_pipe");
let (st_out, st_in) = nix::unistd::pipe().expect("failed to create st_pipe");
let (shm_file, shared_data) = ForkServer::create_shm(fuzz_cfg.bitmap_size);
match fork().expect("couldn't fork") {
// Parent returns
ForkResult::Parent { child: _, .. } => {
unistd::close(ctl_out).expect("coulnd't close ctl_out");
unistd::close(st_in).expect("coulnd't close st_out");
let mut st_out = BufReader::new(TimeoutReader::new(
unsafe { File::from_raw_fd(st_out) },
fuzz_cfg.time_limit,
));
st_out
.read_u32::<LittleEndian>()
.expect("couldn't read child hello");
return Self {
inp_file: inp_file,
ctl_in: unsafe { File::from_raw_fd(ctl_in) },
shared_data: shared_data,
st_out,
input_data: vec![0; cfg.input_size],
input_size: 0,
};
}
//Child does complex stuff
ForkResult::Child => {
let forkserver_fd = 198; // from AFL config.h
unistd::dup2(ctl_out, forkserver_fd as RawFd)
.expect("couldn't dup2 ctl_our to FROKSRV_FD");
unistd::dup2(st_in, (forkserver_fd + 1) as RawFd)
.expect("couldn't dup2 ctl_our to FROKSRV_FD+1");
unistd::dup2(inp_file.as_raw_fd(), 0).expect("couldn't dup2 input file to stdin");
unistd::close(inp_file.as_raw_fd()).expect("couldn't close input file");
unistd::close(ctl_in).expect("couldn't close ctl_in");
unistd::close(ctl_out).expect("couldn't close ctl_out");
unistd::close(st_in).expect("couldn't close ctl_out");
unistd::close(st_out).expect("couldn't close ctl_out");
let path = CString::new(fuzz_cfg.target_binary.as_ref().expect("forkserver requires target path").to_string())
.expect("binary path must not contain zero");
let args = args
.into_iter()
.map(|s| CString::new(s).expect("args must not contain zero"))
.collect::<Vec<_>>();
let shm_id = CString::new(format!("__AFL_SHM_ID={}", shm_file)).unwrap();
//Asan options: set asan SIG to 223 and disable leak detection
let asan_settings =
CString::new("ASAN_OPTIONS=exitcode=223,abort_on_erro=true,detect_leaks=0")
.expect("RAND_2089158993");
let mut env = vec![shm_id, asan_settings];
env.extend(
cfg.env
.iter()
.map(|str| CString::new(str.to_string()).unwrap()),
);
if cfg.hide_output {
let null = fcntl::open("/dev/null", fcntl::OFlag::O_RDWR, stat::Mode::empty())
.expect("couldn't open /dev/null");
unistd::dup2(null, 1 as RawFd).expect("couldn't dup2 /dev/null to stdout");
unistd::dup2(null, 2 as RawFd).expect("couldn't dup2 /dev/null to stderr");
unistd::close(null).expect("couldn't close /dev/null");
}
let arg_ref = &args.iter().map(|x| x.as_c_str()).collect::<Vec<&CStr>>()[..];
let env_ref = &env.iter().map(|x| x.as_c_str()).collect::<Vec<&CStr>>()[..];
println!("EXECVE {:?} {:?} {:?}", fuzz_cfg.target_binary, arg_ref, env_ref);
unistd::execve(&path, arg_ref, env_ref).expect("couldn't execve target");
unreachable!();
}
}
}
pub fn run_data(&mut self, data: &[u8]) -> Result<ExitReason, SubprocessError> {
self.input_data[0..data.len()].copy_from_slice(data);
self.input_size = data.len();
return self.run();
}
pub fn run(&mut self) -> Result<ExitReason, SubprocessError> {
for i in self.get_bitmap_mut().iter_mut() {
*i = 0;
}
unistd::ftruncate(self.inp_file.as_raw_fd(), 0).context(QemuRunNix {
task: "Couldn't truncate inp_file",
})?;
unistd::lseek(self.inp_file.as_raw_fd(), 0, unistd::Whence::SeekSet).context(
QemuRunNix {
task: "Couldn't seek inp_file",
},
)?;
unistd::write(
self.inp_file.as_raw_fd(),
&self.input_data[0..self.input_size],
)
.context(QemuRunNix {
task: "Couldn't write data to inp_file",
})?;
unistd::lseek(self.inp_file.as_raw_fd(), 0, unistd::Whence::SeekSet).context(
QemuRunNix {
task: "Couldn't seek inp_file",
},
)?;
unistd::write(self.ctl_in.as_raw_fd(), &[0, 0, 0, 0]).context(QemuRunNix {
task: "Couldn't send start command",
})?;
let pid = Pid::from_raw(self.st_out.read_i32::<LittleEndian>().context(QemuRunIO {
task: "Couldn't read target pid",
})?);
if let Ok(status) = self.st_out.read_i32::<LittleEndian>() {
return Ok(ExitReason::from_wait_status(
WaitStatus::from_raw(pid, status).expect("402104968"),
));
}
signal::kill(pid, Signal::SIGKILL).context(QemuRunNix {
task: "Couldn't kill timed out process",
})?;
self.st_out.read_u32::<LittleEndian>().context(QemuRunIO {
task: "couldn't read timeout exitcode",
})?;
return Ok(ExitReason::Timeout);
}
pub fn get_bitmap_mut(&mut self) -> &mut [u8] {
unsafe { return &mut *self.shared_data }
}
pub fn get_bitmap(&self) -> &[u8] {
unsafe { return &*self.shared_data }
}
pub fn get_input_mut(&mut self) -> &mut [u8] {
return &mut self.input_data[..];
}
pub fn get_input(&self) -> &[u8] {
return &self.input_data[..];
}
pub fn set_input_size(&mut self, size: usize) {
assert!(size <= self.input_data.len());
self.input_size = size;
}
fn create_shm(bitmap_size: usize) -> (i32, *mut [u8]) {
unsafe {
let shm_id = shmget(IPC_PRIVATE, bitmap_size, IPC_CREAT | IPC_EXCL | 0o600);
if shm_id < 0 {
panic!(
"shm_id {:?}",
CString::from_raw(strerror(*__errno_location()))
);
}
let trace_bits = shmat(shm_id, ptr::null(), 0);
if (trace_bits as isize) < 0 {
panic!(
"shmat {:?}",
CString::from_raw(strerror(*__errno_location()))
);
}
let res = shmctl(shm_id, IPC_RMID, 0 as *mut nix::libc::shmid_ds);
if res < 0 {
panic!(
"shmclt {:?}",
CString::from_raw(strerror(*__errno_location()))
);
}
return (shm_id, trace_bits as *mut [u8; 1 << 16]);
}
}
}
*/

View File

@ -0,0 +1,37 @@
use snafu::{Backtrace, Snafu};
use std::path::PathBuf;
#[derive(Debug, Snafu)]
#[snafu(visibility = "pub")]
pub enum SubprocessError {
#[snafu(display("Could not handle qemu trace file to {} {}", path.display(), source))]
ReadQemuTrace {
path: PathBuf,
source: std::io::Error,
},
#[snafu(display("Could not parse integer in {} {}", line, source))]
ParseIntQemuTrace {
line: String,
source: std::num::ParseIntError,
},
#[snafu(display("Could not parse line {}", line))]
ParseLineQemuTrace { line: String, backtrace: Backtrace },
#[snafu(display("Qemu did not produce any output"))]
NoQemuOutput { backtrace: Backtrace },
#[snafu(display("Could not communicate with QemuForkServer {} {} ", task, source))]
QemuRunNix { task: String, source: nix::Error },
#[snafu(display("Could not communicate with QemuForkServer {} {} ", task, source))]
QemuRunIO {
task: String,
source: std::io::Error,
},
#[snafu(display("Could not disassemble {}", task))]
DisassemblyError { task: String, backtrace: Backtrace },
}

226
fuzz_runner/src/lib.rs Normal file
View File

@ -0,0 +1,226 @@
extern crate byteorder;
extern crate glob;
extern crate nix;
extern crate serde_derive;
extern crate snafu;
extern crate tempfile;
extern crate timeout_readwrite;
extern crate config;
#[macro_use] extern crate lazy_static;
extern crate regex;
extern crate hex;
pub mod exitreason;
pub use exitreason::ExitReason;
pub mod forksrv;
//pub use forksrv::ForkServer;
pub mod nyx;
pub use nyx::QemuProcess;
use std::error::Error;
#[derive(Debug,Clone,Eq,PartialEq,Hash)]
pub struct TestInfo {
pub ops_used: u32,
pub exitreason: ExitReason
}
#[derive(Debug,Clone,Eq,PartialEq,Hash)]
pub enum RedqueenBPType{
Str,
Cmp,
Sub,
}
impl RedqueenBPType{
pub fn new(data:&str) -> Self {
match data {
"STR" => return Self::Str,
"CMP" => return Self::Cmp,
"SUB" => return Self::Sub,
_ => panic!("unknown reqdueen bp type {}",data),
}
}
}
#[derive(Debug,Clone,Eq,PartialEq,Hash)]
pub struct RedqueenEvent{
pub addr: u64,
pub bp_type: RedqueenBPType,
pub lhs: Vec<u8>,
pub rhs: Vec<u8>,
pub imm: bool,
}
impl RedqueenEvent{
pub fn new(line: &str) -> Self{
lazy_static! {
static ref RE : regex::Regex = regex::Regex::new(r"([0-9a-fA-F]+)\s+(CMP|SUB|STR)\s+(\d+)\s+([0-9a-fA-F]+)-([0-9a-fA-F]+)(\sIMM)?").unwrap();
}
if let Some(mat) = RE.captures(line){
let addr_s = mat.get(1).unwrap().as_str();
let type_s = mat.get(2).unwrap().as_str();
//let bits_s =mat.get(3);
let lhs = mat.get(4).unwrap().as_str();
let rhs = mat.get(5).unwrap().as_str();
let imm = mat.get(6).map(|_x| true).unwrap_or(false);
return Self{addr: u64::from_str_radix(addr_s, 16).unwrap(), bp_type: RedqueenBPType::new(type_s), lhs: hex::decode(lhs).unwrap(), rhs: hex::decode(rhs).unwrap(), imm};
}
panic!("couldn't parse redqueen line {}",line);
}
}
#[derive(Debug,Clone,Eq,PartialEq,Hash)]
pub struct RedqueenInfo {pub bps: Vec<RedqueenEvent>}
pub struct CFGInfo {}
pub trait FuzzRunner {
fn run_test(&mut self) -> Result<TestInfo, Box<dyn Error>>;
fn run_redqueen(&mut self) -> Result<RedqueenInfo, Box<dyn Error>>;
fn run_cfg(&mut self) -> Result<CFGInfo, Box<dyn Error>>;
fn run_create_snapshot(&mut self) -> bool;
fn delete_snapshot(&mut self) -> Result<(), Box<dyn Error>>;
fn shutdown(&mut self) -> Result<(), Box<dyn Error>>;
fn input_buffer(&mut self) -> &mut [u8];
fn bitmap_buffer(&self) -> &[u8];
fn ijon_max_buffer(&self) -> &[u64];
fn set_input_size(&mut self, size: usize);
fn parse_redqueen_data(&self, data: &str) -> RedqueenInfo{
let bps = data.lines().map(|line| RedqueenEvent::new(line)).collect::<Vec<_>>();
return RedqueenInfo{bps}
}
fn parse_redqueen_file(&self, path: &str) -> RedqueenInfo{
self.parse_redqueen_data(&std::fs::read_to_string(path).unwrap())
}
}
/*
impl FuzzRunner for ForkServer {
fn run_test(&mut self) -> Result<TestInfo, Box<dyn Error>> {
self.run().unwrap();
return Ok(TestInfo {ops_used: 0, exitreason: ExitReason::FuzzerError}); //TODO fix this!
}
fn run_redqueen(&mut self) -> Result<RedqueenInfo, Box<dyn Error>> {
unreachable!();
//return Ok(parse_redqueen_file());
}
fn run_cfg(&mut self) -> Result<CFGInfo, Box<dyn Error>> {
unreachable!()
//return Ok(CFGInfo {});
}
fn run_create_snapshot(&mut self) -> bool{
unreachable!();
}
fn delete_snapshot(&mut self) -> Result<(), Box<dyn Error>>{
unreachable!();
}
fn shutdown(self) -> Result<(), Box<dyn Error>> {
return Ok(());
}
fn input_buffer(&mut self) -> &mut [u8] {
self.get_input_mut()
}
fn bitmap_buffer(&self) -> &[u8] {
self.get_bitmap()
}
fn ijon_max_buffer(&self) -> &[u64] {
unreachable!();
}
fn set_input_size(&mut self, size: usize) {
ForkServer::set_input_size(self, size)
}
}
*/
impl FuzzRunner for QemuProcess {
fn run_test(&mut self) -> Result<TestInfo, Box<dyn Error>> {
self.send_payload();
let ops_used = self.feedback_data.shared.interpreter.executed_opcode_num;
if self.aux.result.crash_found != 0 {
return Ok(TestInfo {ops_used, exitreason: ExitReason::Crash(self.aux.misc.as_slice().to_vec())});
}
if self.aux.result.payload_write_attempt_found != 0{
return Ok(TestInfo {ops_used, exitreason: ExitReason::InvalidWriteToPayload(self.aux.misc.as_slice().to_vec())});
}
if self.aux.result.timeout_found != 0 {
return Ok(TestInfo {ops_used, exitreason: ExitReason::Timeout});
}
if self.aux.result.asan_found != 0 {
return Ok(TestInfo {ops_used, exitreason: ExitReason::Asan});
}
if self.aux.result.success != 0{
return Ok(TestInfo {ops_used, exitreason: ExitReason::Normal(0)});
}
println!("unknown exeuction result!!");
return Ok(TestInfo {ops_used, exitreason: ExitReason::FuzzerError});
}
fn run_create_snapshot(&mut self) -> bool{
assert_eq!(self.aux.result.tmp_snapshot_created,0);
self.send_payload();
return self.aux.result.tmp_snapshot_created == 1;
}
fn delete_snapshot(&mut self) -> Result<(), Box<dyn Error>>{
if self.aux.result.tmp_snapshot_created != 0 {
self.aux.config.changed = 1;
self.aux.config.discard_tmp_snapshot = 1;
self.send_payload();
if self.aux.result.tmp_snapshot_created != 0 {
println!("AUX BUFFER {:#?}",self.aux);
}
assert_eq!( self.aux.result.tmp_snapshot_created, 0);
}
return Ok(());
}
fn run_redqueen(&mut self) -> Result<RedqueenInfo, Box<dyn Error>> {
self.aux.config.changed = 1;
self.aux.config.redqueen_mode=1;
self.send_payload();
self.aux.config.changed = 1;
self.aux.config.redqueen_mode=0;
let rq_file = format!("{}/redqueen_workdir_{}/redqueen_results.txt",self.params.workdir,self.params.qemu_id);
return Ok(self.parse_redqueen_file(&rq_file));
}
fn run_cfg(&mut self) -> Result<CFGInfo, Box<dyn Error>> {
//println!("TRACE!!!!");
self.aux.config.trace_mode=1;
self.aux.config.changed = 1;
self.send_payload();
self.aux.config.changed = 1;
self.aux.config.trace_mode=0;
return Ok(CFGInfo {});
}
fn shutdown(&mut self) -> Result<(), Box<dyn Error>> {
self.shutdown();
return Ok(());
}
fn input_buffer(&mut self) -> &mut [u8] {
&mut self.payload[..]
}
fn bitmap_buffer(&self) -> &[u8] {
self.bitmap
}
fn ijon_max_buffer(&self) -> &[u64] {
&self.feedback_data.shared.ijon.max_data
}
fn set_input_size(&mut self, _size: usize) {
//self.payload[4..].copy_from_slice(&(size as u32).to_le_bytes());
}
}

View File

@ -0,0 +1,215 @@
use core::ffi::c_void;
use nix::sys::mman::*;
use std::fs::File;
use std::os::unix::io::IntoRawFd;
use std::fmt;
//use std::sync::atomic::compiler_fence;
//use std::sync::atomic::Ordering;
use crate::nyx::mem_barrier::mem_barrier;
use derivative::Derivative;
const AUX_BUFFER_SIZE: usize = 4096;
const AUX_MAGIC: u64 = 0x54502d554d4551_u64;
const QEMU_PT_VERSION: u16 = 1; /* let's start at 1 for the initial version using the aux buffer */
const HEADER_SIZE: usize = 128;
const CAP_SIZE: usize = 256;
const CONFIG_SIZE: usize = 512;
const STATE_SIZE: usize = 512;
//const MISC_SIZE: usize = 4096 - (HEADER_SIZE + CAP_SIZE + CONFIG_SIZE + STATE_SIZE);
const HEADER_OFFSET: usize = 0;
const CAP_OFFSET: usize = HEADER_OFFSET + HEADER_SIZE;
const CONFIG_OFFSET: usize = CAP_OFFSET + CAP_SIZE;
const STATE_OFFSET: usize = CONFIG_OFFSET + CONFIG_SIZE;
const MISC_OFFSET: usize = STATE_OFFSET + STATE_SIZE;
const MISC_SIZE: usize = AUX_BUFFER_SIZE - MISC_OFFSET;
#[derive(Debug)]
pub struct AuxBuffer {
pub header: &'static mut auxilary_buffer_header_s,
pub cap: &'static mut auxilary_buffer_cap_s,
pub config: &'static mut auxilary_buffer_config_s,
pub result: &'static mut auxilary_buffer_result_s,
pub misc: &'static mut auxilary_buffer_misc_s,
}
impl AuxBuffer {
pub fn new_readonly(file: File, read_only: bool) -> Self {
let mut prot = ProtFlags::PROT_READ;
if !read_only{
prot |= ProtFlags::PROT_WRITE;
}
let flags = MapFlags::MAP_SHARED;
unsafe {
let ptr = mmap(0 as *mut c_void, 0x1000, prot, flags, file.into_raw_fd(), 0).unwrap();
let header = (ptr.add(HEADER_OFFSET) as *mut auxilary_buffer_header_s)
.as_mut()
.unwrap();
let cap = (ptr.add(CAP_OFFSET) as *mut auxilary_buffer_cap_s)
.as_mut()
.unwrap();
let config = (ptr.add(CONFIG_OFFSET) as *mut auxilary_buffer_config_s)
.as_mut()
.unwrap();
let result = (ptr.add(STATE_OFFSET) as *mut auxilary_buffer_result_s)
.as_mut()
.unwrap();
let misc = (ptr.add(MISC_OFFSET) as *mut auxilary_buffer_misc_s)
.as_mut()
.unwrap();
return Self {
header,
cap,
config,
result,
misc,
};
}
}
pub fn new(file: File) -> Self {
return AuxBuffer::new_readonly(file, false);
}
pub fn validate_header(&self) {
mem_barrier();
let mgc = self.header.magic;
assert_eq!(mgc, AUX_MAGIC);
let version = self.header.version;
assert_eq!(version, QEMU_PT_VERSION);
let hash = self.header.hash;
assert_eq!(hash, 81);
}
}
#[derive(Debug, Copy, Clone)]
#[repr(C, packed(1))]
pub struct auxilary_buffer_header_s {
pub magic: u64, /* 0x54502d554d4551 */
pub version: u16,
pub hash: u16,
}
#[derive(Debug, Copy, Clone)]
#[repr(C, packed(1))]
pub struct auxilary_buffer_cap_s {
pub redqueen: u8,
pub agent_timeout_detection: u8, /* agent implements own timeout detection; host timeout detection is still in used, but treshold is increased by x2; */
pub agent_trace_bitmap: u8, /* agent implements own tracing mechanism; PT tracing is disabled */
pub agent_ijon_trace_bitmap: u8, /* agent uses the ijon shm buffer */
}
#[derive(Debug, Copy, Clone)]
#[repr(C, packed(1))]
pub struct auxilary_buffer_config_s {
pub changed: u8, /* set this byte to kick in a rescan of this buffer */
pub timeout_sec: u8,
pub timeout_usec: u32,
/* trigger to enable / disable different QEMU-PT modes */
pub redqueen_mode: u8,
pub trace_mode: u8,
pub reload_mode: u8,
pub verbose_level: u8,
pub page_dump_mode: u8,
pub page_addr: u64,
/* 0 -> disabled
1 -> decoding
2 -> decoding + full disassembling
*/
//uint8_t pt_processing_mode;
pub protect_payload_buffer: u8,
/* snapshot extension */
pub discard_tmp_snapshot: u8,
}
#[derive(Derivative)]
#[derivative(Debug)]
#[derive(Copy, Clone)]
#[repr(C, packed(1))]
pub struct auxilary_buffer_result_s {
/* 0 -> booting,
1 -> loader level 1,
2 -> loader level 2,
3 -> ready to fuzz
*/
pub state: u8,
/* snapshot extension */
pub tmp_snapshot_created: u8,
#[derivative(Debug="ignore")]
pub padding_1: u8,
#[derivative(Debug="ignore")]
pub padding_2: u8,
pub bb_coverage: u32,
#[derivative(Debug="ignore")]
pub padding_3: u8,
#[derivative(Debug="ignore")]
pub padding_4: u8,
pub hprintf: u8,
pub exec_done: u8,
pub crash_found: u8,
pub asan_found: u8,
pub timeout_found: u8,
pub reloaded: u8,
pub pt_overflow: u8,
pub runtime_sec: u8,
pub page_not_found: u8,
pub success: u8,
pub runtime_usec: u32,
pub page_not_found_addr: u64,
pub dirty_pages: u32,
pub pt_trace_size: u32,
pub payload_write_attempt_found: u8,
}
#[repr(C, packed(1))]
pub struct auxilary_buffer_misc_s {
pub len: u16,
pub data: [u8;MISC_SIZE-2],
}
fn inspect_bytes(bs: &[u8]) -> String {
use std::ascii::escape_default;
use std::str;
let mut visible = String::new();
for &b in bs {
let part: Vec<u8> = escape_default(b).collect();
visible.push_str(str::from_utf8(&part).unwrap());
}
visible
}
impl auxilary_buffer_misc_s{
pub fn as_slice(&self) -> &[u8]{
assert!(self.len as usize <= self.data.len());
return &self.data[0..self.len as usize];
}
pub fn as_string(&self) -> String{
inspect_bytes(self.as_slice())
}
}
impl fmt::Debug for auxilary_buffer_misc_s {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("auxilary_buffer_misc_s")
.field("data", &inspect_bytes(self.as_slice()))
.finish()
}
}

View File

@ -0,0 +1,29 @@
#[derive(Debug, Copy, Clone)]
#[repr(C, packed(1))]
pub struct InterpreterData{
pub executed_opcode_num: u32
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct IjonData {
pub max_data: [u64;256],
}
#[derive(Copy, Clone)]
#[repr(C, packed(1))]
pub struct SharedFeedbackData{
pub interpreter: InterpreterData,
pad: [u8; 0x1000/2-std::mem::size_of::<InterpreterData>()],
pub ijon: IjonData,
}
pub struct FeedbackBuffer {
pub shared: &'static mut SharedFeedbackData,
}
impl FeedbackBuffer{
pub fn new(shared: &'static mut SharedFeedbackData) -> Self{
Self{shared}
}
}

View File

@ -0,0 +1,33 @@
use std::sync::atomic::compiler_fence;
use std::sync::atomic::Ordering;
// we expect this to be a nop.
// but in some extreme cases, this
/*
use std::sync::atomic::compiler_fence;
use std::sync::atomic::Ordering;
fn barrier() {
compiler_fence(Ordering::SeqCst);
}
pub fn read2(data: &mut u32) -> u32{
let a = *data;
barrier();
let b = *data;
return a.wrapping_add(b);
}
*/
//compiles to
/*
mov eax, dword ptr [rdi]
add eax, dword ptr [rdi]
ret
*/
//while the second access gets optimized out without the barrier.
//To ensure that reads/writes to the shared memory buffer actually are executed, we use mem_barrier to lightweight synchronize the values.
pub fn mem_barrier() {
compiler_fence(Ordering::SeqCst);
}

129
fuzz_runner/src/nyx/mod.rs Normal file
View File

@ -0,0 +1,129 @@
pub mod aux_buffer;
pub mod ijon_data;
pub mod mem_barrier;
pub mod params;
pub mod qemu_process;
pub use qemu_process::QemuProcess;
use std::fs;
use std::path::PathBuf;
extern crate config;
use crate::config::{QemuKernelConfig, QemuSnapshotConfig, FuzzerConfig, SnapshotPath};
fn into_absolute_path(sharedir: &str) -> String{
let srcdir = PathBuf::from(&sharedir);
if srcdir.is_relative(){
return fs::canonicalize(&srcdir).unwrap().to_str().unwrap().to_string();
}
else{
return sharedir.to_string();
}
}
pub fn qemu_process_new_from_kernel(sharedir: String, cfg: &QemuKernelConfig, fuzz_cfg: &FuzzerConfig) -> qemu_process::QemuProcess {
let params = params::KernelVmParams {
qemu_binary: cfg.qemu_binary.to_string(),
kernel: cfg.kernel.to_string(),
sharedir: into_absolute_path(&sharedir),
ramfs: cfg.ramfs.to_string(),
ram_size: fuzz_cfg.mem_limit,
bitmap_size: fuzz_cfg.bitmap_size,
debug: cfg.debug,
dump_python_code_for_inputs: match fuzz_cfg.dump_python_code_for_inputs{
None => false,
Some(x) => x,
}
};
let qemu_id = fuzz_cfg.thread_id;
let qemu_params = params::QemuParams::new_from_kernel(&fuzz_cfg.workdir_path, qemu_id, &params, fuzz_cfg.threads > 1);
/*
if qemu_id == 0{
qemu_process::QemuProcess::prepare_workdir(&fuzz_cfg.workdir_path, fuzz_cfg.seed_pattern.clone());
}
*/
return qemu_process::QemuProcess::new(qemu_params);
}
pub fn qemu_process_new_from_snapshot(sharedir: String, cfg: &QemuSnapshotConfig, fuzz_cfg: &FuzzerConfig) -> qemu_process::QemuProcess {
let snapshot_path = match &cfg.snapshot_path{
SnapshotPath::Create(_x) => panic!(),
SnapshotPath::Reuse(x) => SnapshotPath::Reuse(x.to_string()),
SnapshotPath::DefaultPath => {
if fuzz_cfg.thread_id == 0 {
SnapshotPath::Create(format!("{}/snapshot/",fuzz_cfg.workdir_path))
} else {
SnapshotPath::Reuse(format!("{}/snapshot/",fuzz_cfg.workdir_path))
}
}
};
let params = params::SnapshotVmParams {
qemu_binary: cfg.qemu_binary.to_string(),
hda: cfg.hda.to_string(),
sharedir: into_absolute_path(&sharedir),
presnapshot: cfg.presnapshot.to_string(),
ram_size: fuzz_cfg.mem_limit,
bitmap_size: fuzz_cfg.bitmap_size,
debug: cfg.debug,
snapshot_path,
dump_python_code_for_inputs: match fuzz_cfg.dump_python_code_for_inputs{
None => false,
Some(x) => x,
}
};
let qemu_id = fuzz_cfg.thread_id;
let qemu_params = params::QemuParams::new_from_snapshot(&fuzz_cfg.workdir_path, qemu_id, fuzz_cfg.cpu_pin_start_at, &params, fuzz_cfg.threads > 1);
/*
if qemu_id == 0{
println!("------> WIPING EVERYTHING");
qemu_process::QemuProcess::prepare_workdir(&fuzz_cfg.workdir_path, fuzz_cfg.seed_pattern.clone());
println!("------> WIPING EVERYTHING DONE");
}
*/
return qemu_process::QemuProcess::new(qemu_params);
}
#[cfg(test)]
mod tests {
//use crate::aux_buffer::*;
use super::params::*;
use super::qemu_process::*;
//use std::{thread, time};
#[test]
fn it_works() {
let workdir = "/tmp/workdir_test";
let params = KernelVmParams {
qemu_binary: "/home/kafl/NEW2/QEMU-PT_4.2.0/x86_64-softmmu/qemu-system-x86_64"
.to_string(),
kernel: "/home/kafl/Target-Components/linux_initramfs/bzImage-linux-4.15-rc7"
.to_string(),
ramfs: "/home/kafl/Target-Components/linux_initramfs/init.cpio.gz".to_string(),
sharedir: "foo! invalid".to_string(),
ram_size: 1000,
bitmap_size: 0x1 << 16,
debug: false,
dump_python_code_for_inputs: false,
};
let qemu_id = 1;
let qemu_params = QemuParams::new_from_kernel(workdir, qemu_id, &params);
QemuProcess::prepare_workdir(&workdir, None);
let mut qemu_process = QemuProcess::new(qemu_params);
for _i in 0..100 {
qemu_process.send_payload();
}
println!("test done");
}
}

View File

@ -0,0 +1,266 @@
use std::path::Path;
use crate::config::SnapshotPath;
pub struct KernelVmParams {
pub qemu_binary: String,
pub kernel: String,
pub sharedir: String,
pub ramfs: String,
pub ram_size: usize,
pub bitmap_size: usize,
pub debug: bool,
pub dump_python_code_for_inputs: bool,
}
pub struct SnapshotVmParams{
pub qemu_binary: String,
pub hda: String,
pub sharedir: String,
pub presnapshot: String,
pub snapshot_path: SnapshotPath,
pub ram_size: usize,
pub bitmap_size: usize,
pub debug: bool,
pub dump_python_code_for_inputs: bool,
}
pub struct QemuParams {
pub cmd: Vec<String>,
pub qemu_aux_buffer_filename: String,
pub control_filename: String,
pub bitmap_filename: String,
pub payload_filename: String,
pub binary_filename: String,
pub workdir: String,
pub qemu_id: usize,
pub bitmap_size: usize,
pub payload_size: usize,
pub dump_python_code_for_inputs: bool,
}
impl QemuParams {
pub fn new_from_snapshot(workdir: &str, qemu_id: usize, cpu: usize, params: &SnapshotVmParams, create_snapshot_file: bool) -> QemuParams{
assert!(!(!create_snapshot_file && qemu_id == 1));
let project_name = Path::new(workdir)
.file_name()
.expect("Couldn't get project name from workdir!")
.to_str()
.expect("invalid chars in workdir path")
.to_string();
let qemu_aux_buffer_filename = format!("{}/aux_buffer_{}", workdir, qemu_id);
let payload_filename = format!("/dev/shm/kafl_{}_qemu_payload_{}", project_name, qemu_id);
//let tracedump_filename = format!("/dev/shm/kafl_{}_pt_trace_dump_{}", project_name, qemu_id);
let binary_filename = format!("{}/program", workdir);
let bitmap_filename = format!("/dev/shm/kafl_{}_bitmap_{}", project_name, qemu_id);
let control_filename = format!("{}/interface_{}", workdir, qemu_id);
let mut cmd = vec![];
cmd.push(params.qemu_binary.to_string());
cmd.push("-drive".to_string());
cmd.push(format!("file={},format=raw,index=0,media=disk", params.hda.to_string()));
if !params.debug {
cmd.push("-display".to_string());
cmd.push("none".to_string());
} else {
cmd.push("-vnc".to_string());
cmd.push(format!(":{}",qemu_id+cpu));
}
cmd.push("-serial".to_string());
if params.debug {
cmd.push("mon:stdio".to_string());
} else {
cmd.push("stdio".to_string());
}
cmd.push("-enable-kvm".to_string());
cmd.push("-net".to_string());
cmd.push("none".to_string());
cmd.push("-k".to_string());
cmd.push("de".to_string());
cmd.push("-m".to_string());
cmd.push(params.ram_size.to_string());
cmd.push("-chardev".to_string());
cmd.push(format!(
"socket,server,path={},id=kafl_interface",
control_filename
));
// -fast_vm_reload path=/tmp/fuzz_workdir/snapshot/,load=off,pre_path=/home/kafl/ubuntu_snapshot
cmd.push("-device".to_string());
let mut nyx_ops = format!("kafl,chardev=kafl_interface");
nyx_ops += &format!(",bitmap_size={}", params.bitmap_size+0x1000);
nyx_ops += &format!(",worker_id={}", qemu_id);
nyx_ops += &format!(",workdir={}", workdir);
nyx_ops += &format!(",sharedir={}", params.sharedir);
//nyx_ops += &format!(",ip0_a=0x1000,ip0_b=0x7ffffffff000");
//nyx_ops += &format!(",ip0_a=ffff800000000000,ip0_b=ffffffffffffffff");
cmd.push(nyx_ops);
cmd.push("-machine".to_string());
cmd.push("kAFL64-v1".to_string());
cmd.push("-cpu".to_string());
cmd.push("kAFL64-Hypervisor-v1".to_string());
//cmd.push("kvm64-v1,".to_string());
match &params.snapshot_path {
SnapshotPath::Create(path) => {
if create_snapshot_file {
cmd.push("-fast_vm_reload".to_string());
cmd.push(format!("path={},load=off,pre_path={}", path,params.presnapshot));
}
else{
cmd.push("-fast_vm_reload".to_string());
cmd.push(format!("path={},load=off,pre_path={},skip_serialization=on", path,params.presnapshot));
}
},
SnapshotPath::Reuse(path) => {
cmd.push("-fast_vm_reload".to_string());
cmd.push(format!("path={},load=on", path));
}
SnapshotPath::DefaultPath => panic!(),
}
/*
bin = read_binary_file("/tmp/zsh_fuzz")
assert(len(bin)<= (128 << 20))
atomic_write(binary_filename, bin)
*/
return QemuParams {
cmd,
qemu_aux_buffer_filename,
control_filename,
bitmap_filename,
payload_filename,
binary_filename,
workdir: workdir.to_string(),
qemu_id,
bitmap_size: params.bitmap_size,
payload_size: (1 << 16),
dump_python_code_for_inputs: params.dump_python_code_for_inputs,
};
}
pub fn new_from_kernel(workdir: &str, qemu_id: usize, params: &KernelVmParams, create_snapshot_file: bool) -> QemuParams {
//prepare_working_dir(workdir)
assert!(!(!create_snapshot_file && qemu_id == 1));
let project_name = Path::new(workdir)
.file_name()
.expect("Couldn't get project name from workdir!")
.to_str()
.expect("invalid chars in workdir path")
.to_string();
let qemu_aux_buffer_filename = format!("{}/aux_buffer_{}", workdir, qemu_id);
let payload_filename = format!("/dev/shm/kafl_{}_qemu_payload_{}", project_name, qemu_id);
//let tracedump_filename = format!("/dev/shm/kafl_{}_pt_trace_dump_{}", project_name, qemu_id);
let binary_filename = format!("{}/program", workdir);
let bitmap_filename = format!("/dev/shm/kafl_{}_bitmap_{}", project_name, qemu_id);
let control_filename = format!("{}/interface_{}", workdir, qemu_id);
let mut cmd = vec![];
cmd.push(params.qemu_binary.to_string());
cmd.push("-kernel".to_string());
cmd.push(params.kernel.to_string());
cmd.push("-initrd".to_string());
cmd.push(params.ramfs.to_string());
cmd.push("-append".to_string());
cmd.push("nokaslr oops=panic nopti ignore_rlimit_data".to_string());
if !params.debug {
cmd.push("-display".to_string());
cmd.push("none".to_string());
}
cmd.push("-serial".to_string());
if params.debug {
cmd.push("mon:stdio".to_string());
} else {
cmd.push("none".to_string());
}
cmd.push("-enable-kvm".to_string());
cmd.push("-net".to_string());
cmd.push("none".to_string());
cmd.push("-k".to_string());
cmd.push("de".to_string());
cmd.push("-m".to_string());
cmd.push(params.ram_size.to_string());
//cmd.push//("-cdrom".to_string());
//cmd.push("/home/kafl/rust_dev/nyx/syzkaller_spec/cd.iso".to_string());
cmd.push("-chardev".to_string());
cmd.push(format!(
"socket,server,path={},id=kafl_interface",
control_filename
));
cmd.push("-device".to_string());
let mut nyx_ops = format!("kafl,chardev=kafl_interface");
nyx_ops += &format!(",bitmap_size={}", params.bitmap_size+0x1000); /* + ijon page */
nyx_ops += &format!(",worker_id={}", qemu_id);
nyx_ops += &format!(",workdir={}", workdir);
nyx_ops += &format!(",sharedir={}", params.sharedir);
//nyx_ops += &format!(",ip0_a=0x1000,ip0_b=0x7ffffffff000");
//nyx_ops += &format!(",ip0_a=ffff800000000000,ip0_b=ffffffffffffffff");
cmd.push(nyx_ops);
cmd.push("-machine".to_string());
cmd.push("kAFL64-v1".to_string());
cmd.push("-cpu".to_string());
cmd.push("kAFL64-Hypervisor-v1,+vmx".to_string());
//cmd.push("kvm64-v1,+vmx".to_string());
if create_snapshot_file {
cmd.push("-fast_vm_reload".to_string());
if qemu_id == 0{
cmd.push(format!("path={}/snapshot/,load=off", workdir));
} else {
cmd.push(format!("path={}/snapshot/,load=on", workdir));
}
}
/*
bin = read_binary_file("/tmp/zsh_fuzz")
assert(len(bin)<= (128 << 20))
atomic_write(binary_filename, bin)
*/
return QemuParams {
cmd,
qemu_aux_buffer_filename,
control_filename,
bitmap_filename,
payload_filename,
binary_filename,
workdir: workdir.to_string(),
qemu_id,
bitmap_size: params.bitmap_size,
payload_size: (128 << 10),
dump_python_code_for_inputs: params.dump_python_code_for_inputs,
};
}
}

View File

@ -0,0 +1,387 @@
use core::ffi::c_void;
use nix::sys::mman::*;
use std::fs;
use std::fs::{File, OpenOptions};
use std::io::prelude::*;
use std::os::unix::fs::symlink;
use std::os::unix::io::IntoRawFd;
use std::os::unix::net::UnixStream;
use std::path::Path;
use std::process::Child;
use std::process::Command;
use std::{thread, time};
use std::str;
extern crate colored; // not needed in Rust 2018
use colored::*;
use crate::nyx::aux_buffer::AuxBuffer;
use crate::nyx::ijon_data::{SharedFeedbackData, FeedbackBuffer};
use crate::nyx::mem_barrier::mem_barrier;
use crate::nyx::params::QemuParams;
pub struct QemuProcess {
pub process: Child,
pub aux: AuxBuffer,
pub feedback_data: FeedbackBuffer,
pub ctrl: UnixStream,
pub bitmap: &'static mut [u8],
pub payload: &'static mut [u8],
pub params: QemuParams,
hprintf_log: File,
}
fn execute_qemu(ctrl: &mut UnixStream) {
ctrl.write_all(&[120_u8]).unwrap();
}
fn wait_qemu(ctrl: &mut UnixStream) {
let mut buf = [0];
ctrl.read_exact(&mut buf).unwrap();
}
fn run_qemu(ctrl: &mut UnixStream) {
execute_qemu(ctrl);
wait_qemu(ctrl);
}
fn make_shared_data(file: File, size: usize) -> &'static mut [u8] {
let prot = ProtFlags::PROT_READ | ProtFlags::PROT_WRITE;
let flags = MapFlags::MAP_SHARED;
unsafe {
let ptr = mmap(0 as *mut c_void, size, prot, flags, file.into_raw_fd(), 0).unwrap();
let data = std::slice::from_raw_parts_mut(ptr as *mut u8, size);
return data;
}
}
fn make_shared_ijon_data(file: File, size: usize) -> FeedbackBuffer {
let prot = ProtFlags::PROT_READ | ProtFlags::PROT_WRITE;
let flags = MapFlags::MAP_SHARED;
unsafe {
let ptr = mmap(std::ptr::null_mut::<c_void>(), 0x1000, prot, flags, file.into_raw_fd(), size as i64).unwrap();
FeedbackBuffer::new((ptr as *mut SharedFeedbackData).as_mut().unwrap())
}
}
impl QemuProcess {
pub fn new(params: QemuParams) -> QemuProcess {
Self::prepare_redqueen_workdir(&params.workdir, params.qemu_id);
if params.qemu_id == 0{
println!("[!] libnyx: spawning qemu with:\n {}", params.cmd.join(" "));
}
let bitmap_shm_f = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.open(&params.bitmap_filename)
.expect("couldn't open bitmap file");
let mut payload_shm_f = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.open(&params.payload_filename)
.expect("couldn't open payload file");
symlink(
&params.bitmap_filename,
format!("{}/bitmap_{}", params.workdir, params.qemu_id),
)
.unwrap();
symlink(
&params.payload_filename,
format!("{}/payload_{}", params.workdir, params.qemu_id),
)
.unwrap();
//println!("======================================SET NOT_INIT!!!!");
payload_shm_f.write_all(b"not_init").unwrap();
bitmap_shm_f.set_len(params.bitmap_size as u64).unwrap();
payload_shm_f.set_len(params.payload_size as u64 + 0x1000).unwrap();
let bitmap_shared = make_shared_data(bitmap_shm_f, params.bitmap_size);
let payload_shared = make_shared_data(payload_shm_f, params.payload_size);
let bitmap_shm_f = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.open(&params.bitmap_filename)
.expect("couldn't open bitmap file");
let ijon_shared = make_shared_ijon_data(bitmap_shm_f, params.bitmap_size);
thread::sleep(time::Duration::from_secs(1));
thread::sleep(time::Duration::from_millis(200*params.qemu_id as u64));
let child = if params.dump_python_code_for_inputs{
Command::new(&params.cmd[0])
.args(&params.cmd[1..])
.env("DUMP_PAYLOAD_MODE", "TRUE")
.spawn()
.expect("failed to execute process")
}
else{
Command::new(&params.cmd[0])
.args(&params.cmd[1..])
.spawn()
.expect("failed to execute process")
};
thread::sleep(time::Duration::from_secs(1));
thread::sleep(time::Duration::from_millis(200*params.qemu_id as u64));
//println!("CONNECT TO {}", params.control_filename);
//control.settimeout(None) maybe needed?
//control.setblocking(1)
let mut control = loop {
match UnixStream::connect(&params.control_filename) {
Ok(stream) => break stream,
_ => {
//println!("TRY..."); /* broken af */
thread::sleep(time::Duration::from_millis(1))
},
}
};
// dry_run
//println!("TRHEAD {} run QEMU initial",params.qemu_id);
run_qemu(&mut control);
let aux_shm_f = OpenOptions::new()
.read(true)
.write(true)
.open(&params.qemu_aux_buffer_filename)
.expect("couldn't open aux buffer file");
aux_shm_f.set_len(0x1000).unwrap();
let aux_shm_f = OpenOptions::new()
.write(true)
.read(true)
.open(&params.qemu_aux_buffer_filename)
.expect("couldn't open aux buffer file");
let mut aux_buffer = AuxBuffer::new(aux_shm_f);
aux_buffer.validate_header();
aux_buffer.config.protect_payload_buffer = 1;
loop {
if aux_buffer.result.hprintf == 1 {
let len = aux_buffer.misc.len;
print!("{}", String::from_utf8_lossy(&aux_buffer.misc.data[0..len as usize]).yellow());
}
else{
//println!("QEMU NOT READY");
}
if aux_buffer.result.state == 3 {
break;
}
//println!("QEMU NOT READY");
//println!("TRHEAD {} run QEMU NOT READY",params.qemu_id);
run_qemu(&mut control);
}
//println!("QEMU READY");
println!("[!] libnyx: qemu #{} is ready:", params.qemu_id);
aux_buffer.config.reload_mode = 1;
aux_buffer.config.timeout_sec = 0;
aux_buffer.config.timeout_usec = 500_000;
aux_buffer.config.changed = 1;
//run_qemu(&mut control);
//run_qemu(&mut control);
let mut option = OpenOptions::new();
option.read(true);
option.write(true);
option.create(true);
let hprintf_log = option.open(format!("{}/hprintf_log_{}", params.workdir, params.qemu_id)).unwrap();
return QemuProcess {
process: child,
aux: aux_buffer,
feedback_data: ijon_shared,
ctrl: control,
bitmap: bitmap_shared,
payload: payload_shared,
params,
hprintf_log,
};
}
pub fn send_payload(&mut self) {
let mut old_address: u64 = 0;
//use rand::Rng;
//println!("RUN INPUT");
//std::thread::sleep(std::time::Duration::from_secs(1));
//let time = std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH).unwrap().as_nanos();
//self.hprintf_log.write_all(&format!("===({})===\n", time).as_bytes()).unwrap();
loop {
mem_barrier();
run_qemu(&mut self.ctrl);
mem_barrier();
if self.aux.result.hprintf != 0 {
self.hprintf_log.write_all(&format!("{}\n", self.aux.misc.as_string()).as_bytes()).unwrap();
//println!("HPRINTF {}", self.aux.misc.as_string());
let len = self.aux.misc.len;
print!("{}", String::from_utf8_lossy(&self.aux.misc.data[0..len as usize]).yellow());
//print!("{}", "".clear());
println!("TEST\n");
continue;
}
//println!("pt trace size {:x} bytes",self.aux.result.pt_trace_size);
//println!("{:} dirty pages",self.aux.result.dirty_pages);
//println!("interpreter ran {} ops",self.feedback_data.shared.interpreter.executed_opcode_num);
//let max_v = 0;
//let max_i = 0;
//for (i,v) in self.feedback_data.shared.ijon.max_data.iter().enumerate(){
// if *v > max_v{
// max_v=*v;
// max_i=i;
//
// }
//}
//println!("found IJON MAX: {}\t{:x}",max_i,max_v);
if self.aux.result.success != 0 || self.aux.result.crash_found != 0 || self.aux.result.asan_found != 0 || self.aux.result.payload_write_attempt_found != 0 {
break;
}
if self.aux.result.page_not_found != 0 {
let v = self.aux.result.page_not_found_addr;
println!("PAGE NOT FOUND -> {:x}\n", v);
if old_address == self.aux.result.page_not_found_addr {
break;
}
old_address = self.aux.result.page_not_found_addr;
self.aux.config.page_addr = self.aux.result.page_not_found_addr;
self.aux.config.page_dump_mode = 1;
self.aux.config.changed = 1;
}
//else {
// break;
//}
}
//std::thread::sleep(std::time::Duration::from_secs(1));
//if self.aux.result.tmp_snapshot_created != 0 {
// //println!("created snapshot!!!!!!\n");
//}
}
pub fn set_timeout(&mut self, timeout: std::time::Duration){
self.aux.config.timeout_sec = timeout.as_secs() as u8;
self.aux.config.timeout_usec = timeout.subsec_micros();
self.aux.config.changed = 1;
}
pub fn wait(&mut self) {
self.process.wait().unwrap();
}
pub fn shutdown(&mut self) {
println!("Let's kill QEMU!");
self.process.kill().unwrap();
self.wait();
}
pub fn prepare_workdir(workdir: &str, seed_path: Option<String>) {
Self::clear_workdir(workdir);
let folders = vec![
"/corpus/normal",
"/metadata",
"/corpus/crash",
"/corpus/kasan",
"/corpus/timeout",
"/bitmaps",
"/imports",
"/seeds",
"/snapshot",
"/forced_imports",
];
for folder in folders.iter() {
fs::create_dir_all(format!("{}/{}", workdir, folder))
.expect("couldn't initialize workdir");
}
OpenOptions::new()
.create(true)
.write(true)
.open(format!("{}/filter", workdir))
.unwrap();
OpenOptions::new()
.create(true)
.write(true)
.open(format!("{}/page_cache.lock", workdir))
.unwrap();
OpenOptions::new()
.create(true)
.write(true)
.open(format!("{}/page_cache.dump", workdir))
.unwrap();
OpenOptions::new()
.create(true)
.write(true)
.open(format!("{}/page_cache.addr", workdir))
.unwrap();
OpenOptions::new().create(true).write(true).open(format!("{}/program", workdir)).unwrap();
//println!("IMPORT STUFF FOR {:?}", seed_path);
if let Some(path) = seed_path {
let pattern = format!("{}/*", path);
//println!("IMPORT STUFF FOR {}", pattern);
for (i,p) in glob::glob(&pattern).expect("couldn't glob seed pattern??").enumerate()
{
let src = p.unwrap_or_else(|e| panic!("invalid seed path found {:?}",e));
//println!("import {} to {}/seeds/seed_{}",src.to_string_lossy(), workdir,i);
let dst = format!("{}/seeds/seed_{}.bin",workdir, i);
fs::copy(&src, &dst).unwrap_or_else(|e| panic!("couldn't copy seed {} to {} {:?}",src.to_string_lossy(),dst,e));
}
}
}
fn prepare_redqueen_workdir(workdir: &str, qemu_id: usize) {
//println!("== preparing RQ folder: {}", qemu_id);
fs::create_dir_all(format!("{}/redqueen_workdir_{}", workdir, qemu_id))
.expect("couldn't initialize workdir");
//println!("== preparing RQ folder: {} DONE", qemu_id);
}
fn clear_workdir(workdir: &str) {
let _ = fs::remove_dir_all(workdir);
let project_name = Path::new(workdir)
.file_name()
.expect("Couldn't get project name from workdir!")
.to_str()
.expect("invalid chars in workdir path")
.to_string();
for p in glob::glob(&format!("/dev/shm/kafl_{}_*", project_name)).expect("couldn't glob??")
{
fs::remove_file(p.expect("invalid path found")).unwrap();
}
}
}

View File

@ -0,0 +1,13 @@
// Check hprintf works
// Check bitmap writes work
// Check input data works
// Check crash detection works
// Check timeouts work
// Check snapshot reset memory&regixters works
// Check snapshot reset timer works
// Check snapshot restet hdd works
// Check incremental snapshots work
// Check that all small edit distancem utations are performed in reasonable time
// Check that length extension is performed in reasonable time

4
libnyx/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target
Cargo.lock
app
libnyx.h

16
libnyx/Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "libnyx"
version = "0.1.0"
edition = "2018"
[lib]
name = "libnyx"
crate-type = ["staticlib"]
[build-dependencies]
cbindgen = "0.19"
[dependencies]
config={path="../config"}
fuzz_runner={path="../fuzz_runner"}
libc = "0.2"

29
libnyx/build.rs Normal file
View File

@ -0,0 +1,29 @@
use std::env;
use std::path::PathBuf;
fn main() {
println!("build.rs");
let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")
.expect("CARGO_MANIFEST_DIR env var is not defined"));
println!("CARGO_MANIFEST_DIR: {:?}", crate_dir);
let out_dir = PathBuf::from(env::var("OUT_DIR")
.expect("OUT_DIR env var is not defined"));
println!("OUT_DIR: {:?}", out_dir);
let config = cbindgen::Config::from_file("cbindgen.toml")
.expect("Unable to find cbindgen.toml configuration file");
// OUT_DIR doesn't appear to be configurable, so prolly not a good destination
// cargo +nightly build --out-dir `pwd` -Z unstable-options
// added question to this issue: https://github.com/rust-lang/cargo/issues/6790
// for now, CARGO_MANIFEST_DIR (crate_dir) seems reasonable
cbindgen::generate_with_config(&crate_dir, config)
.unwrap()
.write_to_file(crate_dir.join("libnyx.h"));
}

14
libnyx/cbindgen.toml Normal file
View File

@ -0,0 +1,14 @@
header = "/* This is a very basic example header file */"
include_guard = "libnyx_h"
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
language = "C"
sys_includes = ["stdint.h", "stdbool.h"]
no_includes = true
usize_is_size_t = true
[export.rename]
"str" = "char"
"QemuProcess" = "void"

212
libnyx/src/lib.rs Normal file
View File

@ -0,0 +1,212 @@
extern crate libc;
use config::{Config, FuzzRunnerConfig};
use fuzz_runner::nyx::qemu_process_new_from_kernel;
use fuzz_runner::nyx::qemu_process_new_from_snapshot;
use fuzz_runner::nyx::qemu_process::QemuProcess;
use libc::c_char;
use std::ffi::CStr;
#[repr(C)]
pub enum NyxReturnValue {
Normal,
Crash,
Asan,
Timout,
InvalidWriteToPayload,
Error
}
#[no_mangle]
pub extern "C" fn nyx_new(sharedir: *const c_char, workdir: *const c_char, worker_id: u32, create_snapshot: bool) -> * mut QemuProcess {
let sharedir_c_str = unsafe {
assert!(!sharedir.is_null());
CStr::from_ptr(sharedir)
};
let workdir_c_str = unsafe {
assert!(!workdir.is_null());
CStr::from_ptr(workdir)
};
let sharedir_r_str = sharedir_c_str.to_str().unwrap();
let workdir_r_str = workdir_c_str.to_str().unwrap();
println!("r_str: {}", sharedir_r_str);
let cfg: Config = Config::new_from_sharedir(&sharedir_r_str);
println!("config {}", cfg.fuzz.bitmap_size);
let mut config = cfg.fuzz;
let runner_cfg = cfg.runner;
/* todo: add sanity check */
config.cpu_pin_start_at = worker_id as usize;
config.thread_id = worker_id as usize;
config.threads = if create_snapshot { 2 as usize } else { 1 as usize };
config.workdir_path = format!("{}", workdir_r_str);
let sdir = sharedir_r_str.clone();
if worker_id == 0 {
QemuProcess::prepare_workdir(&config.workdir_path, config.seed_path.clone());
}
match runner_cfg.clone() {
FuzzRunnerConfig::QemuSnapshot(cfg) => {
let runner = qemu_process_new_from_snapshot(sdir.to_string(), &cfg, &config);
return Box::into_raw(Box::new(runner));
}
FuzzRunnerConfig::QemuKernel(cfg) => {
let runner = qemu_process_new_from_kernel(sdir.to_string(), &cfg, &config);
return Box::into_raw(Box::new(runner));
}
}
}
#[no_mangle]
pub extern "C" fn nyx_get_aux_buffer(qemu_process: * mut QemuProcess) -> *mut u8 {
unsafe{
assert!(!qemu_process.is_null());
assert!((qemu_process as usize) % std::mem::align_of::<QemuProcess>() == 0);
//return (*qemu_process).aux.get_raw_ptr();
//return &((*qemu_process).aux.header).as_mut_ptr();
return std::ptr::addr_of!((*qemu_process).aux.header.magic) as *mut u8;
}
}
#[no_mangle]
pub extern "C" fn nyx_get_payload_buffer(qemu_process: * mut QemuProcess) -> *mut u8 {
unsafe{
assert!(!qemu_process.is_null());
assert!((qemu_process as usize) % std::mem::align_of::<QemuProcess>() == 0);
return (*qemu_process).payload.as_mut_ptr();
}
}
#[no_mangle]
pub extern "C" fn nyx_get_bitmap_buffer(qemu_process: * mut QemuProcess) -> *mut u8 {
unsafe{
assert!(!qemu_process.is_null());
assert!((qemu_process as usize) % std::mem::align_of::<QemuProcess>() == 0);
return (*qemu_process).bitmap.as_mut_ptr();
}
}
#[no_mangle]
pub extern "C" fn nyx_shutdown(qemu_process: * mut QemuProcess) {
unsafe{
assert!(!qemu_process.is_null());
assert!((qemu_process as usize) % std::mem::align_of::<QemuProcess>() == 0);
(*qemu_process).shutdown();
}
}
#[no_mangle]
pub extern "C" fn nyx_option_set_reload_mode(qemu_process: * mut QemuProcess, enable: bool) {
unsafe{
assert!(!qemu_process.is_null());
assert!((qemu_process as usize) % std::mem::align_of::<QemuProcess>() == 0);
(*qemu_process).aux.config.reload_mode = if enable {1} else {0};
}
}
#[no_mangle]
pub extern "C" fn nyx_option_set_timeout(qemu_process: * mut QemuProcess, timeout_sec: u8, timeout_usec: u32) {
unsafe{
assert!(!qemu_process.is_null());
assert!((qemu_process as usize) % std::mem::align_of::<QemuProcess>() == 0);
(*qemu_process).aux.config.timeout_sec = timeout_sec;
(*qemu_process).aux.config.timeout_usec = timeout_usec;
}
}
#[no_mangle]
pub extern "C" fn nyx_option_apply(qemu_process: * mut QemuProcess) {
unsafe{
assert!(!qemu_process.is_null());
assert!((qemu_process as usize) % std::mem::align_of::<QemuProcess>() == 0);
(*qemu_process).aux.config.changed = 1;
}
}
#[no_mangle]
pub extern "C" fn nyx_exec(qemu_process: * mut QemuProcess) -> NyxReturnValue {
unsafe{
assert!(!qemu_process.is_null());
assert!((qemu_process as usize) % std::mem::align_of::<QemuProcess>() == 0);
(*qemu_process).send_payload();
if (*qemu_process).aux.result.crash_found != 0 {
return NyxReturnValue::Crash;
}
if (*qemu_process).aux.result.asan_found != 0 {
return NyxReturnValue::Asan;
}
if (*qemu_process).aux.result.timeout_found != 0 {
return NyxReturnValue::Timout;
}
if (*qemu_process).aux.result.payload_write_attempt_found != 0 {
return NyxReturnValue::InvalidWriteToPayload;
}
if (*qemu_process).aux.result.success != 0 {
return NyxReturnValue::Normal;
}
println!("unknown exeuction result!!");
return NyxReturnValue::Error;
}
}
#[no_mangle]
pub extern "C" fn nyx_set_afl_input(qemu_process: * mut QemuProcess, buffer: *mut u8, size: u32) {
unsafe{
assert!(!qemu_process.is_null());
assert!((qemu_process as usize) % std::mem::align_of::<QemuProcess>() == 0);
assert!((buffer as usize) % std::mem::align_of::<u8>() == 0);
std::ptr::copy(&size, (*qemu_process).payload.as_mut_ptr() as *mut u32, 1 as usize);
std::ptr::copy(buffer, (*qemu_process).payload[std::mem::size_of::<u32>()..].as_mut_ptr(), std::cmp::min(size as usize, 0x10000));
}
}
#[no_mangle]
pub extern "C" fn nyx_print_aux_buffer(qemu_process: * mut QemuProcess) {
unsafe{
assert!(!qemu_process.is_null());
assert!((qemu_process as usize) % std::mem::align_of::<QemuProcess>() == 0);
print!("{}", format!("{:#?}", (*qemu_process).aux.result));
if (*qemu_process).aux.result.crash_found != 0 || (*qemu_process).aux.result.asan_found != 0 || (*qemu_process).aux.result.hprintf != 0 {
println!("{}", std::str::from_utf8(&(*qemu_process).aux.misc.data).unwrap());
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
}
}

100
libnyx/test.c Normal file
View File

@ -0,0 +1,100 @@
#include <stdio.h>
#include "libnyx.h"
#include <stdio.h>
#include <ctype.h>
#ifndef HEXDUMP_COLS
#define HEXDUMP_COLS 16
#endif
void hexdump(void *mem, unsigned int len)
{
unsigned int i, j;
for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++)
{
/* print offset */
if(i % HEXDUMP_COLS == 0)
{
printf("0x%06x: ", i);
}
/* print hex data */
if(i < len)
{
printf("%02x ", 0xFF & ((char*)mem)[i]);
}
else /* end of block, just aligning for ASCII dump */
{
printf(" ");
}
/* print ASCII dump */
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1))
{
for(j = i - (HEXDUMP_COLS - 1); j <= i; j++)
{
if(j >= len) /* end of block, not really printing */
{
putchar(' ');
}
else if(isprint(((char*)mem)[j])) /* printable char */
{
putchar(0xFF & ((char*)mem)[j]);
}
else /* other char */
{
putchar('.');
}
}
putchar('\n');
}
}
}
int main(int argc, char** argv){
printf("YO\n");
void* aux_buffer;
void* ptr = nyx_new("/tmp/nyx_bash/");
printf("QEMU Rust Object Pointer: %p\n", ptr);
void* aux = nyx_get_aux_buffer(ptr);
printf("QEMU Rust Aux Pointer: %p\n", aux);
hexdump(aux, 16);
void* payload = nyx_get_payload_buffer(ptr);
nyx_set_afl_input(ptr, "HALLO", 5);
printf("QEMU Rust Payload Pointer: %p\n", payload);
nyx_option_set_reload_mode(ptr, true);
nyx_option_apply(ptr);
hexdump(payload, 16);
printf("About to run init\n");
printf("INIT -> %d\n", nyx_exec(ptr));
printf("Init done\n");
for(int i = 0; i < 32; i++){
nyx_set_afl_input(ptr, "HALLO", 5);
printf("nyx_exec -> %d\n", nyx_exec(ptr));
//nyx_print_aux_buffer(ptr);
}
nyx_shutdown(ptr);
}

1
libnyx/test.sh Normal file
View File

@ -0,0 +1 @@
cargo build && gcc test.c target/debug/liblibnyx.a -o app -pthread -ldl -lrt && ./app

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB