libafl_qemu injections (#1743)
* nits * first steps * different approach * fixes * remove temps * remove temp * initial import * more tests * bug hunt * cleanup * yaml function target 0x.... support * final * update doc * other work * Clippy, fmt * Removed lazystatic dependency * More small cleanups * optimize to_lowercase * move funtionality to libafl_qemu * add missing file * ready * remove qemu_injections * move test files to test directory * doc update * add todos * fixes * add file comment * add test and other platform support * fix clippy * Replace Emulator::new_empty by Emulator::get. Fix visibility identifier. * clippy * let's try this * cpu_target? * fmt * cleanup build system, enable missing fuzzers * fix qemu_launcher * enable hexagon in qemu_launcher * Removed useless `any` predicate in cfg attribute. Replaced wrong types in `syscall_hook` signature. * format * move to read_function_argument * add hexagon injections support * enable injections fuzzing everywhere * unify error msg * Fix build, add initial toml support * intermediate push, wip * fix build * More WIP * Fix build * Clippy * fix qemu * Fix arm * fix more wrong things * fix testcase * try to fix it again? * more release? * make makefile independent of dev/release * trying more fix? * More ugly more works * more trying to fix the testcase * allow yml as filename too * more docs --------- Co-authored-by: Dominik Maier <dmnk@google.com> Co-authored-by: Romain Malmain <romain.malmain@pm.me> Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
parent
ba8ca6723b
commit
0f2cf80085
@ -5,15 +5,23 @@ authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenuk
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std", "injections"]
|
||||||
std = []
|
std = []
|
||||||
|
|
||||||
|
## Enable fuzzing for injections (where supported)
|
||||||
|
injections = ["libafl_qemu/injections"]
|
||||||
|
|
||||||
|
## Set emulator to big endian
|
||||||
be = ["libafl_qemu/be"]
|
be = ["libafl_qemu/be"]
|
||||||
|
|
||||||
|
#! ## Mutually exclusive architectures
|
||||||
arm = ["libafl_qemu/arm"]
|
arm = ["libafl_qemu/arm"]
|
||||||
x86_64 = ["libafl_qemu/x86_64"]
|
x86_64 = ["libafl_qemu/x86_64"]
|
||||||
i386 = ["libafl_qemu/i386"]
|
i386 = ["libafl_qemu/i386"]
|
||||||
aarch64 = ["libafl_qemu/aarch64"]
|
aarch64 = ["libafl_qemu/aarch64"]
|
||||||
mips = ["libafl_qemu/mips"]
|
mips = ["libafl_qemu/mips"]
|
||||||
ppc = ["libafl_qemu/ppc", "be"]
|
ppc = ["libafl_qemu/ppc", "be"]
|
||||||
|
hexagon = ["libafl_qemu/hexagon"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
@ -71,7 +71,6 @@ script='''
|
|||||||
echo "Qemu fuzzer not supported on windows/mac"
|
echo "Qemu fuzzer not supported on windows/mac"
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
[tasks.target_dir]
|
[tasks.target_dir]
|
||||||
condition = { files_not_exist = [ "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}" ] }
|
condition = { files_not_exist = [ "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}" ] }
|
||||||
script_runner="@shell"
|
script_runner="@shell"
|
||||||
@ -285,12 +284,26 @@ mac_alias = "unsupported"
|
|||||||
windows_alias = "unsupported"
|
windows_alias = "unsupported"
|
||||||
|
|
||||||
[tasks.test_unix]
|
[tasks.test_unix]
|
||||||
# Tidy up after we've run our tests so we don't hog all the disk space
|
script_runner="@shell"
|
||||||
dependencies = [ "clean" ]
|
|
||||||
script_runner = "@shell"
|
|
||||||
script='''
|
script='''
|
||||||
echo "This test is skipped"
|
echo "Profile: ${PROFILE}"
|
||||||
|
cd injection_test || exit 1
|
||||||
|
make
|
||||||
|
mkdir in || true
|
||||||
|
echo aaaaaaaaaa > in/a
|
||||||
|
timeout 10s "$(find ${TARGET_DIR} -name 'qemu_launcher')" -o out -i in -j ../injections.toml -v -- ./static >/dev/null 2>fuzz.log || true
|
||||||
|
if [ -z "$(grep -Ei "found.*injection" fuzz.log)" ]; then
|
||||||
|
echo "Fuzzer does not generate any testcases or any crashes"
|
||||||
|
echo "Logs:"
|
||||||
|
tail fuzz.log
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "Fuzzer is working"
|
||||||
|
fi
|
||||||
|
make clean
|
||||||
|
#rm -rf in out fuzz.log || true
|
||||||
'''
|
'''
|
||||||
|
dependencies = ["build_unix"]
|
||||||
|
|
||||||
[tasks.clean]
|
[tasks.clean]
|
||||||
linux_alias = "clean_unix"
|
linux_alias = "clean_unix"
|
||||||
|
@ -11,6 +11,9 @@ The following architectures are supported:
|
|||||||
* mips
|
* mips
|
||||||
* ppc
|
* ppc
|
||||||
|
|
||||||
|
Note that the injection feature `-y` is currently only supported on x86_64
|
||||||
|
and aarch64.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
```bash
|
```bash
|
||||||
sudo apt install \
|
sudo apt install \
|
||||||
|
@ -22,7 +22,7 @@ fn main() {
|
|||||||
.emit()
|
.emit()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_unique_feature!("arm", "aarch64", "i386", "x86_64", "mips", "ppc");
|
assert_unique_feature!("arm", "aarch64", "i386", "x86_64", "mips", "ppc", "hexagon");
|
||||||
|
|
||||||
let cpu_target = if cfg!(feature = "x86_64") {
|
let cpu_target = if cfg!(feature = "x86_64") {
|
||||||
"x86_64".to_string()
|
"x86_64".to_string()
|
||||||
@ -36,6 +36,8 @@ fn main() {
|
|||||||
"mips".to_string()
|
"mips".to_string()
|
||||||
} else if cfg!(feature = "ppc") {
|
} else if cfg!(feature = "ppc") {
|
||||||
"ppc".to_string()
|
"ppc".to_string()
|
||||||
|
} else if cfg!(feature = "hexagon") {
|
||||||
|
"hexagon".to_string()
|
||||||
} else {
|
} else {
|
||||||
println!("cargo:warning=No architecture specified defaulting to x86_64...");
|
println!("cargo:warning=No architecture specified defaulting to x86_64...");
|
||||||
println!("cargo:rustc-cfg=feature=\"x86_64\"");
|
println!("cargo:rustc-cfg=feature=\"x86_64\"");
|
||||||
|
13
fuzzers/qemu_launcher/injection_test/Makefile
Normal file
13
fuzzers/qemu_launcher/injection_test/Makefile
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
all: static sqltest
|
||||||
|
|
||||||
|
sqltest: sqltest.c
|
||||||
|
gcc -g -o sqltest sqltest.c -l sqlite3 -lm
|
||||||
|
|
||||||
|
static: sqltest.c
|
||||||
|
gcc -g -o static sqltest.c -l sqlite3 -lm -static
|
||||||
|
|
||||||
|
fuzz: sqltest.c
|
||||||
|
afl-clang-fast -o fuzz sqltest.c -l sqlite3
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f sqltest static fuzz
|
10
fuzzers/qemu_launcher/injection_test/README.md
Normal file
10
fuzzers/qemu_launcher/injection_test/README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Injection test setup
|
||||||
|
|
||||||
|
To build the injection test target:
|
||||||
|
`make`
|
||||||
|
|
||||||
|
To run qemu_launcher with the injection detection activated:
|
||||||
|
|
||||||
|
```
|
||||||
|
target/release/qemu_launcher -y injections.yaml -i in -o out -- injection_test/static
|
||||||
|
```
|
BIN
fuzzers/qemu_launcher/injection_test/example.db
Normal file
BIN
fuzzers/qemu_launcher/injection_test/example.db
Normal file
Binary file not shown.
63
fuzzers/qemu_launcher/injection_test/sqltest.c
Normal file
63
fuzzers/qemu_launcher/injection_test/sqltest.c
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#include <sqlite3.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
static int callback(void *NotUsed, int argc, char **argv, char **azColName) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < argc; i++) {
|
||||||
|
printf("%s=%s ", azColName[i], argv[i] ? argv[i] : "NULL");
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LLVMFuzzerTestOneInput(char *data, size_t len) {
|
||||||
|
sqlite3 *db;
|
||||||
|
char *err_msg = 0, query[1024];
|
||||||
|
|
||||||
|
if (data[0] % 2) {
|
||||||
|
|
||||||
|
int rc = sqlite3_open_v2("example.db", &db, SQLITE_OPEN_READONLY, 0);
|
||||||
|
if (rc != SQLITE_OK) {
|
||||||
|
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
|
||||||
|
sqlite3_close(db);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(
|
||||||
|
query, sizeof(query),
|
||||||
|
"SELECT * FROM MyTable where user = \"user1\" and password = \"%s\"",
|
||||||
|
data);
|
||||||
|
|
||||||
|
rc = sqlite3_exec(db, query, callback, 0, &err_msg);
|
||||||
|
|
||||||
|
if (rc != SQLITE_OK) {
|
||||||
|
sqlite3_free(err_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_close(db);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
snprintf(query, sizeof(query), "/usr/bin/id \"%s\"", data);
|
||||||
|
system(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
char pw[16];
|
||||||
|
ssize_t len = 1;
|
||||||
|
|
||||||
|
memset(pw, 0, sizeof(pw));
|
||||||
|
if (argc > 1) {
|
||||||
|
if ((len = read(0, pw, sizeof(pw) - 1)) < 4) {
|
||||||
|
fprintf(stderr, "Error: short read from stdin\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return LLVMFuzzerTestOneInput(pw, (size_t)len + 1);
|
||||||
|
}
|
63
fuzzers/qemu_launcher/injections.toml
Normal file
63
fuzzers/qemu_launcher/injections.toml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# The TOML Structure:
|
||||||
|
#
|
||||||
|
# You can specify multiple different injection types if you want.
|
||||||
|
# [name] # any name you want, it is not important
|
||||||
|
# tokens = ["a string", ...] # an injection string to add to the tokens list
|
||||||
|
# matches = ["a string", ...] # if on of these substrings (case insensitive) is found
|
||||||
|
# # in the parameter of the function then crash!
|
||||||
|
# # note that this is not a regex.
|
||||||
|
#
|
||||||
|
# [name.functions]
|
||||||
|
# # multiple function targets to hook can be defined
|
||||||
|
# function_name = # name of the function you want to hook.
|
||||||
|
# # if the function name starts with 0x then
|
||||||
|
# # this is the QEMU Guest address of a
|
||||||
|
# # function you want to hook that does not
|
||||||
|
# # have a symbol.
|
||||||
|
# {param = number} # which parameter to the function contains the string
|
||||||
|
# # 0 = first, 1 = second, ... 0-5 are supported (depending on architecture)
|
||||||
|
|
||||||
|
[sql]
|
||||||
|
tokens = [ "'\"\"'\"\n", "\"1\" OR '1'=\"1\"" ]
|
||||||
|
matches = [ "'\"\"'\"", "1\" OR '1'=\"1" ]
|
||||||
|
|
||||||
|
[sql.functions]
|
||||||
|
sqlite3_exec = {param = 1}
|
||||||
|
PQexec = {param = 1}
|
||||||
|
PQexecParams = {param = 1}
|
||||||
|
mysql_query = {param = 1}
|
||||||
|
mysql_send_query = {param = 1}
|
||||||
|
|
||||||
|
|
||||||
|
# Command injection. Note that for most you will need a libc with debug symbols
|
||||||
|
# We do not need this as we watch the SYS_execve syscall, this is just an
|
||||||
|
# example.
|
||||||
|
[cmd]
|
||||||
|
tokens = [
|
||||||
|
"'\"FUZZ\"'",
|
||||||
|
"\";FUZZ;\"",
|
||||||
|
"';FUZZ;'",
|
||||||
|
"$(FUZZ)",
|
||||||
|
]
|
||||||
|
matches = ["'\"FUZZ\"'"]
|
||||||
|
|
||||||
|
[cmd.functions]
|
||||||
|
popen = {param = 0}
|
||||||
|
system = {param = 0}
|
||||||
|
|
||||||
|
# LDAP injection tests
|
||||||
|
[ldap]
|
||||||
|
tokens = ["*)(FUZZ=*))(|"]
|
||||||
|
matches = ["*)(FUZZ=*))(|"]
|
||||||
|
|
||||||
|
[ldap.functions]
|
||||||
|
ldap_search_ext = {param = 3}
|
||||||
|
ldap_search_ext_s = {param = 3}
|
||||||
|
|
||||||
|
# XSS injection tests
|
||||||
|
# This is a minimal example that only checks for libxml2
|
||||||
|
[xss]
|
||||||
|
tokens = ["'\"><FUZZ"]
|
||||||
|
matches = ["'\"><FUZZ"]
|
||||||
|
[xss.functions]
|
||||||
|
htmlReadMemory = {param = 0}
|
79
fuzzers/qemu_launcher/injections.yaml
Normal file
79
fuzzers/qemu_launcher/injections.yaml
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# The YAML Structure:
|
||||||
|
#
|
||||||
|
# You can specify multiple different injection types if you want.
|
||||||
|
# -name: "name" # any name you want, it is not important
|
||||||
|
# functions:
|
||||||
|
# # multiple function targets to hook can be defined
|
||||||
|
# - function: "function_name" # name of the function you want to hook.
|
||||||
|
# # if the function name starts with 0x then
|
||||||
|
# # this is the QEMU Guest address of a
|
||||||
|
# # function you want to hook that does not
|
||||||
|
# # have a symbol.
|
||||||
|
# parameter: number # which parameter to the function contains the string
|
||||||
|
# # 0 = first, 1 = second, ... 0-5 are supported (depending on architecture)
|
||||||
|
# tests:
|
||||||
|
# # multiple tests can be defined.
|
||||||
|
# - input_value: "a string" # the injection string to add to the tokens list
|
||||||
|
# match_value: "a string" # if this substring (case insensitive) is found
|
||||||
|
# # in the parameter of the function then crash!
|
||||||
|
# # note that this is not a regex.
|
||||||
|
#
|
||||||
|
- name: "sql"
|
||||||
|
functions:
|
||||||
|
- function: "sqlite3_exec"
|
||||||
|
parameter: 1
|
||||||
|
- function: "PQexec"
|
||||||
|
parameter: 1
|
||||||
|
- function: "PQexecParams"
|
||||||
|
parameter: 1
|
||||||
|
- function: "mysql_query"
|
||||||
|
parameter: 1
|
||||||
|
- function: "mysql_send_query"
|
||||||
|
parameter: 1
|
||||||
|
tests:
|
||||||
|
- input_value: "'\"\"'"
|
||||||
|
match_value: "'\"\"'"
|
||||||
|
# this one is not needed, just to show you can have many entries:
|
||||||
|
- input_value: "1\" OR '1'=\"1"
|
||||||
|
match_value: "1\" OR '1'=\"1"
|
||||||
|
|
||||||
|
# Command injection. Note that for most you will need a libc with debug symbols
|
||||||
|
# We do not need this as we watch the SYS_execve syscall, this is just an
|
||||||
|
# example.
|
||||||
|
- name: "cmd"
|
||||||
|
functions:
|
||||||
|
- function: "popen"
|
||||||
|
parameter: 0
|
||||||
|
- function: "system"
|
||||||
|
parameter: 0
|
||||||
|
tests:
|
||||||
|
# basically a dummy because we load the better ones at src/client.rs
|
||||||
|
- input_value: "'\"FUZZ\"'"
|
||||||
|
match_value: "'\"FUZZ\"'"
|
||||||
|
- input_value: "\";FUZZ;\""
|
||||||
|
match_value: "'\"FUZZ\"'"
|
||||||
|
- input_value: "';FUZZ;'"
|
||||||
|
match_value: "'\"FUZZ\"'"
|
||||||
|
- input_value: "$(FUZZ)"
|
||||||
|
match_value: "'\"FUZZ\"'"
|
||||||
|
|
||||||
|
# LDAP injection tests
|
||||||
|
- name: "ldap"
|
||||||
|
functions:
|
||||||
|
- function: "ldap_search_ext"
|
||||||
|
parameter: 3
|
||||||
|
- function: "ldap_search_ext_s"
|
||||||
|
parameter: 3
|
||||||
|
tests:
|
||||||
|
- input_value: "*)(FUZZ=*))(|"
|
||||||
|
match_value: "*)(FUZZ=*))(|"
|
||||||
|
|
||||||
|
# XSS injection tests
|
||||||
|
# This is a minimal example that only checks for libxml2
|
||||||
|
- name: "xss"
|
||||||
|
functions:
|
||||||
|
- function: "htmlReadMemory"
|
||||||
|
parameter: 0
|
||||||
|
tests:
|
||||||
|
- input_value: "'\"><FUZZ"
|
||||||
|
match_value: "'\"><FUZZ"
|
@ -18,8 +18,12 @@ use libafl_qemu::{
|
|||||||
ArchExtras, Emulator, GuestAddr, QemuInstrumentationAddressRangeFilter,
|
ArchExtras, Emulator, GuestAddr, QemuInstrumentationAddressRangeFilter,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "injections")]
|
||||||
|
use libafl_qemu::injections::QemuInjectionHelper;
|
||||||
|
|
||||||
use crate::{instance::Instance, options::FuzzerOptions};
|
use crate::{instance::Instance, options::FuzzerOptions};
|
||||||
|
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
pub type ClientState =
|
pub type ClientState =
|
||||||
StdState<BytesInput, InMemoryOnDiskCorpus<BytesInput>, StdRand, OnDiskCorpus<BytesInput>>;
|
StdState<BytesInput, InMemoryOnDiskCorpus<BytesInput>, StdRand, OnDiskCorpus<BytesInput>>;
|
||||||
|
|
||||||
@ -42,11 +46,11 @@ impl<'a> Client<'a> {
|
|||||||
Ok(args)
|
Ok(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn env(&self) -> Result<Vec<(String, String)>, Error> {
|
#[allow(clippy::unused_self)] // Api should look the same as args above
|
||||||
let env = env::vars()
|
fn env(&self) -> Vec<(String, String)> {
|
||||||
|
env::vars()
|
||||||
.filter(|(k, _v)| k != "LD_LIBRARY_PATH")
|
.filter(|(k, _v)| k != "LD_LIBRARY_PATH")
|
||||||
.collect::<Vec<(String, String)>>();
|
.collect::<Vec<(String, String)>>()
|
||||||
Ok(env)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_pc(emu: &Emulator) -> Result<GuestAddr, Error> {
|
fn start_pc(emu: &Emulator) -> Result<GuestAddr, Error> {
|
||||||
@ -59,6 +63,7 @@ impl<'a> Client<'a> {
|
|||||||
Ok(start_pc)
|
Ok(start_pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::similar_names)] // elf != self
|
||||||
fn coverage_filter(
|
fn coverage_filter(
|
||||||
&self,
|
&self,
|
||||||
emu: &Emulator,
|
emu: &Emulator,
|
||||||
@ -105,7 +110,7 @@ impl<'a> Client<'a> {
|
|||||||
let mut args = self.args()?;
|
let mut args = self.args()?;
|
||||||
log::debug!("ARGS: {:#?}", args);
|
log::debug!("ARGS: {:#?}", args);
|
||||||
|
|
||||||
let mut env = self.env()?;
|
let mut env = self.env();
|
||||||
log::debug!("ENV: {:#?}", env);
|
log::debug!("ENV: {:#?}", env);
|
||||||
|
|
||||||
let (emu, mut asan) = {
|
let (emu, mut asan) = {
|
||||||
@ -120,6 +125,29 @@ impl<'a> Client<'a> {
|
|||||||
let start_pc = Self::start_pc(&emu)?;
|
let start_pc = Self::start_pc(&emu)?;
|
||||||
log::debug!("start_pc @ {start_pc:#x}");
|
log::debug!("start_pc @ {start_pc:#x}");
|
||||||
|
|
||||||
|
#[cfg(not(feature = "injections"))]
|
||||||
|
let extra_tokens = None;
|
||||||
|
|
||||||
|
#[cfg(feature = "injections")]
|
||||||
|
let injection_helper = self
|
||||||
|
.options
|
||||||
|
.injections
|
||||||
|
.as_ref()
|
||||||
|
.map(|injections_file| {
|
||||||
|
let lower = injections_file.to_lowercase();
|
||||||
|
if lower.ends_with("yaml") || lower.ends_with("yml") {
|
||||||
|
QemuInjectionHelper::from_yaml(injections_file)
|
||||||
|
} else if lower.ends_with("toml") {
|
||||||
|
QemuInjectionHelper::from_toml(injections_file)
|
||||||
|
} else {
|
||||||
|
todo!("No injections given, what to do?");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
#[cfg(feature = "injections")]
|
||||||
|
let extra_tokens = Some(injection_helper.tokens.clone());
|
||||||
|
|
||||||
emu.entry_break(start_pc);
|
emu.entry_break(start_pc);
|
||||||
|
|
||||||
let ret_addr: GuestAddr = emu
|
let ret_addr: GuestAddr = emu
|
||||||
@ -137,25 +165,51 @@ impl<'a> Client<'a> {
|
|||||||
.options(self.options)
|
.options(self.options)
|
||||||
.emu(&emu)
|
.emu(&emu)
|
||||||
.mgr(mgr)
|
.mgr(mgr)
|
||||||
.core_id(core_id);
|
.core_id(core_id)
|
||||||
|
.extra_tokens(extra_tokens);
|
||||||
if is_asan && is_cmplog {
|
if is_asan && is_cmplog {
|
||||||
|
#[cfg(not(feature = "injections"))]
|
||||||
let helpers = tuple_list!(
|
let helpers = tuple_list!(
|
||||||
edge_coverage_helper,
|
edge_coverage_helper,
|
||||||
QemuCmpLogHelper::default(),
|
QemuCmpLogHelper::default(),
|
||||||
QemuAsanHelper::default(asan.take().unwrap()),
|
QemuAsanHelper::default(asan.take().unwrap()),
|
||||||
);
|
);
|
||||||
|
#[cfg(feature = "injections")]
|
||||||
|
let helpers = tuple_list!(
|
||||||
|
edge_coverage_helper,
|
||||||
|
QemuCmpLogHelper::default(),
|
||||||
|
QemuAsanHelper::default(asan.take().unwrap()),
|
||||||
|
injection_helper,
|
||||||
|
);
|
||||||
instance.build().run(helpers, state)
|
instance.build().run(helpers, state)
|
||||||
} else if is_asan {
|
} else if is_asan {
|
||||||
|
#[cfg(not(feature = "injections"))]
|
||||||
let helpers = tuple_list!(
|
let helpers = tuple_list!(
|
||||||
edge_coverage_helper,
|
edge_coverage_helper,
|
||||||
QemuAsanHelper::default(asan.take().unwrap()),
|
QemuAsanHelper::default(asan.take().unwrap()),
|
||||||
);
|
);
|
||||||
|
#[cfg(feature = "injections")]
|
||||||
|
let helpers = tuple_list!(
|
||||||
|
edge_coverage_helper,
|
||||||
|
QemuAsanHelper::default(asan.take().unwrap()),
|
||||||
|
injection_helper,
|
||||||
|
);
|
||||||
instance.build().run(helpers, state)
|
instance.build().run(helpers, state)
|
||||||
} else if is_cmplog {
|
} else if is_cmplog {
|
||||||
|
#[cfg(not(feature = "injections"))]
|
||||||
let helpers = tuple_list!(edge_coverage_helper, QemuCmpLogHelper::default(),);
|
let helpers = tuple_list!(edge_coverage_helper, QemuCmpLogHelper::default(),);
|
||||||
|
#[cfg(feature = "injections")]
|
||||||
|
let helpers = tuple_list!(
|
||||||
|
edge_coverage_helper,
|
||||||
|
QemuCmpLogHelper::default(),
|
||||||
|
injection_helper,
|
||||||
|
);
|
||||||
instance.build().run(helpers, state)
|
instance.build().run(helpers, state)
|
||||||
} else {
|
} else {
|
||||||
|
#[cfg(not(feature = "injections"))]
|
||||||
let helpers = tuple_list!(edge_coverage_helper,);
|
let helpers = tuple_list!(edge_coverage_helper,);
|
||||||
|
#[cfg(feature = "injections")]
|
||||||
|
let helpers = tuple_list!(edge_coverage_helper, injection_helper,);
|
||||||
instance.build().run(helpers, state)
|
instance.build().run(helpers, state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ pub struct Harness<'a> {
|
|||||||
ret_addr: GuestAddr,
|
ret_addr: GuestAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const MAX_INPUT_SIZE: usize = 1048576; // 1MB
|
pub const MAX_INPUT_SIZE: usize = 1_048_576; // 1MB
|
||||||
|
|
||||||
impl<'a> Harness<'a> {
|
impl<'a> Harness<'a> {
|
||||||
pub fn new(emu: &Emulator) -> Result<Harness, Error> {
|
pub fn new(emu: &Emulator) -> Result<Harness, Error> {
|
||||||
@ -24,7 +24,7 @@ impl<'a> Harness<'a> {
|
|||||||
|
|
||||||
let pc: GuestReg = emu
|
let pc: GuestReg = emu
|
||||||
.read_reg(Regs::Pc)
|
.read_reg(Regs::Pc)
|
||||||
.map_err(|e| Error::unknown(format!("Failed to read PC: {e:}")))?;
|
.map_err(|e| Error::unknown(format!("Failed to read PC: {e:}")))?;
|
||||||
|
|
||||||
let stack_ptr: GuestAddr = emu
|
let stack_ptr: GuestAddr = emu
|
||||||
.read_reg(Regs::Sp)
|
.read_reg(Regs::Sp)
|
||||||
|
@ -52,6 +52,7 @@ pub struct Instance<'a> {
|
|||||||
emu: &'a Emulator,
|
emu: &'a Emulator,
|
||||||
mgr: ClientMgr,
|
mgr: ClientMgr,
|
||||||
core_id: CoreId,
|
core_id: CoreId,
|
||||||
|
extra_tokens: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Instance<'a> {
|
impl<'a> Instance<'a> {
|
||||||
@ -119,12 +120,21 @@ impl<'a> Instance<'a> {
|
|||||||
|
|
||||||
let observers = tuple_list!(edges_observer, time_observer);
|
let observers = tuple_list!(edges_observer, time_observer);
|
||||||
|
|
||||||
if let Some(tokenfile) = &self.options.tokens {
|
let mut tokens = Tokens::new();
|
||||||
if state.metadata_map().get::<Tokens>().is_none() {
|
|
||||||
state.add_metadata(Tokens::from_file(tokenfile)?);
|
if let Some(extra_tokens) = &self.extra_tokens {
|
||||||
|
for token in extra_tokens {
|
||||||
|
let bytes = token.as_bytes().to_vec();
|
||||||
|
let _ = tokens.add_token(&bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(tokenfile) = &self.options.tokens {
|
||||||
|
tokens.add_from_file(tokenfile)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.add_metadata(tokens);
|
||||||
|
|
||||||
let harness = Harness::new(self.emu)?;
|
let harness = Harness::new(self.emu)?;
|
||||||
let mut harness = |input: &BytesInput| harness.run(input);
|
let mut harness = |input: &BytesInput| harness.run(input);
|
||||||
|
|
||||||
@ -213,7 +223,7 @@ impl<'a> Instance<'a> {
|
|||||||
state
|
state
|
||||||
.load_initial_inputs(fuzzer, executor, &mut self.mgr, &corpus_dirs)
|
.load_initial_inputs(fuzzer, executor, &mut self.mgr, &corpus_dirs)
|
||||||
.unwrap_or_else(|_| {
|
.unwrap_or_else(|_| {
|
||||||
println!("Failed to load initial corpus at {:?}", corpus_dirs);
|
println!("Failed to load initial corpus at {corpus_dirs:?}");
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
});
|
});
|
||||||
println!("We imported {} inputs from disk.", state.corpus().count());
|
println!("We imported {} inputs from disk.", state.corpus().count());
|
||||||
|
@ -11,6 +11,7 @@ use crate::version::Version;
|
|||||||
#[readonly::make]
|
#[readonly::make]
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[clap(author, version, about, long_about = None)]
|
#[clap(author, version, about, long_about = None)]
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
#[command(
|
#[command(
|
||||||
name = format!("qemu_coverage-{}",env!("CPU_TARGET")),
|
name = format!("qemu_coverage-{}",env!("CPU_TARGET")),
|
||||||
version = Version::default(),
|
version = Version::default(),
|
||||||
@ -24,9 +25,17 @@ pub struct FuzzerOptions {
|
|||||||
#[arg(short, long, help = "Output directory")]
|
#[arg(short, long, help = "Output directory")]
|
||||||
pub output: String,
|
pub output: String,
|
||||||
|
|
||||||
#[arg(long, help = "Tokens file")]
|
#[arg(short = 'x', long, help = "Tokens file")]
|
||||||
pub tokens: Option<String>,
|
pub tokens: Option<String>,
|
||||||
|
|
||||||
|
#[cfg(feature = "injections")]
|
||||||
|
#[arg(
|
||||||
|
short = 'j',
|
||||||
|
long,
|
||||||
|
help = "Injections TOML or YAML file definition. Filename must end in .toml or .yaml/.yml."
|
||||||
|
)]
|
||||||
|
pub injections: Option<String>,
|
||||||
|
|
||||||
#[arg(long, help = "Log file")]
|
#[arg(long, help = "Log file")]
|
||||||
pub log: Option<String>,
|
pub log: Option<String>,
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::env;
|
use std::{env, fmt::Write};
|
||||||
|
|
||||||
use clap::builder::Str;
|
use clap::builder::Str;
|
||||||
|
|
||||||
@ -21,8 +21,12 @@ impl From<Version> for Str {
|
|||||||
("Cargo Target Triple", env!("VERGEN_CARGO_TARGET_TRIPLE")),
|
("Cargo Target Triple", env!("VERGEN_CARGO_TARGET_TRIPLE")),
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| format!("{k:25}: {v}\n"))
|
.fold(String::new(), |mut output, (k, v)| {
|
||||||
.collect::<String>();
|
// Note that write!-ing into a String can never fail, despite the return type of write! being std::fmt::Result, so it can be safely ignored or unwrapped.
|
||||||
|
// See https://rust-lang.github.io/rust-clippy/master/index.html#/format_collect
|
||||||
|
let _ = writeln!(output, "{k:25}: {v}");
|
||||||
|
output
|
||||||
|
});
|
||||||
|
|
||||||
format!("\n{version:}").into()
|
format!("\n{version:}").into()
|
||||||
}
|
}
|
||||||
|
@ -221,7 +221,7 @@ where
|
|||||||
// If we would assume the fuzzer loop will always exit after this, we could do this here:
|
// If we would assume the fuzzer loop will always exit after this, we could do this here:
|
||||||
// manager.on_restart(state)?;
|
// manager.on_restart(state)?;
|
||||||
// But as the state may grow to a few megabytes,
|
// But as the state may grow to a few megabytes,
|
||||||
// for now we won' and the user has to do it (unless we find a way to do this on `Drop`).
|
// for now we won't, and the user has to do it (unless we find a way to do this on `Drop`).
|
||||||
|
|
||||||
Ok(ret.unwrap())
|
Ok(ret.unwrap())
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@ impl Tokens {
|
|||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// The caller must ensure that the region between `token_start` and `token_stop`
|
/// The caller must ensure that the region between `token_start` and `token_stop`
|
||||||
/// is a valid region, containing autotokens in the exepcted format.
|
/// is a valid region, containing autotokens in the expected format.
|
||||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
pub unsafe fn from_mut_ptrs(
|
pub unsafe fn from_mut_ptrs(
|
||||||
token_start: *const u8,
|
token_start: *const u8,
|
||||||
|
@ -12,17 +12,30 @@ edition = "2021"
|
|||||||
categories = ["development-tools::testing", "emulators", "embedded", "os", "no-std"]
|
categories = ["development-tools::testing", "emulators", "embedded", "os", "no-std"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
|
features = ["document-features"]
|
||||||
all-features = true
|
all-features = true
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["fork", "build_libqasan", "serdeany_autoreg"]
|
default = ["fork", "build_libqasan", "serdeany_autoreg"]
|
||||||
|
clippy = [] # special feature for clippy, don't use in normal projects§
|
||||||
|
document-features = ["dep:document-features"]
|
||||||
|
|
||||||
|
#! # Feature Flags
|
||||||
|
#! ### General Features
|
||||||
|
## Find injections during fuzzing
|
||||||
|
injections = ["serde_yaml", "toml"]
|
||||||
|
## Python bindings support
|
||||||
python = ["pyo3", "pyo3-build-config"]
|
python = ["pyo3", "pyo3-build-config"]
|
||||||
|
## Fork support
|
||||||
fork = ["libafl/fork"]
|
fork = ["libafl/fork"]
|
||||||
|
## Build libqasan for address sanitization
|
||||||
build_libqasan = []
|
build_libqasan = []
|
||||||
|
|
||||||
# The following architecture features are mutually exclusive.
|
#! ## The following architecture features are mutually exclusive.
|
||||||
x86_64 = ["libafl_qemu_sys/x86_64"] # build qemu for x86_64 (default)
|
|
||||||
|
## build qemu for x86_64 (default)
|
||||||
|
x86_64 = ["libafl_qemu_sys/x86_64"]
|
||||||
i386 = ["libafl_qemu_sys/i386"] # build qemu for i386
|
i386 = ["libafl_qemu_sys/i386"] # build qemu for i386
|
||||||
arm = ["libafl_qemu_sys/arm"] # build qemu for arm
|
arm = ["libafl_qemu_sys/arm"] # build qemu for arm
|
||||||
aarch64 = ["libafl_qemu_sys/aarch64"] # build qemu for aarch64
|
aarch64 = ["libafl_qemu_sys/aarch64"] # build qemu for aarch64
|
||||||
@ -30,18 +43,22 @@ mips = ["libafl_qemu_sys/mips"] # build qemu for mips (el, use with the 'be' fea
|
|||||||
ppc = ["libafl_qemu_sys/ppc"] # build qemu for powerpc
|
ppc = ["libafl_qemu_sys/ppc"] # build qemu for powerpc
|
||||||
hexagon = ["libafl_qemu_sys/hexagon"] # build qemu for hexagon
|
hexagon = ["libafl_qemu_sys/hexagon"] # build qemu for hexagon
|
||||||
|
|
||||||
|
## Big Endian mode
|
||||||
be = ["libafl_qemu_sys/be"]
|
be = ["libafl_qemu_sys/be"]
|
||||||
|
|
||||||
|
## Usermode (mutually exclusive to Systemmode)
|
||||||
usermode = ["libafl_qemu_sys/usermode"]
|
usermode = ["libafl_qemu_sys/usermode"]
|
||||||
|
## Systemmode (mutually exclusive to Usermode)
|
||||||
systemmode = ["libafl_qemu_sys/systemmode"]
|
systemmode = ["libafl_qemu_sys/systemmode"]
|
||||||
|
|
||||||
# SerdeAny features
|
#! ## SerdeAny features
|
||||||
serdeany_autoreg = ["libafl_bolts/serdeany_autoreg"] # Automatically register all `#[derive(SerdeAny)]` types at startup.
|
|
||||||
|
|
||||||
|
## Automatically register all `#[derive(SerdeAny)]` types at startup.
|
||||||
|
serdeany_autoreg = ["libafl_bolts/serdeany_autoreg"]
|
||||||
|
|
||||||
|
## Automatically register all `#[derive(SerdeAny)]` types at startup.
|
||||||
slirp = [ "systemmode", "libafl_qemu_sys/slirp" ] # build qemu with host libslirp (for user networking)
|
slirp = [ "systemmode", "libafl_qemu_sys/slirp" ] # build qemu with host libslirp (for user networking)
|
||||||
|
|
||||||
clippy = [] # special feature for clippy, don't use in normal projects§
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libafl = { path = "../libafl", version = "0.11.2", default-features = false, features = ["std", "derive", "regex"] }
|
libafl = { path = "../libafl", version = "0.11.2", default-features = false, features = ["std", "derive", "regex"] }
|
||||||
libafl_bolts = { path = "../libafl_bolts", version = "0.11.2", default-features = false, features = ["std", "derive"] }
|
libafl_bolts = { path = "../libafl_bolts", version = "0.11.2", default-features = false, features = ["std", "derive"] }
|
||||||
@ -67,8 +84,11 @@ addr2line = "0.21"
|
|||||||
typed-arena = "2.0"
|
typed-arena = "2.0"
|
||||||
paste = "1"
|
paste = "1"
|
||||||
enum-map = "2.7"
|
enum-map = "2.7"
|
||||||
|
serde_yaml = { version = "0.8", optional = true } # For parsing the injections yaml file
|
||||||
|
toml = { version = "0.4.2", optional = true } # For parsing the injections toml file
|
||||||
pyo3 = { version = "0.18", optional = true }
|
pyo3 = { version = "0.18", optional = true }
|
||||||
|
# Document all features of this crate (for `cargo doc`)
|
||||||
|
document-features = { version = "0.2", optional = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
pyo3-build-config = { version = "0.18", optional = true }
|
pyo3-build-config = { version = "0.18", optional = true }
|
||||||
|
@ -105,7 +105,7 @@ impl crate::ArchExtras for crate::CPU {
|
|||||||
self.write_reg(Regs::Lr, val)
|
self.write_reg(Regs::Lr, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String>
|
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
|
||||||
where
|
where
|
||||||
T: From<GuestReg>,
|
T: From<GuestReg>,
|
||||||
{
|
{
|
||||||
@ -113,11 +113,17 @@ impl crate::ArchExtras for crate::CPU {
|
|||||||
return Err(format!("Unsupported calling convention: {conv:#?}"));
|
return Err(format!("Unsupported calling convention: {conv:#?}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
match idx {
|
let reg_id = match idx {
|
||||||
0 => self.read_reg(Regs::X0),
|
0 => Regs::X0,
|
||||||
1 => self.read_reg(Regs::X1),
|
1 => Regs::X1,
|
||||||
_ => Err(format!("Unsupported argument: {idx:}")),
|
2 => Regs::X2,
|
||||||
}
|
3 => Regs::X3,
|
||||||
|
4 => Regs::X4,
|
||||||
|
5 => Regs::X5,
|
||||||
|
r => return Err(format!("Unsupported argument: {r:}")),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.read_reg(reg_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_function_argument<T>(
|
fn write_function_argument<T>(
|
||||||
|
@ -102,7 +102,7 @@ impl crate::ArchExtras for crate::CPU {
|
|||||||
self.write_reg(Regs::Lr, val)
|
self.write_reg(Regs::Lr, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String>
|
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
|
||||||
where
|
where
|
||||||
T: From<GuestReg>,
|
T: From<GuestReg>,
|
||||||
{
|
{
|
||||||
@ -110,11 +110,16 @@ impl crate::ArchExtras for crate::CPU {
|
|||||||
return Err(format!("Unsupported calling convention: {conv:#?}"));
|
return Err(format!("Unsupported calling convention: {conv:#?}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
match idx {
|
let reg_id = match idx {
|
||||||
0 => self.read_reg(Regs::R0),
|
0 => Regs::R0,
|
||||||
1 => self.read_reg(Regs::R1),
|
1 => Regs::R1,
|
||||||
_ => Err(format!("Unsupported argument: {idx:}")),
|
2 => Regs::R2,
|
||||||
}
|
3 => Regs::R3,
|
||||||
|
// 4.. would be on the stack, let's not do this for now
|
||||||
|
r => return Err(format!("Unsupported argument: {r:}")),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.read_reg(reg_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_function_argument<T>(
|
fn write_function_argument<T>(
|
||||||
|
@ -491,7 +491,7 @@ pub trait ArchExtras {
|
|||||||
fn write_return_address<T>(&self, val: T) -> Result<(), String>
|
fn write_return_address<T>(&self, val: T) -> Result<(), String>
|
||||||
where
|
where
|
||||||
T: Into<GuestReg>;
|
T: Into<GuestReg>;
|
||||||
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String>
|
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
|
||||||
where
|
where
|
||||||
T: From<GuestReg>;
|
T: From<GuestReg>;
|
||||||
fn write_function_argument<T>(
|
fn write_function_argument<T>(
|
||||||
@ -1004,7 +1004,7 @@ impl Emulator {
|
|||||||
/// Should not be used if `Emulator::new` has never been used before (otherwise QEMU will not be initialized).
|
/// Should not be used if `Emulator::new` has never been used before (otherwise QEMU will not be initialized).
|
||||||
/// Prefer `Emulator::get` for a safe version of this method.
|
/// Prefer `Emulator::get` for a safe version of this method.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
unsafe fn new_empty() -> Emulator {
|
pub unsafe fn new_empty() -> Emulator {
|
||||||
Emulator { _private: () }
|
Emulator { _private: () }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1640,7 +1640,7 @@ impl ArchExtras for Emulator {
|
|||||||
.write_return_address::<T>(val)
|
.write_return_address::<T>(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String>
|
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
|
||||||
where
|
where
|
||||||
T: From<GuestReg>,
|
T: From<GuestReg>,
|
||||||
{
|
{
|
||||||
@ -1676,7 +1676,7 @@ pub mod pybind {
|
|||||||
static mut PY_GENERIC_HOOKS: Vec<(GuestAddr, PyObject)> = vec![];
|
static mut PY_GENERIC_HOOKS: Vec<(GuestAddr, PyObject)> = vec![];
|
||||||
|
|
||||||
extern "C" fn py_syscall_hook_wrapper(
|
extern "C" fn py_syscall_hook_wrapper(
|
||||||
data: u64,
|
_data: u64,
|
||||||
sys_num: i32,
|
sys_num: i32,
|
||||||
a0: u64,
|
a0: u64,
|
||||||
a1: u64,
|
a1: u64,
|
||||||
@ -1774,7 +1774,7 @@ pub mod pybind {
|
|||||||
|
|
||||||
fn run(&self) {
|
fn run(&self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.emu.run();
|
self.emu.run().unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ impl crate::ArchExtras for crate::CPU {
|
|||||||
self.write_reg(Regs::Lr, val)
|
self.write_reg(Regs::Lr, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String>
|
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
|
||||||
where
|
where
|
||||||
T: From<GuestReg>,
|
T: From<GuestReg>,
|
||||||
{
|
{
|
||||||
@ -114,8 +114,18 @@ impl crate::ArchExtras for crate::CPU {
|
|||||||
return Err(format!("Unsupported calling convention: {conv:#?}"));
|
return Err(format!("Unsupported calling convention: {conv:#?}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// Note that 64 bit values may be passed in two registers (and may have padding), then this mapping is off.
|
||||||
Err(format!("Unsupported argument: {idx:}"))
|
let reg_id = match idx {
|
||||||
|
0 => Regs::R0,
|
||||||
|
1 => Regs::R1,
|
||||||
|
2 => Regs::R2,
|
||||||
|
3 => Regs::R3,
|
||||||
|
4 => Regs::R4,
|
||||||
|
5 => Regs::R5,
|
||||||
|
r => return Err(format!("Unsupported argument: {r:}")),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.read_reg(reg_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_function_argument<T>(
|
fn write_function_argument<T>(
|
||||||
|
@ -567,6 +567,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::similar_names)]
|
||||||
pub fn reads(
|
pub fn reads(
|
||||||
&self,
|
&self,
|
||||||
generation_hook: Hook<
|
generation_hook: Hook<
|
||||||
@ -730,6 +731,7 @@ where
|
|||||||
write_3_exec_hook_wrapper::<QT, S>,
|
write_3_exec_hook_wrapper::<QT, S>,
|
||||||
extern "C" fn(&mut HookState<5>, id: u64, addr: GuestAddr)
|
extern "C" fn(&mut HookState<5>, id: u64, addr: GuestAddr)
|
||||||
);
|
);
|
||||||
|
#[allow(clippy::similar_names)]
|
||||||
let execn = get_raw_hook!(
|
let execn = get_raw_hook!(
|
||||||
execution_hook_n,
|
execution_hook_n,
|
||||||
write_4_exec_hook_wrapper::<QT, S>,
|
write_4_exec_hook_wrapper::<QT, S>,
|
||||||
|
@ -88,7 +88,7 @@ impl crate::ArchExtras for crate::CPU {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String>
|
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
|
||||||
where
|
where
|
||||||
T: From<GuestReg>,
|
T: From<GuestReg>,
|
||||||
{
|
{
|
||||||
|
479
libafl_qemu/src/injections.rs
Normal file
479
libafl_qemu/src/injections.rs
Normal file
@ -0,0 +1,479 @@
|
|||||||
|
//! Detect injection vulnerabilities
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODOs:
|
||||||
|
* - read in export addresses of shared libraries to resolve functions
|
||||||
|
*
|
||||||
|
* Maybe:
|
||||||
|
* - return code analysis support (not needed currently)
|
||||||
|
* - regex support (not needed currently)
|
||||||
|
* - std::string and Rust String support (would need such target functions added)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{ffi::CStr, fmt::Display, fs, os::raw::c_char, path::Path};
|
||||||
|
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use libafl::{inputs::UsesInput, Error};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
elf::EasyElf, emu::ArchExtras, CallingConvention, Emulator, GuestAddr, Hook, QemuHelper,
|
||||||
|
QemuHelperTuple, QemuHooks, SYS_execve, SyscallHookResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Parses `injections.yaml`
|
||||||
|
fn parse_yaml<P: AsRef<Path> + Display>(path: P) -> Result<Vec<YamlInjectionEntry>, Error> {
|
||||||
|
serde_yaml::from_str(&fs::read_to_string(&path)?)
|
||||||
|
.map_err(|e| Error::serialize(format!("Failed to deserialize yaml at {path}: {e}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses `injections.toml`
|
||||||
|
fn parse_toml<P: AsRef<Path> + Display>(
|
||||||
|
path: P,
|
||||||
|
) -> Result<HashMap<String, InjectionDefinition>, Error> {
|
||||||
|
toml::from_str(&fs::read_to_string(&path)?)
|
||||||
|
.map_err(|e| Error::serialize(format!("Failed to deserialize toml at {path}: {e}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the injects.yaml format to the internal toml-like format
|
||||||
|
fn yaml_entries_to_definition(
|
||||||
|
yaml_entries: &Vec<YamlInjectionEntry>,
|
||||||
|
) -> Result<HashMap<String, InjectionDefinition>, Error> {
|
||||||
|
let mut ret = HashMap::new();
|
||||||
|
|
||||||
|
for entry in yaml_entries {
|
||||||
|
let mut functions = HashMap::new();
|
||||||
|
for function in &entry.functions {
|
||||||
|
functions.insert(
|
||||||
|
function.function.clone(),
|
||||||
|
FunctionDescription {
|
||||||
|
param: function.parameter,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut matches = Vec::new();
|
||||||
|
let mut tokens = Vec::new();
|
||||||
|
for test in &entry.tests {
|
||||||
|
matches.push(test.match_value.clone());
|
||||||
|
tokens.push(test.input_value.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret
|
||||||
|
.insert(
|
||||||
|
entry.name.clone(),
|
||||||
|
InjectionDefinition {
|
||||||
|
tokens,
|
||||||
|
matches,
|
||||||
|
functions,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
return Err(Error::illegal_argument(format!(
|
||||||
|
"Entry {} was multiply defined!",
|
||||||
|
entry.name
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct LibInfo {
|
||||||
|
name: String,
|
||||||
|
off: GuestAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LibInfo {
|
||||||
|
fn add_unique(libs: &mut Vec<LibInfo>, new_lib: LibInfo) {
|
||||||
|
if !libs.iter().any(|lib| lib.name == new_lib.name) {
|
||||||
|
libs.push(new_lib);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
struct Test {
|
||||||
|
input_value: String,
|
||||||
|
match_value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
struct Functions {
|
||||||
|
function: String,
|
||||||
|
parameter: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
struct YamlInjectionEntry {
|
||||||
|
name: String,
|
||||||
|
functions: Vec<Functions>,
|
||||||
|
tests: Vec<Test>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||||
|
struct FunctionDescription {
|
||||||
|
param: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct InjectionDefinition {
|
||||||
|
tokens: Vec<String>,
|
||||||
|
matches: Vec<String>,
|
||||||
|
functions: HashMap<String, FunctionDescription>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Matches {
|
||||||
|
id: usize,
|
||||||
|
lib_name: String,
|
||||||
|
matches: Vec<Match>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Match {
|
||||||
|
bytes_lower: Vec<u8>,
|
||||||
|
original_value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct QemuInjectionHelper {
|
||||||
|
pub tokens: Vec<String>,
|
||||||
|
definitions: HashMap<String, InjectionDefinition>,
|
||||||
|
matches_list: Vec<Matches>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QemuInjectionHelper {
|
||||||
|
/// `configure_injections` is the main function to activate the injection
|
||||||
|
/// vulnerability detection feature.
|
||||||
|
pub fn from_yaml<P: AsRef<Path> + Display>(yaml_file: P) -> Result<Self, Error> {
|
||||||
|
let yaml_entries = parse_yaml(yaml_file)?;
|
||||||
|
let definition = yaml_entries_to_definition(&yaml_entries)?;
|
||||||
|
Self::new(definition)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `configure_injections` is the main function to activate the injection
|
||||||
|
/// vulnerability detection feature.
|
||||||
|
pub fn from_toml<P: AsRef<Path> + Display>(toml_file: P) -> Result<Self, Error> {
|
||||||
|
let definition = parse_toml(toml_file)?;
|
||||||
|
Self::new(definition)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(definitions: HashMap<String, InjectionDefinition>) -> Result<Self, Error> {
|
||||||
|
let tokens = definitions
|
||||||
|
.iter()
|
||||||
|
.flat_map(|(_lib_name, definition)| &definition.tokens)
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut matches_list = Vec::with_capacity(definitions.len());
|
||||||
|
|
||||||
|
for (lib_name, definition) in &definitions {
|
||||||
|
let matches: Vec<Match> = definition
|
||||||
|
.matches
|
||||||
|
.iter()
|
||||||
|
.map(|match_str| {
|
||||||
|
let mut bytes_lower = match_str.as_bytes().to_vec();
|
||||||
|
bytes_lower.make_ascii_lowercase();
|
||||||
|
|
||||||
|
Match {
|
||||||
|
original_value: match_str.clone(),
|
||||||
|
bytes_lower,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let id = matches_list.len();
|
||||||
|
matches_list.push(Matches {
|
||||||
|
lib_name: lib_name.clone(),
|
||||||
|
id,
|
||||||
|
matches,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
tokens,
|
||||||
|
definitions,
|
||||||
|
matches_list,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_call_check<S: UsesInput, QT: QemuHelperTuple<S>>(
|
||||||
|
hooks: &mut QemuHooks<QT, S>,
|
||||||
|
id: usize,
|
||||||
|
parameter: u8,
|
||||||
|
) {
|
||||||
|
let emu = hooks.emulator();
|
||||||
|
let reg: GuestAddr = emu
|
||||||
|
.current_cpu()
|
||||||
|
.unwrap()
|
||||||
|
.read_function_argument(CallingConvention::Cdecl, parameter)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let helper = hooks.helpers_mut().match_first_type_mut::<Self>().unwrap();
|
||||||
|
let matches = &helper.matches_list[id];
|
||||||
|
|
||||||
|
//println!("reg value = {:x}", reg);
|
||||||
|
|
||||||
|
if reg != 0x00 {
|
||||||
|
let mut query = unsafe {
|
||||||
|
let c_str_ptr = reg as *const c_char;
|
||||||
|
let c_str = CStr::from_ptr(c_str_ptr);
|
||||||
|
c_str.to_bytes().to_vec()
|
||||||
|
};
|
||||||
|
query.make_ascii_lowercase();
|
||||||
|
|
||||||
|
//println!("query={}", query);
|
||||||
|
log::trace!("Checking {}", matches.lib_name);
|
||||||
|
|
||||||
|
for match_value in &matches.matches {
|
||||||
|
if match_value.bytes_lower.len() > matches.matches.len() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "crash" if we found the right value
|
||||||
|
assert!(
|
||||||
|
find_subsequence(&query, &match_value.bytes_lower).is_none(),
|
||||||
|
"Found value \"{}\" for {query:?} in {}",
|
||||||
|
match_value.original_value,
|
||||||
|
matches.lib_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> QemuHelper<S> for QemuInjectionHelper
|
||||||
|
where
|
||||||
|
S: UsesInput,
|
||||||
|
{
|
||||||
|
fn init_hooks<QT>(&self, hooks: &QemuHooks<QT, S>)
|
||||||
|
where
|
||||||
|
QT: QemuHelperTuple<S>,
|
||||||
|
{
|
||||||
|
hooks.syscalls(Hook::Function(syscall_hook::<QT, S>));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn first_exec<QT>(&self, hooks: &QemuHooks<QT, S>)
|
||||||
|
where
|
||||||
|
QT: QemuHelperTuple<S>,
|
||||||
|
{
|
||||||
|
let emu = hooks.emulator();
|
||||||
|
let mut libs: Vec<LibInfo> = Vec::new();
|
||||||
|
|
||||||
|
for region in emu.mappings() {
|
||||||
|
if let Some(path) = region.path().map(ToOwned::to_owned) {
|
||||||
|
if !path.is_empty() {
|
||||||
|
LibInfo::add_unique(
|
||||||
|
&mut libs,
|
||||||
|
LibInfo {
|
||||||
|
name: path.clone(),
|
||||||
|
off: region.start(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for matches in &self.matches_list {
|
||||||
|
let id = matches.id;
|
||||||
|
let lib_name = &matches.lib_name;
|
||||||
|
|
||||||
|
for (name, func_definition) in &self.definitions[lib_name].functions {
|
||||||
|
let hook_addrs = if name.to_lowercase().starts_with(&"0x".to_string()) {
|
||||||
|
let func_pc = u64::from_str_radix(&name[2..], 16)
|
||||||
|
.map_err(|e| {
|
||||||
|
Error::illegal_argument(format!(
|
||||||
|
"Failed to parse hex string {name} from definition for {lib_name}: {e}"
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.unwrap() as GuestAddr;
|
||||||
|
log::info!("Injections: Hooking hardcoded function {func_pc:#x}");
|
||||||
|
vec![func_pc]
|
||||||
|
} else {
|
||||||
|
libs.iter()
|
||||||
|
.filter_map(|lib| find_function(emu, &lib.name, name, lib.off).unwrap())
|
||||||
|
.map(|func_pc| {
|
||||||
|
log::info!("Injections: Function {name} found at {func_pc:#x}",);
|
||||||
|
func_pc
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
if hook_addrs.is_empty() {
|
||||||
|
log::warn!("Injections: Function not found for {lib_name}: {name}",);
|
||||||
|
}
|
||||||
|
|
||||||
|
let param = func_definition.param;
|
||||||
|
|
||||||
|
for hook_addr in hook_addrs {
|
||||||
|
hooks.instruction(
|
||||||
|
hook_addr,
|
||||||
|
Hook::Closure(Box::new(move |hooks, _state, _guest_addr| {
|
||||||
|
Self::on_call_check(hooks, id, param);
|
||||||
|
})),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn syscall_hook<QT, S>(
|
||||||
|
hooks: &mut QemuHooks<QT, S>, // our instantiated QemuHooks
|
||||||
|
_state: Option<&mut S>,
|
||||||
|
syscall: i32, // syscall number
|
||||||
|
x0: GuestAddr, // registers ...
|
||||||
|
x1: GuestAddr,
|
||||||
|
_x2: GuestAddr,
|
||||||
|
_x3: GuestAddr,
|
||||||
|
_x4: GuestAddr,
|
||||||
|
_x5: GuestAddr,
|
||||||
|
_x6: GuestAddr,
|
||||||
|
_x7: GuestAddr,
|
||||||
|
) -> SyscallHookResult
|
||||||
|
where
|
||||||
|
QT: QemuHelperTuple<S>,
|
||||||
|
S: UsesInput,
|
||||||
|
{
|
||||||
|
log::trace!("syscall_hook {syscall} {SYS_execve}");
|
||||||
|
debug_assert!(i32::try_from(SYS_execve).is_ok());
|
||||||
|
if syscall == SYS_execve as i32 {
|
||||||
|
let _helper = hooks
|
||||||
|
.helpers_mut()
|
||||||
|
.match_first_type_mut::<QemuInjectionHelper>()
|
||||||
|
.unwrap();
|
||||||
|
if x0 > 0 && x1 > 0 {
|
||||||
|
let c_array = x1 as *const *const c_char;
|
||||||
|
let cmd = unsafe {
|
||||||
|
let c_str_ptr = x0 as *const c_char;
|
||||||
|
CStr::from_ptr(c_str_ptr).to_string_lossy()
|
||||||
|
};
|
||||||
|
assert_ne!(
|
||||||
|
cmd.to_lowercase(),
|
||||||
|
"fuzz",
|
||||||
|
"Found verified command injection!"
|
||||||
|
);
|
||||||
|
//println!("CMD {}", cmd);
|
||||||
|
|
||||||
|
let first_parameter = unsafe {
|
||||||
|
if (*c_array.offset(1)).is_null() {
|
||||||
|
return SyscallHookResult::new(None);
|
||||||
|
}
|
||||||
|
CStr::from_ptr(*c_array.offset(1)).to_string_lossy()
|
||||||
|
};
|
||||||
|
let second_parameter = unsafe {
|
||||||
|
if (*c_array.offset(2)).is_null() {
|
||||||
|
return SyscallHookResult::new(None);
|
||||||
|
}
|
||||||
|
CStr::from_ptr(*c_array.offset(2)).to_string_lossy()
|
||||||
|
};
|
||||||
|
if first_parameter == "-c"
|
||||||
|
&& (second_parameter.to_lowercase().contains("';fuzz;'")
|
||||||
|
|| second_parameter.to_lowercase().contains("\";fuzz;\""))
|
||||||
|
{
|
||||||
|
panic!("Found command injection!");
|
||||||
|
}
|
||||||
|
|
||||||
|
//println!("PARAMETERS First {} Second {}", first_parameter, second_
|
||||||
|
}
|
||||||
|
SyscallHookResult::new(Some(0))
|
||||||
|
} else {
|
||||||
|
SyscallHookResult::new(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_function(
|
||||||
|
emu: &Emulator,
|
||||||
|
file: &String,
|
||||||
|
function: &str,
|
||||||
|
loadaddr: GuestAddr,
|
||||||
|
) -> Result<Option<GuestAddr>, Error> {
|
||||||
|
let mut elf_buffer = Vec::new();
|
||||||
|
let elf = EasyElf::from_file(file, &mut elf_buffer)?;
|
||||||
|
let offset = if loadaddr > 0 {
|
||||||
|
loadaddr
|
||||||
|
} else {
|
||||||
|
emu.load_addr()
|
||||||
|
};
|
||||||
|
Ok(elf.resolve_symbol(function, offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
|
||||||
|
haystack
|
||||||
|
.windows(needle.len())
|
||||||
|
.position(|window| window == needle)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
|
||||||
|
use super::{yaml_entries_to_definition, InjectionDefinition, YamlInjectionEntry};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_yaml_parsing() {
|
||||||
|
let injections: Vec<YamlInjectionEntry> = serde_yaml::from_str(
|
||||||
|
r#"
|
||||||
|
# LDAP injection tests
|
||||||
|
- name: "ldap"
|
||||||
|
functions:
|
||||||
|
- function: "ldap_search_ext"
|
||||||
|
parameter: 3
|
||||||
|
- function: "ldap_search_ext_s"
|
||||||
|
parameter: 3
|
||||||
|
tests:
|
||||||
|
- input_value: "*)(FUZZ=*))(|"
|
||||||
|
match_value: "*)(FUZZ=*))(|"
|
||||||
|
|
||||||
|
# XSS injection tests
|
||||||
|
# This is a minimal example that only checks for libxml2
|
||||||
|
- name: "xss"
|
||||||
|
functions:
|
||||||
|
- function: "htmlReadMemory"
|
||||||
|
parameter: 0
|
||||||
|
tests:
|
||||||
|
- input_value: "'\"><FUZZ"
|
||||||
|
match_value: "'\"><FUZZ"
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(injections.len(), 2);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
injections.len(),
|
||||||
|
yaml_entries_to_definition(&injections)
|
||||||
|
.unwrap()
|
||||||
|
.keys()
|
||||||
|
.len(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_toml_parsing() {
|
||||||
|
let injections: HashMap<String, InjectionDefinition> = toml::from_str(
|
||||||
|
r#"
|
||||||
|
[ldap]
|
||||||
|
tokens = ["*)(FUZZ=*))(|"]
|
||||||
|
matches = ["*)(FUZZ=*))(|"]
|
||||||
|
|
||||||
|
[ldap.functions]
|
||||||
|
ldap_search_ext = {param = 3}
|
||||||
|
ldap_search_ext_s = {param = 3}
|
||||||
|
|
||||||
|
# XSS injection tests
|
||||||
|
# This is a minimal example that only checks for libxml2
|
||||||
|
[xss]
|
||||||
|
tokens = ["'\"><FUZZ"]
|
||||||
|
matches = ["'\"><FUZZ"]
|
||||||
|
[xss.functions]
|
||||||
|
htmlReadMemory = {param = 0}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(injections.len(), 2);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,8 @@
|
|||||||
|
//! Welcome to `LibAFL` QEMU
|
||||||
|
//!
|
||||||
|
#![doc = include_str!("../../README.md")]
|
||||||
|
/*! */
|
||||||
|
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
|
||||||
// libafl_qemu only supports Linux currently
|
// libafl_qemu only supports Linux currently
|
||||||
#![cfg(target_os = "linux")]
|
#![cfg(target_os = "linux")]
|
||||||
// This lint triggers too often on the current GuestAddr type when emulating 64-bit targets because
|
// This lint triggers too often on the current GuestAddr type when emulating 64-bit targets because
|
||||||
@ -76,6 +81,11 @@ pub mod cmplog;
|
|||||||
#[cfg(not(any(cpu_target = "mips", cpu_target = "hexagon")))]
|
#[cfg(not(any(cpu_target = "mips", cpu_target = "hexagon")))]
|
||||||
pub use cmplog::QemuCmpLogHelper;
|
pub use cmplog::QemuCmpLogHelper;
|
||||||
|
|
||||||
|
#[cfg(feature = "injections")]
|
||||||
|
pub mod injections;
|
||||||
|
#[cfg(feature = "injections")]
|
||||||
|
pub use injections::QemuInjectionHelper;
|
||||||
|
|
||||||
#[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))]
|
#[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))]
|
||||||
pub mod snapshot;
|
pub mod snapshot;
|
||||||
#[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))]
|
#[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))]
|
||||||
|
@ -102,7 +102,7 @@ impl crate::ArchExtras for crate::CPU {
|
|||||||
self.write_reg(Regs::Ra, val)
|
self.write_reg(Regs::Ra, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String>
|
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
|
||||||
where
|
where
|
||||||
T: From<GuestReg>,
|
T: From<GuestReg>,
|
||||||
{
|
{
|
||||||
@ -110,11 +110,16 @@ impl crate::ArchExtras for crate::CPU {
|
|||||||
return Err(format!("Unsupported calling convention: {conv:#?}"));
|
return Err(format!("Unsupported calling convention: {conv:#?}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
match idx {
|
let reg_id = match idx {
|
||||||
0 => self.read_reg(Regs::A0),
|
0 => Regs::A0,
|
||||||
1 => self.read_reg(Regs::A1),
|
1 => Regs::A1,
|
||||||
_ => Err(format!("Unsupported argument: {idx:}")),
|
2 => Regs::A2,
|
||||||
}
|
3 => Regs::A3,
|
||||||
|
// 4.. would be on the stack, let's not do this for now
|
||||||
|
r => return Err(format!("Unsupported argument: {r:}")),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.read_reg(reg_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_function_argument<T>(
|
fn write_function_argument<T>(
|
||||||
|
@ -142,7 +142,7 @@ impl crate::ArchExtras for crate::CPU {
|
|||||||
self.write_reg(Regs::Lr, val)
|
self.write_reg(Regs::Lr, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String>
|
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
|
||||||
where
|
where
|
||||||
T: From<GuestReg>,
|
T: From<GuestReg>,
|
||||||
{
|
{
|
||||||
@ -150,11 +150,17 @@ impl crate::ArchExtras for crate::CPU {
|
|||||||
return Err(format!("Unsupported calling convention: {conv:#?}"));
|
return Err(format!("Unsupported calling convention: {conv:#?}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
match idx {
|
let reg_id = match idx {
|
||||||
0 => self.read_reg(Regs::R3),
|
0 => Regs::R3,
|
||||||
1 => self.read_reg(Regs::R4),
|
1 => Regs::R4,
|
||||||
_ => Err(format!("Unsupported argument: {idx:}")),
|
2 => Regs::R5,
|
||||||
}
|
3 => Regs::R6,
|
||||||
|
4 => Regs::R7,
|
||||||
|
5 => Regs::R8,
|
||||||
|
r => return Err(format!("Unsupported argument: {r:}")),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.read_reg(reg_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_function_argument<T>(
|
fn write_function_argument<T>(
|
||||||
|
@ -97,7 +97,7 @@ impl crate::ArchExtras for crate::CPU {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String>
|
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
|
||||||
where
|
where
|
||||||
T: From<GuestReg>,
|
T: From<GuestReg>,
|
||||||
{
|
{
|
||||||
@ -105,11 +105,17 @@ impl crate::ArchExtras for crate::CPU {
|
|||||||
return Err(format!("Unsupported calling convention: {conv:#?}"));
|
return Err(format!("Unsupported calling convention: {conv:#?}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
match idx {
|
let reg_id = match idx {
|
||||||
0 => self.read_reg(Regs::Rdi),
|
0 => Regs::Rdi,
|
||||||
1 => self.read_reg(Regs::Rsi),
|
1 => Regs::Rsi,
|
||||||
_ => Err(format!("Unsupported argument: {idx:}")),
|
2 => Regs::Rdx,
|
||||||
}
|
3 => Regs::Rcx,
|
||||||
|
4 => Regs::R8,
|
||||||
|
5 => Regs::R9,
|
||||||
|
r => return Err(format!("Unsupported argument: {r:}")),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.read_reg(reg_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_function_argument<T>(
|
fn write_function_argument<T>(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user