use anyhow::{Context as _, Error};
use clap::Args;
use vmi::{
VmiSession, arch::amd64::Amd64, driver::VmiFullDriver, os::windows::WindowsOs,
utils::injector::UserInjectorHandler,
};
use super::{
bridge::MsgboxBridge,
parameters::MsgboxParameters,
recipe::{msgbox_call_recipe, msgbox_spawn_recipe},
};
use crate::{Execution, common};
#[derive(Debug, Args)]
pub struct MsgboxArguments {
#[arg(long, default_value = "explorer.exe")]
process: String,
#[arg(long, default_value = "Hello from VMI")]
title: String,
#[arg(long, default_value = "Injected by windows-shellcode")]
text: String,
}
#[derive(Debug)]
struct MsgboxRequest {
process: String,
parameters: MsgboxParameters,
}
impl MsgboxArguments {
fn into_request(self) -> MsgboxRequest {
MsgboxRequest {
process: self.process,
parameters: MsgboxParameters::new(self.title, self.text),
}
}
}
fn validate_result(result: u64) -> Result<u64, Error> {
anyhow::ensure!(result != 0, "MessageBoxA failed");
Ok(result)
}
pub fn run<Driver>(
session: &VmiSession<'_, WindowsOs<Driver>>,
arguments: MsgboxArguments,
execution: Execution,
) -> Result<(), Error>
where
Driver: VmiFullDriver<Architecture = Amd64>,
{
let MsgboxRequest {
process,
parameters,
} = arguments.into_request();
let process_id = common::find_process_id(session, &process)?;
tracing::info!(?execution, "injecting msgbox shellcode");
let result = session
.handle(|session| {
let recipe = match execution {
Execution::Call => msgbox_call_recipe(¶meters),
Execution::Spawn => msgbox_spawn_recipe(¶meters),
};
UserInjectorHandler::new(session, recipe)?
.with_bridge(MsgboxBridge)?
.with_pid(process_id)
})?
.context("msgbox injection interrupted")?
.context("msgbox bridge completed without a result")?
.map_err(|packet| anyhow::anyhow!("unhandled msgbox bridge packet: {packet:?}"))?;
let result = validate_result(result)?;
tracing::info!(result, "message box closed");
Ok(())
}