From d29b657bf46a9ac459749a630284a7497b288df2 Mon Sep 17 00:00:00 2001 From: Dominik Maier Date: Sat, 12 Oct 2024 13:16:33 +0200 Subject: [PATCH] Add LibAFL_Jumper util (#2594) * Add jumper * Hex Hex * Fix hex decoding * Cleanup readme * Build with musl * add mips delay slot back * Added linker script * More jump * clippy * taplo --- Cargo.toml | 9 ++ utils/libafl_jumper/Cargo.toml | 19 +++ utils/libafl_jumper/README.md | 28 +++++ utils/libafl_jumper/linker_script.ld | 12 ++ utils/libafl_jumper/src/main.rs | 168 +++++++++++++++++++++++++++ 5 files changed, 236 insertions(+) create mode 100644 utils/libafl_jumper/Cargo.toml create mode 100644 utils/libafl_jumper/README.md create mode 100644 utils/libafl_jumper/linker_script.ld create mode 100644 utils/libafl_jumper/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 6cbe630f14..974cf1893a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ members = [ "utils/build_and_test_fuzzers", "utils/deexit", "utils/libafl_benches", + "utils/libafl_jumper", "utils/gramatron/construct_automata", ] default-members = [ @@ -91,3 +92,11 @@ lto = true codegen-units = 1 opt-level = 3 debug = true + +[profile.release-abort] +inherits = "release" +lto = true +codegen-units = 1 +opt-level = 3 +debug = true +abort = true diff --git a/utils/libafl_jumper/Cargo.toml b/utils/libafl_jumper/Cargo.toml new file mode 100644 index 0000000000..9a97fdb707 --- /dev/null +++ b/utils/libafl_jumper/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "libafl_jumper" +edition = "2021" +version.workspace = true +description = "LibAFL_Jumper: Jump to any address in memory." +repository = "https://github.com/AFLplusplus/LibAFL/" +license = "MIT OR Apache-2.0" +categories = ["development-tools::testing", "os"] +keywords = ["fuzzing", "libafl"] + +[features] +default = ["std"] +std = [] + +[build-dependencies] +cc = "1" + +[dependencies] +hex = { version = "0.4", default-features = false } diff --git a/utils/libafl_jumper/README.md b/utils/libafl_jumper/README.md new file mode 100644 index 0000000000..0a6d6ae93d --- /dev/null +++ b/utils/libafl_jumper/README.md @@ -0,0 +1,28 @@ +# LIBAFL_JUMPER + +If you want to replace your unicorn use with `libafl_qemu`, this might be your tool. +It can run as stub binary. +From inside LibAFL, you can break at `jmp`, then mmap and load all of the memory you need, +then continue running. + +Depending on your toolchain, you want to build the tool for the guest platform. +Since the loader will run inside `qemu-linux-user`, the target OS needs to be `linux` +(Of course, there might be other use cases for you). + +To build this statically linked with `musl` libc, we can do the following: + +```sh +# Install cross compiler toolchain +apt-get install gcc-arm-linux-gnueabihf +# Install the rust toolchain parts +rustup target add arm-unknown-linux-musleabi +# Build for the target. The addresses in the linker script should not be used by your target binary. +RUSTFLAGS="-C target-feature=+crt-static, -C link-self-contained=yes -C linker=arm-linux-gnueabi-gcc -C link-arg=T$(realpath linker_script.ld)" cargo build --target=arm-unknown-linux-musleabi --release +``` + +↪ Or do that for any other architecture, such as `x86_64-unknown-linux-musl`. + +Then, you can run libafl_jumper with a hex-encoded address as parameter, and break at the `libafl_jmp` and (m)map your memory to the right place in memory, before continuing to run. +The jumper will then jump to the provided address. + +Enjoy jumping like a little bunny. diff --git a/utils/libafl_jumper/linker_script.ld b/utils/libafl_jumper/linker_script.ld new file mode 100644 index 0000000000..57daf1a241 --- /dev/null +++ b/utils/libafl_jumper/linker_script.ld @@ -0,0 +1,12 @@ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x20001000, LENGTH = 512K + RAM (xrw) : ORIGIN = 0x21000000, LENGTH = 64K +} + +SECTIONS +{ + .text : { *(.text*) } > FLASH + .rodata : { *(.rodata*) } > FLASH + .bss : { *(.bss*) } > RAM +} \ No newline at end of file diff --git a/utils/libafl_jumper/src/main.rs b/utils/libafl_jumper/src/main.rs new file mode 100644 index 0000000000..92c2743ada --- /dev/null +++ b/utils/libafl_jumper/src/main.rs @@ -0,0 +1,168 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +use core::ffi::CStr; +#[cfg(not(any(test, feature = "std")))] +use core::panic::PanicInfo; +use core::{arch::asm, ffi::c_void, ops::Shl}; + +#[cfg(not(any(test, feature = "std")))] +#[panic_handler] +fn panic(_panic: &PanicInfo<'_>) -> ! { + // No panic! + // # Safety + // This will crash for sure. + unsafe { + libafl_jmp(0x50000B4D_u32 as _); + } +} + +/// Good to kickstart an emulated fuzzing process inside `LibAFL_QEMU`. +/// +/// # Safety +/// This is the most unsafest function you will see today. +/// +/// Man ALL IS LOŚ͖̩͇̗̪̏̈́T ALL I​S LOST the pon̷y he comes he c̶̮omes he comes the ich​or permeates all MY FACE MY FACE ᵒh god no NO NOO̼O​O NΘ stop the an​*̶͑̾̾​̅ͫ͏̙̤g͇̫͛͆̾ͫ̑͆l͖͉̗̩̳̟̍ͫͥͨe̠̅s ͎a̧͈͖r̽̾̈́͒͑e n​ot rè̑ͧ̌aͨl̘̝̙̃ͤ͂̾̆ ZA̡͊͠͝LGΌ ISͮ̂҉̯͈͕̹̘̱ TO͇̹̺ͅƝ̴ȳ̳ TH̘Ë͖́̉ ͠P̯͍̭O̚​N̐Y̡ H̸̡̪̯ͨ͊̽̅̾̎Ȩ̬̩̾͛ͪ̈́̀́͘ ̶̧̨̱̹̭̯ͧ̾ͬC̷̙̲̝͖ͭ̏ͥͮ͟Oͮ͏̮̪̝͍M̲̖͊̒ͪͩͬ̚̚͜Ȇ̴̟̟͙̞ͩ͌͝S̨̥̫͎̭ͯ̿̔̀ͅ +#[inline(never)] +#[no_mangle] +pub unsafe extern "C" fn libafl_jmp(target: *mut c_void) -> ! { + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + asm!( + "jmp {target}", // Jump on x86 + target = in(reg) target, + options(noreturn) + ); + + #[cfg(target_arch = "arm")] + asm!( + "bx {target}", // Branch and exchange instruction (ARM) + target = in(reg) target, + options(noreturn) + ); + + #[cfg(target_arch = "aarch64")] + asm!( + "br {target}", // Branch register instruction (AArch64) + target = in(reg) target, + options(noreturn) + ); + + #[cfg(target_arch = "hexagon")] + asm!( + "jumpr {target}", // Jump register instruction (Hexagon) + target = in(reg) target, + options(noreturn) + ); + + #[cfg(target_arch = "hexagon")] + asm!( + "b {target}", // Branch instruction (PowerPC) + target = in(reg) target, + options(noreturn) + ); + + #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] + asm!( + "jalr x0, {target}, 0", // Jump and link register (RISC-V) + target = in(reg) target, + options(noreturn) + ); + + #[cfg(target_arch = "mips")] + asm!( + "jr {target}", // Jump register (MIPS) + "nop", // Delay slot + target = in(reg) target, + options(noreturn) + ); + + //unreachable!("asm should have jumped!"); +} + +/// The "normal" rust main, mainly for testing +#[cfg(feature = "std")] +fn main() { + let args: Vec = std::env::args().collect(); + + assert!(args.len() >= 2, "No address given"); + + let mut hex_str: &str = &args[1]; + if hex_str.starts_with("0x") || hex_str.starts_with("0X") { + hex_str = &hex_str[2..]; + } + println!("Jumping to {hex_str}"); + decode_hex_and_jmp(hex_str); +} + +/// Main for `no_std` - that's the one we will use inside LibAFL_QEMU. +#[cfg(not(feature = "std"))] +#[no_mangle] +pub unsafe extern "C" fn main(argc: i32, argv: *const *const u8) -> ! { + if argc < 2 || argv.is_null() { + // No params - nothing we can do. + // # Safety + // So much crash. + libafl_jmp(0x42424242_u32 as _); + } + + let arg = argv.add(1); + let mut val = *arg; + + if *val == b'0' && *val.add(1) == b'x' || *val.add(1) == b'X' { + // strip leading 0x + val = val.add(2); + } + + let hex_string = CStr::from_ptr(*val as _).to_str().unwrap(); + + decode_hex_and_jmp(hex_string); +} + +fn decode_hex_and_jmp(hex_string: &str) -> ! { + let mut hex_buf = [0_u8; 8]; + let hex_buf = &mut hex_buf[..hex_string.len() / 2]; + hex::decode_to_slice(hex_string, hex_buf).unwrap(); + + let mut addr: u64 = 0; + for val in hex_buf { + addr = addr.shl(8); + addr += u64::from(*val); + } + + #[cfg(feature = "std")] + println!("Hex: {addr:#x}"); + + #[allow(clippy::cast_possible_truncation)] + let addr = addr as usize; + + let entrypoint = addr as *mut c_void; + + // # Safety + // Obviously unsafe, we're just jumping to a random place in memory... + unsafe { libafl_jmp(entrypoint) } +} + +#[cfg(test)] +mod test { + + extern "C" { + fn exit(ret: i32); + } + + use crate::libafl_jmp; + + #[inline(never)] + pub fn do_exit() { + unsafe { exit(0) } + } + + /// Tests if we can jump to exit. + /// There's a chance this won't work on some systems. + /// Either the assembly above is broken, or something else simply goes wrong. + /// We're deeeep in UB land here. + #[test] + fn test_jmp_to_panic() { + unsafe { libafl_jmp(do_exit as _) } + } +}