use std::{
future::Future,
pin::Pin,
sync::atomic::{AtomicBool, AtomicU64, Ordering},
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
};
use dashmap::{DashMap, DashSet};
use lazy_static::lazy_static;
use parking_lot::{const_mutex, Mutex};
use crate::prelude::*;
use crate::{
plugins::{osi::OSI, syscalls2::Syscalls2Callbacks},
regs, sys, PppCallback,
};
mod arch;
mod conversion;
mod pinned_queue;
mod syscall_future;
mod syscall_regs;
mod syscalls;
pub(crate) use crate::abi::set_is_sysenter;
use {
arch::{FORK_IS_CLONE, SYSCALL_RET, VFORK},
pinned_queue::PinnedQueue,
syscall_future::{INJECTOR_BAIL, WAITING_FOR_SYSCALL},
syscall_regs::SyscallRegs,
};
pub use {conversion::*, syscall_future::*};
type Injector = dyn Future<Output = ()> + 'static;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct ThreadId {
pid: target_ulong,
tid: target_ulong,
}
impl ThreadId {
fn current() -> Self {
let cpu = unsafe { &mut *sys::get_cpu() };
let thread = OSI.get_current_thread(cpu);
let tid = thread.tid as target_ulong;
let pid = thread.pid as target_ulong;
log::trace!("current tid, pid: {:#x?}, {:#x?}", tid, pid);
Self { tid, pid }
}
}
lazy_static! {
static ref INJECTORS: DashMap<ThreadId, PinnedQueue<Injector>> = DashMap::new();
static ref FORKING_THREADS: DashSet<ThreadId> = DashSet::new();
}
struct ChildInjector((SyscallRegs, Pin<Box<Injector>>));
unsafe impl Send for ChildInjector {}
unsafe impl Sync for ChildInjector {}
static CHILD_INJECTOR: Mutex<Option<ChildInjector>> = const_mutex(None);
static PARENT_PID: AtomicU64 = AtomicU64::new(u64::MAX);
static JUST_CLONED: AtomicBool = AtomicBool::new(false);
pub async fn fork(child_injector: impl Future<Output = ()> + 'static) -> target_ulong {
let backed_up_regs = get_backed_up_regs().expect("Fork was run outside of an injector");
PARENT_PID.store(ThreadId::current().pid as u64, Ordering::SeqCst);
FORKING_THREADS.insert(ThreadId::current());
CHILD_INJECTOR
.lock()
.replace(ChildInjector((backed_up_regs, Box::pin(child_injector))));
if FORK_IS_CLONE {
const CLONE_FILES: target_ulong = 0x00000400;
const CLONE_VFORK: target_ulong = 0x00004000;
const CLONE_NEWPID: target_ulong = 0x20000000;
const NULL: target_ptr_t = 0;
const CLONE: target_ulong = VFORK;
JUST_CLONED.swap(true, Ordering::SeqCst);
log::debug!("Running clone syscall");
syscall(
CLONE,
(
CLONE_VFORK | CLONE_NEWPID | CLONE_FILES,
NULL,
NULL,
NULL,
NULL,
),
)
.await
} else {
syscall(VFORK, ()).await
}
}
fn get_child_injector() -> Option<(SyscallRegs, Pin<Box<Injector>>)> {
CHILD_INJECTOR.lock().take().map(|x| x.0)
}
fn restart_syscall(cpu: &mut CPUState, pc: target_ulong) {
regs::set_pc(cpu, pc);
unsafe {
panda::sys::cpu_loop_exit_noexc(cpu);
}
}
#[cfg(any(feature = "x86_64", feature = "i386"))]
const SYSENTER_INSTR: &[u8] = &[0xf, 0x34];
pub fn run_injector(pc: SyscallPc, injector: impl Future<Output = ()> + 'static) {
let pc = pc.pc();
log::trace!("Running injector with syscall pc of {:#x?}", pc);
#[cfg(any(feature = "x86_64", feature = "i386"))]
{
use crate::mem::virtual_memory_read;
let cpu = unsafe { &mut *sys::get_cpu() };
let is_sysenter = virtual_memory_read(cpu, pc, 2)
.ok()
.map(|bytes| bytes == SYSENTER_INSTR)
.unwrap_or(false);
log::trace!("is_sysenter = {}", is_sysenter);
set_is_sysenter(is_sysenter);
}
let is_first = INJECTORS.is_empty();
let thread_id = ThreadId::current();
INJECTORS.entry(thread_id).or_default().push_future(async {
let backed_up_regs = SyscallRegs::backup();
set_backed_up_regs(backed_up_regs.clone());
injector.await;
log::debug!("Restoring backed up registers");
backed_up_regs.restore();
unset_backed_up_regs();
});
if is_first {
log::trace!("Enabling callbacks...");
let sys_enter = PppCallback::new();
let sys_return = PppCallback::new();
let disable_callbacks = move || {
log::trace!("Disabling callbacks...");
sys_enter.disable();
sys_return.disable();
};
sys_return.on_all_sys_return(move |cpu: &mut CPUState, sys_pc, sys_num| {
if JUST_CLONED.load(Ordering::SeqCst) {
log::debug!(
"on_sys_return: {} @ {:#x?} ({:#x?}?) ({:?})",
sys_num,
sys_pc.pc(),
pc,
ThreadId::current(),
);
} else {
log::trace!(
"on_sys_return: {} @ {:#x?} ({:#x?}?) ({:?})",
sys_num,
sys_pc.pc(),
pc,
ThreadId::current(),
);
}
if sys_num == VFORK {
log::trace!("ret = {:#x?}", regs::get_reg(cpu, SYSCALL_RET));
}
let thread_id = ThreadId::current();
if FORKING_THREADS.contains(&thread_id) {
if sys_num != VFORK {
log::warn!("Non-fork ({}) return from {:?}", sys_num, thread_id);
log::warn!("Non-fork ret = {:#x?}", regs::get_reg(cpu, SYSCALL_RET));
if cfg!(not(feature = "arm")) {
return;
}
log::warn!("Returning from fork anyways.");
FORKING_THREADS.remove(&thread_id);
SHOULD_LOOP_AGAIN.store(true, Ordering::SeqCst);
set_ret_value(cpu);
restart_syscall(cpu, pc);
} else {
log::debug!("Returning from fork {:?}", &thread_id);
FORKING_THREADS.remove(&thread_id);
}
}
let forker_pid = PARENT_PID.load(Ordering::SeqCst);
let is_child_of_forker = forker_pid != u64::MAX
&& OSI
.get_current_process(cpu)
.map(|proc| proc.ppid as u64 == forker_pid)
.unwrap_or_else(|| {
log::debug!("Failed to get process");
false
});
if is_child_of_forker {
PARENT_PID.store(u64::MAX, Ordering::SeqCst);
}
let is_fork_child = is_child_of_forker;
if is_fork_child {
if let Some((backed_up_regs, child_injector)) = get_child_injector() {
INJECTORS
.entry(ThreadId::current())
.or_default()
.push_future(async move {
child_injector.await;
backed_up_regs.restore();
});
} else {
println!("WARNING: failed to get child injector");
return;
}
}
log::trace!("Current asid = {:x}", current_asid());
if is_fork_child || is_current_injector_thread() {
SHOULD_LOOP_AGAIN.store(true, Ordering::SeqCst);
if !is_fork_child {
set_ret_value(cpu);
}
restart_syscall(cpu, pc);
}
});
sys_enter.on_all_sys_enter(move |cpu, sys_pc, sys_num| {
log::trace!(
"on_sys_enter: {} @ {:#x?} ({:#x?}?)",
sys_num,
sys_pc.pc(),
pc
);
if poll_injectors() {
disable_callbacks();
}
if SHOULD_LOOP_AGAIN.swap(false, Ordering::SeqCst) {
restart_syscall(cpu, pc);
}
});
if poll_injectors() {
println!("WARN: Injector seemed to not call any system calls?");
disable_callbacks();
}
}
}
static SHOULD_LOOP_AGAIN: AtomicBool = AtomicBool::new(false);
lazy_static! {
static ref CURRENT_REGS_BACKUP: DashMap<ThreadId, SyscallRegs> = DashMap::new();
}
pub fn get_backed_up_regs() -> Option<SyscallRegs> {
CURRENT_REGS_BACKUP
.get(&ThreadId::current())
.map(|regs| regs.clone())
}
fn set_backed_up_regs(regs: SyscallRegs) {
CURRENT_REGS_BACKUP.insert(ThreadId::current(), regs);
}
fn unset_backed_up_regs() {
CURRENT_REGS_BACKUP.remove(&ThreadId::current());
}
fn current_asid() -> target_ulong {
unsafe { sys::panda_current_asid(sys::get_cpu()) }
}
pub fn run_injector_next_syscall(injector: impl Future<Output = ()> + 'static) {
let next_syscall = PppCallback::new();
let mut injector = Some(injector);
next_syscall.on_all_sys_enter(move |_, pc, _| {
let injector = injector.take().unwrap();
run_injector(pc, injector);
next_syscall.disable();
});
}
fn do_nothing(_ptr: *const ()) {}
fn clone(ptr: *const ()) -> RawWaker {
RawWaker::new(ptr, &VTABLE)
}
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, do_nothing, do_nothing, do_nothing);
fn waiting_for_syscall() -> bool {
WAITING_FOR_SYSCALL.load(Ordering::SeqCst)
}
lazy_static! {
static ref CURRENT_INJECTOR_THREAD: Mutex<Option<ThreadId>> = Mutex::new(None);
}
fn is_current_injector_thread() -> bool {
CURRENT_INJECTOR_THREAD
.lock()
.as_ref()
.map(|&id| id == ThreadId::current())
.unwrap_or(false)
}
fn poll_injectors() -> bool {
let raw = RawWaker::new(std::ptr::null(), &VTABLE);
let waker = unsafe { Waker::from_raw(raw) };
let mut ctxt = Context::from_waker(&waker);
WAITING_FOR_SYSCALL.store(false, Ordering::SeqCst);
CURRENT_INJECTOR_THREAD.lock().take();
if let Some(mut injectors) = INJECTORS.get_mut(&ThreadId::current()) {
while let Some(ref mut current_injector) = injectors.current_mut() {
CURRENT_INJECTOR_THREAD.lock().replace(ThreadId::current());
match current_injector.as_mut().poll(&mut ctxt) {
status
if matches!(status, Poll::Ready(_))
|| INJECTOR_BAIL.swap(false, Ordering::SeqCst) =>
{
injectors.pop();
if injectors.is_empty() {
drop(injectors);
INJECTORS.remove(&ThreadId::current());
break;
}
continue;
}
Poll::Pending if waiting_for_syscall() => return false,
Poll::Pending => continue,
_ => unreachable!(),
}
}
} else {
return false;
}
let all_injectors_finished = INJECTORS.is_empty() && CHILD_INJECTOR.lock().is_none();
all_injectors_finished
}