* Bring back python bindings for sugar,qemu (partially revert #2005) * sugarman, won't you hurry * Test?
This commit is contained in:
parent
e8fe5bb614
commit
f19302c9b1
169
.github/workflows/build_and_test.yml
vendored
169
.github/workflows/build_and_test.yml
vendored
@ -280,79 +280,104 @@ jobs:
|
|||||||
- name: Run smoke test
|
- name: Run smoke test
|
||||||
run: ./libafl_concolic/test/smoke_test.sh
|
run: ./libafl_concolic/test/smoke_test.sh
|
||||||
|
|
||||||
fuzzers:
|
python-bindings:
|
||||||
strategy:
|
runs-on: ubuntu-latest
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest]
|
|
||||||
fuzzer:
|
|
||||||
- ./fuzzers/fuzzbench_fork_qemu
|
|
||||||
- ./fuzzers/libfuzzer_stb_image_sugar
|
|
||||||
- ./fuzzers/nyx_libxml2_standalone
|
|
||||||
- ./fuzzers/baby_fuzzer_gramatron
|
|
||||||
- ./fuzzers/tinyinst_simple
|
|
||||||
- ./fuzzers/baby_fuzzer_with_forkexecutor
|
|
||||||
- ./fuzzers/baby_no_std
|
|
||||||
- ./fuzzers/baby_fuzzer_swap_differential
|
|
||||||
- ./fuzzers/baby_fuzzer_grimoire
|
|
||||||
- ./fuzzers/baby_fuzzer
|
|
||||||
- ./fuzzers/libfuzzer_libpng_launcher
|
|
||||||
- ./fuzzers/libfuzzer_libpng_accounting
|
|
||||||
- ./fuzzers/forkserver_libafl_cc
|
|
||||||
- ./fuzzers/libfuzzer_libpng_tcp_manager
|
|
||||||
- ./fuzzers/backtrace_baby_fuzzers
|
|
||||||
- ./fuzzers/fuzzbench_qemu
|
|
||||||
- ./fuzzers/nyx_libxml2_parallel
|
|
||||||
- ./fuzzers/qemu_launcher
|
|
||||||
- ./fuzzers/frida_gdiplus
|
|
||||||
- ./fuzzers/libfuzzer_stb_image_concolic
|
|
||||||
- ./fuzzers/nautilus_sync
|
|
||||||
# - ./fuzzers/qemu_cmin
|
|
||||||
# - ./fuzzers/qemu_systemmode
|
|
||||||
- ./fuzzers/push_harness
|
|
||||||
- ./fuzzers/libfuzzer_libpng_centralized
|
|
||||||
- ./fuzzers/baby_fuzzer_nautilus
|
|
||||||
- ./fuzzers/fuzzbench_text
|
|
||||||
- ./fuzzers/libfuzzer_libpng_cmin
|
|
||||||
- ./fuzzers/forkserver_simple
|
|
||||||
- ./fuzzers/baby_fuzzer_unicode
|
|
||||||
- ./fuzzers/libfuzzer_libpng_norestart
|
|
||||||
- ./fuzzers/baby_fuzzer_multi
|
|
||||||
- ./fuzzers/libafl_atheris
|
|
||||||
- ./fuzzers/frida_libpng
|
|
||||||
- ./fuzzers/fuzzbench_ctx
|
|
||||||
- ./fuzzers/fuzzbench_forkserver_cmplog
|
|
||||||
- ./fuzzers/push_stage_harness
|
|
||||||
- ./fuzzers/libfuzzer_libmozjpeg
|
|
||||||
- ./fuzzers/libfuzzer_libpng_aflpp_ui
|
|
||||||
- ./fuzzers/libfuzzer_libpng
|
|
||||||
- ./fuzzers/baby_fuzzer_wasm
|
|
||||||
- ./fuzzers/fuzzbench
|
|
||||||
- ./fuzzers/libfuzzer_stb_image
|
|
||||||
- ./fuzzers/fuzzbench_forkserver
|
|
||||||
- ./fuzzers/libfuzzer_windows_asan
|
|
||||||
- ./fuzzers/baby_fuzzer_minimizing
|
|
||||||
# - ./fuzzers/qemu_coverage
|
|
||||||
- ./fuzzers/frida_executable_libpng
|
|
||||||
- ./fuzzers/tutorial
|
|
||||||
- ./fuzzers/baby_fuzzer_tokens
|
|
||||||
- ./fuzzers/backtrace_baby_fuzzers/rust_code_with_inprocess_executor
|
|
||||||
- ./fuzzers/backtrace_baby_fuzzers/c_code_with_fork_executor
|
|
||||||
- ./fuzzers/backtrace_baby_fuzzers/command_executor
|
|
||||||
- ./fuzzers/backtrace_baby_fuzzers/forkserver_executor
|
|
||||||
- ./fuzzers/backtrace_baby_fuzzers/c_code_with_inprocess_executor
|
|
||||||
- ./fuzzers/backtrace_baby_fuzzers/rust_code_with_fork_executor
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions-rs/toolchain@v1
|
||||||
- uses: ./.github/workflows/fuzzer-tester-prepare
|
with:
|
||||||
- name: Symlink Headers
|
profile: minimal
|
||||||
if: runner.os == 'Linux'
|
toolchain: stable
|
||||||
shell: bash
|
- name: Remove existing clang and LLVM
|
||||||
run: sudo ln -s /usr/include/asm-generic /usr/include/asm
|
run: sudo apt purge llvm* clang*
|
||||||
- name: Build and run example fuzzers (Linux)
|
- name: Install LLVM and Clang
|
||||||
if: runner.os == 'Linux'
|
uses: KyleMayes/install-llvm-action@v1
|
||||||
shell: bash
|
with:
|
||||||
run: RUN_ON_CI=1 LLVM_CONFIG=llvm-config ./scripts/test_all_fuzzers.sh ${{ matrix.fuzzer }}
|
directory: ${{ runner.temp }}/llvm
|
||||||
|
version: 17
|
||||||
|
- name: Install deps
|
||||||
|
run: sudo apt-get install -y ninja-build python3-dev python3-pip python3-venv libz3-dev
|
||||||
|
- name: Install maturin
|
||||||
|
run: python3 -m pip install maturin
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Run a maturin build
|
||||||
|
run: export LLVM_CONFIG=llvm-config-16 && cd ./bindings/pylibafl && python3 -m venv .env && . .env/bin/activate && pip install --upgrade --force-reinstall . && ./test.sh
|
||||||
|
- name: Run python test
|
||||||
|
run: . ./bindings/pylibafl/.env/bin/activate && cd ./fuzzers/baby_fuzzer && python3 baby_fuzzer.py 2>&1 | grep "Bye"
|
||||||
|
|
||||||
|
fuzzers:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest]
|
||||||
|
fuzzer:
|
||||||
|
- ./fuzzers/fuzzbench_fork_qemu
|
||||||
|
- ./fuzzers/libfuzzer_stb_image_sugar
|
||||||
|
- ./fuzzers/nyx_libxml2_standalone
|
||||||
|
- ./fuzzers/baby_fuzzer_gramatron
|
||||||
|
- ./fuzzers/tinyinst_simple
|
||||||
|
- ./fuzzers/baby_fuzzer_with_forkexecutor
|
||||||
|
- ./fuzzers/baby_no_std
|
||||||
|
- ./fuzzers/baby_fuzzer_swap_differential
|
||||||
|
- ./fuzzers/baby_fuzzer_grimoire
|
||||||
|
- ./fuzzers/baby_fuzzer
|
||||||
|
- ./fuzzers/libfuzzer_libpng_launcher
|
||||||
|
- ./fuzzers/libfuzzer_libpng_accounting
|
||||||
|
- ./fuzzers/forkserver_libafl_cc
|
||||||
|
- ./fuzzers/libfuzzer_libpng_tcp_manager
|
||||||
|
- ./fuzzers/backtrace_baby_fuzzers
|
||||||
|
- ./fuzzers/fuzzbench_qemu
|
||||||
|
- ./fuzzers/nyx_libxml2_parallel
|
||||||
|
- ./fuzzers/qemu_launcher
|
||||||
|
- ./fuzzers/frida_gdiplus
|
||||||
|
- ./fuzzers/libfuzzer_stb_image_concolic
|
||||||
|
- ./fuzzers/nautilus_sync
|
||||||
|
# - ./fuzzers/qemu_cmin
|
||||||
|
# - ./fuzzers/qemu_systemmode
|
||||||
|
- ./fuzzers/push_harness
|
||||||
|
- ./fuzzers/libfuzzer_libpng_centralized
|
||||||
|
- ./fuzzers/baby_fuzzer_nautilus
|
||||||
|
- ./fuzzers/fuzzbench_text
|
||||||
|
- ./fuzzers/libfuzzer_libpng_cmin
|
||||||
|
- ./fuzzers/forkserver_simple
|
||||||
|
- ./fuzzers/baby_fuzzer_unicode
|
||||||
|
- ./fuzzers/libfuzzer_libpng_norestart
|
||||||
|
- ./fuzzers/baby_fuzzer_multi
|
||||||
|
- ./fuzzers/libafl_atheris
|
||||||
|
- ./fuzzers/frida_libpng
|
||||||
|
- ./fuzzers/fuzzbench_ctx
|
||||||
|
- ./fuzzers/fuzzbench_forkserver_cmplog
|
||||||
|
- ./fuzzers/push_stage_harness
|
||||||
|
- ./fuzzers/libfuzzer_libmozjpeg
|
||||||
|
- ./fuzzers/libfuzzer_libpng_aflpp_ui
|
||||||
|
- ./fuzzers/libfuzzer_libpng
|
||||||
|
- ./fuzzers/baby_fuzzer_wasm
|
||||||
|
- ./fuzzers/fuzzbench
|
||||||
|
- ./fuzzers/libfuzzer_stb_image
|
||||||
|
- ./fuzzers/fuzzbench_forkserver
|
||||||
|
- ./fuzzers/libfuzzer_windows_asan
|
||||||
|
- ./fuzzers/baby_fuzzer_minimizing
|
||||||
|
# - ./fuzzers/qemu_coverage
|
||||||
|
- ./fuzzers/frida_executable_libpng
|
||||||
|
- ./fuzzers/tutorial
|
||||||
|
- ./fuzzers/baby_fuzzer_tokens
|
||||||
|
- ./fuzzers/backtrace_baby_fuzzers/rust_code_with_inprocess_executor
|
||||||
|
- ./fuzzers/backtrace_baby_fuzzers/c_code_with_fork_executor
|
||||||
|
- ./fuzzers/backtrace_baby_fuzzers/command_executor
|
||||||
|
- ./fuzzers/backtrace_baby_fuzzers/forkserver_executor
|
||||||
|
- ./fuzzers/backtrace_baby_fuzzers/c_code_with_inprocess_executor
|
||||||
|
- ./fuzzers/backtrace_baby_fuzzers/rust_code_with_fork_executor
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: ./.github/workflows/fuzzer-tester-prepare
|
||||||
|
- name: Symlink Headers
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
shell: bash
|
||||||
|
run: sudo ln -s /usr/include/asm-generic /usr/include/asm
|
||||||
|
- name: Build and run example fuzzers (Linux)
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
shell: bash
|
||||||
|
run: RUN_ON_CI=1 LLVM_CONFIG=llvm-config ./scripts/test_all_fuzzers.sh ${{ matrix.fuzzer }}
|
||||||
|
|
||||||
nostd-build:
|
nostd-build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
1
bindings/pylibafl/.gitignore
vendored
Normal file
1
bindings/pylibafl/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
dist/
|
23
bindings/pylibafl/Cargo.toml
Normal file
23
bindings/pylibafl/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[package]
|
||||||
|
name = "pylibafl"
|
||||||
|
version = "0.11.2"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pyo3 = { version = "0.18.3", features = ["extension-module"] }
|
||||||
|
pyo3-log = "0.8.1"
|
||||||
|
libafl_sugar = { path = "../../libafl_sugar", version = "0.11.2", features = ["python"] }
|
||||||
|
libafl_bolts = { path = "../../libafl_bolts", version = "0.11.2", features = ["python"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
libafl_qemu = { path = "../../libafl_qemu", version = "0.11.2", features = ["python"] }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
pyo3-build-config = { version = "0.17" }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "pylibafl"
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
panic = "abort"
|
31
bindings/pylibafl/README.md
Normal file
31
bindings/pylibafl/README.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# How to use python bindings
|
||||||
|
## First time setup
|
||||||
|
```bash
|
||||||
|
# Install maturin
|
||||||
|
pip install maturin
|
||||||
|
# Create virtual environment
|
||||||
|
python3 -m venv .env
|
||||||
|
```
|
||||||
|
## Build bindings
|
||||||
|
```
|
||||||
|
# Activate virtual environment
|
||||||
|
source .env/bin/activate
|
||||||
|
# Build python module
|
||||||
|
maturin develop
|
||||||
|
```
|
||||||
|
This is going to install `pylibafl` python module into this venv.
|
||||||
|
|
||||||
|
## Use bindings
|
||||||
|
### Example: Running baby_fuzzer in fuzzers/baby_fuzzer/baby_fuzzer.py
|
||||||
|
First, make sure the python virtual environment is activated. If not, run `source .env/bin/activate
|
||||||
|
`. Running `pip freeze` at this point should display the following (versions may differ):
|
||||||
|
```
|
||||||
|
maturin==0.12.6
|
||||||
|
pylibafl==0.7.0
|
||||||
|
toml==0.10.2
|
||||||
|
```
|
||||||
|
Then simply run
|
||||||
|
```
|
||||||
|
python PATH_TO_BABY_FUZZER/baby_fuzzer.py
|
||||||
|
```
|
||||||
|
The crashes directory will be created in the directory from which you ran the command.
|
26
bindings/pylibafl/pyproject.toml
Normal file
26
bindings/pylibafl/pyproject.toml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["maturin[patchelf]>=0.14.10,<0.15"]
|
||||||
|
build-backend = "maturin"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "PyLibAFL"
|
||||||
|
version = "0.10.1"
|
||||||
|
description = "Advanced Fuzzing Library for Python"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
license = {text = "Apache-2.0"}
|
||||||
|
classifiers = [
|
||||||
|
"License :: OSI Approved :: Apache Software License",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Programming Language :: Rust",
|
||||||
|
"Topic :: Security",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
repository = "https://github.com/AFLplusplus/LibAFL.git"
|
||||||
|
|
||||||
|
[tool.maturin]
|
||||||
|
bindings = "pylibafl"
|
||||||
|
manifest-path = "Cargo.toml"
|
||||||
|
python-source = "python"
|
||||||
|
all-features = true
|
33
bindings/pylibafl/src/lib.rs
Normal file
33
bindings/pylibafl/src/lib.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
/// Setup python modules for `libafl_qemu` and `libafl_sugar`.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns error if python libafl setup failed.
|
||||||
|
#[pymodule]
|
||||||
|
#[pyo3(name = "pylibafl")]
|
||||||
|
pub fn python_module(py: Python, m: &PyModule) -> PyResult<()> {
|
||||||
|
pyo3_log::init();
|
||||||
|
|
||||||
|
let modules = py.import("sys")?.getattr("modules")?;
|
||||||
|
|
||||||
|
let sugar_module = PyModule::new(py, "sugar")?;
|
||||||
|
libafl_sugar::python_module(py, sugar_module)?;
|
||||||
|
m.add_submodule(sugar_module)?;
|
||||||
|
modules.set_item("pylibafl.sugar", sugar_module)?;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
let qemu_module = PyModule::new(py, "qemu")?;
|
||||||
|
libafl_qemu::python_module(py, qemu_module)?;
|
||||||
|
m.add_submodule(qemu_module)?;
|
||||||
|
modules.set_item("pylibafl.qemu", qemu_module)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bolts_module = PyModule::new(py, "libafl_bolts")?;
|
||||||
|
libafl_bolts::pybind::python_module(py, bolts_module)?;
|
||||||
|
m.add_submodule(bolts_module)?;
|
||||||
|
modules.set_item("pylibafl.libafl_bolts", bolts_module)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
7
bindings/pylibafl/test.py
Normal file
7
bindings/pylibafl/test.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import pylibafl.sugar as sugar
|
||||||
|
import ctypes
|
||||||
|
import platform
|
||||||
|
|
||||||
|
print("Starting to fuzz from python!")
|
||||||
|
fuzzer = sugar.InMemoryBytesCoverageSugar(input_dirs=["./in"], output_dir="out", broker_port=1337, cores=[0,1])
|
||||||
|
fuzzer.run(lambda b: print("foo"))
|
14
bindings/pylibafl/test.sh
Executable file
14
bindings/pylibafl/test.sh
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
mkdir in || true
|
||||||
|
echo "a" > ./in/a
|
||||||
|
|
||||||
|
timeout 10 python3 ./test.py
|
||||||
|
export exit_code=$?
|
||||||
|
if [ $exit_code -eq 124 ]; then
|
||||||
|
# 124 = timeout happened. All good.
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit $exit_code
|
||||||
|
fi
|
||||||
|
|
14
fuzzers/python_qemu/fuzz.c
Normal file
14
fuzzers/python_qemu/fuzz.c
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||||
|
// printf("Got %ld bytes.\n", Size);
|
||||||
|
if (Size >= 4 && *(uint32_t *)Data == 0xaabbccdd) { abort(); }
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
char buf[10] = {0};
|
||||||
|
LLVMFuzzerTestOneInput(buf, 10);
|
||||||
|
}
|
43
fuzzers/python_qemu/fuzzer.py
Normal file
43
fuzzers/python_qemu/fuzzer.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# from the maturin venv, after running 'maturin develop' in the pylibafl directory
|
||||||
|
|
||||||
|
from pylibafl import sugar, qemu
|
||||||
|
import lief
|
||||||
|
|
||||||
|
MAX_SIZE = 0x100
|
||||||
|
BINARY_PATH = './a.out'
|
||||||
|
|
||||||
|
emu = qemu.Emulator(['qemu-x86_64', BINARY_PATH], [])
|
||||||
|
|
||||||
|
elf = lief.parse(BINARY_PATH)
|
||||||
|
test_one_input = elf.get_function_address("LLVMFuzzerTestOneInput")
|
||||||
|
if elf.is_pie:
|
||||||
|
test_one_input += emu.load_addr()
|
||||||
|
print('LLVMFuzzerTestOneInput @ 0x%x' % test_one_input)
|
||||||
|
|
||||||
|
emu.set_breakpoint(test_one_input)
|
||||||
|
emu.run()
|
||||||
|
|
||||||
|
sp = emu.read_reg(qemu.regs.Rsp)
|
||||||
|
print('SP = 0x%x' % sp)
|
||||||
|
|
||||||
|
retaddr = int.from_bytes(emu.read_mem(sp, 8), 'little')
|
||||||
|
print('RET = 0x%x' % retaddr)
|
||||||
|
|
||||||
|
inp = emu.map_private(0, MAX_SIZE, qemu.mmap.ReadWrite)
|
||||||
|
assert(inp > 0)
|
||||||
|
|
||||||
|
emu.remove_breakpoint(test_one_input)
|
||||||
|
emu.set_breakpoint(retaddr)
|
||||||
|
|
||||||
|
def harness(b):
|
||||||
|
if len(b) > MAX_SIZE:
|
||||||
|
b = b[:MAX_SIZE]
|
||||||
|
emu.write_mem(inp, b)
|
||||||
|
emu.write_reg(qemu.regs.Rsi, len(b))
|
||||||
|
emu.write_reg(qemu.regs.Rdi, inp)
|
||||||
|
emu.write_reg(qemu.regs.Rsp, sp)
|
||||||
|
emu.write_reg(qemu.regs.Rip, test_one_input)
|
||||||
|
emu.run()
|
||||||
|
|
||||||
|
fuzz = sugar.QemuBytesCoverageSugar(['./in'], './out', 3456, [0,1,2,3])
|
||||||
|
fuzz.run(emu, harness)
|
@ -35,6 +35,9 @@ derive = ["libafl_derive"]
|
|||||||
## If set, libafl_bolt's `rand` implementations will implement `rand::Rng`
|
## If set, libafl_bolt's `rand` implementations will implement `rand::Rng`
|
||||||
rand_trait = ["rand_core"]
|
rand_trait = ["rand_core"]
|
||||||
|
|
||||||
|
## Will build the `pyo3` bindings
|
||||||
|
python = ["pyo3", "std"]
|
||||||
|
|
||||||
## Expose `libafl::prelude` for direct access to all types without additional `use` directives
|
## Expose `libafl::prelude` for direct access to all types without additional `use` directives
|
||||||
prelude = []
|
prelude = []
|
||||||
|
|
||||||
@ -113,6 +116,8 @@ uuid = { version = "1.4", optional = true, features = ["serde", "v4"] }
|
|||||||
clap = {version = "4.5", features = ["derive", "wrap_help"], optional = true} # CLI parsing, for libafl_bolts::cli / the `cli` feature
|
clap = {version = "4.5", features = ["derive", "wrap_help"], optional = true} # CLI parsing, for libafl_bolts::cli / the `cli` feature
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
|
|
||||||
|
pyo3 = { version = "0.18", optional = true, features = ["serde", "macros"] }
|
||||||
|
|
||||||
# optional-dev deps (change when target.'cfg(accessible(::std))'.test-dependencies will be stable)
|
# optional-dev deps (change when target.'cfg(accessible(::std))'.test-dependencies will be stable)
|
||||||
serial_test = { version = "2", optional = true, default-features = false, features = ["logging"] }
|
serial_test = { version = "2", optional = true, default-features = false, features = ["logging"] }
|
||||||
|
|
||||||
|
@ -611,6 +611,22 @@ impl From<windows::core::Error> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
impl From<pyo3::PyErr> for Error {
|
||||||
|
fn from(err: pyo3::PyErr) -> Self {
|
||||||
|
pyo3::Python::with_gil(|py| {
|
||||||
|
if err.matches(
|
||||||
|
py,
|
||||||
|
pyo3::types::PyType::new::<pyo3::exceptions::PyKeyboardInterrupt>(py),
|
||||||
|
) {
|
||||||
|
Self::shutting_down()
|
||||||
|
} else {
|
||||||
|
Self::illegal_state(format!("Python exception: {err:?}"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(all(not(nightly), feature = "std"))]
|
#[cfg(all(not(nightly), feature = "std"))]
|
||||||
impl std::error::Error for Error {}
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
@ -1022,6 +1038,148 @@ pub unsafe fn set_error_print_panic_hook(new_stderr: RawFd) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub mod pybind {
|
||||||
|
|
||||||
|
use pyo3::{pymodule, types::PyModule, PyResult, Python};
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! unwrap_me_body {
|
||||||
|
($wrapper:expr, $name:ident, $body:block, $wrapper_type:ident, { $($wrapper_option:tt),* }) => {
|
||||||
|
match &$wrapper {
|
||||||
|
$(
|
||||||
|
$wrapper_type::$wrapper_option(py_wrapper) => {
|
||||||
|
Python::with_gil(|py| -> PyResult<_> {
|
||||||
|
let borrowed = py_wrapper.borrow(py);
|
||||||
|
let $name = &borrowed.inner;
|
||||||
|
Ok($body)
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($wrapper:expr, $name:ident, $body:block, $wrapper_type:ident, { $($wrapper_option:tt),* }, { $($wrapper_optional:tt($pw:ident) => $code_block:block)* }) => {
|
||||||
|
match &$wrapper {
|
||||||
|
$(
|
||||||
|
$wrapper_type::$wrapper_option(py_wrapper) => {
|
||||||
|
Python::with_gil(|py| -> PyResult<_> {
|
||||||
|
let borrowed = py_wrapper.borrow(py);
|
||||||
|
let $name = &borrowed.inner;
|
||||||
|
Ok($body)
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
$($wrapper_type::$wrapper_optional($pw) => { $code_block })*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! unwrap_me_mut_body {
|
||||||
|
($wrapper:expr, $name:ident, $body:block, $wrapper_type:ident, { $($wrapper_option:tt),*}) => {
|
||||||
|
match &mut $wrapper {
|
||||||
|
$(
|
||||||
|
$wrapper_type::$wrapper_option(py_wrapper) => {
|
||||||
|
Python::with_gil(|py| -> PyResult<_> {
|
||||||
|
let mut borrowed = py_wrapper.borrow_mut(py);
|
||||||
|
let $name = &mut borrowed.inner;
|
||||||
|
Ok($body)
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($wrapper:expr, $name:ident, $body:block, $wrapper_type:ident, { $($wrapper_option:tt),*}, { $($wrapper_optional:tt($pw:ident) => $code_block:block)* }) => {
|
||||||
|
match &mut $wrapper {
|
||||||
|
$(
|
||||||
|
$wrapper_type::$wrapper_option(py_wrapper) => {
|
||||||
|
Python::with_gil(|py| -> PyResult<_> {
|
||||||
|
let mut borrowed = py_wrapper.borrow_mut(py);
|
||||||
|
let $name = &mut borrowed.inner;
|
||||||
|
Ok($body)
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
$($wrapper_type::$wrapper_optional($pw) => { $code_block })*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! impl_serde_pyobjectwrapper {
|
||||||
|
($struct_name:ident, $inner:tt) => {
|
||||||
|
const _: () = {
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
|
impl Serialize for $struct_name {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let buf = Python::with_gil(|py| -> PyResult<Vec<u8>> {
|
||||||
|
let pickle = PyModule::import(py, "pickle")?;
|
||||||
|
let buf: Vec<u8> =
|
||||||
|
pickle.getattr("dumps")?.call1((&self.$inner,))?.extract()?;
|
||||||
|
Ok(buf)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
serializer.serialize_bytes(&buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PyObjectVisitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for PyObjectVisitor {
|
||||||
|
type Value = $struct_name;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter
|
||||||
|
.write_str("Expecting some bytes to deserialize from the Python side")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
let obj = Python::with_gil(|py| -> PyResult<PyObject> {
|
||||||
|
let pickle = PyModule::import(py, "pickle")?;
|
||||||
|
let obj = pickle.getattr("loads")?.call1((v,))?.to_object(py);
|
||||||
|
Ok(obj)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
Ok($struct_name::new(obj))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for $struct_name {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_byte_buf(PyObjectVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymodule]
|
||||||
|
#[pyo3(name = "libafl_bolts")]
|
||||||
|
/// Register the classes to the python module
|
||||||
|
pub fn python_module(py: Python, m: &PyModule) -> PyResult<()> {
|
||||||
|
crate::rands::pybind::register(py, m)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
@ -433,3 +433,92 @@ mod tests {
|
|||||||
log::info!("random value: {}", mutator.rng.next_u32());
|
log::info!("random value: {}", mutator.rng.next_u32());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
#[allow(clippy::unnecessary_fallible_conversions, unused_qualifications)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
/// `Rand` Python bindings
|
||||||
|
pub mod pybind {
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::Rand;
|
||||||
|
use crate::{current_nanos, rands::StdRand};
|
||||||
|
|
||||||
|
#[pyclass(unsendable, name = "StdRand")]
|
||||||
|
#[allow(clippy::unsafe_derive_deserialize)]
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
/// Python class for StdRand
|
||||||
|
pub struct PythonStdRand {
|
||||||
|
/// Rust wrapped StdRand object
|
||||||
|
pub inner: StdRand,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PythonStdRand {
|
||||||
|
#[staticmethod]
|
||||||
|
fn with_current_nanos() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: StdRand::with_seed(current_nanos()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
fn with_seed(seed: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: StdRand::with_seed(seed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_rand(slf: Py<Self>) -> PythonRand {
|
||||||
|
PythonRand::new_std(slf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
enum PythonRandWrapper {
|
||||||
|
Std(Py<PythonStdRand>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rand Trait binding
|
||||||
|
#[pyclass(unsendable, name = "Rand")]
|
||||||
|
#[allow(clippy::unsafe_derive_deserialize)]
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct PythonRand {
|
||||||
|
wrapper: PythonRandWrapper,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! unwrap_me_mut {
|
||||||
|
($wrapper:expr, $name:ident, $body:block) => {
|
||||||
|
crate::unwrap_me_mut_body!($wrapper, $name, $body, PythonRandWrapper, { Std })
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PythonRand {
|
||||||
|
#[staticmethod]
|
||||||
|
fn new_std(py_std_rand: Py<PythonStdRand>) -> Self {
|
||||||
|
Self {
|
||||||
|
wrapper: PythonRandWrapper::Std(py_std_rand),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rand for PythonRand {
|
||||||
|
fn set_seed(&mut self, seed: u64) {
|
||||||
|
unwrap_me_mut!(self.wrapper, r, { r.set_seed(seed) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn next(&mut self) -> u64 {
|
||||||
|
unwrap_me_mut!(self.wrapper, r, { r.next() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register the classes to the python module
|
||||||
|
pub fn register(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||||
|
m.add_class::<PythonStdRand>()?;
|
||||||
|
m.add_class::<PythonRand>()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -12,7 +12,7 @@ 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", "default", "x86_64", "usermode"]
|
features = ["document-features", "default", "python", "x86_64", "usermode"]
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
@ -24,6 +24,8 @@ document-features = ["dep:document-features"]
|
|||||||
#! ### General Features
|
#! ### General Features
|
||||||
## Find injections during fuzzing
|
## Find injections during fuzzing
|
||||||
injections = ["serde_yaml", "toml"]
|
injections = ["serde_yaml", "toml"]
|
||||||
|
## Python bindings support
|
||||||
|
python = ["pyo3", "pyo3-build-config", "libafl_qemu_sys/python"]
|
||||||
## Fork support
|
## Fork support
|
||||||
fork = ["libafl/fork"]
|
fork = ["libafl/fork"]
|
||||||
## Build libqasan for address sanitization
|
## Build libqasan for address sanitization
|
||||||
@ -85,10 +87,12 @@ paste = "1"
|
|||||||
enum-map = "2.7"
|
enum-map = "2.7"
|
||||||
serde_yaml = { version = "0.8", optional = true } # For parsing the injections yaml file
|
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
|
toml = { version = "0.4.2", optional = true } # For parsing the injections toml file
|
||||||
|
pyo3 = { version = "0.18", optional = true }
|
||||||
# Document all features of this crate (for `cargo doc`)
|
# Document all features of this crate (for `cargo doc`)
|
||||||
document-features = { version = "0.2", optional = true }
|
document-features = { version = "0.2", optional = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
pyo3-build-config = { version = "0.18", optional = true }
|
||||||
rustversion = "1.0"
|
rustversion = "1.0"
|
||||||
bindgen = "0.69"
|
bindgen = "0.69"
|
||||||
|
|
||||||
|
@ -30,6 +30,8 @@ be = []
|
|||||||
usermode = []
|
usermode = []
|
||||||
systemmode = []
|
systemmode = []
|
||||||
|
|
||||||
|
python = ["pyo3", "pyo3-build-config"]
|
||||||
|
|
||||||
slirp = [ "systemmode", "libafl_qemu_build/slirp" ] # build qemu with host libslirp (for user networking)
|
slirp = [ "systemmode", "libafl_qemu_build/slirp" ] # build qemu with host libslirp (for user networking)
|
||||||
shared = [ "libafl_qemu_build/shared" ]
|
shared = [ "libafl_qemu_build/shared" ]
|
||||||
|
|
||||||
@ -41,6 +43,8 @@ num_enum = "0.7"
|
|||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
strum = "0.25"
|
strum = "0.25"
|
||||||
strum_macros = "0.25"
|
strum_macros = "0.25"
|
||||||
|
pyo3 = { version = "0.18", optional = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
libafl_qemu_build = { path = "../libafl_qemu_build", version = "0.11.2" }
|
libafl_qemu_build = { path = "../libafl_qemu_build", version = "0.11.2" }
|
||||||
|
pyo3-build-config = { version = "0.18", optional = true }
|
||||||
|
@ -121,6 +121,7 @@ pub type GuestVirtAddr = crate::vaddr;
|
|||||||
pub type GuestHwAddrInfo = crate::qemu_plugin_hwaddr;
|
pub type GuestHwAddrInfo = crate::qemu_plugin_hwaddr;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
#[cfg_attr(feature = "python", pyclass(unsendable))]
|
||||||
pub struct MapInfo {
|
pub struct MapInfo {
|
||||||
start: GuestAddr,
|
start: GuestAddr,
|
||||||
end: GuestAddr,
|
end: GuestAddr,
|
||||||
@ -207,6 +208,7 @@ extern_c_checked! {
|
|||||||
pub fn libafl_qemu_gdb_reply(buf: *const u8, len: usize);
|
pub fn libafl_qemu_gdb_reply(buf: *const u8, len: usize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "python", pymethods)]
|
||||||
impl MapInfo {
|
impl MapInfo {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn start(&self) -> GuestAddr {
|
pub fn start(&self) -> GuestAddr {
|
||||||
@ -282,3 +284,11 @@ impl MmapPerms {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
impl IntoPy<PyObject> for MmapPerms {
|
||||||
|
fn into_py(self, py: Python) -> PyObject {
|
||||||
|
let n: i32 = self.into();
|
||||||
|
n.into_py(py)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,6 +3,8 @@ use std::sync::OnceLock;
|
|||||||
use capstone::arch::BuildsCapstone;
|
use capstone::arch::BuildsCapstone;
|
||||||
use enum_map::{enum_map, EnumMap};
|
use enum_map::{enum_map, EnumMap};
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
use pyo3::prelude::*;
|
||||||
pub use strum_macros::EnumIter;
|
pub use strum_macros::EnumIter;
|
||||||
pub use syscall_numbers::aarch64::*;
|
pub use syscall_numbers::aarch64::*;
|
||||||
|
|
||||||
@ -71,6 +73,14 @@ impl Regs {
|
|||||||
pub const Lr: Regs = Regs::X30;
|
pub const Lr: Regs = Regs::X30;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
impl IntoPy<PyObject> for Regs {
|
||||||
|
fn into_py(self, py: Python) -> PyObject {
|
||||||
|
let n: i32 = self.into();
|
||||||
|
n.into_py(py)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return an ARM64 ArchCapstoneBuilder
|
/// Return an ARM64 ArchCapstoneBuilder
|
||||||
pub fn capstone() -> capstone::arch::arm64::ArchCapstoneBuilder {
|
pub fn capstone() -> capstone::arch::arm64::ArchCapstoneBuilder {
|
||||||
capstone::Capstone::new()
|
capstone::Capstone::new()
|
||||||
|
@ -3,6 +3,8 @@ use std::sync::OnceLock;
|
|||||||
use capstone::arch::BuildsCapstone;
|
use capstone::arch::BuildsCapstone;
|
||||||
use enum_map::{enum_map, EnumMap};
|
use enum_map::{enum_map, EnumMap};
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
use pyo3::prelude::*;
|
||||||
pub use strum_macros::EnumIter;
|
pub use strum_macros::EnumIter;
|
||||||
pub use syscall_numbers::arm::*;
|
pub use syscall_numbers::arm::*;
|
||||||
|
|
||||||
@ -61,6 +63,14 @@ impl Regs {
|
|||||||
pub const Cpsr: Regs = Regs::R25;
|
pub const Cpsr: Regs = Regs::R25;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
impl IntoPy<PyObject> for Regs {
|
||||||
|
fn into_py(self, py: Python) -> PyObject {
|
||||||
|
let n: i32 = self.into();
|
||||||
|
n.into_py(py)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return an ARM ArchCapstoneBuilder
|
/// Return an ARM ArchCapstoneBuilder
|
||||||
pub fn capstone() -> capstone::arch::arm::ArchCapstoneBuilder {
|
pub fn capstone() -> capstone::arch::arm::ArchCapstoneBuilder {
|
||||||
capstone::Capstone::new()
|
capstone::Capstone::new()
|
||||||
|
@ -352,6 +352,9 @@ impl From<libafl_qemu_sys::MemOpIdx> for MemAccessInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
pub const SKIP_EXEC_HOOK: u64 = u64::MAX;
|
pub const SKIP_EXEC_HOOK: u64 = u64::MAX;
|
||||||
|
|
||||||
pub use libafl_qemu_sys::{CPUArchState, CPUState};
|
pub use libafl_qemu_sys::{CPUArchState, CPUState};
|
||||||
@ -360,11 +363,33 @@ use crate::sync_backdoor::{SyncBackdoor, SyncBackdoorError};
|
|||||||
|
|
||||||
// syshook_ret
|
// syshook_ret
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
#[cfg_attr(feature = "python", pyclass)]
|
||||||
|
#[cfg_attr(feature = "python", derive(FromPyObject))]
|
||||||
pub struct SyscallHookResult {
|
pub struct SyscallHookResult {
|
||||||
pub retval: GuestAddr,
|
pub retval: GuestAddr,
|
||||||
pub skip_syscall: bool,
|
pub skip_syscall: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
#[pymethods]
|
||||||
|
impl SyscallHookResult {
|
||||||
|
#[new]
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(value: Option<GuestAddr>) -> Self {
|
||||||
|
value.map_or(
|
||||||
|
Self {
|
||||||
|
retval: 0,
|
||||||
|
skip_syscall: false,
|
||||||
|
},
|
||||||
|
|v| Self {
|
||||||
|
retval: v,
|
||||||
|
skip_syscall: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "python"))]
|
||||||
impl SyscallHookResult {
|
impl SyscallHookResult {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(value: Option<GuestAddr>) -> Self {
|
pub fn new(value: Option<GuestAddr>) -> Self {
|
||||||
@ -625,25 +650,25 @@ pub struct HookData(u64);
|
|||||||
|
|
||||||
impl<T> From<Pin<&mut T>> for HookData {
|
impl<T> From<Pin<&mut T>> for HookData {
|
||||||
fn from(value: Pin<&mut T>) -> Self {
|
fn from(value: Pin<&mut T>) -> Self {
|
||||||
unsafe { HookData(transmute::<Pin<&mut T>, u64>(value)) }
|
unsafe { HookData(core::mem::transmute(value)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<Pin<&T>> for HookData {
|
impl<T> From<Pin<&T>> for HookData {
|
||||||
fn from(value: Pin<&T>) -> Self {
|
fn from(value: Pin<&T>) -> Self {
|
||||||
unsafe { HookData(transmute::<Pin<&T>, u64>(value)) }
|
unsafe { HookData(core::mem::transmute(value)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<&'static mut T> for HookData {
|
impl<T> From<&'static mut T> for HookData {
|
||||||
fn from(value: &'static mut T) -> Self {
|
fn from(value: &'static mut T) -> Self {
|
||||||
unsafe { HookData(transmute::<&mut T, u64>(value)) }
|
unsafe { HookData(core::mem::transmute(value)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<&'static T> for HookData {
|
impl<T> From<&'static T> for HookData {
|
||||||
fn from(value: &'static T) -> Self {
|
fn from(value: &'static T) -> Self {
|
||||||
unsafe { HookData(transmute::<&T, u64>(value)) }
|
unsafe { HookData(core::mem::transmute(value)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -903,7 +928,7 @@ impl Qemu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[allow(clippy::missing_transmute_annotations)]
|
|
||||||
fn post_run(&self) -> Result<QemuExitReason, QemuExitReasonError> {
|
fn post_run(&self) -> Result<QemuExitReason, QemuExitReasonError> {
|
||||||
let exit_reason = unsafe { libafl_get_exit_reason() };
|
let exit_reason = unsafe { libafl_get_exit_reason() };
|
||||||
if exit_reason.is_null() {
|
if exit_reason.is_null() {
|
||||||
@ -1718,3 +1743,190 @@ where
|
|||||||
// self.qemu.write_function_argument(conv, idx, val)
|
// self.qemu.write_function_argument(conv, idx, val)
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
pub mod pybind {
|
||||||
|
use pyo3::{exceptions::PyValueError, prelude::*, types::PyInt};
|
||||||
|
|
||||||
|
use super::{GuestAddr, GuestUsize, MmapPerms, SyscallHookResult};
|
||||||
|
|
||||||
|
static mut PY_SYSCALL_HOOK: Option<PyObject> = None;
|
||||||
|
static mut PY_GENERIC_HOOKS: Vec<(GuestAddr, PyObject)> = vec![];
|
||||||
|
|
||||||
|
extern "C" fn py_syscall_hook_wrapper(
|
||||||
|
_data: u64,
|
||||||
|
sys_num: i32,
|
||||||
|
a0: u64,
|
||||||
|
a1: u64,
|
||||||
|
a2: u64,
|
||||||
|
a3: u64,
|
||||||
|
a4: u64,
|
||||||
|
a5: u64,
|
||||||
|
a6: u64,
|
||||||
|
a7: u64,
|
||||||
|
) -> SyscallHookResult {
|
||||||
|
unsafe { PY_SYSCALL_HOOK.as_ref() }.map_or_else(
|
||||||
|
|| SyscallHookResult::new(None),
|
||||||
|
|obj| {
|
||||||
|
let args = (sys_num, a0, a1, a2, a3, a4, a5, a6, a7);
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let ret = obj.call1(py, args).expect("Error in the syscall hook");
|
||||||
|
let any = ret.as_ref(py);
|
||||||
|
if any.is_none() {
|
||||||
|
SyscallHookResult::new(None)
|
||||||
|
} else {
|
||||||
|
let a: Result<&PyInt, _> = any.downcast();
|
||||||
|
if let Ok(i) = a {
|
||||||
|
SyscallHookResult::new(Some(
|
||||||
|
i.extract().expect("Invalid syscall hook return value"),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
SyscallHookResult::extract(any)
|
||||||
|
.expect("The syscall hook must return a SyscallHookResult")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn py_generic_hook_wrapper(idx: u64, _pc: GuestAddr) {
|
||||||
|
let obj = unsafe { &PY_GENERIC_HOOKS[idx as usize].1 };
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
obj.call0(py).expect("Error in the hook");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(unsendable)]
|
||||||
|
pub struct Qemu {
|
||||||
|
pub qemu: super::Qemu,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl Qemu {
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
|
#[new]
|
||||||
|
fn new(args: Vec<String>, env: Vec<(String, String)>) -> PyResult<Qemu> {
|
||||||
|
let qemu = super::Qemu::init(&args, &env)
|
||||||
|
.map_err(|e| PyValueError::new_err(format!("{e}")))?;
|
||||||
|
|
||||||
|
Ok(Qemu { qemu })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_mem(&self, addr: GuestAddr, buf: &[u8]) {
|
||||||
|
unsafe {
|
||||||
|
self.qemu.write_mem(addr, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_mem(&self, addr: GuestAddr, size: usize) -> Vec<u8> {
|
||||||
|
let mut buf = vec![0; size];
|
||||||
|
unsafe {
|
||||||
|
self.qemu.read_mem(addr, &mut buf);
|
||||||
|
}
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
fn num_regs(&self) -> i32 {
|
||||||
|
self.qemu.num_regs()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_reg(&self, reg: i32, val: GuestUsize) -> PyResult<()> {
|
||||||
|
self.qemu.write_reg(reg, val).map_err(PyValueError::new_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_reg(&self, reg: i32) -> PyResult<GuestUsize> {
|
||||||
|
self.qemu.read_reg(reg).map_err(PyValueError::new_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_breakpoint(&self, addr: GuestAddr) {
|
||||||
|
self.qemu.set_breakpoint(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn entry_break(&self, addr: GuestAddr) {
|
||||||
|
self.qemu.entry_break(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_breakpoint(&self, addr: GuestAddr) {
|
||||||
|
self.qemu.remove_breakpoint(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn g2h(&self, addr: GuestAddr) -> u64 {
|
||||||
|
self.qemu.g2h::<*const u8>(addr) as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn h2g(&self, addr: u64) -> GuestAddr {
|
||||||
|
self.qemu.h2g(addr as *const u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn binary_path(&self) -> String {
|
||||||
|
self.qemu.binary_path().to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_addr(&self) -> GuestAddr {
|
||||||
|
self.qemu.load_addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush_jit(&self) {
|
||||||
|
self.qemu.flush_jit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_private(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult<GuestAddr> {
|
||||||
|
if let Ok(p) = MmapPerms::try_from(perms) {
|
||||||
|
self.qemu
|
||||||
|
.map_private(addr, size, p)
|
||||||
|
.map_err(PyValueError::new_err)
|
||||||
|
} else {
|
||||||
|
Err(PyValueError::new_err("Invalid perms"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_fixed(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult<GuestAddr> {
|
||||||
|
if let Ok(p) = MmapPerms::try_from(perms) {
|
||||||
|
self.qemu
|
||||||
|
.map_fixed(addr, size, p)
|
||||||
|
.map_err(PyValueError::new_err)
|
||||||
|
} else {
|
||||||
|
Err(PyValueError::new_err("Invalid perms"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mprotect(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult<()> {
|
||||||
|
if let Ok(p) = MmapPerms::try_from(perms) {
|
||||||
|
self.qemu
|
||||||
|
.mprotect(addr, size, p)
|
||||||
|
.map_err(PyValueError::new_err)
|
||||||
|
} else {
|
||||||
|
Err(PyValueError::new_err("Invalid perms"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unmap(&self, addr: GuestAddr, size: usize) -> PyResult<()> {
|
||||||
|
self.qemu.unmap(addr, size).map_err(PyValueError::new_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_syscall_hook(&self, hook: PyObject) {
|
||||||
|
unsafe {
|
||||||
|
PY_SYSCALL_HOOK = Some(hook);
|
||||||
|
}
|
||||||
|
self.qemu
|
||||||
|
.add_pre_syscall_hook(0u64, py_syscall_hook_wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_hook(&self, addr: GuestAddr, hook: PyObject) {
|
||||||
|
unsafe {
|
||||||
|
let idx = PY_GENERIC_HOOKS.len();
|
||||||
|
PY_GENERIC_HOOKS.push((addr, hook));
|
||||||
|
self.qemu
|
||||||
|
.set_hook(idx as u64, addr, py_generic_hook_wrapper, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_hooks_at(&self, addr: GuestAddr) -> usize {
|
||||||
|
unsafe {
|
||||||
|
PY_GENERIC_HOOKS.retain(|(a, _)| *a != addr);
|
||||||
|
}
|
||||||
|
self.qemu.remove_hooks_at(addr, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,11 +3,12 @@ use std::{cell::OnceCell, slice::from_raw_parts, str::from_utf8_unchecked};
|
|||||||
|
|
||||||
use libafl_qemu_sys::{
|
use libafl_qemu_sys::{
|
||||||
exec_path, free_self_maps, guest_base, libafl_dump_core_hook, libafl_force_dfl, libafl_get_brk,
|
exec_path, free_self_maps, guest_base, libafl_dump_core_hook, libafl_force_dfl, libafl_get_brk,
|
||||||
libafl_load_addr, libafl_maps_first, libafl_maps_next, libafl_qemu_run, libafl_set_brk,
|
libafl_load_addr, libafl_maps_next, libafl_qemu_run, libafl_set_brk, mmap_next_start,
|
||||||
mmap_next_start, read_self_maps, strlen, GuestAddr, GuestUsize, MapInfo, MmapPerms,
|
read_self_maps, strlen, GuestAddr, GuestUsize, MapInfo, MmapPerms, VerifyAccess,
|
||||||
VerifyAccess,
|
|
||||||
};
|
};
|
||||||
use libc::c_int;
|
use libc::c_int;
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
emu::{HasExecutions, State},
|
emu::{HasExecutions, State},
|
||||||
@ -23,6 +24,7 @@ pub enum HandlerError {
|
|||||||
MultipleInputDefinition,
|
MultipleInputDefinition,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "python", pyclass(unsendable))]
|
||||||
pub struct GuestMaps {
|
pub struct GuestMaps {
|
||||||
maps_root: *const c_void,
|
maps_root: *const c_void,
|
||||||
maps_node: *const c_void,
|
maps_node: *const c_void,
|
||||||
@ -59,6 +61,17 @@ impl Iterator for GuestMaps {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
#[pymethods]
|
||||||
|
impl GuestMaps {
|
||||||
|
fn __iter__(slf: PyRef<Self>) -> PyRef<Self> {
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
fn __next__(mut slf: PyRefMut<Self>) -> Option<PyObject> {
|
||||||
|
Python::with_gil(|py| slf.next().map(|x| x.into_py(py)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Drop for GuestMaps {
|
impl Drop for GuestMaps {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -2,6 +2,8 @@ use std::sync::OnceLock;
|
|||||||
|
|
||||||
use enum_map::{enum_map, EnumMap};
|
use enum_map::{enum_map, EnumMap};
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
use pyo3::prelude::*;
|
||||||
pub use strum_macros::EnumIter;
|
pub use strum_macros::EnumIter;
|
||||||
|
|
||||||
use crate::{sync_backdoor::BackdoorArgs, CallingConvention};
|
use crate::{sync_backdoor::BackdoorArgs, CallingConvention};
|
||||||
|
@ -3,6 +3,8 @@ use std::{mem::size_of, sync::OnceLock};
|
|||||||
use capstone::arch::BuildsCapstone;
|
use capstone::arch::BuildsCapstone;
|
||||||
use enum_map::{enum_map, EnumMap};
|
use enum_map::{enum_map, EnumMap};
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
use pyo3::prelude::*;
|
||||||
pub use strum_macros::EnumIter;
|
pub use strum_macros::EnumIter;
|
||||||
pub use syscall_numbers::x86::*;
|
pub use syscall_numbers::x86::*;
|
||||||
|
|
||||||
@ -47,6 +49,14 @@ impl Regs {
|
|||||||
pub const Pc: Regs = Regs::Eip;
|
pub const Pc: Regs = Regs::Eip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
impl IntoPy<PyObject> for Regs {
|
||||||
|
fn into_py(self, py: Python) -> PyObject {
|
||||||
|
let n: i32 = self.into();
|
||||||
|
n.into_py(py)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return an X86 ArchCapstoneBuilder
|
/// Return an X86 ArchCapstoneBuilder
|
||||||
pub fn capstone() -> capstone::arch::x86::ArchCapstoneBuilder {
|
pub fn capstone() -> capstone::arch::x86::ArchCapstoneBuilder {
|
||||||
capstone::Capstone::new()
|
capstone::Capstone::new()
|
||||||
|
@ -131,3 +131,33 @@ pub fn filter_qemu_args() -> Vec<String> {
|
|||||||
}
|
}
|
||||||
args
|
args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
#[pymodule]
|
||||||
|
#[pyo3(name = "libafl_qemu")]
|
||||||
|
#[allow(clippy::items_after_statements, clippy::too_many_lines)]
|
||||||
|
pub fn python_module(py: Python, m: &PyModule) -> PyResult<()> {
|
||||||
|
let regsm = PyModule::new(py, "regs")?;
|
||||||
|
for r in Regs::iter() {
|
||||||
|
let v: i32 = r.into();
|
||||||
|
regsm.add(&format!("{r:?}"), v)?;
|
||||||
|
}
|
||||||
|
m.add_submodule(regsm)?;
|
||||||
|
|
||||||
|
let mmapm = PyModule::new(py, "mmap")?;
|
||||||
|
for r in emu::MmapPerms::iter() {
|
||||||
|
let v: i32 = r.into();
|
||||||
|
mmapm.add(&format!("{r:?}"), v)?;
|
||||||
|
}
|
||||||
|
m.add_submodule(mmapm)?;
|
||||||
|
|
||||||
|
m.add_class::<emu::MapInfo>()?;
|
||||||
|
m.add_class::<emu::GuestMaps>()?;
|
||||||
|
m.add_class::<emu::SyscallHookResult>()?;
|
||||||
|
m.add_class::<emu::pybind::Qemu>()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -2,6 +2,8 @@ use std::sync::OnceLock;
|
|||||||
|
|
||||||
use enum_map::{enum_map, EnumMap};
|
use enum_map::{enum_map, EnumMap};
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
use pyo3::prelude::*;
|
||||||
pub use strum_macros::EnumIter;
|
pub use strum_macros::EnumIter;
|
||||||
pub use syscall_numbers::mips::*;
|
pub use syscall_numbers::mips::*;
|
||||||
|
|
||||||
@ -70,6 +72,14 @@ impl Regs {
|
|||||||
pub const Zero: Regs = Regs::R0;
|
pub const Zero: Regs = Regs::R0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
impl IntoPy<PyObject> for Regs {
|
||||||
|
fn into_py(self, py: Python) -> PyObject {
|
||||||
|
let n: i32 = self.into();
|
||||||
|
n.into_py(py)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return an MIPS ArchCapstoneBuilder
|
/// Return an MIPS ArchCapstoneBuilder
|
||||||
pub fn capstone() -> capstone::arch::mips::ArchCapstoneBuilder {
|
pub fn capstone() -> capstone::arch::mips::ArchCapstoneBuilder {
|
||||||
capstone::Capstone::new().mips()
|
capstone::Capstone::new().mips()
|
||||||
|
@ -2,6 +2,8 @@ use std::sync::OnceLock;
|
|||||||
|
|
||||||
use enum_map::{enum_map, EnumMap};
|
use enum_map::{enum_map, EnumMap};
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
use pyo3::prelude::*;
|
||||||
pub use strum_macros::EnumIter;
|
pub use strum_macros::EnumIter;
|
||||||
pub use syscall_numbers::powerpc::*;
|
pub use syscall_numbers::powerpc::*;
|
||||||
|
|
||||||
@ -110,6 +112,14 @@ impl Regs {
|
|||||||
pub const Sp: Regs = Regs::R1;
|
pub const Sp: Regs = Regs::R1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
impl IntoPy<PyObject> for Regs {
|
||||||
|
fn into_py(self, py: Python) -> PyObject {
|
||||||
|
let n: i32 = self.into();
|
||||||
|
n.into_py(py)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return an MIPS ArchCapstoneBuilder
|
/// Return an MIPS ArchCapstoneBuilder
|
||||||
pub fn capstone() -> capstone::arch::ppc::ArchCapstoneBuilder {
|
pub fn capstone() -> capstone::arch::ppc::ArchCapstoneBuilder {
|
||||||
capstone::Capstone::new().ppc()
|
capstone::Capstone::new().ppc()
|
||||||
|
@ -3,6 +3,8 @@ use std::{mem::size_of, sync::OnceLock};
|
|||||||
use capstone::arch::BuildsCapstone;
|
use capstone::arch::BuildsCapstone;
|
||||||
use enum_map::{enum_map, EnumMap};
|
use enum_map::{enum_map, EnumMap};
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
use pyo3::prelude::*;
|
||||||
pub use strum_macros::EnumIter;
|
pub use strum_macros::EnumIter;
|
||||||
pub use syscall_numbers::x86_64::*;
|
pub use syscall_numbers::x86_64::*;
|
||||||
|
|
||||||
@ -55,6 +57,14 @@ impl Regs {
|
|||||||
pub const Pc: Regs = Regs::Rip;
|
pub const Pc: Regs = Regs::Rip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
impl IntoPy<PyObject> for Regs {
|
||||||
|
fn into_py(self, py: Python) -> PyObject {
|
||||||
|
let n: i32 = self.into();
|
||||||
|
n.into_py(py)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return an X86 `ArchCapstoneBuilder`
|
/// Return an X86 `ArchCapstoneBuilder`
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn capstone() -> capstone::arch::x86::ArchCapstoneBuilder {
|
pub fn capstone() -> capstone::arch::x86::ArchCapstoneBuilder {
|
||||||
|
@ -9,12 +9,14 @@ readme = "../README.md"
|
|||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["fuzzing"]
|
keywords = ["fuzzing"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
build = "build.rs"
|
||||||
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]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
python = ["pyo3", "libafl_qemu/python", "pyo3-build-config"]
|
||||||
default = []
|
default = []
|
||||||
|
|
||||||
# for libafl_qemu
|
# for libafl_qemu
|
||||||
@ -27,12 +29,16 @@ mips = ["libafl_qemu/mips"] # build qemu for mips (el, use with the 'be' feature
|
|||||||
ppc = ["libafl_qemu/ppc"] # build qemu for powerpc
|
ppc = ["libafl_qemu/ppc"] # build qemu for powerpc
|
||||||
hexagon = ["libafl_qemu/hexagon"] # build qemu for hexagon
|
hexagon = ["libafl_qemu/hexagon"] # build qemu for hexagon
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
pyo3-build-config = { version = "0.18", optional = true }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libafl = { path = "../libafl", version = "0.11.2" }
|
libafl = { path = "../libafl", version = "0.11.2" }
|
||||||
libafl_bolts = { path = "../libafl_bolts", version = "0.11.2" }
|
libafl_bolts = { path = "../libafl_bolts", version = "0.11.2" }
|
||||||
libafl_targets = { path = "../libafl_targets", version = "0.11.2" }
|
libafl_targets = { path = "../libafl_targets", version = "0.11.2" }
|
||||||
|
|
||||||
typed-builder = "0.16" # Implement the builder pattern at compiletime
|
typed-builder = "0.16" # Implement the builder pattern at compiletime
|
||||||
|
pyo3 = { version = "0.18", optional = true }
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
4
libafl_sugar/build.rs
Normal file
4
libafl_sugar/build.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
fn main() {
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
pyo3_build_config::add_extension_module_link_args();
|
||||||
|
}
|
@ -299,3 +299,80 @@ impl<'a> ForkserverBytesCoverageSugar<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The python bindings for this sugar
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
pub mod pybind {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use libafl_bolts::core_affinity::Cores;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
use crate::forkserver;
|
||||||
|
|
||||||
|
/// Python bindings for the `LibAFL` forkserver sugar
|
||||||
|
#[pyclass(unsendable)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ForkserverBytesCoverageSugar {
|
||||||
|
input_dirs: Vec<PathBuf>,
|
||||||
|
output_dir: PathBuf,
|
||||||
|
broker_port: u16,
|
||||||
|
cores: Cores,
|
||||||
|
use_cmplog: Option<bool>,
|
||||||
|
iterations: Option<u64>,
|
||||||
|
tokens_file: Option<PathBuf>,
|
||||||
|
timeout: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl ForkserverBytesCoverageSugar {
|
||||||
|
/// Create a new [`ForkserverBytesCoverageSugar`]
|
||||||
|
#[new]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn new(
|
||||||
|
input_dirs: Vec<PathBuf>,
|
||||||
|
output_dir: PathBuf,
|
||||||
|
broker_port: u16,
|
||||||
|
cores: Vec<usize>,
|
||||||
|
use_cmplog: Option<bool>,
|
||||||
|
iterations: Option<u64>,
|
||||||
|
tokens_file: Option<PathBuf>,
|
||||||
|
timeout: Option<u64>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
input_dirs,
|
||||||
|
output_dir,
|
||||||
|
broker_port,
|
||||||
|
cores: cores.into(),
|
||||||
|
use_cmplog,
|
||||||
|
iterations,
|
||||||
|
tokens_file,
|
||||||
|
timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the fuzzer
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
|
pub fn run(&self, program: String, arguments: Vec<String>) {
|
||||||
|
forkserver::ForkserverBytesCoverageSugar::builder()
|
||||||
|
.input_dirs(&self.input_dirs)
|
||||||
|
.output_dir(self.output_dir.clone())
|
||||||
|
.broker_port(self.broker_port)
|
||||||
|
.cores(&self.cores)
|
||||||
|
.program(program)
|
||||||
|
.arguments(&arguments)
|
||||||
|
.use_cmplog(self.use_cmplog)
|
||||||
|
.timeout(self.timeout)
|
||||||
|
.tokens_file(self.tokens_file.clone())
|
||||||
|
.iterations(self.iterations)
|
||||||
|
.build()
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register the module
|
||||||
|
pub fn register(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||||
|
m.add_class::<ForkserverBytesCoverageSugar>()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -348,3 +348,87 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Python bindings for this sugar
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
pub mod pybind {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use libafl_bolts::core_affinity::Cores;
|
||||||
|
use pyo3::{prelude::*, types::PyBytes};
|
||||||
|
|
||||||
|
use crate::inmemory;
|
||||||
|
|
||||||
|
/// In-Memory fuzzing made easy.
|
||||||
|
/// Use this sugar for scaling `libfuzzer`-style fuzzers.
|
||||||
|
#[pyclass(unsendable)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct InMemoryBytesCoverageSugar {
|
||||||
|
input_dirs: Vec<PathBuf>,
|
||||||
|
output_dir: PathBuf,
|
||||||
|
broker_port: u16,
|
||||||
|
cores: Cores,
|
||||||
|
use_cmplog: Option<bool>,
|
||||||
|
iterations: Option<u64>,
|
||||||
|
tokens_file: Option<PathBuf>,
|
||||||
|
timeout: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl InMemoryBytesCoverageSugar {
|
||||||
|
/// Create a new [`InMemoryBytesCoverageSugar`]
|
||||||
|
#[new]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn new(
|
||||||
|
input_dirs: Vec<PathBuf>,
|
||||||
|
output_dir: PathBuf,
|
||||||
|
broker_port: u16,
|
||||||
|
cores: Vec<usize>,
|
||||||
|
use_cmplog: Option<bool>,
|
||||||
|
iterations: Option<u64>,
|
||||||
|
tokens_file: Option<PathBuf>,
|
||||||
|
timeout: Option<u64>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
input_dirs,
|
||||||
|
output_dir,
|
||||||
|
broker_port,
|
||||||
|
cores: cores.into(),
|
||||||
|
use_cmplog,
|
||||||
|
iterations,
|
||||||
|
tokens_file,
|
||||||
|
timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the fuzzer
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
|
pub fn run(&self, harness: PyObject) {
|
||||||
|
inmemory::InMemoryBytesCoverageSugar::builder()
|
||||||
|
.input_dirs(&self.input_dirs)
|
||||||
|
.output_dir(self.output_dir.clone())
|
||||||
|
.broker_port(self.broker_port)
|
||||||
|
.cores(&self.cores)
|
||||||
|
.harness(|buf| {
|
||||||
|
Python::with_gil(|py| -> PyResult<()> {
|
||||||
|
let args = (PyBytes::new(py, buf),); // TODO avoid copy
|
||||||
|
harness.call1(py, args)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
})
|
||||||
|
.use_cmplog(self.use_cmplog)
|
||||||
|
.timeout(self.timeout)
|
||||||
|
.tokens_file(self.tokens_file.clone())
|
||||||
|
.iterations(self.iterations)
|
||||||
|
.build()
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register the module
|
||||||
|
pub fn register(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||||
|
m.add_class::<InMemoryBytesCoverageSugar>()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -78,3 +78,23 @@ pub const DEFAULT_TIMEOUT_SECS: u64 = 1200;
|
|||||||
/// Default cache size for the corpus in memory.
|
/// Default cache size for the corpus in memory.
|
||||||
/// Anything else will be on disk.
|
/// Anything else will be on disk.
|
||||||
pub const CORPUS_CACHE_SIZE: usize = 4096;
|
pub const CORPUS_CACHE_SIZE: usize = 4096;
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
/// The sugar python module
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
#[pymodule]
|
||||||
|
#[pyo3(name = "libafl_sugar")]
|
||||||
|
pub fn python_module(py: Python, m: &PyModule) -> PyResult<()> {
|
||||||
|
inmemory::pybind::register(py, m)?;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
qemu::pybind::register(py, m)?;
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
forkserver::pybind::register(py, m)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -437,3 +437,86 @@ where
|
|||||||
launcher.build().launch().expect("Launcher failed");
|
launcher.build().launch().expect("Launcher failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// python bindings for this sugar
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
pub mod pybind {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use libafl_bolts::core_affinity::Cores;
|
||||||
|
use libafl_qemu::emu::pybind::Qemu;
|
||||||
|
use pyo3::{prelude::*, types::PyBytes};
|
||||||
|
|
||||||
|
use crate::qemu;
|
||||||
|
|
||||||
|
#[pyclass(unsendable)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct QemuBytesCoverageSugar {
|
||||||
|
input_dirs: Vec<PathBuf>,
|
||||||
|
output_dir: PathBuf,
|
||||||
|
broker_port: u16,
|
||||||
|
cores: Cores,
|
||||||
|
use_cmplog: Option<bool>,
|
||||||
|
iterations: Option<u64>,
|
||||||
|
tokens_file: Option<PathBuf>,
|
||||||
|
timeout: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl QemuBytesCoverageSugar {
|
||||||
|
/// Create a new [`QemuBytesCoverageSugar`]
|
||||||
|
#[new]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn new(
|
||||||
|
input_dirs: Vec<PathBuf>,
|
||||||
|
output_dir: PathBuf,
|
||||||
|
broker_port: u16,
|
||||||
|
cores: Vec<usize>,
|
||||||
|
use_cmplog: Option<bool>,
|
||||||
|
iterations: Option<u64>,
|
||||||
|
tokens_file: Option<PathBuf>,
|
||||||
|
timeout: Option<u64>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
input_dirs,
|
||||||
|
output_dir,
|
||||||
|
broker_port,
|
||||||
|
cores: cores.into(),
|
||||||
|
use_cmplog,
|
||||||
|
iterations,
|
||||||
|
tokens_file,
|
||||||
|
timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the fuzzer
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
|
pub fn run(&self, qemu: &Qemu, harness: PyObject) {
|
||||||
|
qemu::QemuBytesCoverageSugar::builder()
|
||||||
|
.input_dirs(&self.input_dirs)
|
||||||
|
.output_dir(self.output_dir.clone())
|
||||||
|
.broker_port(self.broker_port)
|
||||||
|
.cores(&self.cores)
|
||||||
|
.harness(|buf| {
|
||||||
|
Python::with_gil(|py| -> PyResult<()> {
|
||||||
|
let args = (PyBytes::new(py, buf),); // TODO avoid copy
|
||||||
|
harness.call1(py, args)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
})
|
||||||
|
.use_cmplog(self.use_cmplog)
|
||||||
|
.timeout(self.timeout)
|
||||||
|
.tokens_file(self.tokens_file.clone())
|
||||||
|
.iterations(self.iterations)
|
||||||
|
.build()
|
||||||
|
.run(&qemu.qemu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register this class
|
||||||
|
pub fn register(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||||
|
m.add_class::<QemuBytesCoverageSugar>()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user