Skip to main content

windows_breakpoint_manager/common/
mod.rs

1//! Logging, VMI and kernel profile bootstrap shared by the examples.
2
3#![allow(
4    dead_code,
5    reason = "each example uses a different subset of these helpers"
6)]
7
8use std::sync::{Arc, atomic::AtomicBool};
9
10use anyhow::{Context as _, Error};
11use isr::{Profile, cache::IsrCache};
12use tracing_subscriber::EnvFilter;
13use vmi::{
14    VcpuId, VmiCore, VmiSession,
15    arch::amd64::Amd64,
16    driver::{VmiFullDriver, xen::VmiXenDriver},
17    os::{
18        ProcessId, VmiOsProcess as _,
19        windows::{WindowsKernelInformation, WindowsOs},
20    },
21};
22
23/// A VMI setup that attaches the driver on creation and detaches it on drop.
24pub struct VmiSetup {
25    core: VmiCore<VmiXenDriver<Amd64>>,
26    os: WindowsOs<VmiXenDriver<Amd64>>,
27    isr: IsrCache,
28    profile: Profile<'static>,
29    kernel_info: WindowsKernelInformation,
30    terminate_flag: Arc<AtomicBool>,
31}
32
33impl VmiSetup {
34    /// Creates a new VMI setup and loads the kernel profile.
35    pub fn new() -> Result<Self, Error> {
36        let filter = EnvFilter::default()
37            .add_directive(tracing::Level::DEBUG.into())
38            .add_directive("reqwest=warn".parse()?)
39            .add_directive("rustls=warn".parse()?);
40
41        tracing_subscriber::fmt()
42            .with_env_filter(filter)
43            .with_target(false)
44            .init();
45
46        // Turn Ctrl-C into a clean shutdown.
47        let terminate_flag = Arc::new(AtomicBool::new(false));
48        signal_hook::flag::register(signal_hook::consts::SIGHUP, terminate_flag.clone())?;
49        signal_hook::flag::register(signal_hook::consts::SIGINT, terminate_flag.clone())?;
50        signal_hook::flag::register(signal_hook::consts::SIGALRM, terminate_flag.clone())?;
51        signal_hook::flag::register(signal_hook::consts::SIGTERM, terminate_flag.clone())?;
52
53        // Setup VMI.
54        let driver = VmiXenDriver::<Amd64>::try_from_env()?
55            .context("invalid VMI_XEN_DOMAIN environment variable")?;
56        let core = VmiCore::new(driver)?;
57
58        // Try to find the kernel information.
59        // This is necessary in order to load the profile.
60        let kernel_info = {
61            // Pause the vCPU to get consistent state.
62            let _pause_guard = core.pause_guard()?;
63
64            // Get the register state for the first vCPU.
65            let registers = core.registers(VcpuId(0))?;
66
67            // On AMD64 architecture, the kernel is usually found using the
68            // `MSR_LSTAR` register, which contains the address of the system call
69            // handler. This register is set by the operating system during boot
70            // and is left unchanged (unless some rootkits are involved).
71            //
72            // Therefore, we can take an arbitrary registers at any point in time
73            // (as long as the OS has booted and the page tables are set up) and
74            // use them to find the kernel.
75            WindowsOs::find_kernel(&core, &registers)?.context("cannot find kernel information")?
76        };
77
78        // Load the profile.
79        // The profile contains offsets to kernel functions and data structures.
80        tracing::info!(codeview = ?kernel_info.codeview, "loading kernel profile");
81        let isr = IsrCache::new("cache")?;
82        let entry = isr.entry_from_codeview(kernel_info.codeview.clone())?;
83
84        // Please don't do this in production code.
85        // This is only done for the sake of the example.
86        let entry = Box::leak(Box::new(entry));
87        let profile = entry.profile()?;
88
89        tracing::info!("creating VMI session");
90        let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
91
92        Ok(Self {
93            core,
94            os,
95            isr,
96            profile,
97            kernel_info,
98            terminate_flag,
99        })
100    }
101
102    /// Creates a VMI session.
103    pub fn session(&self) -> VmiSession<'_, WindowsOs<VmiXenDriver<Amd64>>> {
104        VmiSession::new(&self.core, &self.os)
105    }
106
107    /// Returns the kernel ISR profile of the VMI session.
108    pub fn profile(&self) -> &Profile<'static> {
109        &self.profile
110    }
111
112    /// Returns the ISR symbol cache.
113    pub fn isr(&self) -> &IsrCache {
114        &self.isr
115    }
116
117    /// Returns the kernel image information.
118    pub fn kernel_info(&self) -> &WindowsKernelInformation {
119        &self.kernel_info
120    }
121
122    /// Returns the flag set on `SIGHUP`, `SIGINT`, `SIGALRM` or `SIGTERM`.
123    pub fn terminate_flag(&self) -> Arc<AtomicBool> {
124        self.terminate_flag.clone()
125    }
126}
127
128/// Finds a process by its name.
129pub fn find_process_id<Driver>(
130    session: &VmiSession<'_, WindowsOs<Driver>>,
131    process_name: &str,
132) -> Result<ProcessId, Error>
133where
134    Driver: VmiFullDriver<Architecture = Amd64>,
135{
136    let paused = session.pause_guard()?;
137    let vmi = paused.state();
138    let process = vmi
139        .os()
140        .find_process(process_name)?
141        .with_context(|| format!("process `{process_name}` not found"))?;
142
143    let process_id = process.id()?;
144
145    tracing::info!(
146        process = process_name,
147        pid = %process_id,
148        "found target process"
149    );
150
151    Ok(process_id)
152}