More Nyx hypercalls supported in libafl qemu. add tests for filters. (#2825)
* more nyx hypercalls implemented, among them: - panic hypercall - range filtering hypercall * fixed some nyx hypercalls behavior. * added generic read / write to qemu memory * port linux kernel example to also have nyx API, add better filtering as well. * make nyx api structs volatile to avoid optimization issues * Introduce a method create a Vec in place, using a closure. * use new vec_init function in relevant places. * removed unused unsafe keywork * add more allocated memory r/w callbacks * add more safety notes * move emulator hooks to separate struct * update QEMU version
This commit is contained in:
parent
02566b33cd
commit
d8460d14a2
@ -10,6 +10,8 @@ edition = "2021"
|
||||
[features]
|
||||
shared = ["libafl_qemu/shared"]
|
||||
|
||||
nyx = []
|
||||
|
||||
[profile.release]
|
||||
incremental = true
|
||||
debug = true
|
||||
@ -26,6 +28,7 @@ libafl_qemu = { path = "../../../libafl_qemu", default-features = false, feature
|
||||
] }
|
||||
libafl_targets = { path = "../../../libafl_targets" }
|
||||
env_logger = "0.11.5"
|
||||
log = "0.4.22"
|
||||
|
||||
[build-dependencies]
|
||||
libafl_qemu_build = { path = "../../../libafl_qemu/libafl_qemu_build" }
|
||||
|
@ -1,12 +1,22 @@
|
||||
env_scripts = ['''
|
||||
#!@duckscript
|
||||
profile = get_env PROFILE
|
||||
harness_api = get_env HARNESS_API
|
||||
|
||||
if eq ${profile} "dev"
|
||||
set_env PROFILE_DIR debug
|
||||
else
|
||||
set_env PROFILE_DIR ${profile}
|
||||
end
|
||||
|
||||
if eq ${harness_api} "nyx"
|
||||
set_env FEATURE nyx
|
||||
elseif eq ${harness_api} "lqemu"
|
||||
set_env FEATURE ""
|
||||
else
|
||||
echo "Unknown harness API: ${harness_api}"
|
||||
exit 1
|
||||
end
|
||||
''', '''
|
||||
#!@duckscript
|
||||
runs_on_ci = get_env RUN_ON_CI
|
||||
@ -24,6 +34,7 @@ TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}"
|
||||
LIBAFL_QEMU_CLONE_DIR = { value = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/qemu-libafl-bridge", condition = { env_not_set = [
|
||||
"LIBAFL_QEMU_DIR",
|
||||
] } }
|
||||
HARNESS_API = { value = "lqemu", condition = { env_not_set = ["HARNESS_API"] } }
|
||||
|
||||
LINUX_BUILDER_URL = "git@github.com:AFLplusplus/linux-qemu-image-builder.git"
|
||||
LINUX_BUILDER_DIR = { value = "${TARGET_DIR}/linux_builder", condition = { env_not_set = [
|
||||
@ -85,7 +96,15 @@ ${LINUX_BUILDER_DIR}/update.sh
|
||||
[tasks.build]
|
||||
dependencies = ["target_dir"]
|
||||
command = "cargo"
|
||||
args = ["build", "--profile", "${PROFILE}", "--target-dir", "${TARGET_DIR}"]
|
||||
args = [
|
||||
"build",
|
||||
"--profile",
|
||||
"${PROFILE}",
|
||||
"--target-dir",
|
||||
"${TARGET_DIR}",
|
||||
"--features",
|
||||
"${FEATURE}",
|
||||
]
|
||||
|
||||
[tasks.run]
|
||||
dependencies = ["build"]
|
||||
@ -100,18 +119,18 @@ else
|
||||
LIBAFL_QEMU_BIOS_DIR=${LIBAFL_QEMU_CLONE_DIR}/build/qemu-bundle/usr/local/share/qemu
|
||||
fi
|
||||
|
||||
cp ${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd ${LINUX_BUILDER_OUT}/OVMF_CODE.fd.clone
|
||||
cp ${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd ${LINUX_BUILDER_OUT}/OVMF_VARS.fd.clone
|
||||
cp ${LINUX_BUILDER_OUT}/linux.qcow2 ${LINUX_BUILDER_OUT}/linux.qcow2.clone
|
||||
qemu-img create -f qcow2 -o backing_file=${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd -F raw ${LINUX_BUILDER_OUT}/OVMF_CODE.4m.qcow2
|
||||
qemu-img create -f qcow2 -o backing_file=${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd -F raw ${LINUX_BUILDER_OUT}/OVMF_VARS.4m.qcow2
|
||||
qemu-img create -f qcow2 -o backing_file=${LINUX_BUILDER_OUT}/linux.qcow2 -F qcow2 ${LINUX_BUILDER_OUT}/linux.tmp.qcow2
|
||||
|
||||
${TARGET_DIR}/${PROFILE_DIR}/qemu_linux_kernel \
|
||||
-accel tcg \
|
||||
-m 4G \
|
||||
-drive if=pflash,format=raw,file="${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd" `# OVMF code pflash` \
|
||||
-drive if=pflash,format=raw,file="${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd" `# OVMF vars pflash` \
|
||||
-device virtio-scsi-pci,id=scsi0 `# SCSI bus` \
|
||||
-device scsi-hd,bus=scsi0.0,drive=disk,id=virtio-disk0,bootindex=1 \
|
||||
-blockdev driver=file,filename="${LINUX_BUILDER_OUT}/linux.qcow2",node-name=storage `# Backend file of "disk"` \
|
||||
-drive if=pflash,format=qcow2,file="${LINUX_BUILDER_OUT}/OVMF_CODE.4m.qcow2" `# OVMF code pflash` \
|
||||
-drive if=pflash,format=qcow2,file="${LINUX_BUILDER_OUT}/OVMF_VARS.4m.qcow2" `# OVMF vars pflash` \
|
||||
-device ahci,id=ahci,bus=pci.0,addr=4 \
|
||||
-device ide-hd,bus=ahci.0,drive=disk,bootindex=1 \
|
||||
-blockdev driver=file,filename="${LINUX_BUILDER_OUT}/linux.tmp.qcow2",node-name=storage `# Backend file of "disk"` \
|
||||
-blockdev driver=qcow2,file=storage,node-name=disk `# QCOW2 "disk"` \
|
||||
-L "${LIBAFL_QEMU_BIOS_DIR}" \
|
||||
-nographic \
|
||||
|
@ -1,7 +1,12 @@
|
||||
obj-m += harness.o
|
||||
# harness-objs += symfinder.o
|
||||
|
||||
all:
|
||||
make -C /lib/modules/$(LINUX_MODULES)/build M=$(PWD) modules
|
||||
make EXTRA_CFLAGS="-DUSE_LQEMU=1" -C /lib/modules/$(LINUX_MODULES)/build M=$(PWD) modules
|
||||
gcc -Wall -Werror -o user user.c
|
||||
|
||||
nyx:
|
||||
make EXTRA_CFLAGS="-DUSE_NYX=1" -C /lib/modules/$(LINUX_MODULES)/build M=$(PWD) modules
|
||||
gcc -Wall -Werror -o user user.c
|
||||
|
||||
clean:
|
||||
|
@ -11,11 +11,14 @@
|
||||
#include <linux/kallsyms.h>
|
||||
|
||||
#include "x509-parser.h"
|
||||
|
||||
#if defined(USE_LQEMU)
|
||||
#include "libafl_qemu.h"
|
||||
#elif defined(USE_NYX)
|
||||
#include "nyx_api.h"
|
||||
#endif
|
||||
|
||||
#define MAX_DEV 1
|
||||
|
||||
#define BUF_SIZE 4096
|
||||
#define PAYLOAD_MAX_SIZE 65536
|
||||
|
||||
static int harness_open(struct inode *inode, struct file *file);
|
||||
static int harness_release(struct inode *inode, struct file *file);
|
||||
@ -37,13 +40,16 @@ static struct mychar_device_data harness_data;
|
||||
#define KPROBE_PRE_HANDLER(fname) \
|
||||
static int __kprobes fname(struct kprobe *p, struct pt_regs *regs)
|
||||
|
||||
long unsigned int kln_addr = 0;
|
||||
unsigned long (*kln_pointer)(const char *name) = NULL;
|
||||
// kallsyms_lookup_name function address
|
||||
static long unsigned int kallsyms_lookup_name_addr = 0;
|
||||
|
||||
// kallsyms_lookup_name function
|
||||
static unsigned long (*kall_syms_lookup_name_fn)(const char *name) = NULL;
|
||||
|
||||
static struct kprobe kp0, kp1;
|
||||
|
||||
KPROBE_PRE_HANDLER(handler_pre0) {
|
||||
kln_addr = (--regs->ip);
|
||||
kallsyms_lookup_name_addr = (--regs->ip);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -89,11 +95,16 @@ static int harness_find_kallsyms_lookup(void) {
|
||||
unregister_kprobe(&kp0);
|
||||
unregister_kprobe(&kp1);
|
||||
|
||||
lqprintf("kallsyms_lookup_name address = 0x%lx\n", kln_addr);
|
||||
#ifdef USE_NYX
|
||||
hprintf("kallsyms_lookup_name address = 0x%lx\n", kallsyms_lookup_name_addr);
|
||||
#elif DEFINED(USE_LQEMU)
|
||||
lqprintf("kallsyms_lookup_name address = 0x%lx\n", kallsyms_lookup_name_addr);
|
||||
#endif
|
||||
|
||||
if (kln_addr == 0) { return -1; }
|
||||
if (kallsyms_lookup_name_addr == 0) { return -1; }
|
||||
|
||||
kln_pointer = (unsigned long (*)(const char *name))kln_addr;
|
||||
kall_syms_lookup_name_fn =
|
||||
(unsigned long (*)(const char *name))kallsyms_lookup_name_addr;
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -104,6 +115,104 @@ static int harness_uevent(const struct device *dev,
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef USE_NYX
|
||||
/**
|
||||
* Allocate page-aligned memory
|
||||
*/
|
||||
static void *malloc_resident_pages(size_t num_pages) {
|
||||
size_t data_size = PAGE_SIZE * num_pages;
|
||||
void *ptr = NULL;
|
||||
|
||||
if ((ptr = kzalloc(data_size, GFP_KERNEL)) == NULL) {
|
||||
printk("Allocation failure\n");
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
// ensure pages are aligned and resident
|
||||
memset(ptr, 0x42, data_size);
|
||||
// if (mlock(ptr, data_size) == -1) {
|
||||
// printk("Error locking scratch buffer\n");
|
||||
// goto err_out;
|
||||
// }
|
||||
|
||||
// assert(((uintptr_t)ptr % PAGE_SIZE) == 0);
|
||||
return ptr;
|
||||
err_out:
|
||||
// free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static volatile __attribute__((aligned(PAGE_SIZE))) uint64_t range_args[3];
|
||||
|
||||
static void hrange_submit(unsigned id, unsigned long start, unsigned long end) {
|
||||
range_args[0] = start;
|
||||
range_args[1] = end;
|
||||
range_args[2] = id;
|
||||
|
||||
kAFL_hypercall(HYPERCALL_KAFL_RANGE_SUBMIT, (unsigned long)range_args);
|
||||
}
|
||||
|
||||
static int agent_init(int verbose) {
|
||||
host_config_t host_config;
|
||||
|
||||
hprintf("Nyx agent init");
|
||||
|
||||
// set ready state
|
||||
kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0);
|
||||
kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
|
||||
|
||||
kAFL_hypercall(HYPERCALL_KAFL_GET_HOST_CONFIG, (uintptr_t)&host_config);
|
||||
|
||||
if (verbose) {
|
||||
printk("GET_HOST_CONFIG\n");
|
||||
printk("\thost magic: 0x%x, version: 0x%x\n", host_config.host_magic,
|
||||
host_config.host_version);
|
||||
printk("\tbitmap size: 0x%x, ijon: 0x%x\n", host_config.bitmap_size,
|
||||
host_config.ijon_bitmap_size);
|
||||
printk("\tpayload size: %u KB\n", host_config.payload_buffer_size / 1024);
|
||||
printk("\tworker id: %d\n", host_config.worker_id);
|
||||
}
|
||||
|
||||
if (host_config.host_magic != NYX_HOST_MAGIC) {
|
||||
hprintf("HOST_MAGIC mismatch: %08x != %08x", host_config.host_magic,
|
||||
NYX_HOST_MAGIC);
|
||||
habort("HOST_MAGIC mismatch!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (host_config.host_version != NYX_HOST_VERSION) {
|
||||
hprintf("HOST_VERSION mismatch: %08x != %08x\n", host_config.host_version,
|
||||
NYX_HOST_VERSION);
|
||||
habort("HOST_VERSION mismatch!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (host_config.payload_buffer_size > PAYLOAD_MAX_SIZE) {
|
||||
hprintf("Fuzzer payload size too large: %lu > %lu\n",
|
||||
host_config.payload_buffer_size, PAYLOAD_MAX_SIZE);
|
||||
habort("Host payload size too large!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
agent_config_t agent_config = {0};
|
||||
agent_config.agent_magic = NYX_AGENT_MAGIC;
|
||||
agent_config.agent_version = NYX_AGENT_VERSION;
|
||||
// agent_config.agent_timeout_detection = 0; // timeout by host
|
||||
// agent_config.agent_tracing = 0; // trace by host
|
||||
// agent_config.agent_ijon_tracing = 0; // no IJON
|
||||
agent_config.agent_non_reload_mode = 0; // no persistent mode
|
||||
// agent_config.trace_buffer_vaddr = 0xdeadbeef;
|
||||
// agent_config.ijon_trace_buffer_vaddr = 0xdeadbeef;
|
||||
agent_config.coverage_bitmap_size = host_config.bitmap_size;
|
||||
// agent_config.input_buffer_size;
|
||||
// agent_config.dump_payloads; // set by hypervisor (??)
|
||||
|
||||
kAFL_hypercall(HYPERCALL_KAFL_SET_AGENT_CONFIG, (uintptr_t)&agent_config);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int __init harness_init(void) {
|
||||
int err;
|
||||
dev_t dev;
|
||||
@ -126,7 +235,10 @@ static int __init harness_init(void) {
|
||||
|
||||
err = harness_find_kallsyms_lookup();
|
||||
|
||||
if (err < 0) { return err; }
|
||||
if (err < 0) {
|
||||
habort("error while trying to find kallsyms");
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -141,35 +253,91 @@ static void __exit harness_exit(void) {
|
||||
}
|
||||
|
||||
static int harness_open(struct inode *inode, struct file *file) {
|
||||
unsigned long x509_fn_addr = kall_syms_lookup_name_fn("x509_cert_parse");
|
||||
unsigned long asn1_ber_decoder_addr =
|
||||
kall_syms_lookup_name_fn("asn1_ber_decoder");
|
||||
unsigned long x509_get_sig_params_addr =
|
||||
kall_syms_lookup_name_fn("x509_get_sig_params");
|
||||
|
||||
// hprintf("action 0: %p", x509_decoder.actions[0]);
|
||||
|
||||
#if defined(USE_LQEMU)
|
||||
lqprintf("harness: Device open\n");
|
||||
|
||||
char *data = kzalloc(BUF_SIZE, GFP_KERNEL);
|
||||
data[0] = 0xff; // init page
|
||||
|
||||
unsigned long x509_fn_addr = kln_pointer("x509_cert_parse");
|
||||
lqprintf("harness: x509 fn addr: 0x%lx\n", x509_fn_addr);
|
||||
|
||||
if (x509_fn_addr == 0) {
|
||||
lqprintf("harness: Error: x509 function not found.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// TODO: better filtering...
|
||||
libafl_qemu_trace_vaddr_size(x509_fn_addr, 0x1000);
|
||||
|
||||
libafl_qemu_test();
|
||||
|
||||
u64 buf_size = libafl_qemu_start_virt(data, BUF_SIZE);
|
||||
char *input_buf = kzalloc(PAYLOAD_MAX_SIZE, GFP_KERNEL);
|
||||
input_buf[0] = 0xff; // init page
|
||||
|
||||
struct x509_certificate *cert_ret = x509_cert_parse(data, buf_size);
|
||||
#elif defined(USE_NYX)
|
||||
hprintf("harness: Device open.");
|
||||
hprintf("\tx509_cert_parse: %p", x509_fn_addr);
|
||||
hprintf("\tasn1_ber_decoder: %p", asn1_ber_decoder_addr);
|
||||
hprintf("\tx509_get_sig_params: %p", x509_get_sig_params_addr);
|
||||
|
||||
if (!x509_fn_addr || !asn1_ber_decoder_addr) { habort("Invalid ranges"); }
|
||||
|
||||
kAFL_payload *pbuf = malloc_resident_pages(PAYLOAD_MAX_SIZE / PAGE_SIZE);
|
||||
|
||||
agent_init(1);
|
||||
|
||||
// kAFL_hypercall(HYPERCALL_KAFL_SUBMIT_CR3, 0);
|
||||
hrange_submit(0, x509_fn_addr, x509_fn_addr + 0x1000);
|
||||
hrange_submit(1, asn1_ber_decoder_addr, asn1_ber_decoder_addr + 0x1000);
|
||||
hrange_submit(2, x509_get_sig_params_addr, x509_get_sig_params_addr + 0x1000);
|
||||
|
||||
kAFL_hypercall(HYPERCALL_KAFL_GET_PAYLOAD, (uintptr_t)pbuf);
|
||||
|
||||
hprintf("payload size addr: %p", &pbuf->size);
|
||||
hprintf("payload addr: %p", &pbuf->data);
|
||||
|
||||
#else
|
||||
#error No API specified.
|
||||
#endif
|
||||
|
||||
// int ret;
|
||||
// uintptr_t start_addr = 0, end_addr = 0;
|
||||
|
||||
// ret = lqemu_symfinder_widen_range("x509_cert_parse", &start_addr,
|
||||
// &end_addr); if (ret) {
|
||||
// printk("error while handling range");
|
||||
// return ret;
|
||||
// }
|
||||
|
||||
while (true) {
|
||||
#if defined(USE_LQEMU)
|
||||
uint8_t *data = input_buf;
|
||||
size_t size = libafl_qemu_start_virt(data, PAYLOAD_MAX_SIZE);
|
||||
#elif defined(USE_NYX)
|
||||
kAFL_hypercall(HYPERCALL_KAFL_NEXT_PAYLOAD, 0);
|
||||
kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0);
|
||||
|
||||
size_t size = pbuf->size;
|
||||
const volatile uint8_t *data = pbuf->data;
|
||||
#endif
|
||||
|
||||
__maybe_unused struct x509_certificate *cert_ret =
|
||||
x509_cert_parse((const void *)data, size);
|
||||
|
||||
#if defined(USE_LQEMU)
|
||||
libafl_qemu_end(LIBAFL_QEMU_END_OK);
|
||||
#elif defined(USE_NYX)
|
||||
kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int harness_release(struct inode *inode, struct file *file) {
|
||||
#if defined(USE_LQEMU)
|
||||
lqprintf("harness: Device close\n");
|
||||
#elif defined(USE_NYX)
|
||||
hprintf("harness: Device close");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,10 @@
|
||||
LINUX_MODULES=$(pacman -Ql linux-headers | grep -m 1 -E '/usr/lib/modules/[^/]*/' | sed 's|.*/usr/lib/modules/\([^/]*\)/.*|\1|')
|
||||
export LINUX_MODULES
|
||||
|
||||
# Default root password
|
||||
echo "root:toor" | chpasswd
|
||||
|
||||
cd /setup
|
||||
|
||||
make clean
|
||||
make -j
|
||||
make -j nyx
|
@ -7,6 +7,9 @@
|
||||
|
||||
// From https://lore.kernel.org/kvm/20240304065703.GA24373@wunner.de/T/
|
||||
|
||||
#ifndef X509_PARSER_H
|
||||
#define X509_PARSER_H
|
||||
|
||||
#include <crypto/public_key.h>
|
||||
#include <keys/asymmetric-type.h>
|
||||
#include <linux/time64.h>
|
||||
@ -44,3 +47,5 @@ struct x509_certificate {
|
||||
|
||||
struct x509_certificate *x509_cert_parse(const void *data, size_t datalen);
|
||||
void x509_free_certificate(struct x509_certificate *cert);
|
||||
|
||||
#endif
|
@ -3,6 +3,8 @@
|
||||
use core::time::Duration;
|
||||
use std::{env, path::PathBuf, process};
|
||||
|
||||
#[cfg(not(feature = "nyx"))]
|
||||
use libafl::state::{HasExecutions, State};
|
||||
use libafl::{
|
||||
corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus},
|
||||
events::{launcher::Launcher, EventConfig},
|
||||
@ -10,7 +12,7 @@ use libafl::{
|
||||
feedback_or, feedback_or_fast,
|
||||
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
|
||||
fuzzer::{Fuzzer, StdFuzzer},
|
||||
inputs::BytesInput,
|
||||
inputs::{BytesInput, HasTargetBytes, UsesInput},
|
||||
monitors::MultiMonitor,
|
||||
mutators::{havoc_mutations, scheduled::StdScheduledMutator, I2SRandReplaceBinonly},
|
||||
observers::{CanTrack, HitcountsMapObserver, TimeObserver, VariableMapObserver},
|
||||
@ -27,14 +29,82 @@ use libafl_bolts::{
|
||||
shmem::{ShMemProvider, StdShMemProvider},
|
||||
tuples::tuple_list,
|
||||
};
|
||||
#[cfg(feature = "nyx")]
|
||||
use libafl_qemu::{command::nyx::NyxCommandManager, NyxEmulatorDriver};
|
||||
#[cfg(not(feature = "nyx"))]
|
||||
use libafl_qemu::{command::StdCommandManager, StdEmulatorDriver};
|
||||
use libafl_qemu::{
|
||||
emu::Emulator,
|
||||
executor::QemuExecutor,
|
||||
modules::{cmplog::CmpLogObserver, edges::StdEdgeCoverageClassicModule, CmpLogModule},
|
||||
// StdEmulatorDriver
|
||||
modules::{
|
||||
cmplog::CmpLogObserver, edges::StdEdgeCoverageClassicModule, CmpLogModule,
|
||||
EmulatorModuleTuple,
|
||||
},
|
||||
FastSnapshotManager, NopSnapshotManager, QemuInitError,
|
||||
};
|
||||
use libafl_targets::{edges_map_mut_ptr, EDGES_MAP_DEFAULT_SIZE, MAX_EDGES_FOUND};
|
||||
|
||||
#[cfg(feature = "nyx")]
|
||||
fn get_emulator<ET, S>(
|
||||
args: Vec<String>,
|
||||
modules: ET,
|
||||
) -> Result<
|
||||
Emulator<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, NopSnapshotManager>,
|
||||
QemuInitError,
|
||||
>
|
||||
where
|
||||
ET: EmulatorModuleTuple<S>,
|
||||
S: UsesInput + Unpin,
|
||||
<S as UsesInput>::Input: HasTargetBytes,
|
||||
{
|
||||
Emulator::empty()
|
||||
.qemu_parameters(args)
|
||||
.modules(modules)
|
||||
.driver(NyxEmulatorDriver::builder().build())
|
||||
.command_manager(NyxCommandManager::default())
|
||||
.snapshot_manager(NopSnapshotManager::default())
|
||||
.build()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nyx"))]
|
||||
fn get_emulator<ET, S>(
|
||||
args: Vec<String>,
|
||||
mut modules: ET,
|
||||
) -> Result<
|
||||
Emulator<StdCommandManager<S>, StdEmulatorDriver, ET, S, FastSnapshotManager>,
|
||||
QemuInitError,
|
||||
>
|
||||
where
|
||||
ET: EmulatorModuleTuple<S>,
|
||||
S: State + HasExecutions + Unpin,
|
||||
<S as UsesInput>::Input: HasTargetBytes,
|
||||
{
|
||||
// Allow linux process address space addresses as feedback
|
||||
modules.allow_address_range_all(LINUX_PROCESS_ADDRESS_RANGE);
|
||||
|
||||
Emulator::builder()
|
||||
.qemu_parameters(args)
|
||||
.modules(modules)
|
||||
.build()
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn display_args() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
let mut arg_str = String::new();
|
||||
for arg in args {
|
||||
arg_str.push_str(&arg);
|
||||
arg_str.push_str(" \\\n\t");
|
||||
}
|
||||
arg_str.pop();
|
||||
arg_str.pop();
|
||||
arg_str.pop();
|
||||
|
||||
log::info!("QEMU args:");
|
||||
log::info!("\n{arg_str}");
|
||||
}
|
||||
|
||||
pub fn fuzz() {
|
||||
env_logger::init();
|
||||
|
||||
@ -70,10 +140,7 @@ pub fn fuzz() {
|
||||
CmpLogModule::default(),
|
||||
);
|
||||
|
||||
let emu = Emulator::builder()
|
||||
.qemu_parameters(args)
|
||||
.modules(modules)
|
||||
.build()?;
|
||||
let emu = get_emulator(args, modules)?;
|
||||
|
||||
let devices = emu.list_devices();
|
||||
println!("Devices = {:?}", devices);
|
||||
|
@ -27,6 +27,7 @@ libafl_qemu = { path = "../../../libafl_qemu", default-features = false, feature
|
||||
] }
|
||||
env_logger = "0.11.5"
|
||||
libafl_targets = { path = "../../../libafl_targets" }
|
||||
log = "0.4.22"
|
||||
|
||||
[build-dependencies]
|
||||
libafl_qemu_build = { path = "../../../libafl_qemu/libafl_qemu_build" }
|
||||
|
@ -11,8 +11,11 @@ end
|
||||
|
||||
if eq ${harness_api} "nyx"
|
||||
set_env FEATURE nyx
|
||||
else
|
||||
elseif eq ${harness_api} "lqemu"
|
||||
set_env FEATURE ""
|
||||
else
|
||||
echo "Unknown harness API: ${harness_api}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
''', '''
|
||||
@ -145,18 +148,18 @@ else
|
||||
LIBAFL_QEMU_BIOS_DIR=${LIBAFL_QEMU_CLONE_DIR}/build/qemu-bundle/usr/local/share/qemu
|
||||
fi
|
||||
|
||||
cp ${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd ${LINUX_BUILDER_OUT}/OVMF_CODE.fd.clone
|
||||
cp ${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd ${LINUX_BUILDER_OUT}/OVMF_VARS.fd.clone
|
||||
cp ${LINUX_BUILDER_OUT}/linux.qcow2 ${LINUX_BUILDER_OUT}/linux.qcow2.clone
|
||||
qemu-img create -f qcow2 -o backing_file=${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd -F raw ${LINUX_BUILDER_OUT}/OVMF_CODE.4m.qcow2
|
||||
qemu-img create -f qcow2 -o backing_file=${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd -F raw ${LINUX_BUILDER_OUT}/OVMF_VARS.4m.qcow2
|
||||
qemu-img create -f qcow2 -o backing_file=${LINUX_BUILDER_OUT}/linux.qcow2 -F qcow2 ${LINUX_BUILDER_OUT}/linux.tmp.qcow2
|
||||
|
||||
${TARGET_DIR}/${PROFILE_DIR}/qemu_linux_process \
|
||||
-accel tcg \
|
||||
-m 4G \
|
||||
-drive if=pflash,format=raw,file="${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd" `# OVMF code pflash` \
|
||||
-drive if=pflash,format=raw,file="${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd" `# OVMF vars pflash` \
|
||||
-device virtio-scsi-pci,id=scsi0 `# SCSI bus` \
|
||||
-device scsi-hd,bus=scsi0.0,drive=disk,id=virtio-disk0,bootindex=1 \
|
||||
-blockdev driver=file,filename="${LINUX_BUILDER_OUT}/linux.qcow2",node-name=storage `# Backend file of "disk"` \
|
||||
-drive if=pflash,format=qcow2,file="${LINUX_BUILDER_OUT}/OVMF_CODE.4m.qcow2" `# OVMF code pflash` \
|
||||
-drive if=pflash,format=qcow2,file="${LINUX_BUILDER_OUT}/OVMF_VARS.4m.qcow2" `# OVMF vars pflash` \
|
||||
-device ahci,id=ahci,bus=pci.0,addr=4 \
|
||||
-device ide-hd,bus=ahci.0,drive=disk,bootindex=1 \
|
||||
-blockdev driver=file,filename="${LINUX_BUILDER_OUT}/linux.tmp.qcow2",node-name=storage `# Backend file of "disk"` \
|
||||
-blockdev driver=qcow2,file=storage,node-name=disk `# QCOW2 "disk"` \
|
||||
-L "${LIBAFL_QEMU_BIOS_DIR}" \
|
||||
-nographic \
|
||||
|
@ -55,9 +55,22 @@ err_out:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void hrange_submit(unsigned id, uintptr_t start, uintptr_t end) {
|
||||
uint64_t range_arg[3] __attribute__((aligned(PAGE_SIZE)));
|
||||
memset(range_arg, 0, sizeof(range_arg));
|
||||
|
||||
range_arg[0] = start;
|
||||
range_arg[1] = end;
|
||||
range_arg[2] = id;
|
||||
|
||||
kAFL_hypercall(HYPERCALL_KAFL_RANGE_SUBMIT, (uintptr_t)range_arg);
|
||||
}
|
||||
|
||||
int agent_init(int verbose) {
|
||||
host_config_t host_config;
|
||||
|
||||
hprintf("Nyx agent init");
|
||||
|
||||
// set ready state
|
||||
kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0);
|
||||
kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
|
||||
@ -120,18 +133,25 @@ int main() {
|
||||
|
||||
agent_init(1);
|
||||
|
||||
kAFL_hypercall(HYPERCALL_KAFL_SUBMIT_CR3, 0);
|
||||
hrange_submit(0, 0x0, 0x00007fffffffffff);
|
||||
|
||||
kAFL_hypercall(HYPERCALL_KAFL_GET_PAYLOAD, (uint64_t)pbuf);
|
||||
|
||||
hprintf("payload size addr: %p", &pbuf->size);
|
||||
hprintf("payload addr: %p", &pbuf->data);
|
||||
|
||||
while (true) {
|
||||
kAFL_hypercall(HYPERCALL_KAFL_NEXT_PAYLOAD, 0);
|
||||
kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0);
|
||||
|
||||
// Call the target
|
||||
bool ret = FuzzMe(pbuf->data, pbuf->size);
|
||||
|
||||
kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
|
||||
if (ret) { kAFL_hypercall(HYPERCALL_KAFL_PANIC, 0); }
|
||||
|
||||
habort("Error: post-release code has been triggered.");
|
||||
kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
|
||||
}
|
||||
|
||||
habort("post-release code has been triggered. Snapshot error?");
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Nothing to do
|
||||
echo 'root:toor' | sudo chpasswd
|
@ -8,8 +8,8 @@ use libafl::state::{HasExecutions, State};
|
||||
use libafl::{
|
||||
corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus},
|
||||
events::{launcher::Launcher, EventConfig},
|
||||
executors::ShadowExecutor,
|
||||
feedback_or, feedback_or_fast,
|
||||
executors::{ExitKind, ShadowExecutor},
|
||||
feedback_and_fast, feedback_or, feedback_or_fast,
|
||||
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
|
||||
fuzzer::{Fuzzer, StdFuzzer},
|
||||
inputs::{BytesInput, HasTargetBytes, UsesInput},
|
||||
@ -37,10 +37,11 @@ use libafl_qemu::{
|
||||
emu::Emulator,
|
||||
executor::QemuExecutor,
|
||||
modules::{
|
||||
cmplog::CmpLogObserver, edges::StdEdgeCoverageClassicModule, CmpLogModule,
|
||||
EmulatorModuleTuple,
|
||||
cmplog::CmpLogObserver, edges::StdEdgeCoverageClassicModule,
|
||||
utils::filters::LINUX_PROCESS_ADDRESS_RANGE, CmpLogModule, EmulatorModuleTuple,
|
||||
},
|
||||
FastSnapshotManager, QemuInitError,
|
||||
EmulatorDriverError, FastSnapshotManager, NopSnapshotManager, QemuInitError,
|
||||
QemuSnapshotManager,
|
||||
};
|
||||
use libafl_targets::{edges_map_mut_ptr, EDGES_MAP_DEFAULT_SIZE, MAX_EDGES_FOUND};
|
||||
|
||||
@ -49,7 +50,7 @@ fn get_emulator<ET, S>(
|
||||
args: Vec<String>,
|
||||
modules: ET,
|
||||
) -> Result<
|
||||
Emulator<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, FastSnapshotManager>,
|
||||
Emulator<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, NopSnapshotManager>,
|
||||
QemuInitError,
|
||||
>
|
||||
where
|
||||
@ -60,21 +61,16 @@ where
|
||||
Emulator::empty()
|
||||
.qemu_parameters(args)
|
||||
.modules(modules)
|
||||
.driver(
|
||||
NyxEmulatorDriver::builder()
|
||||
.allow_page_on_start(true)
|
||||
.process_only(true)
|
||||
.build(),
|
||||
)
|
||||
.driver(NyxEmulatorDriver::builder().build())
|
||||
.command_manager(NyxCommandManager::default())
|
||||
.snapshot_manager(FastSnapshotManager::default())
|
||||
.snapshot_manager(NopSnapshotManager::default())
|
||||
.build()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nyx"))]
|
||||
fn get_emulator<ET, S>(
|
||||
args: Vec<String>,
|
||||
modules: ET,
|
||||
mut modules: ET,
|
||||
) -> Result<
|
||||
Emulator<StdCommandManager<S>, StdEmulatorDriver, ET, S, FastSnapshotManager>,
|
||||
QemuInitError,
|
||||
@ -84,7 +80,29 @@ where
|
||||
S: State + HasExecutions + Unpin,
|
||||
<S as UsesInput>::Input: HasTargetBytes,
|
||||
{
|
||||
Emulator::builder().qemu_cli(args).modules(modules).build()
|
||||
// Allow linux process address space addresses as feedback
|
||||
modules.allow_address_range_all(LINUX_PROCESS_ADDRESS_RANGE);
|
||||
|
||||
Emulator::builder()
|
||||
.qemu_parameters(args)
|
||||
.modules(modules)
|
||||
.build()
|
||||
}
|
||||
|
||||
fn display_args() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
let mut arg_str = String::new();
|
||||
for arg in args {
|
||||
arg_str.push_str(&arg);
|
||||
arg_str.push_str(" \\\n\t");
|
||||
}
|
||||
arg_str.pop();
|
||||
arg_str.pop();
|
||||
arg_str.pop();
|
||||
|
||||
log::info!("QEMU args:");
|
||||
log::info!("\n{arg_str}");
|
||||
}
|
||||
|
||||
pub fn fuzz() {
|
||||
@ -94,14 +112,16 @@ pub fn fuzz() {
|
||||
str::parse::<usize>(&s).expect("FUZZ_SIZE was not a number");
|
||||
};
|
||||
// Hardcoded parameters
|
||||
let timeout = Duration::from_secs(99999999);
|
||||
let timeout = Duration::from_secs(50);
|
||||
let broker_port = 1338;
|
||||
let cores = Cores::from_cmdline("1").unwrap();
|
||||
let corpus_dirs = [PathBuf::from("./corpus")];
|
||||
let objective_dir = PathBuf::from("./crashes");
|
||||
|
||||
display_args();
|
||||
|
||||
let mut run_client = |state: Option<_>, mut mgr, _client_description| {
|
||||
// Initialize QEMU
|
||||
// Fetch QEMU args from program's arguments
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
// Create an observation channel using the coverage map
|
||||
@ -127,7 +147,12 @@ pub fn fuzz() {
|
||||
// The wrapped harness function, calling out to the LLVM-style harness
|
||||
let mut harness =
|
||||
|emulator: &mut Emulator<_, _, _, _, _>, state: &mut _, input: &BytesInput| unsafe {
|
||||
emulator.run(state, input).unwrap().try_into().unwrap()
|
||||
match emulator.run(state, input) {
|
||||
Ok(res) => res.try_into().unwrap(),
|
||||
Err(e) => match e {
|
||||
_ => panic!("{e:?}"),
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// Create an observation channel to keep track of the execution time
|
||||
@ -145,8 +170,13 @@ pub fn fuzz() {
|
||||
TimeFeedback::new(&time_observer)
|
||||
);
|
||||
|
||||
let map_feedback = MaxMapFeedback::new(&edges_observer);
|
||||
|
||||
// A feedback to choose if an input is a solution or not
|
||||
let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
|
||||
let mut objective = feedback_and_fast!(
|
||||
feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()),
|
||||
map_feedback,
|
||||
);
|
||||
|
||||
// If not restarting, create a State from scratch
|
||||
let mut state = state.unwrap_or_else(|| {
|
||||
|
@ -1310,6 +1310,27 @@ pub mod pybind {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`Vec`] of the given type with `nb_elts` elements, initialized in place.
|
||||
/// The closure must initialize [`Vec`] (of size `nb_elts` * `sizeo_of::<T>()`).
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The input closure should fully initialize the new [`Vec`], not leaving any uninitialized bytes.
|
||||
// TODO: Use MaybeUninit API at some point.
|
||||
#[cfg(feature = "alloc")]
|
||||
#[expect(clippy::uninit_vec)]
|
||||
pub unsafe fn vec_init<E, F, T>(nb_elts: usize, init_fn: F) -> Result<Vec<T>, E>
|
||||
where
|
||||
F: FnOnce(&mut Vec<T>) -> Result<(), E>,
|
||||
{
|
||||
let mut new_vec: Vec<T> = Vec::with_capacity(nb_elts);
|
||||
new_vec.set_len(nb_elts);
|
||||
|
||||
init_fn(&mut new_vec)?;
|
||||
|
||||
Ok(new_vec)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
@ -11,7 +11,7 @@ use crate::cargo_add_rpath;
|
||||
|
||||
pub const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
|
||||
pub const QEMU_DIRNAME: &str = "qemu-libafl-bridge";
|
||||
pub const QEMU_REVISION: &str = "ace364678aef879514e150e626695c4793db41e9";
|
||||
pub const QEMU_REVISION: &str = "2b5e4bfcff875571b2813a9494de8b2e4c56120e";
|
||||
|
||||
pub struct BuildResult {
|
||||
pub qemu_path: PathBuf,
|
||||
|
@ -96,12 +96,12 @@
|
||||
#define KAFL_MODE_32 1
|
||||
#define KAFL_MODE_16 2
|
||||
|
||||
typedef struct {
|
||||
typedef volatile struct {
|
||||
int32_t size;
|
||||
uint8_t data[];
|
||||
} kAFL_payload;
|
||||
|
||||
typedef struct {
|
||||
typedef volatile struct {
|
||||
uint64_t ip[4];
|
||||
uint64_t size[4];
|
||||
uint8_t enabled[4];
|
||||
@ -154,7 +154,7 @@ typedef struct {
|
||||
/* more to come */
|
||||
} __attribute__((packed)) host_config_t;
|
||||
|
||||
typedef struct {
|
||||
typedef volatile struct {
|
||||
uint32_t agent_magic;
|
||||
uint32_t agent_version;
|
||||
uint8_t agent_timeout_detection;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::{mem::size_of, ops::Range, sync::OnceLock};
|
||||
use std::{mem::size_of, sync::OnceLock};
|
||||
|
||||
use capstone::arch::BuildsCapstone;
|
||||
use enum_map::{enum_map, EnumMap};
|
||||
@ -65,8 +65,6 @@ pub fn capstone() -> capstone::arch::x86::ArchCapstoneBuilder {
|
||||
|
||||
pub type GuestReg = u64;
|
||||
|
||||
pub const PROCESS_ADDRESS_RANGE: Range<u64> = 0..0x0000_7fff_ffff_ffff;
|
||||
|
||||
impl crate::ArchExtras for crate::CPU {
|
||||
fn read_return_address(&self) -> Result<GuestReg, QemuRWError> {
|
||||
let stack_ptr: GuestReg = self.read_reg(Regs::Rsp)?;
|
||||
|
@ -32,7 +32,10 @@ use crate::{
|
||||
IsSnapshotManager, Qemu, QemuMemoryChunk, QemuRWError, Regs, StdEmulatorDriver, CPU,
|
||||
};
|
||||
|
||||
#[cfg(any(cpu_target = "i386", cpu_target = "x86_64"))]
|
||||
#[cfg(all(
|
||||
any(cpu_target = "i386", cpu_target = "x86_64"),
|
||||
feature = "systemmode"
|
||||
))]
|
||||
pub mod nyx;
|
||||
pub mod parser;
|
||||
|
||||
@ -251,6 +254,7 @@ pub enum CommandError {
|
||||
TestDifference(GuestReg, GuestReg), // received, expected
|
||||
StartedTwice,
|
||||
EndBeforeStart,
|
||||
WrongUsage,
|
||||
}
|
||||
|
||||
impl From<QemuRWError> for CommandError {
|
||||
@ -461,18 +465,12 @@ where
|
||||
// Auto page filtering if option is enabled
|
||||
#[cfg(feature = "systemmode")]
|
||||
if emu.driver_mut().allow_page_on_start() {
|
||||
if let Some(page_id) = qemu.current_cpu().unwrap().current_paging_id() {
|
||||
emu.modules_mut().modules_mut().allow_page_id_all(page_id);
|
||||
if let Some(paging_id) = qemu.current_cpu().unwrap().current_paging_id() {
|
||||
log::info!("Filter: allow page ID {paging_id}.");
|
||||
emu.modules_mut().modules_mut().allow_page_id_all(paging_id);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "x86_64")]
|
||||
if emu.driver_mut().is_process_only() {
|
||||
emu.modules_mut()
|
||||
.modules_mut()
|
||||
.allow_address_range_all(crate::PROCESS_ADDRESS_RANGE);
|
||||
}
|
||||
|
||||
// Make sure JIT cache is empty just before starting
|
||||
qemu.flush_jit();
|
||||
|
||||
|
@ -10,6 +10,7 @@ use std::{
|
||||
fmt::{Debug, Formatter},
|
||||
marker::PhantomData,
|
||||
mem::offset_of,
|
||||
ops::Range,
|
||||
ptr,
|
||||
slice::from_raw_parts,
|
||||
};
|
||||
@ -19,7 +20,7 @@ use libafl::{
|
||||
executors::ExitKind,
|
||||
inputs::{HasTargetBytes, UsesInput},
|
||||
};
|
||||
use libafl_qemu_sys::GuestVirtAddr;
|
||||
use libafl_qemu_sys::{GuestAddr, GuestVirtAddr};
|
||||
use libc::c_uint;
|
||||
use paste::paste;
|
||||
|
||||
@ -27,8 +28,9 @@ use crate::{
|
||||
command::{
|
||||
parser::nyx::{
|
||||
AcquireCommandParser, GetHostConfigCommandParser, GetPayloadCommandParser,
|
||||
NextPayloadCommandParser, PrintfCommandParser, ReleaseCommandParser,
|
||||
SetAgentConfigCommandParser,
|
||||
NextPayloadCommandParser, PanicCommandParser, PrintfCommandParser,
|
||||
RangeSubmitCommandParser, ReleaseCommandParser, SetAgentConfigCommandParser,
|
||||
SubmitCR3CommandParser, SubmitPanicCommandParser, UserAbortCommandParser,
|
||||
},
|
||||
CommandError, CommandManager, IsCommand, NativeCommandParser,
|
||||
},
|
||||
@ -109,12 +111,13 @@ macro_rules! define_nyx_command_manager {
|
||||
#[deny(unreachable_patterns)]
|
||||
fn parse(&self, qemu: Qemu) -> Result<Self::Commands, CommandError> {
|
||||
let arch_regs_map: &'static EnumMap<ExitArgs, Regs> = get_exit_arch_regs();
|
||||
let nyx_backdoor = qemu.read_reg(arch_regs_map[ExitArgs::Cmd])? as c_uint;
|
||||
let nyx_backdoor = qemu.read_reg(Regs::Rax)? as c_uint;
|
||||
let cmd_id = qemu.read_reg(Regs::Rbx)? as c_uint;
|
||||
|
||||
// Check nyx backdoor correctness
|
||||
debug_assert_eq!(nyx_backdoor, 0x1f);
|
||||
|
||||
let cmd_id = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])? as c_uint;
|
||||
log::debug!("Received Nyx command ID: {cmd_id}");
|
||||
|
||||
match cmd_id {
|
||||
// <StartPhysCommandParser as NativeCommandParser<S>>::COMMAND_ID => Ok(StdCommandManagerCommands::StartPhysCommandParserCmd(<StartPhysCommandParser as NativeCommandParser<S>>::parse(qemu, arch_regs_map)?)),
|
||||
@ -176,7 +179,12 @@ define_nyx_command_manager!(
|
||||
SetAgentConfigCommand,
|
||||
PrintfCommand,
|
||||
GetPayloadCommand,
|
||||
NextPayloadCommand
|
||||
NextPayloadCommand,
|
||||
SubmitCR3Command,
|
||||
PanicCommand,
|
||||
SubmitPanicCommand,
|
||||
UserAbortCommand,
|
||||
RangeSubmitCommand
|
||||
],
|
||||
[
|
||||
AcquireCommandParser,
|
||||
@ -185,7 +193,12 @@ define_nyx_command_manager!(
|
||||
SetAgentConfigCommandParser,
|
||||
PrintfCommandParser,
|
||||
GetPayloadCommandParser,
|
||||
NextPayloadCommandParser
|
||||
NextPayloadCommandParser,
|
||||
SubmitCR3CommandParser,
|
||||
SubmitPanicCommandParser,
|
||||
PanicCommandParser,
|
||||
UserAbortCommandParser,
|
||||
RangeSubmitCommandParser
|
||||
]
|
||||
);
|
||||
|
||||
@ -309,14 +322,11 @@ where
|
||||
Option<EmulatorDriverResult<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>>,
|
||||
EmulatorDriverError,
|
||||
> {
|
||||
if emu.command_manager_mut().start() {
|
||||
return Err(EmulatorDriverError::CommandError(
|
||||
CommandError::StartedTwice,
|
||||
));
|
||||
}
|
||||
|
||||
let qemu = emu.qemu();
|
||||
|
||||
if !emu.command_manager_mut().start() {
|
||||
log::debug!("Creating snapshot.");
|
||||
|
||||
// Snapshot VM
|
||||
let snapshot_id = emu.snapshot_manager_mut().save(qemu);
|
||||
|
||||
@ -325,9 +335,6 @@ where
|
||||
.set_snapshot_id(snapshot_id)
|
||||
.map_err(|_| EmulatorDriverError::MultipleSnapshotDefinition)?;
|
||||
|
||||
// write nyx input to vm
|
||||
emu.driver().write_input(qemu, input)?;
|
||||
|
||||
// Unleash hooks if locked
|
||||
if emu.driver_mut().unlock_hooks() {
|
||||
// Prepare hooks
|
||||
@ -338,26 +345,228 @@ where
|
||||
// Auto page filtering if option is enabled
|
||||
#[cfg(feature = "systemmode")]
|
||||
if emu.driver_mut().allow_page_on_start() {
|
||||
if let Some(page_id) = qemu.current_cpu().unwrap().current_paging_id() {
|
||||
emu.modules_mut().modules_mut().allow_page_id_all(page_id);
|
||||
if let Some(paging_id) = qemu.current_cpu().unwrap().current_paging_id() {
|
||||
log::info!("Filter: allow page ID {paging_id}.");
|
||||
emu.modules_mut().modules_mut().allow_page_id_all(paging_id);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "x86_64")]
|
||||
if emu.driver_mut().is_process_only() {
|
||||
emu.modules_mut()
|
||||
.modules_mut()
|
||||
.allow_address_range_all(crate::PROCESS_ADDRESS_RANGE);
|
||||
}
|
||||
|
||||
// Make sure JIT cache is empty just before starting
|
||||
qemu.flush_jit();
|
||||
|
||||
log::info!("Fuzzing starts");
|
||||
}
|
||||
|
||||
// write nyx input to vm
|
||||
emu.driver().write_input(qemu, input)?;
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SubmitCR3Command;
|
||||
|
||||
impl<ET, S, SM> IsCommand<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM> for SubmitCR3Command
|
||||
where
|
||||
ET: EmulatorModuleTuple<S>,
|
||||
S: UsesInput + Unpin,
|
||||
S::Input: HasTargetBytes,
|
||||
SM: IsSnapshotManager,
|
||||
{
|
||||
fn usable_at_runtime(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
emu: &mut Emulator<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>,
|
||||
_state: &mut S,
|
||||
_input: &S::Input,
|
||||
_ret_reg: Option<Regs>,
|
||||
) -> Result<
|
||||
Option<EmulatorDriverResult<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>>,
|
||||
EmulatorDriverError,
|
||||
> {
|
||||
let qemu = emu.qemu();
|
||||
|
||||
if let Some(current_cpu) = qemu.current_cpu() {
|
||||
if let Some(paging_id) = current_cpu.current_paging_id() {
|
||||
log::info!("Filter: allow page ID {paging_id}.");
|
||||
emu.modules_mut().modules_mut().allow_page_id_all(paging_id);
|
||||
Ok(None)
|
||||
} else {
|
||||
log::warn!("No paging id found for current cpu");
|
||||
Err(EmulatorDriverError::CommandError(CommandError::WrongUsage))
|
||||
}
|
||||
} else {
|
||||
log::error!("No current cpu found");
|
||||
Err(EmulatorDriverError::CommandError(CommandError::WrongUsage))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RangeSubmitCommand {
|
||||
allowed_range: Range<GuestAddr>,
|
||||
}
|
||||
|
||||
impl RangeSubmitCommand {
|
||||
pub fn new(allowed_range: Range<GuestAddr>) -> Self {
|
||||
Self { allowed_range }
|
||||
}
|
||||
}
|
||||
|
||||
impl<ET, S, SM> IsCommand<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM> for RangeSubmitCommand
|
||||
where
|
||||
ET: EmulatorModuleTuple<S>,
|
||||
S: UsesInput + Unpin,
|
||||
S::Input: HasTargetBytes,
|
||||
SM: IsSnapshotManager,
|
||||
{
|
||||
fn usable_at_runtime(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
emu: &mut Emulator<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>,
|
||||
_state: &mut S,
|
||||
_input: &S::Input,
|
||||
_ret_reg: Option<Regs>,
|
||||
) -> Result<
|
||||
Option<EmulatorDriverResult<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>>,
|
||||
EmulatorDriverError,
|
||||
> {
|
||||
log::info!("Allow address range: {:#x?}", self.allowed_range);
|
||||
|
||||
const EMPTY_RANGE: Range<GuestAddr> = 0..0;
|
||||
|
||||
if self.allowed_range == EMPTY_RANGE {
|
||||
log::warn!("The given range is {:#x?}, which is most likely invalid. It is most likely a guest error.", EMPTY_RANGE);
|
||||
log::warn!("Hint: make sure the range is not getting optimized out (the volatile keyword may help you).");
|
||||
}
|
||||
|
||||
emu.modules_mut()
|
||||
.modules_mut()
|
||||
.allow_address_range_all(self.allowed_range.clone());
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PanicCommand;
|
||||
|
||||
impl<ET, S, SM> IsCommand<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM> for PanicCommand
|
||||
where
|
||||
ET: EmulatorModuleTuple<S>,
|
||||
S: UsesInput + Unpin,
|
||||
S::Input: HasTargetBytes,
|
||||
SM: IsSnapshotManager,
|
||||
{
|
||||
fn usable_at_runtime(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
emu: &mut Emulator<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>,
|
||||
_state: &mut S,
|
||||
_input: &S::Input,
|
||||
_ret_reg: Option<Regs>,
|
||||
) -> Result<
|
||||
Option<EmulatorDriverResult<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>>,
|
||||
EmulatorDriverError,
|
||||
> {
|
||||
let qemu = emu.qemu();
|
||||
|
||||
if !emu.command_manager_mut().has_started() {
|
||||
return Err(EmulatorDriverError::CommandError(
|
||||
CommandError::EndBeforeStart,
|
||||
));
|
||||
}
|
||||
|
||||
let snapshot_id = emu
|
||||
.driver_mut()
|
||||
.snapshot_id()
|
||||
.ok_or(EmulatorDriverError::SnapshotNotFound)?;
|
||||
|
||||
log::debug!("Restoring snapshot");
|
||||
emu.snapshot_manager_mut().restore(qemu, &snapshot_id)?;
|
||||
|
||||
emu.snapshot_manager_mut().check(qemu, &snapshot_id)?;
|
||||
|
||||
Ok(Some(EmulatorDriverResult::EndOfRun(ExitKind::Crash)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SubmitPanicCommand;
|
||||
|
||||
impl<ET, S, SM> IsCommand<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM> for SubmitPanicCommand
|
||||
where
|
||||
ET: EmulatorModuleTuple<S>,
|
||||
S: UsesInput + Unpin,
|
||||
S::Input: HasTargetBytes,
|
||||
SM: IsSnapshotManager,
|
||||
{
|
||||
fn usable_at_runtime(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_emu: &mut Emulator<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>,
|
||||
_state: &mut S,
|
||||
_input: &S::Input,
|
||||
_ret_reg: Option<Regs>,
|
||||
) -> Result<
|
||||
Option<EmulatorDriverResult<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>>,
|
||||
EmulatorDriverError,
|
||||
> {
|
||||
// TODO: add breakpoint to submit panic addr / page and associate it with a panic command
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserAbortCommand {
|
||||
content: String,
|
||||
}
|
||||
|
||||
impl UserAbortCommand {
|
||||
pub fn new(content: String) -> Self {
|
||||
Self { content }
|
||||
}
|
||||
}
|
||||
|
||||
impl<ET, S, SM> IsCommand<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM> for UserAbortCommand
|
||||
where
|
||||
ET: EmulatorModuleTuple<S>,
|
||||
S: UsesInput + Unpin,
|
||||
S::Input: HasTargetBytes,
|
||||
SM: IsSnapshotManager,
|
||||
{
|
||||
fn usable_at_runtime(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_emu: &mut Emulator<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>,
|
||||
_state: &mut S,
|
||||
_input: &S::Input,
|
||||
_ret_reg: Option<Regs>,
|
||||
) -> Result<
|
||||
Option<EmulatorDriverResult<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>>,
|
||||
EmulatorDriverError,
|
||||
> {
|
||||
log::error!("Nyx Guest Abort: {}", self.content);
|
||||
|
||||
Ok(Some(EmulatorDriverResult::ShutdownRequest))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ReleaseCommand;
|
||||
impl<ET, S, SM> IsCommand<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM> for ReleaseCommand
|
||||
@ -384,11 +593,14 @@ where
|
||||
let qemu = emu.qemu();
|
||||
|
||||
if emu.command_manager().has_started() {
|
||||
log::debug!("Release: end of fuzzing run. Restoring...");
|
||||
|
||||
let snapshot_id = emu
|
||||
.driver_mut()
|
||||
.snapshot_id()
|
||||
.ok_or(EmulatorDriverError::SnapshotNotFound)?;
|
||||
|
||||
log::debug!("Restoring snapshot");
|
||||
emu.snapshot_manager_mut().restore(qemu, &snapshot_id)?;
|
||||
|
||||
#[cfg(feature = "paranoid_debug")]
|
||||
@ -396,6 +608,8 @@ where
|
||||
|
||||
Ok(Some(EmulatorDriverResult::EndOfRun(ExitKind::Ok)))
|
||||
} else {
|
||||
log::debug!("Early release. Skipping...");
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ use libafl::{
|
||||
executors::ExitKind,
|
||||
inputs::{HasTargetBytes, UsesInput},
|
||||
};
|
||||
use libafl_bolts::AsSliceMut;
|
||||
use libafl_qemu_sys::{GuestAddr, GuestPhysAddr, GuestVirtAddr};
|
||||
use libc::c_uint;
|
||||
|
||||
@ -20,7 +19,10 @@ use crate::{
|
||||
GuestReg, IsSnapshotManager, Qemu, QemuMemoryChunk, Regs, StdEmulatorDriver,
|
||||
};
|
||||
|
||||
#[cfg(any(cpu_target = "i386", cpu_target = "x86_64"))]
|
||||
#[cfg(all(
|
||||
any(cpu_target = "i386", cpu_target = "x86_64"),
|
||||
feature = "systemmode"
|
||||
))]
|
||||
pub mod nyx;
|
||||
|
||||
pub static EMU_EXIT_KIND_MAP: OnceLock<EnumMap<NativeExitKind, Option<ExitKind>>> = OnceLock::new();
|
||||
@ -279,7 +281,6 @@ where
|
||||
type OutputCommand = LqprintfCommand;
|
||||
const COMMAND_ID: c_uint = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_LQPRINTF.0;
|
||||
|
||||
#[expect(clippy::uninit_vec)]
|
||||
fn parse(
|
||||
qemu: Qemu,
|
||||
arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
|
||||
@ -293,15 +294,10 @@ where
|
||||
|
||||
let total_size = str_size + 1;
|
||||
|
||||
let mut str_copy: Vec<u8> = unsafe {
|
||||
let mut res = Vec::<u8>::with_capacity(total_size);
|
||||
res.set_len(total_size);
|
||||
res
|
||||
};
|
||||
|
||||
let mem_chunk =
|
||||
QemuMemoryChunk::virt(buf_addr as GuestVirtAddr, total_size as GuestReg, cpu);
|
||||
mem_chunk.read(qemu, str_copy.as_slice_mut())?;
|
||||
|
||||
let str_copy: Vec<u8> = mem_chunk.read_vec(qemu)?;
|
||||
|
||||
let c_str: &CStr = CStr::from_bytes_with_nul(str_copy.as_slice()).unwrap();
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::{ffi::CStr, mem::transmute, sync::OnceLock};
|
||||
use std::{ffi::CStr, sync::OnceLock};
|
||||
|
||||
use enum_map::EnumMap;
|
||||
use libafl::{
|
||||
@ -12,7 +12,8 @@ use crate::{
|
||||
command::{
|
||||
nyx::{
|
||||
bindings, AcquireCommand, GetHostConfigCommand, GetPayloadCommand, NextPayloadCommand,
|
||||
NyxCommandManager, PrintfCommand, ReleaseCommand, SetAgentConfigCommand,
|
||||
NyxCommandManager, PanicCommand, PrintfCommand, RangeSubmitCommand, ReleaseCommand,
|
||||
SetAgentConfigCommand, SubmitCR3Command, SubmitPanicCommand, UserAbortCommand,
|
||||
},
|
||||
parser::NativeCommandParser,
|
||||
CommandError, CommandManager, NativeExitKind,
|
||||
@ -22,6 +23,20 @@ use crate::{
|
||||
IsSnapshotManager, NyxEmulatorDriver, Qemu, QemuMemoryChunk, Regs,
|
||||
};
|
||||
|
||||
fn get_guest_string(qemu: Qemu, string_ptr_reg: Regs) -> Result<String, CommandError> {
|
||||
let str_addr = qemu.read_reg(string_ptr_reg)? as GuestVirtAddr;
|
||||
|
||||
let mut msg_chunk: [u8; bindings::HPRINTF_MAX_SIZE as usize] =
|
||||
[0; bindings::HPRINTF_MAX_SIZE as usize];
|
||||
qemu.read_mem(str_addr, &mut msg_chunk)?;
|
||||
|
||||
Ok(CStr::from_bytes_until_nul(&msg_chunk)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string())
|
||||
}
|
||||
|
||||
pub static EMU_EXIT_KIND_MAP: OnceLock<EnumMap<NativeExitKind, Option<ExitKind>>> = OnceLock::new();
|
||||
|
||||
pub struct AcquireCommandParser;
|
||||
@ -58,14 +73,122 @@ where
|
||||
|
||||
fn parse(
|
||||
qemu: Qemu,
|
||||
arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
|
||||
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
|
||||
) -> Result<Self::OutputCommand, CommandError> {
|
||||
let payload_addr = qemu.read_reg(arch_regs_map[ExitArgs::Arg2]).unwrap() as GuestVirtAddr;
|
||||
let payload_addr = qemu.read_reg(Regs::Rcx).unwrap() as GuestVirtAddr;
|
||||
|
||||
Ok(GetPayloadCommand::new(payload_addr))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SubmitCR3CommandParser;
|
||||
impl<ET, S, SM> NativeCommandParser<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>
|
||||
for SubmitCR3CommandParser
|
||||
where
|
||||
ET: EmulatorModuleTuple<S>,
|
||||
S: UsesInput + Unpin,
|
||||
S::Input: HasTargetBytes,
|
||||
SM: IsSnapshotManager,
|
||||
{
|
||||
type OutputCommand = SubmitCR3Command;
|
||||
const COMMAND_ID: c_uint = bindings::HYPERCALL_KAFL_SUBMIT_CR3;
|
||||
|
||||
fn parse(
|
||||
_qemu: Qemu,
|
||||
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
|
||||
) -> Result<Self::OutputCommand, CommandError> {
|
||||
Ok(SubmitCR3Command)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RangeSubmitCommandParser;
|
||||
impl<ET, S, SM> NativeCommandParser<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>
|
||||
for RangeSubmitCommandParser
|
||||
where
|
||||
ET: EmulatorModuleTuple<S>,
|
||||
S: UsesInput + Unpin,
|
||||
S::Input: HasTargetBytes,
|
||||
SM: IsSnapshotManager,
|
||||
{
|
||||
type OutputCommand = RangeSubmitCommand;
|
||||
const COMMAND_ID: c_uint = bindings::HYPERCALL_KAFL_RANGE_SUBMIT;
|
||||
|
||||
fn parse(
|
||||
qemu: Qemu,
|
||||
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
|
||||
) -> Result<Self::OutputCommand, CommandError> {
|
||||
let allowed_range_addr = qemu.read_reg(Regs::Rcx)? as GuestVirtAddr;
|
||||
|
||||
// # Safety
|
||||
// Range submit is represented with an array of 3 u64 in the Nyx API.
|
||||
let allowed_range: [u64; 3] = unsafe { qemu.read_mem_val(allowed_range_addr)? };
|
||||
|
||||
Ok(RangeSubmitCommand::new(allowed_range[0]..allowed_range[1]))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SubmitPanicCommandParser;
|
||||
impl<ET, S, SM> NativeCommandParser<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>
|
||||
for SubmitPanicCommandParser
|
||||
where
|
||||
ET: EmulatorModuleTuple<S>,
|
||||
S: UsesInput + Unpin,
|
||||
S::Input: HasTargetBytes,
|
||||
SM: IsSnapshotManager,
|
||||
{
|
||||
type OutputCommand = SubmitPanicCommand;
|
||||
const COMMAND_ID: c_uint = bindings::HYPERCALL_KAFL_SUBMIT_PANIC;
|
||||
|
||||
fn parse(
|
||||
_qemu: Qemu,
|
||||
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
|
||||
) -> Result<Self::OutputCommand, CommandError> {
|
||||
Ok(SubmitPanicCommand)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PanicCommandParser;
|
||||
impl<ET, S, SM> NativeCommandParser<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>
|
||||
for PanicCommandParser
|
||||
where
|
||||
ET: EmulatorModuleTuple<S>,
|
||||
S: UsesInput + Unpin,
|
||||
S::Input: HasTargetBytes,
|
||||
SM: IsSnapshotManager,
|
||||
{
|
||||
type OutputCommand = PanicCommand;
|
||||
const COMMAND_ID: c_uint = bindings::HYPERCALL_KAFL_PANIC;
|
||||
|
||||
fn parse(
|
||||
_qemu: Qemu,
|
||||
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
|
||||
) -> Result<Self::OutputCommand, CommandError> {
|
||||
Ok(PanicCommand)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UserAbortCommandParser;
|
||||
impl<ET, S, SM> NativeCommandParser<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>
|
||||
for UserAbortCommandParser
|
||||
where
|
||||
ET: EmulatorModuleTuple<S>,
|
||||
S: UsesInput + Unpin,
|
||||
S::Input: HasTargetBytes,
|
||||
SM: IsSnapshotManager,
|
||||
{
|
||||
type OutputCommand = UserAbortCommand;
|
||||
const COMMAND_ID: c_uint = bindings::HYPERCALL_KAFL_USER_ABORT;
|
||||
|
||||
fn parse(
|
||||
qemu: Qemu,
|
||||
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
|
||||
) -> Result<Self::OutputCommand, CommandError> {
|
||||
let msg = get_guest_string(qemu, Regs::Rcx)?;
|
||||
|
||||
Ok(UserAbortCommand::new(msg))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NextPayloadCommandParser;
|
||||
impl<ET, S, SM> NativeCommandParser<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>
|
||||
for NextPayloadCommandParser
|
||||
@ -121,9 +244,9 @@ where
|
||||
|
||||
fn parse(
|
||||
qemu: Qemu,
|
||||
arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
|
||||
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
|
||||
) -> Result<Self::OutputCommand, CommandError> {
|
||||
let host_config_addr = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])? as GuestVirtAddr;
|
||||
let host_config_addr = qemu.read_reg(Regs::Rcx)? as GuestVirtAddr;
|
||||
|
||||
Ok(GetHostConfigCommand::new(QemuMemoryChunk::virt(
|
||||
host_config_addr,
|
||||
@ -146,16 +269,14 @@ where
|
||||
|
||||
fn parse(
|
||||
qemu: Qemu,
|
||||
arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
|
||||
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
|
||||
) -> Result<Self::OutputCommand, CommandError> {
|
||||
let agent_config_addr = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])? as GuestVirtAddr;
|
||||
let agent_config_addr = qemu.read_reg(Regs::Rcx)? as GuestVirtAddr;
|
||||
|
||||
let mut agent_config_buf: [u8; size_of::<bindings::agent_config_t>()] =
|
||||
[0; size_of::<bindings::agent_config_t>()];
|
||||
|
||||
qemu.read_mem(agent_config_addr, &mut agent_config_buf)?;
|
||||
|
||||
let agent_config: bindings::agent_config_t = unsafe { transmute(agent_config_buf) };
|
||||
// # Safety
|
||||
// We use the C struct directly to get the agent config
|
||||
let agent_config: bindings::agent_config_t =
|
||||
unsafe { qemu.read_mem_val(agent_config_addr)? };
|
||||
|
||||
Ok(SetAgentConfigCommand::new(agent_config))
|
||||
}
|
||||
@ -174,16 +295,10 @@ where
|
||||
|
||||
fn parse(
|
||||
qemu: Qemu,
|
||||
arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
|
||||
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
|
||||
) -> Result<Self::OutputCommand, CommandError> {
|
||||
let str_addr = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])? as GuestVirtAddr;
|
||||
let msg = get_guest_string(qemu, Regs::Rcx)?;
|
||||
|
||||
let mut msg_chunk: [u8; bindings::HPRINTF_MAX_SIZE as usize] =
|
||||
[0; bindings::HPRINTF_MAX_SIZE as usize];
|
||||
qemu.read_mem(str_addr, &mut msg_chunk)?;
|
||||
|
||||
let cstr = CStr::from_bytes_until_nul(&msg_chunk).unwrap();
|
||||
|
||||
Ok(PrintfCommand::new(cstr.to_str().unwrap().to_string()))
|
||||
Ok(PrintfCommand::new(msg))
|
||||
}
|
||||
}
|
||||
|
@ -18,9 +18,15 @@ use crate::{
|
||||
QemuShutdownCause, Regs, SnapshotId, SnapshotManagerCheckError, SnapshotManagerError,
|
||||
};
|
||||
|
||||
#[cfg(any(cpu_target = "i386", cpu_target = "x86_64"))]
|
||||
#[cfg(all(
|
||||
any(cpu_target = "i386", cpu_target = "x86_64"),
|
||||
feature = "systemmode"
|
||||
))]
|
||||
pub mod nyx;
|
||||
#[cfg(any(cpu_target = "i386", cpu_target = "x86_64"))]
|
||||
#[cfg(all(
|
||||
any(cpu_target = "i386", cpu_target = "x86_64"),
|
||||
feature = "systemmode"
|
||||
))]
|
||||
pub use nyx::{NyxEmulatorDriver, NyxEmulatorDriverBuilder};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -34,6 +40,9 @@ where
|
||||
|
||||
/// The run is over and the emulator is ready for the next iteration.
|
||||
EndOfRun(ExitKind),
|
||||
|
||||
/// Internal shutdown request
|
||||
ShutdownRequest,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -296,6 +305,10 @@ where
|
||||
Err(format!("Unhandled QEMU exit: {:?}", &unhandled_qemu_exit))
|
||||
}
|
||||
EmulatorDriverResult::EndOfRun(exit_kind) => Ok(exit_kind),
|
||||
EmulatorDriverResult::ShutdownRequest => {
|
||||
log::warn!("Shutdown request. Stopping fuzzing...");
|
||||
std::process::exit(CTRL_C_EXIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,10 +29,7 @@ pub struct NyxEmulatorDriver {
|
||||
hooks_locked: bool,
|
||||
#[cfg(feature = "systemmode")]
|
||||
#[builder(default = false)]
|
||||
allow_page_on_start: bool, // when fuzzing starts, module filters will only allow the current page table.
|
||||
#[cfg(feature = "x86_64")]
|
||||
#[builder(default = false)]
|
||||
process_only: bool, // adds x86_64 process address space in the address filters of every module.
|
||||
allow_page_on_start: bool, // when fuzzing starts, all modules will only accept the current page table
|
||||
#[builder(default = false)]
|
||||
print_commands: bool,
|
||||
#[builder(default = (1024 * 1024))]
|
||||
@ -108,11 +105,6 @@ impl NyxEmulatorDriver {
|
||||
pub fn allow_page_on_start(&self) -> bool {
|
||||
self.allow_page_on_start
|
||||
}
|
||||
|
||||
#[cfg(feature = "x86_64")]
|
||||
pub fn is_process_only(&self) -> bool {
|
||||
self.process_only
|
||||
}
|
||||
}
|
||||
|
||||
impl<CM, ET, S, SM> EmulatorDriver<CM, ET, S, SM> for NyxEmulatorDriver
|
||||
|
@ -82,7 +82,7 @@ where
|
||||
let emulator_modules = EmulatorModules::<ET, S>::emulator_modules_mut().unwrap();
|
||||
let qemu = Qemu::get_unchecked();
|
||||
|
||||
let crash_hooks_ptr = &raw mut emulator_modules.hooks.crash_hooks;
|
||||
let crash_hooks_ptr = &raw mut emulator_modules.hooks.hook_collection.crash_hooks;
|
||||
|
||||
for crash_hook in &mut (*crash_hooks_ptr) {
|
||||
match crash_hook {
|
||||
@ -103,23 +103,14 @@ where
|
||||
|
||||
/// High-level `Emulator` modules, using `QemuHooks`.
|
||||
#[derive(Debug)]
|
||||
pub struct EmulatorModules<ET, S>
|
||||
where
|
||||
S: UsesInput,
|
||||
{
|
||||
pub struct EmulatorModules<ET, S> {
|
||||
modules: Pin<Box<ET>>,
|
||||
hooks: EmulatorHooks<ET, S>,
|
||||
phantom: PhantomData<S>,
|
||||
}
|
||||
|
||||
/// Hook collection,
|
||||
#[derive(Debug)]
|
||||
pub struct EmulatorHooks<ET, S>
|
||||
where
|
||||
S: UsesInput,
|
||||
{
|
||||
qemu_hooks: QemuHooks,
|
||||
|
||||
struct EmulatorHookCollection<ET, S> {
|
||||
instruction_hooks: Vec<Pin<Box<(InstructionHookId, FatPtr)>>>,
|
||||
backdoor_hooks: Vec<Pin<Box<(BackdoorHookId, FatPtr)>>>,
|
||||
edge_hooks: Vec<Pin<Box<TcgHookState<1, EdgeHookId>>>>,
|
||||
@ -144,6 +135,42 @@ where
|
||||
phantom: PhantomData<(ET, S)>,
|
||||
}
|
||||
|
||||
impl<ET, S> Default for EmulatorHookCollection<ET, S> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
instruction_hooks: Vec::default(),
|
||||
backdoor_hooks: Vec::default(),
|
||||
edge_hooks: Vec::default(),
|
||||
block_hooks: Vec::default(),
|
||||
read_hooks: Vec::default(),
|
||||
write_hooks: Vec::default(),
|
||||
cmp_hooks: Vec::default(),
|
||||
|
||||
cpu_run_hooks: Vec::default(),
|
||||
|
||||
new_thread_hooks: Vec::default(),
|
||||
|
||||
#[cfg(feature = "usermode")]
|
||||
pre_syscall_hooks: Vec::default(),
|
||||
|
||||
#[cfg(feature = "usermode")]
|
||||
post_syscall_hooks: Vec::default(),
|
||||
|
||||
#[cfg(feature = "usermode")]
|
||||
crash_hooks: Vec::default(),
|
||||
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Hook collection,
|
||||
#[derive(Debug)]
|
||||
pub struct EmulatorHooks<ET, S> {
|
||||
qemu_hooks: QemuHooks,
|
||||
hook_collection: EmulatorHookCollection<ET, S>,
|
||||
}
|
||||
|
||||
impl<ET, S> EmulatorHooks<ET, S>
|
||||
where
|
||||
S: UsesInput + Unpin,
|
||||
@ -152,27 +179,7 @@ where
|
||||
pub fn new(qemu_hooks: QemuHooks) -> Self {
|
||||
Self {
|
||||
qemu_hooks,
|
||||
phantom: PhantomData,
|
||||
instruction_hooks: Vec::new(),
|
||||
backdoor_hooks: Vec::new(),
|
||||
edge_hooks: Vec::new(),
|
||||
block_hooks: Vec::new(),
|
||||
read_hooks: Vec::new(),
|
||||
write_hooks: Vec::new(),
|
||||
cmp_hooks: Vec::new(),
|
||||
|
||||
cpu_run_hooks: Vec::new(),
|
||||
|
||||
new_thread_hooks: Vec::new(),
|
||||
|
||||
#[cfg(feature = "usermode")]
|
||||
pre_syscall_hooks: Vec::new(),
|
||||
|
||||
#[cfg(feature = "usermode")]
|
||||
post_syscall_hooks: Vec::new(),
|
||||
|
||||
#[cfg(feature = "usermode")]
|
||||
crash_hooks: Vec::new(),
|
||||
hook_collection: EmulatorHookCollection::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,11 +196,13 @@ where
|
||||
) -> InstructionHookId {
|
||||
let fat: FatPtr = unsafe { transmute(hook) };
|
||||
|
||||
self.instruction_hooks
|
||||
self.hook_collection
|
||||
.instruction_hooks
|
||||
.push(Box::pin((InstructionHookId::invalid(), fat)));
|
||||
|
||||
unsafe {
|
||||
let hook_state = &raw mut self
|
||||
.hook_collection
|
||||
.instruction_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
@ -207,7 +216,8 @@ where
|
||||
closure_instruction_hook_wrapper::<ET, S>,
|
||||
invalidate_block,
|
||||
);
|
||||
self.instruction_hooks
|
||||
self.hook_collection
|
||||
.instruction_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -275,7 +285,9 @@ where
|
||||
unsafe extern "C" fn(&mut TcgHookState<1, EdgeHookId>, id: u64)
|
||||
);
|
||||
|
||||
self.edge_hooks.push(Box::pin(TcgHookState::new(
|
||||
self.hook_collection
|
||||
.edge_hooks
|
||||
.push(Box::pin(TcgHookState::new(
|
||||
EdgeHookId::invalid(),
|
||||
hook_to_repr!(generation_hook),
|
||||
HookRepr::Empty,
|
||||
@ -283,7 +295,8 @@ where
|
||||
)));
|
||||
|
||||
let hook_state = &mut *ptr::from_mut::<TcgHookState<1, EdgeHookId>>(
|
||||
self.edge_hooks
|
||||
self.hook_collection
|
||||
.edge_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -292,7 +305,8 @@ where
|
||||
|
||||
let id = self.qemu_hooks.add_edge_hooks(hook_state, gen, exec);
|
||||
|
||||
self.edge_hooks
|
||||
self.hook_collection
|
||||
.edge_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -332,7 +346,9 @@ where
|
||||
unsafe extern "C" fn(&mut TcgHookState<1, BlockHookId>, id: u64)
|
||||
);
|
||||
|
||||
self.block_hooks.push(Box::pin(TcgHookState::new(
|
||||
self.hook_collection
|
||||
.block_hooks
|
||||
.push(Box::pin(TcgHookState::new(
|
||||
BlockHookId::invalid(),
|
||||
hook_to_repr!(generation_hook),
|
||||
hook_to_repr!(post_generation_hook),
|
||||
@ -340,7 +356,8 @@ where
|
||||
)));
|
||||
|
||||
let hook_state = &mut *ptr::from_mut::<TcgHookState<1, BlockHookId>>(
|
||||
self.block_hooks
|
||||
self.hook_collection
|
||||
.block_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -351,7 +368,8 @@ where
|
||||
.qemu_hooks
|
||||
.add_block_hooks(hook_state, gen, postgen, exec);
|
||||
|
||||
self.block_hooks
|
||||
self.hook_collection
|
||||
.block_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -380,14 +398,17 @@ where
|
||||
unsafe extern "C" fn(&mut HookState<CpuRunHookId>, cpu: CPUStatePtr)
|
||||
);
|
||||
|
||||
self.cpu_run_hooks.push(Box::pin(HookState::new(
|
||||
self.hook_collection
|
||||
.cpu_run_hooks
|
||||
.push(Box::pin(HookState::new(
|
||||
CpuRunHookId::invalid(),
|
||||
hook_to_repr!(pre_exec_hook),
|
||||
hook_to_repr!(post_exec_hook),
|
||||
)));
|
||||
|
||||
let hook_state = &mut *ptr::from_mut::<HookState<CpuRunHookId>>(
|
||||
self.cpu_run_hooks
|
||||
self.hook_collection
|
||||
.cpu_run_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -398,7 +419,8 @@ where
|
||||
.qemu_hooks
|
||||
.add_cpu_run_hooks(hook_state, pre_run, post_run);
|
||||
|
||||
self.cpu_run_hooks
|
||||
self.hook_collection
|
||||
.cpu_run_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -461,7 +483,9 @@ where
|
||||
)
|
||||
);
|
||||
|
||||
self.read_hooks.push(Box::pin(TcgHookState::new(
|
||||
self.hook_collection
|
||||
.read_hooks
|
||||
.push(Box::pin(TcgHookState::new(
|
||||
ReadHookId::invalid(),
|
||||
hook_to_repr!(generation_hook),
|
||||
HookRepr::Empty,
|
||||
@ -475,7 +499,8 @@ where
|
||||
)));
|
||||
|
||||
let hook_state = &mut *ptr::from_mut::<TcgHookState<5, ReadHookId>>(
|
||||
self.read_hooks
|
||||
self.hook_collection
|
||||
.read_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -486,7 +511,8 @@ where
|
||||
.qemu_hooks
|
||||
.add_read_hooks(hook_state, gen, exec1, exec2, exec4, exec8, execn);
|
||||
|
||||
self.read_hooks
|
||||
self.hook_collection
|
||||
.read_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -549,7 +575,9 @@ where
|
||||
)
|
||||
);
|
||||
|
||||
self.write_hooks.push(Box::pin(TcgHookState::new(
|
||||
self.hook_collection
|
||||
.write_hooks
|
||||
.push(Box::pin(TcgHookState::new(
|
||||
WriteHookId::invalid(),
|
||||
hook_to_repr!(generation_hook),
|
||||
HookRepr::Empty,
|
||||
@ -563,7 +591,8 @@ where
|
||||
)));
|
||||
|
||||
let hook_state = &mut *ptr::from_mut::<TcgHookState<5, WriteHookId>>(
|
||||
self.write_hooks
|
||||
self.hook_collection
|
||||
.write_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -574,7 +603,8 @@ where
|
||||
.qemu_hooks
|
||||
.add_write_hooks(hook_state, gen, exec1, exec2, exec4, exec8, execn);
|
||||
|
||||
self.write_hooks
|
||||
self.hook_collection
|
||||
.write_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -624,7 +654,9 @@ where
|
||||
unsafe extern "C" fn(&mut TcgHookState<4, CmpHookId>, id: u64, v0: u64, v1: u64)
|
||||
);
|
||||
|
||||
self.cmp_hooks.push(Box::pin(TcgHookState::new(
|
||||
self.hook_collection
|
||||
.cmp_hooks
|
||||
.push(Box::pin(TcgHookState::new(
|
||||
CmpHookId::invalid(),
|
||||
hook_to_repr!(generation_hook),
|
||||
HookRepr::Empty,
|
||||
@ -637,7 +669,8 @@ where
|
||||
)));
|
||||
|
||||
let hook_state = &mut *ptr::from_mut::<TcgHookState<4, CmpHookId>>(
|
||||
self.cmp_hooks
|
||||
self.hook_collection
|
||||
.cmp_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -648,7 +681,8 @@ where
|
||||
.qemu_hooks
|
||||
.add_cmp_hooks(hook_state, gen, exec1, exec2, exec4, exec8);
|
||||
|
||||
self.cmp_hooks
|
||||
self.hook_collection
|
||||
.cmp_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -664,10 +698,12 @@ where
|
||||
pub unsafe fn backdoor_closure(&mut self, hook: BackdoorHookClosure<ET, S>) -> BackdoorHookId {
|
||||
unsafe {
|
||||
let fat: FatPtr = transmute(hook);
|
||||
self.backdoor_hooks
|
||||
self.hook_collection
|
||||
.backdoor_hooks
|
||||
.push(Box::pin((BackdoorHookId::invalid(), fat)));
|
||||
|
||||
let hook_state = &raw mut self
|
||||
.hook_collection
|
||||
.backdoor_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
@ -679,7 +715,8 @@ where
|
||||
.qemu_hooks
|
||||
.add_backdoor_hook(&mut *hook_state, closure_backdoor_hook_wrapper::<ET, S>);
|
||||
|
||||
self.backdoor_hooks
|
||||
self.hook_collection
|
||||
.backdoor_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -736,10 +773,12 @@ where
|
||||
) -> NewThreadHookId {
|
||||
unsafe {
|
||||
let fat: FatPtr = transmute(hook);
|
||||
self.new_thread_hooks
|
||||
self.hook_collection
|
||||
.new_thread_hooks
|
||||
.push(Box::pin((NewThreadHookId::invalid(), fat)));
|
||||
|
||||
let hook_state = &raw mut self
|
||||
.hook_collection
|
||||
.new_thread_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
@ -750,7 +789,8 @@ where
|
||||
let id = self
|
||||
.qemu_hooks
|
||||
.add_new_thread_hook(&mut *hook_state, closure_new_thread_hook_wrapper::<ET, S>);
|
||||
self.new_thread_hooks
|
||||
self.hook_collection
|
||||
.new_thread_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -794,10 +834,12 @@ where
|
||||
unsafe {
|
||||
let fat: FatPtr = transmute(hook);
|
||||
|
||||
self.pre_syscall_hooks
|
||||
self.hook_collection
|
||||
.pre_syscall_hooks
|
||||
.push(Box::pin((PreSyscallHookId::invalid(), fat)));
|
||||
|
||||
let hook_state = &raw mut self
|
||||
.hook_collection
|
||||
.pre_syscall_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
@ -808,7 +850,9 @@ where
|
||||
let id = self
|
||||
.qemu_hooks
|
||||
.add_pre_syscall_hook(&mut *hook_state, closure_pre_syscall_hook_wrapper::<ET, S>);
|
||||
self.pre_syscall_hooks
|
||||
|
||||
self.hook_collection
|
||||
.pre_syscall_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -845,10 +889,12 @@ where
|
||||
) -> PostSyscallHookId {
|
||||
unsafe {
|
||||
let fat: FatPtr = transmute(hook);
|
||||
self.post_syscall_hooks
|
||||
self.hook_collection
|
||||
.post_syscall_hooks
|
||||
.push(Box::pin((PostSyscallHookId::invalid(), fat)));
|
||||
|
||||
let hooks_state = &raw mut self
|
||||
.hook_collection
|
||||
.post_syscall_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
@ -860,7 +906,8 @@ where
|
||||
&mut *hooks_state,
|
||||
closure_post_syscall_hook_wrapper::<ET, S>,
|
||||
);
|
||||
self.post_syscall_hooks
|
||||
self.hook_collection
|
||||
.post_syscall_hooks
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -874,7 +921,8 @@ where
|
||||
// # Safety
|
||||
// Will cast the valid hook to a ptr.
|
||||
self.qemu_hooks.set_crash_hook(crash_hook_wrapper::<ET, S>);
|
||||
self.crash_hooks
|
||||
self.hook_collection
|
||||
.crash_hooks
|
||||
.push(HookRepr::Function(hook as *const libc::c_void));
|
||||
}
|
||||
|
||||
@ -883,20 +931,13 @@ where
|
||||
// Will cast the hook to a [`FatPtr`].
|
||||
unsafe {
|
||||
self.qemu_hooks.set_crash_hook(crash_hook_wrapper::<ET, S>);
|
||||
self.crash_hooks.push(HookRepr::Closure(transmute(hook)));
|
||||
self.hook_collection
|
||||
.crash_hooks
|
||||
.push(HookRepr::Closure(transmute(hook)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<ET, S> Default for EmulatorHooks<ET, S>
|
||||
where
|
||||
S: Unpin + UsesInput,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new(QemuHooks::get().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<ET, S> EmulatorModules<ET, S>
|
||||
where
|
||||
S: UsesInput,
|
||||
@ -1085,7 +1126,12 @@ where
|
||||
ET: EmulatorModuleTuple<S>,
|
||||
S: UsesInput + Unpin,
|
||||
{
|
||||
pub(super) fn new(emulator_hooks: EmulatorHooks<ET, S>, modules: ET) -> Pin<Box<Self>> {
|
||||
/// Create a new [`EmulatorModules`]
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Only one such struct should be ever created.
|
||||
pub(super) unsafe fn new(emulator_hooks: EmulatorHooks<ET, S>, modules: ET) -> Pin<Box<Self>> {
|
||||
let mut modules = Box::pin(Self {
|
||||
modules: Box::pin(modules),
|
||||
hooks: emulator_hooks,
|
||||
@ -1262,12 +1308,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<ET, S> Drop for EmulatorModules<ET, S>
|
||||
where
|
||||
S: UsesInput,
|
||||
{
|
||||
impl<ET, S> Drop for EmulatorModules<ET, S> {
|
||||
fn drop(&mut self) {
|
||||
// Make the global pointer null at drop time
|
||||
// # Safety
|
||||
// There can only be one EmulatorModules.
|
||||
unsafe {
|
||||
EMULATOR_MODULES = ptr::null_mut();
|
||||
}
|
||||
|
@ -161,7 +161,6 @@ where
|
||||
S: UsesInput,
|
||||
{
|
||||
#[must_use]
|
||||
#[expect(clippy::match_wildcard_for_single_variants)]
|
||||
pub fn end_of_run(&self) -> Option<ExitKind> {
|
||||
match self {
|
||||
EmulatorDriverResult::EndOfRun(exit_kind) => Some(*exit_kind),
|
||||
@ -377,9 +376,20 @@ where
|
||||
{
|
||||
let mut qemu_params = qemu_params.into();
|
||||
|
||||
// # Safety
|
||||
// `QemuHooks` can be used without QEMU being fully initialized, we make sure to only call
|
||||
// functions that do not depend on whether QEMU is well-initialized or not.
|
||||
let emulator_hooks = unsafe { EmulatorHooks::new(QemuHooks::get_unchecked()) };
|
||||
let mut emulator_modules = EmulatorModules::new(emulator_hooks, modules);
|
||||
|
||||
// # Safety
|
||||
// This is the only call to `EmulatorModules::new`.
|
||||
// Since Emulator can only be created once, we fulfil the conditions to call this function.
|
||||
let mut emulator_modules = unsafe { EmulatorModules::new(emulator_hooks, modules) };
|
||||
|
||||
// # Safety
|
||||
// This is mostly safe, but can cause issues if module hooks call to emulator_modules.modules_mut().
|
||||
// In that case, it would cause the creation of a double mutable reference.
|
||||
// We need to refactor Modules to avoid such problem in the future at some point.
|
||||
// TODO: fix things there properly. The biggest issue being that it creates 2 mut ref to the module with the callback being called
|
||||
unsafe {
|
||||
emulator_modules.modules_mut().pre_qemu_init_all(
|
||||
@ -390,6 +400,8 @@ where
|
||||
|
||||
let qemu = Qemu::init(qemu_params)?;
|
||||
|
||||
// # Safety
|
||||
// Pre-init hooks have been called above.
|
||||
unsafe {
|
||||
Ok(Self::new_with_qemu(
|
||||
qemu,
|
||||
|
@ -62,7 +62,7 @@ impl Default for NopSnapshotManager {
|
||||
|
||||
impl IsSnapshotManager for NopSnapshotManager {
|
||||
fn save(&mut self, _qemu: Qemu) -> SnapshotId {
|
||||
log::warn!("Saving snapshot with the NopSnapshotManager");
|
||||
log::debug!("Saving snapshot with the NopSnapshotManager");
|
||||
SnapshotId { id: 0 }
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@ impl IsSnapshotManager for NopSnapshotManager {
|
||||
_qemu: Qemu,
|
||||
_snapshot_id: &SnapshotId,
|
||||
) -> Result<(), SnapshotManagerError> {
|
||||
log::warn!("Restoring snapshot with the NopSnapshotManager");
|
||||
log::debug!("Restoring snapshot with the NopSnapshotManager");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,16 @@ use libafl_qemu_sys::{GuestAddr, GuestPhysAddr};
|
||||
|
||||
use crate::Qemu;
|
||||
|
||||
// TODO: make a better arch-specific / os-specific system. only works for x86_64 for now.
|
||||
#[cfg(feature = "x86_64")]
|
||||
pub const LINUX_PROCESS_ADDRESS_RANGE: Range<u64> = 0..0x0000_7fff_ffff_ffff;
|
||||
#[cfg(feature = "x86_64")]
|
||||
pub const LINUX_KERNEL_ADDRESS_RANGE: Range<u64> = 0xFFFFFFFF80000000..0xFFFFFFFF9FFFFFFF;
|
||||
|
||||
/// Generic Filter that can be:
|
||||
/// - an allow list: allow nothing but the given list
|
||||
/// - a deny list: allow anything but the given list
|
||||
/// - none: allow everything (no filtering applied)
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FilterList<T> {
|
||||
AllowList(T),
|
||||
@ -59,17 +69,22 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// An address filter list.
|
||||
///
|
||||
/// It will allow anything in the registered ranges, and deny anything else.
|
||||
/// If there is no range registered, it will allow anything.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct AddressFilterVec {
|
||||
// ideally, we should use a tree
|
||||
registered_addresses: Vec<Range<GuestAddr>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StdAddressFilter(FilterList<AddressFilterVec>);
|
||||
|
||||
impl Default for StdAddressFilter {
|
||||
fn default() -> Self {
|
||||
Self(FilterList::None)
|
||||
Self::allow_list(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,7 +116,10 @@ impl AddressFilterVec {
|
||||
impl AddressFilter for AddressFilterVec {
|
||||
fn register(&mut self, address_range: Range<GuestAddr>) {
|
||||
self.registered_addresses.push(address_range);
|
||||
Qemu::get().unwrap().flush_jit();
|
||||
|
||||
if let Some(qemu) = Qemu::get() {
|
||||
qemu.flush_jit();
|
||||
}
|
||||
}
|
||||
|
||||
fn allowed(&self, addr: &GuestAddr) -> bool {
|
||||
@ -129,6 +147,10 @@ impl AddressFilter for StdAddressFilter {
|
||||
}
|
||||
}
|
||||
|
||||
/// A page filter list.
|
||||
///
|
||||
/// It will allow anything in the registered pages, and deny anything else.
|
||||
/// If there is no page registered, it will allow anything.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PageFilterVec {
|
||||
registered_pages: HashSet<GuestPhysAddr>,
|
||||
@ -141,6 +163,19 @@ pub struct StdPageFilter(FilterList<PageFilterVec>);
|
||||
#[cfg(feature = "usermode")]
|
||||
pub type StdPageFilter = NopPageFilter;
|
||||
|
||||
#[cfg(feature = "systemmode")]
|
||||
impl StdPageFilter {
|
||||
#[must_use]
|
||||
pub fn allow_list(registered_pages: PageFilterVec) -> Self {
|
||||
StdPageFilter(FilterList::AllowList(registered_pages))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn deny_list(registered_pages: PageFilterVec) -> Self {
|
||||
StdPageFilter(FilterList::DenyList(registered_pages))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PageFilterVec {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@ -152,20 +187,23 @@ impl Default for PageFilterVec {
|
||||
#[cfg(feature = "systemmode")]
|
||||
impl Default for StdPageFilter {
|
||||
fn default() -> Self {
|
||||
Self(FilterList::None)
|
||||
Self::allow_list(PageFilterVec::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl PageFilter for PageFilterVec {
|
||||
fn register(&mut self, page_id: GuestPhysAddr) {
|
||||
self.registered_pages.insert(page_id);
|
||||
Qemu::get().unwrap().flush_jit();
|
||||
|
||||
if let Some(qemu) = Qemu::get() {
|
||||
qemu.flush_jit();
|
||||
}
|
||||
}
|
||||
|
||||
fn allowed(&self, paging_id: &GuestPhysAddr) -> bool {
|
||||
// if self.allowed_pages.is_empty() {
|
||||
// return true;
|
||||
// }
|
||||
if self.registered_pages.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.registered_pages.contains(paging_id)
|
||||
}
|
||||
@ -219,3 +257,171 @@ pub(crate) static mut NOP_ADDRESS_FILTER: UnsafeCell<NopAddressFilter> =
|
||||
UnsafeCell::new(NopAddressFilter);
|
||||
#[cfg(feature = "systemmode")]
|
||||
pub(crate) static mut NOP_PAGE_FILTER: UnsafeCell<NopPageFilter> = UnsafeCell::new(NopPageFilter);
|
||||
|
||||
#[cfg(all(feature = "systemmode", test))]
|
||||
mod tests {
|
||||
use libafl::{
|
||||
inputs::{NopInput, UsesInput},
|
||||
state::NopState,
|
||||
HasMetadata,
|
||||
};
|
||||
use libafl_bolts::tuples::tuple_list;
|
||||
|
||||
use crate::modules::{
|
||||
utils::filters::{
|
||||
AddressFilter, NopAddressFilter, NopPageFilter, PageFilter, StdAddressFilter,
|
||||
StdPageFilter,
|
||||
},
|
||||
EmulatorModule, EmulatorModuleTuple,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct DummyModule<AF, PF> {
|
||||
address_filter: AF,
|
||||
page_filter: PF,
|
||||
}
|
||||
|
||||
impl<S, AF, PF> EmulatorModule<S> for DummyModule<AF, PF>
|
||||
where
|
||||
AF: AddressFilter + 'static,
|
||||
PF: PageFilter + 'static,
|
||||
S: Unpin + UsesInput + HasMetadata,
|
||||
{
|
||||
type ModuleAddressFilter = AF;
|
||||
type ModulePageFilter = PF;
|
||||
|
||||
fn address_filter(&self) -> &Self::ModuleAddressFilter {
|
||||
&self.address_filter
|
||||
}
|
||||
|
||||
fn address_filter_mut(&mut self) -> &mut Self::ModuleAddressFilter {
|
||||
&mut self.address_filter
|
||||
}
|
||||
|
||||
fn page_filter(&self) -> &Self::ModulePageFilter {
|
||||
&self.page_filter
|
||||
}
|
||||
|
||||
fn page_filter_mut(&mut self) -> &mut Self::ModulePageFilter {
|
||||
&mut self.page_filter
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_module<AF, PF, S>(
|
||||
af: AF,
|
||||
pf: PF,
|
||||
) -> impl EmulatorModule<S, ModuleAddressFilter = AF, ModulePageFilter = PF>
|
||||
where
|
||||
AF: AddressFilter,
|
||||
PF: PageFilter,
|
||||
S: HasMetadata + UsesInput + Unpin,
|
||||
{
|
||||
DummyModule {
|
||||
address_filter: af,
|
||||
page_filter: pf,
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! test_module {
|
||||
($modules:ident, $Id:tt) => {
|
||||
assert!($modules.$Id.address_filter().allowed(&0x100));
|
||||
assert!($modules.$Id.address_filter().allowed(&0x101));
|
||||
assert!($modules.$Id.address_filter().allowed(&0x1ff));
|
||||
assert!($modules.$Id.address_filter().allowed(&0x301));
|
||||
|
||||
assert!(!$modules.$Id.address_filter().allowed(&0xff));
|
||||
assert!(!$modules.$Id.address_filter().allowed(&0x200));
|
||||
assert!(!$modules.$Id.address_filter().allowed(&0x201));
|
||||
|
||||
assert!($modules.$Id.page_filter().allowed(&0xaaaa));
|
||||
assert!($modules.$Id.page_filter().allowed(&0xbbbb));
|
||||
|
||||
assert!(!$modules.$Id.page_filter().allowed(&0xcccc));
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filter_nop() {
|
||||
let module = gen_module::<NopAddressFilter, NopPageFilter, NopState<NopInput>>(
|
||||
NopAddressFilter,
|
||||
NopPageFilter,
|
||||
);
|
||||
let mut modules = tuple_list!(module);
|
||||
|
||||
modules.allow_address_range_all(0x100..0x200);
|
||||
modules.allow_address_range_all(0x300..0x400);
|
||||
|
||||
modules.allow_page_id_all(0xaaaa);
|
||||
modules.allow_page_id_all(0xbbbb);
|
||||
|
||||
assert!(modules.0.address_filter().allowed(&0xff));
|
||||
assert!(modules.0.address_filter().allowed(&0x200));
|
||||
assert!(modules.0.address_filter().allowed(&0x201));
|
||||
assert!(modules.0.address_filter().allowed(&0x300));
|
||||
|
||||
assert!(modules.0.page_filter().allowed(&0xaaaa));
|
||||
assert!(modules.0.page_filter().allowed(&0xbbbb));
|
||||
assert!(modules.0.page_filter().allowed(&0xcccc));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filters_simple() {
|
||||
let module = gen_module::<StdAddressFilter, StdPageFilter, NopState<NopInput>>(
|
||||
StdAddressFilter::default(),
|
||||
StdPageFilter::default(),
|
||||
);
|
||||
let mut modules = tuple_list!(module);
|
||||
|
||||
assert!(modules.0.address_filter().allowed(&0x000));
|
||||
assert!(modules.0.address_filter().allowed(&0x100));
|
||||
assert!(modules.0.address_filter().allowed(&0x200));
|
||||
assert!(modules.0.address_filter().allowed(&0xffffffff));
|
||||
|
||||
assert!(modules.0.page_filter().allowed(&0xabcd));
|
||||
|
||||
modules.allow_address_range_all(0x100..0x200);
|
||||
modules.allow_address_range_all(0x300..0x400);
|
||||
|
||||
modules.allow_page_id_all(0xaaaa);
|
||||
modules.allow_page_id_all(0xbbbb);
|
||||
|
||||
assert!(modules.0.address_filter().allowed(&0x100));
|
||||
assert!(modules.0.address_filter().allowed(&0x101));
|
||||
assert!(modules.0.address_filter().allowed(&0x1ff));
|
||||
assert!(modules.0.address_filter().allowed(&0x301));
|
||||
|
||||
assert!(!modules.0.address_filter().allowed(&0xff));
|
||||
assert!(!modules.0.address_filter().allowed(&0x200));
|
||||
assert!(!modules.0.address_filter().allowed(&0x201));
|
||||
|
||||
assert!(modules.0.page_filter().allowed(&0xaaaa));
|
||||
assert!(modules.0.page_filter().allowed(&0xbbbb));
|
||||
|
||||
assert!(!modules.0.page_filter().allowed(&0xcccc));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filters_multiple() {
|
||||
let module1 = gen_module::<StdAddressFilter, StdPageFilter, NopState<NopInput>>(
|
||||
StdAddressFilter::default(),
|
||||
StdPageFilter::default(),
|
||||
);
|
||||
let module2 = gen_module::<StdAddressFilter, StdPageFilter, NopState<NopInput>>(
|
||||
StdAddressFilter::default(),
|
||||
StdPageFilter::default(),
|
||||
);
|
||||
let module3 = gen_module::<StdAddressFilter, StdPageFilter, NopState<NopInput>>(
|
||||
StdAddressFilter::default(),
|
||||
StdPageFilter::default(),
|
||||
);
|
||||
let mut modules = tuple_list!(module1, module2, module3);
|
||||
|
||||
modules.allow_address_range_all(0x100..0x200);
|
||||
modules.allow_address_range_all(0x300..0x400);
|
||||
|
||||
modules.allow_page_id_all(0xaaaa);
|
||||
modules.allow_page_id_all(0xbbbb);
|
||||
|
||||
test_module!(modules, 0);
|
||||
}
|
||||
}
|
||||
|
@ -337,6 +337,7 @@ pub struct QemuConfig {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[cfg(feature = "usermode")]
|
||||
use crate::Qemu;
|
||||
|
||||
#[test]
|
||||
|
@ -863,7 +863,7 @@ impl QemuHooks {
|
||||
/// Should not be used out of Qemu itself.
|
||||
/// Prefer `Qemu::get` for a safe version of this method.
|
||||
#[must_use]
|
||||
pub unsafe fn get_unchecked() -> Self {
|
||||
pub(crate) unsafe fn get_unchecked() -> Self {
|
||||
QemuHooks { _private: () }
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
use core::{
|
||||
cmp::{Ordering, PartialOrd},
|
||||
fmt, ptr,
|
||||
fmt, ptr, slice,
|
||||
};
|
||||
use std::{
|
||||
ffi::{c_void, CString},
|
||||
@ -51,6 +51,7 @@ pub use systemmode::*;
|
||||
|
||||
mod hooks;
|
||||
pub use hooks::*;
|
||||
use libafl_bolts::{vec_init, AsSliceMut};
|
||||
|
||||
static mut QEMU_IS_INITIALIZED: bool = false;
|
||||
|
||||
@ -375,6 +376,18 @@ impl CPU {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_mem_vec(&self, addr: GuestAddr, len: usize) -> Result<Vec<u8>, QemuRWError> {
|
||||
// # Safety
|
||||
// This is safe because we read exactly `len` bytes from QEMU.
|
||||
unsafe {
|
||||
vec_init(len, |buf| {
|
||||
self.read_mem(addr, buf.as_slice_mut())?;
|
||||
|
||||
Ok::<(), QemuRWError>(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a value to a guest address, taking into account the potential MMU / MPU.
|
||||
pub fn write_mem(&self, addr: GuestAddr, buf: &[u8]) -> Result<(), QemuRWError> {
|
||||
// TODO use gdbstub's target_cpu_memory_rw_debug
|
||||
@ -724,6 +737,13 @@ impl Qemu {
|
||||
.read_mem(addr, buf)
|
||||
}
|
||||
|
||||
/// Read a value from a guest address, taking into account the potential indirections with the current CPU.
|
||||
pub fn read_mem_vec(&self, addr: GuestAddr, len: usize) -> Result<Vec<u8>, QemuRWError> {
|
||||
self.current_cpu()
|
||||
.unwrap_or_else(|| self.cpu_from_index(0))
|
||||
.read_mem_vec(addr, len)
|
||||
}
|
||||
|
||||
/// Write a value to a guest address, taking into account the potential indirections with the current CPU.
|
||||
pub fn write_mem(&self, addr: GuestAddr, buf: &[u8]) -> Result<(), QemuRWError> {
|
||||
self.current_cpu()
|
||||
@ -731,6 +751,33 @@ impl Qemu {
|
||||
.write_mem(addr, buf)
|
||||
}
|
||||
|
||||
/// Read a value from memory to a guest addr, taking into account the potential indirections with the current CPU.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The read object should have the same layout as the type of val.
|
||||
/// No checked is performed to check whether the returned object makes sense or not.
|
||||
// TODO: Use sized array when const generics are stabilized.
|
||||
pub unsafe fn read_mem_val<T>(&self, addr: GuestAddr) -> Result<T, QemuRWError> {
|
||||
// let mut val_buf: [u8; size_of::<T>()] = [0; size_of::<T>()];
|
||||
|
||||
let val_buf: Vec<u8> = vec_init(size_of::<T>(), |buf| self.read_mem(addr, buf))?;
|
||||
|
||||
Ok(ptr::read(val_buf.as_ptr() as *const T))
|
||||
}
|
||||
|
||||
/// Write a value to memory at a guest addr, taking into account the potential indirections with the current CPU.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// val will be used as parameter of [`slice::from_raw_parts`], and thus must enforce the same requirements.
|
||||
pub unsafe fn write_mem_val<T>(&self, addr: GuestAddr, val: &T) -> Result<(), QemuRWError> {
|
||||
let val_buf: &[u8] = slice::from_raw_parts(ptr::from_ref(val) as *const u8, size_of::<T>());
|
||||
self.write_mem(addr, val_buf)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read a value from a guest address.
|
||||
///
|
||||
/// # Safety
|
||||
@ -1007,6 +1054,18 @@ impl QemuMemoryChunk {
|
||||
Ok(output_sliced.len().try_into().unwrap())
|
||||
}
|
||||
|
||||
pub fn read_vec(&self, qemu: Qemu) -> Result<Vec<u8>, QemuRWError> {
|
||||
// # Safety
|
||||
// This is safe because we read exactly `self.size` bytes from QEMU.
|
||||
unsafe {
|
||||
vec_init(self.size as usize, |buf| {
|
||||
self.read(qemu, buf.as_slice_mut())?;
|
||||
|
||||
Ok::<(), QemuRWError>(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of bytes effectively written.
|
||||
/// Input will get chunked at `size` bytes.
|
||||
pub fn write(&self, qemu: Qemu, input: &[u8]) -> Result<GuestReg, QemuRWError> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user