├── panda-sys ├── .gitignore ├── src │ ├── fake_headers │ │ └── sys │ │ │ └── cachectl.h │ ├── bindings.h │ ├── bindings.rs │ ├── lib.rs │ ├── gen_bindings.sh │ └── extensions.rs ├── README.md ├── Cargo.toml └── build.rs ├── panda-rs ├── .gitignore ├── src │ ├── syscall_injection │ │ ├── syscalls.rs │ │ ├── arch.rs │ │ ├── syscall_regs.rs │ │ ├── pinned_queue.rs │ │ ├── conversion.rs │ │ └── syscall_future.rs │ ├── plugins │ │ ├── syscalls2.rs │ │ ├── hooks2.rs │ │ ├── guest_plugin_manager │ │ │ ├── from_channel_msg.rs │ │ │ └── guest_plugin.rs │ │ ├── cosi │ │ │ └── osi_statics.rs │ │ ├── glib.rs │ │ ├── guest_plugin_manager.rs │ │ ├── osi.rs │ │ └── proc_start_linux.rs │ ├── api │ │ ├── mod.rs │ │ ├── utils.rs │ │ ├── require_plugin.rs │ │ ├── llvm.rs │ │ ├── rr.rs │ │ ├── misc.rs │ │ ├── os.rs │ │ ├── mem.rs │ │ └── regs.rs │ ├── init_return.rs │ ├── guest_ptr │ │ ├── guest_align.rs │ │ └── impls.rs │ ├── error.rs │ ├── callbacks.rs │ ├── enums.rs │ ├── arch.rs │ ├── callbacks │ │ ├── closure.rs │ │ ├── ppp_closures.rs │ │ └── export.rs │ ├── guest_ptr.rs │ ├── panda_arg.rs │ ├── lib.rs │ ├── library_mode │ │ └── qcows.rs │ └── abi.rs ├── examples │ ├── custom_plugin_import.rs │ ├── closures.rs │ ├── hooks.rs │ ├── dump_regs.rs │ ├── ppp_callback_export.rs │ ├── guest_ptr.rs │ ├── osi.rs │ ├── unicorn.rs │ ├── unicorn_aarch64.rs │ ├── syscall_injection.rs │ ├── showcase.rs │ └── unicorn_taint.rs ├── record_example_replay.py └── Cargo.toml ├── Cargo.toml ├── syscall-parser ├── Cargo.toml ├── README.md └── src │ └── main.rs ├── run_example.sh ├── .gitignore ├── record_example_replay.py ├── panda-macros ├── Cargo.toml └── src │ ├── hooks2.rs │ ├── guest_type │ └── struct_impl.rs │ ├── osi_static.rs │ ├── panda_args.rs │ ├── guest_type.rs │ └── osi_type.rs ├── .github └── workflows │ └── rust.yml └── README.md /panda-sys/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /panda-rs/.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | **/Cargo.lock 3 | -------------------------------------------------------------------------------- /panda-rs/src/syscall_injection/syscalls.rs: -------------------------------------------------------------------------------- 1 | // TODO: provide bindings for linux system calls 2 | -------------------------------------------------------------------------------- /panda-sys/src/fake_headers/sys/cachectl.h: -------------------------------------------------------------------------------- 1 | #define ICACHE 0 2 | 3 | void cacheflush(void* start, int len, int x) { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "panda-rs", 4 | "panda-sys", 5 | "panda-macros", 6 | "syscall-parser", 7 | ] 8 | -------------------------------------------------------------------------------- /panda-sys/src/bindings.h: -------------------------------------------------------------------------------- 1 | #include "panda/plugin.h" 2 | #include "panda/common.h" 3 | #include "panda/plog.h" 4 | #include "panda/panda_api.h" 5 | -------------------------------------------------------------------------------- /syscall-parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "syscall-parser" 3 | version = "0.2.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | peg = "0.7.0" 8 | -------------------------------------------------------------------------------- /syscall-parser/README.md: -------------------------------------------------------------------------------- 1 | # syscall-parser 2 | 3 | Parses `panda/plugins/syscalls2/generated/syscalls_ext_typedefs_*.h` in order to provide Rust bindings to syscalls2. To regenerate `../panda-macros/src/syscalls/*.rs`, run: 4 | 5 | ``` 6 | cargo run 7 | ``` 8 | -------------------------------------------------------------------------------- /panda-sys/README.md: -------------------------------------------------------------------------------- 1 | # panda-sys 2 | 3 | To update low-level bindings: 4 | 5 | 1. Clone and build the latest [panda](https://github.com/panda-re/panda)'s `master` branch. 6 | 2. `export PANDA_ROOT=/path/where/you/cloned/panda` 7 | 3. `cd src/ && ./gen_bindings.sh` 8 | -------------------------------------------------------------------------------- /run_example.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | cargo build && \ 3 | cp target/debug/libpanda_rs_example.so $PANDA_PATH/x86_64-softmmu/panda/plugins/panda_panda_rs_example.so && \ 4 | $PANDA_PATH/x86_64-softmmu/panda-system-x86_64 -os "linux-64-ubuntu:4.15.0-72-generic-noaslr-nokaslr" -replay test -panda panda_rs_example -m 1G 5 | -------------------------------------------------------------------------------- /panda-rs/examples/custom_plugin_import.rs: -------------------------------------------------------------------------------- 1 | use panda::prelude::*; 2 | 3 | #[repr(C)] 4 | pub struct AuxvValues { 5 | // TODO 6 | } 7 | 8 | panda::plugin_import! { 9 | static PROC_START_LINUX: ProcStartLinux = extern "proc_start_linux" { 10 | callbacks { 11 | fn on_rec_auxv(cpu: &mut CPUState, tb: &mut TranslationBlock, auxv: &AuxvValues); 12 | } 13 | }; 14 | } 15 | 16 | fn main() {} 17 | -------------------------------------------------------------------------------- /panda-rs/src/plugins/syscalls2.rs: -------------------------------------------------------------------------------- 1 | //! Rust bindings to syscalls2 2 | //! 3 | //! Not intended to be used directly, but is used internally for the callbacks in [`on_sys`] 4 | //! 5 | //! [`on_sys`]: crate::on_sys 6 | //! 7 | 8 | #[allow(unused_imports)] 9 | use crate::sys::{target_ptr_t, target_ulong, CPUState}; 10 | use crate::{cbs::generate_syscalls_callbacks, plugin_import, regs::SyscallPc}; 11 | 12 | generate_syscalls_callbacks!(); 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # Replays 13 | **/*.log 14 | test-rr-nondet.log 15 | test-rr-snp 16 | 17 | # Example artifacts 18 | _string_matches.txt 19 | -------------------------------------------------------------------------------- /record_example_replay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from sys import argv 4 | from pandare import blocking, Panda 5 | 6 | # No arguments, i386. Otherwise argument should be guest arch 7 | generic_type = argv[1] if len(argv) > 1 else "x86_64" 8 | panda = Panda(generic=generic_type) 9 | 10 | @blocking 11 | def run_cmd(): 12 | # First revert to root snapshot, then type a command via serial 13 | panda.revert_sync("root") 14 | panda.record_cmd("echo test", recording_name="test") 15 | 16 | panda.end_analysis() 17 | 18 | panda.queue_async(run_cmd) 19 | 20 | panda.run() 21 | -------------------------------------------------------------------------------- /panda-rs/src/plugins/hooks2.rs: -------------------------------------------------------------------------------- 1 | //! Raw Rust bindings for hooks2 plugin 2 | //! 3 | //! Not designed to be used directly, but is used internally for: 4 | //! 5 | //! * [`on_process_start`](crate::on_process_start) 6 | //! * [`on_process_end`](crate::on_process_end) 7 | //! * [`on_thread_start`](crate::on_thread_start) 8 | //! * [`on_thread_end`](crate::on_thread_end) 9 | //! * [`on_mmap_updated`](crate::on_mmap_updated) 10 | use std::os::raw::c_char; 11 | use crate::plugin_import; 12 | use crate::sys::{CPUState, target_pid_t, target_ulong}; 13 | 14 | panda_macros::generate_hooks2_callbacks!(); 15 | -------------------------------------------------------------------------------- /panda-rs/record_example_replay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from sys import argv 4 | from pandare import blocking, Panda 5 | 6 | # No arguments, i386. Otherwise argument should be guest arch 7 | generic_type = argv[1] if len(argv) > 1 else "x86_64" 8 | panda = Panda(generic=generic_type) 9 | 10 | @blocking 11 | def run_cmd(): 12 | # First revert to root snapshot, then type a command via serial 13 | panda.revert_sync("root") 14 | panda.record_cmd("echo test && bash -c \"echo test2\"", recording_name="test") 15 | 16 | panda.end_analysis() 17 | 18 | panda.queue_async(run_cmd) 19 | 20 | panda.run() 21 | -------------------------------------------------------------------------------- /panda-rs/src/api/mod.rs: -------------------------------------------------------------------------------- 1 | /// Functions for working with PANDA's LLVM execution 2 | pub mod llvm; 3 | /// Utilities for working with the guest's memory 4 | pub mod mem; 5 | /// Functions for reading and modifying guest registers 6 | pub mod regs; 7 | /// Functions for record and replay 8 | pub mod rr; 9 | 10 | /// Utilities for working with the PANDA OS API 11 | /// 12 | /// For OS introspection, see [the `osi` plugin](crate::plugins::osi). 13 | pub mod os; 14 | 15 | /// Miscellaneous PANDA API utilities 16 | mod misc; 17 | pub use misc::*; 18 | 19 | mod utils; 20 | pub use utils::*; 21 | 22 | mod require_plugin; 23 | pub use require_plugin::*; 24 | -------------------------------------------------------------------------------- /panda-rs/src/api/utils.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(feature = "i386", feature = "x86_64"))] 2 | pub type CPUArchPtr = *mut panda_sys::CPUX86State; 3 | 4 | #[cfg(any(feature = "arm", feature = "aarch64"))] 5 | pub type CPUArchPtr = *mut panda_sys::CPUARMState; 6 | 7 | #[cfg(any( 8 | feature = "mips", 9 | feature = "mipsel", 10 | feature = "mips64", 11 | feature = "mips64el" 12 | ))] 13 | pub type CPUArchPtr = *mut panda_sys::CPUMIPSState; 14 | 15 | #[cfg(feature = "ppc")] 16 | pub type CPUArchPtr = *mut panda_sys::CPUPPCState; 17 | 18 | #[macro_export] 19 | macro_rules! cpu_arch_state { 20 | ($cpu:expr) => { 21 | $cpu.env_ptr as CPUArchPtr 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /panda-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "panda-re-sys" 3 | version = "0.8.0" 4 | authors = ["Jordan McLeod "] 5 | edition = "2018" 6 | description = "The official *-sys library for interfacing with PANDA (Platform for Architecture-Neutral Dynamic Analysis)" 7 | license = "GPL-2.0" 8 | documentation = "https://docs.rs/panda-re-sys" 9 | homepage = "https://panda-re.mit.edu" 10 | 11 | [package.metadata.docs.rs] 12 | features = ["x86_64"] 13 | 14 | [lib] 15 | name = "panda_sys" 16 | 17 | [features] 18 | libpanda = [] 19 | x86_64 = [] 20 | i386 = [] 21 | arm = [] 22 | aarch64 = [] 23 | ppc = [] 24 | mips = [] 25 | mipsel = [] 26 | mips64 = [] 27 | mips64el = [] 28 | -------------------------------------------------------------------------------- /panda-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "panda-re-macros" 3 | version = "0.26.0" 4 | authors = ["Jordan McLeod "] 5 | edition = "2018" 6 | description = "Macros needed for the `panda-re` library" 7 | license = "GPL-2.0" 8 | documentation = "https://docs.rs/panda-re" 9 | homepage = "https://panda-re.mit.edu" 10 | 11 | [lib] 12 | name = "panda_macros" 13 | proc-macro = true 14 | 15 | [dependencies] 16 | syn = { version = "1.0.37", features = ["full"] } 17 | quote = "1.0.7" 18 | doc-comment = "0.3.3" 19 | darling = "0.10.2" 20 | proc-macro2 = "1" 21 | 22 | [features] 23 | x86_64 = [] 24 | i386 = [] 25 | arm = [] 26 | aarch64 = [] 27 | ppc = [] 28 | mips = [] 29 | mipsel = [] 30 | mips64 = [] 31 | mips64el = [] 32 | -------------------------------------------------------------------------------- /panda-rs/src/syscall_injection/arch.rs: -------------------------------------------------------------------------------- 1 | pub(crate) use crate::abi::syscall::*; 2 | use crate::prelude::*; 3 | 4 | #[cfg(feature = "x86_64")] 5 | pub(crate) const VFORK: target_ulong = 58; 6 | 7 | #[cfg(feature = "i386")] 8 | pub(crate) const VFORK: target_ulong = 190; 9 | 10 | #[cfg(feature = "arm")] 11 | pub(crate) const VFORK: target_ulong = 190; 12 | 13 | #[cfg(feature = "aarch64")] 14 | pub(crate) const VFORK: target_ulong = 220; 15 | 16 | // TODO: mips needs to be changed to VFORK 17 | #[cfg(any(feature = "mips64", feature = "mips64el"))] 18 | pub(crate) const VFORK: target_ulong = 4002; 19 | 20 | #[cfg(any(feature = "mips", feature = "mipsel"))] 21 | pub(crate) const VFORK: target_ulong = 4120; // n32 22 | 23 | pub(crate) const FORK_IS_CLONE: bool = cfg!(any( 24 | feature = "aarch64", 25 | feature = "mips", 26 | feature = "mipsel" 27 | )); 28 | -------------------------------------------------------------------------------- /panda-rs/src/api/require_plugin.rs: -------------------------------------------------------------------------------- 1 | use crate::sys::{panda_add_arg, panda_load_plugin, panda_plugin_path}; 2 | use crate::PandaArgs; 3 | 4 | use std::ffi::CString; 5 | 6 | /// Require a plugin to be loaded, and if it isn't loaded load it with the given 7 | /// arguments. If the plugin is already loaded the arguments will be discarded. 8 | pub fn require_plugin(plugin: &Args) { 9 | let plugin_name = CString::new(Args::PLUGIN_NAME).unwrap(); 10 | 11 | let path = unsafe { panda_plugin_path(plugin_name.as_ptr()) }; 12 | 13 | for (name, arg) in plugin.to_panda_args() { 14 | let arg = CString::new(format!("{}={}", name, arg)).unwrap(); 15 | unsafe { 16 | panda_add_arg(plugin_name.as_ptr(), arg.as_ptr()); 17 | } 18 | } 19 | 20 | unsafe { 21 | panda_load_plugin(path, plugin_name.as_ptr()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /panda-rs/src/init_return.rs: -------------------------------------------------------------------------------- 1 | /// A trait representing types that can be used as the return value for a `#[panda::init]` 2 | /// function 3 | pub trait InitReturn { 4 | fn into_init_bool(self) -> bool; 5 | } 6 | 7 | impl InitReturn for bool { 8 | fn into_init_bool(self) -> bool { 9 | self 10 | } 11 | } 12 | 13 | impl InitReturn for () { 14 | fn into_init_bool(self) -> bool { 15 | true 16 | } 17 | } 18 | 19 | impl InitReturn for i32 { 20 | fn into_init_bool(self) -> bool { 21 | self == 0 22 | } 23 | } 24 | 25 | impl InitReturn for Result { 26 | fn into_init_bool(self) -> bool { 27 | match self { 28 | Ok(x) => x.into_init_bool(), 29 | Err(err) => { 30 | eprintln!("Error initializing plugin: {:?}", err); 31 | 32 | false 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /panda-sys/src/bindings.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "x86_64")] 2 | mod x86_64; 3 | #[cfg(feature = "x86_64")] 4 | pub use x86_64::*; 5 | 6 | #[cfg(feature = "i386")] 7 | mod i386; 8 | #[cfg(feature = "i386")] 9 | pub use i386::*; 10 | 11 | #[cfg(feature = "arm")] 12 | mod arm; 13 | #[cfg(feature = "arm")] 14 | pub use arm::*; 15 | 16 | #[cfg(feature = "aarch64")] 17 | mod aarch64; 18 | #[cfg(feature = "aarch64")] 19 | pub use aarch64::*; 20 | 21 | #[cfg(feature = "ppc")] 22 | mod ppc; 23 | #[cfg(feature = "ppc")] 24 | pub use ppc::*; 25 | 26 | #[cfg(feature = "mips")] 27 | mod mips; 28 | #[cfg(feature = "mips")] 29 | pub use mips::*; 30 | 31 | #[cfg(feature = "mipsel")] 32 | mod mipsel; 33 | #[cfg(feature = "mipsel")] 34 | pub use mipsel::*; 35 | 36 | #[cfg(feature = "mips64")] 37 | mod mips64; 38 | #[cfg(feature = "mips64")] 39 | pub use mips64::*; 40 | 41 | #[cfg(feature = "mips64el")] 42 | mod mips64el; 43 | #[cfg(feature = "mips64el")] 44 | pub use mips64el::*; 45 | -------------------------------------------------------------------------------- /panda-rs/examples/closures.rs: -------------------------------------------------------------------------------- 1 | use panda::plugins::proc_start_linux::ProcStartLinuxCallbacks; 2 | use panda::prelude::*; 3 | use panda::{Callback, PppCallback}; 4 | 5 | fn main() { 6 | // Callbacks can capture state 7 | let mut count = 1; 8 | let bb_callback = Callback::new(); 9 | bb_callback.before_block_exec(move |cpu, _| { 10 | println!("Block: {} | PC: {:#x?}", count, panda::regs::get_pc(cpu)); 11 | count += 1; 12 | if count > 5 { 13 | // callbacks can disable themselves by capturing a copy 14 | // of the `Callback` reference to it 15 | bb_callback.disable(); 16 | } 17 | }); 18 | 19 | // If you don't need to enable and disable the callback, you can just 20 | // use method chaining instead of assigning to a variable 21 | PppCallback::new().on_rec_auxv(|_, _, auxv| { 22 | // print out the auxillary vector when any process starts 23 | dbg!(auxv); 24 | }); 25 | 26 | Panda::new().generic("x86_64").replay("test").run(); 27 | } 28 | -------------------------------------------------------------------------------- /panda-rs/examples/hooks.rs: -------------------------------------------------------------------------------- 1 | use panda::plugins::hooks::Hook; 2 | use panda::plugins::proc_start_linux::AuxvValues; 3 | use panda::prelude::*; 4 | 5 | #[panda::init] 6 | fn init(_: &mut PluginHandle) { 7 | // No specialized init needed 8 | } 9 | 10 | #[panda::hook] 11 | fn entry_hook(_cpu: &mut CPUState, _tb: &mut TranslationBlock, _exit_code: u8, hook: &mut Hook) { 12 | println!("\n\nHit entry hook!\n"); 13 | hook.enabled = false; 14 | } 15 | 16 | #[panda::on_rec_auxv] 17 | fn on_proc_start(_cpu: &mut CPUState, _tb: &mut TranslationBlock, auxv: &AuxvValues) { 18 | let address = auxv.entry; 19 | panda::hook::before_block_exec(move |_, _, hook| { 20 | println!( 21 | "Before block exec of closure entry hook. (at address: {:#x?})", 22 | address 23 | ); 24 | 25 | hook.enabled = false; 26 | }) 27 | .at_addr(auxv.entry); 28 | 29 | entry_hook::hook().after_block_exec().at_addr(auxv.entry) 30 | } 31 | 32 | fn main() { 33 | Panda::new().generic("x86_64").replay("test").run(); 34 | } 35 | -------------------------------------------------------------------------------- /panda-rs/src/guest_ptr/guest_align.rs: -------------------------------------------------------------------------------- 1 | pub(crate) trait GuestAlign { 2 | const ALIGN: usize; 3 | } 4 | 5 | macro_rules! align { 6 | ($ident:ident = $align:literal) => { 7 | impl GuestAlign for $ident { 8 | const ALIGN: usize = $align / 8; 9 | } 10 | }; 11 | } 12 | 13 | #[cfg(any( 14 | feature = "x86_64", 15 | feature = "i386", 16 | feature = "arm", 17 | feature = "aarch64", 18 | feature = "mips", 19 | feature = "mipsel", 20 | feature = "mips64", 21 | feature = "mips64el", 22 | feature = "ppc", 23 | ))] 24 | macro_rules! alignments { 25 | () => { 26 | align!(bool = 8); 27 | 28 | align!(f32 = 32); 29 | align!(f64 = 64); 30 | 31 | align!(u8 = 8); 32 | align!(u16 = 16); 33 | align!(u32 = 32); 34 | align!(u64 = 64); 35 | align!(u128 = 128); 36 | 37 | align!(i8 = 8); 38 | align!(i16 = 16); 39 | align!(i32 = 32); 40 | align!(i64 = 64); 41 | align!(i128 = 128); 42 | }; 43 | } 44 | 45 | alignments!(); 46 | -------------------------------------------------------------------------------- /panda-macros/src/hooks2.rs: -------------------------------------------------------------------------------- 1 | define_hooks2_callbacks!{ 2 | fn(add_callback_on_process_start) on_process_start( 3 | cpu: &mut CPUState, 4 | procname: *const c_char, 5 | asid: target_ulong, 6 | pid: target_pid_t, 7 | ); 8 | 9 | fn(add_callback_on_process_end) on_process_end( 10 | cpu: &mut CPUState, 11 | procname: *const c_char, 12 | asid: target_ulong, 13 | pid: target_pid_t, 14 | ); 15 | 16 | fn(add_callback_on_thread_start) on_thread_start( 17 | cpu: &mut CPUState, 18 | procname: *const c_char, 19 | asid: target_ulong, 20 | pid: target_pid_t, 21 | tid: target_pid_t, 22 | ); 23 | 24 | fn(add_callback_on_thread_end) on_thread_end( 25 | cpu: &mut CPUState, 26 | procname: *const c_char, 27 | asid: target_ulong, 28 | pid: target_pid_t, 29 | tid: target_pid_t, 30 | ); 31 | 32 | fn(add_callback_on_mmap_updated) on_mmap_updated( 33 | cpu: &mut CPUState, 34 | libname: *const c_char, 35 | base: target_ulong, 36 | size: target_ulong, 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /panda-rs/examples/dump_regs.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicU64, Ordering}; 2 | use std::ffi::CStr; 3 | 4 | use panda::prelude::*; 5 | use panda::plugins::osi::OSI; 6 | 7 | static NUM_BB: AtomicU64 = AtomicU64::new(0); 8 | 9 | #[panda::init] 10 | fn init(_: &mut PluginHandle) { 11 | // No specialized init needed 12 | } 13 | 14 | // Dump registers every 1000 basic blocks 15 | #[panda::before_block_exec] 16 | fn every_basic_block(cpu: &mut CPUState, tb: &mut TranslationBlock) { 17 | if panda::in_kernel_mode(cpu) { 18 | return; 19 | } 20 | 21 | let curr_proc = OSI.get_current_process(cpu); 22 | let curr_proc_name_c_str = unsafe { CStr::from_ptr((*curr_proc).name) }; 23 | let curr_bb = NUM_BB.fetch_add(1, Ordering::SeqCst); 24 | 25 | if (curr_bb % 1000 == 0) && (curr_bb != 0) { 26 | println!("\nRegister state for process {:?} @ 0x{:016x}, {} basic blocks into execution:", 27 | curr_proc_name_c_str, 28 | tb.pc, 29 | NUM_BB.load(Ordering::SeqCst) - 1, 30 | ); 31 | panda::regs::dump_regs(cpu); 32 | } 33 | } 34 | 35 | fn main() { 36 | Panda::new() 37 | .generic("x86_64") 38 | .replay("test") 39 | .run(); 40 | } 41 | -------------------------------------------------------------------------------- /panda-rs/src/syscall_injection/syscall_regs.rs: -------------------------------------------------------------------------------- 1 | use super::arch::*; 2 | use crate::prelude::*; 3 | use crate::regs::{get_reg, set_reg}; 4 | use crate::sys::get_cpu; 5 | 6 | #[derive(Copy, Clone, Debug)] 7 | pub struct SyscallRegs { 8 | sys_num_reg: target_ulong, 9 | arg_regs: [target_ulong; SYSCALL_ARGS_LEN], 10 | } 11 | 12 | impl SyscallRegs { 13 | /// Backup all the registers needed for performing a system call 14 | pub fn backup() -> Self { 15 | let cpu = unsafe { &mut *get_cpu() }; 16 | 17 | let sys_num_reg = get_reg(cpu, SYSCALL_NUM_REG); 18 | let arg_regs = SYSCALL_ARGS.map(|storage| storage.read(cpu)); 19 | 20 | Self { 21 | sys_num_reg, 22 | arg_regs, 23 | } 24 | } 25 | 26 | /// Restore the registers needed for performing a system call from a backup 27 | pub fn restore(self) { 28 | let Self { 29 | sys_num_reg, 30 | arg_regs, 31 | } = self; 32 | let cpu = unsafe { &mut *get_cpu() }; 33 | 34 | set_reg(cpu, SYSCALL_NUM_REG, sys_num_reg); 35 | for (&storage, &val) in SYSCALL_ARGS.iter().zip(arg_regs.iter()) { 36 | storage.write(cpu, val); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /panda-rs/src/syscall_injection/pinned_queue.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::pin::Pin; 3 | 4 | pub(crate) struct PinnedQueue(Vec>>); 5 | 6 | impl Default for PinnedQueue { 7 | fn default() -> Self { 8 | Self::new() 9 | } 10 | } 11 | 12 | unsafe impl Send for PinnedQueue {} 13 | unsafe impl Sync for PinnedQueue {} 14 | 15 | impl PinnedQueue { 16 | pub(crate) const fn new() -> Self { 17 | Self(Vec::new()) 18 | } 19 | 20 | pub(crate) fn current_mut(&mut self) -> Option<&mut Pin>> { 21 | self.0.get_mut(0) 22 | } 23 | 24 | pub(crate) fn pop(&mut self) -> Option>> { 25 | if !self.0.is_empty() { 26 | Some(self.0.remove(0)) 27 | } else { 28 | None 29 | } 30 | } 31 | 32 | pub(crate) fn len(&self) -> usize { 33 | self.0.len() 34 | } 35 | 36 | pub(crate) fn is_empty(&self) -> bool { 37 | self.len() == 0 38 | } 39 | } 40 | 41 | impl PinnedQueue> { 42 | pub(crate) fn push_future(&mut self, future: impl Future + 'static) { 43 | self.0.push(Box::pin(future) as _); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /panda-rs/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | use std::os::raw::c_int; 3 | 4 | // Top-level ----------------------------------------------------------------------------------------------------------- 5 | 6 | #[derive(Debug, Error)] 7 | pub enum Error { 8 | #[error("The provided string contained a null, which is not permitted")] 9 | InvalidString(#[from] std::ffi::NulError), 10 | 11 | #[error("The provided size was not properly page-aligned")] 12 | UnalignedPageSize, 13 | 14 | #[error(transparent)] 15 | RecordReplayError(#[from] RrError) 16 | } 17 | 18 | // Transparent Subclasses ---------------------------------------------------------------------------------------------- 19 | 20 | #[derive(Debug, Error)] 21 | pub enum RrError { 22 | #[error("Recording already enabled")] 23 | RrCtrlEInvalid, 24 | 25 | #[error("Recording enable request already pending")] 26 | RrCtrlEPending, 27 | } 28 | 29 | impl RrError { 30 | pub fn translate_err_code(code: c_int) -> Result<(), Error> { 31 | match code { 32 | panda_sys::RRCTRL_ret_RRCTRL_EINVALID => Err(Error::RecordReplayError(RrError::RrCtrlEInvalid)), 33 | panda_sys::RRCTRL_ret_RRCTRL_EPENDING => Err(Error::RecordReplayError(RrError::RrCtrlEPending)), 34 | panda_sys::RRCTRL_ret_RRCTRL_OK => Ok(()), 35 | _ => unreachable!() 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /panda-rs/examples/ppp_callback_export.rs: -------------------------------------------------------------------------------- 1 | use panda::export_ppp_callback; 2 | use panda::prelude::*; 3 | 4 | export_ppp_callback! { 5 | // disables all callbacks when true is returned 6 | pub(crate) fn on_every_odd_block(cpu: &mut CPUState) -> bool; 7 | pub(crate) fn on_every_even_block(cpu: &mut CPUState); 8 | } 9 | 10 | fn main() { 11 | let mut i = 0; 12 | let callback = panda::Callback::new(); 13 | callback.before_block_exec(move |cpu, _| { 14 | if i % 2 == 0 { 15 | on_every_even_block::trigger(cpu); 16 | } else { 17 | if on_every_odd_block::trigger(cpu) { 18 | callback.disable(); 19 | } 20 | } 21 | i += 1; 22 | }); 23 | 24 | on_every_even_block::add_callback(on_even_test); 25 | on_every_odd_block::add_callback(on_odd_test); 26 | 27 | Panda::new().generic("x86_64").replay("test").run(); 28 | } 29 | 30 | // ===== test callbacks ====== 31 | 32 | use std::sync::atomic::{AtomicUsize, Ordering}; 33 | 34 | // using a global variable to keep track and disable after 3 odds 35 | static TEST_COUNTER: AtomicUsize = AtomicUsize::new(0); 36 | 37 | extern "C" fn on_odd_test(_: &mut CPUState) -> bool { 38 | println!("Odd!"); 39 | 40 | TEST_COUNTER.fetch_add(1, Ordering::SeqCst) >= 3 41 | } 42 | 43 | extern "C" fn on_even_test(_: &mut CPUState) { 44 | println!("Even!"); 45 | } 46 | -------------------------------------------------------------------------------- /panda-rs/examples/guest_ptr.rs: -------------------------------------------------------------------------------- 1 | use panda::mem::{map_memory, physical_memory_write, PAGE_SIZE}; 2 | use panda::prelude::*; 3 | use panda::{GuestPtr, GuestType}; 4 | 5 | #[derive(GuestType, Debug, PartialEq)] 6 | struct Test { 7 | x: u32, 8 | y: u32, 9 | } 10 | 11 | const ADDRESS: target_ptr_t = 0x1000; 12 | 13 | #[panda::after_machine_init] 14 | fn setup(_: &mut CPUState) { 15 | // Map 2MB memory for this emulation 16 | map_memory("mymem", 2 * 1024 * PAGE_SIZE, ADDRESS).unwrap(); 17 | 18 | // Write code into memory 19 | physical_memory_write(ADDRESS, b"\x34\x12\x00\x00\x20\x00\x00\x00"); 20 | 21 | // read memory back using GuestPtr 22 | let ptr: GuestPtr = ADDRESS.into(); 23 | 24 | assert_eq!(*ptr, 0x1234); 25 | assert_eq!(*ptr.offset(1), 0x20); 26 | println!("u32 ptr read success!"); 27 | 28 | let mut ptr = ptr.cast::(); 29 | assert_eq!(*ptr, Test { x: 0x1234, y: 0x20 }); 30 | println!("Struct read success!"); 31 | 32 | ptr.write(|test| { 33 | test.x = 0x2345; 34 | test.y = 0x21; 35 | }); 36 | 37 | assert_eq!(*ptr, Test { x: 0x2345, y: 0x21 }); 38 | println!("Write to GuestPtr cache success"); 39 | 40 | ptr.clear_cache(); 41 | 42 | assert_eq!(*ptr, Test { x: 0x2345, y: 0x21 }); 43 | println!("Write to memory success"); 44 | } 45 | 46 | fn main() { 47 | Panda::new().arch(panda::Arch::x86_64).configurable().run(); 48 | } 49 | -------------------------------------------------------------------------------- /panda-rs/examples/osi.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use std::sync::atomic::{AtomicU64, Ordering}; 3 | 4 | use panda::plugins::osi::OSI; 5 | use panda::prelude::*; 6 | 7 | static NUM_BB: AtomicU64 = AtomicU64::new(0); 8 | 9 | #[panda::init] 10 | fn init(_: &mut PluginHandle) { 11 | // No specialized init needed 12 | } 13 | 14 | // Print every 1000 basic blocks 15 | #[panda::after_block_exec] 16 | fn every_basic_block(cpu: &mut CPUState, tb: &mut TranslationBlock, exit_code: u8) { 17 | if (u32::from(exit_code) > panda_sys::TB_EXIT_IDX1) || (panda::in_kernel_mode(cpu)) { 18 | return; 19 | } 20 | 21 | let curr_proc = OSI.get_current_process(cpu); 22 | let curr_proc_name_c_str = unsafe { CStr::from_ptr(curr_proc.as_ref().unwrap().name) }; 23 | 24 | let curr_bb = NUM_BB.fetch_add(1, Ordering::SeqCst); 25 | if (curr_bb % 1000 == 0) && (curr_bb != 0) { 26 | println!( 27 | "{:?} @ 0x{:016x}, {} BBs in - in shared lib? {}", 28 | curr_proc_name_c_str, 29 | tb.pc, 30 | NUM_BB.load(Ordering::SeqCst) - 1, 31 | OSI.in_shared_object(cpu, &**curr_proc.as_ref().unwrap()), 32 | ); 33 | } 34 | } 35 | 36 | use panda::plugins::proc_start_linux::AuxvValues; 37 | 38 | #[panda::on_rec_auxv] 39 | fn on_proc_start(cpu: &mut CPUState, tb: &mut TranslationBlock, auxv: &AuxvValues) { 40 | dbg!(auxv); 41 | } 42 | 43 | fn main() { 44 | Panda::new().generic("x86_64").replay("test").run(); 45 | } 46 | -------------------------------------------------------------------------------- /panda-rs/examples/unicorn.rs: -------------------------------------------------------------------------------- 1 | use panda::prelude::*; 2 | use panda::regs::{get_reg, set_reg, set_pc, get_pc, Reg}; 3 | use panda::mem::{map_memory, physical_memory_write, PAGE_SIZE}; 4 | 5 | // inc rax 6 | // add rbx, rax 7 | // inc rcx 8 | const X86_CODE: &[u8] = b"\x48\xFF\xC0\x48\x01\xC3\x48\xFF\xC1"; 9 | 10 | const ADDRESS: target_ulong = 0x1000; 11 | const STOP_ADDR: target_ulong = ADDRESS + (X86_CODE.len() as target_ulong); 12 | 13 | #[panda::after_machine_init] 14 | fn setup(cpu: &mut CPUState) { 15 | // Map 2MB memory for this emulation 16 | map_memory("mymem", 2 * 1024 * PAGE_SIZE, ADDRESS).unwrap(); 17 | 18 | // Write code into memory 19 | physical_memory_write(ADDRESS, X86_CODE); 20 | 21 | // Setup registers 22 | set_reg(cpu, Reg::RAX, 0x1); 23 | set_reg(cpu, Reg::RBX, 0x2); 24 | set_reg(cpu, Reg::RCX, 0x3); 25 | set_reg(cpu, Reg::RDX, 0x4); 26 | 27 | // Set starting PC 28 | set_pc(cpu, ADDRESS); 29 | } 30 | 31 | #[panda::insn_translate] 32 | fn insn_translate(cpu: &mut CPUState, pc: target_ptr_t) -> bool { 33 | true 34 | } 35 | 36 | #[panda::insn_exec] 37 | fn insn_exec(cpu: &mut CPUState, pc: target_ptr_t) { 38 | println!("pc: {:#x?}", pc); 39 | if pc == STOP_ADDR { 40 | println!("Final CPU state:"); 41 | panda::regs::dump_regs(cpu); 42 | unsafe { 43 | // ? 44 | panda::sys::exit(0); 45 | } 46 | } 47 | } 48 | 49 | fn main() { 50 | Panda::new() 51 | .arch(panda::Arch::x86_64) 52 | .configurable() 53 | .run(); 54 | } 55 | -------------------------------------------------------------------------------- /panda-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | macro_rules! if_any_two_features { 4 | ($first:literal $(, $feature:literal)+ { $item:item }) => { 5 | #[cfg(all(feature = $first, any($(feature = $feature),*)))] 6 | $item 7 | 8 | if_any_two_features!($($feature),* { $item }); 9 | }; 10 | ($first:literal { $item:item }) => {}; 11 | } 12 | 13 | macro_rules! if_not_any_two_features { 14 | (@inner $first:literal $(, $feature:literal)+ { $item:item }) => { 15 | #[cfg(not(all(feature = $first, any($(feature = $feature),*))))] 16 | if_not_any_two_features!(@inner $($feature),* { 17 | $item 18 | }); 19 | }; 20 | (@inner $first:literal { $item:item }) => { 21 | $item 22 | }; 23 | ($($features:literal),* { $first:item $($items:item)* }) => { 24 | if_not_any_two_features!(@inner $($features),* { $first }); 25 | 26 | if_not_any_two_features!($($features),* { $($items)* }); 27 | }; 28 | ($($features:literal),* { }) => {}; 29 | } 30 | 31 | if_any_two_features!("x86_64", "i386", "arm", "ppc", "mips", "mipsel", "mips64", "mips64el" { 32 | compile_error!("Cannot enable two features at once, make sure you are using `default-features = false`"); 33 | }); 34 | 35 | if_not_any_two_features!("x86_64", "i386", "arm", "ppc", "mips", "mipsel", "mips64", "mips64el" { 36 | 37 | #[allow(nonstandard_style)] 38 | #[allow(improper_ctypes)] // TODO!!! need to actually fix these FFI issues... 39 | mod bindings; 40 | 41 | mod extensions; 42 | 43 | pub use bindings::*; 44 | }); 45 | -------------------------------------------------------------------------------- /panda-rs/src/callbacks.rs: -------------------------------------------------------------------------------- 1 | use crate::sys::panda_cb_type; 2 | 3 | mod closure; 4 | mod export; 5 | pub use closure::{set_plugin_ref, Callback}; 6 | pub use export::CallbackReturn; 7 | 8 | mod ppp_closures; 9 | pub use ppp_closures::{ 10 | InternalPppClosureCallback, PppCallback, __internal_install_ppp_closure_callback, 11 | }; 12 | 13 | /// An opaque type used to register/unregister callbacks with PANDA. Passed into init/unit 14 | /// callbacks 15 | pub struct PluginHandle; 16 | 17 | /// A typeless PANDA callback used internally by callback attributes. Not recommended for direct 18 | /// use. 19 | #[doc(hidden)] 20 | pub struct InternalCallback { 21 | pub cb_type: panda_cb_type, 22 | pub fn_pointer: *const (), 23 | } 24 | 25 | impl InternalCallback { 26 | pub fn new(cb_type: panda_cb_type, fn_pointer: *const ()) -> Self { 27 | Self { 28 | cb_type, 29 | fn_pointer, 30 | } 31 | } 32 | } 33 | 34 | /// A callback set to run on plugin uninit. To add an uninit callback use `#[panda::uninit]` on a 35 | /// function which takes an `&mut PluginHandle` as an argument. 36 | /// 37 | /// ### Example 38 | /// 39 | /// ```rust 40 | /// use panda::PluginHandle; 41 | /// 42 | /// #[panda::uninit] 43 | /// fn on_exit(plugin: &mut PluginHandle) { 44 | /// // Do stuff 45 | /// } 46 | /// ``` 47 | pub struct UninitCallback(pub fn(&mut PluginHandle)); 48 | 49 | #[doc(hidden)] 50 | pub struct PPPCallbackSetup(pub fn()); 51 | 52 | inventory::collect!(InternalCallback); 53 | inventory::collect!(UninitCallback); 54 | inventory::collect!(PPPCallbackSetup); 55 | -------------------------------------------------------------------------------- /panda-rs/examples/unicorn_aarch64.rs: -------------------------------------------------------------------------------- 1 | use panda::prelude::*; 2 | use panda::regs::{get_reg, set_reg, set_pc, get_pc, Reg}; 3 | use panda::mem::{map_memory, physical_memory_write, PAGE_SIZE}; 4 | 5 | // ADD X1, X1, 1 6 | // ADD X0, X1, X2 7 | // ADD X2, X2, 1 8 | const AARCH64_CODE: &[u8] = b"\x21\x04\x00\x91\x20\x00\x02\x8b\x42\x04\x00\x91"; 9 | 10 | const ADDRESS: target_ulong = 0x1000; 11 | const STOP_ADDR: target_ulong = ADDRESS + (AARCH64_CODE.len() as target_ulong); 12 | 13 | #[panda::after_machine_init] 14 | fn setup(cpu: &mut CPUState) { 15 | // Map 2MB memory for this emulation 16 | map_memory("mymem", 2 * 1024 * PAGE_SIZE, ADDRESS).unwrap(); 17 | 18 | // Write code into memory 19 | physical_memory_write(ADDRESS, AARCH64_CODE); 20 | 21 | // Setup registers 22 | set_reg(cpu, Reg::X0, 0x1); 23 | set_reg(cpu, Reg::X1, 0x2); 24 | set_reg(cpu, Reg::X2, 0x3); 25 | set_reg(cpu, Reg::X3, 0x4); 26 | 27 | // Set starting PC 28 | set_pc(cpu, ADDRESS); 29 | } 30 | 31 | #[panda::insn_translate] 32 | fn insn_translate(cpu: &mut CPUState, pc: target_ptr_t) -> bool { 33 | true 34 | } 35 | 36 | #[panda::insn_exec] 37 | fn insn_exec(cpu: &mut CPUState, pc: target_ptr_t) { 38 | println!("pc: {:#x?}", pc); 39 | if pc == STOP_ADDR { 40 | println!("Final CPU state:"); 41 | panda::regs::dump_regs(cpu); 42 | unsafe { 43 | // ? 44 | panda::sys::exit(0); 45 | } 46 | } 47 | } 48 | 49 | fn main() { 50 | Panda::new() 51 | .arch(panda::Arch::AArch64) 52 | .configurable() 53 | .run(); 54 | } 55 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-python@v1 20 | - uses: BSFishy/pip-action@v1 21 | with: 22 | packages: pandare 23 | - name: Build x86_64 24 | run: cd panda-rs && cargo build --verbose 25 | - name: Build i386 26 | run: cd panda-rs && cargo build --verbose --no-default-features --features=i386,syscall-injection 27 | - name: Build ARM 28 | run: cd panda-rs && cargo build --verbose --no-default-features --features=arm,syscall-injection 29 | - name: Build 64-bit ARM 30 | run: cd panda-rs && cargo build --verbose --no-default-features --features=aarch64,syscall-injection 31 | - name: Build Mips 32 | run: cd panda-rs && cargo build --verbose --no-default-features --features=mips,syscall-injection 33 | - name: Build Mips (Little Endian) 34 | run: cd panda-rs && cargo build --verbose --no-default-features --features=mipsel,syscall-injection 35 | - name: Build 64-bit Mips 36 | run: cd panda-rs && cargo build --verbose --no-default-features --features=mips64,syscall-injection 37 | - name: Build 64-bit Mips (Little Endian) 38 | run: cd panda-rs && cargo build --verbose --no-default-features --features=mips64el,syscall-injection 39 | - name: Build PowerPC 40 | run: cd panda-rs && cargo build --verbose --no-default-features --features=ppc 41 | -------------------------------------------------------------------------------- /panda-rs/src/enums.rs: -------------------------------------------------------------------------------- 1 | /// For fallible virt/phys memory R/W operations 2 | #[repr(i32)] 3 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] 4 | pub enum MemRWStatus { 5 | Unknown = -2, 6 | GenericErrorRet = -1, 7 | MemTxOk = panda_sys::MEMTX_OK as i32, 8 | MemTxError = panda_sys::MEMTX_ERROR as i32, 9 | MemTxDecodeError = panda_sys::MEMTX_DECODE_ERROR as i32, 10 | } 11 | 12 | impl From for MemRWStatus { 13 | fn from(v: i32) -> Self { 14 | match v { 15 | x if x == MemRWStatus::GenericErrorRet as i32 => MemRWStatus::GenericErrorRet, 16 | x if x == MemRWStatus::MemTxOk as i32 => MemRWStatus::MemTxOk, 17 | x if x == MemRWStatus::MemTxError as i32 => MemRWStatus::MemTxError, 18 | x if x == MemRWStatus::MemTxDecodeError as i32 => MemRWStatus::MemTxDecodeError, 19 | _ => MemRWStatus::Unknown, // This means there is a bug in the C side of things 20 | } 21 | } 22 | } 23 | 24 | #[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] 25 | pub enum Endian { 26 | Big, 27 | Little, 28 | } 29 | 30 | /// For fallible generic C functions 31 | #[repr(i32)] 32 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] 33 | pub enum GenericRet { 34 | Unknown = -2, 35 | Error = -1, 36 | Success = 0, 37 | } 38 | 39 | impl From for GenericRet { 40 | fn from(v: i32) -> Self { 41 | match v { 42 | x if x == GenericRet::Error as i32 => GenericRet::Error, 43 | x if x == GenericRet::Success as i32 => GenericRet::Success, 44 | _ => GenericRet::Unknown, // This means there is a bug in the C side of things 45 | } 46 | } 47 | } 48 | 49 | pub(crate) mod arch {} 50 | -------------------------------------------------------------------------------- /panda-rs/examples/syscall_injection.rs: -------------------------------------------------------------------------------- 1 | use panda::plugins::osi::OSI; 2 | use panda::prelude::*; 3 | use panda::syscall_injection::{run_injector, syscall}; 4 | 5 | const GET_PID: target_ulong = 39; 6 | const GET_UID: target_ulong = 102; 7 | 8 | async fn getpid() -> target_ulong { 9 | syscall(GET_PID, ()).await 10 | } 11 | 12 | async fn getuid() -> target_ulong { 13 | syscall(GET_UID, ()).await 14 | } 15 | 16 | #[panda::on_all_sys_enter] 17 | fn any_syscall(cpu: &mut CPUState, pc: SyscallPc, syscall_num: target_ulong) { 18 | if FORBIDDEN_SYSCALLS.contains(&syscall_num) || in_same_asid(cpu) { 19 | return; 20 | } 21 | 22 | let current_pid = OSI.get_current_process(cpu).unwrap().pid; 23 | println!("OSI PID: {}", current_pid); 24 | 25 | run_injector(pc, async { 26 | println!("PID: {}", getpid().await); 27 | println!("UID: {}", getuid().await); 28 | println!("PID (again): {}", getpid().await); 29 | }); 30 | } 31 | 32 | fn main() { 33 | Panda::new() 34 | .generic("x86_64") 35 | //.args(&["-loadvm", "root"]) 36 | .run(); 37 | } 38 | 39 | // The rest is to prevent applying syscall injectors to syscalls which might 40 | // cause issues 41 | 42 | use std::sync::atomic::{AtomicU64, Ordering}; 43 | 44 | fn in_same_asid(cpu: &mut CPUState) -> bool { 45 | static LAST_ASID: AtomicU64 = AtomicU64::new(0x1234); 46 | 47 | let asid = unsafe { panda::sys::panda_current_asid(cpu) }; 48 | 49 | LAST_ASID.swap(asid, Ordering::SeqCst) == asid 50 | } 51 | 52 | const FORBIDDEN_SYSCALLS: &[target_ulong] = &[FORK, VFORK, EXIT_GROUP, RT_SIGRETURN]; 53 | 54 | const FORK: target_ulong = 57; 55 | const VFORK: target_ulong = 58; 56 | const EXIT_GROUP: target_ulong = 231; 57 | const RT_SIGRETURN: target_ulong = 15; 58 | -------------------------------------------------------------------------------- /panda-rs/src/arch.rs: -------------------------------------------------------------------------------- 1 | use crate::enums::Endian; 2 | 3 | // ================ ARCH_NAME ================ 4 | 5 | /// The name of the architecture as used by PANDA 6 | /// 7 | /// This can be one of: 8 | /// 9 | /// * x86_64 10 | /// * i386 11 | /// * arm 12 | /// * ppc 13 | /// * mips 14 | /// * mipsel 15 | /// * mips64 16 | /// * aarch64 17 | pub const ARCH_NAME: &str = ARCH; 18 | 19 | #[cfg(feature = "x86_64")] 20 | const ARCH: &str = "x86_64"; 21 | 22 | #[cfg(feature = "i386")] 23 | const ARCH: &str = "i386"; 24 | 25 | #[cfg(feature = "arm")] 26 | const ARCH: &str = "arm"; 27 | 28 | #[cfg(feature = "ppc")] 29 | const ARCH: &str = "ppc"; 30 | 31 | #[cfg(feature = "mips")] 32 | const ARCH: &str = "mips"; 33 | 34 | #[cfg(feature = "mipsel")] 35 | const ARCH: &str = "mipsel"; 36 | 37 | #[cfg(feature = "aarch64")] 38 | const ARCH: &str = "aarch64"; 39 | 40 | #[cfg(feature = "mips64")] 41 | const ARCH: &str = "mips64"; 42 | 43 | #[cfg(feature = "mips64el")] 44 | const ARCH: &str = "mips64el"; 45 | 46 | // ================ ARCH_ENDIAN ================ 47 | 48 | /// The byte order of the guest architecture being targetted by PANDA 49 | pub const ARCH_ENDIAN: Endian = ENDIAN; 50 | 51 | #[cfg(feature = "x86_64")] 52 | const ENDIAN: Endian = Endian::Little; 53 | 54 | #[cfg(feature = "i386")] 55 | const ENDIAN: Endian = Endian::Little; 56 | 57 | #[cfg(feature = "arm")] 58 | const ENDIAN: Endian = Endian::Little; 59 | 60 | #[cfg(feature = "ppc")] 61 | const ENDIAN: Endian = Endian::Big; 62 | 63 | #[cfg(feature = "mips")] 64 | const ENDIAN: Endian = Endian::Big; 65 | 66 | #[cfg(feature = "mipsel")] 67 | const ENDIAN: Endian = Endian::Little; 68 | 69 | #[cfg(feature = "aarch64")] 70 | const ENDIAN: Endian = Endian::Little; 71 | 72 | #[cfg(feature = "mips64")] 73 | const ENDIAN: Endian = Endian::Big; 74 | 75 | #[cfg(feature = "mips64el")] 76 | const ENDIAN: Endian = Endian::Little; 77 | -------------------------------------------------------------------------------- /panda-rs/examples/showcase.rs: -------------------------------------------------------------------------------- 1 | use panda::plugins::{osi::OSI, syscalls2::SYSCALLS}; 2 | use panda::prelude::*; 3 | use std::sync::atomic::{AtomicU64, Ordering}; 4 | 5 | static NUM_BB: AtomicU64 = AtomicU64::new(0); 6 | 7 | #[derive(PandaArgs)] 8 | #[name = "panda_rs_example"] 9 | struct Args { 10 | #[arg(default = 3)] 11 | num: u32, 12 | 13 | #[arg(required, about = "File to do a thing with")] 14 | file: String, 15 | } 16 | 17 | #[panda::on_sys::write_enter] 18 | fn sys_write_test(cpu: &mut CPUState, _pc: SyscallPc, _fd: u32, buf: target_ulong, count: u32) { 19 | println!( 20 | "sys_write buf = \"{}\"", 21 | String::from_utf8_lossy(&cpu.mem_read(buf, count as usize)) 22 | ); 23 | } 24 | 25 | // print out the pc and syscall number of the first syscall to run 26 | #[panda::on_all_sys_enter] 27 | fn on_sys_enter(cpu: &mut CPUState, pc: SyscallPc, callno: target_ulong) { 28 | println!("pc: {:#x?} | syscall: {}", pc.pc(), callno); 29 | 30 | // remove the hook once the first syscall has been printed out 31 | SYSCALLS.remove_callback_on_all_sys_enter(on_sys_enter); 32 | } 33 | 34 | #[panda::init] 35 | fn init(_: &mut PluginHandle) { 36 | // let args = Args::from_panda_args(); 37 | 38 | // println!("Test plugin init, num: {}, file: {}", args.num, args.file); 39 | } 40 | 41 | #[panda::before_block_exec] 42 | fn every_basic_block(cpu: &mut CPUState, tb: &mut TranslationBlock) { 43 | // every 1000 basic blocks visited 44 | if NUM_BB.fetch_add(1, Ordering::SeqCst) % 1000 == 0 { 45 | println!("pc: {:X}", tb.pc); 46 | let Some(proc) = OSI.get_current_process(cpu) else { 47 | println!("Current Process is null"); 48 | return; 49 | }; 50 | println!("pid: {:X}", (*proc).pid); 51 | 52 | if (*proc).pid == 0x1f { 53 | every_basic_block::disable(); 54 | } 55 | } 56 | } 57 | 58 | #[derive(PandaArgs)] 59 | #[name = "stringsearch"] 60 | struct StringSearch { 61 | str: String, 62 | } 63 | 64 | fn main() { 65 | Panda::new() 66 | .generic("x86_64") 67 | .replay("test") 68 | .plugin_args(&StringSearch { str: "test".into() }) 69 | .run(); 70 | } 71 | -------------------------------------------------------------------------------- /panda-rs/src/plugins/guest_plugin_manager/from_channel_msg.rs: -------------------------------------------------------------------------------- 1 | use std::str; 2 | 3 | /// Represents a type which can be converted to from a channel message sent by a 4 | /// guest plugin. Used by the [`channel_recv`](super::channel_recv) macro. 5 | /// 6 | /// ## Supported Types 7 | /// * `&[u8]` - gives you the raw bytes 8 | /// * `Vec` - gives you the raw bytes, but owned 9 | /// * `&str` - gives you the bytes as a UTF-8 string. Prints a warning if invalid 10 | /// unicode and skips your callback. 11 | /// * `String` - same as `&str` but owned 12 | /// * `Option` - rather than print a warning if the type can't be decoded, pass None 13 | /// * `Result` - rather than print out the warning to stdout, pass the warning 14 | /// as a `String` 15 | pub trait FromChannelMessage: Sized { 16 | unsafe fn from_channel_message(data: *const u8, size: usize) -> Result; 17 | } 18 | 19 | impl<'a> FromChannelMessage for &'a [u8] { 20 | unsafe fn from_channel_message(data: *const u8, size: usize) -> Result { 21 | Ok(std::slice::from_raw_parts(data, size)) 22 | } 23 | } 24 | 25 | impl FromChannelMessage for Vec { 26 | unsafe fn from_channel_message(data: *const u8, size: usize) -> Result { 27 | <&[u8]>::from_channel_message(data, size).map(ToOwned::to_owned) 28 | } 29 | } 30 | 31 | impl<'a> FromChannelMessage for &'a str { 32 | unsafe fn from_channel_message(data: *const u8, size: usize) -> Result { 33 | <&[u8]>::from_channel_message(data, size) 34 | .map(str::from_utf8)? 35 | .map_err(|_| String::from("Channel message is not valid UTF-8")) 36 | } 37 | } 38 | 39 | impl FromChannelMessage for String { 40 | unsafe fn from_channel_message(data: *const u8, size: usize) -> Result { 41 | <&str>::from_channel_message(data, size).map(ToOwned::to_owned) 42 | } 43 | } 44 | 45 | impl FromChannelMessage for Option { 46 | unsafe fn from_channel_message(data: *const u8, size: usize) -> Result { 47 | Ok(T::from_channel_message(data, size).ok()) 48 | } 49 | } 50 | 51 | impl FromChannelMessage for Result { 52 | unsafe fn from_channel_message(data: *const u8, size: usize) -> Result { 53 | Ok(T::from_channel_message(data, size)) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /panda-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "panda-re" 3 | version = "0.49.0" 4 | authors = ["Jordan McLeod ", "tnballo "] 5 | edition = "2018" 6 | description = "The official library for interfacing with PANDA (Platform for Architecture-Neutral Dynamic Analysis)" 7 | license = "GPL-2.0" 8 | documentation = "https://docs.rs/panda-re" 9 | homepage = "https://panda-re.mit.edu" 10 | repository = "https://github.com/panda-re/panda-rs" 11 | 12 | [package.metadata.docs.rs] 13 | cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] 14 | rustdoc-args = ["--cfg", "doc_cfg"] 15 | 16 | [lib] 17 | name = "panda" 18 | 19 | [[example]] 20 | name = "showcase" 21 | required-features = ["libpanda"] 22 | 23 | [[example]] 24 | name = "dump_regs" 25 | required-features = ["libpanda"] 26 | 27 | [[example]] 28 | name = "unicorn" 29 | required-features = ["libpanda"] 30 | 31 | [[example]] 32 | name = "unicorn_aarch64" 33 | required-features = ["libpanda", "aarch64"] 34 | 35 | [dependencies] 36 | panda-re-sys = { version = "0.8", path = "../panda-sys" } 37 | panda-re-macros = { version = "0.26", path = "../panda-macros" } 38 | inventory = "0.1.8" 39 | dirs = "3.0.1" 40 | lazy_static = "1.4.0" 41 | libloading = "0.6.2" 42 | paste = "1.0.0" 43 | glib-sys = "0.10.0" 44 | strum = "0.20" 45 | strum_macros = "0.20" 46 | thiserror = "1" 47 | once_cell = "1.8.0" 48 | array-init = "2" 49 | 50 | # syscall-injection 51 | async-trait = { version = "0.1", optional = true } 52 | parking_lot = { version = "0.11", optional = true } 53 | dashmap = { version = "4", optional = true } 54 | log = { version = "0.4", optional = true } 55 | 56 | [features] 57 | default = ["x86_64", "syscall-injection"] 58 | libpanda = ["panda-re-sys/libpanda"] 59 | syscall-injection = ["async-trait", "parking_lot", "dashmap", "log"] 60 | 61 | # Architectures 62 | x86_64 = ["panda-re-sys/x86_64", "panda-re-macros/x86_64"] 63 | i386 = ["panda-re-sys/i386", "panda-re-macros/i386"] 64 | arm = ["panda-re-sys/arm", "panda-re-macros/arm"] 65 | aarch64 = ["panda-re-sys/aarch64", "panda-re-macros/aarch64"] 66 | ppc = ["panda-re-sys/ppc", "panda-re-macros/ppc"] 67 | mips = ["panda-re-sys/mips", "panda-re-macros/mips"] 68 | mipsel = ["panda-re-sys/mipsel", "panda-re-macros/mipsel"] 69 | mips64 = ["panda-re-sys/mips64", "panda-re-macros/mips64"] 70 | mips64el = ["panda-re-sys/mips64el", "panda-re-macros/mips64el"] 71 | -------------------------------------------------------------------------------- /panda-rs/src/api/llvm.rs: -------------------------------------------------------------------------------- 1 | //use crate::enums::GenericRet; 2 | //use std::ffi::CString; 3 | //use std::path::Path; 4 | //use llvm_ir::Module; 5 | 6 | /// Enable translating TCG -> LLVM and executing LLVM 7 | pub fn enable_llvm() { 8 | unsafe { 9 | panda_sys::panda_enable_llvm() 10 | } 11 | } 12 | 13 | /// Enable translating TCG -> LLVM, but still execute TCG 14 | pub fn enable_llvm_no_exec() { 15 | unsafe { 16 | panda_sys::panda_enable_llvm_no_exec() 17 | } 18 | } 19 | 20 | /// Disable LLVM translation and execution 21 | pub fn disable_llvm() { 22 | unsafe { 23 | panda_sys::panda_disable_llvm() 24 | } 25 | } 26 | 27 | /// Enable LLVM helpers 28 | pub fn enable_llvm_helpers() { 29 | unsafe { 30 | panda_sys::panda_enable_llvm_helpers() 31 | } 32 | } 33 | 34 | /// Disable LLVM helpers 35 | pub fn disable_llvm_helpers() { 36 | unsafe { 37 | panda_sys::panda_disable_llvm_helpers() 38 | } 39 | } 40 | 41 | /* 42 | /// Get current (last translated) LLVM module. 43 | /// This is wildly experimental. 44 | pub fn get_current_llvm_mod() -> Result { 45 | 46 | // Try three RAM-backed Linux dirs (for speed), fallback to OS-agnostic temp dir 47 | let temp_path; 48 | let file_path = if Path::new("/dev/run").exists() { 49 | Path::new("/dev/run/curr_llvm.bc") 50 | } else if Path::new("/run/shm").exists() { 51 | Path::new("/run/shm/curr_llvm.bc") 52 | } else if Path::new("/dev/shm").exists() { 53 | Path::new("/dev/shm/curr_llvm.bc") 54 | } else { 55 | temp_path = std::env::temp_dir().as_path().join("curr_llvm.bc"); 56 | &temp_path 57 | }; 58 | 59 | if let Some(path_str) = file_path.to_str() { 60 | if let Ok(path_c_str) = CString::new(path_str.as_bytes()) { 61 | unsafe { 62 | match panda_sys::panda_write_current_llvm_bitcode_to_file( 63 | path_c_str.as_ptr() 64 | ).into() { 65 | GenericRet::Success => Module::from_bc_path(file_path), 66 | GenericRet::Error | GenericRet::Unknown => Err("Failed to write bitcode file".to_string()) 67 | } 68 | } 69 | } else { 70 | Err(format!("Failed to convert path \'{:?}\' to C string!", file_path)) 71 | } 72 | } else { 73 | Err(format!("Failed to convert path \'{:?}\' to string!", file_path)) 74 | } 75 | } 76 | */ -------------------------------------------------------------------------------- /panda-rs/src/api/rr.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | use std::ptr; 3 | 4 | use crate::{Error, RrError}; 5 | 6 | /// RR point-in-time: get current count of instructions replayed 7 | pub fn rr_get_guest_instr_count() -> u64 { 8 | unsafe { panda_sys::rr_get_guest_instr_count_external() as _ } 9 | } 10 | 11 | /// Stop and quit, wraps QMP functions. 12 | pub fn vm_quit() { 13 | let rr_ctrl_ret = unsafe { panda_sys::panda_vm_quit() }; 14 | // Non-fallible: https://sourcegraph.com/github.com/panda-re/panda/-/blob/panda/src/callbacks.c#L755:5 15 | // Defensive assert in case C-side ever becomes fallible 16 | assert_eq!(rr_ctrl_ret, panda_sys::RRCTRL_ret_RRCTRL_OK); 17 | } 18 | 19 | /// Start recording. 20 | /// If `snapshot.is_some()` restore the named snapshot prior to recording. 21 | pub fn record_begin(name: &str, snapshot: Option<&str>) -> Result<(), Error> { 22 | match CString::new(name) { 23 | Ok(c_name) => match snapshot { 24 | Some(snap_name) => match CString::new(snap_name) { 25 | Ok(c_snap_name) => { 26 | let rr_ctrl_ret = unsafe { 27 | panda_sys::panda_record_begin(c_name.as_ptr(), c_snap_name.as_ptr()) 28 | }; 29 | RrError::translate_err_code(rr_ctrl_ret) 30 | } 31 | Err(e) => Err(Error::InvalidString(e)), 32 | }, 33 | None => { 34 | let rr_ctrl_ret = 35 | unsafe { panda_sys::panda_record_begin(c_name.as_ptr(), ptr::null()) }; 36 | RrError::translate_err_code(rr_ctrl_ret) 37 | } 38 | }, 39 | Err(e) => Err(Error::InvalidString(e)), 40 | } 41 | } 42 | 43 | /// End currently recording. 44 | pub fn record_end() -> Result<(), Error> { 45 | let rr_ctrl_ret = unsafe { panda_sys::panda_record_end() }; 46 | RrError::translate_err_code(rr_ctrl_ret) 47 | } 48 | 49 | /// Start replay. 50 | pub fn replay_begin(name: &str) -> Result<(), Error> { 51 | match CString::new(name) { 52 | Ok(c_name) => { 53 | let rr_ctrl_ret = unsafe { panda_sys::panda_replay_begin(c_name.as_ptr()) }; 54 | RrError::translate_err_code(rr_ctrl_ret) 55 | } 56 | Err(e) => Err(Error::InvalidString(e)), 57 | } 58 | } 59 | 60 | /// End currently running replay. 61 | pub fn replay_end() -> Result<(), Error> { 62 | let rr_ctrl_ret = unsafe { panda_sys::panda_replay_end() }; 63 | RrError::translate_err_code(rr_ctrl_ret) 64 | } 65 | 66 | -------------------------------------------------------------------------------- /panda-rs/src/plugins/cosi/osi_statics.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | 3 | use crate::guest_ptr::GuestReadFail; 4 | use crate::prelude::*; 5 | use crate::GuestType; 6 | 7 | use super::{find_per_cpu_address, symbol_addr_from_name}; 8 | 9 | /// A trait representing that a type is readable using OSI 2. 10 | /// 11 | /// See the [`OsiType`](macro@panda::plugins::cosi::OsiType) derive macro for more details. 12 | pub trait OsiType: Sized { 13 | type MethodDispatcher; 14 | 15 | /// Read the given type out of memory starting at `base_ptr` 16 | fn osi_read(cpu: &mut CPUState, base_ptr: target_ptr_t) -> Result; 17 | } 18 | 19 | #[doc(hidden)] 20 | pub struct EmptyMethodDelegator(&'static str, bool); 21 | 22 | impl EmptyMethodDelegator { 23 | pub const fn new(_: &'static str, _: bool) -> Self { 24 | Self("", false) 25 | } 26 | } 27 | 28 | /// Types with a fixed layout (primarily primitives) implement OsiType automatically, 29 | /// allowing them to be used with [`osi_static`](super::osi_static) seamlessly. 30 | impl OsiType for T { 31 | type MethodDispatcher = EmptyMethodDelegator; 32 | 33 | fn osi_read(cpu: &mut CPUState, base_ptr: target_ptr_t) -> Result { 34 | T::read_from_guest(cpu, base_ptr) 35 | } 36 | } 37 | 38 | /// A type used internally by [`osi_static`](panda::plugins::cosi::osi_static) in order 39 | /// to provide a value that can be read wholesale or one field at a time. 40 | #[doc(hidden)] 41 | pub struct PerCpu(pub &'static str, pub T::MethodDispatcher); 42 | 43 | impl PerCpu { 44 | pub fn read(&self, cpu: &mut CPUState) -> Result { 45 | let ptr = find_per_cpu_address(cpu, self.0)?; 46 | 47 | T::osi_read(cpu, ptr) 48 | } 49 | } 50 | 51 | impl Deref for PerCpu { 52 | type Target = T::MethodDispatcher; 53 | 54 | fn deref(&self) -> &Self::Target { 55 | &self.1 56 | } 57 | } 58 | 59 | /// A type used internally by [`osi_static`](panda::plugins::cosi::osi_static) in order 60 | /// to provide a value that can be read wholesale or one field at a time. 61 | #[doc(hidden)] 62 | pub struct OsiGlobal(pub &'static str, pub T::MethodDispatcher); 63 | 64 | impl OsiGlobal { 65 | pub fn read(&self, cpu: &mut CPUState) -> Result { 66 | let ptr = symbol_addr_from_name(self.0); 67 | 68 | T::osi_read(cpu, ptr) 69 | } 70 | } 71 | 72 | impl Deref for OsiGlobal { 73 | type Target = T::MethodDispatcher; 74 | 75 | fn deref(&self) -> &Self::Target { 76 | &self.1 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /panda-rs/src/plugins/glib.rs: -------------------------------------------------------------------------------- 1 | //! glib wrappers for supporting glib-based plugins 2 | 3 | use glib_sys::{g_array_free, g_free, g_malloc, gpointer, GArray}; 4 | use std::marker::PhantomData; 5 | use std::mem::size_of; 6 | use std::ops::{Deref, DerefMut}; 7 | 8 | use std::ptr::NonNull; 9 | 10 | /// An owned glib-allocated value that will be freed using glib's allocator on drop. 11 | #[repr(transparent)] 12 | pub struct GBox(NonNull); 13 | 14 | impl GBox { 15 | pub fn new(val: T) -> Self { 16 | unsafe { 17 | let ptr = g_malloc(size_of::()); 18 | if !ptr.is_null() { 19 | *(ptr as *mut T) = val; 20 | } 21 | Self(NonNull::new(ptr as *mut T).unwrap()) 22 | } 23 | } 24 | 25 | pub fn as_ptr(&self) -> *const T { 26 | self.0.as_ptr() 27 | } 28 | } 29 | 30 | impl Deref for GBox { 31 | type Target = T; 32 | 33 | fn deref(&self) -> &Self::Target { 34 | unsafe { self.0.as_ref() } 35 | } 36 | } 37 | 38 | impl DerefMut for GBox { 39 | fn deref_mut(&mut self) -> &mut Self::Target { 40 | unsafe { self.0.as_mut() } 41 | } 42 | } 43 | 44 | impl Drop for GBox { 45 | fn drop(&mut self) { 46 | unsafe { 47 | g_free(self.0.as_ptr() as gpointer); 48 | } 49 | } 50 | } 51 | 52 | #[repr(transparent)] 53 | pub struct GBoxedSlice(pub *mut GArray, PhantomData); 54 | 55 | impl GBoxedSlice { 56 | pub fn is_null(&self) -> bool { 57 | self.0.is_null() 58 | } 59 | } 60 | 61 | impl Deref for GBoxedSlice { 62 | type Target = [T]; 63 | 64 | fn deref(&self) -> &Self::Target { 65 | if self.0.is_null() { 66 | panic!("Invalid GBoxedSlice: null"); 67 | } else { 68 | let g_array = unsafe { &*self.0 }; 69 | 70 | if g_array.data.is_null() { 71 | &[] 72 | } else { 73 | unsafe { std::slice::from_raw_parts(g_array.data as _, g_array.len as usize) } 74 | } 75 | } 76 | } 77 | } 78 | 79 | impl DerefMut for GBoxedSlice { 80 | fn deref_mut(&mut self) -> &mut Self::Target { 81 | if self.0.is_null() { 82 | panic!("Invalid GBoxedSlice: null"); 83 | } 84 | let g_array = unsafe { &mut *self.0 }; 85 | 86 | if g_array.data.is_null() { 87 | &mut [] 88 | } else { 89 | unsafe { std::slice::from_raw_parts_mut(g_array.data as *mut T, g_array.len as usize) } 90 | } 91 | } 92 | } 93 | 94 | impl Drop for GBoxedSlice { 95 | fn drop(&mut self) { 96 | if !self.0.is_null() { 97 | unsafe { 98 | g_array_free(self.0, true as _); 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /panda-rs/examples/unicorn_taint.rs: -------------------------------------------------------------------------------- 1 | use panda::prelude::*; 2 | use panda::regs::{set_reg, set_pc, Reg}; 3 | use panda::mem::{map_memory, physical_memory_write, PAGE_SIZE}; 4 | use panda::taint; 5 | 6 | // inc rax 7 | // add rbx, rax 8 | // inc rcx 9 | const X86_CODE: &[u8] = b"\x48\xFF\xC0\x48\x01\xC3\x48\xFF\xC1"; 10 | 11 | const ADDRESS: target_ulong = 0x1000; 12 | const STOP_ADDR: target_ulong = ADDRESS + (X86_CODE.len() as target_ulong); 13 | 14 | // configure our state before running 15 | #[panda::after_machine_init] // <--- runs immediately after the QEMU machine is accessible 16 | fn setup(cpu: &mut CPUState) { 17 | // Map 2MB memory for this emulation 18 | map_memory("mymem", 2 * 1024 * PAGE_SIZE, ADDRESS).unwrap(); 19 | 20 | // Write code into memory 21 | physical_memory_write(ADDRESS, X86_CODE); 22 | 23 | // Setup registers 24 | set_reg(cpu, Reg::RAX, 0x1); 25 | set_reg(cpu, Reg::RBX, 0x2); 26 | set_reg(cpu, Reg::RCX, 0x3); 27 | set_reg(cpu, Reg::RDX, 0x4); 28 | 29 | // Iterate over registers to show initial taint 30 | for reg in [Reg::RAX, Reg::RBX, Reg::RCX, Reg::RDX] { 31 | println!("{:?} is tained? {:?}", reg, taint::check_reg(reg)); 32 | } 33 | 34 | println!("Tainting RAX with label '1'..."); 35 | taint::label_reg(Reg::RAX, 1); 36 | 37 | println!("Tainting RBX with label '2'..."); 38 | taint::label_reg(Reg::RBX, 2); 39 | 40 | // Set starting PC 41 | set_pc(cpu, ADDRESS); 42 | } 43 | 44 | // In order to ensure our `insn_exec` callback runs every instruction, we must instruct PANDA to 45 | // translate and instrument (i.e. add code to call our callback) each instruction. 46 | #[panda::insn_translate] // <--- runs every instruction 47 | fn insn_translate(_cpu: &mut CPUState, _pc: target_ptr_t) -> bool { 48 | true 49 | } 50 | 51 | #[panda::insn_exec] // <--- runs every instrumented instruction 52 | fn insn_exec(cpu: &mut CPUState, pc: target_ptr_t) { 53 | println!("pc: {:#x?}", pc); 54 | 55 | // if we've reached the end of our shellcode, dump the registers and taint to stdout, then quit 56 | if pc == STOP_ADDR { 57 | println!("Final CPU state:"); 58 | panda::regs::dump_regs(cpu); 59 | 60 | for reg in [Reg::RAX, Reg::RBX, Reg::RCX, Reg::RDX] { 61 | println!("{:?} is tained? {:?}", reg, taint::check_reg(reg)); 62 | 63 | if taint::check_reg(reg) { 64 | println!("(Tainted by {:?})", taint::get_reg(reg)); 65 | } 66 | } 67 | 68 | unsafe { 69 | panda::sys::exit(0); 70 | } 71 | } 72 | } 73 | 74 | // When the example runs, start up a new PANDA instance. It's marked `.configurable()` so that 75 | // instead of running a built-in QEMU system, we can just build our own baremetal execution 76 | // environment 77 | fn main() { 78 | Panda::new() 79 | .arch(panda::Arch::x86_64) 80 | .configurable() 81 | .run(); 82 | } 83 | -------------------------------------------------------------------------------- /panda-rs/src/api/misc.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use std::os::raw::c_int; 3 | use std::ffi::CStr; 4 | 5 | /// Determine if guest is currently executing in kernel mode 6 | pub fn in_kernel_mode(cpu: &mut CPUState) -> bool { 7 | unsafe { 8 | panda_sys::panda_in_kernel_mode_external(cpu) 9 | } 10 | } 11 | 12 | /// Determine if guest is currently executing kernel code 13 | pub fn in_kernel_code_linux(cpu: &mut CPUState) -> bool { 14 | unsafe { 15 | panda_sys::panda_in_kernel_code_linux_external(cpu) 16 | } 17 | } 18 | 19 | /// Get current architecture independent Address-Space ID (ASID) 20 | pub fn current_asid(cpu: &mut CPUState) -> target_ulong { 21 | unsafe { 22 | panda_sys::panda_current_asid(cpu) 23 | } 24 | } 25 | 26 | /// Get current guest program counter 27 | pub fn current_pc(cpu: &mut CPUState) -> target_ulong { 28 | unsafe { 29 | panda_sys::panda_current_pc(cpu) 30 | } 31 | } 32 | 33 | /// Get current guest userspace stack pointer 34 | pub fn current_sp(cpu: &mut CPUState) -> target_ulong { 35 | unsafe { 36 | panda_sys::panda_current_sp_external(cpu) 37 | } 38 | } 39 | 40 | /// Get current guest userspace stack pointer, masking of page size MSBs 41 | pub fn current_sp_masked_pagesize(cpu: &mut CPUState, page_size: target_ulong) -> target_ulong { 42 | unsafe { 43 | panda_sys::panda_current_sp_masked_pagesize_external(cpu, page_size) 44 | } 45 | } 46 | 47 | /// Get current guest kernelspace stack pointer 48 | pub fn current_ksp(cpu: &mut CPUState) -> target_ulong { 49 | unsafe { 50 | panda_sys::panda_current_ksp_external(cpu) 51 | } 52 | } 53 | 54 | /// Get current guest function return value 55 | pub fn get_ret_val(cpu: &mut CPUState) -> target_ulong { 56 | unsafe { 57 | panda_sys::panda_get_retval_external(cpu) 58 | } 59 | } 60 | 61 | /// If required for the target architecture, enter into a high-privilege mode in order to conduct some memory access. 62 | /// Returns true if a switch into high-privilege mode has been made. 63 | /// A NO-OP on systems where such changes are unnecessary. 64 | pub fn enter_priv(cpu: &mut CPUState) -> bool { 65 | unsafe { 66 | panda_sys::enter_priv(cpu) 67 | } 68 | } 69 | 70 | /// Revert the guest to the privilege mode it was in prior to the last call to enter_priv(). 71 | /// A NO-OP for architectures where enter_priv() is a NO-OP. 72 | pub fn exit_priv(cpu: &mut CPUState) { 73 | unsafe { 74 | panda_sys::exit_priv(cpu) 75 | } 76 | } 77 | 78 | /// Get count of commandline arguments 79 | pub fn argc() -> c_int { 80 | unsafe { 81 | panda_sys::panda_argc 82 | } 83 | } 84 | 85 | /// Get commandline arguments 86 | pub fn argv() -> Vec { 87 | let mut rs_argv = Vec::new(); 88 | 89 | for char_ptr in unsafe { panda_sys::panda_argv }.iter() { 90 | if let Ok(str_slice) = unsafe { CStr::from_ptr(*char_ptr) }.to_str() { 91 | rs_argv.push(str_slice.to_owned()); 92 | } 93 | } 94 | 95 | rs_argv 96 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # panda-rs 2 | 3 | Rust bindings for [PANDA](https://github.com/panda-re/panda) for use in either plugin mode or libpanda mode. 4 | 5 | ## Getting Started 6 | 7 | Some resources for helping introduce you to PANDA and panda-rs: 8 | 9 | * [The release blog post](https://panda-re.mit.edu/blog/panda-rs/) 10 | * [The documentation](https://docs.rs/panda-re) 11 | * [Our collection of plugins](https://github.com/panda-re/panda-rs-plugins) 12 | * [The panda-rs example(s)](https://github.com/panda-re/panda-rs/tree/master/panda-rs/examples) 13 | 14 | For running an example, simply clone the repo then: 15 | 16 | ``` 17 | cd panda-rs 18 | python3 record_example_replay.py 19 | cargo run --example showcase --features=libpanda 20 | ``` 21 | 22 | ## Features 23 | 24 | * Programmatically drive QEMU - integrate with QEMU more easily 25 | * Record/Replay - Record parts of execution for quickly replaying and analyzing 26 | * Snapshots - Utilize QEMU's snapshot feature to resume execution from a predetermined state 27 | * Attribute-based hooking - Just tag a function with the events it should run during 28 | * Share code between plugins and libpanda use 29 | * Plugin support - create a PANDA plugin that can be used by other plugins or from the command line 30 | * libpanda - drive an instance of PANDA from your own analysis/script/application. 31 | * Multithread plugins with ease. Not burdened by Python's GIL and C/C++'s lack of thread safety or eurgonomic threading, write concurrent plugins fearlessly. 32 | * Take advantage of the Rust ecosystem—plugins are just crates, so everything from concurrency primatives to backend frameworks are a Cargo.toml line away 33 | 34 | ## Brief Example 35 | 36 | Below we have a simple plugin (that can be built as a cdylib): 37 | 38 | ```rust 39 | use panda::prelude::*; 40 | 41 | #[panda::on_sys_write_enter] 42 | fn sys_write_test(cpu: &mut CPUState, pc: u64, fd: u64, buf: u64, count: u64) { 43 | // Output the contents of every sys_write as a string 44 | println!( 45 | "sys_write buf = \"{}\"", 46 | String::from_utf8_lossy(&cpu.mem_read(buf, count as usize)) 47 | ); 48 | } 49 | 50 | #[panda::init] 51 | fn init(_: &mut PluginHandle) { 52 | println!("Plugin initialized!"); 53 | } 54 | ``` 55 | 56 | however, if we want to make this into an executable that calls into libpanda we can add a main: 57 | 58 | ```rust 59 | fn main() { 60 | Panda::new() 61 | .generic("x86_64") // use a generic x86_64 linux QCOW (a VM image) 62 | .replay("my_application_replay") // load a replay of the name "my_application_replay" 63 | .run(); 64 | } 65 | ``` 66 | 67 | and enable the `libpanda` feature of panda-rs. 68 | 69 | ## Executing Examples 70 | 71 | Sample snippets in the `panda-rs/examples` directory can be run by name, e.g. `showcase.rs` is executed with: 72 | 73 | ``` 74 | cd panda-rs 75 | cargo run --example showcase --features=libpanda 76 | ``` 77 | 78 | Note the addition of the 'libpanda' feature. This is because Rust examples are standalone executables, not shared libraries (like PANDA plugins) and thus must consume PANDA as a library. 79 | -------------------------------------------------------------------------------- /panda-sys/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::process::Command; 3 | use std::{env, fs}; 4 | 5 | const MISSING_ERROR: &'static str = "Missing PANDA_PATH. Please set it to the `build` folder in your panda install or use pip to install the `pandare` package."; 6 | const PYTHON_GET_SITE_PACKAGES: &'static str = r#"import sys; print("\n".join(sys.path))"#; 7 | 8 | fn get_site_packages() -> Result, std::io::Error> { 9 | Ok(String::from_utf8_lossy( 10 | &Command::new("python3") 11 | .args(&["-c", PYTHON_GET_SITE_PACKAGES]) 12 | .output() 13 | .or_else(|_| { 14 | Command::new("python") 15 | .args(&["-c", PYTHON_GET_SITE_PACKAGES]) 16 | .output() 17 | })? 18 | .stdout[..], 19 | ) 20 | .split("\n") 21 | .map(PathBuf::from) 22 | .collect()) 23 | } 24 | 25 | fn get_panda_path() -> PathBuf { 26 | PathBuf::from( 27 | &env::var("PANDA_PATH") 28 | .map(PathBuf::from) 29 | .or_else(|_| -> Result<_, ()> { 30 | println!("cargo:warning=PANDA_PATH is missing"); 31 | Ok(get_site_packages() 32 | .map_err(|_| ())? 33 | .into_iter() 34 | .filter_map(|site_package_folder| { 35 | let path = site_package_folder.join("pandare").join("data"); 36 | if path.exists() { 37 | Some(path) 38 | } else { 39 | None 40 | } 41 | }) 42 | .next() 43 | .ok_or(())?) 44 | }) 45 | .expect(MISSING_ERROR), 46 | ) 47 | } 48 | 49 | #[cfg(feature = "x86_64")] 50 | const ARCH: &str = "x86_64"; 51 | 52 | #[cfg(feature = "i386")] 53 | const ARCH: &str = "i386"; 54 | 55 | #[cfg(feature = "arm")] 56 | const ARCH: &str = "arm"; 57 | 58 | #[cfg(feature = "aarch64")] 59 | const ARCH: &str = "aarch64"; 60 | 61 | #[cfg(feature = "ppc")] 62 | const ARCH: &str = "ppc"; 63 | 64 | #[cfg(feature = "mips")] 65 | const ARCH: &str = "mips"; 66 | 67 | #[cfg(feature = "mipsel")] 68 | const ARCH: &str = "mipsel"; 69 | 70 | #[cfg(feature = "mips64")] 71 | const ARCH: &str = "mips64"; 72 | 73 | #[cfg(feature = "mips64el")] 74 | const ARCH: &str = "mips64el"; 75 | 76 | fn main() { 77 | if cfg!(feature = "libpanda") { 78 | println!("libpanda mode enabled"); 79 | let dylib_path = get_panda_path().join(format!("{}-softmmu", ARCH)); 80 | println!("cargo:rustc-link-lib=dylib=panda-{}", ARCH); 81 | println!("cargo:rustc-link-search=native={}", dylib_path.display()); 82 | 83 | let out_dir: PathBuf = env::var("OUT_DIR").unwrap().into(); 84 | fs::copy( 85 | dylib_path.join(format!("libpanda-{}.so", ARCH)), 86 | out_dir 87 | .join("..") 88 | .join("..") 89 | .join("..") 90 | .join(format!("libpanda-{}.so", ARCH)), 91 | ) 92 | .unwrap(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /panda-macros/src/guest_type/struct_impl.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use super::GuestTypeField; 5 | 6 | pub fn struct_layout<'a>(fields: impl Iterator + 'a) -> TokenStream { 7 | quote! { 8 | Some( 9 | ::std::alloc::Layout::from_size_align(0, 1).ok()? 10 | #( 11 | .extend(<#fields as ::panda::GuestType>::guest_layout()?).ok()?.0 12 | )* 13 | .pad_to_align() 14 | ) 15 | } 16 | } 17 | 18 | fn read(is_virt: bool, fields: &[GuestTypeField]) -> TokenStream { 19 | let field_name = fields 20 | .iter() 21 | .map(|field| field.ident.as_ref().unwrap()) 22 | .collect::>(); 23 | let field_ty = fields.iter().map(|field| &field.ty); 24 | 25 | let read_method = if is_virt { 26 | quote!(read_from_guest) 27 | } else { 28 | quote!(read_from_guest_phys) 29 | }; 30 | 31 | let cpu = is_virt.then(|| quote! { __cpu, }); 32 | let layout = quote! { __layout }; 33 | quote! { 34 | let #layout = ::std::alloc::Layout::from_size_align(0, 1).unwrap(); 35 | 36 | #( 37 | let (#layout, offset) = #layout.extend( 38 | <#field_ty as ::panda::GuestType>::guest_layout().unwrap() 39 | ).unwrap(); 40 | 41 | let #field_name = <#field_ty as ::panda::GuestType>::#read_method( 42 | #cpu __ptr + (offset as ::panda::prelude::target_ptr_t) 43 | )?; 44 | )* 45 | 46 | Ok(Self { #( #field_name ),* }) 47 | } 48 | } 49 | 50 | pub(super) fn read_from_guest(fields: &[GuestTypeField]) -> TokenStream { 51 | read(true, fields) 52 | } 53 | 54 | pub(super) fn read_from_guest_phys(fields: &[GuestTypeField]) -> TokenStream { 55 | read(false, fields) 56 | } 57 | 58 | fn write(is_virt: bool, fields: &[GuestTypeField]) -> TokenStream { 59 | let field_name = fields 60 | .iter() 61 | .map(|field| field.ident.as_ref().unwrap()) 62 | .collect::>(); 63 | let field_ty = fields.iter().map(|field| &field.ty); 64 | 65 | let write_method = if is_virt { 66 | quote!(write_to_guest) 67 | } else { 68 | quote!(write_to_guest_phys) 69 | }; 70 | 71 | let cpu = is_virt.then(|| quote! { __cpu, }); 72 | let layout = quote! { __layout }; 73 | quote! { 74 | let #layout = ::std::alloc::Layout::from_size_align(0, 1).unwrap(); 75 | 76 | #( 77 | let (#layout, offset) = #layout.extend( 78 | <#field_ty as ::panda::GuestType>::guest_layout().unwrap() 79 | ).unwrap(); 80 | 81 | <#field_ty as ::panda::GuestType>::#write_method( 82 | &self.#field_name, 83 | #cpu __ptr + (offset as ::panda::prelude::target_ptr_t) 84 | )?; 85 | )* 86 | 87 | Ok(()) 88 | } 89 | } 90 | 91 | pub(super) fn write_to_guest(fields: &[GuestTypeField]) -> TokenStream { 92 | write(true, fields) 93 | } 94 | 95 | pub(super) fn write_to_guest_phys(fields: &[GuestTypeField]) -> TokenStream { 96 | write(false, fields) 97 | } 98 | -------------------------------------------------------------------------------- /panda-macros/src/osi_static.rs: -------------------------------------------------------------------------------- 1 | use quote::{quote, quote_spanned}; 2 | use syn::spanned::Spanned; 3 | 4 | pub(crate) struct OsiStatics { 5 | inner: Vec, 6 | } 7 | 8 | impl syn::parse::Parse for OsiStatics { 9 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 10 | let mut inner = vec![]; 11 | 12 | while !input.is_empty() { 13 | inner.push(input.parse()?); 14 | } 15 | 16 | Ok(OsiStatics { inner }) 17 | } 18 | } 19 | 20 | impl quote::ToTokens for OsiStatics { 21 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 22 | for item in &self.inner { 23 | let is_per_cpu = item.attrs.iter().any(|attr| { 24 | attr.path 25 | .get_ident() 26 | .map(|ident| ident == "per_cpu") 27 | .unwrap_or(false) 28 | }); 29 | 30 | let symbol = item.attrs.iter().find_map(|attr| { 31 | (attr.path.get_ident()? == "symbol").then(|| { 32 | let meta = attr.parse_meta().map_err(|_| { 33 | let span = attr.path.span(); 34 | quote_spanned! { span => 35 | compile_error!("Symbol must take the form of `#[symbol = \"…\"]`") 36 | } 37 | })?; 38 | 39 | if let syn::Meta::NameValue(syn::MetaNameValue { 40 | lit: syn::Lit::Str(symbol), 41 | .. 42 | }) = meta 43 | { 44 | Ok(symbol) 45 | } else { 46 | let span = attr.path.span(); 47 | 48 | Err(quote_spanned! { span => 49 | compile_error!("Symbol must take the form of `#[symbol = \"…\"]`") 50 | }) 51 | } 52 | }) 53 | }); 54 | 55 | match symbol { 56 | Some(Ok(symbol)) => { 57 | let osi_static_type = if is_per_cpu { 58 | quote! { ::panda::plugins::cosi::PerCpu } 59 | } else { 60 | quote! { ::panda::plugins::cosi::OsiGlobal } 61 | }; 62 | let ident = &item.ident; 63 | let ty = &item.ty; 64 | 65 | tokens.extend(quote! { 66 | static #ident: #osi_static_type<#ty> = #osi_static_type( 67 | #symbol, 68 | <#ty as ::panda::plugins::cosi::OsiType>::MethodDispatcher::new( 69 | #symbol, #is_per_cpu 70 | ), 71 | ); 72 | }); 73 | } 74 | Some(Err(err)) => tokens.extend(err), 75 | None => { 76 | let span = item.span(); 77 | 78 | tokens.extend(quote_spanned! { span => 79 | compile_error!("Missing attribute `#[symbol = \"…\"]`") 80 | }); 81 | } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /panda-rs/src/plugins/guest_plugin_manager/guest_plugin.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CString, io::Write, os::raw::c_char, path::Path, ptr}; 2 | 3 | use super::GUEST_PLUGIN_MANAGER; 4 | 5 | /// A raw Channel ID 6 | pub type ChannelId = u32; 7 | 8 | /// A callback for recieving writes to a channel performed by the guest 9 | pub type ChannelCB = extern "C" fn(ChannelId, *const u8, usize); 10 | 11 | /// A guest plugin to be loaded by the guest plugin manager 12 | #[repr(C)] 13 | pub struct GuestPlugin { 14 | pub plugin_name: *const c_char, 15 | pub guest_binary_path: *const c_char, 16 | pub msg_receive_cb: ChannelCB, 17 | } 18 | 19 | /// An [`io::Write`](Write) type for writing to a guest plugin channel 20 | #[repr(transparent)] 21 | pub struct Channel(ChannelId); 22 | 23 | impl Channel { 24 | /// Write data to a single packet without going through the io::Write trait 25 | pub fn write_packet(&mut self, buf: &[u8]) { 26 | GUEST_PLUGIN_MANAGER.channel_write(self.0, buf.as_ptr(), buf.len()); 27 | } 28 | 29 | /// Creates a new anonymous channel provided a callback for handling writes 30 | pub fn new(callback: ChannelCB) -> Self { 31 | Channel(GUEST_PLUGIN_MANAGER.allocate_channel(callback)) 32 | } 33 | 34 | /// Get the raw channel ID of this channel 35 | pub fn id(&self) -> ChannelId { 36 | self.0 37 | } 38 | } 39 | 40 | impl Write for Channel { 41 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 42 | self.write_packet(buf); 43 | 44 | Ok(buf.len()) 45 | } 46 | 47 | fn flush(&mut self) -> std::io::Result<()> { 48 | Ok(()) 49 | } 50 | } 51 | 52 | /// Load a guest plugin given the guest plugin's name and a callback for when a message 53 | /// is recieved from this plugin. 54 | /// 55 | /// Returns a channel with the same name as the plugin for use when communicating with 56 | /// the guest plugin. 57 | pub fn load_guest_plugin(name: impl Into, msg_received: ChannelCB) -> Channel { 58 | Channel(GUEST_PLUGIN_MANAGER.add_guest_plugin(GuestPlugin::new(name.into(), msg_received))) 59 | } 60 | 61 | impl GuestPlugin { 62 | /// Initiailizes a `GuestPlugin` to be passed to `add_guest_plugin` by name, finding 63 | /// the path of the plugin by name lookup. 64 | pub fn new(plugin_name: String, msg_receive_cb: ChannelCB) -> Self { 65 | let plugin_name = CString::new(plugin_name).unwrap().into_raw(); 66 | 67 | GuestPlugin { 68 | plugin_name, 69 | guest_binary_path: ptr::null(), 70 | msg_receive_cb, 71 | } 72 | } 73 | 74 | /// Initiailizes a `GuestPlugin` to be passed to `add_guest_plugin` by name, using 75 | /// a set path rather than lookup by name. 76 | pub fn new_with_path( 77 | plugin_name: String, 78 | guest_binary_path: &Path, 79 | msg_receive_cb: ChannelCB, 80 | ) -> Self { 81 | let plugin_name = CString::new(plugin_name).unwrap().into_raw(); 82 | let guest_binary_path = CString::new(guest_binary_path.to_string_lossy().into_owned()) 83 | .unwrap() 84 | .into_raw(); 85 | 86 | GuestPlugin { 87 | plugin_name, 88 | guest_binary_path, 89 | msg_receive_cb, 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /panda-rs/src/api/os.rs: -------------------------------------------------------------------------------- 1 | use crate::sys::{ 2 | panda_os_bits, panda_os_family, panda_os_familyno, panda_os_name, panda_os_variant, 3 | }; 4 | use once_cell::sync::Lazy; 5 | 6 | use std::ffi::CStr; 7 | 8 | macro_rules! convert_static_str { 9 | ($str_name:ident) => { 10 | if unsafe { $str_name.is_null() } { 11 | None 12 | } else { 13 | let c_string = unsafe { CStr::from_ptr($str_name) }; 14 | 15 | Some(c_string.to_string_lossy().into_owned()) 16 | } 17 | }; 18 | } 19 | 20 | /// Get the name of the OS currently set. This is typically set by the `-os` command line 21 | /// argument passed to a PANDA instance. 22 | pub static NAME: Lazy> = Lazy::new(|| unsafe { 23 | if panda_os_name.is_null() { 24 | None 25 | } else { 26 | CStr::from_ptr(panda_os_name) 27 | .to_str() 28 | .ok() 29 | .map(String::from) 30 | } 31 | }); 32 | 33 | /// Get the name of the OS currently set. This is typically set by the `-os` command line 34 | /// argument passed to a PANDA instance. 35 | pub fn name() -> Option { 36 | convert_static_str!(panda_os_name) 37 | } 38 | 39 | /// Get the family name of the OS currently set. This is typically set by the `-os` 40 | /// command line argument passed to a PANDA instance. 41 | pub fn family_name() -> Option { 42 | convert_static_str!(panda_os_family) 43 | } 44 | 45 | /// Get the name of the variation of the OS currently set. This is typically set by the 46 | /// `-os` command line argument passed to a PANDA instance. 47 | pub fn variant() -> Option { 48 | convert_static_str!(panda_os_variant) 49 | } 50 | 51 | /// The bit-width of the OS being currently run. This is not necessarily equivelant to the 52 | /// bit-width of the architecture as, for example, 32-bit Windows can run on a 64-bit 53 | /// x86 processor. 54 | /// 55 | /// This is typically set by the `-os` command line argument passed to a PANDA instance. 56 | pub fn bits() -> u32 { 57 | unsafe { panda_os_bits } 58 | } 59 | 60 | #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 61 | #[repr(u32)] 62 | pub enum OsFamily { 63 | Unknown = 0, 64 | Windows = 1, 65 | Linux = 2, 66 | FreeBsd = 3, 67 | } 68 | 69 | impl OsFamily { 70 | pub fn is_linux(self) -> bool { 71 | self == OsFamily::Linux 72 | } 73 | 74 | pub fn is_windows(self) -> bool { 75 | self == OsFamily::Windows 76 | } 77 | 78 | pub fn is_bsd(self) -> bool { 79 | self == OsFamily::FreeBsd 80 | } 81 | 82 | pub fn is_unix(self) -> bool { 83 | self.is_linux() | self.is_bsd() 84 | } 85 | } 86 | 87 | impl From for OsFamily { 88 | fn from(fam: u32) -> Self { 89 | match fam { 90 | 1 => OsFamily::Windows, 91 | 2 => OsFamily::Linux, 92 | 3 => OsFamily::FreeBsd, 93 | _ => OsFamily::Unknown, 94 | } 95 | } 96 | } 97 | 98 | /// The family of OS being run (Windows, Linux, etc). 99 | /// 100 | /// This is typically set by the `-os` command line argument passed to a PANDA instance. 101 | pub fn family() -> OsFamily { 102 | OsFamily::from(unsafe { panda_os_familyno }) 103 | } 104 | -------------------------------------------------------------------------------- /panda-sys/src/gen_bindings.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | 3 | # Generate Rust bindings to C API for each architecture 4 | bindgen bindings.h -o bindings/x86_64.rs --no-layout-tests -- -I$PANDA_ROOT/panda/include -I$PANDA_ROOT/build -I$PANDA_ROOT/build/x86_64-softmmu -I$PANDA_ROOT/include -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I$PANDA_ROOT/target/i386 -I$PANDA_ROOT/tcg/i386 -I$PANDA_ROOT/tcg -DNEED_CPU_H -I$PANDA_ROOT 5 | bindgen bindings.h -o bindings/i386.rs --no-layout-tests -- -I$PANDA_ROOT/panda/include -I$PANDA_ROOT/build -I$PANDA_ROOT/build/i386-softmmu -I$PANDA_ROOT/include -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I$PANDA_ROOT/target/i386 -I$PANDA_ROOT/tcg/i386 -I$PANDA_ROOT/tcg -DNEED_CPU_H -I$PANDA_ROOT 6 | bindgen bindings.h -o bindings/arm.rs --no-layout-tests -- -I$PANDA_ROOT/panda/include -I$PANDA_ROOT/build -I$PANDA_ROOT/build/arm-softmmu -I$PANDA_ROOT/include -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I$PANDA_ROOT/target/arm -I$PANDA_ROOT/tcg/arm -I$PANDA_ROOT/tcg -DNEED_CPU_H -I$PANDA_ROOT 7 | bindgen bindings.h -o bindings/ppc.rs --no-layout-tests -- -I$PANDA_ROOT/panda/include -I$PANDA_ROOT/build -I$PANDA_ROOT/build/ppc-softmmu -I$PANDA_ROOT/include -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I$PANDA_ROOT/target/ppc -I$PANDA_ROOT/tcg/ppc -I$PANDA_ROOT/tcg -DNEED_CPU_H -I$PANDA_ROOT 8 | bindgen bindings.h -o bindings/mips.rs --no-layout-tests -- -I$PANDA_ROOT/panda/include -I$PANDA_ROOT/build -I$PANDA_ROOT/build/mips-softmmu -I$PANDA_ROOT/include -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I$PANDA_ROOT/target/mips -I$PANDA_ROOT/tcg/mips -I$PANDA_ROOT/tcg -DNEED_CPU_H -I$PANDA_ROOT -Ifake_headers 9 | bindgen bindings.h -o bindings/mipsel.rs --no-layout-tests -- -I$PANDA_ROOT/panda/include -I$PANDA_ROOT/build -I$PANDA_ROOT/build/mipsel-softmmu -I$PANDA_ROOT/include -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I$PANDA_ROOT/target/mips -I$PANDA_ROOT/tcg/mips -I$PANDA_ROOT/tcg -DNEED_CPU_H -I$PANDA_ROOT -Ifake_headers 10 | bindgen bindings.h -o bindings/aarch64.rs --no-layout-tests -- -I$PANDA_ROOT/panda/include -I$PANDA_ROOT/build -I$PANDA_ROOT/build/aarch64-softmmu -I$PANDA_ROOT/include -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I$PANDA_ROOT/target/arm -I$PANDA_ROOT/tcg/aarch64 -I$PANDA_ROOT/tcg -DNEED_CPU_H -I$PANDA_ROOT 11 | bindgen bindings.h -o bindings/mips64.rs --no-layout-tests -- -I$PANDA_ROOT/panda/include -I$PANDA_ROOT/build -I$PANDA_ROOT/build/mips64-softmmu -I$PANDA_ROOT/include -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I$PANDA_ROOT/target/mips -I$PANDA_ROOT/tcg/mips -I$PANDA_ROOT/tcg -DNEED_CPU_H -I$PANDA_ROOT -Ifake_headers 12 | bindgen bindings.h -o bindings/mips64el.rs --no-layout-tests -- -I$PANDA_ROOT/panda/include -I$PANDA_ROOT/build -I$PANDA_ROOT/build/mips64el-softmmu -I$PANDA_ROOT/include -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I$PANDA_ROOT/target/mips -I$PANDA_ROOT/tcg/mips -I$PANDA_ROOT/tcg -DNEED_CPU_H -I$PANDA_ROOT -Ifake_headers 13 | 14 | # Remove double-declared constant 15 | sed '/pub const IPPORT_RESERVED: .* = 1024;/d' bindings/x86_64.rs -i 16 | sed '/pub const IPPORT_RESERVED: .* = 1024;/d' bindings/i386.rs -i 17 | sed '/pub const IPPORT_RESERVED: .* = 1024;/d' bindings/arm.rs -i 18 | sed '/pub const IPPORT_RESERVED: .* = 1024;/d' bindings/ppc.rs -i 19 | sed '/pub const IPPORT_RESERVED: .* = 1024;/d' bindings/mips.rs -i 20 | sed '/pub const IPPORT_RESERVED: .* = 1024;/d' bindings/mipsel.rs -i 21 | sed '/pub const IPPORT_RESERVED: .* = 1024;/d' bindings/aarch64.rs -i 22 | sed '/pub const IPPORT_RESERVED: .* = 1024;/d' bindings/mips64.rs -i 23 | sed '/pub const IPPORT_RESERVED: .* = 1024;/d' bindings/mips64el.rs -i 24 | -------------------------------------------------------------------------------- /panda-sys/src/extensions.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | panda_physical_memory_read_external, panda_virtual_memory_read_external, 3 | panda_virtual_memory_write_external, target_ptr_t, target_ulong, CPUState, 4 | }; 5 | use std::ffi::c_char; 6 | use std::mem::{size_of, transmute, MaybeUninit}; 7 | 8 | const READ_CHUNK_SIZE: target_ptr_t = 0x10; 9 | 10 | impl CPUState { 11 | pub fn mem_read(&mut self, addr: target_ulong, len: usize) -> Vec { 12 | let mut temp = vec![0; len]; 13 | 14 | unsafe { 15 | if panda_virtual_memory_read_external( 16 | self, 17 | addr, 18 | temp.as_mut_ptr() as *mut c_char, 19 | len as _, 20 | ) != 0 21 | { 22 | panic!("Virtual memory read failed"); 23 | } 24 | } 25 | 26 | temp 27 | } 28 | 29 | pub fn mem_write(&mut self, addr: target_ulong, data: &[u8]) { 30 | unsafe { 31 | if panda_virtual_memory_write_external( 32 | self, 33 | addr, 34 | transmute(data.as_ptr()), 35 | data.len() as _, 36 | ) != 0 37 | { 38 | panic!("Virtual memory write failed"); 39 | } 40 | } 41 | } 42 | 43 | pub fn try_mem_read(&mut self, addr: target_ulong, len: usize) -> Option> { 44 | let mut temp = vec![0; len]; 45 | 46 | let ret = unsafe { 47 | panda_virtual_memory_read_external( 48 | self, 49 | addr, 50 | temp.as_mut_ptr() as *mut c_char, 51 | len as _, 52 | ) 53 | }; 54 | 55 | if ret == 0 { 56 | Some(temp) 57 | } else { 58 | None 59 | } 60 | } 61 | 62 | pub fn try_mem_read_phys(&mut self, addr: target_ptr_t, len: usize) -> Option> { 63 | let mut temp = vec![0; len]; 64 | 65 | unsafe { 66 | if panda_physical_memory_read_external(addr as _, temp.as_mut_ptr(), len as _) == 0 { 67 | Some(temp) 68 | } else { 69 | None 70 | } 71 | } 72 | } 73 | 74 | pub fn mem_read_val(&mut self, addr: target_ulong) -> T { 75 | let mut temp = MaybeUninit::uninit(); 76 | 77 | unsafe { 78 | if panda_virtual_memory_read_external( 79 | self, 80 | addr, 81 | temp.as_mut_ptr() as *mut c_char, 82 | size_of::() as _, 83 | ) != 0 84 | { 85 | panic!("Virtual memory read failed"); 86 | } 87 | 88 | temp.assume_init() 89 | } 90 | } 91 | 92 | pub fn mem_read_string(&mut self, mut addr: target_ptr_t) -> String { 93 | let mut buf = vec![]; 94 | let mut temp = [0; READ_CHUNK_SIZE as usize]; 95 | loop { 96 | unsafe { 97 | panda_virtual_memory_read_external( 98 | self, 99 | addr, 100 | temp.as_mut_ptr() as *mut c_char, 101 | READ_CHUNK_SIZE as _, 102 | ); 103 | } 104 | 105 | let null_index = temp.iter().position(|x| x == &0); 106 | match null_index { 107 | Some(index) => { 108 | // A null exists in the current chunk 109 | buf.extend_from_slice(&temp[0..index]); 110 | break; 111 | } 112 | None => { 113 | // No null byte found yet 114 | buf.extend_from_slice(&temp); 115 | addr += READ_CHUNK_SIZE; 116 | } 117 | } 118 | } 119 | 120 | String::from_utf8_lossy(&buf).into_owned() 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /panda-rs/src/plugins/guest_plugin_manager.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for the guest plugin manager 2 | //! 3 | //! The guest plugin manager is a PANDA plugin which manages "guest plugins", or programs 4 | //! which are injected into the guest and can communicate back to the host. 5 | //! 6 | //! See [`load_guest_plugin`] and [`channel_recv`] for more info. 7 | use crate::plugin_import; 8 | 9 | use std::{ 10 | ffi::{CStr, CString}, 11 | os::raw::c_char, 12 | path::PathBuf, 13 | }; 14 | 15 | mod guest_plugin; 16 | pub use guest_plugin::{load_guest_plugin, Channel, ChannelCB, ChannelId, GuestPlugin}; 17 | 18 | mod from_channel_msg; 19 | pub use from_channel_msg::FromChannelMessage; 20 | 21 | /// Allows declaring a callback for recieving messages from a channel 22 | /// 23 | /// Support functions with the signature `fn(u32, Msg)` where `u32` is the ID of the 24 | /// channel being written to, while `Msg` is a type that implements [`FromChannelMessage`] 25 | /// (&str, &[u8], String, etc). 26 | /// 27 | /// ## Example 28 | /// 29 | /// ``` 30 | /// use panda::plugins::guest_plugin_manager::{load_guest_plugin, channel_recv}; 31 | /// 32 | /// // Print every message and which channel it's sent to 33 | /// #[channel_recv] 34 | /// fn receive_message_callback(channel: u32, message: &str) { 35 | /// println!("[channel {}] {}", channel, message); 36 | /// } 37 | /// 38 | /// // Alternatively, use `Option`/`Result` to opt-in to handling invalid unicode 39 | /// #[channel_recv] 40 | /// fn receive_message_callback(channel: u32, message: Option<&str>) { 41 | /// if let Some(msg) = message { 42 | /// println!("[channel {}] {}", channel, msg); 43 | /// } 44 | /// } 45 | /// 46 | /// // Or just ask for raw bytes 47 | /// #[channel_recv] 48 | /// fn receive_message_callback(_: u32, message: &[u8]) { 49 | /// println!("Message length: {}", message.len()); 50 | /// } 51 | /// 52 | /// #[panda::init] 53 | /// fn init() { 54 | /// load_guest_plugin("my_guest_plugin", receive_message_callback); 55 | /// } 56 | /// ``` 57 | #[doc(inline)] 58 | pub use panda_macros::channel_recv; 59 | 60 | plugin_import! { 61 | /// A PANDA plugin which manages "guest plugins", programs which are injected into 62 | /// the guest which can communicate with the host process via "channels".test 63 | /// 64 | /// Unless you need greater control, it is recommended to use `load_guest_plugin` rather 65 | /// than using the `GUEST_PLUGIN_MANAGER` object directly. 66 | static GUEST_PLUGIN_MANAGER: GuestPluginManager = extern "guest_plugin_manager" { 67 | /// Add a guest plugin to the guest plugin manager, loading it into the guest 68 | /// as soon as possible. 69 | fn add_guest_plugin(plugin: GuestPlugin) -> ChannelId; 70 | 71 | /// Write to a channel, buffering the message until the guest performs a read 72 | /// to the channel. 73 | fn channel_write(channel: ChannelId, out: *const u8, out_len: usize); 74 | 75 | /// Get a channel given a name, typically the name of the guest plugin it is 76 | /// associated with, as each guest plugin is allocated a "main" channel of the 77 | /// same name. 78 | fn get_channel_from_name(channel_name: *const c_char) -> ChannelId; 79 | 80 | /// Create a new channel given a callback for handling writes, returns the ID 81 | /// of the newly allocated channel. 82 | fn allocate_channel(callback: ChannelCB) -> ChannelId; 83 | }; 84 | } 85 | 86 | /// Get the guest plugin's path from its name, returning `None` if the guest plugin 87 | /// could not be found. 88 | pub fn guest_plugin_path(name: &str) -> Option { 89 | extern "C" { 90 | fn panda_guest_plugin_path(name: *const c_char) -> *mut c_char; 91 | } 92 | 93 | let name = CString::new(name).ok()?; 94 | let path_result = unsafe { panda_guest_plugin_path(name.as_ptr()) }; 95 | 96 | if path_result.is_null() { 97 | None 98 | } else { 99 | let path = unsafe { CStr::from_ptr(path_result) }; 100 | let path = path.to_str().ok().map(PathBuf::from); 101 | 102 | unsafe { 103 | panda::sys::free(path_result as _); 104 | } 105 | 106 | path 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /panda-rs/src/syscall_injection/conversion.rs: -------------------------------------------------------------------------------- 1 | use crate::sys::target_ulong; 2 | use async_trait::async_trait; 3 | 4 | use std::convert::TryInto; 5 | 6 | #[cfg(doc)] 7 | use super::syscall; 8 | 9 | /// A trait for converting a single value into a syscall argument. 10 | /// 11 | /// This trait is asynchronous to allow for system calls to be performed 12 | /// during the conversion (for example to map memory in the guest). 13 | #[async_trait] 14 | pub trait IntoSyscallArg { 15 | async fn into_syscall_arg(self) -> target_ulong; 16 | } 17 | 18 | macro_rules! impl_for_ints { 19 | ($($int:ty),*) => { 20 | $( 21 | #[async_trait] 22 | impl IntoSyscallArg for $int { 23 | async fn into_syscall_arg(self) -> target_ulong { 24 | self.try_into().unwrap() 25 | } 26 | } 27 | )* 28 | }; 29 | } 30 | 31 | impl_for_ints!(u8, u16, u32, u64); 32 | 33 | /// A trait for converting a set of values into a full set of arguments for 34 | /// performing a system call. This trait is primarily used to provide arguments 35 | /// to the [`syscall`] function. 36 | /// 37 | /// This trait is asynchronous to allow for system calls to be performed 38 | /// during the conversion (for example to map memory in the guest). 39 | /// 40 | /// This is implemented both for arrays and tuples, up to length 6 (the max number of 41 | /// system call arguments). 42 | #[async_trait] 43 | pub trait IntoSyscallArgs { 44 | async fn into_syscall_args(self) -> SyscallArgs; 45 | } 46 | 47 | /// Arguments to be passed to a system call 48 | /// 49 | /// Should be converted to using [`IntoSyscallArgs`]. Conversion is handled generically 50 | /// by [`syscall`]. 51 | pub struct SyscallArgs { 52 | regs: [target_ulong; 6], 53 | regs_used: usize, 54 | } 55 | 56 | impl SyscallArgs { 57 | pub fn iter_args(&self) -> impl Iterator + '_ { 58 | self.regs.iter().copied().take(self.regs_used) 59 | } 60 | } 61 | 62 | #[doc(hidden)] 63 | pub struct SyscallCount; 64 | 65 | #[doc(hidden)] 66 | pub trait LessThan7 {} 67 | 68 | impl LessThan7 for SyscallCount<0> {} 69 | impl LessThan7 for SyscallCount<1> {} 70 | impl LessThan7 for SyscallCount<2> {} 71 | impl LessThan7 for SyscallCount<3> {} 72 | impl LessThan7 for SyscallCount<4> {} 73 | impl LessThan7 for SyscallCount<5> {} 74 | impl LessThan7 for SyscallCount<6> {} 75 | 76 | #[async_trait] 77 | impl IntoSyscallArgs for [Arg; N] 78 | where 79 | SyscallCount: LessThan7, 80 | { 81 | async fn into_syscall_args(self) -> SyscallArgs { 82 | assert!(N <= 6, "Only up to 6 syscall arguments are allowed"); 83 | let mut regs = [0; 6]; 84 | for (i, arg) in IntoIterator::into_iter(self).enumerate() { 85 | regs[i] = arg.into_syscall_arg().await; 86 | } 87 | 88 | SyscallArgs { 89 | regs, 90 | regs_used: N as _, 91 | } 92 | } 93 | } 94 | 95 | macro_rules! impl_for_tuples { 96 | ($first:ident $(, $nth:ident)*) => { 97 | #[async_trait] 98 | impl<$first $(, $nth)*> IntoSyscallArgs for ($first, $($nth),*) 99 | where $first: IntoSyscallArg + Send + Sync, 100 | $($nth: IntoSyscallArg + Send + Sync),* 101 | { 102 | #[allow(non_snake_case)] 103 | async fn into_syscall_args(self) -> SyscallArgs { 104 | let ($first, $($nth),*) = self; 105 | let arr = [ 106 | $first.into_syscall_arg().await, 107 | $($nth.into_syscall_arg().await),* 108 | ]; 109 | let mut regs = [0; 6]; 110 | let regs_used = arr.len(); 111 | 112 | regs[..regs_used].copy_from_slice(&arr[..]); 113 | 114 | SyscallArgs { regs, regs_used } 115 | } 116 | } 117 | 118 | impl_for_tuples!($($nth),*); 119 | }; 120 | () => { 121 | #[async_trait] 122 | impl IntoSyscallArgs for () { 123 | async fn into_syscall_args(self) -> SyscallArgs { 124 | SyscallArgs { regs: [0; 6], regs_used: 0 } 125 | } 126 | } 127 | } 128 | } 129 | 130 | impl_for_tuples!(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6); 131 | -------------------------------------------------------------------------------- /panda-rs/src/plugins/osi.rs: -------------------------------------------------------------------------------- 1 | //! Bingings for the OSI (Operating System Introspection) plugin 2 | use crate::plugin_import; 3 | use crate::plugins::glib::{GBox, GBoxedSlice}; 4 | use crate::sys::{target_pid_t, target_ptr_t, target_ulong, CPUState}; 5 | 6 | use std::borrow::Cow; 7 | use std::ffi::CStr; 8 | 9 | use glib_sys::GArray; 10 | 11 | plugin_import! { 12 | static OSI: Osi = extern "osi" { 13 | fn get_process_handles(cpu: *mut CPUState) -> GBoxedSlice; 14 | fn get_current_thread(cpu: *mut CPUState) -> GBox; 15 | fn get_modules(cpu: *mut CPUState) -> GBoxedSlice; 16 | fn get_mappings(cpu: *mut CPUState, p: *mut OsiProc) -> GBoxedSlice; 17 | fn get_processes(cpu: *mut CPUState) -> GBoxedSlice; 18 | fn get_current_process(cpu: *mut CPUState) -> Option>; 19 | fn get_one_module(osimodules: *mut GArray, idx: ::std::os::raw::c_uint) -> *mut OsiModule; 20 | fn get_one_proc(osiprocs: *mut GArray, idx: ::std::os::raw::c_uint) -> *mut OsiProc; 21 | fn cleanup_garray(g: *mut GArray); 22 | fn get_current_process_handle(cpu: *mut CPUState) -> GBox; 23 | fn get_process(cpu: *mut CPUState, h: *const OsiProcHandle) -> GBox; 24 | fn get_process_pid(cpu: *mut CPUState, h: *const OsiProcHandle) -> target_pid_t; 25 | fn get_process_ppid(cpu: *mut CPUState, h: *const OsiProcHandle) -> target_pid_t; 26 | fn in_shared_object(cpu: *mut CPUState, h: *const OsiProc) -> bool; 27 | }; 28 | } 29 | 30 | #[doc = " Minimal handle for a process. Contains a unique identifier \\p asid"] 31 | #[doc = " and a task descriptor pointer \\p taskd that can be used to retrieve the full"] 32 | #[doc = " details of the process."] 33 | #[repr(C)] 34 | #[derive(Debug, Copy, Clone)] 35 | pub struct osi_proc_handle_struct { 36 | pub taskd: target_ptr_t, 37 | pub asid: target_ptr_t, 38 | } 39 | #[doc = " Minimal handle for a process. Contains a unique identifier \\p asid"] 40 | #[doc = " and a task descriptor pointer \\p taskd that can be used to retrieve the full"] 41 | #[doc = " details of the process."] 42 | pub type OsiProcHandle = osi_proc_handle_struct; 43 | #[doc = " Minimal information about a process thread."] 44 | #[doc = " Address space and open resources are shared between threads"] 45 | #[doc = " of the same process. This information is stored in OsiProc."] 46 | #[repr(C)] 47 | #[derive(Debug, Copy, Clone)] 48 | pub struct osi_thread_struct { 49 | pub pid: target_pid_t, 50 | pub tid: target_pid_t, 51 | } 52 | #[doc = " Minimal information about a process thread."] 53 | #[doc = " Address space and open resources are shared between threads"] 54 | #[doc = " of the same process. This information is stored in OsiProc."] 55 | pub type OsiThread = osi_thread_struct; 56 | #[doc = " Represents a page in the address space of a process."] 57 | #[doc = ""] 58 | #[doc = " This has not been implemented/used so far."] 59 | #[repr(C)] 60 | #[derive(Debug, Copy, Clone)] 61 | pub struct osi_page_struct { 62 | pub start: target_ptr_t, 63 | pub len: target_ulong, 64 | } 65 | #[doc = " Represents a page in the address space of a process."] 66 | #[doc = ""] 67 | #[doc = " This has not been implemented/used so far."] 68 | pub type OsiPage = osi_page_struct; 69 | #[doc = " Represents information about a guest OS module (kernel module"] 70 | #[doc = " or shared library)."] 71 | #[repr(C)] 72 | #[derive(Debug, Copy, Clone)] 73 | pub struct osi_module_struct { 74 | pub modd: target_ptr_t, 75 | pub base: target_ptr_t, 76 | pub size: target_ptr_t, 77 | pub file: *mut ::std::os::raw::c_char, 78 | pub name: *mut ::std::os::raw::c_char, 79 | } 80 | #[doc = " Represents information about a guest OS module (kernel module"] 81 | #[doc = " or shared library)."] 82 | pub type OsiModule = osi_module_struct; 83 | #[doc = " Detailed information for a process."] 84 | #[repr(C)] 85 | #[derive(Debug, Copy, Clone)] 86 | pub struct osi_proc_struct { 87 | pub taskd: target_ptr_t, 88 | pub pgd: target_ptr_t, 89 | pub asid: target_ptr_t, 90 | pub pid: target_pid_t, 91 | pub ppid: target_pid_t, 92 | pub name: *mut ::std::os::raw::c_char, 93 | pub pages: *mut OsiPage, 94 | pub create_time: u64, 95 | } 96 | #[doc = " Detailed information for a process."] 97 | pub type OsiProc = osi_proc_struct; 98 | 99 | impl osi_proc_struct { 100 | pub fn get_name(&self) -> Cow { 101 | if self.name.is_null() { 102 | "".into() 103 | } else { 104 | unsafe { CStr::from_ptr(self.name) }.to_string_lossy() 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /panda-macros/src/panda_args.rs: -------------------------------------------------------------------------------- 1 | #[derive(FromField)] 2 | #[darling(attributes(arg))] 3 | struct DeriveArgs { 4 | #[darling(default)] 5 | about: Option, 6 | #[darling(default)] 7 | default: Option, 8 | #[darling(default)] 9 | required: bool, 10 | ident: Option, 11 | ty: syn::Type, 12 | } 13 | 14 | fn derive_args_to_mappings( 15 | DeriveArgs { 16 | about, 17 | default, 18 | ident, 19 | ty, 20 | required, 21 | }: DeriveArgs, 22 | ) -> (syn::Stmt, syn::Ident) { 23 | let name = &ident; 24 | let default = if let Some(default) = default { 25 | match default { 26 | syn::Lit::Str(string) => quote!(::std::string::String::from(#string)), 27 | default => quote!(#default), 28 | } 29 | } else { 30 | quote!(Default::default()) 31 | }; 32 | let about = about.unwrap_or_default(); 33 | ( 34 | syn::parse_quote!( 35 | let #name = <#ty as ::panda::panda_arg::GetPandaArg>::get_panda_arg( 36 | __args_ptr, 37 | stringify!(#name), 38 | #default, 39 | #about, 40 | #required 41 | ); 42 | ), 43 | ident.unwrap(), 44 | ) 45 | } 46 | 47 | fn get_field_statements( 48 | fields: &syn::Fields, 49 | ) -> Result<(Vec, Vec), darling::Error> { 50 | Ok(fields 51 | .iter() 52 | .map(DeriveArgs::from_field) 53 | .collect::, _>>()? 54 | .into_iter() 55 | .map(derive_args_to_mappings) 56 | .unzip()) 57 | } 58 | 59 | fn get_name(attrs: &[syn::Attribute]) -> Option { 60 | attrs 61 | .iter() 62 | .find(|attr| attr.path.get_ident().map(|x| *x == "name").unwrap_or(false)) 63 | .map(|attr| attr.parse_meta().ok()) 64 | .flatten() 65 | .map(|meta| { 66 | if let syn::Meta::NameValue(syn::MetaNameValue { 67 | lit: syn::Lit::Str(s), 68 | .. 69 | }) = meta 70 | { 71 | Some(s.value()) 72 | } else { 73 | None 74 | } 75 | }) 76 | .flatten() 77 | } 78 | 79 | #[proc_macro_derive(PandaArgs, attributes(name, arg))] 80 | pub fn derive_panda_args(input: TokenStream) -> TokenStream { 81 | let input = syn::parse_macro_input!(input as syn::ItemStruct); 82 | 83 | let name = match get_name(&input.attrs) { 84 | Some(name) => name, 85 | None => { 86 | return quote!(compile_error!( 87 | "Missing plugin name, add `#[name = ...]` above struct" 88 | )) 89 | .into() 90 | } 91 | }; 92 | 93 | let ident = &input.ident; 94 | 95 | match get_field_statements(&input.fields) { 96 | Ok((statements, fields)) => { 97 | let format_args = iter::repeat("{}={}") 98 | .take(statements.len()) 99 | .collect::>() 100 | .join(","); 101 | quote!( 102 | impl ::panda::PandaArgs for #ident { 103 | const PLUGIN_NAME: &'static str = #name; 104 | 105 | fn from_panda_args() -> Self { 106 | let name = ::std::ffi::CString::new(#name).unwrap(); 107 | 108 | unsafe { 109 | let __args_ptr = ::panda::sys::panda_get_args(name.as_ptr()); 110 | 111 | #( 112 | #statements 113 | )* 114 | 115 | ::panda::sys::panda_free_args(__args_ptr); 116 | 117 | Self { 118 | #(#fields),* 119 | } 120 | } 121 | } 122 | 123 | fn to_panda_args_str(&self) -> ::std::string::String { 124 | format!( 125 | concat!(#name, ":", #format_args), 126 | #( 127 | stringify!(#fields), self.#fields 128 | ),* 129 | ) 130 | } 131 | 132 | fn to_panda_args(&self) -> ::std::vec::Vec<(&'static str, ::std::string::String)> { 133 | ::std::vec![ 134 | #( 135 | (stringify!(#fields), self.#fields.to_string()), 136 | )* 137 | ] 138 | } 139 | } 140 | ).into() 141 | } 142 | Err(err) => err.write_errors().into(), 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /panda-rs/src/plugins/proc_start_linux.rs: -------------------------------------------------------------------------------- 1 | //! Raw Rust bindings for proc_start_linux plugin 2 | //! 3 | //! Not designed to be used directly, but is used internally for: 4 | //! 5 | //! * [`on_rec_auxv`](crate::on_rec_auxv) 6 | use crate::plugin_import; 7 | use std::os::raw::c_int; 8 | 9 | use crate::sys::{target_ulong, CPUState, TranslationBlock}; 10 | 11 | plugin_import! { 12 | static PROC_START_LINUX: ProcStartLinux = extern "proc_start_linux" { 13 | callbacks { 14 | fn on_rec_auxv(cpu: &mut CPUState, tb: &mut TranslationBlock, auxv: &AuxvValues); 15 | } 16 | }; 17 | } 18 | 19 | pub const MAX_PATH_LEN: u32 = 256; 20 | pub const MAX_NUM_ARGS: u32 = 10; 21 | pub const MAX_NUM_ENV: u32 = 20; 22 | 23 | /// A struct representing the contents of the Auxilary Vector, the 24 | /// information provided by the kernel when starting up a new process. 25 | /// 26 | /// Resources on the auxilary vector: 27 | /// * 28 | /// * 29 | #[repr(C)] 30 | #[derive(Clone)] 31 | pub struct AuxvValues { 32 | pub argc: c_int, 33 | pub argv_ptr_ptr: target_ulong, 34 | pub arg_ptr: [target_ulong; 10usize], 35 | pub argv: [[u8; 256usize]; 10usize], 36 | pub envc: c_int, 37 | pub env_ptr_ptr: target_ulong, 38 | pub env_ptr: [target_ulong; 20usize], 39 | pub envp: [[u8; 256usize]; 20usize], 40 | pub execfn_ptr: target_ulong, 41 | pub execfn: [u8; 256usize], 42 | pub phdr: target_ulong, 43 | pub entry: target_ulong, 44 | pub ehdr: target_ulong, 45 | pub hwcap: target_ulong, 46 | pub hwcap2: target_ulong, 47 | pub pagesz: target_ulong, 48 | pub clktck: target_ulong, 49 | pub phent: target_ulong, 50 | pub phnum: target_ulong, 51 | pub base: target_ulong, 52 | pub flags: target_ulong, 53 | pub uid: target_ulong, 54 | pub euid: target_ulong, 55 | pub gid: target_ulong, 56 | pub egid: target_ulong, 57 | pub secure: bool, 58 | pub random: target_ulong, 59 | pub platform: target_ulong, 60 | pub program_header: target_ulong, 61 | } 62 | 63 | impl AuxvValues { 64 | pub fn argv(&self) -> Vec { 65 | self.argv[..self.argc as usize] 66 | .iter() 67 | .map(|arg| { 68 | String::from_utf8_lossy(&arg[..arg.iter().position(|x| *x == 0).unwrap()]) 69 | .into_owned() 70 | }) 71 | .collect() 72 | } 73 | 74 | pub fn envp(&self) -> Vec { 75 | self.envp[..self.envc as usize] 76 | .iter() 77 | .map(|env| { 78 | String::from_utf8_lossy(&env[..env.iter().position(|x| *x == 0).unwrap()]) 79 | .into_owned() 80 | }) 81 | .collect() 82 | } 83 | 84 | pub fn execfn(&self) -> String { 85 | let execfn = &self.execfn; 86 | String::from_utf8_lossy(&execfn[..execfn.iter().position(|x| *x == 0).unwrap()]) 87 | .into_owned() 88 | } 89 | } 90 | 91 | use std::fmt; 92 | 93 | struct HexDebug(D); 94 | 95 | impl fmt::Debug for HexDebug { 96 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 97 | write!(f, "{:#x?}", self.0) 98 | } 99 | } 100 | 101 | impl fmt::Debug for AuxvValues { 102 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 103 | f.debug_struct("AuxvValues") 104 | .field("argc", &self.argc) 105 | .field("argv_ptr_ptr", &HexDebug(self.argv_ptr_ptr)) 106 | .field("arg_ptr", &HexDebug(&self.arg_ptr[..self.argc as usize])) 107 | .field("argv", &self.argv()) 108 | .field("envc", &self.envc) 109 | .field("env_ptr_ptr", &HexDebug(self.env_ptr_ptr)) 110 | .field("env_ptr", &HexDebug(&self.env_ptr[..self.envc as usize])) 111 | .field("envp", &self.envp()) 112 | .field("execfn_ptr", &HexDebug(self.execfn_ptr)) 113 | .field("execfn", &self.execfn()) 114 | .field("phdr", &HexDebug(self.phdr)) 115 | .field("entry", &HexDebug(self.entry)) 116 | .field("ehdr", &HexDebug(self.ehdr)) 117 | .field("hwcap", &self.hwcap) 118 | .field("hwcap2", &self.hwcap2) 119 | .field("pagesz", &HexDebug(self.pagesz)) 120 | .field("clktck", &self.clktck) 121 | .field("phent", &self.phent) 122 | .field("phnum", &self.phnum) 123 | .field("base", &HexDebug(self.base)) 124 | .field("flags", &self.flags) 125 | .field("uid", &self.uid) 126 | .field("euid", &self.euid) 127 | .field("gid", &self.gid) 128 | .field("egid", &self.egid) 129 | .field("secure", &self.secure) 130 | .field("random", &HexDebug(self.random)) 131 | .field("platform", &HexDebug(self.platform)) 132 | .field("program_header", &HexDebug(self.program_header)) 133 | .finish() 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /panda-rs/src/callbacks/closure.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | ffi::c_void, 4 | sync::{ 5 | atomic::{AtomicU64, Ordering}, 6 | RwLock, 7 | }, 8 | }; 9 | 10 | use once_cell::sync::OnceCell; 11 | 12 | use crate::sys::{hwaddr, target_ptr_t, CPUState, MachineState, Monitor, TranslationBlock}; 13 | use crate::{sys, PluginHandle}; 14 | 15 | /// A reference to a given callback slot which can be used to install, 16 | /// enable, disable, or otherwise reference, a closure-based callback. 17 | /// 18 | /// Since this is a reference to a callback slot and does not include storage 19 | /// for the callback itself, it can be trivially copied, as well as included in 20 | /// the callback itself (for the purposes of enabling/disabling). 21 | /// 22 | /// ## Example 23 | /// 24 | /// ``` 25 | /// use panda::prelude::*; 26 | /// use panda::Callback; 27 | /// 28 | /// let mut count = 0; 29 | /// let bb_callback = Callback::new(); 30 | /// bb_callback.before_block_exec(move |_, _| { 31 | /// count += 1; 32 | /// println!("Basic block #{}", count); 33 | /// 34 | /// if count > 5 { 35 | /// bb_callback.disable(); 36 | /// } 37 | /// }); 38 | /// 39 | /// Panda::new() 40 | /// .generic("x86_64") 41 | /// .run(); 42 | /// ``` 43 | /// 44 | /// ## Note 45 | /// 46 | /// Callback closures must have a static lifetime in order to live past the end of the 47 | /// function. This means that the only references a callback can include are references 48 | /// to static variables or leaked objects on the heap (See `Box::leak` for more info). 49 | /// 50 | /// If you'd like to reference shared data without leaking, this can be accomplished via 51 | /// reference counting. See [`Arc`] for more info. If you want to capture data owned 52 | /// by the current function without sharing it, you can mark your closure as `move` in 53 | /// order to move all the variables you capture into your closure. (Such as in the above 54 | /// example, where `count` is moved into the closure for modification) 55 | /// 56 | /// [`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html 57 | #[repr(transparent)] 58 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 59 | pub struct Callback(u64); 60 | 61 | static CURRENT_CALLBACK_ID: AtomicU64 = AtomicU64::new(0); 62 | 63 | impl Callback { 64 | /// Create a new callback slot which can then be used to install or modify 65 | /// a given callback. 66 | pub fn new() -> Self { 67 | Self(CURRENT_CALLBACK_ID.fetch_add(1, Ordering::SeqCst)) 68 | } 69 | 70 | /// Enable the callback assigned to the given slot, if any. 71 | pub fn enable(&self) { 72 | let callbacks = CALLBACKS.read().unwrap(); 73 | if let Some(callback) = callbacks.get(&self.0) { 74 | unsafe { 75 | sys::panda_enable_callback_with_context( 76 | get_plugin_ref(), 77 | callback.cb_kind, 78 | callback.trampoline, 79 | callback.closure_ref as *mut c_void, 80 | ); 81 | } 82 | } 83 | } 84 | 85 | /// Disable the callback assigned to the given slot, if any. 86 | pub fn disable(&self) { 87 | let callbacks = CALLBACKS.read().unwrap(); 88 | 89 | if let Some(callback) = callbacks.get(&self.0) { 90 | unsafe { 91 | sys::panda_disable_callback_with_context( 92 | get_plugin_ref(), 93 | callback.cb_kind, 94 | callback.trampoline, 95 | callback.closure_ref as *mut c_void, 96 | ); 97 | } 98 | } 99 | } 100 | } 101 | 102 | struct ClosureCallback { 103 | closure_ref: *mut *mut c_void, 104 | cb_kind: sys::panda_cb_type, 105 | trampoline: sys::panda_cb_with_context, 106 | drop_fn: unsafe fn(*mut *mut c_void), 107 | } 108 | 109 | unsafe impl Sync for ClosureCallback {} 110 | unsafe impl Send for ClosureCallback {} 111 | 112 | lazy_static::lazy_static! { 113 | static ref CALLBACKS: RwLock> = RwLock::new(HashMap::new()); 114 | } 115 | 116 | static PLUGIN_REF: OnceCell = OnceCell::new(); 117 | 118 | #[doc(hidden)] 119 | pub fn set_plugin_ref(plugin: *mut PluginHandle) { 120 | let _ = PLUGIN_REF.set(plugin as u64); 121 | } 122 | 123 | fn get_plugin_ref() -> *mut c_void { 124 | *PLUGIN_REF.get_or_init(|| &PLUGIN_REF as *const _ as u64) as _ 125 | } 126 | 127 | fn install_closure_callback(id: u64, callback: ClosureCallback) { 128 | unsafe { 129 | sys::panda_register_callback_with_context( 130 | get_plugin_ref(), 131 | callback.cb_kind, 132 | callback.trampoline, 133 | callback.closure_ref as *mut c_void, 134 | ); 135 | } 136 | 137 | CALLBACKS.write().unwrap().insert(id, callback); 138 | } 139 | 140 | impl std::ops::Drop for ClosureCallback { 141 | fn drop(&mut self) { 142 | unsafe { (self.drop_fn)(self.closure_ref) } 143 | } 144 | } 145 | 146 | panda_macros::define_closure_callbacks!(); 147 | -------------------------------------------------------------------------------- /panda-macros/src/guest_type.rs: -------------------------------------------------------------------------------- 1 | use darling::ast::Data; 2 | use darling::{FromDeriveInput, FromField, FromVariant}; 3 | 4 | use proc_macro2::TokenStream; 5 | use quote::quote; 6 | 7 | #[derive(FromDeriveInput)] 8 | pub(crate) struct GuestTypeInput { 9 | ident: syn::Ident, 10 | data: Data, 11 | 12 | #[darling(default)] 13 | guest_repr: String, 14 | } 15 | 16 | #[allow(dead_code)] 17 | #[derive(FromVariant)] 18 | struct GuestTypeVariant { 19 | ident: syn::Ident, 20 | discriminant: Option, 21 | fields: darling::ast::Fields, 22 | } 23 | 24 | #[derive(FromField)] 25 | struct GuestTypeVariantField {} 26 | 27 | #[derive(FromField)] 28 | struct GuestTypeField { 29 | ident: Option, 30 | ty: syn::Type, 31 | } 32 | 33 | enum IntRepr { 34 | U8, 35 | U16, 36 | U32, 37 | U64, 38 | I8, 39 | I16, 40 | I32, 41 | I64, 42 | } 43 | 44 | enum Repr { 45 | C, 46 | Packed, 47 | Int(IntRepr), 48 | } 49 | 50 | impl Repr { 51 | fn from_str(repr: &str) -> Self { 52 | match repr { 53 | "" | "c" | "C" => Repr::C, 54 | "packed" => Repr::Packed, 55 | "u8" => Repr::Int(IntRepr::U8), 56 | "u16" => Repr::Int(IntRepr::U16), 57 | "u32" => Repr::Int(IntRepr::U32), 58 | "u64" => Repr::Int(IntRepr::U64), 59 | "i8" => Repr::Int(IntRepr::I8), 60 | "i16" => Repr::Int(IntRepr::I16), 61 | "i32" => Repr::Int(IntRepr::I32), 62 | "i64" => Repr::Int(IntRepr::I64), 63 | _ => panic!("Invalid repr: must be one of 'c', 'packed', or integer type"), 64 | } 65 | } 66 | } 67 | 68 | struct Impls { 69 | guest_layout: TokenStream, 70 | read_from_guest: TokenStream, 71 | write_to_guest: TokenStream, 72 | read_from_guest_phys: TokenStream, 73 | write_to_guest_phys: TokenStream, 74 | } 75 | 76 | fn todo() -> TokenStream { 77 | quote! { todo!() } 78 | } 79 | 80 | mod struct_impl; 81 | 82 | impl GuestTypeInput { 83 | pub(crate) fn to_tokens(self) -> TokenStream { 84 | let Self { 85 | ident, 86 | data, 87 | guest_repr, 88 | } = self; 89 | 90 | let ty = ident; 91 | let repr = Repr::from_str(&guest_repr); 92 | 93 | if data.is_struct() && matches!(repr, Repr::Int(_)) { 94 | panic!("guest_repr = \"{}\" is only allowed on enums", guest_repr); 95 | } 96 | 97 | let impls = match data { 98 | Data::Enum(_en) => Impls { 99 | guest_layout: todo(), 100 | read_from_guest: todo(), 101 | write_to_guest: todo(), 102 | read_from_guest_phys: todo(), 103 | write_to_guest_phys: todo(), 104 | }, 105 | Data::Struct(st) => { 106 | let guest_layout = 107 | struct_impl::struct_layout(st.fields.iter().map(|field| &field.ty)); 108 | 109 | let read_from_guest = struct_impl::read_from_guest(&st.fields); 110 | let read_from_guest_phys = struct_impl::read_from_guest_phys(&st.fields); 111 | 112 | let write_to_guest = struct_impl::write_to_guest(&st.fields); 113 | let write_to_guest_phys = struct_impl::write_to_guest_phys(&st.fields); 114 | 115 | Impls { 116 | guest_layout, 117 | read_from_guest, 118 | write_to_guest, 119 | read_from_guest_phys, 120 | write_to_guest_phys, 121 | } 122 | } 123 | }; 124 | 125 | let Impls { 126 | guest_layout, 127 | read_from_guest, 128 | write_to_guest, 129 | read_from_guest_phys, 130 | write_to_guest_phys, 131 | } = impls; 132 | 133 | let ret_type = quote!( Result ); 134 | let write_ret = quote!( Result<(), ::panda::GuestWriteFail> ); 135 | 136 | quote! { 137 | const _: fn() = || { 138 | use panda::prelude::*; 139 | 140 | impl ::panda::GuestType for #ty { 141 | fn guest_layout() -> Option<::std::alloc::Layout> { 142 | #guest_layout 143 | } 144 | 145 | fn read_from_guest(__cpu: &mut CPUState, __ptr: target_ptr_t) -> #ret_type { 146 | #read_from_guest 147 | } 148 | 149 | fn write_to_guest(&self, __cpu: &mut CPUState, __ptr: target_ptr_t) -> #write_ret { 150 | #write_to_guest 151 | } 152 | 153 | fn read_from_guest_phys(__ptr: target_ptr_t) -> #ret_type { 154 | #read_from_guest_phys 155 | } 156 | 157 | fn write_to_guest_phys(&self, __ptr: target_ptr_t) -> #write_ret { 158 | #write_to_guest_phys 159 | } 160 | } 161 | }; 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /panda-rs/src/guest_ptr.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use once_cell::sync::OnceCell; 3 | 4 | use std::alloc::Layout; 5 | use std::ops::Deref; 6 | 7 | mod guest_align; 8 | mod impls; 9 | 10 | pub(crate) use guest_align::GuestAlign; 11 | 12 | #[derive(Copy, Clone, Debug)] 13 | pub struct GuestReadFail; 14 | 15 | #[derive(Copy, Clone, Debug)] 16 | pub struct GuestWriteFail; 17 | 18 | /// A type which can be converted to and from a guest memory representation, allowing 19 | /// it to be used with [`GuestPtr`]. 20 | pub trait GuestType: Sized { 21 | fn guest_layout() -> Option; 22 | 23 | /// The size of the type in the guest, `None` if the type is dynamically sized 24 | fn guest_size() -> Option { 25 | Self::guest_layout().map(|layout| layout.size()) 26 | } 27 | 28 | /// The required minimum alignment of the type in the guest 29 | fn guest_align() -> usize { 30 | Self::guest_layout() 31 | .map(|layout| layout.align()) 32 | .unwrap_or(1) 33 | } 34 | 35 | fn read_from_guest(cpu: &mut CPUState, ptr: target_ptr_t) -> Result; 36 | fn write_to_guest(&self, cpu: &mut CPUState, ptr: target_ptr_t) -> Result<(), GuestWriteFail>; 37 | 38 | fn read_from_guest_phys(ptr: target_ptr_t) -> Result; 39 | fn write_to_guest_phys(&self, ptr: target_ptr_t) -> Result<(), GuestWriteFail>; 40 | } 41 | 42 | pub struct GuestPtr { 43 | pointer: target_ptr_t, 44 | guest_type: OnceCell>, 45 | } 46 | 47 | impl From for GuestPtr { 48 | fn from(pointer: target_ptr_t) -> Self { 49 | GuestPtr { 50 | pointer, 51 | guest_type: OnceCell::new(), 52 | } 53 | } 54 | } 55 | 56 | impl Clone for GuestPtr { 57 | fn clone(&self) -> Self { 58 | Self::from(self.pointer) 59 | } 60 | } 61 | 62 | impl GuestPtr { 63 | /// Reads the value from the guest to be accessed later. This is a no-op if a value 64 | /// has already been cached. This is only needed if you need to read at a different 65 | /// time than you intend to. 66 | /// 67 | /// If you want read a value and replace the cache if it exists, use 68 | /// [`GuestPtr::update`] instead. If you wish to read at time of first access, 69 | /// the `GuestPtr` only needs to be dereferenced without calling `read` ahead of 70 | /// time. 71 | pub fn read(&self) -> Result<&T, GuestReadFail> { 72 | let cpu = unsafe { &mut *crate::sys::get_cpu() }; 73 | 74 | self.guest_type 75 | .get_or_try_init(|| T::read_from_guest(cpu, self.pointer).map(Box::new)) 76 | .map(|x| &**x) // &Box -> &T 77 | } 78 | 79 | /// Reads the value from the guest, replacing it if any exists. 80 | pub fn update(&mut self) { 81 | self.clear_cache(); 82 | self.read().unwrap(); 83 | } 84 | 85 | /// Clear the cached value, if any exists. 86 | pub fn clear_cache(&mut self) { 87 | self.guest_type = OnceCell::new(); 88 | } 89 | 90 | /// Returns a reference to the cached value if one exists. 91 | pub fn get_cached(&self) -> Option<&T> { 92 | self.guest_type.get().map(Box::as_ref) 93 | } 94 | 95 | /// Creates a copy of the pointer offset by N items. 96 | /// 97 | /// **Note:** Similar to normal pointer arithmatic the actual value of the offset 98 | /// will be multiplied by the size of the object. 99 | pub fn offset(&self, off: usize) -> Self { 100 | let size = 101 | T::guest_size().expect("Attempted to offset an unsized GuestType") as target_ptr_t; 102 | GuestPtr { 103 | pointer: self.pointer + (size * (off as target_ptr_t)), 104 | guest_type: OnceCell::new(), 105 | } 106 | } 107 | 108 | /// Creates a copy of the pointer offset by N bytes. 109 | pub fn offset_bytes(&self, bytes: usize) -> Self { 110 | GuestPtr { 111 | pointer: self.pointer + (bytes as target_ptr_t), 112 | guest_type: OnceCell::new(), 113 | } 114 | } 115 | 116 | /// Casts the GuestPtr to another type of GuestPtr 117 | pub fn cast(&self) -> GuestPtr { 118 | GuestPtr { 119 | pointer: self.pointer, 120 | guest_type: OnceCell::new(), 121 | } 122 | } 123 | 124 | /// Write to the GuestPtr, with all modifications flushed at the end of the scope of 125 | /// the function provided to `write`. 126 | pub fn write(&mut self, func: impl FnOnce(&mut T)) -> Result<(), GuestWriteFail> { 127 | if self.guest_type.get().is_none() { 128 | self.read().unwrap(); 129 | } 130 | 131 | let mut inner = self.guest_type.get_mut(); 132 | let inner = inner.as_mut().unwrap(); 133 | 134 | func(inner); 135 | 136 | let cpu = unsafe { &mut *crate::sys::get_cpu() }; 137 | inner.write_to_guest(cpu, self.pointer) 138 | } 139 | } 140 | 141 | impl Deref for GuestPtr { 142 | type Target = T; 143 | 144 | fn deref(&self) -> &Self::Target { 145 | self.read().unwrap(); 146 | self.get_cached() 147 | .expect("Failed to read cached value from GuestPtr") 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /panda-rs/src/syscall_injection/syscall_future.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::Future, 3 | pin::Pin, 4 | sync::{ 5 | atomic::{AtomicBool, AtomicU64, Ordering}, 6 | Arc, 7 | }, 8 | task::{Context, Poll}, 9 | }; 10 | 11 | use super::arch::{SYSCALL_ARGS, SYSCALL_NUM_REG, SYSCALL_RET}; 12 | use super::{IntoSyscallArgs, SyscallArgs, ThreadId}; 13 | use crate::regs; 14 | 15 | use dashmap::DashMap; 16 | use lazy_static::lazy_static; 17 | use once_cell::sync::OnceCell; 18 | use panda_sys::{get_cpu, target_ulong, CPUState}; 19 | 20 | pub(crate) struct SyscallFuture { 21 | ret_val: Arc>, 22 | } 23 | 24 | // write all the syscall arguments to their corresponding registers 25 | fn set_syscall_args(cpu: &mut CPUState, args: SyscallArgs) { 26 | for (storage_location, arg) in SYSCALL_ARGS.iter().copied().zip(args.iter_args()) { 27 | storage_location.write(cpu, arg); 28 | } 29 | } 30 | 31 | lazy_static! { 32 | static ref LAST_INJECTED_SYSCALL: DashMap = DashMap::new(); 33 | } 34 | 35 | pub(crate) fn last_injected_syscall() -> target_ulong { 36 | LAST_INJECTED_SYSCALL 37 | .get(&ThreadId::current()) 38 | .map(|num| num.load(Ordering::SeqCst) as target_ulong) 39 | .unwrap_or_else(|| { 40 | log::warn!("No syscall num found for thread {:?}", ThreadId::current()); 41 | 0xBADCA11 42 | }) 43 | } 44 | 45 | fn set_syscall_num(cpu: &mut CPUState, num: target_ulong) { 46 | LAST_INJECTED_SYSCALL 47 | .entry(ThreadId::current()) 48 | .or_default() 49 | .store(num as u64, Ordering::SeqCst); 50 | regs::set_reg(cpu, SYSCALL_NUM_REG, num); 51 | } 52 | 53 | /// Perform a system call in the guest. Should only be run within an injector being 54 | /// run by [`run_injector`](crate::syscall_injection::run_injector) 55 | pub async fn syscall(num: target_ulong, args: impl IntoSyscallArgs) -> target_ulong { 56 | log::trace!("Injecting syscall {}", num); 57 | let cpu = unsafe { &mut *get_cpu() }; 58 | 59 | let saved_sp = regs::get_reg(cpu, regs::reg_sp()); 60 | 61 | #[cfg(feature = "i386")] 62 | let saved_bp = regs::get_reg(cpu, regs::Reg::EBP); 63 | 64 | // Setup the system call 65 | set_syscall_num(cpu, num); 66 | set_syscall_args(cpu, args.into_syscall_args().await); 67 | 68 | // Wait until the system call has returned to get the return value 69 | let ret = Pin::new(&mut SyscallFuture { 70 | ret_val: Arc::new(OnceCell::new()), 71 | }) 72 | .await; 73 | 74 | log::trace!("Injected syscall {} returned {}", num, ret); 75 | 76 | regs::set_reg(cpu, regs::reg_sp(), saved_sp); 77 | 78 | #[cfg(feature = "i386")] 79 | regs::set_reg(cpu, regs::Reg::EBP, saved_bp); 80 | 81 | ret 82 | } 83 | 84 | /// Perform a system call in the guest. Should only be run within an injector being 85 | /// run by [`run_injector`](crate::syscall_injection::run_injector). Registers will 86 | /// not be restored after this syscall has been ran. 87 | pub async fn syscall_no_return(num: target_ulong, args: impl IntoSyscallArgs) -> ! { 88 | log::trace!("syscall_no_return num={}", num); 89 | let cpu = unsafe { &mut *get_cpu() }; 90 | 91 | // Setup the system call 92 | set_syscall_num(cpu, num); 93 | set_syscall_args(cpu, args.into_syscall_args().await); 94 | 95 | bail_no_restore_regs().await 96 | } 97 | 98 | /// Bail from the current injector without restoring the original registers 99 | pub async fn bail_no_restore_regs() -> ! { 100 | log::trace!("Bailing without restoring syscall args"); 101 | INJECTOR_BAIL.store(true, Ordering::SeqCst); 102 | 103 | std::future::pending().await 104 | } 105 | 106 | pub(crate) static INJECTOR_BAIL: AtomicBool = AtomicBool::new(false); 107 | pub(crate) static WAITING_FOR_SYSCALL: AtomicBool = AtomicBool::new(false); 108 | 109 | // Maps ASID to RET_SLOT 110 | lazy_static! { 111 | static ref RET_SLOT: DashMap>> = DashMap::new(); 112 | } 113 | 114 | pub(crate) fn set_ret_value(cpu: &mut CPUState) { 115 | if let Some(ret_slot) = RET_SLOT.get(&ThreadId::current()) { 116 | if ret_slot.set(regs::get_reg(cpu, SYSCALL_RET)).is_err() { 117 | println!("WARNING: Attempted to double-set syscall return value"); 118 | } 119 | 120 | log::trace!( 121 | "Return value set to {:#x?}", 122 | regs::get_reg(cpu, SYSCALL_RET) 123 | ); 124 | } 125 | } 126 | 127 | impl Future for SyscallFuture { 128 | type Output = target_ulong; 129 | 130 | fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { 131 | match self.ret_val.get() { 132 | // if the return value of the syscall has already been set, then this 133 | // future can return 134 | Some(ret_val) => Poll::Ready(*ret_val), 135 | 136 | // if the return value hasn't been set, set this future as the next 137 | // return value to set 138 | None => { 139 | let ret_val = Arc::clone(&self.ret_val); 140 | 141 | WAITING_FOR_SYSCALL.store(true, Ordering::SeqCst); 142 | RET_SLOT.insert(ThreadId::current(), ret_val); 143 | 144 | Poll::Pending 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /panda-rs/src/panda_arg.rs: -------------------------------------------------------------------------------- 1 | use crate::sys::*; 2 | use std::ffi::{CStr, CString}; 3 | 4 | /// A trait for allowing conversion to and from PANDA command line arguments. Should only be used 5 | /// with the provided derive macro. 6 | /// 7 | /// ### Example 8 | /// ```rust 9 | /// use panda::prelude::*; 10 | /// 11 | /// #[derive(PandaArgs)] 12 | /// #[name = "my_plugin"] 13 | /// struct MyPluginArgs { 14 | /// file: String, 15 | /// } 16 | /// 17 | /// let args = MyPluginArgs::from_panda_args(); 18 | /// ``` 19 | pub trait PandaArgs { 20 | const PLUGIN_NAME: &'static str; 21 | 22 | /// Get an instance of this struct from the PANDA arguments for the given plugin 23 | fn from_panda_args() -> Self; 24 | 25 | /// Convert this struct into a string to be passed via PANDA command line arguments. 26 | /// 27 | /// Used internally by `Panda::plugin_args`. 28 | fn to_panda_args_str(&self) -> std::string::String; 29 | 30 | /// Convert this struct into a set of argument pairs to be passed to PANDA 31 | /// 32 | /// Used internally by `plugin_require` 33 | fn to_panda_args(&self) -> Vec<(&'static str, std::string::String)>; 34 | } 35 | 36 | /// A wrapper trait for getting a PANDA argument as a given type. Used internally by the `PandaArgs` 37 | /// derive macro. 38 | pub trait GetPandaArg { 39 | fn get_panda_arg( 40 | args: *mut panda_arg_list, 41 | name: &str, 42 | default: Self, 43 | description: &str, 44 | required: bool, 45 | ) -> Self; 46 | } 47 | 48 | impl GetPandaArg for bool { 49 | fn get_panda_arg( 50 | args: *mut panda_arg_list, 51 | name: &str, 52 | _default: Self, 53 | description: &str, 54 | required: bool, 55 | ) -> Self { 56 | let name = CString::new(name).unwrap(); 57 | let desc = CString::new(description).unwrap(); 58 | 59 | unsafe { 60 | if required { 61 | panda_parse_bool_req(args, name.as_ptr(), desc.as_ptr()) 62 | } else { 63 | panda_parse_bool_opt(args, name.as_ptr(), desc.as_ptr()) 64 | } 65 | } 66 | } 67 | } 68 | 69 | impl GetPandaArg for u64 { 70 | fn get_panda_arg( 71 | args: *mut panda_arg_list, 72 | name: &str, 73 | default: Self, 74 | description: &str, 75 | required: bool, 76 | ) -> Self { 77 | let name = CString::new(name).unwrap(); 78 | let desc = CString::new(description).unwrap(); 79 | 80 | unsafe { 81 | if required { 82 | panda_parse_uint64_req(args, name.as_ptr(), desc.as_ptr()) 83 | } else { 84 | panda_parse_uint64_opt(args, name.as_ptr(), default, desc.as_ptr()) 85 | } 86 | } 87 | } 88 | } 89 | 90 | impl GetPandaArg for u32 { 91 | fn get_panda_arg( 92 | args: *mut panda_arg_list, 93 | name: &str, 94 | default: Self, 95 | description: &str, 96 | required: bool, 97 | ) -> Self { 98 | let name = CString::new(name).unwrap(); 99 | let desc = CString::new(description).unwrap(); 100 | 101 | unsafe { 102 | if required { 103 | panda_parse_uint32_req(args, name.as_ptr(), desc.as_ptr()) 104 | } else { 105 | panda_parse_uint32_opt(args, name.as_ptr(), default, desc.as_ptr()) 106 | } 107 | } 108 | } 109 | } 110 | 111 | impl GetPandaArg for f64 { 112 | fn get_panda_arg( 113 | args: *mut panda_arg_list, 114 | name: &str, 115 | default: Self, 116 | description: &str, 117 | required: bool, 118 | ) -> Self { 119 | let name = CString::new(name).unwrap(); 120 | let desc = CString::new(description).unwrap(); 121 | 122 | unsafe { 123 | if required { 124 | panda_parse_double_req(args, name.as_ptr(), desc.as_ptr()) 125 | } else { 126 | panda_parse_double_opt(args, name.as_ptr(), default, desc.as_ptr()) 127 | } 128 | } 129 | } 130 | } 131 | 132 | impl GetPandaArg for f32 { 133 | fn get_panda_arg( 134 | args: *mut panda_arg_list, 135 | name: &str, 136 | default: Self, 137 | description: &str, 138 | required: bool, 139 | ) -> Self { 140 | let name = CString::new(name).unwrap(); 141 | let desc = CString::new(description).unwrap(); 142 | 143 | unsafe { 144 | if required { 145 | panda_parse_double_req(args, name.as_ptr(), desc.as_ptr()) as f32 146 | } else { 147 | panda_parse_double_opt(args, name.as_ptr(), default as f64, desc.as_ptr()) as f32 148 | } 149 | } 150 | } 151 | } 152 | 153 | impl GetPandaArg for std::string::String { 154 | fn get_panda_arg( 155 | args: *mut panda_arg_list, 156 | name: &str, 157 | default: Self, 158 | description: &str, 159 | required: bool, 160 | ) -> Self { 161 | let name = CString::new(name).unwrap(); 162 | let desc = CString::new(description).unwrap(); 163 | let default = CString::new(default).unwrap(); 164 | 165 | unsafe { 166 | if required { 167 | CStr::from_ptr(panda_parse_string_req(args, name.as_ptr(), desc.as_ptr())) 168 | .to_str() 169 | .unwrap() 170 | .to_owned() 171 | } else { 172 | CStr::from_ptr(panda_parse_string_opt( 173 | args, 174 | name.as_ptr(), 175 | default.as_ptr(), 176 | desc.as_ptr(), 177 | )) 178 | .to_str() 179 | .unwrap() 180 | .to_owned() 181 | } 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /panda-macros/src/osi_type.rs: -------------------------------------------------------------------------------- 1 | use darling::ast::Data; 2 | use darling::{FromDeriveInput, FromField, FromVariant}; 3 | 4 | use proc_macro2::TokenStream; 5 | use quote::quote; 6 | 7 | #[derive(FromDeriveInput)] 8 | #[darling(attributes(osi))] 9 | pub(crate) struct OsiTypeInput { 10 | ident: syn::Ident, 11 | data: Data, 12 | 13 | type_name: String, 14 | } 15 | 16 | #[allow(dead_code)] 17 | #[derive(FromVariant, Clone)] 18 | struct OsiTypeVariant { 19 | ident: syn::Ident, 20 | discriminant: Option, 21 | fields: darling::ast::Fields, 22 | } 23 | 24 | #[derive(FromField, Clone)] 25 | struct OsiTypeVariantField {} 26 | 27 | #[derive(FromField, Clone)] 28 | #[darling(attributes(osi))] 29 | struct OsiTypeField { 30 | ident: Option, 31 | ty: syn::Type, 32 | 33 | #[darling(default)] 34 | rename: Option, 35 | 36 | #[darling(default)] 37 | osi_type: bool, 38 | } 39 | 40 | impl OsiTypeInput { 41 | pub(crate) fn to_tokens(self) -> TokenStream { 42 | let method_dispatcher = quote::format_ident!("{}MethodDispatcher", self.ident); 43 | let self_ident = &self.ident; 44 | 45 | let type_name = &self.type_name; 46 | 47 | let self_struct = self.data.clone().take_struct().unwrap(); 48 | let read_fields = self_struct.fields.iter().map(|field| { 49 | let ident = &field.ident; 50 | let ty = &field.ty; 51 | 52 | let field_name = field.rename 53 | .clone() 54 | .or_else(|| ident.as_ref().map(ToString::to_string)) 55 | .unwrap(); 56 | 57 | let read_func = if field.osi_type { 58 | quote! { 59 | <#ty as ::panda::plugins::cosi::OsiType>::osi_read 60 | } 61 | } else { 62 | quote! { 63 | ::panda::mem::read_guest_type::<#ty> 64 | } 65 | }; 66 | 67 | quote! { 68 | let __field_offset = { 69 | static FIELD_OFFSET: ::panda::once_cell::sync::OnceCell<::panda::prelude::target_long> 70 | = ::panda::once_cell::sync::OnceCell::new(); 71 | 72 | *FIELD_OFFSET.get_or_init(|| { 73 | __osi_type.offset_of(#field_name) 74 | }) 75 | }; 76 | 77 | let #ident = #read_func ( 78 | __cpu, __base_ptr + (__field_offset as ::panda::prelude::target_ptr_t) 79 | )?; 80 | } 81 | }); 82 | 83 | let read_field_methods = self_struct.fields.iter().map(|field| { 84 | let ident = &field.ident; 85 | let ty = &field.ty; 86 | 87 | let field_name = field.rename 88 | .clone() 89 | .or_else(|| ident.as_ref().map(ToString::to_string)) 90 | .unwrap(); 91 | 92 | let read_func = if field.osi_type { 93 | quote! { 94 | <#ty as ::panda::plugins::cosi::OsiType>::osi_read 95 | } 96 | } else { 97 | quote! { 98 | ::panda::mem::read_guest_type::<#ty> 99 | } 100 | }; 101 | 102 | quote! { 103 | pub(crate) fn #ident(&self, __cpu: &mut CPUState) -> Result<#ty, ::panda::GuestReadFail> { 104 | let __osi_type = ::panda::plugins::cosi::type_from_name(#type_name) 105 | .ok_or(::panda::GuestReadFail)?; 106 | 107 | let is_per_cpu = self.1; 108 | let __base_ptr = if is_per_cpu { 109 | ::panda::plugins::cosi::find_per_cpu_address(__cpu, self.0)? 110 | } else { 111 | static SYMBOL_ADDR: ::panda::once_cell::sync::OnceCell<::panda::prelude::target_ptr_t> 112 | = ::panda::once_cell::sync::OnceCell::new(); 113 | 114 | *SYMBOL_ADDR.get_or_init(|| { 115 | ::panda::plugins::cosi::symbol_addr_from_name( 116 | self.0 117 | ) 118 | }) 119 | }; 120 | 121 | #read_func ( 122 | __cpu, __base_ptr + (__osi_type.offset_of(#field_name) as ::panda::prelude::target_ptr_t) 123 | ) 124 | } 125 | } 126 | }); 127 | 128 | let field_names = self_struct.fields.iter().map(|field| &field.ident); 129 | 130 | quote! { 131 | #[doc(hidden)] 132 | pub struct #method_dispatcher(&'static str, bool); 133 | 134 | impl #method_dispatcher { 135 | pub const fn new(symbol: &'static str, is_per_cpu: bool) -> Self { 136 | Self(symbol, is_per_cpu) 137 | } 138 | 139 | #( 140 | #read_field_methods 141 | )* 142 | } 143 | 144 | impl ::panda::plugins::cosi::OsiType for #self_ident { 145 | type MethodDispatcher = #method_dispatcher; 146 | 147 | fn osi_read( 148 | __cpu: &mut ::panda::prelude::CPUState, 149 | __base_ptr: ::panda::prelude::target_ptr_t, 150 | ) -> Result { 151 | let __osi_type = ::panda::plugins::cosi::type_from_name(#type_name) 152 | .ok_or(::panda::GuestReadFail)?; 153 | 154 | 155 | #( 156 | #read_fields 157 | )* 158 | 159 | Ok(Self { #( #field_names ),* }) 160 | } 161 | } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /panda-rs/src/guest_ptr/impls.rs: -------------------------------------------------------------------------------- 1 | use super::{GuestAlign, GuestPtr, GuestReadFail, GuestWriteFail}; 2 | use crate::prelude::*; 3 | use crate::{enums::Endian, mem::*, GuestType, ARCH_ENDIAN}; 4 | 5 | use std::alloc::Layout; 6 | 7 | macro_rules! impl_for_num { 8 | ($($ty:ty),*) => { 9 | $( 10 | impl GuestType for $ty { 11 | fn guest_layout() -> Option { 12 | Layout::from_size_align( 13 | core::mem::size_of::<$ty>(), 14 | <$ty as GuestAlign>::ALIGN 15 | ).ok() 16 | } 17 | 18 | fn read_from_guest(cpu: &mut CPUState, ptr: target_ptr_t) -> Result { 19 | let mut bytes = [0u8; core::mem::size_of::<$ty>()]; 20 | virtual_memory_read_into(cpu, ptr, &mut bytes).or(Err(GuestReadFail))?; 21 | 22 | Ok(match ARCH_ENDIAN { 23 | Endian::Big => <$ty>::from_be_bytes(bytes), 24 | Endian::Little => <$ty>::from_le_bytes(bytes), 25 | }) 26 | } 27 | 28 | fn read_from_guest_phys(ptr: target_ptr_t) -> Result { 29 | let mut bytes = [0u8; core::mem::size_of::<$ty>()]; 30 | physical_memory_read_into(ptr, &mut bytes).or(Err(GuestReadFail))?; 31 | 32 | Ok(match ARCH_ENDIAN { 33 | Endian::Big => <$ty>::from_be_bytes(bytes), 34 | Endian::Little => <$ty>::from_le_bytes(bytes), 35 | }) 36 | } 37 | 38 | fn write_to_guest(&self, cpu: &mut CPUState, ptr: target_ptr_t) -> Result<(), GuestWriteFail> { 39 | let bytes = match ARCH_ENDIAN { 40 | Endian::Big => <$ty>::to_be_bytes(*self), 41 | Endian::Little => <$ty>::to_le_bytes(*self), 42 | }; 43 | 44 | virtual_memory_write(cpu, ptr, &bytes); 45 | 46 | Ok(()) 47 | } 48 | 49 | fn write_to_guest_phys(&self, ptr: target_ptr_t) -> Result<(), GuestWriteFail> { 50 | let bytes = match ARCH_ENDIAN { 51 | Endian::Big => <$ty>::to_be_bytes(*self), 52 | Endian::Little => <$ty>::to_le_bytes(*self), 53 | }; 54 | 55 | physical_memory_write(ptr, &bytes); 56 | 57 | Ok(()) 58 | } 59 | } 60 | )* 61 | }; 62 | } 63 | 64 | impl_for_num!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64); 65 | 66 | impl GuestType for GuestPtr { 67 | fn guest_layout() -> Option { 68 | target_ptr_t::guest_layout() 69 | } 70 | 71 | fn read_from_guest(cpu: &mut CPUState, ptr: target_ptr_t) -> Result { 72 | target_ptr_t::read_from_guest(cpu, ptr).map(Self::from) 73 | } 74 | 75 | fn write_to_guest(&self, cpu: &mut CPUState, ptr: target_ptr_t) -> Result<(), GuestWriteFail> { 76 | self.pointer.write_to_guest(cpu, ptr) 77 | } 78 | 79 | fn read_from_guest_phys(ptr: target_ptr_t) -> Result { 80 | target_ptr_t::read_from_guest_phys(ptr).map(Self::from) 81 | } 82 | 83 | fn write_to_guest_phys(&self, ptr: target_ptr_t) -> Result<(), GuestWriteFail> { 84 | self.pointer.write_to_guest_phys(ptr) 85 | } 86 | } 87 | 88 | fn padding_needed_for(layout: &Layout, align: usize) -> usize { 89 | let len = layout.size(); 90 | 91 | let len_rounded_up = len.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1); 92 | len_rounded_up.wrapping_sub(len) 93 | } 94 | 95 | fn padded_size(layout: &Layout) -> usize { 96 | layout.size() + padding_needed_for(&layout, layout.align()) 97 | } 98 | 99 | fn repeat(layout: &Layout, n: usize) -> Layout { 100 | let alloc_size = padded_size(layout) 101 | .checked_mul(n) 102 | .expect("Layout of guest array overflow"); 103 | 104 | Layout::from_size_align(alloc_size, layout.align()).expect("Layout of guest array invalid") 105 | } 106 | 107 | impl GuestType for [T; N] { 108 | fn guest_layout() -> Option { 109 | T::guest_layout().map(|layout| repeat(&layout, N)) 110 | } 111 | 112 | fn read_from_guest(cpu: &mut CPUState, ptr: target_ptr_t) -> Result { 113 | let padded_size = padded_size( 114 | &T::guest_layout().expect("Cannot read array of unsized types from guest."), 115 | ); 116 | 117 | array_init::from_iter( 118 | (ptr..) 119 | .step_by(padded_size) 120 | .take(N) 121 | .filter_map(|ptr| T::read_from_guest(cpu, ptr).ok()), 122 | ) 123 | .ok_or(GuestReadFail) 124 | } 125 | 126 | fn write_to_guest(&self, cpu: &mut CPUState, ptr: target_ptr_t) -> Result<(), GuestWriteFail> { 127 | let padded_size = padded_size( 128 | &T::guest_layout().expect("Cannot write array of unsized types to the guest."), 129 | ); 130 | 131 | for (ptr, item) in (ptr..).step_by(padded_size).zip(self.iter()) { 132 | item.write_to_guest(cpu, ptr)?; 133 | } 134 | 135 | Ok(()) 136 | } 137 | 138 | fn read_from_guest_phys(ptr: target_ptr_t) -> Result { 139 | let padded_size = padded_size( 140 | &T::guest_layout().expect("Cannot read array of unsized types from guest."), 141 | ); 142 | 143 | array_init::from_iter( 144 | (ptr..) 145 | .step_by(padded_size) 146 | .take(N) 147 | .filter_map(|ptr| T::read_from_guest_phys(ptr).ok()), 148 | ) 149 | .ok_or(GuestReadFail) 150 | } 151 | 152 | fn write_to_guest_phys(&self, ptr: target_ptr_t) -> Result<(), GuestWriteFail> { 153 | let padded_size = padded_size( 154 | &T::guest_layout().expect("Cannot write array of unsized types to the guest."), 155 | ); 156 | 157 | for (ptr, item) in (ptr..).step_by(padded_size).zip(self.iter()) { 158 | item.write_to_guest_phys(ptr)?; 159 | } 160 | 161 | Ok(()) 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /panda-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! panda-rs is a set of Rust bindings for PANDA. 2 | //! 3 | //! **The following are provided:** 4 | //! * Callbacks to various PANDA events in the form of attribute macros 5 | //! * Callbacks for when guest syscalls happen 6 | //! * Bindings to various core PANDA plugins (hooks2, osi, etc) 7 | //! * Safe bindings to the core PANDA API 8 | //! * An API for driving PANDA via libpanda 9 | //! * Access to raw PANDA and QEMU API bindings via panda_sys 10 | //! 11 | //! ### Feature flags: 12 | //! 13 | //! * `libpanda` - enable libpanda mode. This is used to allow for compiling as a binary that links 14 | //! against libpanda, for pypanda-style use. 15 | //! 16 | //! #### Architecture-specific features 17 | //! 18 | //! PANDA supports multiple architectures, but requires plugins to be compiled for each 19 | //! architecture. In order to target a specific guest arch, use exactly one of the following: 20 | //! `x86_64`, `i386`, `arm`, `aarch64`, `mips`, `mipsel`, `mips64`, `ppc` 21 | //! 22 | //! Typically PANDA plugins forward each of these features in their Cargo.toml: 23 | //! 24 | //! ```toml 25 | //! [features] 26 | //! x86_64 = ["panda/x86_64"] 27 | //! i386 = ["panda/i386"] 28 | //! # ... 29 | //! ``` 30 | //! 31 | //! ### Callbacks 32 | //! 33 | //! `panda-rs` makes extensive use of callbacks for handling analyses on various events. To use 34 | //! callbacks, you simply apply the callback's attribute to any functions which should be called 35 | //! for the given callback. In order to use a callback in a PANDA plugin (not to be confused with 36 | //! an application that uses libpanda), one function must be marked [`#[panda::init]`](init), 37 | //! otherwise the plugin will not work in PANDA. 38 | //! 39 | //! Callbacks come in two forms: free form functions (which use the attribute macros) 40 | //! mentioned above) and closure callbacks, which use the [`Callback`] API. 41 | //! 42 | //! ### libpanda Mode 43 | //! 44 | //! PANDA also offers a dynamic library (libpanda). panda-rs allows linking against libpanda 45 | //! instead of linking as a PANDA plugin. This creates a executable that requires libpanda to run. 46 | //! To compile in libpanda mode, make sure the `PANDA_PATH` environment variable is set to your 47 | //! PANDA `build` folder. 48 | //! 49 | //! ## Helpful Links 50 | //! 51 | //! | Important | Popular Callbacks | Popular Plugins | 52 | //! |:---------:|:-----------------------:|:---------------:| 53 | //! | [`init`] | [`before_block_exec`] | [`osi`](plugins::osi) | 54 | //! | [`Panda`] | [`virt_mem_after_read`] | [`proc_start_linux`](plugins::proc_start_linux) | 55 | //! | [`mod@hook`] | [`virt_mem_after_write`]| [`hooks2`](plugins::hooks2) | 56 | //! | [`on_sys`]| [`asid_changed`] | [`guest_plugin_manager`](plugins::guest_plugin_manager) | 57 | //! | [`uninit`]| [`before_block_exec_invalidate_opt`] || 58 | //! | [`regs`] | [`insn_translate`] || 59 | //! | [`PandaArgs`] | [`insn_exec`] || 60 | #![cfg_attr(doc_cfg, feature(doc_cfg))] 61 | 62 | /// Raw bindings to the PANDA API 63 | #[doc(inline)] 64 | pub use panda_sys as sys; 65 | //pub use panda_macros::*; 66 | 67 | /// PANDA callback macros 68 | #[doc(hidden)] 69 | pub use panda_macros as cbs; 70 | 71 | #[doc(inline)] 72 | pub use plugins::hooks::hook; 73 | 74 | #[doc(hidden)] 75 | pub use {inventory, lazy_static, once_cell, paste}; 76 | 77 | #[doc(hidden)] 78 | extern crate self as panda; 79 | 80 | /// Helpers and constants for interacting with various ABIs 81 | pub mod abi; 82 | 83 | /// Callbacks for linux syscalls (from syscalls2) 84 | pub mod on_sys; 85 | 86 | /// Safe wrappers for the libpanda API for helping create and manage an instance of the PANDA API 87 | mod library_mode; 88 | pub use library_mode::*; 89 | 90 | mod guest_ptr; 91 | pub use guest_ptr::*; 92 | 93 | /// Safe wrappers for the PANDA API 94 | mod api; 95 | pub use api::*; 96 | 97 | /// Architecture-specific definitions 98 | mod arch; 99 | pub use arch::*; 100 | 101 | mod error; 102 | pub use error::*; 103 | 104 | /// Event-based callbacks, for both VM events (e.g. translation of a basic block) and PANDA events (e.g. plugin init) 105 | mod callbacks; 106 | pub use callbacks::*; 107 | 108 | mod init_return; 109 | pub use init_return::InitReturn; 110 | 111 | /// Helpers for getting plugin arguments from panda 112 | pub mod panda_arg; 113 | 114 | #[doc(inline)] 115 | pub use panda_arg::PandaArgs; 116 | 117 | pub mod enums; 118 | pub mod plugins; 119 | pub mod taint; 120 | 121 | #[cfg_attr(doc_cfg, doc(cfg(feature = "syscall-injection")))] 122 | #[cfg(all(feature = "syscall-injection", not(feature = "ppc")))] 123 | pub mod syscall_injection; 124 | 125 | pub use enums::arch::*; 126 | 127 | /// A set of types PANDA frequently requires but have a low likelihood of clashing with 128 | /// other types you import, for use as a wildcard import. 129 | /// 130 | /// ## Example 131 | /// 132 | /// ``` 133 | /// use panda::prelude::*; 134 | /// ``` 135 | pub mod prelude { 136 | pub use crate::panda_arg::PandaArgs; 137 | pub use crate::regs::SyscallPc; 138 | pub use crate::sys::target_long; 139 | pub use crate::sys::target_pid_t; 140 | pub use crate::sys::target_ptr_t; 141 | pub use crate::sys::target_ulong; 142 | pub use crate::sys::CPUState; 143 | pub use crate::sys::TranslationBlock; 144 | pub use crate::Panda; 145 | pub use crate::PluginHandle; 146 | pub use panda_macros::PandaArgs; 147 | } 148 | 149 | #[cfg(not(feature = "ppc"))] 150 | pub use panda_macros::{on_all_sys_enter, on_all_sys_return}; 151 | 152 | // callbacks 153 | pub use panda_macros::{ 154 | after_block_exec, after_block_translate, after_cpu_exec_enter, after_insn_exec, 155 | after_insn_translate, after_loadvm, after_machine_init, asid_changed, before_block_exec, 156 | before_block_exec_invalidate_opt, before_block_translate, before_cpu_exec_exit, 157 | before_handle_exception, before_handle_interrupt, before_loadvm, before_tcg_codegen, 158 | cpu_restore_state, during_machine_init, end_block_exec, guest_hypercall, hd_read, hd_write, 159 | hook, init, insn_exec, insn_translate, main_loop_wait, mmio_after_read, mmio_before_write, 160 | monitor, on_mmap_updated, on_process_end, on_process_start, on_rec_auxv, on_thread_end, 161 | on_thread_start, phys_mem_after_read, phys_mem_after_write, phys_mem_before_read, 162 | phys_mem_before_write, pre_shutdown, replay_after_dma, replay_before_dma, replay_handle_packet, 163 | replay_hd_transfer, replay_net_transfer, replay_serial_read, replay_serial_receive, 164 | replay_serial_send, replay_serial_write, start_block_exec, top_loop, unassigned_io_read, 165 | unassigned_io_write, uninit, virt_mem_after_read, virt_mem_after_write, virt_mem_before_read, 166 | virt_mem_before_write, GuestType, 167 | }; 168 | -------------------------------------------------------------------------------- /panda-rs/src/callbacks/ppp_closures.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | ffi::c_void, 4 | sync::{ 5 | atomic::{AtomicU64, Ordering}, 6 | Mutex, 7 | }, 8 | }; 9 | 10 | /// A reference to a given callback slot which can be used to install, 11 | /// enable, disable, or otherwise reference, a closure-based callback for 12 | /// PANDA plugin-to-plugin ("PPP") callbacks. 13 | /// 14 | /// Since this is a reference to a callback slot and does not include storage 15 | /// for the callback itself, it can be trivially copied, as well as included in 16 | /// the callback itself (for the purposes of enabling/disabling). 17 | /// 18 | /// In order to actually install the callback, you will need to import the trait 19 | /// for the specific plugin whose callbacks you want to run. In the example below, 20 | /// the [`ProcStartLinuxCallbacks`] trait provides the [`on_rec_auxv`] method in 21 | /// order to add the callback. 22 | /// 23 | /// [`ProcStartLinuxCallbacks`]: crate::plugins::proc_start_linux::ProcStartLinuxCallbacks 24 | /// [`on_rec_auxv`]: crate::plugins::proc_start_linux::ProcStartLinuxCallbacks::on_rec_auxv 25 | /// 26 | /// ## Example 27 | /// 28 | /// ``` 29 | /// use panda::plugins::proc_start_linux::ProcStartLinuxCallbacks; 30 | /// use panda::PppCallback; 31 | /// use panda::prelude::*; 32 | /// 33 | /// PppCallback::new().on_rec_auxv(|_, _, auxv| { 34 | /// dbg!(auxv); 35 | /// }); 36 | /// 37 | /// Panda::new().generic("x86_64").replay("test").run(); 38 | /// ``` 39 | /// 40 | /// The above installs a callback to print out the contents of the auxillary vector 41 | /// using [`dbg`] whenever a new process is spawned in the guest. 42 | /// 43 | /// Example output: 44 | /// 45 | /// ```no_run 46 | /// ... 47 | /// [panda-rs/examples/closures.rs:18] auxv = AuxvValues { 48 | /// argc: 3, 49 | /// argv_ptr_ptr: 0x7fffffffebb8, 50 | /// arg_ptr: [ 51 | /// 0x7fffffffede6, 52 | /// 0x7fffffffedeb, 53 | /// 0x7fffffffedee, 54 | /// ], 55 | /// argv: [ 56 | /// "bash", 57 | /// "-c", 58 | /// "echo test2", 59 | /// ], 60 | /// envc: 20, 61 | /// env_ptr_ptr: 0x7fffffffebd8, 62 | /// env_ptr: [ 63 | /// 0x7fffffffedf9, 64 | /// 0x7fffffffee04, 65 | /// // ... 66 | /// 0x7fffffffefc2, 67 | /// 0x7fffffffefe2, 68 | /// ], 69 | /// envp: [ 70 | /// "LS_COLORS=", 71 | /// "LESSCLOSE=/usr/bin/lesspipe %s %s", 72 | /// "LANG=C.UTwcap2: 0,F-8", 73 | /// "INVOCATION_ID=0b2d5ea4eb39435388bf53e507047b2f", 74 | /// "XDG_SESSION_ID=1", 75 | /// "HUSHLOGIN=FALSE", 76 | /// "USER=root", 77 | /// "PWD=/root", 78 | /// "HOME=/root", 79 | /// "JOURNAL_STREAM=9:16757", 80 | /// "XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop", 81 | /// "MAIL=/var/mail/root", 82 | /// "SHELL=/bin/bash", 83 | /// "TERM=vt220", 84 | /// "SHLVL=1", 85 | /// "LOGNAME=root", 86 | /// "XDG_RUNTIME_DIR=/run/user/0", 87 | /// "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin", 88 | /// "LESSOPEN=| /usr/bin/lesspipe %s", 89 | /// "_=/bin/bash", 90 | /// ], 91 | /// execfn_ptr: 0x7fffffffefee, 92 | /// execfn: "/bin/bash", 93 | /// phdr: 0x555555554040, 94 | /// entry: 0x555555585520, 95 | /// ehdr: 0x7ffff7ffa000, 96 | /// // ... 97 | /// } 98 | /// Replay completed successfully 99 | /// Exiting cpu_handle_exception loop 100 | /// ``` 101 | /// 102 | /// ## Note 103 | /// 104 | /// Callback closures must have a `'static` lifetime in order to live past the end of the 105 | /// function. This means that the only references a callback can include are references 106 | /// to static variables or leaked objects on the heap (See [`Box::leak`] for more info). 107 | /// 108 | /// If you'd like to reference shared data without leaking, this can be accomplished via 109 | /// reference counting. See [`Arc`] for more info. If you want to capture data owned 110 | /// by the current function without sharing it, you can mark your closure as `move` in 111 | /// order to move all the variables you capture into your closure. (Such as in the above 112 | /// example, where `count` is moved into the closure for modification) 113 | /// 114 | /// [`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html 115 | #[repr(transparent)] 116 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 117 | pub struct PppCallback(pub(crate) u64); 118 | 119 | static CURRENT_CALLBACK_ID: AtomicU64 = AtomicU64::new(0); 120 | 121 | impl PppCallback { 122 | /// Create a new callback slot which can then be used to install or modify 123 | /// a given callback. 124 | pub fn new() -> Self { 125 | Self(CURRENT_CALLBACK_ID.fetch_add(1, Ordering::SeqCst)) 126 | } 127 | 128 | /// Enable the callback assigned to the given slot, if any. 129 | pub fn enable(&self) { 130 | let mut callbacks = CALLBACKS.lock().unwrap(); 131 | if let Some(callback) = callbacks.get_mut(&self.0) { 132 | if !callback.is_enabled { 133 | unsafe { 134 | (callback.enable)(callback.closure_ref); 135 | } 136 | callback.is_enabled = true; 137 | } 138 | } 139 | } 140 | 141 | /// Disable the callback assigned to the given slot, if any. 142 | pub fn disable(&self) { 143 | let mut callbacks = CALLBACKS.lock().unwrap(); 144 | 145 | if let Some(callback) = callbacks.get_mut(&self.0) { 146 | if callback.is_enabled { 147 | unsafe { 148 | (callback.disable)(callback.closure_ref); 149 | } 150 | callback.is_enabled = false; 151 | } 152 | } 153 | } 154 | } 155 | 156 | lazy_static::lazy_static! { 157 | static ref CALLBACKS: Mutex> = Mutex::new(HashMap::new()); 158 | } 159 | 160 | #[doc(hidden)] 161 | pub struct InternalPppClosureCallback { 162 | pub closure_ref: *mut c_void, 163 | pub enable: unsafe fn(*mut c_void), 164 | pub disable: unsafe fn(*mut c_void), 165 | pub drop_fn: unsafe fn(*mut c_void), 166 | pub is_enabled: bool, 167 | } 168 | 169 | unsafe impl Sync for InternalPppClosureCallback {} 170 | unsafe impl Send for InternalPppClosureCallback {} 171 | 172 | #[doc(hidden)] 173 | pub unsafe fn __internal_install_ppp_closure_callback( 174 | PppCallback(id): PppCallback, 175 | mut callback: InternalPppClosureCallback, 176 | ) { 177 | (callback.enable)(callback.closure_ref); 178 | callback.is_enabled = true; 179 | 180 | let mut callbacks = CALLBACKS.lock().unwrap(); 181 | callbacks.insert(id, callback); 182 | } 183 | -------------------------------------------------------------------------------- /panda-rs/src/library_mode/qcows.rs: -------------------------------------------------------------------------------- 1 | use super::Arch; 2 | use std::path::PathBuf; 3 | use std::process::Command; 4 | 5 | #[derive(Debug)] 6 | pub struct Image<'a> { 7 | pub arch: Arch, 8 | pub os: &'a str, 9 | pub prompt: &'a str, 10 | pub cdrom: &'a str, 11 | pub snapshot: &'a str, 12 | pub default_mem: &'a str, 13 | pub url: &'a str, 14 | pub extra_args: &'a [&'a str], 15 | } 16 | 17 | pub fn get_supported_image(name: &str) -> Image<'static> { 18 | match name { 19 | /*"i386_wheezy" => Image { 20 | arch: "i386", 21 | os:"linux-32-debian:3.2.0-4-686-pae", 22 | prompt:r#"root@debian-i386:.*# "#, 23 | qcow:"wheezy_panda2.qcow2", // Backwards compatability 24 | cdrom:"ide1-cd0", 25 | snapshot:"root", 26 | default_mem:"128M", 27 | url:"https://panda-re.mit.edu/qcows/linux/debian/7.3/x86/debian_7.3_x86.qcow", 28 | extra_args:"-display none"}, 29 | 30 | "x86_64_wheezy" => Image { 31 | arch: "x86_64", 32 | os: "linux-64-debian:3.2.0-4-amd64", 33 | prompt: r#"root@debian-amd64:.*# "#, 34 | qcow="wheezy_x64.qcow2",// Backwards compatability 35 | cdrom: "ide1-cd0", 36 | snapshot: "root", 37 | default_mem: "128M", 38 | url: "https://panda-re.mit.edu/qcows/linux/debian/7.3/x86_64/debian_7.3_x86_64.qcow", 39 | extra_args: "-display none"}, 40 | 41 | "ppc_wheezy" => Image { 42 | arch: "ppc", 43 | os: "linux-64-debian:3.2.0-4-ppc-pae", 44 | prompt: r#"root@debian-powerpc:.*# "#, 45 | qcow="ppc_wheezy.qcow2",// Backwards compatability 46 | cdrom: "ide1-cd0", 47 | default_mem: "128M", 48 | snapshot: "root", 49 | url: "https://panda-re.mit.edu/qcows/linux/debian/7.3/ppc/debian_7.3_ppc.qcow", 50 | extra_args: "-display none"}, 51 | 52 | "arm_wheezy" => Image { 53 | arch: "arm", 54 | os: "linux-32-debian:3.2.0-4-versatile-arm", 55 | prompt: r#"root@debian-armel:.*# "#, 56 | qcow="arm_wheezy.qcow",// Backwards compatability 57 | cdrom: "scsi0-cd2", 58 | default_mem: "128M", 59 | snapshot: "root", 60 | url: "https://panda-re.mit.edu/qcows/linux/debian/7.3/arm/debian_7.3_arm.qcow", 61 | extra_files=["vmlinuz-3.2.0-4-versatile', 'initrd.img-3.2.0-4-versatile"], 62 | extra_args: '-display none -M versatilepb -append "root=/dev/sda1" -kernel {DOT_DIR}/vmlinuz-3.2.0-4-versatile -initrd {DOT_DIR}/initrd.img-3.2.0-4-versatile'.format(DOT_DIR=VM_DIR)}, 63 | 64 | "mips_wheezy" => Image { 65 | arch: "mips", 66 | os: "linux-64-debian:3.2.0-4-arm-pae", // XXX wrong 67 | prompt: r#"root@debian-mips:.*# "#, 68 | cdrom: "ide1-cd0", 69 | snapshot: "root", 70 | url: "https://panda-re.mit.edu/qcows/linux/debian/7.3/mips/debian_7.3_mips.qcow", 71 | default_mem: "1G", 72 | extra_files=['vmlinux-3.2.0-4-4kc-malta'], 73 | extra_args: '-M malta -kernel {DOT_DIR}/vmlinux-3.2.0-4-4kc-malta -append "root=/dev/sda1" -nographic'.format(DOT_DIR=VM_DIR)}, 74 | 75 | "mipsel_wheezy": Image { 76 | arch: "mipsel", 77 | os = "linux-32-debian:3.2.0-4-4kc-malta", 78 | prompt: r#"root@debian-mipsel:.*# "#, 79 | cdrom: "ide1-cd0", 80 | snapshot: "root", 81 | default_mem: "1G", 82 | url: "https://panda-re.mit.edu/qcows/linux/debian/7.3/mipsel/debian_7.3_mipsel.qcow", 83 | extra_files=["vmlinux-3.2.0-4-4kc-malta.mipsel",], 84 | extra_args: "-M malta -kernel {DOT_DIR}/vmlinux-3.2.0-4-4kc-malta.mipsel -append \"root=/dev/sda1\" -nographic"}, 85 | 86 | // Ubuntu: x86/x86_64 support for 16.04, x86_64 support for 18.04 87 | "i386_ubuntu_1604" => Image { 88 | arch: "i386", 89 | os: "linux-32-ubuntu:4.4.200-170-generic", # Version.c is 200 but name is 4.4.0. Not sure why 90 | prompt: r#"root@instance-1:.*#"#, 91 | cdrom: "ide1-cd0", 92 | snapshot: "root", 93 | default_mem: "1024", 94 | url: "https://panda-re.mit.edu/qcows/linux/ubuntu/1604/x86/ubuntu_1604_x86.qcow", 95 | extra_args: "-display none"}, 96 | 97 | // 'x86_64_ubuntu_1604' => Image { // XXX: This one is broken 98 | // arch: "x86_64", 99 | // os: "linux-64-ubuntu:4.4.0-180-pae", 100 | // prompt: r#"root@instance-1:.*#"#, 101 | // cdrom: "ide1-cd0", 102 | // snapshot: "root", 103 | // default_mem: "1024", 104 | // url: "https://panda-re.mit.edu/qcows/linux/ubuntu/1604/x86_64/ubuntu_1604_x86_64.qcow", 105 | // extra_files=['xenial-server-cloudimg-amd64-disk1.img',], 106 | // extra_args: "-display none"}, 107 | */ 108 | "x86_64_ubuntu_1804" => Image { 109 | arch: Arch::x86_64, 110 | os: "linux-64-ubuntu:4.15.0-72-generic-noaslr-nokaslr", 111 | prompt: r#"root@ubuntu:.*#"#, 112 | cdrom: "ide1-cd0", 113 | snapshot: "root", 114 | default_mem: "1024", 115 | url: "https://panda-re.mit.edu/qcows/linux/ubuntu/1804/x86_64/bionic-server-cloudimg-amd64-noaslr-nokaslr.qcow2", 116 | extra_args: &["-display", "none"]}, 117 | "x86_64" => get_supported_image("x86_64_ubuntu_1804"), 118 | _ => panic!("Unsupported image {}", name) 119 | } 120 | } 121 | 122 | fn panda_image_dir() -> PathBuf { 123 | let dir = dirs::home_dir().unwrap().join(".panda"); 124 | 125 | if !dir.exists() { 126 | std::fs::create_dir(&dir).unwrap(); 127 | } 128 | 129 | dir 130 | } 131 | 132 | // Given a generic name of a qcow or a path to a qcow, return the path. Downloads the qcow if it 133 | // hasn't already been downloaded to ~/.panda/ yet. 134 | pub fn get_generic_path(name: &str) -> PathBuf { 135 | let image = get_supported_image(name); 136 | let filename = image.url.split('/').last().unwrap(); 137 | let path = panda_image_dir().join(filename); 138 | 139 | if !path.exists() { 140 | println!( 141 | "QCOW {} doesn't exist. Downloading from https://panda-re.mit.edu. Thanks MIT!", 142 | name 143 | ); 144 | Command::new("wget") 145 | .args(&["--quiet", &image.url, "-O"]) 146 | .arg(&path) 147 | .status() 148 | .unwrap(); 149 | } 150 | 151 | path 152 | } 153 | 154 | #[cfg(test)] 155 | mod tests { 156 | use super::*; 157 | 158 | #[test] 159 | fn test_install_qcow() { 160 | let x = get_generic_path("x86_64"); 161 | 162 | assert!(x.exists()); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /syscall-parser/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | #[derive(Debug)] 4 | struct CallbackType { 5 | name: String, 6 | arguments: Vec, 7 | } 8 | 9 | #[derive(Debug, Clone)] 10 | struct Argument { 11 | name: String, 12 | ty: Type, 13 | } 14 | 15 | impl Argument { 16 | fn set_pc_type(&self) -> Self { 17 | let Self { name, ty } = self.clone(); 18 | 19 | if name == "pc" 20 | && matches!( 21 | &ty, 22 | Type::Ident(ident) 23 | if matches!(ident.as_str(), "target_ulong" | "target_ptr_t") 24 | ) 25 | { 26 | Self { 27 | name: "pc".into(), 28 | ty: Type::Ident("SyscallPc".into()), 29 | } 30 | } else { 31 | Self { name, ty } 32 | } 33 | } 34 | } 35 | 36 | #[derive(Debug, Clone)] 37 | enum Type { 38 | Ptr(Box), 39 | Ident(String), 40 | } 41 | 42 | impl Type { 43 | fn as_rust_type(&self) -> String { 44 | match self { 45 | Self::Ptr(ptr) => format!("&mut {}", ptr.as_rust_type()), 46 | Self::Ident(ident) => match &**ident { 47 | "uint8_t" => "u8", 48 | "uint16_t" => "u16", 49 | "uint32_t" => "u32", 50 | "uint64_t" => "u64", 51 | "int8_t" => "i8", 52 | "int16_t" => "i16", 53 | "int32_t" => "i32", 54 | "int64_t" => "i64", 55 | ident @ ("CPUState" | "target_ulong" | "SyscallPc") => ident, 56 | ident => panic!("type {} unrecognized", ident), 57 | } 58 | .into(), 59 | } 60 | } 61 | } 62 | 63 | fn sanitize_name(name: &str) -> String { 64 | match name { 65 | "type" => "_type".into(), 66 | _ => name.into(), 67 | } 68 | } 69 | 70 | fn syscall_name(callback_name: &str) -> &str { 71 | let cb_name = callback_name.strip_prefix("on_sys_").unwrap(); 72 | 73 | cb_name 74 | .strip_suffix("_return") 75 | .unwrap_or_else(|| cb_name.strip_suffix("_enter").unwrap()) 76 | } 77 | 78 | fn before_or_after(callback_name: &str) -> &'static str { 79 | if callback_name.ends_with("_return") { 80 | "after" 81 | } else if callback_name.ends_with("_enter") { 82 | "before" 83 | } else { 84 | panic!( 85 | "Callback {} does not end in `return` or `enter`", 86 | callback_name 87 | ) 88 | } 89 | } 90 | 91 | peg::parser! { 92 | grammar c_parser() for str { 93 | pub(crate) rule callback_types() -> Vec 94 | = (_? comment())* cb_types:callback_type() ** (_? ";" _?) ";" _ (comment() _?)* { 95 | cb_types 96 | } 97 | 98 | rule comment() 99 | = "//" [^ '\n']* "\n" 100 | 101 | rule callback_type() -> CallbackType 102 | = "PPP_CB_TYPEDEF" _? "(" _? "void" _? "," _? name:ident() _? "," 103 | _? arguments:argument() ** ("," _?) _? ")" 104 | { 105 | CallbackType { name, arguments } 106 | } 107 | 108 | // XXX: leftover from typedef version 109 | //rule callback_type() -> CallbackType 110 | // = "typedef" _ "void" _ "(" _? "*" _? name:ident() _? ")" _? 111 | // "(" _? arguments:argument() ** ("," _?) _? ")" 112 | // { 113 | // CallbackType { name, arguments } 114 | // } 115 | 116 | rule argument() -> Argument 117 | = ty:c_type() _ name:ident() { Argument { name, ty } } 118 | 119 | rule c_type() -> Type 120 | = ptr() 121 | / type_ident() 122 | 123 | rule ptr() -> Type 124 | = ident:type_ident() _? "*" { Type::Ptr(Box::new(ident)) } 125 | 126 | rule type_ident() -> Type 127 | = ident:ident() { Type::Ident(ident) } 128 | 129 | rule ident() -> String 130 | = ident:$( 131 | ['a'..='z' | 'A'..='Z' | '_'] ['a'..='z' | 'A'..='Z' | '_' | '0'..='9']* 132 | ) { ident.into() } 133 | 134 | rule _() = quiet!{ [' ' | '\n' | '\t']+ } 135 | } 136 | } 137 | 138 | const ARCHES: &[(&str, &str)] = &[ 139 | ("x86_64", "x64"), 140 | ("i386", "x86"), 141 | ("arm", "arm"), 142 | ("aarch64", "arm64"), 143 | // no ppc support 144 | //("ppc", "ppc"), 145 | ("mips", "mips"), 146 | ("mipsel", "mips"), 147 | ("mips64", "mips"), 148 | ]; 149 | 150 | fn main() { 151 | let mut decl_file = std::fs::File::create("../panda-rs/src/on_sys.rs").unwrap(); 152 | writeln!( 153 | decl_file, 154 | "// AUTOGENERATED BY panda-rs/syscall-parser DO NOT EDIT\n" 155 | ) 156 | .unwrap(); 157 | for (arch, syscalls_arch) in ARCHES { 158 | println!("Generating for {}...", arch); 159 | 160 | writeln!( 161 | decl_file, 162 | "#[doc(inline)]\n#[cfg(feature = \"{arch}\")]\npub use crate::cbs::{{", 163 | arch = arch 164 | ) 165 | .unwrap(); 166 | 167 | let path = 168 | std::path::Path::new(&std::env::var("PANDA_ROOT").expect("Please set 'PANDA_ROOT'")) 169 | .join(&format!( 170 | "panda/plugins/syscalls2/generated/syscalls_ext_typedefs_{}.h", 171 | syscalls_arch 172 | )); 173 | 174 | let contents = std::fs::read_to_string(path).unwrap(); 175 | let mut f = 176 | std::fs::File::create(format!("../panda-macros/src/syscalls/{}.rs", arch)).unwrap(); 177 | let callback_types = c_parser::callback_types(&contents).unwrap(); 178 | 179 | writeln!(f, "// AUTOGENERATED BY panda-rs/syscall-parser DO NOT EDIT").unwrap(); 180 | writeln!(f, "define_syscalls_callbacks! {{").unwrap(); 181 | for callback_type in callback_types { 182 | let name = callback_type.name.trim_end_matches("_t"); 183 | 184 | // omit BSD syscalls 185 | if name.starts_with("on_sys_") { 186 | let args = callback_type 187 | .arguments 188 | .iter() 189 | .map(Argument::set_pc_type) 190 | .map(|arg| format!("{}: {}", sanitize_name(&arg.name), arg.ty.as_rust_type())) 191 | .collect::>() 192 | .join(", "); 193 | writeln!( 194 | f, 195 | " ({name}, add_callback_{name}, {sys_name}, {before_or_after:?}, ({args})),", 196 | name = name, 197 | sys_name = syscall_name(name), 198 | before_or_after = before_or_after(name), 199 | args = args, 200 | ) 201 | .unwrap(); 202 | 203 | writeln!( 204 | decl_file, 205 | " {} as {},", 206 | name, 207 | name.strip_prefix("on_sys_").unwrap() 208 | ) 209 | .unwrap(); 210 | } 211 | } 212 | 213 | writeln!(f, "}}").unwrap(); 214 | writeln!(decl_file, "}};").unwrap(); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /panda-rs/src/abi.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_macros, unused_imports)] 2 | use crate::prelude::*; 3 | use crate::regs::Reg::{self, *}; 4 | 5 | use crate::mem::{virtual_memory_read, virtual_memory_write}; 6 | use crate::regs; 7 | 8 | use std::convert::TryInto; 9 | use std::sync::atomic::{AtomicBool, Ordering}; 10 | 11 | static IS_SYSENTER: AtomicBool = AtomicBool::new(false); 12 | 13 | #[allow(dead_code)] 14 | pub(crate) fn set_is_sysenter(is_sysenter: bool) { 15 | IS_SYSENTER.store(is_sysenter, Ordering::SeqCst); 16 | } 17 | 18 | fn is_sysenter() -> bool { 19 | IS_SYSENTER.load(Ordering::SeqCst) 20 | } 21 | 22 | struct Stack; 23 | 24 | pub mod syscall { 25 | use super::*; 26 | 27 | macro_rules! reg_or_stack { 28 | (Stack) => { 29 | StorageLocation::StackOffset(0) 30 | }; 31 | ($reg:ident) => { 32 | StorageLocation::Reg($reg) 33 | }; 34 | } 35 | 36 | macro_rules! syscall_regs { 37 | { 38 | const { $syscall_args:ident, $syscall_ret:ident, $syscall_num_reg:ident, $syscall_args_len:ident }; 39 | $( 40 | #[cfg($(arch = $arch:literal),+)] { 41 | args$(: $size:literal)? = [$( $args:ident $(@ $offset:literal)? ),*]; 42 | return = $ret:ident; 43 | syscall_number = $sys_num:ident; 44 | } 45 | )* 46 | } => { 47 | $( 48 | /// Number of syscall arguments 49 | #[cfg(any($(feature = $arch),*))] 50 | pub const $syscall_args_len: usize = 6 $(+ ($size - 6))?; 51 | 52 | /// Argument registers for performing syscalls 53 | #[cfg(any($(feature = $arch),*))] 54 | pub const $syscall_args: [StorageLocation; $syscall_args_len] = [$( 55 | reg_or_stack!($args) $(.with_offset($offset))? 56 | ),*]; 57 | 58 | /// Register where syscall return value is stored on syscall exit 59 | #[cfg(any($(feature = $arch),*))] 60 | pub const $syscall_ret: Reg = $ret; 61 | 62 | /// Register where the syscall number is stored on syscall enter 63 | #[cfg(any($(feature = $arch),*))] 64 | pub const $syscall_num_reg: Reg = $sys_num; 65 | )* 66 | } 67 | } 68 | 69 | syscall_regs! { 70 | const {SYSCALL_ARGS, SYSCALL_RET, SYSCALL_NUM_REG, SYSCALL_ARGS_LEN}; 71 | 72 | #[cfg(arch = "x86_64")] { 73 | args = [RDI, RSI, RDX, R10, R8, R9]; 74 | return = RAX; 75 | syscall_number = RAX; 76 | } 77 | 78 | #[cfg(arch = "i386")] { 79 | args = [EBX, ECX @ 0x8, EDX @ 0x4, ESI, EDI, EBP @ 0x0]; 80 | return = EAX; 81 | syscall_number = EAX; 82 | } 83 | 84 | // we primarily support EABI systems, but this might work for OABI too 85 | #[cfg(arch = "arm")] { 86 | args = [R0, R1, R2, R3, R4, R5]; 87 | return = R0; 88 | syscall_number = R7; 89 | } 90 | 91 | #[cfg(arch = "aarch64")] { 92 | args = [X0, X1, X2, X3, X4, X5]; 93 | return = X0; 94 | syscall_number = X8; 95 | } 96 | 97 | // we "only" "support" the n32 ABI (syscalls2 supports configuring o32 ABI at 98 | // compile-time, other things probably(?) don't) 99 | #[cfg(arch = "mips", arch = "mipsel")] { 100 | // n32 ABI 101 | // args = [A0, A1, A2, A3, T0, T1]; 102 | 103 | // o32 ABI 104 | args: 8 = [A0, A1, A2, A3, Stack@0x10, Stack@0x14, Stack@0x18, Stack@0x1c]; 105 | return = V0; 106 | syscall_number = V0; 107 | } 108 | 109 | #[cfg(arch = "mips64", arch = "mips64el")] { 110 | // n32 ABI 111 | args = [A0, A1, A2, A3, T0, T1]; 112 | return = V0; 113 | syscall_number = V0; 114 | } 115 | } 116 | } 117 | 118 | #[derive(Clone, Copy, PartialEq, Debug)] 119 | pub enum StorageLocation { 120 | Reg(Reg), 121 | StackOffset(target_ulong), 122 | StackReg(Reg, target_ulong), 123 | } 124 | 125 | impl From for StorageLocation { 126 | fn from(reg: Reg) -> Self { 127 | Self::Reg(reg) 128 | } 129 | } 130 | 131 | impl From<(Reg, target_ulong)> for StorageLocation { 132 | fn from((reg, offset): (Reg, target_ulong)) -> Self { 133 | Self::StackReg(reg, offset) 134 | } 135 | } 136 | 137 | impl From for StorageLocation { 138 | fn from(_: Stack) -> Self { 139 | Self::StackOffset(0) 140 | } 141 | } 142 | 143 | const REG_SIZE: usize = std::mem::size_of::(); 144 | 145 | fn is_little_endian() -> bool { 146 | crate::ARCH_ENDIAN == crate::enums::Endian::Little 147 | } 148 | 149 | impl StorageLocation { 150 | #[allow(dead_code)] 151 | pub(crate) const fn with_offset(self, offset: target_ulong) -> Self { 152 | if let Self::Reg(reg) | Self::StackReg(reg, _) = self { 153 | Self::StackReg(reg, offset) 154 | } else { 155 | Self::StackOffset(offset) 156 | } 157 | } 158 | 159 | fn is_stack(&self) -> bool { 160 | matches!(self, Self::StackOffset(_)) || is_sysenter() 161 | } 162 | 163 | pub fn read(self, cpu: &mut CPUState) -> target_ulong { 164 | match self { 165 | Self::StackReg(_, offset) | Self::StackOffset(offset) if self.is_stack() => { 166 | let sp = regs::get_reg(cpu, regs::reg_sp()); 167 | 168 | let bytes = virtual_memory_read(cpu, sp + offset, REG_SIZE) 169 | .expect("Failed to read syscall argument from stack") 170 | .try_into() 171 | .unwrap(); 172 | 173 | if is_little_endian() { 174 | target_ulong::from_le_bytes(bytes) 175 | } else { 176 | target_ulong::from_be_bytes(bytes) 177 | } 178 | } 179 | Self::StackOffset(_offset) => unreachable!(), 180 | Self::Reg(reg) | Self::StackReg(reg, _) => regs::get_reg(cpu, reg), 181 | } 182 | } 183 | 184 | pub fn write(self, cpu: &mut CPUState, val: target_ulong) { 185 | match self { 186 | Self::StackReg(reg, offset) if is_sysenter() => { 187 | let sp = regs::get_reg(cpu, regs::reg_sp()); 188 | 189 | virtual_memory_write(cpu, sp + offset, &val.to_le_bytes()); 190 | 191 | #[cfg(feature = "i386")] 192 | if reg == Reg::EBP { 193 | return; 194 | } 195 | 196 | regs::set_reg(cpu, reg, val); 197 | } 198 | Self::StackOffset(offset) => { 199 | let sp = regs::get_reg(cpu, regs::reg_sp()); 200 | 201 | let bytes = if is_little_endian() { 202 | val.to_le_bytes() 203 | } else { 204 | val.to_be_bytes() 205 | }; 206 | 207 | virtual_memory_write(cpu, sp + offset, &bytes); 208 | } 209 | Self::Reg(reg) | Self::StackReg(reg, _) => regs::set_reg(cpu, reg, val), 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /panda-rs/src/callbacks/export.rs: -------------------------------------------------------------------------------- 1 | /// Declare and export a plugin-to-plugin (PPP) callback, which allows other plugins 2 | /// to add callbacks for this plugin to run. 3 | /// 4 | /// ## Example 5 | /// 6 | /// For example, if you were writing a Rust plugin that would use a callback that 7 | /// runs every other basic block, you could declare and use two callbacks like so: 8 | /// 9 | /// ``` 10 | /// use panda::{Callback, export_ppp_callback}; 11 | /// use panda::prelude::*; 12 | /// 13 | /// export_ppp_callback! { 14 | /// pub(crate) fn on_every_even_block(cpu: &mut CPUState); 15 | /// pub(crate) fn on_every_odd_block(cpu: &mut CPUState); 16 | /// } 17 | /// 18 | /// #[panda::init] 19 | /// fn init() { 20 | /// let mut i = 0; 21 | /// Callback::new().before_block_exec(move |cpu, _| { 22 | /// if i % 2 == 0 { 23 | /// on_every_even_block::trigger(cpu); 24 | /// } else { 25 | /// on_every_odd_block::trigger(cpu); 26 | /// } 27 | /// 28 | /// i += 1; 29 | /// }); 30 | /// } 31 | /// ``` 32 | /// 33 | /// (For further usage see `panda-rs/examples/ppp_callback_export.rs`) 34 | /// 35 | /// The return type of each callback can be any which implements [`CallbackReturn`], a 36 | /// trait which describes how to fold all the return values into a single return value 37 | /// to be returned by `::trigger(...)`. For example a callback that returns 38 | /// a `bool` will return `true` if any of the callbacks return `true`, and will only return 39 | /// false if every registered callback returns false. 40 | /// 41 | /// If you wish to alter this behavior for existing types, use the [newtype pattern], 42 | /// which will allow you to provide your own implementation by implementing the trait. 43 | /// 44 | /// [newtype pattern]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html 45 | /// 46 | /// **Note:** All callback arguments and return values are expected to be FFI safe. If rustc 47 | /// emits a warning for a given type, it is very likely it is not compatible with the 48 | /// C ABI. In order to have your own custom types be FFI-safe, they should be marked 49 | /// either `#[repr(transparent)]` or `#[repr(C)]` or should be treated as opaque types 50 | /// (and thus should only be created and accessed within the same plugin and passed as 51 | /// references). 52 | #[macro_export] 53 | macro_rules! export_ppp_callback { 54 | { 55 | $( 56 | $vis:vis fn $cb_name:ident ( 57 | $( 58 | $arg:ident : $arg_ty:ty 59 | ),* $(,)? 60 | ) $(-> $ret_ty:ty)?; 61 | )* 62 | } => {$( 63 | $vis mod $cb_name { 64 | use super::*; 65 | 66 | use ::std::ffi::c_void; 67 | 68 | #[derive(PartialEq)] 69 | struct PppContextInternal(*mut c_void); 70 | 71 | unsafe impl Sync for PppContextInternal {} 72 | unsafe impl Send for PppContextInternal {} 73 | 74 | $vis type CallbackType = extern "C" fn($( $arg_ty ),*) $(-> $ret_ty)?; 75 | $vis type CallbackTypeWithContext = extern "C" fn(*mut c_void, $( $arg_ty ),*) $(-> $ret_ty)?; 76 | 77 | extern "C" fn trampoline(context: *mut c_void, $($arg : $arg_ty),*) $(-> $ret_ty)? { 78 | let cb: CallbackType = unsafe { 79 | ::core::mem::transmute(context) 80 | }; 81 | 82 | cb($($arg),*) 83 | } 84 | 85 | #[export_name = concat!("ppp_add_cb_", stringify!($cb_name))] 86 | $vis extern "C" fn add_callback(callback: CallbackType) { 87 | unsafe { 88 | add_callback_with_context(trampoline, ::core::mem::transmute(callback)) 89 | } 90 | } 91 | 92 | #[export_name = concat!("ppp_add_cb_", stringify!($cb_name), "_with_context")] 93 | $vis extern "C" fn add_callback_with_context( 94 | callback: CallbackTypeWithContext, 95 | context: *mut c_void, 96 | ) { 97 | CALLBACKS 98 | .lock() 99 | .unwrap() 100 | .push((callback, PppContextInternal(context))); 101 | } 102 | 103 | #[export_name = concat!("ppp_remove_cb_", stringify!($cb_name))] 104 | $vis extern "C" fn remove_callback(callback: CallbackType) -> bool { 105 | unsafe { 106 | remove_callback_with_context(trampoline, ::core::mem::transmute(callback)) 107 | } 108 | } 109 | 110 | #[export_name = concat!("ppp_remove_cb_", stringify!($cb_name), "_with_context")] 111 | $vis extern "C" fn remove_callback_with_context( 112 | callback: CallbackTypeWithContext, 113 | context: *mut c_void, 114 | ) -> bool { 115 | let context = PppContextInternal(context); 116 | let mut callbacks = CALLBACKS.lock().unwrap(); 117 | let old_len = callbacks.len(); 118 | 119 | callbacks.retain( 120 | |(cb, cb_ctxt)| (*cb as usize, cb_ctxt) != (callback as _, &context) 121 | ); 122 | 123 | callbacks.len() != old_len 124 | } 125 | 126 | $crate::lazy_static::lazy_static! { 127 | static ref CALLBACKS: ::std::sync::Mutex< 128 | Vec<(CallbackTypeWithContext, PppContextInternal)> 129 | > = ::std::sync::Mutex::new(Vec::new()); 130 | } 131 | 132 | $vis fn trigger($($arg : $arg_ty),*) $(-> $ret_ty)? { 133 | CALLBACKS.lock() 134 | .unwrap() 135 | .iter_mut() 136 | .map(|(callback, PppContextInternal(context))| callback( 137 | *context, 138 | $($arg),* 139 | )) 140 | .fold( 141 | $crate::__callback_fold_default!($($ret_ty)?), 142 | $crate::__callback_fold_fn!($($ret_ty)?) 143 | ) 144 | } 145 | } 146 | )*}; 147 | } 148 | 149 | #[doc(hidden)] 150 | #[macro_export] 151 | macro_rules! __callback_fold_default { 152 | () => { 153 | () 154 | }; 155 | ($ty:ty) => { 156 | <$ty as $crate::CallbackReturn>::callback_fold_default() 157 | }; 158 | } 159 | 160 | #[doc(hidden)] 161 | #[macro_export] 162 | macro_rules! __callback_fold_fn { 163 | () => { 164 | (|(), _| ()) 165 | }; 166 | ($ty:ty) => { 167 | <$ty as $crate::CallbackReturn>::fold_callback_return 168 | }; 169 | } 170 | 171 | /// A type which can be returned from a callback and folded into a single value 172 | /// 173 | /// As an example, here's the provided implementation for `bool`: 174 | /// 175 | /// ```no_run 176 | /// /// Returns true if any of the callbacks returned true without short circuiting 177 | /// impl CallbackReturn for bool { 178 | /// type FoldType = bool; 179 | /// 180 | /// fn fold_callback_return(folded: Self::FoldType, ret: Self) -> Self::FoldType { 181 | /// folded | ret 182 | /// } 183 | /// } 184 | /// ``` 185 | /// 186 | /// The way this is used is by taking the `FoldType` and creating a default instance. For 187 | /// a `bool` this will be `false`. Then, for each callback return value it will take the 188 | /// previous instance (starting with `false`) and do `previous | current_callback_return`. 189 | /// 190 | /// The result will mean that if callbacks `a`, `b`, and `c` are registered, the resulting 191 | /// value returned from `::trigger(...)` is `((false | a) | b) | c`. (Parenthesis 192 | /// added to demonstrate folding order) 193 | pub trait CallbackReturn { 194 | type FoldType: Default; 195 | 196 | /// Function for folding each callback return value into a single value 197 | fn fold_callback_return(folded: Self::FoldType, ret: Self) -> Self::FoldType; 198 | 199 | /// Get the default value for folding the callback returns into a single value 200 | fn callback_fold_default() -> Self::FoldType { 201 | Self::FoldType::default() 202 | } 203 | } 204 | 205 | /// Returns true if any of the callbacks returned true without short circuiting 206 | impl CallbackReturn for bool { 207 | type FoldType = bool; 208 | 209 | fn fold_callback_return(folded: Self::FoldType, ret: Self) -> Self::FoldType { 210 | folded | ret 211 | } 212 | } 213 | 214 | macro_rules! impl_for_ints { 215 | ($($ty:ty)*) => { 216 | $( 217 | /// Returns the first non-zero value without short-circuiting 218 | impl CallbackReturn for $ty { 219 | type FoldType = $ty; 220 | 221 | fn fold_callback_return(folded: Self::FoldType, ret: Self) -> Self::FoldType { 222 | if folded != 0 { 223 | folded 224 | } else { 225 | ret 226 | } 227 | } 228 | } 229 | )* 230 | }; 231 | } 232 | 233 | impl_for_ints!(u8 u16 u32 u64 usize i8 i16 i32 i64 isize); 234 | -------------------------------------------------------------------------------- /panda-rs/src/api/mem.rs: -------------------------------------------------------------------------------- 1 | use crate::enums::MemRWStatus; 2 | use crate::prelude::*; 3 | use crate::GuestType; 4 | use crate::{sys, Error}; 5 | use crate::{GuestReadFail, GuestWriteFail}; 6 | 7 | use std::ffi::CString; 8 | use std::os::raw::c_char; 9 | 10 | // Public API ---------------------------------------------------------------------------------------------------------- 11 | 12 | /// Read a structure or value from guest memory using the guest endianess and 13 | /// layout for the given type. 14 | /// 15 | /// ## Example 16 | /// 17 | /// ``` 18 | /// use panda::mem::read_guest_type; 19 | /// use panda::prelude::*; 20 | /// 21 | /// # let cpu: &mut CPUState = todo!(); 22 | /// let pid: u32 = read_guest_type(cpu, 0x55550000).unwrap(); 23 | /// ``` 24 | /// 25 | /// To use custom structures and types, derive the [`GuestType`] trait for your 26 | /// given structure. 27 | pub fn read_guest_type( 28 | cpu: &mut CPUState, 29 | addr: target_ptr_t, 30 | ) -> Result { 31 | T::read_from_guest(cpu, addr) 32 | } 33 | 34 | /// Write a given type to guest memory using the guest endianess and layout for the 35 | /// given type. 36 | /// 37 | /// ## Example 38 | /// 39 | /// ``` 40 | /// use panda::mem::write_guest_type; 41 | /// use panda::prelude::*; 42 | /// 43 | /// # let cpu: &mut CPUState = todo!(); 44 | /// let pid = 1234_u32; 45 | /// 46 | /// write_guest_type(cpu, 0x55550000, pid); 47 | /// ``` 48 | pub fn write_guest_type( 49 | cpu: &mut CPUState, 50 | addr: target_ptr_t, 51 | val: &T, 52 | ) -> Result<(), GuestWriteFail> { 53 | val.write_to_guest(cpu, addr) 54 | } 55 | 56 | /// Read a structure or value from physical guest memory using the guest endianess and layout 57 | /// for the given type. 58 | /// 59 | /// ## Example 60 | /// 61 | /// ``` 62 | /// use panda::mem::read_guest_type_phys; 63 | /// use panda::prelude::*; 64 | /// 65 | /// # let cpu: &mut CPUState = todo!(); 66 | /// let ptr = 0xF8000010; 67 | /// let pid: u32 = read_guest_type_phys(ptr).unwrap(); 68 | /// ``` 69 | pub fn read_guest_type_phys(addr: target_ptr_t) -> Result { 70 | T::read_from_guest_phys(addr) 71 | } 72 | 73 | /// Write a given type to guest physical memory using the guest endianess and layout for the 74 | /// given type. 75 | /// 76 | /// ## Example 77 | /// 78 | /// ``` 79 | /// use panda::mem::write_guest_type_phys; 80 | /// use panda::prelude::*; 81 | /// 82 | /// # let cpu: &mut CPUState = todo!(); 83 | /// let pid = 1234_u32; 84 | /// 85 | /// write_guest_type_phys(0xF8000010, pid); 86 | /// ``` 87 | pub fn write_guest_type_phys( 88 | addr: target_ptr_t, 89 | val: &T, 90 | ) -> Result<(), GuestWriteFail> { 91 | val.write_to_guest_phys(addr) 92 | } 93 | 94 | /// Read from guest virtual memory 95 | pub fn virtual_memory_read( 96 | cpu: &mut CPUState, 97 | addr: target_ulong, 98 | len: usize, 99 | ) -> Result, MemRWStatus> { 100 | let mut buf: Vec = Vec::with_capacity(len); 101 | 102 | unsafe { 103 | let res = 104 | panda_sys::panda_virtual_memory_read_external(cpu, addr, buf.as_mut_ptr(), len as i32) 105 | .into(); 106 | 107 | match res { 108 | MemRWStatus::MemTxOk => { 109 | buf.set_len(len); 110 | Ok(vec_i8_into_u8(buf)) 111 | } 112 | _ => Err(res), 113 | } 114 | } 115 | } 116 | 117 | /// Read from guest virtual memory into a buffer 118 | pub fn virtual_memory_read_into( 119 | cpu: &mut CPUState, 120 | addr: target_ulong, 121 | buf: &mut [u8], 122 | ) -> Result<(), MemRWStatus> { 123 | let res = unsafe { 124 | panda_sys::panda_virtual_memory_read_external( 125 | cpu, 126 | addr, 127 | buf.as_mut_ptr() as _, 128 | buf.len() as i32, 129 | ) 130 | .into() 131 | }; 132 | 133 | match res { 134 | MemRWStatus::MemTxOk => Ok(()), 135 | _ => Err(res), 136 | } 137 | } 138 | 139 | /// Read from guest physical memory 140 | pub fn physical_memory_read(addr: target_ulong, len: usize) -> Result, MemRWStatus> { 141 | let mut buf: Vec = Vec::with_capacity(len); 142 | 143 | unsafe { 144 | let res = panda_sys::panda_physical_memory_read_external( 145 | addr as u64, 146 | buf.as_mut_ptr(), 147 | len as i32, 148 | ) 149 | .into(); 150 | 151 | match res { 152 | MemRWStatus::MemTxOk => { 153 | buf.set_len(len); 154 | Ok(buf) 155 | } 156 | _ => Err(res), 157 | } 158 | } 159 | } 160 | 161 | /// Read from guest physical memory into a pre-allocated buffer 162 | pub fn physical_memory_read_into(addr: target_ulong, buf: &mut [u8]) -> Result<(), MemRWStatus> { 163 | let res = unsafe { 164 | panda_sys::panda_physical_memory_read_external( 165 | addr as u64, 166 | buf.as_mut_ptr() as _, 167 | buf.len() as i32, 168 | ) 169 | .into() 170 | }; 171 | 172 | match res { 173 | MemRWStatus::MemTxOk => Ok(()), 174 | _ => Err(res), 175 | } 176 | } 177 | 178 | /// Write to guest virtual memory 179 | pub fn virtual_memory_write(cpu: &mut CPUState, addr: target_ulong, data: &[u8]) -> MemRWStatus { 180 | let mut c_data = data.to_vec(); // Alloc b/c C API wants mut 181 | unsafe { 182 | panda_sys::panda_virtual_memory_write_external( 183 | cpu, 184 | addr, 185 | c_data.as_mut_ptr() as *mut c_char, 186 | c_data.len() as i32, 187 | ) 188 | .into() 189 | } 190 | } 191 | 192 | /// Write to guest physical memory 193 | pub fn physical_memory_write(addr: target_ulong, data: &[u8]) -> MemRWStatus { 194 | let mut c_data = data.to_vec(); // Alloc b/c C API wants mut 195 | unsafe { 196 | panda_sys::panda_physical_memory_write_external( 197 | addr as _, 198 | c_data.as_mut_ptr(), 199 | c_data.len() as i32, 200 | ) 201 | .into() 202 | } 203 | } 204 | 205 | /// Translate guest virtual address to physical address, returning `None` if no mapping 206 | /// can be found. 207 | pub fn virt_to_phys(cpu: &mut CPUState, addr: target_ulong) -> Option { 208 | match unsafe { panda_sys::panda_virt_to_phys_external(cpu, addr) } { 209 | target_ulong::MAX => None, 210 | phys_addr => Some(phys_addr), 211 | } 212 | } 213 | 214 | pub const PAGE_SIZE: target_ulong = 1024; 215 | 216 | /// Map RAM into the system at a given physical address 217 | pub fn map_memory(name: &str, size: target_ulong, addr: target_ptr_t) -> Result<(), Error> { 218 | let name = CString::new(name)?; 219 | 220 | if size % PAGE_SIZE != 0 { 221 | Err(Error::UnalignedPageSize) 222 | } else { 223 | unsafe { 224 | sys::map_memory(name.as_ptr() as _, size as _, addr as _); 225 | } 226 | 227 | drop(name); 228 | 229 | Ok(()) 230 | } 231 | } 232 | 233 | const IS_32_BIT: bool = std::mem::size_of::() == 4; 234 | const TARGET_BITS: usize = std::mem::size_of::() * 8; 235 | 236 | fn hex_addr(addr: target_ptr_t) -> impl std::fmt::Display { 237 | if IS_32_BIT { 238 | format!("{:08x}", addr) 239 | } else { 240 | format!("{:016x}", addr) 241 | } 242 | } 243 | 244 | pub fn virt_memory_dump(cpu: &mut CPUState, addr: target_ptr_t, len: usize) { 245 | let memory = virtual_memory_read(cpu, addr, len).unwrap(); 246 | 247 | let start_addr_aligned = addr & !0xf; 248 | let end_addr_aligned = (addr + (len as target_ptr_t)) & !0xf; 249 | 250 | let bytes_offset = addr - start_addr_aligned; 251 | 252 | let hex_dump = (start_addr_aligned..=end_addr_aligned) 253 | .step_by(0x10) 254 | .enumerate() 255 | .map(|(line_num, line_addr)| { 256 | let hex_data = (0..0x10) 257 | .map(|offset_in_line| { 258 | if line_num == 0 && offset_in_line < (bytes_offset as usize) { 259 | " ".into() 260 | } else { 261 | let byte_index = 262 | ((line_num * 0x10) + offset_in_line) - (bytes_offset as usize); 263 | 264 | if let Some(byte) = memory.get(byte_index) { 265 | format!("{:02x}", byte).into() 266 | } else { 267 | " ".into() 268 | } 269 | } 270 | }) 271 | .collect::>>() 272 | .join(" "); 273 | 274 | format!("{}║{}\n", hex_addr(line_addr), hex_data) 275 | }) 276 | .collect::(); 277 | 278 | println!( 279 | "{} 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f", 280 | " ".repeat(TARGET_BITS / 4) 281 | ); 282 | println!( 283 | "{}╦═══════════════════════════════════════════════", 284 | "═".repeat(TARGET_BITS / 4) 285 | ); 286 | println!("{}", hex_dump); 287 | } 288 | 289 | // Private API --------------------------------------------------------------------------------------------------------- 290 | 291 | // https://stackoverflow.com/questions/59707349/cast-vector-of-i8-to-vector-of-u8-in-rust/59707887#59707887 292 | // TODO: replace with https://doc.rust-lang.org/std/vec/struct.Vec.html#method.into_raw_parts, once on stable 293 | fn vec_i8_into_u8(v: Vec) -> Vec { 294 | // Make sure v's destructor doesn't free the data it thinks it owns when it goes out of scope 295 | let mut v = std::mem::ManuallyDrop::new(v); 296 | 297 | // Pick apart the existing Vec 298 | let p = v.as_mut_ptr(); 299 | let len = v.len(); 300 | let cap = v.capacity(); 301 | 302 | // Adopt the data into a new Vec 303 | unsafe { Vec::from_raw_parts(p as *mut u8, len, cap) } 304 | } 305 | -------------------------------------------------------------------------------- /panda-rs/src/api/regs.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use crate::{cpu_arch_state, CPUArchPtr}; 3 | 4 | use strum::IntoEnumIterator; 5 | use strum_macros::{EnumIter, EnumString, ToString}; 6 | 7 | /// Type-safe API to allow APIs to accept only program counters coming from 8 | /// syscall callbacks. To convert to integer of the width of your target, use the 9 | /// `.pc()` method. 10 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 11 | #[repr(transparent)] 12 | pub struct SyscallPc(target_ulong); 13 | 14 | impl SyscallPc { 15 | pub fn pc(self) -> target_ulong { 16 | self.0 17 | } 18 | } 19 | 20 | impl Reg { 21 | /// Iterate over the available registers 22 | pub fn iter() -> RegIter { 23 | ::iter() 24 | } 25 | } 26 | 27 | // Arch-specific mappings ---------------------------------------------------------------------------------------------- 28 | 29 | // TODO: handle AX/AH/AL, etc via shifts? Tricky b/c enum val used to index QEMU array 30 | /// x86 named guest registers 31 | #[cfg(feature = "i386")] 32 | #[derive(Debug, Copy, Clone, PartialEq, Eq, EnumString, EnumIter, ToString)] 33 | pub enum Reg { 34 | EAX = 0, 35 | ECX = 1, 36 | EDX = 2, 37 | EBX = 3, 38 | ESP = 4, 39 | EBP = 5, 40 | ESI = 6, 41 | EDI = 7, 42 | } 43 | 44 | /// x86 return registers 45 | #[cfg(feature = "i386")] 46 | static RET_REGS: &'static [Reg] = &[Reg::EAX]; 47 | 48 | // TODO: handle EAX/AX/AH/AL, etc via shifts? Tricky b/c enum val used to index QEMU array 49 | /// x64 named guest registers 50 | #[cfg(feature = "x86_64")] 51 | #[derive(Debug, Copy, Clone, PartialEq, Eq, EnumString, EnumIter, ToString)] 52 | pub enum Reg { 53 | RAX = 0, 54 | RCX = 1, 55 | RDX = 2, 56 | RBX = 3, 57 | RSP = 4, 58 | RBP = 5, 59 | RSI = 6, 60 | RDI = 7, 61 | R8 = 8, 62 | R9 = 9, 63 | R10 = 10, 64 | R11 = 11, 65 | R12 = 12, 66 | R13 = 13, 67 | R14 = 14, 68 | R15 = 15, 69 | } 70 | 71 | /// x64 return registers 72 | #[cfg(feature = "x86_64")] 73 | static RET_REGS: &'static [Reg] = &[Reg::RAX]; 74 | 75 | /// ARM named guest registers 76 | #[cfg(feature = "arm")] 77 | #[derive(Debug, Copy, Clone, PartialEq, Eq, EnumString, EnumIter, ToString)] 78 | pub enum Reg { 79 | R0 = 0, 80 | R1 = 1, 81 | R2 = 2, 82 | R3 = 3, 83 | R4 = 4, 84 | R5 = 5, 85 | R6 = 6, 86 | R7 = 7, 87 | R8 = 8, 88 | R9 = 9, 89 | R10 = 10, 90 | R11 = 11, 91 | R12 = 12, 92 | LR = 14, 93 | SP = 13, 94 | IP = 15, 95 | } 96 | 97 | /// ARM return registers 98 | #[cfg(feature = "arm")] 99 | static RET_REGS: &'static [Reg] = &[Reg::R0, Reg::R1, Reg::R2, Reg::R3]; 100 | 101 | /// AArch64 named guest registers 102 | #[cfg(feature = "aarch64")] 103 | #[derive(Debug, Copy, Clone, PartialEq, Eq, EnumString, EnumIter, ToString)] 104 | pub enum Reg { 105 | X0 = 0, 106 | X1 = 1, 107 | X2 = 2, 108 | X3 = 3, 109 | X4 = 4, 110 | X5 = 5, 111 | X6 = 6, 112 | X7 = 7, 113 | X8 = 8, 114 | X9 = 9, 115 | X10 = 10, 116 | X11 = 11, 117 | X12 = 12, 118 | LR = 13, 119 | SP = 14, 120 | IP = 15, 121 | } 122 | 123 | /// AArch64 return registers 124 | #[cfg(feature = "aarch64")] 125 | static RET_REGS: &'static [Reg] = &[Reg::X0, Reg::X1, Reg::X2, Reg::X3]; 126 | 127 | /// MIPS named guest registers 128 | #[cfg(any( 129 | feature = "mips", 130 | feature = "mipsel", 131 | feature = "mips64", 132 | feature = "mips64el" 133 | ))] 134 | #[derive(Debug, Copy, Clone, PartialEq, Eq, EnumString, EnumIter, ToString)] 135 | pub enum Reg { 136 | ZERO = 0, 137 | AT = 1, 138 | V0 = 2, 139 | V1 = 3, 140 | A0 = 4, 141 | A1 = 5, 142 | A2 = 6, 143 | A3 = 7, 144 | T0 = 8, 145 | T1 = 9, 146 | T2 = 10, 147 | T3 = 11, 148 | T4 = 12, 149 | T5 = 13, 150 | T6 = 14, 151 | T7 = 15, 152 | S0 = 16, 153 | S1 = 17, 154 | S2 = 18, 155 | S3 = 19, 156 | S4 = 20, 157 | S5 = 21, 158 | S6 = 22, 159 | S7 = 23, 160 | T8 = 24, 161 | T9 = 25, 162 | K0 = 26, 163 | K1 = 27, 164 | GP = 28, 165 | SP = 29, 166 | FP = 30, 167 | RA = 31, 168 | } 169 | 170 | /// MIPS return registers 171 | #[cfg(any( 172 | feature = "mips", 173 | feature = "mipsel", 174 | feature = "mips64", 175 | feature = "mips64el" 176 | ))] 177 | static RET_REGS: &'static [Reg] = &[Reg::V0, Reg::V1]; 178 | 179 | // TODO: support floating point set as well? Separate QEMU bank. 180 | /// PPC named guest registers 181 | #[cfg(feature = "ppc")] 182 | #[derive(Debug, Copy, Clone, PartialEq, Eq, EnumString, EnumIter, ToString)] 183 | pub enum Reg { 184 | R0 = 0, 185 | R1 = 1, 186 | R2 = 2, 187 | R3 = 3, 188 | R4 = 4, 189 | R5 = 5, 190 | R6 = 6, 191 | R7 = 7, 192 | R8 = 8, 193 | R9 = 9, 194 | R10 = 10, 195 | R11 = 11, 196 | R12 = 12, 197 | R13 = 13, 198 | R14 = 14, 199 | R15 = 15, 200 | R16 = 16, 201 | R17 = 17, 202 | R18 = 18, 203 | R19 = 19, 204 | R20 = 20, 205 | R21 = 21, 206 | R22 = 22, 207 | R23 = 23, 208 | R24 = 24, 209 | R25 = 25, 210 | R26 = 26, 211 | R27 = 27, 212 | R28 = 28, 213 | R29 = 29, 214 | R30 = 30, 215 | R31 = 31, 216 | LR = 100, // Special case - separate bank in QEMU 217 | } 218 | 219 | /// PPC return registers 220 | #[cfg(feature = "ppc")] 221 | static RET_REGS: &'static [Reg] = &[Reg::R3, Reg::R4]; 222 | 223 | // Getters/setters ----------------------------------------------------------------------------------------------------- 224 | 225 | /// Get stack pointer register 226 | pub fn reg_sp() -> Reg { 227 | #[cfg(feature = "i386")] 228 | return Reg::ESP; 229 | 230 | #[cfg(feature = "x86_64")] 231 | return Reg::RSP; 232 | 233 | #[cfg(any( 234 | feature = "arm", 235 | feature = "aarch64", 236 | feature = "mips", 237 | feature = "mipsel", 238 | feature = "mips64", 239 | feature = "mips64el" 240 | ))] 241 | return Reg::SP; 242 | 243 | #[cfg(any(feature = "ppc"))] 244 | return Reg::R1; 245 | } 246 | 247 | /// Get return value registers 248 | /// MIPS/ARM/PPC: Note that most C code will only use the first register, e.g. index 0 in returned `Vec` 249 | pub fn reg_ret_val() -> &'static [Reg] { 250 | return &RET_REGS; 251 | } 252 | 253 | /// Get return address register 254 | pub fn reg_ret_addr() -> Option { 255 | #[cfg(feature = "i386")] 256 | return None; 257 | 258 | #[cfg(feature = "x86_64")] 259 | return None; 260 | 261 | #[cfg(any(feature = "arm", feature = "aarch64", feature = "ppc"))] 262 | return Some(Reg::LR); 263 | 264 | #[cfg(any( 265 | feature = "mips", 266 | feature = "mipsel", 267 | feature = "mips64", 268 | feature = "mips64el" 269 | ))] 270 | return Some(Reg::RA); 271 | } 272 | 273 | /// Read the current value of a register 274 | pub fn get_reg>(cpu: &CPUState, reg: T) -> target_ulong { 275 | let cpu_arch = cpu_arch_state!(cpu); 276 | let val; 277 | 278 | #[cfg(any(feature = "i386", feature = "x86_64", feature = "arm"))] 279 | unsafe { 280 | val = (*cpu_arch).regs[reg.into() as usize]; 281 | } 282 | 283 | #[cfg(feature = "aarch64")] 284 | unsafe { 285 | val = (*cpu_arch).xregs[reg.into() as usize]; 286 | } 287 | 288 | #[cfg(any( 289 | feature = "mips", 290 | feature = "mipsel", 291 | feature = "mips64", 292 | feature = "mips64el" 293 | ))] 294 | unsafe { 295 | val = (*cpu_arch).active_tc.gpr[reg.into() as usize]; 296 | } 297 | 298 | #[cfg(any(feature = "ppc"))] 299 | unsafe { 300 | let reg_enum = reg.into(); 301 | if reg_enum == Reg::LR { 302 | val = (*cpu_arch).lr; 303 | } else { 304 | val = (*cpu_arch).gpr[reg_enum as usize]; 305 | } 306 | } 307 | 308 | val 309 | } 310 | 311 | /// Set the value for a register 312 | pub fn set_reg>(cpu: &CPUState, reg: T, val: target_ulong) { 313 | let cpu_arch = cpu_arch_state!(cpu); 314 | 315 | #[cfg(any(feature = "i386", feature = "x86_64", feature = "arm"))] 316 | unsafe { 317 | (*cpu_arch).regs[reg.into() as usize] = val; 318 | } 319 | 320 | #[cfg(any( 321 | feature = "mips", 322 | feature = "mipsel", 323 | feature = "mips64", 324 | feature = "mips64el" 325 | ))] 326 | unsafe { 327 | (*cpu_arch).active_tc.gpr[reg.into() as usize] = val; 328 | } 329 | 330 | #[cfg(any(feature = "ppc"))] 331 | unsafe { 332 | let reg_enum = reg.into(); 333 | if reg_enum == Reg::LR { 334 | (*cpu_arch).lr = val; 335 | } else { 336 | (*cpu_arch).gpr[reg_enum as usize] = val; 337 | } 338 | } 339 | 340 | #[cfg(feature = "aarch64")] 341 | unsafe { 342 | (*cpu_arch).xregs[reg.into() as usize] = val; 343 | } 344 | } 345 | 346 | pub fn get_pc(cpu: &CPUState) -> target_ulong { 347 | let cpu_arch = cpu_arch_state!(cpu); 348 | let val; 349 | 350 | #[cfg(any(feature = "x86_64", feature = "i386"))] 351 | unsafe { 352 | val = (*cpu_arch).eip; 353 | } 354 | 355 | #[cfg(feature = "arm")] 356 | unsafe { 357 | val = (*cpu_arch).regs[15]; 358 | } 359 | 360 | #[cfg(feature = "aarch64")] 361 | unsafe { 362 | val = (*cpu_arch).pc; 363 | } 364 | 365 | #[cfg(feature = "ppc")] 366 | unsafe { 367 | val = (*cpu_arch).nip; 368 | } 369 | 370 | #[cfg(any( 371 | feature = "mips", 372 | feature = "mipsel", 373 | feature = "mips64", 374 | feature = "mips64el" 375 | ))] 376 | unsafe { 377 | val = (*cpu_arch).active_tc.PC; 378 | } 379 | 380 | val 381 | } 382 | 383 | pub fn set_pc(cpu: &mut CPUState, pc: target_ulong) { 384 | let cpu_arch = cpu_arch_state!(cpu); 385 | 386 | #[cfg(any(feature = "x86_64", feature = "i386"))] 387 | unsafe { 388 | (*cpu_arch).eip = pc; 389 | } 390 | 391 | #[cfg(feature = "arm")] 392 | unsafe { 393 | (*cpu_arch).regs[15] = pc; 394 | } 395 | 396 | #[cfg(feature = "aarch64")] 397 | unsafe { 398 | (*cpu_arch).pc = pc; 399 | } 400 | 401 | #[cfg(feature = "ppc")] 402 | unsafe { 403 | (*cpu_arch).nip = pc; 404 | } 405 | 406 | #[cfg(any( 407 | feature = "mips", 408 | feature = "mipsel", 409 | feature = "mips64", 410 | feature = "mips64el" 411 | ))] 412 | unsafe { 413 | (*cpu_arch).active_tc.PC = pc; 414 | } 415 | } 416 | 417 | // Printing ------------------------------------------------------------------------------------------------------------ 418 | 419 | /// Print the contents of all registers 420 | pub fn dump_regs(cpu: &CPUState) { 421 | for reg in Reg::iter() { 422 | println!("{:?}:\t0x{:016x}", reg, get_reg(cpu, reg)); 423 | } 424 | } 425 | --------------------------------------------------------------------------------