├── .gitmodules ├── .gitignore ├── .clang-format ├── pyproject.toml ├── include └── unicornafl │ ├── priv.h │ ├── cmplog.h │ └── unicornafl.h ├── Cargo.toml ├── src ├── hash.rs ├── bindings.rs ├── harness.rs ├── target.rs ├── lib.rs ├── forkserver.rs └── executor.rs ├── examples ├── sample.py └── sample.rs ├── Readme.md ├── .github └── workflows │ ├── py_test.yaml │ ├── ci.yaml │ └── py.yaml ├── docs ├── fuzzing.md ├── c-usage.md ├── python-usage.md └── rust-usage.md ├── python └── unicornafl │ └── __init__.py └── LICENSE /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | build 3 | .vscode 4 | bindings/python/unicornafl.egg-info/ 5 | bindings/python/unicornafl/__pycache__/ 6 | bindings/python/unicornafl/include/ 7 | bindings/python/unicornafl/lib/ 8 | bindings/python/dist/ 9 | build_python/ 10 | target/ 11 | Cargo.lock 12 | .gdb_history 13 | *.so 14 | __pycache__ -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | IndentWidth: 4 3 | UseTab: Never 4 | BreakBeforeBraces: Attach 5 | AllowShortIfStatementsOnASingleLine: Never 6 | AllowShortCaseLabelsOnASingleLine: false 7 | AllowShortBlocksOnASingleLine: Empty 8 | AllowShortFunctionsOnASingleLine: Empty 9 | AllowShortLoopsOnASingleLine: false 10 | IndentCaseLabels: false 11 | ColumnLimit: 80 12 | SortIncludes: false 13 | AllowShortLambdasOnASingleLine: Inline 14 | AlwaysBreakBeforeMultilineStrings: false 15 | BreakStringLiterals: true 16 | PointerAlignment: Left 17 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["maturin>=1.8,<2.0"] 3 | build-backend = "maturin" 4 | 5 | [project] 6 | name = "unicornafl" 7 | requires-python = ">=3.8" 8 | classifiers = [ 9 | "Programming Language :: Rust", 10 | "Programming Language :: Python :: Implementation :: CPython", 11 | "Programming Language :: Python :: Implementation :: PyPy", 12 | ] 13 | dynamic = ["version"] 14 | dependencies = ["unicorn>=2.1.3"] 15 | 16 | [tool.maturin] 17 | bindings = "pyo3" 18 | manifest-path = "Cargo.toml" 19 | features = ["python"] 20 | python-source = "python" 21 | compatibility = "manylinux_2_28" 22 | -------------------------------------------------------------------------------- /include/unicornafl/priv.h: -------------------------------------------------------------------------------- 1 | #ifndef UC2AFL_PRIV_H 2 | #define UC2AFL_PRIV_H 3 | 4 | #ifndef likely 5 | #if __GNUC__ < 3 6 | #define __builtin_expect(x, n) (x) 7 | #endif 8 | 9 | #define likely(x) __builtin_expect(!!(x), 1) 10 | #define unlikely(x) __builtin_expect(!!(x), 0) 11 | #endif 12 | 13 | #define mem_barrier() __asm__ volatile("" ::: "memory") 14 | 15 | /* We use one additional file descriptor to relay "needs translation" 16 | or "child done" messages between the child and the fork server. */ 17 | 18 | #define FF16 (0xFFFFFFFFFFFFFFFF) 19 | 20 | /* Copied from aflpp/types.h to talk to forkserver */ 21 | #define FS_OPT_ENABLED 0x80000001 22 | #define FS_OPT_SHDMEM_FUZZ 0x01000000 23 | #define FS_OPT_NEWCMPLOG 0x02000000 24 | 25 | /** 26 | * The correct fds for reading and writing pipes 27 | */ 28 | 29 | #define _R(pipe) ((pipe)[0]) 30 | #define _W(pipe) ((pipe)[1]) 31 | 32 | enum afl_child_ret { 33 | 34 | // Persistent 35 | AFL_CHILD_NEXT, 36 | // Crash discovered but still alive in persistent mode 37 | AFL_CHILD_FOUND_CRASH, 38 | // Read again, one afl_tsl struct. 39 | AFL_CHILD_TSL_REQUEST, 40 | // Child no longer there. Read status code. 41 | AFL_CHILD_EXITED, 42 | 43 | }; 44 | 45 | #endif -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "unicornafl" 3 | version = "3.0.0" 4 | edition = "2024" 5 | rust-version = "1.87" 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | # For macOS autotokens fix 10 | libafl_targets = { git = "https://github.com/AFLplusplus/LibAFL", rev = "c604d2dac47e84b7133e85cfd137acf2d6e0175b", features = [ 11 | "pointer_maps", 12 | "forkserver", 13 | "cmplog", 14 | "cmplog_extended_instrumentation", 15 | ] } 16 | libafl = { git = "https://github.com/AFLplusplus/LibAFL", rev = "c604d2dac47e84b7133e85cfd137acf2d6e0175b" } 17 | libafl_bolts = { git = "https://github.com/AFLplusplus/LibAFL", rev = "c604d2dac47e84b7133e85cfd137acf2d6e0175b" } 18 | # libafl_targets = { path = "../LibAFL/libafl_targets", features = [ 19 | # "pointer_maps", 20 | # "forkserver", 21 | # "cmplog", 22 | # "cmplog_extended_instrumentation", 23 | # ] } 24 | # libafl = {path = "../LibAFL/libafl"} 25 | # libafl_bolts = {path = "../LibAFL/libafl_bolts"} 26 | 27 | serde = { version = "1.0", features = ["derive"] } 28 | unicorn-engine = { git = "https://github.com/unicorn-engine/unicorn-engine-rs", rev = "4c1aff177e7846bbda3e560c18a613c2bbd0d130" } 29 | log = "0.4" 30 | nix = { version = "0.30", features = ["signal"] } 31 | env_logger = { version = "0.11", optional = true } 32 | pyo3 = { version = "0.24.0", features = [ 33 | "extension-module", 34 | "abi3-py38", 35 | ], optional = true } 36 | pyo3-log = { version = "0.12.2", optional = true } 37 | 38 | [features] 39 | default = [] 40 | bindings = ["unicorn-engine/dynamic_linkage"] 41 | python = ["pyo3", "pyo3-log", "env_logger", "bindings"] 42 | 43 | [lib] 44 | name = "unicornafl" 45 | crate-type = ["cdylib", "staticlib", "rlib"] # For python 46 | 47 | [[example]] 48 | name = "sample" 49 | 50 | [profile.release] 51 | lto = true 52 | codegen-units = 1 53 | strip = true 54 | -------------------------------------------------------------------------------- /src/hash.rs: -------------------------------------------------------------------------------- 1 | //! Adapted from original unicornafl implementation 2 | 3 | const SECRET: [u8; 192] = [ 4 | 0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c, 5 | 0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f, 6 | 0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21, 7 | 0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c, 8 | 0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3, 9 | 0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8, 10 | 0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d, 11 | 0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64, 12 | 0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb, 13 | 0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e, 14 | 0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce, 15 | 0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e, 16 | ]; 17 | 18 | fn afl_rrmxmx(mut h64: u64, len: u64) -> u64 { 19 | h64 ^= h64.rotate_left(49) ^ h64.rotate_left(24); 20 | h64 = h64.wrapping_mul(0x9FB21C651E98DF25); 21 | h64 ^= (h64 >> 35).wrapping_add(len); 22 | h64 = h64.wrapping_mul(0x9FB21C651E98DF25); 23 | h64 ^ (h64 >> 28) 24 | } 25 | 26 | pub fn afl_hash_ip(ip: u64) -> u32 { 27 | let input1 = ip as u32; 28 | let input2 = (ip >> 32) as u32; 29 | 30 | let bitflip = u64::from_le_bytes(SECRET[8..16].try_into().unwrap()) 31 | ^ u64::from_le_bytes(SECRET[16..24].try_into().unwrap()); 32 | let input64 = ((input1 as u64) << 32).wrapping_add(input2 as u64); 33 | let keyed = input64 ^ bitflip; 34 | 35 | // Mix the keyed value and return the lower 32 bits. 36 | afl_rrmxmx(keyed, 8) as u32 37 | } 38 | -------------------------------------------------------------------------------- /examples/sample.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import unicornafl 3 | from unicorn import * 4 | from unicorn.x86_const import * 5 | 6 | def place_input_callback(mu, input_bytes, persistent_round, data): 7 | # The mode we specified in the command line 8 | do_x86_64 = data 9 | if do_x86_64: 10 | if len(input_bytes) < 8: 11 | # decline the input 12 | return False 13 | buf = bytearray(8) 14 | cp_len = min(len(input_bytes), 8) 15 | buf[0:cp_len] = input_bytes[0:cp_len] 16 | rdx = int.from_bytes(buf, byteorder='little') 17 | mu.reg_write(UC_X86_REG_RDX, rdx) 18 | else: 19 | if len(input_bytes) < 4: 20 | # decline the input 21 | return False 22 | buf = bytearray(4) 23 | cp_len = min(len(input_bytes), 4) 24 | buf[0:cp_len] = input_bytes[0:cp_len] 25 | rdx = int.from_bytes(buf, byteorder='little') 26 | mu.reg_write(UC_X86_REG_EDX, rdx) 27 | 28 | if __name__ == '__main__': 29 | if len(sys.argv) == 1: 30 | input_file = None 31 | do_x86_64 = False 32 | elif len(sys.argv) == 2: 33 | input_file = sys.argv[1] 34 | do_x86_64 = False 35 | else: 36 | # If we have a second arguments, solve 8 bytes magic intead, which is more difficult. 37 | input_file = sys.argv[1] 38 | do_x86_64 = True 39 | 40 | if do_x86_64: 41 | mu = Uc(UC_ARCH_X86, UC_MODE_64) 42 | # 8 bytes magic 43 | # ks.asm("mov rax, rdx; cmp rax, 0x114514; je die; xor rax, rax; die: mov rax, [rax]; xor rax, rax") 44 | CODE = b"\x48\x89\xd0\x48\x3d\x14\x45\x11\x00\x74\x03\x48\x31\xc0\x48\x8b\x00\x48\x31\xc0" 45 | exits = [0x100b, 0x1011] 46 | else: 47 | mu = Uc(UC_ARCH_X86, UC_MODE_32) 48 | # 4 bytes magic 49 | # ks.asm("mov eax, edx; cmp eax, 0x114514; je die; xor eax, eax; die: mov eax, [eax]; xor eax, eax") 50 | CODE = b"\x89\xd0\x3d\x14\x45\x11\x00\x74\x02\x31\xc0\x8b\x00\x31\xc0" 51 | exits = [ 52 | 0x1009, # xor eax, eax after je die 53 | 0x100d # xor eax, eax in the end 54 | ] 55 | 56 | mu.mem_map(0x1000, 0x4000) 57 | mu.mem_write(0x1000, CODE) 58 | if do_x86_64: 59 | mu.reg_write(UC_X86_REG_RIP, 0x1000) 60 | else: 61 | mu.reg_write(UC_X86_REG_EIP, 0x1000) 62 | 63 | unicornafl.uc_afl_fuzz( 64 | mu, 65 | input_file, 66 | place_input_callback, 67 | exits, 68 | data=do_x86_64 69 | ) -------------------------------------------------------------------------------- /include/unicornafl/cmplog.h: -------------------------------------------------------------------------------- 1 | /* 2 | american fuzzy lop++ - cmplog header 3 | ------------------------------------ 4 | 5 | Originally written by Michal Zalewski 6 | 7 | Forkserver design by Jann Horn 8 | 9 | Now maintained by Marc Heuse , 10 | Heiko Eissfeldt , 11 | Andrea Fioraldi , 12 | Dominik Maier 13 | 14 | Copyright 2016, 2017 Google Inc. All rights reserved. 15 | Copyright 2019-2024 AFLplusplus Project. All rights reserved. 16 | 17 | Licensed under the Apache License, Version 2.0 (the "License"); 18 | you may not use this file except in compliance with the License. 19 | You may obtain a copy of the License at: 20 | 21 | https://www.apache.org/licenses/LICENSE-2.0 22 | 23 | Shared code to handle the shared memory. This is used by the fuzzer 24 | as well the other components like afl-tmin, afl-showmap, etc... 25 | 26 | */ 27 | 28 | #ifndef _AFL_CMPLOG_H 29 | #define _AFL_CMPLOG_H 30 | 31 | #include 32 | #include "config.h" 33 | 34 | #define CMPLOG_LVL_MAX 3 35 | 36 | #define CMP_MAP_W 65536 37 | #define CMP_MAP_H 32 38 | #define CMP_MAP_RTN_H (CMP_MAP_H / 2) 39 | 40 | #define SHAPE_BYTES(x) (x + 1) 41 | 42 | #define CMP_TYPE_INS 0 43 | #define CMP_TYPE_RTN 1 44 | 45 | struct cmp_header { // 16 bit = 2 bytes 46 | 47 | unsigned hits : 6; // up to 63 entries, we have CMP_MAP_H = 32 48 | unsigned shape : 5; // 31+1 bytes max 49 | unsigned type : 1; // 2: cmp, rtn 50 | unsigned attribute : 4; // 16 for arithmetic comparison types 51 | 52 | } __attribute__((packed)); 53 | 54 | struct cmp_operands { 55 | 56 | uint64_t v0; 57 | uint64_t v0_128; 58 | uint64_t v0_256_0; // u256 is unsupported by any compiler for now, so future use 59 | uint64_t v0_256_1; 60 | uint64_t v1; 61 | uint64_t v1_128; 62 | uint64_t v1_256_0; 63 | uint64_t v1_256_1; 64 | uint8_t unused[8]; // 2 bits could be used for "is constant operand" 65 | 66 | } __attribute__((packed)); 67 | 68 | struct cmpfn_operands { 69 | 70 | uint8_t v0[32]; 71 | uint8_t v0_len; 72 | uint8_t v1[32]; 73 | uint8_t v1_len; 74 | 75 | } __attribute__((packed)); 76 | 77 | typedef struct cmp_operands cmp_map_list[CMP_MAP_H]; 78 | 79 | struct cmp_map { 80 | 81 | struct cmp_header headers[CMP_MAP_W]; 82 | struct cmp_operands log[CMP_MAP_W][CMP_MAP_H]; 83 | 84 | }; 85 | 86 | /* Execs the child */ 87 | 88 | struct afl_forkserver; 89 | void cmplog_exec_child(struct afl_forkserver *fsrv, char **argv); 90 | 91 | #endif 92 | 93 | -------------------------------------------------------------------------------- /examples/sample.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use unicorn_engine::{Arch, Mode, Prot, RegisterX86, Unicorn}; 4 | use unicornafl::{afl_fuzz, executor::UnicornFuzzData}; 5 | 6 | fn place_input_cb<'a>( 7 | uc: &mut Unicorn<'a, UnicornFuzzData>, 8 | input: &[u8], 9 | _persistent_round: u64, 10 | ) -> bool { 11 | // The mode we specified in the command line 12 | let do_x86_64 = uc.get_data().user_data; 13 | if do_x86_64 { 14 | let mut buf = [0; 8]; 15 | if input.len() < 8 { 16 | // decline the input 17 | return false; 18 | } 19 | let cp_len = input.len().min(8); 20 | buf[0..cp_len].copy_from_slice(&input[0..cp_len]); 21 | let rdx = u64::from_le_bytes(buf); 22 | uc.reg_write(RegisterX86::RDX, rdx) 23 | .expect("Fail to write reg"); 24 | } else { 25 | let mut buf = [0; 4]; 26 | if input.len() < 4 { 27 | // decline the input 28 | return false; 29 | } 30 | let cp_len = input.len().min(4); 31 | buf[0..cp_len].copy_from_slice(&input[0..cp_len]); 32 | let edx = u32::from_le_bytes(buf); 33 | uc.reg_write(RegisterX86::EDX, edx as _) 34 | .expect("Fail to write reg"); 35 | } 36 | 37 | true 38 | } 39 | 40 | fn main() { 41 | let input_file = std::env::args().nth(1); 42 | // If we have a second arguments, solve 8 bytes magic intead, which is more difficult. 43 | let do_x86_64 = std::env::args().nth(2).is_some(); 44 | 45 | let mut uc = if do_x86_64 { 46 | Unicorn::new_with_data(Arch::X86, Mode::MODE_64, UnicornFuzzData::new(do_x86_64)) 47 | .expect("fail to open uc") 48 | } else { 49 | Unicorn::new_with_data(Arch::X86, Mode::MODE_32, UnicornFuzzData::new(do_x86_64)) 50 | .expect("fail to open uc") 51 | }; 52 | 53 | let code = if do_x86_64 { 54 | // 8 bytes magic 55 | // ks.asm("mov rax, rdx; cmp rax, 0x114514; je die; xor rax, rax; die: mov rax, [rax]; xor rax, rax") 56 | b"\x48\x89\xd0\x48\x3d\x14\x45\x11\x00\x74\x03\x48\x31\xc0\x48\x8b\x00\x48\x31\xc0".to_vec() 57 | } else { 58 | // 4 bytes magic 59 | // ks.asm("mov eax, edx; cmp eax, 0x114514; je die; xor eax, eax; die: mov eax, [eax]; xor eax, eax") 60 | b"\x89\xd0\x3d\x14\x45\x11\x00\x74\x02\x31\xc0\x8b\x00\x31\xc0".to_vec() 61 | }; 62 | 63 | uc.mem_map(0x1000, 0x4000, Prot::ALL).expect("fail to map"); 64 | uc.mem_write(0x1000, &code).expect("fail to write code"); 65 | let pc = 0x1000; 66 | uc.set_pc(pc).expect("fail to write pc"); 67 | 68 | let exits = if do_x86_64 { 69 | vec![0x100b, 0x1011] 70 | } else { 71 | vec![ 72 | 0x1009, // xor eax, eax after je die 73 | 0x100d, // xor eax, eax in the end 74 | ] 75 | }; 76 | 77 | let input_file = input_file.map(PathBuf::from); 78 | afl_fuzz(uc, input_file, place_input_cb, exits, Some(1)).expect("fail to fuzz?") 79 | } 80 | -------------------------------------------------------------------------------- /src/bindings.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | use nix::libc::uintptr_t; 4 | use pyo3::{ 5 | Bound, IntoPyObject, PyResult, pyfunction, pymodule, 6 | types::{PyInt, PyModule, PyModuleMethods}, 7 | wrap_pyfunction, 8 | }; 9 | 10 | use crate::uc_afl_ret; 11 | 12 | impl<'py> IntoPyObject<'py> for uc_afl_ret { 13 | type Target = PyInt; 14 | type Output = Bound<'py, Self::Target>; 15 | type Error = Infallible; 16 | 17 | fn into_pyobject(self, py: pyo3::Python<'py>) -> Result { 18 | u64::into_pyobject(self as _, py) 19 | } 20 | } 21 | 22 | #[pyfunction] 23 | pub fn uc_afl_fuzz( 24 | uc_handle: uintptr_t, 25 | input_file: uintptr_t, 26 | place_input_callback: uintptr_t, 27 | exits: uintptr_t, 28 | exit_count: usize, 29 | validate_crash_callback: Option, // will non-ffi safe with pyfunction and extern "C" 30 | always_validate: bool, 31 | persistent_iters: u64, 32 | data: uintptr_t, 33 | ) -> uc_afl_ret { 34 | crate::uc_afl_fuzz( 35 | uc_handle as _, 36 | input_file as _, 37 | unsafe { std::mem::transmute(place_input_callback) }, 38 | exits as _, 39 | exit_count, 40 | unsafe { validate_crash_callback.map(|t| std::mem::transmute(t)) }, 41 | always_validate, 42 | persistent_iters, 43 | data as _, 44 | ) 45 | } 46 | 47 | #[pyfunction] 48 | pub fn uc_afl_fuzz_custom( 49 | uc_handle: uintptr_t, 50 | input_file: uintptr_t, 51 | place_input_callback: uintptr_t, 52 | fuzz_callback: uintptr_t, 53 | validate_crash_callback: Option, 54 | always_validate: bool, 55 | persistent_iters: u64, 56 | data: uintptr_t, 57 | ) -> uc_afl_ret { 58 | crate::uc_afl_fuzz_custom( 59 | uc_handle as _, 60 | input_file as _, 61 | unsafe { std::mem::transmute(place_input_callback) }, 62 | unsafe { std::mem::transmute(fuzz_callback) }, 63 | unsafe { validate_crash_callback.map(|t| std::mem::transmute(t)) }, 64 | always_validate, 65 | persistent_iters, 66 | data as _, 67 | ) 68 | } 69 | 70 | #[pymodule] 71 | fn unicornafl(m: &Bound<'_, PyModule>) -> PyResult<()> { 72 | m.add_function(wrap_pyfunction!(uc_afl_fuzz, m)?)?; 73 | m.add_function(wrap_pyfunction!(uc_afl_fuzz_custom, m)?)?; 74 | m.add("UC_AFL_RET_OK", uc_afl_ret::UC_AFL_RET_OK as i32)?; 75 | m.add("UC_AFL_RET_ERROR", uc_afl_ret::UC_AFL_RET_ERROR as i32)?; 76 | m.add("UC_AFL_RET_CHILD", uc_afl_ret::UC_AFL_RET_CHILD as i32)?; 77 | m.add("UC_AFL_RET_NO_AFL", uc_afl_ret::UC_AFL_RET_NO_AFL as i32)?; 78 | m.add( 79 | "UC_AFL_RET_CALLED_TWICE", 80 | uc_afl_ret::UC_AFL_RET_CALLED_TWICE as i32, 81 | )?; 82 | m.add( 83 | "UC_AFL_RET_FINISHED", 84 | uc_afl_ret::UC_AFL_RET_FINISHED as i32, 85 | )?; 86 | m.add( 87 | "UC_AFL_RET_INVALID_UC", 88 | uc_afl_ret::UC_AFL_RET_INVALID_UC as i32, 89 | )?; 90 | m.add("UC_AFL_RET_UC_ERR", uc_afl_ret::UC_AFL_RET_UC_ERR as i32)?; 91 | m.add("UC_AFL_RET_LIBAFL", uc_afl_ret::UC_AFL_RET_LIBAFL as i32)?; 92 | m.add("UC_AFL_RET_FFI", uc_afl_ret::UC_AFL_RET_FFI as i32)?; 93 | Ok(()) 94 | } 95 | -------------------------------------------------------------------------------- /include/unicornafl/unicornafl.h: -------------------------------------------------------------------------------- 1 | #ifndef UNICORNAFL_H 2 | #define UNICORNAFL_H 3 | 4 | #include "unicorn/unicorn.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | #ifdef __GNUC__ 11 | #define UNICORNAFL_EXPORT __attribute__((visibility("default"))) 12 | #else 13 | #define UNICORNAFL_EXPORT 14 | #endif 15 | 16 | #define MIN_UC_VERSION 0x02000100 17 | 18 | typedef enum uc_afl_ret { 19 | UC_AFL_RET_OK = 0, 20 | UC_AFL_RET_ERROR, 21 | UC_AFL_RET_CHILD, 22 | UC_AFL_RET_NO_AFL, 23 | UC_AFL_RET_CALLED_TWICE, 24 | UC_AFL_RET_FINISHED, 25 | UC_AFL_RET_INVALID_UC, 26 | UC_AFL_RET_UC_ERR, 27 | UC_AFL_RET_LIBAFL, 28 | UC_AFL_RET_FFI, 29 | } uc_afl_ret; 30 | 31 | typedef bool (*uc_afl_cb_place_input_t)(uc_engine* uc, char* input, 32 | size_t input_len, 33 | uint32_t persistent_round, void* data); 34 | 35 | typedef bool (*uc_afl_cb_validate_crash_t)(uc_engine* uc, uc_err unicorn_result, 36 | char* input, int input_len, 37 | int persistent_round, void* data); 38 | 39 | typedef uc_err (*uc_afl_fuzz_cb_t)(uc_engine *uc, void *data); 40 | 41 | // 42 | // Start our fuzzer. 43 | // 44 | // If no afl-fuzz instance is found, this function is almost identical to uc_emu_start() 45 | // 46 | // @uc: The uc_engine return-ed from uc_open(). 47 | // @input_file: This usually is the input file name provided by the command argument. 48 | // @place_input_callback: This callback is triggered every time a new child is generated. It returns 49 | // true if the input is accepted, or the input would be skipped. 50 | // @exits: All possible exits. 51 | // @exit_count: The count of the @exits array. 52 | // @validate_crash_callback: This callback is triggered every time to check if we are crashed. 53 | // @always_validate: If this is set to False, validate_crash_callback will be only triggered if 54 | // uc_emu_start (which is called internally by uc_afl_fuzz) returns an error. Or 55 | // the validate_crash_callback will be triggered every time. 56 | // @persistent_iters: Fuzz how many times before forking a new child. 57 | // @data: The extra data user provides. 58 | // 59 | // @uc_afl_ret: The error the fuzzer returns. 60 | UNICORNAFL_EXPORT 61 | uc_afl_ret uc_afl_fuzz(uc_engine* uc, char* input_file, 62 | uc_afl_cb_place_input_t place_input_callback, 63 | uint64_t* exits, size_t exit_count, 64 | uc_afl_cb_validate_crash_t validate_crash_callback, 65 | bool always_validate, uint32_t persistent_iters, 66 | void* data); 67 | 68 | // 69 | // By default, uc_afl_fuzz internall calls uc_emu_start only once and if uc_emu_stop 70 | // is called, the child will stop fuzzing current test case. 71 | // 72 | // To implement more complex fuzzing logic, pass an extra fuzzing_callback with this API. 73 | // 74 | UNICORN_EXPORT 75 | uc_afl_ret uc_afl_fuzz_custom(uc_engine* uc, char* input_file, 76 | uc_afl_cb_place_input_t place_input_callback, 77 | uc_afl_fuzz_cb_t fuzz_callbck, 78 | uc_afl_cb_validate_crash_t validate_crash_callback, 79 | bool always_validate, uint32_t persistent_iters, 80 | void* data); 81 | 82 | #ifdef __cplusplus 83 | } 84 | #endif 85 | 86 | #endif 87 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # UnicornAFL 2 | 3 | UnicornAFL is a bridge between AFL++ and the [unicorn engine](https://github.com/unicorn-engine/unicorn). Generally, it allows you to fuzz any machine code in a few setups, with coverage, cmpcov, and cmplog support. 4 | 5 | Starting from v3.0.0, unicornafl is fully rewritten with `libafl_targets` in Rust though we still provide Python and C bindings. 6 | 7 | ## Usage 8 | 9 | ### Rust 10 | 11 | To use `unicornafl` as a library, just add this to your `Cargo.toml` 12 | 13 | ```toml 14 | unicornafl = { git = "https://github.com/AFLplusplus/unicornafl", branch = "main" } 15 | ``` 16 | 17 | `main` is used here because `unicorn` is not released yet. We will make it ready shortly. 18 | 19 | For more details, please refer to [Rust usage](./docs/rust-usage.md). 20 | 21 | ### Python 22 | 23 | At this moment, manual building is required (see below) but we will soon release wheels. 24 | 25 | For more details, please refer to [Python usage](./docs/python-usage.md). 26 | 27 | ### C/C++ 28 | 29 | After building this repo, you could link the generated static archive or shared library with included C/C++ header file in [include/unicornafl.h](./include/unicornafl.h). 30 | 31 | For more details, please refer to [C/C++ usage](./docs/c-usage.md). 32 | 33 | ## Build 34 | 35 | Simply do: 36 | 37 | ```bash 38 | git clone https://github.com/AFLplusplus/unicornafl 39 | cd unicornafl 40 | cargo build --release 41 | ``` 42 | 43 | For python bindings, we have: 44 | 45 | ```bash 46 | maturin build --release 47 | ``` 48 | 49 | ## Unicorn Linkage 50 | 51 | Please note that, unicorn is already bundled with unicornafl by default and you **shall not** pass unicorn engine pointer created by another unicorn dynamic library. 52 | 53 | For Rust, stick to `unicornafl::unicorn_engine::*` to avoid incorrect structs and pointers. Refer to [Rust usage](./docs/rust-usage.md) for more details on override dependencies. 54 | 55 | For Python, you are free to use `from unicorn import Uc` because Python only allows dynamic linkage and we will always use the copy from the Unicorn python bindings. 56 | 57 | For C/C++, similar to Python, you should provide a dynamic unicorn library which our unicornafl will pick. 58 | 59 | ## Example && Minimal Tutorial 60 | 61 | We provide a sample harness at [the examples](https://github.com/AFLplusplus/AFLplusplus/tree/stable/unicorn_mode/samples). 62 | 63 | The target assembly is: 64 | 65 | ``` 66 | mov rax, rdx; 67 | cmp rax, 0x114514; 68 | je die; 69 | xor rax, rax; 70 | die: 71 | mov rax, [rax]; 72 | xor rax, rax; 73 | ``` 74 | 75 | We artifically make our harness stops at any of the `xor rax, rax` instruction. Therefore, if `rax==0x114514` is true, our harness will have an unmapped read error, which will be captured by `unicornafl` as a crash. Otherwise, it just stops without any crashes. You could start fuzzing by: 76 | 77 | ```bash 78 | cargo build --example sample --release 79 | # assume AFL++ is installed 80 | afl-fuzz -i ./input -o ./output-8 -b 1 -g 8 -G 8 -V 60 -c 0 -- ./target/release/examples/sample @@ true 81 | ``` 82 | 83 | This shall find the crash instantly, thanks to the `cmplog` integration (enabled by `-c 0`). 84 | 85 | For more details, please refer to [Fuzzing using UnicornAFL](./docs/fuzzing.md). 86 | 87 | ## Migration 88 | 89 | There should be nothing special migrating from unicornafl v2.x to unicornafl v3.x, execpt the way integrating with AFL++. If your harness builds and statically links against unicornafl directly, there is no longer needed for the unicorn mode with AFL++. However, if you are using Python, or using C/C++ with `libunicornafl.so` dynamically linked, unicorn mode (`-U` option) is still needed for `afl-fuzz` command line. 90 | 91 | If you are unsure whether `-U` is needed, it is harmless in all integration and you can safely always add that. -------------------------------------------------------------------------------- /src/harness.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use libafl::executors::ExitKind; 4 | use libafl_targets::{EDGES_MAP_PTR, INPUT_LENGTH_PTR, INPUT_PTR}; 5 | use log::{error, trace}; 6 | 7 | use crate::executor::{UnicornAflExecutor, UnicornAflExecutorHook}; 8 | 9 | /// Harness loop for forkserver mode 10 | pub fn forkserver_run_harness<'a, D, OT, H>( 11 | executor: &mut UnicornAflExecutor<'a, D, OT, H>, 12 | input_path: Option, 13 | iters: Option, 14 | ) -> Result<(), libafl::Error> 15 | where 16 | D: 'a, 17 | H: UnicornAflExecutorHook<'a, D>, 18 | { 19 | if let Some(iters) = iters { 20 | for persistent_round in 0..iters { 21 | forkserver_run_harness_once( 22 | executor, 23 | &input_path, 24 | persistent_round == 0, 25 | persistent_round == iters - 1, 26 | persistent_round, 27 | )?; 28 | } 29 | Ok(()) 30 | } else { 31 | let mut persistent_round = 0u64; 32 | let mut is_first_pass = true; 33 | loop { 34 | forkserver_run_harness_once( 35 | executor, 36 | &input_path, 37 | is_first_pass, 38 | false, 39 | persistent_round, 40 | )?; 41 | is_first_pass = false; 42 | persistent_round = persistent_round.wrapping_add(1); 43 | } 44 | } 45 | } 46 | 47 | /// If this returns `Err`, this means there are something fatal happened in 48 | /// execution, this means the loop cannot proceed. 49 | fn forkserver_run_harness_once<'a, D, OT, H>( 50 | executor: &mut UnicornAflExecutor<'a, D, OT, H>, 51 | input_path: &Option, 52 | is_first_pass: bool, 53 | is_last_pass: bool, 54 | persistent_round: u64, 55 | ) -> Result<(), libafl::Error> 56 | where 57 | D: 'a, 58 | H: UnicornAflExecutorHook<'a, D>, 59 | { 60 | if !is_first_pass { 61 | if let Some(parent_pipe_r) = &executor.uc.get_data().parent_pipe_r { 62 | if crate::forkserver::read_u32_from_fd(parent_pipe_r).is_err() { 63 | error!("[!] Error reading from parent pipe. Parent dead?"); 64 | } 65 | } 66 | } 67 | unsafe { 68 | std::ptr::write_bytes(EDGES_MAP_PTR, 0, executor.uc.get_data().map_size() as usize); 69 | std::ptr::write_volatile(EDGES_MAP_PTR, 1); 70 | } 71 | 72 | let input_str; 73 | let input = if let Some(input) = input_path.as_ref() { 74 | input_str = std::fs::read(input)?; 75 | input_str.as_slice() 76 | } else if unsafe { !INPUT_PTR.is_null() && !INPUT_LENGTH_PTR.is_null() } { 77 | unsafe { std::slice::from_raw_parts(INPUT_PTR, (*INPUT_LENGTH_PTR) as usize) } 78 | } else { 79 | return Err(libafl::Error::empty("corpus")); 80 | }; 81 | 82 | let exit_kind = executor.execute_internal(input, persistent_round)?; 83 | let msg = if matches!(exit_kind, ExitKind::Ok) { 84 | if is_last_pass { 85 | // We are at last round, tell parent we will die 86 | crate::forkserver::afl_child_ret::EXITED 87 | } else { 88 | crate::forkserver::afl_child_ret::NEXT 89 | } 90 | } else if is_last_pass { 91 | // We are at last round, tell parent we will die 92 | crate::forkserver::afl_child_ret::FOUND_CRASH_AND_EXITED 93 | } else { 94 | crate::forkserver::afl_child_ret::FOUND_CRASH 95 | }; 96 | trace!("Sending back msg to parent(unicornafl) = {msg:?}"); 97 | if let Some(child_pipe_w) = &executor.uc.get_data().child_pipe_w { 98 | if crate::forkserver::write_u32_to_fd(child_pipe_w, msg).is_err() { 99 | error!("[!] Error writing to parent pipe. Parent dead?"); 100 | } 101 | } 102 | 103 | Ok(()) 104 | } 105 | -------------------------------------------------------------------------------- /.github/workflows/py_test.yaml: -------------------------------------------------------------------------------- 1 | name: Python Example Testing 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | name: Build 11 | steps: 12 | - uses: actions/checkout@v4 13 | 14 | - name: '🛠️ Set up Rust' 15 | uses: dtolnay/rust-toolchain@stable 16 | 17 | - name: '🛠️ Set up Python' 18 | uses: actions/setup-python@v5 19 | with: 20 | python-version: '3.11' 21 | 22 | - name: '🛠️ Have a venv' 23 | run: python3 -m venv venv 24 | 25 | - name: '🛠️ Install the latest Unicorn dev' 26 | run: source venv/bin/activate && python3 -m pip install "git+https://github.com/unicorn-engine/unicorn@dev#subdirectory=bindings/python/" 27 | 28 | - name: '🛠️ Set up dependency of AFL++' 29 | run: | 30 | sudo apt update && sudo apt install -y llvm-16-dev clang-16 build-essential \ 31 | libtool libtool-bin libglib2.0-dev python3 make cmake automake meson ninja-build bison flex &&\ 32 | sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-16 0 && \ 33 | sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-16 0 34 | 35 | - name: '🚧 Install Maturin' 36 | run: | 37 | source venv/bin/activate && python3 -m pip install maturin 38 | 39 | - name: '🚧 Build Maturin Develop' 40 | run: | 41 | source venv/bin/activate && maturin develop --release 42 | 43 | - name: '🚧 AFLplusplus Checkout' 44 | uses: actions/checkout@v4 45 | with: 46 | repository: 'wtdcode/AFLplusplus' # Until merged into main 47 | ref: 'uc-mode' 48 | path: 'AFLplusplus' 49 | 50 | - name: '🚧 AFLplusplus Setup' 51 | run: | 52 | cd AFLplusplus &&\ 53 | make -j4 afl-fuzz 54 | 55 | - name: '🚧 Prepare fuzz resources' 56 | run: | 57 | mkdir ./input && echo 'a' > ./input/a 58 | 59 | - name: '🚧 Fuzz 4-byte cmplog for 60 seconds' 60 | run: | 61 | source venv/bin/activate && ./AFLplusplus/afl-fuzz -i ./input -o ./output-4 -b 1 -g 4 -G 4 -V 60 -c 0 -U -- python3 examples/sample.py @@ 62 | env: 63 | AFL_BENCH_UNTIL_CRASH: 1 64 | AFL_NO_CRASH_README: 1 65 | AFL_NO_UI: 1 66 | AFL_DEBUG: 1 67 | AFL_DEBUG_CHILD: 1 68 | AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES: 1 69 | AFL_SKIP_CPUFREQ: 1 70 | UNICORN_AFL_CMPCOV: 1 71 | 72 | - name: "🚧 Check if we find the crash" 73 | run: | 74 | ls ./output-4/default/crashes/ 75 | if [ "$(find ./output-4/default/crashes/ -type f | wc -l)" -eq 0 ]; then 76 | cat ./output-4/default/fuzzer_stats 77 | exit 1; 78 | else 79 | if ! [ "$(grep "stab" ./output-4/default/fuzzer_stats | awk '{print $3}')" = '100.00%' ]; then 80 | cat ./output-4/default/fuzzer_stats 81 | exit 2; 82 | fi 83 | fi 84 | 85 | - name: '🚧 Fuzz 8-byte cmplog for 180 seconds' 86 | run: | 87 | source venv/bin/activate && ./AFLplusplus/afl-fuzz -i ./input -o ./output-8 -b 1 -g 8 -G 8 -V 180 -c 0 -U -- python3 examples/sample.py @@ true 88 | env: 89 | AFL_BENCH_UNTIL_CRASH: 1 90 | AFL_NO_CRASH_README: 1 91 | AFL_NO_UI: 1 92 | AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES: 1 93 | AFL_SKIP_CPUFREQ: 1 94 | UNICORN_AFL_CMPCOV: 1 95 | 96 | - name: "🚧 Check if we find the crash" 97 | run: | 98 | ls ./output-8/default/crashes/ 99 | if [ "$(find ./output-8/default/crashes/ -type f | wc -l)" -eq 0 ]; then 100 | cat ./output-8/default/fuzzer_stats 101 | exit 1; 102 | else 103 | if ! [ "$(grep "stab" ./output-8/default/fuzzer_stats | awk '{print $3}')" = '100.00%' ]; then 104 | cat ./output-8/default/fuzzer_stats 105 | exit 2; 106 | fi 107 | fi 108 | -------------------------------------------------------------------------------- /docs/fuzzing.md: -------------------------------------------------------------------------------- 1 | # Fuzzing using UnicornAFL 2 | 3 | UnicornAFL is a bridge between AFL++ and Unicorn. 4 | 5 | ## Running Mode 6 | 7 | The harness built with UnicornAFL supports two running mode: standalone mode and fuzzing mode. 8 | 9 | ### Standalone Mode 10 | 11 | This mode is not intended for fuzzing. Instead, you should use this mode to check whether you have written the correct harness, and it is also helpful to analyze the crashes found by AFL++. 12 | 13 | To run harness in standalone mode, you should directly execute the harness executable that uses UnicornAFL without using `afl-fuzz`. The commandline options for executing this harness is defined by users. Users need to then pass correct value to the parameter of UnicornAFL API, especially the `input_file` argument. The commandline harness executable should take a path to a file, then if it is passed to the `input_file`, UnicornAFL will use that file as input to execute the Unicorn engine for the target being tested. 14 | 15 | Before any fuzzing, you should create a normal input seed that don't expect to crash the harness. Then you should run in standalone mode to check that the harness can execute normally. Then if anything unexpected happened during standalone mode, this means you write the wrong harness. 16 | 17 | ### Fuzzing mode 18 | 19 | After testing the correctness of the harness, then you can fuzz the harness using `afl-fuzz`. To use `afl-fuzz` with UnicornAFL, you should first make sure how you build the harness. 20 | 21 | If you are using Rust, or if you are using C/C++ that statically link the `libunicornafl.a`, then the minimized working example is 22 | 23 | ```shell 24 | afl-fuzz \ 25 | -i input \ 26 | -o output \ 27 | -- \ 28 | ./your-harness --and-your-own-harness-options 29 | ``` 30 | 31 | If you are using Python, or if you are using C/C++ that dynamically link the `libunicornafl.so`, then the minimized working example is 32 | 33 | ```shell 34 | afl-fuzz \ 35 | -U \ 36 | -i input \ 37 | -o output \ 38 | -- \ 39 | ./your-harness --and-your-own-harness-options 40 | ``` 41 | 42 | The `-U` option specifies that this is the legacy Unicorn mode. 43 | 44 | Note that you don't need to use `@@` to specify input file, we use shared memory to get input seed. 45 | 46 | ## Persistent Fuzzing 47 | 48 | UnicornAFL supports persistent fuzzing. Instead of forking at the beginning of each execution round, persistent fuzzing will just do a `for`-loop to execute the target. The overall steps are: 49 | 50 | 1. Users invoke `afl-fuzz` and pass the path to your UnicornAFL harness. 51 | 2. `afl-fuzz` spawns a harness process (which we call it harness parent). 52 | 3. The harness process will execute until the beginning of one of the UnicornAFL's APIs (`uc_afl_fuzz` and `uc_afl_fuzz_custom`). Then it will fork itself, producing another process (which we call it harness child). 53 | 4. The harness child contains a loop that executes the target with Unicorn engine repeatly. Each round is counted as a execution for `afl-fuzz`. 54 | 5. When the user specified `persistent_round` is achieved, or the harness child process crashes (which is rare, since the exceptions shall be captured by Unicorns already), the harness child end. The harness parent will fork a new harness child and do the same thing. 55 | 56 | Since in the harness child, the target is executed repeatly, it is very important that **you should restore the Unicorn's state after each round** unless you can make sure the target does not modify Unicorn's CPU and memory in this round. To make things easier, you can just specify `persistent_round` as 1, which downgrade to the legacy forkserver-based fuzzing, which is significantly slower. 57 | 58 | ## CMPLOG and CMPCOV 59 | 60 | UnicornAFL also supprost CMPLOG and CMPCOV in AFL++. If you don't know these terms, please refer to the AFL++'s documentation. In short, this is aimed to bypass the long comparison like `CMP RAX, 0x114514`. 61 | 62 | To use CMPCOV mode, you should specify `UNICORN_AFL_CMPCOV=1` environment in `afl-fuzz`. 63 | 64 | To use CMPLOG mode, you can just add `-c 0` option to `afl-fuzz`. 65 | 66 | ## Which language should I choose to use? 67 | 68 | The language to choose may have a little affect on the throughput of fuzzing, while you should keep in mind that the main overhead is the target itself. 69 | 70 | Although not benchmarked, Rust may be a slightly faster than C/C++ due to the power of inlining and LTO. The python version is much more slower. However, since the it only have a little affect, it is more appropriate if you choose the language that you are good at. Don't struggle with language itself, it is fuzzing that is all you need :) 71 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Crate 📦 Distribution 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | runs-on: ${{ matrix.config.os }} 10 | name: Test on ${{ matrix.config.os}} 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | config: 15 | - { 16 | os: 'ubuntu-latest' 17 | } 18 | 19 | - { 20 | os: 'macos-latest' 21 | } 22 | - { 23 | os: 'macos-13' 24 | } 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - name: '🛠️ Set up Rust' 29 | uses: dtolnay/rust-toolchain@stable 30 | 31 | - name: '🛠️ Set up dependency of AFL++ on Linux' 32 | if: ${{ contains(matrix.config.os, 'ubuntu') }} 33 | run: | 34 | sudo apt update && sudo apt install -y llvm-16-dev clang-16 build-essential \ 35 | libtool libtool-bin libglib2.0-dev python3 make cmake automake meson ninja-build bison flex &&\ 36 | sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-16 0 && \ 37 | sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-16 0 38 | 39 | - name: '🛠️ Set up dependency of AFL++ on macOS' 40 | if: ${{ contains(matrix.config.os, 'macos') }} 41 | run: | 42 | brew install wget git make cmake llvm gdb coreutils 43 | 44 | - name: '🚧 Cargo test' 45 | run: | 46 | cargo test 47 | 48 | - name: '🚧 Test Building bindings feature' 49 | run: | 50 | cargo build --features env_logger,bindings 51 | 52 | - name: '🚧 Build sample' 53 | run: | 54 | cargo build --release --example sample --features env_logger 55 | 56 | - name: '🚧 AFLplusplus Checkout' 57 | uses: actions/checkout@v4 58 | with: 59 | repository: 'AFLplusplus/AFLplusplus' 60 | ref: 'dev' 61 | path: 'AFLplusplus' 62 | 63 | - name: '🛠️ macOS quirks for AFL++' 64 | if: contains(matrix.config.os, 'macos') 65 | run: | 66 | cd AFLplusplus && sudo sh ./afl-system-config 67 | 68 | - name: '🚧 AFLplusplus Setup' 69 | run: | 70 | cd AFLplusplus &&\ 71 | make -j4 afl-fuzz 72 | 73 | - name: '🚧 Prepare fuzz resources' 74 | run: | 75 | mkdir ./input && echo 'a' > ./input/a 76 | 77 | - name: '🚧 Fuzz 4-byte cmplog for 60 seconds' 78 | run: | 79 | ./AFLplusplus/afl-fuzz -i ./input -o ./output-4 -b 1 -g 4 -G 4 -V 60 -c 0 -- ./target/release/examples/sample @@ 80 | env: 81 | AFL_BENCH_UNTIL_CRASH: 1 82 | AFL_NO_CRASH_README: 1 83 | AFL_NO_UI: 1 84 | AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES: 1 85 | AFL_SKIP_CPUFREQ: 1 86 | UNICORN_AFL_CMPCOV: 1 87 | 88 | - name: "🚧 Check if we find the crash" 89 | run: | 90 | ls ./output-4/default/crashes/ 91 | if [ "$(find ./output-4/default/crashes/ -type f | wc -l)" -eq 0 ]; then 92 | cat ./output-4/default/fuzzer_stats 93 | exit 1; 94 | else 95 | if ! [ "$(grep "stab" ./output-4/default/fuzzer_stats | awk '{print $3}')" = '100.00%' ]; then 96 | cat ./output-4/default/fuzzer_stats 97 | exit 2; 98 | fi 99 | fi 100 | 101 | - name: '🚧 Fuzz 8-byte cmplog for 180 seconds' 102 | run: | 103 | ./AFLplusplus/afl-fuzz -i ./input -o ./output-8 -b 1 -g 8 -G 8 -V 180 -c 0 -- ./target/release/examples/sample @@ true 104 | env: 105 | AFL_BENCH_UNTIL_CRASH: 1 106 | AFL_NO_CRASH_README: 1 107 | AFL_NO_UI: 1 108 | AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES: 1 109 | AFL_SKIP_CPUFREQ: 1 110 | UNICORN_AFL_CMPCOV: 1 111 | 112 | - name: "🚧 Check if we find the crash" 113 | run: | 114 | ls ./output-8/default/crashes/ 115 | if [ "$(find ./output-8/default/crashes/ -type f | wc -l)" -eq 0 ]; then 116 | cat ./output-8/default/fuzzer_stats 117 | exit 1; 118 | else 119 | if ! [ "$(grep "stab" ./output-8/default/fuzzer_stats | awk '{print $3}')" = '100.00%' ]; then 120 | cat ./output-8/default/fuzzer_stats 121 | exit 2; 122 | fi 123 | fi 124 | 125 | - name: '📦 Cargo Publish' 126 | if: ${{ startsWith(github.ref, 'refs/tags') && contains(matrix.config.os, 'ubuntu') }} 127 | env: 128 | TOKEN: ${{ secrets.CRATES_IO_KEY }} 129 | run: | 130 | cargo login $TOKEN && cargo test && cargo publish 131 | 132 | fmt-check: 133 | runs-on: ubuntu-latest 134 | steps: 135 | - uses: actions/checkout@v4 136 | - name: Cargo fmt 137 | run: cargo fmt --check 138 | 139 | fmt-toml-check: 140 | runs-on: ubuntu-latest 141 | steps: 142 | - name: Install taplo 143 | run: cargo install taplo-cli --locked 144 | - uses: actions/checkout@v4 145 | - name: Run taplo 146 | run: taplo format --check 147 | -------------------------------------------------------------------------------- /.github/workflows/py.yaml: -------------------------------------------------------------------------------- 1 | name: PyPI 📦 Distribution 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | # https://github.com/Cryptex-github/ril-py/blob/main/.github/workflows/py-binding.yml 8 | jobs: 9 | build: 10 | runs-on: ${{ matrix.config.os }} 11 | name: ${{ matrix.config.name }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | config: 16 | - { 17 | os: ubuntu-latest, 18 | arch: x64, 19 | python-version: 'cp38', 20 | name: 'manylinux_x86_64', 21 | alt_arch_name: x86_64 22 | } 23 | 24 | # - { 25 | # os: macos-latest, 26 | # arch: x64, 27 | # python-ver: '3.8', 28 | # name: 'macos_x86_64' 29 | # } 30 | steps: 31 | - uses: actions/checkout@v4 32 | 33 | - name: set up python 34 | uses: actions/setup-python@v4 35 | with: 36 | python-version: '3.9' 37 | 38 | - name: set up rust 39 | uses: dtolnay/rust-toolchain@stable 40 | with: 41 | toolchain: stable 42 | 43 | - name: Setup Rust cache 44 | uses: Swatinem/rust-cache@v2 45 | with: 46 | key: ${{ matrix.config.alt_arch_name }} 47 | 48 | - name: Get pip cache dir 49 | id: pip-cache 50 | if: matrix.config.os != 'windows-latest' 51 | run: | 52 | echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT 53 | 54 | - name: Cache python dependencies 55 | uses: actions/cache@v3 56 | with: 57 | path: ${{ steps.pip-cache.outputs.dir || steps.pip-cache-win.outputs.dir }} 58 | key: ${{ runner.os }}-pip-${{ matrix.config.python-version }} 59 | 60 | - name: install python dependencies 61 | run: pip install -U setuptools wheel twine cibuildwheel platformdirs 62 | 63 | - name: Display cibuildwheel cache dir 64 | id: cibuildwheel-cache 65 | run: | 66 | from platformdirs import user_cache_path 67 | import os 68 | 69 | with open(os.getenv('GITHUB_OUTPUT'), 'w') as f: 70 | f.write(f"dir={str(user_cache_path(appname='cibuildwheel', appauthor='pypa'))}") 71 | shell: python 72 | 73 | - name: Cache cibuildwheel tools 74 | uses: actions/cache@v3 75 | with: 76 | path: ${{ steps.cibuildwheel-cache.outputs.dir }} 77 | key: ${{ runner.os }}-cibuildwheel-${{ matrix.config.python-version }} 78 | 79 | - name: build sdist 80 | if: matrix.config.os == 'ubuntu-latest' && matrix.config.python-version == 'cp38' 81 | run: | 82 | pip install maturin build 83 | python -m build --sdist -o wheelhouse 84 | 85 | - name: build ${{ matrix.config.platform || matrix.config.os }} binaries 86 | run: cibuildwheel --output-dir wheelhouse 87 | env: 88 | CIBW_BUILD_FRONTEND: build 89 | CIBW_BUILD: '${{ matrix.config.python-version }}-${{ matrix.config.name }}' 90 | # rust doesn't seem to be available for musl linux on i686 91 | CIBW_SKIP: '*-musllinux_i686' 92 | # we build for "alt_arch_name" if it exists, else 'auto' 93 | CIBW_ARCHS: ${{ matrix.config.alt_arch_name || 'auto' }} 94 | CIBW_ENVIRONMENT: 'PATH="$HOME/.cargo/bin:$PATH" CARGO_TERM_COLOR="always"' 95 | CIBW_ENVIRONMENT_WINDOWS: 'PATH="$UserProfile\.cargo\bin;$PATH"' 96 | CIBW_ENVIRONMENT_LINUX: 'PATH="$HOME/.cargo/bin:$PATH" CARGO_TERM_COLOR="always" MATURIN_PEP517_ARGS="--compatibility manylinux_2_28 --auditwheel=skip"' 97 | # These are needed for Unicorn and cbindgen 98 | CIBW_BEFORE_ALL: > 99 | yum update -y && yum install -y clang clang-devel 100 | CIBW_BEFORE_BUILD: rustup show 101 | CIBW_BEFORE_BUILD_LINUX: > 102 | curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain=stable --profile=minimal -y && 103 | rustup show 104 | # CIBW_TEST_COMMAND: 'pytest {project}/test' 105 | # CIBW_TEST_REQUIRES: pytest requests 106 | # CIBW_TEST_SKIP: '*-macosx_arm64 *-macosx_universal2:arm64' 107 | # We must skip auditwheel because libunicorn.so is loaded at import time instead of load time and thus auditwheel will always fail! 108 | # This is hacky but should be okay 109 | CIBW_REPAIR_WHEEL_COMMAND: '' 110 | CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 111 | CIBW_BUILD_VERBOSITY: 1 112 | 113 | - run: ${{ matrix.config.ls || 'ls -lh' }} wheelhouse/ 114 | 115 | - name: '📤 Upload artifact' 116 | uses: actions/upload-artifact@v4 117 | with: 118 | name: wheels 119 | path: wheelhouse/ 120 | 121 | publish: 122 | needs: [build] 123 | runs-on: ubuntu-latest 124 | if: startsWith(github.ref, 'refs/tags') 125 | steps: 126 | - uses: actions/download-artifact@v2 127 | with: 128 | name: artifact 129 | path: dist 130 | 131 | - name: '📦 Publish distribution to PyPI' 132 | uses: pypa/gh-action-pypi-publish@master 133 | with: 134 | user: __token__ 135 | password: ${{ secrets.pypi_pass }} 136 | -------------------------------------------------------------------------------- /docs/c-usage.md: -------------------------------------------------------------------------------- 1 | # C/C++ Usage for UnicornAFL 2 | 3 | To use UnicornAFL with C/C++, you should clone this repository and build it yourself: 4 | 5 | ```shell 6 | git clone --depth 1 https://github.com/AFLplusplus/unicornafl && cd unicornafl 7 | cargo build --release --features bindings 8 | ``` 9 | 10 | Before building this repo, make sure that you have installed dependencies to build [Unicorn](https://github.com/unicorn-engine/unicorn), and installed stable Rust compiler with at least 1.87.0. 11 | 12 | After building this repo, there will be a `libunicornafl.a` and a `libunicornafl.so` in `./target/release/` directory. To use UnicornAFL, you should link either one, and use header file at `./include/unicornafl.h`. 13 | 14 | ## API usage 15 | 16 | The API for UnicornAFL is simple but powerful, which is the following two functions: `uc_afl_fuzz` and `uc_afl_fuzz_custom`. 17 | 18 | ### Simplified API 19 | 20 | `uc_afl_fuzz` 21 | 22 | ```c 23 | uc_afl_ret uc_afl_fuzz(uc_engine* uc, char* input_file, 24 | uc_afl_cb_place_input_t place_input_callback, 25 | uint64_t* exits, size_t exit_count, 26 | uc_afl_cb_validate_crash_t validate_crash_callback, 27 | bool always_validate, uint32_t persistent_iters, 28 | void* data); 29 | ``` 30 | 31 | `uc` is a unicorn instance created in advance. See the following [Creating Unicorn Instance](#Creating-Unicorn-Instance) for more details. 32 | 33 | `input_file` is a path to input file. If you are using the fuzzing mode, just pass `NULL` to this argument, and the input seed directory should be passed to `afl-fuzz` instead. For standalone mode, UnicornAFL takes input using this argument. 34 | 35 | `place_input_callback` is the callback for UnicornAFL to place received input into Unicorn's memory space. This callback takes five arguments: a pointer to the unicorn intance which users could use to read/write unicorn's emulated CPU/memory in this callback, a pointer to the input buffer, the input buffer length, the persistent round (which means how many times have this harness executed without exiting and forking to another child process), and custom data. This callback should return a bool, indicating whether this input is acceptable. 36 | 37 | `exits` and `exit_count` means the exit points for Unicorn. When the Unicorn instance reaches one of the given exit address, UnicornAFL will switch to next round. 38 | 39 | `validate_crash_callback` is the callback for UnicornAFL when an error encounted when executing the harness. It takes six arguments: a pointer to the unicorn intance, a value indicating the error of Unicorn when exuecting the harness, a pointer to the input buffer, the input buffer length, the persistent round, and custom data. This callback should return a bool, if it is `false`, then the AFL++ main executable will not treat this round as crash. This could be used to eliminate false positives during fuzzing. 40 | 41 | `always_validate` means whether the `validate_crash_callback` will be invoked even if the Unicorn does not face errors during execution. 42 | 43 | `persistent_iters` specifies how many times should this harness being executed persistently until the parent forks another child. For simplicity, you could just pass `1` here, which means always exiting and forking whenever this harness ends. However, if you want to write a more efficient harness, you should consider running persistently. Passing `0` here means never exiting or forking unless the process crashes, just run persistently. 44 | 45 | `data` is a custom data. In each callback listed above, this pointer will also passed as the callback argument. By this way you could maintain some shared data across execution. 46 | 47 | This function returns a `uc_afl_ret`. If it is not `UC_AFL_RET_OK`, this means unexpected things happened during fuzzing that you should take care of. 48 | 49 | ### Advanced API 50 | 51 | `uc_afl_fuzz_custom` 52 | 53 | ```c 54 | uc_afl_ret uc_afl_fuzz_custom(uc_engine* uc, char* input_file, 55 | uc_afl_cb_place_input_t place_input_callback, 56 | uc_afl_fuzz_cb_t fuzz_callbck, 57 | uc_afl_cb_validate_crash_t validate_crash_callback, 58 | bool always_validate, uint32_t persistent_iters, 59 | void* data); 60 | ``` 61 | 62 | Some of the arguments are the same as the simplified API. The only difference is the `fuzz_callbck` argument. UnicornAFL will use this function to start one execution round, and when this function stops, UnicornAFL knows this round has ended. By default, UnicornAFL will just use `uc_emu_start()`. 63 | 64 | ### Creating Unicorn Instance 65 | 66 | Before using fuzzing APIs, you should create unicorn instance on your own. It should be noted that, UnicornAFL does not need to know the actual target to fuzz. Instead, you should manually setup your target in Unicorn instance (for example, map the codes in unicorn's memory space). 67 | 68 | ## Tips 69 | 70 | ### Debugging 71 | 72 | Inside UnicornAFL, there are many logs could be used for debugging. To enable logging, you should compile this repo using 73 | 74 | ```shell 75 | cargo build --release --features env_logger 76 | ``` 77 | 78 | And when running, passing `RUST_LOG=trace` as environment. (`AFL_DEBUG=1` is also needed if you are using `afl-fuzz` to run the harness) 79 | -------------------------------------------------------------------------------- /docs/python-usage.md: -------------------------------------------------------------------------------- 1 | # Python Usage for UnicornAFL 2 | 3 | To use UnicornAFL with Python, you should clone this repository and build it yourself: 4 | 5 | ```shell 6 | git clone --depth 1 https://github.com/AFLplusplus/unicornafl && cd unicornafl 7 | cargo build --release 8 | maturin build --release 9 | ``` 10 | 11 | Before building this repo, make sure that you have installed dependencies to build [Unicorn](https://github.com/unicorn-engine/unicorn), and installed stable Rust compiler with at least 1.87.0, and you should also install [maturin](https://www.maturin.rs). 12 | 13 | After building this repo, there will be a wheel in `./target/wheels`, just use it. 14 | 15 | ## API usage 16 | 17 | The API for UnicornAFL is simple but powerful, which is the following two functions: `uc_afl_fuzz` and `uc_afl_fuzz_custom`. 18 | 19 | ### Simplified API 20 | 21 | `uc_afl_fuzz` 22 | 23 | ```python 24 | def uc_afl_fuzz(uc: Uc, 25 | input_file: str, 26 | place_input_callback: Callable, 27 | exits: List[int], 28 | validate_crash_callback: Callable = None, 29 | always_validate: bool = False, 30 | persistent_iters: int = 1, 31 | data: Any = None): ... 32 | ``` 33 | 34 | `uc` is a unicorn instance created in advance. See the following [Creating Unicorn Instance](#Creating-Unicorn-Instance) for more details. 35 | 36 | `input_file` is a path to input file. If you are using the fuzzing mode, just pass `None` to this argument, and the input seed directory should be passed to `afl-fuzz` instead. For standalone mode, UnicornAFL takes input using this argument. 37 | 38 | `place_input_callback` is the callback for UnicornAFL to place received input into Unicorn's memory space. This callback takes four arguments: a pointer to the unicorn intance which users could use to read/write unicorn's emulated CPU/memory in this callback, input buffer, the persistent round (which means how many times have this harness executed without exiting and forking to another child process), and custom data. This callback should return a Bool, indicating whether this input is acceptable. 39 | 40 | `exits` means the exit points for Unicorn. When the Unicorn instance reaches one of the given exit address, UnicornAFL will switch to next round. 41 | 42 | `validate_crash_callback` is the callback for UnicornAFL when an error encounted when executing the harness. It takes five arguments: a pointer to the unicorn intance, a value indicating the error of Unicorn when exuecting the harness, the input buffer, the persistent round, and custom data. This callback should return a Bool, if it is `False`, then the AFL++ main executable will not treat this round as crash. This could be used to eliminate false positives during fuzzing. 43 | 44 | `always_validate` means whether the `validate_crash_callback` will be invoked even if the Unicorn does not face errors during execution. 45 | 46 | `persistent_iters` specifies how many times should this harness being executed persistently until the parent forks another child. For simplicity, you could just pass `1` here, which means always exiting and forking whenever this harness ends. However, if you want to write a more efficient harness, you should consider running persistently. Passing `0` here means never exiting or forking unless the process crashes, just run persistently. 47 | 48 | `data` is a custom data. In each callback listed above, this pointer will also passed as the callback argument. By this way you could maintain some shared data across execution. 49 | 50 | This function returns a `UcAflError` or value `UC_AFL_RET_OK`. If the return value is not `UC_AFL_RET_OK`, this means unexpected things happened during fuzzing that you should take care of. 51 | 52 | ### Advanced API 53 | 54 | `uc_afl_fuzz_custom` 55 | 56 | ```python 57 | def uc_afl_fuzz_custom(uc: Uc, 58 | input_file: str, 59 | place_input_callback: Callable, 60 | fuzzing_callback: Callable, 61 | validate_crash_callback: Callable = None, 62 | always_validate: bool = False, 63 | persistent_iters: int = 1, 64 | data: Any = None): ... 65 | ``` 66 | 67 | Some of the arguments are the same as the simplified API. The only difference is the `fuzz_callbck` argument. UnicornAFL will use this function to start one execution round, and when this function stops, UnicornAFL knows this round has ended. By default, UnicornAFL will just use `uc_emu_start()`. 68 | 69 | ### Creating Unicorn Instance 70 | 71 | Before using fuzzing APIs, you should create unicorn instance on your own. It should be noted that, UnicornAFL does not need to know the actual target to fuzz. Instead, you should manually setup your target in Unicorn instance (for example, map the codes in unicorn's memory space). 72 | 73 | ## Tips 74 | 75 | ### Unicorn Linkage 76 | 77 | Unlike the Rust bindings which statically links against Unicorn, UnicornAFL builds a shared object `unicornafl.abi3.so` which dynamically links against `libunicorn.so` provided by the Unicorn Python bindings. 78 | 79 | It is possible to use an alternate `libunicorn.so` by `LIBUNICORN_PATH` environment variable. 80 | 81 | ### Debugging 82 | 83 | Inside UnicornAFL, there are many logs could be used for debugging. To enable logging, you should compile this repo using 84 | 85 | ```shell 86 | cargo build --release --features env_logger 87 | ``` 88 | 89 | And when running, passing `RUST_LOG=trace` as environment. (`AFL_DEBUG=1` is also needed if you are using `afl-fuzz` to run the harness) 90 | -------------------------------------------------------------------------------- /src/target.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use libafl_bolts::shmem::{ShMemProvider, UnixShMemProvider}; 4 | use libafl_targets::{__afl_map_size, EDGES_MAP_PTR, SHM_FUZZING, cmps::CMPLOG_ENABLED}; 5 | use log::{debug, error, trace, warn}; 6 | use unicorn_engine::{Arch, RegisterARM, Unicorn, uc_error}; 7 | 8 | use crate::{ 9 | executor::{CmpPolicy, UnicornAflExecutor, UnicornAflExecutorHook, UnicornFuzzData}, 10 | uc_afl_ret, 11 | }; 12 | 13 | /// Dummy fuzz callback if user don't specify their own callback 14 | pub fn dummy_uc_fuzz_callback<'a, D: 'a>( 15 | uc: &mut Unicorn<'a, UnicornFuzzData>, 16 | ) -> Result<(), uc_error> { 17 | let arch = uc.get_arch(); 18 | 19 | let mut pc = uc.pc_read()?; 20 | if arch == Arch::ARM { 21 | let cpsr = uc.reg_read(RegisterARM::CPSR)?; 22 | if cpsr & 0x20 != 0 { 23 | pc |= 1; 24 | } 25 | } 26 | 27 | uc.emu_start(pc, 0, 0, 0) 28 | } 29 | 30 | /// Dummy crash validation callback if user don't specify their own callback 31 | pub fn dummy_uc_validate_crash_callback<'a, D: 'a>( 32 | _uc: &mut Unicorn<'a, UnicornFuzzData>, 33 | unicorn_result: Result<(), uc_error>, 34 | _input: &[u8], 35 | _persistent_round: u64, 36 | ) -> bool { 37 | unicorn_result.is_err() 38 | } 39 | 40 | /// Internal entrypoint for fuzzing. 41 | /// 42 | /// This is only expected to be runned in forkserver mode. 43 | pub fn child_fuzz<'a, D: 'a>( 44 | uc: Unicorn<'a, UnicornFuzzData>, 45 | input_file: Option, 46 | iters: Option, 47 | callbacks: impl UnicornAflExecutorHook<'a, D>, 48 | exits: Vec, 49 | always_validate: bool, 50 | run_once_if_no_afl_present: bool, 51 | ) -> Result<(), uc_afl_ret> { 52 | // Enable logging 53 | #[cfg(feature = "env_logger")] 54 | env_logger::init(); 55 | 56 | // Dump all environments 57 | for (env, val) in std::env::vars_os() { 58 | trace!("{:?}={:?}", env, val); 59 | } 60 | 61 | let mut shmem_provider = UnixShMemProvider::new()?; 62 | 63 | trace!("Mapping both input shm and coverage shm..."); 64 | let has_afl = libafl_targets::map_input_shared_memory(&mut shmem_provider).is_ok() 65 | && libafl_targets::map_shared_memory(&mut shmem_provider).is_ok(); 66 | 67 | trace!("AFL detected: {has_afl}"); 68 | if input_file.is_some() && has_afl { 69 | warn!("Shared memory fuzzing is enabled and the input file is ignored!"); 70 | } 71 | if input_file.is_none() && !has_afl { 72 | warn!("No input file is provided. We will run harness with zero inputs."); 73 | } 74 | let mut local_map; 75 | if !has_afl && run_once_if_no_afl_present { 76 | let map_size = uc.get_data().map_size(); 77 | // This local variable will never be freed until current function is end, which 78 | // is after the forkserver loop. 79 | local_map = vec![0u8; map_size as usize]; 80 | unsafe { EDGES_MAP_PTR = local_map.as_mut_ptr() } 81 | // If no afl, input will be read from input_file in forkserver_run_harness, 82 | // thus no need to setup INPUT_PTR and INPUT_LENGTH_PTR 83 | } 84 | if has_afl || run_once_if_no_afl_present { 85 | let map_size = uc.get_data().map_size(); 86 | unsafe { 87 | __afl_map_size = map_size as usize; 88 | SHM_FUZZING = 1; 89 | } 90 | debug!("Map size is: {map_size}"); 91 | 92 | // The following code will handle CMPLOG and CMPCOV envs 93 | // 94 | // In AFL++, the cmplog shared memory id env will always set if 95 | // users specify "-c" option to `afl-fuzz` in commandline, no 96 | // matter whether it is invoking the cmplog_binary or normal binary. See 97 | // https://github.com/AFLplusplus/AFLplusplus/blob/c340a022e2546488c15f85593d0f37e30eeaab3a/src/afl-sharedmem.c#L321 98 | // 99 | // But only for cmplog binaries, AFL++ will set ___AFL_EINS_ZWEI_POLIZEI___, see 100 | // https://github.com/AFLplusplus/AFLplusplus/blob/c340a022e2546488c15f85593d0f37e30eeaab3a/src/afl-fuzz-cmplog.c#L34 101 | // 102 | // As a result, we check both env vars, and users can pass the same unicornafl 103 | // binary as both normal binary and cmplog binary (use `-c 0` in `afl-fuzz` cmdline). 104 | // 105 | // The `CMPLOG_ENABLED` controls whether the underlined `__libafl_targets_cmplog_instructions` 106 | // will take effect. 107 | let cmp_policy; 108 | let has_cmplog = std::env::var("___AFL_EINS_ZWEI_POLIZEI___").is_ok() 109 | && libafl_targets::map_cmplog_shared_memory(&mut shmem_provider).is_ok(); 110 | let has_cmpcov = std::env::var("UNICORN_AFL_CMPCOV").is_ok(); 111 | if has_cmplog { 112 | if has_cmpcov { 113 | warn!("CMPLOG and CMPCOV turned on at the same time!"); 114 | warn!("I'll turn off CMPCOV."); 115 | } 116 | unsafe { 117 | CMPLOG_ENABLED = 1; 118 | } 119 | cmp_policy = CmpPolicy::Cmplog; 120 | } else if has_cmpcov { 121 | cmp_policy = CmpPolicy::Cmpcov; 122 | } else { 123 | cmp_policy = CmpPolicy::None; 124 | } 125 | 126 | let iters = if !has_afl && run_once_if_no_afl_present { 127 | Some(1) 128 | } else { 129 | iters 130 | }; 131 | 132 | let executor = UnicornAflExecutor::new( 133 | uc, 134 | (), 135 | callbacks, 136 | always_validate, 137 | exits, 138 | iters.is_some(), 139 | cmp_policy, 140 | )?; 141 | let mut forkserver_parent = crate::forkserver::UnicornAflForkserverParent::new(executor); 142 | libafl_targets::start_forkserver(&mut forkserver_parent)?; 143 | let mut executor = forkserver_parent.executor; 144 | let input_file = if has_afl { None } else { input_file }; 145 | if let Err(e) = crate::harness::forkserver_run_harness(&mut executor, input_file, iters) { 146 | // The error cannot be propagated since we are in child process now. 147 | // So just log. 148 | error!("Fuzzing fails with error from libafl: {e}"); 149 | } 150 | } else { 151 | // TODO: Run with libafl directly 152 | } 153 | Ok(()) 154 | } 155 | -------------------------------------------------------------------------------- /python/unicornafl/__init__.py: -------------------------------------------------------------------------------- 1 | # Compatibility and ensure we use the unicorn library from the unicorn bindings 2 | from unicorn import * 3 | 4 | # Now we load unicornafl.abi3.so and since we already loaded libunicorn.so by 5 | # unicorn bindings, we won't load a duplicate one. 6 | from unicornafl.unicornafl import uc_afl_fuzz as uc_afl_fuzz_impl 7 | from unicornafl.unicornafl import uc_afl_fuzz_custom as uc_afl_fuzz_custom_impl 8 | from unicornafl.unicornafl import UC_AFL_RET_OK, \ 9 | UC_AFL_RET_ERROR, \ 10 | UC_AFL_RET_CHILD, \ 11 | UC_AFL_RET_NO_AFL, \ 12 | UC_AFL_RET_CALLED_TWICE, \ 13 | UC_AFL_RET_FINISHED, \ 14 | UC_AFL_RET_INVALID_UC, \ 15 | UC_AFL_RET_UC_ERR, \ 16 | UC_AFL_RET_LIBAFL, \ 17 | UC_AFL_RET_FFI 18 | import ctypes 19 | from typing import Any, Callable, List, Optional 20 | 21 | 22 | UC_AFL_PLACE_INPUT_CB = ctypes.CFUNCTYPE( 23 | ctypes.c_bool, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t, ctypes.c_uint32, ctypes.c_void_p 24 | ) 25 | 26 | UC_AFL_VALIDATE_CRASH_CB = ctypes.CFUNCTYPE( 27 | ctypes.c_bool, ctypes.c_void_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint32, ctypes.c_int, ctypes.c_void_p 28 | ) 29 | 30 | UC_AFL_FUZZ_CALLBACK_CB = ctypes.CFUNCTYPE( 31 | ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p 32 | ) 33 | 34 | _data_dict = {} 35 | _data_idx = 0 36 | 37 | def _place_input_cb(uc, input, input_len, persistent_round, idx): 38 | cb, _, _, uc, data = _data_dict[idx] 39 | input_bs = ctypes.cast(input, ctypes.POINTER(ctypes.c_char * input_len)).contents 40 | if cb is not None: 41 | ret = cb(uc, input_bs, persistent_round, data) 42 | if ret is False: # None is considered as True, for unicornafl compatibility 43 | return False 44 | return True 45 | else: 46 | return True 47 | 48 | 49 | def _validate_crash_cb(uc, result, input, input_len, persistent_round, idx): 50 | _, cb, _, uc, data = _data_dict[idx] 51 | input_bs = ctypes.cast(input, ctypes.POINTER(ctypes.c_char * input_len)).contents 52 | if cb is not None: 53 | ret = cb(uc, result, input_bs, persistent_round, data) 54 | if ret is False: # None is considered as True, for unicornafl compatibility 55 | return False 56 | return True 57 | else: 58 | return True 59 | 60 | def _fuzz_callback_cb(uc, idx): 61 | _, _, cb, uc, data = _data_dict[idx] 62 | 63 | return cb(uc, data) 64 | 65 | 66 | class UcAflError(Exception): 67 | 68 | def __init__(self, errno=UC_AFL_RET_ERROR, message=None): 69 | super().__init__() 70 | self.errno = errno 71 | self.message = message 72 | 73 | def __str__(self): 74 | # type: (UcAflError) -> str 75 | if self.message: 76 | return self.message 77 | return { 78 | UC_AFL_RET_CHILD: "Fork worked. we are a child (no Error)", 79 | UC_AFL_RET_NO_AFL: "No AFL, no need to fork (but no real Error)", 80 | UC_AFL_RET_FINISHED: "We forked before but now AFL is gone (time to quit)", 81 | UC_AFL_RET_CALLED_TWICE: "Forkserver already running. This may be an error.", 82 | UC_AFL_RET_ERROR: "Something went horribly wrong in the parent!", 83 | UC_AFL_RET_FFI: "Unexpected FFI error, probably the unicorn version unicornafl built is not the same as Python dependency, consider rerun with RUST_LOG=trace", 84 | UC_AFL_RET_LIBAFL: "Error in LibAFL, consider rerun with RUST_LOG=trace", 85 | UC_AFL_RET_INVALID_UC: "Invalid unicorn pointer.", 86 | UC_AFL_RET_UC_ERR: "Error from unicorn." 87 | }[self.errno] 88 | 89 | def __eq__(self, other): 90 | # type: (UcAflError) -> str 91 | if isinstance(other, int): 92 | return self.errno == other 93 | elif isinstance(other, str): 94 | return self.message == other 95 | elif isinstance(other, UcAflError): 96 | return self.errno == other.errno 97 | elif other is None: 98 | return None 99 | else: 100 | raise ValueError("Tried to compare UcAflError to {} ({})".format((type(other), other))) 101 | 102 | def __handle_input_string(input_file: Optional[str | bytes]) -> int: 103 | if isinstance(input_file, str): 104 | return \ 105 | ctypes.cast( 106 | ctypes.create_string_buffer(input_file.encode('utf-8')), 107 | ctypes.c_void_p 108 | ).value 109 | elif isinstance(input_file, bytes): 110 | return \ 111 | ctypes.cast( 112 | ctypes.create_string_buffer(input_file), 113 | ctypes.c_void_p 114 | ).value 115 | elif input_file is None: 116 | return 0 117 | else: 118 | raise TypeError("Input file should be string or bytes or None") 119 | 120 | def uc_afl_fuzz(uc: Uc, 121 | input_file: Optional[str | bytes], 122 | place_input_callback: Callable, 123 | exits: List[int], 124 | validate_crash_callback: Callable = None, 125 | always_validate: bool = False, 126 | persistent_iters: int = 1, 127 | data: Any = None): 128 | global _data_idx, _data_dict 129 | 130 | # Someone else is fuzzing, quit! 131 | # For unicornafl compatiblity 132 | if len(_data_dict) != 0: 133 | raise UcAflError(UC_AFL_RET_CALLED_TWICE) 134 | 135 | _data_idx += 1 136 | idx = _data_idx # 1 will be interpreted as None so we skip it 137 | _data_dict[idx] = (place_input_callback, validate_crash_callback, None, uc, data) 138 | exits_len = len(exits) 139 | exits_array = (ctypes.c_uint64 * exits_len)() 140 | 141 | for i, exit in enumerate(exits): 142 | exits_array[i] = exit 143 | 144 | cb1 = ctypes.cast(UC_AFL_PLACE_INPUT_CB( 145 | _place_input_cb), UC_AFL_PLACE_INPUT_CB) 146 | cb2 = ctypes.cast(UC_AFL_VALIDATE_CRASH_CB( 147 | _validate_crash_cb), UC_AFL_VALIDATE_CRASH_CB) 148 | 149 | input_file = __handle_input_string(input_file) 150 | err = uc_afl_fuzz_impl( 151 | uc._uch.value, 152 | input_file, 153 | ctypes.cast(cb1, ctypes.c_void_p).value, 154 | ctypes.cast( 155 | exits_array, ctypes.c_void_p 156 | ).value, 157 | exits_len, 158 | ctypes.cast(cb2, ctypes.c_void_p).value, 159 | always_validate, 160 | persistent_iters, 161 | ctypes.cast(idx, ctypes.c_void_p).value 162 | ) 163 | if err != UC_AFL_RET_OK: 164 | del _data_dict[idx] 165 | raise UcAflError(err) 166 | 167 | del _data_dict[idx] 168 | # Really? 169 | return err 170 | 171 | def uc_afl_fuzz_custom(uc: Uc, 172 | input_file: Optional[str | bytes], 173 | place_input_callback: Callable, 174 | fuzzing_callback: Callable, 175 | validate_crash_callback: Callable = None, 176 | always_validate: bool = False, 177 | persistent_iters: int = 1, 178 | data: Any = None): 179 | global _data_idx, _data_dict 180 | 181 | # Someone else is fuzzing, quit! 182 | # For unicornafl compatiblity 183 | if len(_data_dict) != 0: 184 | raise UcAflError(UC_AFL_RET_CALLED_TWICE) 185 | 186 | _data_idx += 1 187 | idx = _data_idx # 1 will be interpreted as None so we skip it 188 | _data_dict[idx] = (place_input_callback, validate_crash_callback, fuzzing_callback, uc, data) 189 | 190 | cb1 = ctypes.cast(UC_AFL_PLACE_INPUT_CB( 191 | _place_input_cb), UC_AFL_PLACE_INPUT_CB) 192 | cb2 = ctypes.cast(UC_AFL_VALIDATE_CRASH_CB( 193 | _validate_crash_cb), UC_AFL_VALIDATE_CRASH_CB) 194 | cb3 = ctypes.cast(UC_AFL_FUZZ_CALLBACK_CB( 195 | _fuzz_callback_cb), UC_AFL_FUZZ_CALLBACK_CB) 196 | 197 | input_file = __handle_input_string(input_file) 198 | err = uc_afl_fuzz_custom_impl( 199 | uc._uch.value, 200 | input_file, 201 | ctypes.cast(cb1, ctypes.c_void_p).value, 202 | ctypes.cast(cb3, ctypes.c_void_p).value, 203 | ctypes.cast(cb2, ctypes.c_void_p).value, 204 | always_validate, 205 | persistent_iters, 206 | ctypes.cast(idx, ctypes.c_void_p).value 207 | ) 208 | 209 | if err != UC_AFL_RET_OK: 210 | del _data_dict[idx] 211 | raise UcAflError(err) 212 | 213 | del _data_dict[idx] 214 | # Really? 215 | return err 216 | 217 | # Compatibility monkeypatch 218 | def monkeypatch(): 219 | Uc.afl_fuzz = uc_afl_fuzz -------------------------------------------------------------------------------- /docs/rust-usage.md: -------------------------------------------------------------------------------- 1 | # Rust Usage for UnicornAFL 2 | 3 | To use UnicornAFL with Rust, you should create a new Rust package, and add the following line into the `[dependencies]` section in `Cargo.toml`: 4 | 5 | ```toml 6 | unicornafl = { git = "https://github.com/AFLplusplus/unicornafl", branch = "main" } 7 | ``` 8 | 9 | Before building the new package, make sure that you have installed dependencies to build [Unicorn](https://github.com/unicorn-engine/unicorn). 10 | 11 | ## API usage 12 | 13 | After declaring UnicornAFL as a dependency, you could now write your own fuzzing harness. The API for UnicornAFL is simple but powerful, which is the following two functions: `afl_fuzz` and `afl_fuzz_custom`. 14 | 15 | ### Simplified API 16 | 17 | `unicornafl::afl_fuzz` 18 | 19 | ```rust 20 | fn afl_fuzz<'a, D: 'a>( 21 | uc: Unicorn<'a, UnicornFuzzData>, 22 | input_file: Option, 23 | place_input_cb: impl FnMut(&mut Unicorn<'a, UnicornFuzzData>, &[u8], u64) -> bool + 'a, 24 | exits: Vec, 25 | persistent_iters: Option, 26 | ) -> Result<(), uc_afl_ret>; 27 | ``` 28 | 29 | Please don't be scared by the lifetime mark in function signature. In most time, you don't need to care about that. 30 | 31 | `uc` is a unicorn instance created in advance. See the following [Creating Unicorn Instance](#Creating-Unicorn-Instance) for more details. 32 | 33 | `input_file` is a path to input file. If you are using the fuzzing mode, just pass `None` to this argument, and the input seed directory should be passed to `afl-fuzz` instead. For standalone mode, UnicornAFL takes input using this argument. 34 | 35 | `place_input_cb` is the callback for UnicornAFL to place received input into Unicorn's memory space. This closure takes three arguments: a mutable reference to the unicorn intance which users could use to read/write unicorn's emulated CPU/memory in this callback, a reference to the input buffer, the persistent round (which means how many times have this harness executed without exiting and forking to another child process). This closure should return a bool, indicating whether this input is acceptable. 36 | 37 | `exits` means the exit points for Unicorn. When the Unicorn instance reaches one of the given exit address, UnicornAFL will switch to next round. 38 | 39 | `persistent_iters` specifies how many times should this harness being executed persistently until the parent forks another child. For simplicity, you could just pass `Some(1)` here, which means always exiting and forking whenever this harness ends. However, if you want to write a more efficient harness, you should consider running persistently. Passing `None` here means never exiting or forking unless the process crashes, just run persistently. 40 | 41 | This function returns a `Result`. If it is an `Err`, this means unexpected things happened during fuzzing that you should take care of. 42 | 43 | To use this API, you could write code like this: 44 | 45 | ```rust 46 | // Creating uc 47 | // Other setup ... 48 | 49 | if let Err(err) = unicornafl::afl_fuzz( 50 | uc, 51 | None, 52 | |uc, input, persistent_round| { 53 | // Custom logics here, use uc.reg_write() or uc.mem_write(), for instance. 54 | true 55 | }, 56 | vec![0x4001000, 0x4002000], 57 | Some(1), 58 | ) { 59 | eprintln!("Unexpected happened! {err:?}"); 60 | } 61 | ``` 62 | 63 | ### Advanced API 64 | 65 | `unicornafl::afl_fuzz_custom` 66 | 67 | ```rust 68 | fn afl_fuzz_custom<'a, D: 'a>( 69 | uc: Unicorn<'a, UnicornFuzzData>, 70 | input_file: Option, 71 | callbacks: impl UnicornAflExecutorHook<'a, D>, 72 | exits: Vec, 73 | always_validate: bool, 74 | persistent_iters: Option, 75 | ) -> Result<(), uc_afl_ret>; 76 | ``` 77 | 78 | Some of the arguments are the same as the simplified API. The only difference is `callbacks` and `always_validate`. 79 | 80 | `callbacks` is a structure that users should define and implement on their own. The definition of `UnicornAflExecutorHook` is in [executor.rs](../src/executor.rs), which is well-documented that you should look at first. 81 | 82 | There are three methods in `UnicornAflExecutorHook`: 83 | 84 | * `place_input` 85 | 86 | This is required to implement. The meaning of this method is the same as `place_input_cb` in simplified API. 87 | * `validate_crash` 88 | 89 | This is optional to implement. This will be invoked if Unicorn encounters exceptions when executing the harness, or users specify `always_validate` to be true. This could be used to eliminate false positives during fuzzing. If this function returns `false`, then the AFL++ main executable will not treat this round as crash. 90 | * `fuzz` 91 | 92 | This is optional to implement. UnicornAFL will use this function to start one execution round, and when this function stops, UnicornAFL knows this round has ended. By default, UnicornAFL will just use `uc.emu_start()`. 93 | 94 | Note that all these three methods take `&mut self` as input. This means if there are some data shared across persistent rounds and are used by these callbacks, you could store it in the structure. However, you should be noted that when the max persistent round is reached (which you specified in `persistent_round` argument), current process will exit and the parent will fork a new one whose initial state is just before the invoking of `afl_fuzz_custom`. As a result, you should save your data after `afl_fuzz_custom` ends, and read last round's data before `afl_fuzz_custom`. 95 | 96 | To use this API, you could write code like this: 97 | 98 | ```rust 99 | use unicornafl::UnicornAflExecutorHook; 100 | 101 | struct MyOwnExecutorHook { 102 | my_data: usize 103 | } 104 | 105 | impl UnicornAflExecutorHook for MyOwnExecutorHook { 106 | fn place_input( 107 | &mut self, 108 | uc: &mut Unicorn<'a, UnicornFuzzData>, 109 | input: &[u8], 110 | persistent_round: u64, 111 | ) -> bool { 112 | // Custom logics here, use uc.reg_write() or uc.mem_write(), for instance. 113 | true 114 | } 115 | 116 | // I don't need to implement `validate_crash` and `fuzz`. 117 | } 118 | 119 | fn main() { 120 | // Creating uc 121 | // Other setup ... 122 | 123 | if let Err(err) = unicornafl::afl_fuzz_custom( 124 | uc, 125 | None, 126 | MyOwnExecutorHook { my_data: 0 }, 127 | vec![0x4001000, 0x4002000], 128 | false, 129 | Some(1), 130 | ) { 131 | eprintln!("Unexpected happened! {err:?}"); 132 | } 133 | } 134 | ``` 135 | 136 | ### Creating Unicorn Instance 137 | 138 | Before using fuzzing APIs, you should create unicorn instance on your own. It should be noted that, UnicornAFL does not need to know the actual target to fuzz. Instead, you should manually setup your target in Unicorn instance (for example, map the codes in unicorn's memory space). To create a Unicorn instance used for UnicornAFL for fuzzing, you should do things like: 139 | 140 | ```rust 141 | use unicornafl::UnicornFuzzData; 142 | 143 | // Set up arch, mode, and some shared fuzzing data. 144 | let mut uc = Unicorn::new_with_data(Arch::X86, Mode::MODE_64, UnicornFuzzData::default()); 145 | ``` 146 | 147 | `UnicornFuzzData` is a helper for maintaining shared data during execution. In general, there are two kinds of data to share when using UnicornAFL: 1. Data that need to be shared during persistent execution, and is only used in `UnicornAflExecutorHook`'s callbacks, which means these data are only used **before** or **after** one execution round. 2. Data that need to be shared during one execution round, and is used in multiple hooks of Unicorn (code hooks, memory hooks, etc.). The former data should be stored in the structure that implements `UnicornAflExecutorHook`, and the latter one is hat `UnicornFuzzData` is used for. 148 | 149 | In fact, `UnicornFuzzData` is a wrapper over arbitrary generic structure, which you could define your own data. Inside Unicorn's hooks, users could use `get_data()` or `get_data_mut()` to access the `UnicornFuzzData` structure, which can be further used to access user-defined data using `.user_data` field. 150 | 151 | For example: 152 | 153 | ```rust 154 | use unicornafl::UnicornFuzzData; 155 | 156 | struct MyFuzzData { 157 | hook_call_count: usize 158 | } 159 | 160 | let mut uc = Unicorn::new_with_data( 161 | Arch::X86, 162 | Mode::MODE_64, 163 | UnicornFuzzData::new( 164 | MyFuzzData { 165 | hook_call_count: 0 166 | } 167 | ) 168 | ); 169 | 170 | uc.add_code_hook(/* ... */, |uc, _, _| { 171 | let my_fuzz_data = &mut uc.get_data_mut().user_data; 172 | my_fuzz_data.hook_call_count += 1; 173 | }).unwrap(); 174 | ``` 175 | 176 | However, you should note that the `get_data` or `get_data_mut` would require `Rc` check, which may potentially decrease the performance. As a result, you should minimize such data accesses. 177 | 178 | ## Tips 179 | 180 | ### Build release version 181 | 182 | In Rust, the default profile used for `cargo build` is debug build, which is slow. To optimize fuzzing throughput, you should use release profile by `cargo build --release`. Moreover, inside your `Cargo.toml`, it is suggested to add 183 | 184 | ```toml 185 | [profile.release] 186 | lto = true 187 | codegen-units = 1 188 | ``` 189 | 190 | This may significantly increase compile time, but the generated binary is very optimized. 191 | 192 | ### Use a different version of Unicorn 193 | 194 | It should be noted that the internal of UnicornAFL depends heavily on some newest Unicorn APIs. As a result, older version of Unicorn may not work. However, if you want to use your own version of Unicorn, you should modify your `Cargo.toml`, add the following blocks: 195 | 196 | ```toml 197 | [patch.'https://github.com/unicorn-engine/unicorn'] 198 | unicorn-engine = { path = "/my/own/path/to/local/unicorn" } 199 | # or 200 | unicorn-engine = { git = "http://my/own/unicorn/fork" } 201 | ``` 202 | 203 | For more, see [Overriding Dependencies](https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html). 204 | 205 | ### Debugging 206 | 207 | Inside UnicornAFL, there are many logs could be used for debugging. To enable logging, the easiest way is to add dependency for [env_logger](https://crates.io/crates/env_logger), and at the beginning of your `main` function: 208 | 209 | ```rust 210 | fn main() { 211 | env_logger::init(); 212 | // ... 213 | } 214 | ``` 215 | 216 | And when running, passing `RUST_LOG=trace` as environment. (`AFL_DEBUG=1` is also needed if you are using `afl-fuzz` to run the harness) 217 | 218 | The logging in UnicornAFL utilizes the [log crate](https://crates.io/crates/log), which supports various type of logging backend. If you want to customize the logging behavior, you are suggested to read that crate's document. `env_logger` also supports many customizations. For example, if you only want to see your own logs, and don't want to see logs from UnicornAFL, you could use environment variable `RUST_LOG=trace,unicornafl=off`. 219 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use std::{ 3 | ffi::{CStr, c_uchar}, 4 | os::raw::{c_char, c_void}, 5 | path::PathBuf, 6 | }; 7 | 8 | use executor::{UnicornAflExecutorCustomHook, UnicornAflExecutorHook, UnicornFuzzData}; 9 | 10 | pub mod unicorn_engine { 11 | pub use unicorn_engine::*; 12 | } 13 | 14 | pub mod executor; 15 | mod forkserver; 16 | pub mod harness; 17 | pub mod hash; 18 | pub mod target; 19 | 20 | #[allow(non_camel_case_types)] 21 | #[repr(C)] 22 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 23 | pub enum uc_afl_ret { 24 | UC_AFL_RET_OK = 0, 25 | UC_AFL_RET_ERROR, 26 | UC_AFL_RET_CHILD, 27 | UC_AFL_RET_NO_AFL, 28 | UC_AFL_RET_CALLED_TWICE, 29 | UC_AFL_RET_FINISHED, 30 | UC_AFL_RET_INVALID_UC, 31 | UC_AFL_RET_UC_ERR, 32 | UC_AFL_RET_LIBAFL, 33 | UC_AFL_RET_FFI, 34 | } 35 | 36 | impl From for uc_afl_ret { 37 | fn from(_: libafl::Error) -> Self { 38 | Self::UC_AFL_RET_LIBAFL 39 | } 40 | } 41 | 42 | impl From for uc_afl_ret { 43 | fn from(_: unicorn_engine::uc_error) -> Self { 44 | Self::UC_AFL_RET_UC_ERR 45 | } 46 | } 47 | 48 | #[allow(non_camel_case_types)] 49 | pub type uc_afl_cb_place_input_t = extern "C" fn( 50 | uc: *mut unicorn_engine::uc_engine, 51 | input: *const c_uchar, 52 | input_len: usize, 53 | persistent_round: u64, 54 | data: *mut c_void, 55 | ) -> bool; 56 | 57 | #[allow(non_camel_case_types)] 58 | pub type uc_afl_cb_validate_crash_t = extern "C" fn( 59 | uc: *mut unicorn_engine::uc_engine, 60 | unicorn_result: unicorn_engine::uc_error, 61 | input: *const c_uchar, 62 | input_len: usize, 63 | persistent_round: u64, 64 | data: *mut c_void, 65 | ) -> bool; 66 | 67 | #[allow(non_camel_case_types)] 68 | pub type uc_afl_fuzz_cb_t = extern "C" fn( 69 | uc: *mut unicorn_engine::uc_engine, 70 | data: *mut c_void, 71 | ) -> unicorn_engine::uc_error; 72 | 73 | /// Customized afl fuzz routine entrypoint for Rust user. 74 | /// 75 | /// `exits` means instruction addresses that stop the execution. You can pass 76 | /// an empty vec here if there is not explicit exit. 77 | /// 78 | /// If `always_validate` is true, then `validate_crash_cb` is invoked everytime 79 | /// regardless of the result of execution; Otherwise, only failed execution will 80 | /// invoke such callback. 81 | /// 82 | /// `persistent_iters` is the number of persistent execution rounds. If it is `None`, 83 | /// then the loop will be infinite 84 | pub fn afl_fuzz_custom<'a, D: 'a>( 85 | uc: unicorn_engine::Unicorn<'a, UnicornFuzzData>, 86 | input_file: Option, 87 | callbacks: impl UnicornAflExecutorHook<'a, D>, 88 | exits: Vec, 89 | always_validate: bool, 90 | persistent_iters: Option, 91 | ) -> Result<(), uc_afl_ret> { 92 | target::child_fuzz( 93 | uc, 94 | input_file, 95 | persistent_iters, 96 | callbacks, 97 | exits, 98 | always_validate, 99 | true, 100 | ) 101 | } 102 | 103 | /// Simplified afl fuzz routine entrypoint for Rust user. 104 | /// 105 | /// If you want to manually validate crash or kick fuzzing, call [`afl_fuzz_custom`]. 106 | pub fn afl_fuzz<'a, D: 'a>( 107 | uc: unicorn_engine::Unicorn<'a, UnicornFuzzData>, 108 | input_file: Option, 109 | place_input_cb: impl FnMut(&mut unicorn_engine::Unicorn<'a, UnicornFuzzData>, &[u8], u64) -> bool 110 | + 'a, 111 | exits: Vec, 112 | persistent_iters: Option, 113 | ) -> Result<(), uc_afl_ret> { 114 | afl_fuzz_custom( 115 | uc, 116 | input_file, 117 | UnicornAflExecutorCustomHook::new( 118 | place_input_cb, 119 | target::dummy_uc_validate_crash_callback, 120 | target::dummy_uc_fuzz_callback, 121 | ), 122 | exits, 123 | false, 124 | persistent_iters, 125 | ) 126 | } 127 | 128 | /// Fuzzing entrypoint for FFI 129 | #[cfg(feature = "bindings")] 130 | #[unsafe(no_mangle)] 131 | #[allow(non_camel_case_types)] 132 | pub extern "C" fn uc_afl_fuzz( 133 | uc_handle: *mut unicorn_engine::uc_engine, 134 | input_file: *const c_char, 135 | place_input_callback: uc_afl_cb_place_input_t, 136 | exits: *const u64, 137 | exit_count: usize, 138 | validate_crash_callback: Option, 139 | always_validate: bool, 140 | persistent_iters: u64, 141 | data: *mut c_void, 142 | ) -> uc_afl_ret { 143 | uc_afl_fuzz_internal( 144 | uc_handle, 145 | input_file, 146 | place_input_callback, 147 | exits, 148 | exit_count, 149 | None, 150 | validate_crash_callback, 151 | always_validate, 152 | persistent_iters, 153 | data, 154 | ) 155 | } 156 | 157 | /// Custom fuzzing entrypoint for FFI 158 | #[cfg(feature = "bindings")] 159 | #[unsafe(no_mangle)] 160 | #[allow(non_camel_case_types)] 161 | pub extern "C" fn uc_afl_fuzz_custom( 162 | uc_handle: *mut unicorn_engine::uc_engine, 163 | input_file: *const c_char, 164 | place_input_callback: uc_afl_cb_place_input_t, 165 | fuzz_callback: uc_afl_fuzz_cb_t, 166 | validate_crash_callback: Option, 167 | always_validate: bool, 168 | persistent_iters: u64, 169 | data: *mut c_void, 170 | ) -> uc_afl_ret { 171 | uc_afl_fuzz_internal( 172 | uc_handle, 173 | input_file, 174 | place_input_callback, 175 | std::ptr::null(), 176 | 0, 177 | Some(fuzz_callback), 178 | validate_crash_callback, 179 | always_validate, 180 | persistent_iters, 181 | data, 182 | ) 183 | } 184 | 185 | // In the implementation, there is a lot of manually created closures. 186 | // This is due to the fact that two closure have different types even if 187 | // their signature is the same. As a result, we must split the invocation 188 | // to avoid checking the emptyness inside every round. 189 | #[expect(clippy::too_many_arguments)] 190 | fn uc_afl_fuzz_internal( 191 | uc_handle: *mut unicorn_engine::uc_engine, 192 | input_file: *const c_char, 193 | place_input_callback: uc_afl_cb_place_input_t, 194 | exits: *const u64, 195 | exit_count: usize, 196 | fuzz_callback: Option, 197 | validate_crash_callback: Option, 198 | always_validate: bool, 199 | persistent_iters: u64, 200 | data: *mut c_void, 201 | ) -> uc_afl_ret { 202 | let fuzz_data = UnicornFuzzData::new(data); 203 | let uc = match unsafe { unicorn_engine::Unicorn::from_handle_with_data(uc_handle, fuzz_data) } { 204 | Ok(uc) => uc, 205 | Err(err) => { 206 | return err.into(); 207 | } 208 | }; 209 | 210 | let place_input_cb = 211 | move |uc: &mut unicorn_engine::Unicorn<'_, UnicornFuzzData<*mut c_void>>, 212 | input: &[u8], 213 | persistent_round: u64| { 214 | let handle = uc.get_handle(); 215 | let data = uc.get_data_mut().user_data; 216 | (place_input_callback)(handle, input.as_ptr(), input.len(), persistent_round, data) 217 | }; 218 | let validate_crash_cb = validate_crash_callback.map(|validate_crash_callback| { 219 | move |uc: &mut unicorn_engine::Unicorn<'_, UnicornFuzzData<*mut c_void>>, 220 | unicorn_result: Result<(), unicorn_engine::uc_error>, 221 | input: &[u8], 222 | persistent_round: u64| { 223 | let handle = uc.get_handle(); 224 | let data = uc.get_data_mut().user_data; 225 | let unicorn_result = if let Err(err) = unicorn_result { 226 | err 227 | } else { 228 | unicorn_engine::uc_error::OK 229 | }; 230 | (validate_crash_callback)( 231 | handle, 232 | unicorn_result, 233 | input.as_ptr(), 234 | input.len(), 235 | persistent_round, 236 | data, 237 | ) 238 | } 239 | }); 240 | let fuzz_cb = fuzz_callback.map(|fuzz_callback| { 241 | move |uc: &mut unicorn_engine::Unicorn<'_, UnicornFuzzData<*mut c_void>>| { 242 | let handle = uc.get_handle(); 243 | let data = uc.get_data_mut().user_data; 244 | let unicorn_result = fuzz_callback(handle, data); 245 | if unicorn_result == unicorn_engine::uc_error::OK { 246 | Ok(()) 247 | } else { 248 | Err(unicorn_result) 249 | } 250 | } 251 | }); 252 | 253 | let input_file = if input_file.is_null() { 254 | None 255 | } else { 256 | // legacy usage 257 | let Ok(input_file_str) = unsafe { CStr::from_ptr(input_file) }.to_str() else { 258 | return uc_afl_ret::UC_AFL_RET_FFI; 259 | }; 260 | Some(PathBuf::from(input_file_str)) 261 | }; 262 | 263 | let exits = if exits.is_null() { 264 | vec![] 265 | } else { 266 | unsafe { std::slice::from_raw_parts(exits, exit_count) }.to_vec() 267 | }; 268 | 269 | let persistent_iters = if persistent_iters == 0 { 270 | None 271 | } else { 272 | Some(persistent_iters) 273 | }; 274 | 275 | let res = match (validate_crash_cb, fuzz_cb) { 276 | (Some(validate_crash_cb), Some(fuzz_cb)) => afl_fuzz_custom( 277 | uc, 278 | input_file, 279 | UnicornAflExecutorCustomHook::new(place_input_cb, validate_crash_cb, fuzz_cb), 280 | exits, 281 | always_validate, 282 | persistent_iters, 283 | ), 284 | (Some(validate_crash_cb), None) => afl_fuzz_custom( 285 | uc, 286 | input_file, 287 | UnicornAflExecutorCustomHook::new( 288 | place_input_cb, 289 | validate_crash_cb, 290 | target::dummy_uc_fuzz_callback, 291 | ), 292 | exits, 293 | always_validate, 294 | persistent_iters, 295 | ), 296 | (None, Some(fuzz_cb)) => afl_fuzz_custom( 297 | uc, 298 | input_file, 299 | UnicornAflExecutorCustomHook::new( 300 | place_input_cb, 301 | target::dummy_uc_validate_crash_callback, 302 | fuzz_cb, 303 | ), 304 | exits, 305 | always_validate, 306 | persistent_iters, 307 | ), 308 | (None, None) => afl_fuzz_custom( 309 | uc, 310 | input_file, 311 | UnicornAflExecutorCustomHook::new( 312 | place_input_cb, 313 | target::dummy_uc_validate_crash_callback, 314 | target::dummy_uc_fuzz_callback, 315 | ), 316 | exits, 317 | always_validate, 318 | persistent_iters, 319 | ), 320 | }; 321 | 322 | match res { 323 | Ok(_) => uc_afl_ret::UC_AFL_RET_OK, 324 | Err(err) => err, 325 | } 326 | } 327 | 328 | #[cfg(feature = "python")] 329 | pub mod bindings; 330 | #[cfg(feature = "python")] 331 | pub use bindings::*; 332 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/forkserver.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{PipeReader, PipeWriter}, 3 | os::fd::AsFd, 4 | }; 5 | 6 | use libafl_bolts::os::{ChildHandle, ForkResult}; 7 | use libafl_targets::ForkserverParent; 8 | use log::{error, trace}; 9 | use nix::{ 10 | sys::signal::{SigHandler, Signal}, 11 | unistd::Pid, 12 | }; 13 | 14 | use crate::{executor::UnicornAflExecutor, uc_afl_ret}; 15 | 16 | fn write_to_fd(fd: impl AsFd, message: &[u8]) -> Result<(), uc_afl_ret> { 17 | let bytes_written = 18 | nix::unistd::write(fd, message).map_err(|_| uc_afl_ret::UC_AFL_RET_ERROR)?; 19 | if bytes_written != message.len() { 20 | return Err(uc_afl_ret::UC_AFL_RET_ERROR); 21 | } 22 | Ok(()) 23 | } 24 | pub(crate) fn write_u32_to_fd(fd: impl AsFd, message: u32) -> Result<(), uc_afl_ret> { 25 | write_to_fd(fd, &message.to_ne_bytes()) 26 | } 27 | pub(crate) fn write_u64_to_fd(fd: impl AsFd, message: u64) -> Result<(), uc_afl_ret> { 28 | write_to_fd(fd, &message.to_ne_bytes()) 29 | } 30 | 31 | fn read_from_fd(fd: impl AsFd, message: &mut [u8]) -> Result<(), uc_afl_ret> { 32 | let bytes_read = 33 | nix::unistd::read(fd.as_fd(), message).map_err(|_| uc_afl_ret::UC_AFL_RET_ERROR)?; 34 | if bytes_read != message.len() { 35 | return Err(uc_afl_ret::UC_AFL_RET_ERROR); 36 | } 37 | Ok(()) 38 | } 39 | pub(crate) fn read_u32_from_fd(fd: impl AsFd) -> Result { 40 | let mut buf = [0u8; 4]; 41 | read_from_fd(fd, &mut buf)?; 42 | Ok(u32::from_ne_bytes(buf)) 43 | } 44 | pub(crate) fn read_u64_from_fd(fd: impl AsFd) -> Result { 45 | let mut buf = [0u8; 8]; 46 | read_from_fd(fd, &mut buf)?; 47 | Ok(u64::from_ne_bytes(buf)) 48 | } 49 | 50 | /// Messages from unicornafl child to parent 51 | pub(crate) mod afl_child_ret { 52 | pub(crate) type ChildRet = u32; 53 | /// Current execution done without any interestring findings. 54 | /// Wait for parent to fire next execution 55 | pub(crate) const NEXT: ChildRet = 0; 56 | /// Current execution done with a crash found 57 | pub(crate) const FOUND_CRASH: ChildRet = 1; 58 | /// Edge generation event. This is always followed by generated edge PC. 59 | /// 60 | /// This will never be sent when child finished its execution. 61 | pub(crate) const TSL_REQUEST: ChildRet = 2; 62 | /// The child process has exited. 63 | /// 64 | /// This will never be sent from child to parent. Instead, this is a phantom 65 | /// state used for forkserver parent state management. 66 | pub(crate) const EXITED: ChildRet = 3; 67 | /// The child process found a crash, and is the last execution of one persistent loop. 68 | /// 69 | /// This is used for indicating the parent that it should not expect the child is in 70 | /// persistent loop any more. 71 | pub(crate) const FOUND_CRASH_AND_EXITED: ChildRet = 4; 72 | } 73 | 74 | type AflChildRet = afl_child_ret::ChildRet; 75 | 76 | /// Forkserver parent for UnicornAFL 77 | pub struct UnicornAflForkserverParent<'a, D, OT, H> 78 | where 79 | D: 'a, 80 | { 81 | /// Executor. 82 | /// 83 | /// You could drop the parent and take ownership back when parent 84 | /// returns from [`start_forkserver`][libafl_targets::start_forkserver], which 85 | /// indicates that it is the child process, and parent is useless anymore (the 86 | /// owned resources have been transferred to the executor itself). 87 | pub(crate) executor: UnicornAflExecutor<'a, D, OT, H>, 88 | child_pipe_r: Option, 89 | child_pipe_w: Option, 90 | parent_pipe_r: Option, 91 | parent_pipe_w: Option, 92 | last_child_pid: Option, 93 | last_child_ret: AflChildRet, 94 | old_sigchld_handler: Option, 95 | wifsignaled: i32, 96 | } 97 | 98 | impl<'a, D, OT, H> UnicornAflForkserverParent<'a, D, OT, H> 99 | where 100 | D: 'a, 101 | { 102 | /// Create a new forkserver parent 103 | pub fn new(executor: UnicornAflExecutor<'a, D, OT, H>) -> Self { 104 | Self { 105 | executor, 106 | child_pipe_r: None, 107 | child_pipe_w: None, 108 | parent_pipe_r: None, 109 | parent_pipe_w: None, 110 | last_child_pid: None, 111 | last_child_ret: afl_child_ret::EXITED, 112 | old_sigchld_handler: None, 113 | wifsignaled: get_valid_wifsignaled(), 114 | } 115 | } 116 | } 117 | 118 | impl<'a, D, OT, H> ForkserverParent for UnicornAflForkserverParent<'a, D, OT, H> 119 | where 120 | D: 'a, 121 | { 122 | fn pre_fuzzing(&mut self) -> Result<(), libafl::Error> { 123 | let old_sigchld_handler = 124 | (unsafe { nix::sys::signal::signal(Signal::SIGCHLD, SigHandler::SigDfl) }) 125 | .inspect_err(|_| { 126 | error!("Fail to swap signal handler for SIGCHLD."); 127 | })?; 128 | self.old_sigchld_handler = Some(old_sigchld_handler); 129 | Ok(()) 130 | } 131 | 132 | fn handle_child_requests(&mut self) -> Result { 133 | let child_pipe_r = self.child_pipe_r.as_ref().unwrap().as_fd(); 134 | self.last_child_ret = loop { 135 | let Ok(child_msg) = read_u32_from_fd(child_pipe_r) else { 136 | break afl_child_ret::EXITED; 137 | }; 138 | 139 | trace!("Get a child_msg={child_msg}"); 140 | 141 | if child_msg == afl_child_ret::NEXT 142 | || child_msg == afl_child_ret::FOUND_CRASH 143 | || child_msg == afl_child_ret::EXITED 144 | || child_msg == afl_child_ret::FOUND_CRASH_AND_EXITED 145 | { 146 | break child_msg; 147 | } else if child_msg == afl_child_ret::TSL_REQUEST { 148 | let Ok(pc) = read_u64_from_fd(child_pipe_r) else { 149 | error!("Fail to read child tsl request."); 150 | break afl_child_ret::EXITED; 151 | }; 152 | 153 | if self.executor.uc.ctl_request_cache(pc, None).is_ok() { 154 | trace!("TB is cached at 0x{pc:x}"); 155 | } else { 156 | error!("Failed to cache the TB at 0x{pc:x}"); 157 | } 158 | } else { 159 | error!("Unexpected response by child! {child_msg}. Please report this as bug for unicornafl. 160 | Expected one of {{AFL_CHILD_NEXT: {}, AFL_CHILD_FOUND_CRASH: {}, AFL_CHILD_TSL_REQUEST: {}, AFL_CHILD_EXITED: {}, AFL_CHILD_FOUND_CRASH_AND_EXITED: {}}}.", afl_child_ret::NEXT, afl_child_ret::FOUND_CRASH, afl_child_ret::TSL_REQUEST, afl_child_ret::EXITED, afl_child_ret::FOUND_CRASH_AND_EXITED); 161 | } 162 | }; 163 | 164 | match self.last_child_ret { 165 | afl_child_ret::NEXT => { 166 | // Child asks for next in persistent mode 167 | // This status tells AFL we are not crashed. 168 | Ok(0) 169 | } 170 | afl_child_ret::FOUND_CRASH => { 171 | // WIFSIGNALED(wifsignaled) == 1 -> tells AFL the child crashed 172 | // (even though it's still alive for persistent mode) 173 | Ok(self.wifsignaled) 174 | } 175 | afl_child_ret::FOUND_CRASH_AND_EXITED => { 176 | if unsafe { 177 | nix::libc::waitpid( 178 | *self.last_child_pid.as_ref().unwrap(), 179 | std::ptr::null_mut(), 180 | 0, 181 | ) 182 | } < 0 183 | { 184 | // Zombie Child could not be collected. Scary! 185 | error!("[!] The child's exit code could not be determined."); 186 | return Err(libafl::Error::illegal_state("waitpid")); 187 | } 188 | // WIFSIGNALED(wifsignaled) == 1 -> tells AFL the child crashed 189 | // (even though it's still alive for persistent mode) 190 | Ok(self.wifsignaled) 191 | } 192 | afl_child_ret::EXITED => { 193 | // Tell parent(unicornafl) to fork a new child. 194 | self.last_child_ret = afl_child_ret::EXITED; 195 | // If child exited, get and relay exit status to parent through waitpid 196 | let mut status = 0i32; 197 | if unsafe { 198 | nix::libc::waitpid(*self.last_child_pid.as_ref().unwrap(), &mut status, 0) 199 | } < 0 200 | { 201 | // Zombie Child could not be collected. Scary! 202 | error!("[!] The child's exit code could not be determined."); 203 | return Err(libafl::Error::illegal_state("waitpid")); 204 | } 205 | 206 | Ok(status) 207 | } 208 | _ => unreachable!(), 209 | } 210 | } 211 | 212 | fn spawn_child(&mut self, was_killed: bool) -> Result { 213 | // If we stopped the child in persistent mode, but there was a race 214 | // condition and afl-fuzz already issued SIGKILL, write off the old 215 | // process. 216 | if self.last_child_ret != afl_child_ret::EXITED && was_killed { 217 | error!("Child was killed by AFL in the meantime."); 218 | 219 | self.last_child_ret = afl_child_ret::EXITED; 220 | if let Some(child_pid) = self.last_child_pid.take() { 221 | nix::sys::wait::waitpid(Pid::from_raw(child_pid), None).inspect_err(|_| { 222 | error!("Error waiting for child"); 223 | })?; 224 | } 225 | } 226 | 227 | trace!("Spawn a child, last: {:?}", &self.last_child_ret); 228 | if self.last_child_ret == afl_child_ret::EXITED { 229 | // Child dead. Establish new a channel with child to grab 230 | // translation commands. We'll read from child_pipe_r, 231 | // child will write to child_pipe_w. 232 | let (child_pipe_r, child_pipe_w) = std::io::pipe().inspect_err(|_| { 233 | error!("[!] Error creating pipe to child"); 234 | })?; 235 | // The re-assignment will close the previously-unclosed pipe ends 236 | self.child_pipe_r = Some(child_pipe_r); 237 | self.child_pipe_w = Some(child_pipe_w); 238 | let (parent_pipe_r, parent_pipe_w) = std::io::pipe().inspect_err(|_| { 239 | error!("[!] Error creating pipe to parent"); 240 | })?; 241 | self.parent_pipe_r = Some(parent_pipe_r); 242 | self.parent_pipe_w = Some(parent_pipe_w); 243 | 244 | trace!("Going to fork a new child!"); 245 | // Create a clone of our process. 246 | let fork_result = (unsafe { libafl_bolts::os::fork() }).inspect_err(|_| { 247 | error!("[!] Could not fork"); 248 | })?; 249 | 250 | // In child process: close fds, resume execution. 251 | match &fork_result { 252 | ForkResult::Child => { 253 | // New Child 254 | (unsafe { 255 | nix::sys::signal::signal( 256 | Signal::SIGCHLD, 257 | self.old_sigchld_handler.take().unwrap(), 258 | ) 259 | }) 260 | .inspect_err(|_| { 261 | error!("Fail to restore signal handler for SIGCHLD."); 262 | })?; 263 | self.child_pipe_r = None; 264 | self.parent_pipe_w = None; 265 | // Forward owned fd to executor to make it alive 266 | self.executor.uc.get_data_mut().child_pipe_w = self.child_pipe_w.take(); 267 | self.executor.uc.get_data_mut().parent_pipe_r = self.parent_pipe_r.take(); 268 | } 269 | ForkResult::Parent(child_pid) => { 270 | // parent for new child 271 | 272 | // If we don't close this in parent, we don't get notified 273 | // on afl_child_pipe once child is gone 274 | self.child_pipe_w = None; 275 | self.parent_pipe_r = None; 276 | self.last_child_pid = Some(child_pid.pid); 277 | } 278 | } 279 | Ok(fork_result) 280 | } else { 281 | // parent, in persistent mode 282 | let child_pid = ChildHandle { 283 | pid: *self.last_child_pid.as_ref().unwrap(), 284 | }; 285 | 286 | // Special handling for persistent mode: if the child is alive 287 | // but currently stopped, simply restart it with a write to 288 | // afl_parent_pipe. In case we fuzz using shared map, use this 289 | // method to forward the size of the current testcase to the 290 | // child without cost. 291 | if write_u32_to_fd(self.parent_pipe_w.as_ref().unwrap().as_fd(), 0).is_err() { 292 | self.last_child_ret = afl_child_ret::EXITED; 293 | return self.spawn_child(was_killed); 294 | } 295 | 296 | Ok(ForkResult::Parent(child_pid)) 297 | } 298 | } 299 | } 300 | 301 | /// Try to get a valid status which could make `WIFSIGNALED` return `true`. 302 | fn get_valid_wifsignaled() -> i32 { 303 | let mut status = 0; 304 | 305 | while !nix::libc::WIFSIGNALED(status) { 306 | status += 1; 307 | } 308 | 309 | status 310 | } 311 | -------------------------------------------------------------------------------- /src/executor.rs: -------------------------------------------------------------------------------- 1 | //! Executor to conduct unicorn afl fuzzing in one execution round. 2 | 3 | use std::{ 4 | io::{PipeReader, PipeWriter}, 5 | marker::PhantomData, 6 | }; 7 | 8 | use libafl::{ 9 | executors::{Executor, ExitKind, HasObservers}, 10 | inputs::HasTargetBytes, 11 | observers::ObserversTuple, 12 | state::HasExecutions, 13 | }; 14 | use libafl_bolts::tuples::RefIndexable; 15 | use libafl_targets::{CMPLOG_MAP_W, EDGES_MAP_PTR}; 16 | use log::{debug, error, trace, warn}; 17 | use unicorn_engine::{TcgOpCode, TcgOpFlag, UcHookId, Unicorn, uc_error}; 18 | 19 | use crate::hash::afl_hash_ip; 20 | 21 | /// State for hook edge 22 | #[derive(Debug)] 23 | struct HookState { 24 | prev_loc: u32, 25 | map_size: u32, 26 | } 27 | 28 | fn get_afl_map_size() -> u32 { 29 | std::env::var("AFL_MAP_SIZE") 30 | .ok() 31 | .and_then(|sz| sz.parse::().ok()) 32 | .unwrap_or(1 << 16) // MAP_SIZE 33 | } 34 | 35 | /// Data persisted during fuzzing. You can use `uc.get_data()`[Unicorn::get_data] 36 | /// and `uc.get_data_mut()`[Unicorn::get_data_mut] to access this data in callbacks 37 | /// and hooks during fuzzing. 38 | /// 39 | /// You can create a default fuzz data by [`UnicornFuzzData::default()`] if you don't 40 | /// want any custom data. 41 | #[derive(Debug)] 42 | pub struct UnicornFuzzData { 43 | hook_state: HookState, 44 | /// Store write side to child pipe. Closed when dropping 45 | pub(crate) child_pipe_w: Option, 46 | /// Store read side to parent pipe. Closed when dropping 47 | pub(crate) parent_pipe_r: Option, 48 | /// User-defined data. 49 | pub user_data: D, 50 | } 51 | 52 | impl UnicornFuzzData { 53 | pub(crate) fn map_size(&self) -> u32 { 54 | self.hook_state.map_size 55 | } 56 | } 57 | 58 | impl Default for UnicornFuzzData<()> { 59 | fn default() -> Self { 60 | Self::new(()) 61 | } 62 | } 63 | 64 | impl UnicornFuzzData { 65 | /// Create a new unicorn fuzz data. 66 | /// 67 | /// This will try to retrieve env `AFL_MAP_SIZE` to determine map size, and 68 | /// fill the default value if no such env. 69 | pub fn new(user_data: D) -> Self { 70 | Self { 71 | hook_state: HookState { 72 | prev_loc: 0, 73 | map_size: get_afl_map_size(), 74 | }, 75 | user_data, 76 | child_pipe_w: None, 77 | parent_pipe_r: None, 78 | } 79 | } 80 | 81 | /// Clear hook state. Always call this method before each execution 82 | pub(crate) fn clear_prev_loc(&mut self) { 83 | self.hook_state.prev_loc = 0; 84 | } 85 | } 86 | 87 | unsafe fn update_coverage(idx: usize) { 88 | unsafe { 89 | let loc = EDGES_MAP_PTR.byte_add(idx); 90 | let prev = *loc; 91 | *loc = prev + 1; 92 | } 93 | } 94 | 95 | unsafe fn update_with_prev(loc: u32, prev: u32) { 96 | let idx = prev ^ loc; 97 | unsafe { 98 | update_coverage(idx as usize); 99 | } 100 | } 101 | 102 | fn hook_code_coverage<'a, D: 'a>( 103 | uc: &mut Unicorn<'a, UnicornFuzzData>, 104 | address: u64, 105 | _size: u32, 106 | ) { 107 | let state = &mut uc.get_data_mut().hook_state; 108 | let cur_loc = afl_hash_ip(address) & (state.map_size - 1); 109 | trace!( 110 | "Coverage address={:x} prev={:x} cur_loc={:x}", 111 | address, state.prev_loc, cur_loc 112 | ); 113 | unsafe { update_with_prev(cur_loc, state.prev_loc) }; 114 | state.prev_loc = cur_loc >> 1; 115 | } 116 | 117 | fn hook_sub_impl_16(cur_loc: u32, prev_loc: u32, arg1: u64, arg2: u64) { 118 | if (arg1 & 0xff00) == (arg2 & 0xff00) { 119 | unsafe { update_with_prev(cur_loc, prev_loc) } 120 | } 121 | } 122 | 123 | fn hook_sub_impl_32(cur_loc: u32, prev_loc: u32, arg1: u64, arg2: u64) { 124 | if (arg1 & 0xff000000) == (arg2 & 0xff000000) { 125 | unsafe { update_with_prev(cur_loc + 2, prev_loc) } 126 | if (arg1 & 0xff0000) == (arg2 & 0xff0000) { 127 | unsafe { update_with_prev(cur_loc + 1, prev_loc) } 128 | if (arg1 & 0xff00) == (arg2 & 0xff00) { 129 | unsafe { update_with_prev(cur_loc, prev_loc) } 130 | } 131 | } 132 | } 133 | } 134 | 135 | fn hook_sub_impl_64(cur_loc: u32, prev_loc: u32, arg1: u64, arg2: u64) { 136 | if (arg1 & 0xff00000000000000) == (arg2 & 0xff00000000000000) { 137 | unsafe { update_with_prev(cur_loc + 6, prev_loc) } 138 | if (arg1 & 0xff000000000000) == (arg2 & 0xff000000000000) { 139 | unsafe { update_with_prev(cur_loc + 5, prev_loc) } 140 | if (arg1 & 0xff0000000000) == (arg2 & 0xff0000000000) { 141 | unsafe { update_with_prev(cur_loc + 4, prev_loc) } 142 | if (arg1 & 0xff00000000) == (arg2 & 0xff00000000) { 143 | unsafe { update_with_prev(cur_loc + 3, prev_loc) } 144 | if (arg1 & 0xff000000) == (arg2 & 0xff000000) { 145 | unsafe { update_with_prev(cur_loc + 2, prev_loc) } 146 | if (arg1 & 0xff0000) == (arg2 & 0xff0000) { 147 | unsafe { update_with_prev(cur_loc + 1, prev_loc) } 148 | if (arg1 & 0xff00) == (arg2 & 0xff00) { 149 | unsafe { update_with_prev(cur_loc, prev_loc) } 150 | } 151 | } 152 | } 153 | } 154 | } 155 | } 156 | } 157 | } 158 | 159 | fn hook_opcode_cmpcov<'a, D: 'a>( 160 | uc: &mut Unicorn<'a, UnicornFuzzData>, 161 | address: u64, 162 | arg1: u64, 163 | arg2: u64, 164 | size: usize, 165 | ) { 166 | let state = &uc.get_data().hook_state; 167 | let mut cur_loc = afl_hash_ip(address) & (state.map_size - 1); 168 | 169 | trace!( 170 | "Compcov address={:x} arg1={:x} arg2={:x} size={} cur_loc={:x}", 171 | address, arg1, arg2, size, cur_loc 172 | ); 173 | if size >= 64 { 174 | if cur_loc + 8 >= state.map_size { 175 | cur_loc -= 8; 176 | } 177 | hook_sub_impl_64(cur_loc, state.prev_loc, arg1, arg2); 178 | } else if size >= 32 { 179 | if cur_loc + 4 >= state.map_size { 180 | cur_loc -= 4; 181 | } 182 | hook_sub_impl_32(cur_loc, state.prev_loc, arg1, arg2); 183 | } else { 184 | if cur_loc + 2 >= state.map_size { 185 | cur_loc -= 2; 186 | } 187 | hook_sub_impl_16(cur_loc, state.prev_loc, arg1, arg2); 188 | } 189 | } 190 | 191 | fn hook_opcode_cmplog<'a, D: 'a>( 192 | uc: &mut Unicorn<'a, UnicornFuzzData>, 193 | address: u64, 194 | arg1: u64, 195 | arg2: u64, 196 | size: usize, 197 | ) { 198 | let state = &uc.get_data().hook_state; 199 | let cur_loc = afl_hash_ip(address) & (state.map_size - 1); 200 | let k = cur_loc as usize & (CMPLOG_MAP_W - 1); 201 | let shape = match size { 202 | 8 => 1, 203 | 16 => 2, 204 | 32 => 4, 205 | 64 => 8, 206 | _ => 0, 207 | }; 208 | 209 | trace!( 210 | "Complog address={} arg1={} arg2={} size={} shape={} cur_loc={} k={}", 211 | address, arg1, arg2, size, shape, cur_loc, k 212 | ); 213 | 214 | unsafe { 215 | libafl_targets::cmps::__libafl_targets_cmplog_instructions_extended(k, shape, arg1, arg2); 216 | } 217 | } 218 | 219 | /// Callbacks for each execution round 220 | pub trait UnicornAflExecutorHook<'a, D> { 221 | /// Place the generated input into unicorn's memory. 222 | /// 223 | /// Return false if the generated input is not acceptable 224 | fn place_input( 225 | &mut self, 226 | uc: &mut Unicorn<'a, UnicornFuzzData>, 227 | input: &[u8], 228 | persistent_round: u64, 229 | ) -> bool; 230 | 231 | /// Return true if the crash is valid after validation. 232 | /// 233 | /// The default implementation is [`dummy_uc_validate_crash_callback`][crate::target::dummy_uc_validate_crash_callback] 234 | fn validate_crash( 235 | &mut self, 236 | uc: &mut Unicorn<'a, UnicornFuzzData>, 237 | unicorn_result: Result<(), uc_error>, 238 | input: &[u8], 239 | persistent_round: u64, 240 | ) -> bool { 241 | crate::target::dummy_uc_validate_crash_callback(uc, unicorn_result, input, persistent_round) 242 | } 243 | 244 | /// The real procedure to kick unicorn engine start 245 | /// 246 | /// The default implementation is [`dummy_uc_fuzz_callback`][crate::target::dummy_uc_fuzz_callback] 247 | fn fuzz(&mut self, uc: &mut Unicorn<'a, UnicornFuzzData>) -> Result<(), uc_error> { 248 | crate::target::dummy_uc_fuzz_callback(uc) 249 | } 250 | } 251 | 252 | /// Convenient struct to create a [`UnicornAflExecutorHook`] from closures 253 | pub struct UnicornAflExecutorCustomHook<'a, D, FI, FV, FC> { 254 | place_input_callback: FI, 255 | validate_crash_callback: FV, 256 | fuzz_callback: FC, 257 | phantom: PhantomData<&'a D>, 258 | } 259 | 260 | impl<'a, D, FI, FV, FC> UnicornAflExecutorCustomHook<'a, D, FI, FV, FC> 261 | where 262 | FI: FnMut(&mut Unicorn<'a, UnicornFuzzData>, &[u8], u64) -> bool + 'a, 263 | FV: FnMut(&mut Unicorn<'a, UnicornFuzzData>, Result<(), uc_error>, &[u8], u64) -> bool + 'a, 264 | FC: FnMut(&mut Unicorn<'a, UnicornFuzzData>) -> Result<(), uc_error> + 'a, 265 | { 266 | /// Create a new custom hook from closures 267 | pub fn new(place_input_callback: FI, validate_crash_callback: FV, fuzz_callback: FC) -> Self { 268 | Self { 269 | place_input_callback, 270 | validate_crash_callback, 271 | fuzz_callback, 272 | phantom: PhantomData, 273 | } 274 | } 275 | } 276 | 277 | impl<'a, D, FI, FV, FC> UnicornAflExecutorHook<'a, D> 278 | for UnicornAflExecutorCustomHook<'a, D, FI, FV, FC> 279 | where 280 | FI: FnMut(&mut Unicorn<'a, UnicornFuzzData>, &[u8], u64) -> bool + 'a, 281 | FV: FnMut(&mut Unicorn<'a, UnicornFuzzData>, Result<(), uc_error>, &[u8], u64) -> bool + 'a, 282 | FC: FnMut(&mut Unicorn<'a, UnicornFuzzData>) -> Result<(), uc_error> + 'a, 283 | { 284 | fn place_input( 285 | &mut self, 286 | uc: &mut Unicorn<'a, UnicornFuzzData>, 287 | input: &[u8], 288 | persistent_round: u64, 289 | ) -> bool { 290 | (self.place_input_callback)(uc, input, persistent_round) 291 | } 292 | 293 | fn validate_crash( 294 | &mut self, 295 | uc: &mut Unicorn<'a, UnicornFuzzData>, 296 | unicorn_result: Result<(), uc_error>, 297 | input: &[u8], 298 | persistent_round: u64, 299 | ) -> bool { 300 | (self.validate_crash_callback)(uc, unicorn_result, input, persistent_round) 301 | } 302 | 303 | fn fuzz(&mut self, uc: &mut Unicorn<'a, UnicornFuzzData>) -> Result<(), uc_error> { 304 | (self.fuzz_callback)(uc) 305 | } 306 | } 307 | 308 | #[derive(Debug, Clone, Copy)] 309 | /// Policy to deal with CMP and SUB instructions 310 | pub enum CmpPolicy { 311 | /// Use Redqueen algorithm 312 | /// 313 | /// To use this policy, users should first setup [`CMPLOG_MAP_PTR`][libafl_targets::cmps::CMPLOG_MAP_PTR] 314 | /// by methods like [`map_cmplog_shared_memory`][libafl_targets::map_cmplog_shared_memory] 315 | Cmplog, 316 | /// Use CMPCOV algorithm 317 | /// 318 | /// To use this policy, users should first setup [`EDGES_MAP_PTR`][libafl_targets::EDGES_MAP_PTR] 319 | /// by methods like [`map_shared_memory`][libafl_targets::map_shared_memory]. 320 | Cmpcov, 321 | /// Do nothing 322 | None, 323 | } 324 | 325 | /// Executor for unicorn afl fuzzing. Can be used in both forkserver mode 326 | /// and LibAFL. 327 | pub struct UnicornAflExecutor<'a, D, OT, H> 328 | where 329 | D: 'a, 330 | { 331 | /// The real unicorn engine 332 | pub uc: Unicorn<'a, UnicornFuzzData>, 333 | /// The observers, observing each run 334 | observers: OT, 335 | /// Whether the `validate_crash_cb` is invoked everytime regardless of 336 | /// the execution result. 337 | /// 338 | /// If false, only execution failure will lead to the callback. 339 | always_validate: bool, 340 | /// Stored for deleting hook when dropping 341 | /// 342 | /// None if in CMPLOG mode, which does not require coverage feedback 343 | block_hook: Option, 344 | /// Stored for deleting hook when dropping 345 | /// 346 | /// None if user does not specify CMPLOG nor CMPCOV 347 | sub_hook: Option, 348 | /// Stored for deleting hook when dropping 349 | /// 350 | /// None if user does not specify CMPLOG nor CMPCOV 351 | cmp_hook: Option, 352 | /// Stored for deleting hook when dropping 353 | /// 354 | /// None if in infinite persistent mode, which does not TB cache 355 | new_tb_hook: Option, 356 | /// Callback hooks 357 | callbacks: H, 358 | } 359 | 360 | impl<'a, D, OT, H> UnicornAflExecutor<'a, D, OT, H> 361 | where 362 | D: 'a, 363 | H: UnicornAflExecutorHook<'a, D>, 364 | { 365 | /// Create a new executor 366 | /// 367 | /// * `always_validate`: Whether call validate callback even if this round does not lead to crash 368 | /// * `cache_tb`: Whether enable TB-cache, which is useful if not in infinite loop 369 | pub fn new( 370 | mut uc: Unicorn<'a, UnicornFuzzData>, 371 | observers: OT, 372 | callbacks: H, 373 | always_validate: bool, 374 | exits: Vec, 375 | cache_tb: bool, 376 | cmp_policy: CmpPolicy, 377 | ) -> Result { 378 | if !exits.is_empty() { 379 | // Enable exits if requested 380 | uc.ctl_exits_enable().inspect_err(|ret| { 381 | warn!("Fail to enable exits due to {ret}"); 382 | })?; 383 | uc.ctl_set_exits(&exits).inspect_err(|ret| { 384 | warn!("Fail to write exits due to {ret}"); 385 | })?; 386 | } 387 | 388 | let block_hook = if matches!(cmp_policy, CmpPolicy::Cmplog) { 389 | None 390 | } else { 391 | trace!("Adding block hook"); 392 | Some( 393 | uc.add_block_hook(1, 0, |uc, address, size| { 394 | hook_code_coverage(uc, address, size); 395 | }) 396 | .inspect_err(|ret| { 397 | warn!("Fail to add block hooks due to {ret}"); 398 | })?, 399 | ) 400 | }; 401 | let sub_hook; 402 | let cmp_hook; 403 | debug!("Our cmp policy is {:?}", &cmp_policy); 404 | match cmp_policy { 405 | CmpPolicy::Cmplog => { 406 | sub_hook = Some( 407 | uc.add_tcg_hook( 408 | TcgOpCode::SUB, 409 | TcgOpFlag::DIRECT, 410 | 1, 411 | 0, 412 | |uc, address, arg1, arg2, size| { 413 | hook_opcode_cmplog(uc, address, arg1, arg2, size); 414 | }, 415 | ) 416 | .inspect_err(|ret| { 417 | warn!("Fail to add sub hooks due to {ret}"); 418 | })?, 419 | ); 420 | cmp_hook = Some( 421 | uc.add_tcg_hook( 422 | TcgOpCode::SUB, 423 | TcgOpFlag::CMP, 424 | 1, 425 | 0, 426 | |uc, address, arg1, arg2, size| { 427 | hook_opcode_cmplog(uc, address, arg1, arg2, size); 428 | }, 429 | ) 430 | .inspect_err(|ret| { 431 | warn!("Fail to add cmp hooks due to {ret}"); 432 | })?, 433 | ); 434 | } 435 | CmpPolicy::Cmpcov => { 436 | sub_hook = Some( 437 | uc.add_tcg_hook( 438 | TcgOpCode::SUB, 439 | TcgOpFlag::DIRECT, 440 | 1, 441 | 0, 442 | |uc, address, arg1, arg2, size| { 443 | hook_opcode_cmpcov(uc, address, arg1, arg2, size); 444 | }, 445 | ) 446 | .inspect_err(|ret| { 447 | warn!("Fail to add sub hooks due to {ret}"); 448 | })?, 449 | ); 450 | cmp_hook = Some( 451 | uc.add_tcg_hook( 452 | TcgOpCode::SUB, 453 | TcgOpFlag::CMP, 454 | 1, 455 | 0, 456 | |uc, address, arg1, arg2, size| { 457 | hook_opcode_cmpcov(uc, address, arg1, arg2, size); 458 | }, 459 | ) 460 | .inspect_err(|ret| { 461 | warn!("Fail to add cmp hooks due to {ret}"); 462 | })?, 463 | ); 464 | } 465 | CmpPolicy::None => { 466 | sub_hook = None; 467 | cmp_hook = None; 468 | } 469 | } 470 | let new_tb_hook = if cache_tb { 471 | Some( 472 | uc.add_edge_gen_hook(1, 0, |uc, cur_tb, _| { 473 | if let Some(child_pipe_w) = &uc.get_data_mut().child_pipe_w { 474 | if crate::forkserver::write_u32_to_fd( 475 | child_pipe_w, 476 | crate::forkserver::afl_child_ret::TSL_REQUEST, 477 | ) 478 | .is_err() 479 | { 480 | error!("Error writing TSL REQUEST"); 481 | return; 482 | } 483 | #[expect(clippy::needless_return)] 484 | if crate::forkserver::write_u64_to_fd(child_pipe_w, cur_tb.pc).is_err() { 485 | error!("Error writing TSL REQUEST pc"); 486 | return; 487 | } 488 | } 489 | }) 490 | .inspect_err(|ret| { 491 | warn!("Fail to add edge gen hooks due to {ret}"); 492 | })?, 493 | ) 494 | } else { 495 | None 496 | }; 497 | 498 | Ok(Self { 499 | uc, 500 | observers, 501 | callbacks, 502 | always_validate, 503 | block_hook, 504 | sub_hook, 505 | cmp_hook, 506 | new_tb_hook, 507 | }) 508 | } 509 | 510 | /// Bare execution without any state modification. Always call wrappers 511 | /// like [`run_target`][Executor::run_target] or [`forkserver_run_harness`][crate::harness::forkserver_run_harness] 512 | pub(crate) fn execute_internal( 513 | &mut self, 514 | input: &[u8], 515 | persistent_round: u64, 516 | ) -> Result { 517 | self.uc.get_data_mut().clear_prev_loc(); 518 | 519 | let accepted = self 520 | .callbacks 521 | .place_input(&mut self.uc, input, persistent_round); 522 | 523 | if !accepted { 524 | trace!("Input not accepted"); 525 | return Ok(ExitKind::Ok); 526 | } 527 | 528 | let err = self.callbacks.fuzz(&mut self.uc); 529 | 530 | if let Err(err) = &err { 531 | trace!("Child returns: {err}"); 532 | } else { 533 | trace!("Child returns: OK"); 534 | } 535 | 536 | let mut crash_found = false; 537 | 538 | if (err.is_err() || self.always_validate) 539 | && self 540 | .callbacks 541 | .validate_crash(&mut self.uc, err, input, persistent_round) 542 | { 543 | crash_found = true; 544 | } 545 | 546 | if crash_found { 547 | Ok(ExitKind::Crash) 548 | } else { 549 | Ok(ExitKind::Ok) 550 | } 551 | } 552 | } 553 | 554 | impl HasObservers for UnicornAflExecutor<'_, D, OT, H> { 555 | type Observers = OT; 556 | 557 | fn observers(&self) -> RefIndexable<&Self::Observers, Self::Observers> { 558 | RefIndexable::from(&self.observers) 559 | } 560 | 561 | fn observers_mut(&mut self) -> RefIndexable<&mut Self::Observers, Self::Observers> { 562 | RefIndexable::from(&mut self.observers) 563 | } 564 | } 565 | 566 | impl<'a, D, OT, H> Drop for UnicornAflExecutor<'a, D, OT, H> 567 | where 568 | D: 'a, 569 | { 570 | fn drop(&mut self) { 571 | if let Some(block_hook) = self.block_hook.take() { 572 | if let Err(ret) = self.uc.remove_hook(block_hook) { 573 | warn!("Fail to uninstall block hook due to {ret}"); 574 | } 575 | } 576 | if let Some(sub_hook) = self.sub_hook.take() { 577 | if let Err(ret) = self.uc.remove_hook(sub_hook) { 578 | warn!("Fail to uninstall sub tcg opcode hook due to {ret}"); 579 | } 580 | } 581 | if let Some(cmp_hook) = self.cmp_hook.take() { 582 | if let Err(ret) = self.uc.remove_hook(cmp_hook) { 583 | warn!("Fail to uninstall cmp tcg opcode hook due to {ret}"); 584 | } 585 | } 586 | if let Some(new_tb_hook) = self.new_tb_hook.take() { 587 | if let Err(ret) = self.uc.remove_hook(new_tb_hook) { 588 | warn!("Fail to uninstall edge gen hook due to {ret}"); 589 | } 590 | } 591 | } 592 | } 593 | 594 | impl<'a, EM, I, S, Z, D, OT, H> Executor for UnicornAflExecutor<'a, D, OT, H> 595 | where 596 | S: HasExecutions, 597 | I: HasTargetBytes, 598 | OT: ObserversTuple, 599 | D: 'a, 600 | H: UnicornAflExecutorHook<'a, D>, 601 | { 602 | fn run_target( 603 | &mut self, 604 | _fuzzer: &mut Z, 605 | state: &mut S, 606 | _mgr: &mut EM, 607 | input: &I, 608 | ) -> Result { 609 | *state.executions_mut() += 1; 610 | self.observers.pre_exec_all(state, input)?; 611 | 612 | let exit_kind = self.execute_internal(&input.target_bytes(), *state.executions())?; 613 | 614 | self.observers.post_exec_all(state, input, &exit_kind)?; 615 | 616 | Ok(exit_kind) 617 | } 618 | } 619 | --------------------------------------------------------------------------------