use crate::error::{Error, Result};
use crate::windows::{
NtSetInformationProcessFn, PROCESS_INSTRUMENTATION_CALLBACK, ProcessInstrumentationCallbackInfo,
};
use defer_rs::defer;
use ntapi::ntpsapi::NtCurrentProcess;
use ntapi::winapi_local::um::winnt::NtCurrentTeb;
use std::arch::naked_asm;
use std::{ffi::c_void, sync::RwLock};
use windows::Win32::{
Foundation::{FreeLibrary, HANDLE},
System::{
Diagnostics::Debug::{CONTEXT, RtlRestoreContext},
LibraryLoader::{GetProcAddress, LoadLibraryA},
},
};
use windows_strings::*;
pub type InstrumentationCallback = fn(&mut CONTEXT);
static INSTRUMENTATION_CALLBACK: RwLock<Option<InstrumentationCallback>> = RwLock::new(None);
#[unsafe(no_mangle)]
extern "C" fn instrumentation_callback(ctx: &mut CONTEXT) {
let teb = unsafe { NtCurrentTeb() };
unsafe {
ctx.Rip = (*teb).InstrumentationCallbackPreviousPc as _;
ctx.Rsp = (*teb).InstrumentationCallbackPreviousSp as _;
ctx.Rcx = ctx.R10;
ctx.R10 = ctx.Rip;
(*teb).InstrumentationCallbackDisabled = 1;
}
{
let callback = INSTRUMENTATION_CALLBACK.read().unwrap();
callback.unwrap()(ctx);
}
unsafe {
(*teb).InstrumentationCallbackDisabled = 0;
RtlRestoreContext(ctx, None);
}
}
#[unsafe(naked)]
#[rustfmt::skip]
pub extern "C" fn callback_stub() {
naked_asm!(
"cmp byte ptr gs:[0x2ec], 1", "je resume", "", "mov gs:[0x2e0], rsp", "mov gs:[0x2d8], r10", "mov r10, rcx", "sub rsp, 0x4d0", "and rsp, -0x10", "mov rcx, rsp", "call RtlCaptureContext", "sub rsp, 0x20", "call instrumentation_callback", "int3", "", "resume:", "jmp r10" );
}
pub fn set_instrumentation_callback(callback: Option<InstrumentationCallback>) -> Result<()> {
*INSTRUMENTATION_CALLBACK.write().unwrap() = callback;
let ntdll = unsafe { LoadLibraryA(s!("ntdll.dll")) }.map_err(Error::LoadLibrary)?;
defer! { unsafe { _ = FreeLibrary(ntdll); } }
let nt_set_info_proc_addr = unsafe { GetProcAddress(ntdll, s!("NtSetInformationProcess")) };
let nt_set_info_proc: NtSetInformationProcessFn =
unsafe { std::mem::transmute(nt_set_info_proc_addr) };
let mut info = ProcessInstrumentationCallbackInfo {
version: 0,
reserved: 0,
callback: if callback.is_some() {
callback_stub as *const c_void
} else {
std::ptr::null()
},
};
let status = unsafe {
nt_set_info_proc(
HANDLE(NtCurrentProcess as _),
PROCESS_INSTRUMENTATION_CALLBACK,
&mut info as *mut _ as *mut c_void,
core::mem::size_of::<ProcessInstrumentationCallbackInfo>() as u32,
)
};
if status.is_err() {
return Err(Error::NtSetInformationProcess(status));
}
Ok(())
}