use std::{mem::transmute, path::Path};
use anyhow::{Context, anyhow, bail};
use bon::bon;
use dll_syringe::{
Syringe,
process::{BorrowedProcessModule, OwnedProcess},
rpc::RemotePayloadProcedure,
};
use tracing::{error, info};
use crate::hook::{HookConfig, dll::SET_HOOK};
pub struct ShellItemHook {
syringe: Syringe,
payload: BorrowedProcessModule<'static>,
remote_set_hook: RemotePayloadProcedure<fn(Option<HookConfig>)>,
}
impl ShellItemHook {
pub fn set_hook(&self, config: &Option<HookConfig>) {
let _ = self.remote_set_hook.call(config);
}
pub fn disable_hook(&self) {
let _ = self.remote_set_hook.call(&None);
}
pub fn eject(self) -> Result<(), String> {
self.syringe.eject(self.payload).map_err(|e| e.to_string())
}
}
pub struct ShellItemHooks {
hooks: Vec<ShellItemHook>,
}
#[bon]
impl ShellItemHooks {
#[builder]
pub fn inject(
dll_path: &Path,
#[builder(default = "explorer.exe")]
process_name: &str,
config: Option<HookConfig>,
) -> anyhow::Result<Self> {
if !dll_path.exists() {
bail!("DLL not found at: {:?}", dll_path);
}
let processes = OwnedProcess::find_all_by_name(process_name);
if processes.is_empty() {
bail!("Failed to find any {} process", process_name);
}
info!("Found {} {} processes", processes.len(), process_name);
let mut hooks: Vec<ShellItemHook> = Vec::new();
for (i, target_process) in processes.into_iter().enumerate() {
let syringe = Syringe::for_process(target_process);
info!("[{}] Injecting DLL: {:?}", i, dll_path);
match syringe.find_or_inject(&dll_path) {
Ok(payload) => {
info!("[{}] Successfully injected hook.dll", i);
let remote_set_hook =
unsafe { syringe.get_payload_procedure(payload, SET_HOOK) }
.context("Failed to get set_hook procedure")?
.context("set_hook not found")?;
let injected_hook = ShellItemHook {
payload: unsafe { transmute(payload) },
syringe,
remote_set_hook,
};
if config.is_some() {
injected_hook.set_hook(&config);
info!("[{}] Hook enabled", i);
}
hooks.push(injected_hook);
}
Err(e) => {
error!("[{}] Failed to inject DLL: {}", i, e);
}
}
}
Ok(ShellItemHooks { hooks })
}
pub fn eject(&mut self) -> Result<(), anyhow::Error> {
let mut first_error: Option<anyhow::Error> = None;
for hook in self.hooks.drain(..) {
hook.disable_hook();
if let Err(e) = hook.eject() {
error!("Failed to eject DLL: {}", e);
if first_error.is_none() {
first_error = Some(anyhow::anyhow!(e));
}
}
}
if let Some(e) = first_error {
return Err(anyhow!(e));
}
info!("Successfully ejected DLL");
Ok(())
}
}