use std::{
mem::transmute,
path::{Path, PathBuf},
};
use bon::bon;
use derive_more::{Deref, DerefMut};
use dll_syringe::{
Syringe,
process::{BorrowedProcessModule, Process},
rpc::RemotePayloadProcedure,
};
use thiserror::Error;
use crate::{log::*, process::Pid};
#[doc(hidden)]
pub use dll_syringe::payload_utils::__payload_procedure_helper;
pub use dll_syringe::process::OwnedProcess;
#[derive(Error, Debug)]
pub enum InjectError {
#[error("dll not found: {0}")]
DllNotFound(PathBuf),
#[error("cannot find any {0} process")]
ProcessNotFound(String),
#[error("inject failed: {0}")]
InjectFailed(#[from] dll_syringe::error::InjectError),
#[error("get apply failed: {0}")]
GetApplyFailed(#[from] dll_syringe::error::LoadProcedureError),
#[error("apply not found")]
ApplyNotFound,
#[error("apply: {0}")]
ApplyFailed(#[from] dll_syringe::rpc::PayloadRpcError),
#[error("eject failed: {0}")]
EjectFailed(#[from] dll_syringe::error::EjectError),
}
pub trait DllApp {
const APPLY: &str;
type Input: serde::Serialize + 'static;
type Output: serde::de::DeserializeOwned + 'static;
}
#[macro_export]
macro_rules! export_apply {
($apply:ident, $export_name:literal) => {
const _: () = {
#[unsafe(export_name = $export_name)]
pub unsafe extern "system" fn _ib_hook_inject_dll_app_apply(
__args_and_params: *mut ::core::ffi::c_void,
) {
$crate::inject::dll::app::__payload_procedure_helper(__args_and_params, |__args| {
let (input,) = __args;
$apply(input)
});
}
};
};
}
pub use export_apply;
pub struct DllInjection<D: DllApp> {
syringe: Syringe,
payload: BorrowedProcessModule<'static>,
remote_apply: RemotePayloadProcedure<fn(Option<&'static D::Input>) -> D::Output>,
pid: Pid,
applied: bool,
ejected: bool,
}
unsafe impl<D: DllApp> Send for DllInjection<D> {}
#[bon]
impl<D: DllApp> DllInjection<D> {
#[builder]
pub fn inject(
#[builder(start_fn)] process: OwnedProcess,
dll_path: &Path,
apply: Option<&D::Input>,
) -> Result<Self, InjectError> {
let pid = Pid(process.pid().unwrap().get());
let syringe = Syringe::for_process(process);
info!(%pid, ?dll_path, "Injecting");
let payload = syringe.find_or_inject(dll_path)?;
let eject = || {
if let Err(e) = syringe.eject(payload) {
warn!(?e, "eject");
}
};
let remote_apply = unsafe { syringe.get_payload_procedure(payload, D::APPLY) }
.map_err(InjectError::from)
.inspect_err(|_| eject())?
.ok_or(InjectError::ApplyNotFound)
.inspect_err(|_| eject())?;
let payload = unsafe { transmute(payload) };
let mut injection = Self {
payload,
syringe,
remote_apply,
pid,
applied: false,
ejected: false,
};
if let Some(input) = apply {
injection.apply(input)?;
injection.applied = true;
}
info!(%pid, "Successfully injected");
Ok(injection)
}
pub fn pid(&self) -> Pid {
self.pid
}
pub fn maybe_apply(
&self,
input: Option<&D::Input>,
) -> Result<D::Output, dll_syringe::rpc::PayloadRpcError> {
if let Some(input) = input {
self.apply(input)
} else {
self.unapply()
}
}
pub fn apply(&self, input: &D::Input) -> Result<D::Output, dll_syringe::rpc::PayloadRpcError> {
let input: &'static D::Input = unsafe { transmute(input) };
self.remote_apply.call(&Some(input))
}
pub fn unapply(&self) -> Result<D::Output, dll_syringe::rpc::PayloadRpcError> {
self.remote_apply.call(&None)
}
pub fn eject(&mut self) -> Result<(), InjectError> {
if self.ejected {
return Ok(());
}
if self.applied {
self.unapply()?;
}
self.syringe.eject(self.payload)?;
self.ejected = true;
Ok(())
}
pub fn leak(&mut self) {
self.ejected = true;
}
}
impl<D: DllApp> Drop for DllInjection<D> {
fn drop(&mut self) {
if let Err(e) = self.eject() {
error!(pid = self.pid.0, ?e, "Failed to eject on drop");
}
}
}
pub struct DllInjectionVec<D: DllApp> {
injections: Vec<DllInjection<D>>,
ejected: bool,
}
impl<D: DllApp> Default for DllInjectionVec<D> {
fn default() -> Self {
Self {
injections: Default::default(),
ejected: Default::default(),
}
}
}
#[bon]
impl<D: DllApp> DllInjectionVec<D> {
pub fn new() -> Self {
Self::default()
}
pub fn injections(&self) -> &[DllInjection<D>] {
&self.injections
}
pub fn injections_mut(&mut self) -> &mut [DllInjection<D>] {
&mut self.injections
}
#[builder]
pub fn inject(
&mut self,
#[builder(start_fn)]
processes: impl Iterator<Item = OwnedProcess>,
dll_path: &Path,
apply: Option<&D::Input>,
mut on_error: Option<impl FnMut(Pid, InjectError)>,
) -> Result<&mut Self, InjectError> {
if !dll_path.exists() {
return Err(InjectError::DllNotFound(dll_path.to_path_buf()));
}
for target_process in processes {
let pid = Pid(target_process.pid().unwrap().get());
match DllInjection::inject(target_process)
.dll_path(&dll_path)
.maybe_apply(apply)
.call()
{
Ok(injection) => {
self.injections.push(injection);
}
Err(e) => {
error!(%pid, ?e, "Failed to inject");
if let Some(cb) = on_error.as_mut() {
cb(pid, e);
}
}
}
}
Ok(self)
}
#[builder]
pub fn inject_with_process_name(
&mut self,
#[builder(start_fn)]
process_name: &str,
dll_path: &Path,
apply: Option<&D::Input>,
on_error: Option<impl FnMut(Pid, InjectError)>,
) -> Result<&mut Self, InjectError> {
let processes = OwnedProcess::find_all_by_name(process_name);
if processes.is_empty() {
return Err(InjectError::ProcessNotFound(process_name.to_string()));
}
info!("Found {} {} processes", processes.len(), process_name);
self.inject(processes.into_iter())
.dll_path(dll_path)
.maybe_apply(apply)
.maybe_on_error(on_error)
.call()
}
#[builder]
pub fn apply(
&self,
#[builder(start_fn)] input: &D::Input,
mut on_error: Option<impl FnMut(Pid, &dll_syringe::rpc::PayloadRpcError)>,
) {
for injection in &self.injections {
if let Err(e) = injection.apply(input) {
if let Some(on_error) = on_error.as_mut() {
on_error(injection.pid(), &e);
}
}
}
}
#[builder]
pub fn unapply(
&self,
mut on_error: Option<impl FnMut(Pid, &dll_syringe::rpc::PayloadRpcError)>,
) {
for injection in &self.injections {
if let Err(e) = injection.unapply() {
if let Some(on_error) = on_error.as_mut() {
on_error(injection.pid(), &e);
}
}
}
}
#[builder]
pub fn eject(
&mut self,
mut on_error: Option<impl FnMut(Pid, InjectError)>,
) {
for mut injection in self.injections.drain(..) {
let pid = injection.pid;
if let Err(e) = injection.eject() {
warn!(%pid, ?e, "Failed to eject");
if let Some(cb) = on_error.as_mut() {
cb(pid, e);
}
}
}
info!("Successfully ejected");
}
pub fn leak(&mut self) {
self.ejected = true;
}
}
impl<D: DllApp> Drop for DllInjectionVec<D> {
fn drop(&mut self) {
if self.ejected {
for injection in &mut self.injections {
injection.leak();
}
return;
}
self.eject().on_error(|_, _| ()).call();
}
}
#[derive(Deref, DerefMut)]
pub struct DllInjectionVecWithInput<D: DllApp> {
dll_path: PathBuf,
input: Option<D::Input>,
#[deref]
#[deref_mut]
inner: DllInjectionVec<D>,
}
impl<D: DllApp> DllInjectionVecWithInput<D> {
pub fn new(dll_path: PathBuf) -> Result<Self, InjectError> {
Self::with_input(dll_path, None)
}
pub fn with_input(dll_path: PathBuf, input: Option<D::Input>) -> Result<Self, InjectError> {
if !dll_path.exists() {
return Err(InjectError::DllNotFound(dll_path));
}
Ok(Self {
dll_path,
input,
inner: Default::default(),
})
}
pub fn dll_path(&self) -> &PathBuf {
&self.dll_path
}
pub fn input(&self) -> Option<&D::Input> {
self.input.as_ref()
}
}
#[bon]
impl<D: DllApp> DllInjectionVecWithInput<D> {
#[builder]
pub fn inject(
&mut self,
#[builder(start_fn)]
processes: impl Iterator<Item = OwnedProcess>,
on_error: Option<impl FnMut(Pid, InjectError)>,
) -> Result<&mut Self, InjectError> {
self.inner
.inject(processes)
.dll_path(&self.dll_path)
.maybe_apply(self.input.as_ref())
.maybe_on_error(on_error)
.call()?;
Ok(self)
}
#[builder]
pub fn inject_with_process_name(
&mut self,
#[builder(start_fn)]
process_name: &str,
on_error: Option<impl FnMut(Pid, InjectError)>,
) -> Result<&mut Self, InjectError> {
self.inner
.inject_with_process_name(process_name)
.dll_path(&self.dll_path)
.maybe_apply(self.input.as_ref())
.maybe_on_error(on_error)
.call()?;
Ok(self)
}
#[builder]
pub fn apply(
&mut self,
#[builder(start_fn)] input: D::Input,
on_error: Option<impl FnMut(Pid, &dll_syringe::rpc::PayloadRpcError)>,
) {
let input = self.input.insert(input);
self.inner.apply(input).maybe_on_error(on_error).call();
}
#[builder]
pub fn unapply(
&mut self,
on_error: Option<impl FnMut(Pid, &dll_syringe::rpc::PayloadRpcError)>,
) {
self.input = None;
self.inner.unapply().maybe_on_error(on_error).call()
}
}