pub struct PageTableMonitor<Driver, Tag = &'static str>where
Driver: VmiRead + VmiSetProtection,
<Driver as VmiDriver>::Architecture: ArchAdapter<Driver, Tag>,
Tag: TagType,{ /* private fields */ }ptm and utils only.Expand description
Architecture-independent page table monitor.
Thin wrapper that delegates to an architecture-specific implementation
selected via ArchAdapter.
Implementations§
Source§impl<Driver, Tag> PageTableMonitor<Driver, Tag>where
Driver: VmiRead + VmiSetProtection,
<Driver as VmiDriver>::Architecture: ArchAdapter<Driver, Tag>,
Tag: TagType,
impl<Driver, Tag> PageTableMonitor<Driver, Tag>where
Driver: VmiRead + VmiSetProtection,
<Driver as VmiDriver>::Architecture: ArchAdapter<Driver, Tag>,
Tag: TagType,
Sourcepub fn new() -> PageTableMonitor<Driver, Tag>
pub fn new() -> PageTableMonitor<Driver, Tag>
Creates a new page table monitor.
Examples found in repository?
67 pub fn new(
68 session: &VmiSession<WindowsOs<Driver>>,
69 profile: &Profile,
70 terminate_flag: Arc<AtomicBool>,
71 ) -> Result<Self, VmiError> {
72 // Capture the current state of the vCPU and get the base address of
73 // the kernel.
74 //
75 // This base address is essential to correctly offset monitored
76 // functions.
77 //
78 // NOTE: `kernel_image_base` tries to find the kernel in the memory
79 // with the help of the CPU registers. On AMD64 architecture,
80 // the kernel image base is usually found using the `MSR_LSTAR`
81 // register, which contains the address of the system call
82 // handler. This register is set by the operating system during
83 // boot and is left unchanged (unless some rootkits are involved).
84 //
85 // Therefore, we can take an arbitrary registers at any point
86 // in time (as long as the OS has booted and the page tables are
87 // set up) and use them to find the kernel image base.
88 let registers = session.registers(VcpuId(0))?;
89 let vmi = session.with_registers(®isters);
90
91 let kernel_image_base = vmi.os().kernel_image_base()?;
92 tracing::info!(%kernel_image_base);
93
94 // Get the system process.
95 //
96 // The system process is the first process created by the kernel.
97 // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98 // To monitor page table entries, we need to locate the translation root
99 // of this process.
100 let system_process = vmi.os().system_process()?;
101 tracing::info!(system_process = %system_process.object()?);
102
103 // Get the translation root of the system process.
104 // This is effectively "the CR3 of the kernel".
105 //
106 // The translation root is the root of the page table hierarchy (also
107 // known as the Directory Table Base or PML4).
108 let root = system_process.translation_root()?;
109 tracing::info!(%root);
110
111 // Load the symbols from the profile.
112 let symbols = Symbols::new(profile)?;
113
114 // Enable monitoring of the INT3 and singlestep events.
115 //
116 // INT3 is used to monitor the execution of specific functions.
117 // Singlestep is used to monitor the modifications of page table
118 // entries.
119 vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120 vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122 // Create a new view for the monitor.
123 // This view is used for monitoring function calls and memory accesses.
124 let view = vmi.create_view(MemoryAccess::RWX)?;
125 vmi.switch_to_view(view)?;
126
127 // Create a new breakpoint controller.
128 //
129 // The breakpoint controller is used to insert breakpoints for specific
130 // functions.
131 //
132 // From the guest's perspective, these breakpoints are "hidden", since
133 // the breakpoint controller will unset the read/write access to the
134 // physical memory page where the breakpoint is inserted, while keeping
135 // the execute access.
136 //
137 // This way, the guest will be able to execute the code, but attempts to
138 // read or write the memory will trigger the `memory_access` callback.
139 //
140 // When a vCPU tries to execute the breakpoint instruction:
141 // - an `interrupt` callback will be triggered
142 // - the breakpoint will be handled (e.g., log the function call)
143 // - a fast-singlestep[1] will be performed over the INT3 instruction
144 //
145 // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146 // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147 // access type)
148 // - fast-singlestep[1] will be performed over the instruction that tried to
149 // read the memory
150 //
151 // This way, the instruction will read the original memory content.
152 //
153 // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154 // to a different view, execute a single instruction, and then
155 // switch back to the original view. In this case, the view is
156 // switched to the `default_view` (which is unmodified).
157 let mut bpm = BreakpointManager::new();
158
159 // Create a new page table monitor.
160 //
161 // The page table monitor is used to monitor the page table entries of
162 // the hooked functions.
163 //
164 // More specifically, it is used to monitor the pages that the breakpoint
165 // was inserted into. This is necessary to handle the case when the
166 // page containing the breakpoint is paged out (and then paged in
167 // again).
168 //
169 // `PageTableMonitor` works by unsetting the write access to the page
170 // tables of the hooked functions. When the page is paged out, the
171 // `PRESENT` bit in the page table entry is unset and, conversely, when
172 // the page is paged in, the `PRESENT` bit is set again.
173 //
174 // When that happens:
175 // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176 // access type)
177 // - the callback will mark the page as dirty in the page table monitor
178 // - a singlestep will be performed over the instruction that tried to modify
179 // the memory containing the page table entry
180 // - the `singlestep` handler will process the dirty page table entries and
181 // inform the breakpoint controller to handle the changes
182 let mut ptm = PageTableMonitor::new();
183
184 // Pause the VM to avoid race conditions between inserting breakpoints
185 // and monitoring page table entries. The VM resumes when the pause
186 // guard is dropped.
187 let _pause_guard = vmi.pause_guard()?;
188
189 // Insert breakpoint for the `NtCreateFile` function.
190 let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191 let cx_NtCreateFile = (va_NtCreateFile, root);
192 let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193 .global()
194 .with_tag("NtCreateFile");
195 bpm.insert(&vmi, bp_NtCreateFile)?;
196 ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197 tracing::info!(%va_NtCreateFile);
198
199 // Insert breakpoint for the `NtWriteFile` function.
200 let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201 let cx_NtWriteFile = (va_NtWriteFile, root);
202 let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203 .global()
204 .with_tag("NtWriteFile");
205 bpm.insert(&vmi, bp_NtWriteFile)?;
206 ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207 tracing::info!(%va_NtWriteFile);
208
209 // Insert breakpoint for the `PspInsertProcess` function.
210 let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211 let cx_PspInsertProcess = (va_PspInsertProcess, root);
212 let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213 .global()
214 .with_tag("PspInsertProcess");
215 bpm.insert(&vmi, bp_PspInsertProcess)?;
216 ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218 // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219 let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220 let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221 let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222 .global()
223 .with_tag("MmCleanProcessAddressSpace");
224 bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225 ptm.monitor(
226 &vmi,
227 cx_MmCleanProcessAddressSpace,
228 view,
229 "MmCleanProcessAddressSpace",
230 )?;
231
232 Ok(Self {
233 terminate_flag,
234 view,
235 bpm,
236 ptm,
237 })
238 }Sourcepub fn monitored_tables(&self) -> usize
pub fn monitored_tables(&self) -> usize
Returns the number of write-protected page table pages.
Sourcepub fn monitored_entries(&self) -> usize
pub fn monitored_entries(&self) -> usize
Returns the number of monitored entries.
Sourcepub fn paged_in_entries(&self) -> usize
pub fn paged_in_entries(&self) -> usize
Returns the number of monitored addresses currently backed by physical memory.
Sourcepub fn monitor(
&mut self,
vmi: &VmiCore<Driver>,
ctx: impl Into<AddressContext>,
view: View,
tag: Tag,
) -> Result<(), VmiError>
pub fn monitor( &mut self, vmi: &VmiCore<Driver>, ctx: impl Into<AddressContext>, view: View, tag: Tag, ) -> Result<(), VmiError>
Begins monitoring a virtual address for page table changes.
Walks the page table hierarchy and write-protects the relevant pages.
Examples found in repository?
67 pub fn new(
68 session: &VmiSession<WindowsOs<Driver>>,
69 profile: &Profile,
70 terminate_flag: Arc<AtomicBool>,
71 ) -> Result<Self, VmiError> {
72 // Capture the current state of the vCPU and get the base address of
73 // the kernel.
74 //
75 // This base address is essential to correctly offset monitored
76 // functions.
77 //
78 // NOTE: `kernel_image_base` tries to find the kernel in the memory
79 // with the help of the CPU registers. On AMD64 architecture,
80 // the kernel image base is usually found using the `MSR_LSTAR`
81 // register, which contains the address of the system call
82 // handler. This register is set by the operating system during
83 // boot and is left unchanged (unless some rootkits are involved).
84 //
85 // Therefore, we can take an arbitrary registers at any point
86 // in time (as long as the OS has booted and the page tables are
87 // set up) and use them to find the kernel image base.
88 let registers = session.registers(VcpuId(0))?;
89 let vmi = session.with_registers(®isters);
90
91 let kernel_image_base = vmi.os().kernel_image_base()?;
92 tracing::info!(%kernel_image_base);
93
94 // Get the system process.
95 //
96 // The system process is the first process created by the kernel.
97 // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98 // To monitor page table entries, we need to locate the translation root
99 // of this process.
100 let system_process = vmi.os().system_process()?;
101 tracing::info!(system_process = %system_process.object()?);
102
103 // Get the translation root of the system process.
104 // This is effectively "the CR3 of the kernel".
105 //
106 // The translation root is the root of the page table hierarchy (also
107 // known as the Directory Table Base or PML4).
108 let root = system_process.translation_root()?;
109 tracing::info!(%root);
110
111 // Load the symbols from the profile.
112 let symbols = Symbols::new(profile)?;
113
114 // Enable monitoring of the INT3 and singlestep events.
115 //
116 // INT3 is used to monitor the execution of specific functions.
117 // Singlestep is used to monitor the modifications of page table
118 // entries.
119 vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120 vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122 // Create a new view for the monitor.
123 // This view is used for monitoring function calls and memory accesses.
124 let view = vmi.create_view(MemoryAccess::RWX)?;
125 vmi.switch_to_view(view)?;
126
127 // Create a new breakpoint controller.
128 //
129 // The breakpoint controller is used to insert breakpoints for specific
130 // functions.
131 //
132 // From the guest's perspective, these breakpoints are "hidden", since
133 // the breakpoint controller will unset the read/write access to the
134 // physical memory page where the breakpoint is inserted, while keeping
135 // the execute access.
136 //
137 // This way, the guest will be able to execute the code, but attempts to
138 // read or write the memory will trigger the `memory_access` callback.
139 //
140 // When a vCPU tries to execute the breakpoint instruction:
141 // - an `interrupt` callback will be triggered
142 // - the breakpoint will be handled (e.g., log the function call)
143 // - a fast-singlestep[1] will be performed over the INT3 instruction
144 //
145 // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146 // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147 // access type)
148 // - fast-singlestep[1] will be performed over the instruction that tried to
149 // read the memory
150 //
151 // This way, the instruction will read the original memory content.
152 //
153 // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154 // to a different view, execute a single instruction, and then
155 // switch back to the original view. In this case, the view is
156 // switched to the `default_view` (which is unmodified).
157 let mut bpm = BreakpointManager::new();
158
159 // Create a new page table monitor.
160 //
161 // The page table monitor is used to monitor the page table entries of
162 // the hooked functions.
163 //
164 // More specifically, it is used to monitor the pages that the breakpoint
165 // was inserted into. This is necessary to handle the case when the
166 // page containing the breakpoint is paged out (and then paged in
167 // again).
168 //
169 // `PageTableMonitor` works by unsetting the write access to the page
170 // tables of the hooked functions. When the page is paged out, the
171 // `PRESENT` bit in the page table entry is unset and, conversely, when
172 // the page is paged in, the `PRESENT` bit is set again.
173 //
174 // When that happens:
175 // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176 // access type)
177 // - the callback will mark the page as dirty in the page table monitor
178 // - a singlestep will be performed over the instruction that tried to modify
179 // the memory containing the page table entry
180 // - the `singlestep` handler will process the dirty page table entries and
181 // inform the breakpoint controller to handle the changes
182 let mut ptm = PageTableMonitor::new();
183
184 // Pause the VM to avoid race conditions between inserting breakpoints
185 // and monitoring page table entries. The VM resumes when the pause
186 // guard is dropped.
187 let _pause_guard = vmi.pause_guard()?;
188
189 // Insert breakpoint for the `NtCreateFile` function.
190 let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191 let cx_NtCreateFile = (va_NtCreateFile, root);
192 let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193 .global()
194 .with_tag("NtCreateFile");
195 bpm.insert(&vmi, bp_NtCreateFile)?;
196 ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197 tracing::info!(%va_NtCreateFile);
198
199 // Insert breakpoint for the `NtWriteFile` function.
200 let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201 let cx_NtWriteFile = (va_NtWriteFile, root);
202 let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203 .global()
204 .with_tag("NtWriteFile");
205 bpm.insert(&vmi, bp_NtWriteFile)?;
206 ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207 tracing::info!(%va_NtWriteFile);
208
209 // Insert breakpoint for the `PspInsertProcess` function.
210 let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211 let cx_PspInsertProcess = (va_PspInsertProcess, root);
212 let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213 .global()
214 .with_tag("PspInsertProcess");
215 bpm.insert(&vmi, bp_PspInsertProcess)?;
216 ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218 // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219 let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220 let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221 let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222 .global()
223 .with_tag("MmCleanProcessAddressSpace");
224 bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225 ptm.monitor(
226 &vmi,
227 cx_MmCleanProcessAddressSpace,
228 view,
229 "MmCleanProcessAddressSpace",
230 )?;
231
232 Ok(Self {
233 terminate_flag,
234 view,
235 bpm,
236 ptm,
237 })
238 }Sourcepub fn unmonitor(
&mut self,
vmi: &VmiCore<Driver>,
ctx: impl Into<AddressContext>,
view: View,
) -> Result<(), VmiError>
pub fn unmonitor( &mut self, vmi: &VmiCore<Driver>, ctx: impl Into<AddressContext>, view: View, ) -> Result<(), VmiError>
Stops monitoring a virtual address.
Removes write protection from page table pages that no longer have any monitored entries.
Sourcepub fn unmonitor_all(&mut self, vmi: &VmiCore<Driver>)
pub fn unmonitor_all(&mut self, vmi: &VmiCore<Driver>)
Stops monitoring all virtual addresses and restores memory access.
Sourcepub fn unmonitor_view(&mut self, vmi: &VmiCore<Driver>, view: View)
pub fn unmonitor_view(&mut self, vmi: &VmiCore<Driver>, view: View)
Stops monitoring all virtual addresses associated with a view.
Sourcepub fn mark_dirty_entry(
&mut self,
entry_pa: Pa,
view: View,
vcpu_id: VcpuId,
) -> bool
pub fn mark_dirty_entry( &mut self, entry_pa: Pa, view: View, vcpu_id: VcpuId, ) -> bool
Marks a page table entry as dirty after a write to a monitored page.
Returns true if the entry is being monitored (and was marked),
false otherwise.
Examples found in repository?
241 fn memory_access(
242 &mut self,
243 vmi: &VmiContext<WindowsOs<Driver>>,
244 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
245 let memory_access = vmi.event().reason().as_memory_access();
246
247 tracing::trace!(
248 pa = %memory_access.pa,
249 va = %memory_access.va,
250 access = %memory_access.access,
251 );
252
253 if memory_access.access.contains(MemoryAccess::W) {
254 // It is assumed that a write memory access event is caused by a
255 // page table modification.
256 //
257 // The page table entry is marked as dirty in the page table monitor
258 // and a singlestep is performed to process the dirty entries.
259 self.ptm
260 .mark_dirty_entry(memory_access.pa, self.view, vmi.event().vcpu_id());
261
262 Ok(VmiEventResponse::singlestep().with_view(vmi.default_view()))
263 }
264 else if memory_access.access.contains(MemoryAccess::R) {
265 // When the guest tries to read from the memory, a fast-singlestep
266 // is performed over the instruction that tried to read the memory.
267 // This is done to allow the instruction to read the original memory
268 // content.
269 Ok(VmiEventResponse::fast_singlestep(vmi.default_view()))
270 }
271 else {
272 panic!("Unhandled memory access: {memory_access:?}");
273 }
274 }Sourcepub fn process_dirty_entries(
&mut self,
vmi: &VmiCore<Driver>,
vcpu_id: VcpuId,
) -> Result<Vec<PageTableMonitorEvent>, VmiError>
pub fn process_dirty_entries( &mut self, vmi: &VmiCore<Driver>, vcpu_id: VcpuId, ) -> Result<Vec<PageTableMonitorEvent>, VmiError>
Processes dirty entries for a vCPU, returning page-in/page-out events.
Should be called after singlestepping past the write that triggered the dirty marking, so the new PTE value is committed to memory.
Examples found in repository?
324 fn singlestep(
325 &mut self,
326 vmi: &VmiContext<WindowsOs<Driver>>,
327 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
328 // Get the page table modifications by processing the dirty page table
329 // entries.
330 let ptm_events = self.ptm.process_dirty_entries(vmi, vmi.event().vcpu_id())?;
331
332 // Let the breakpoint controller handle the page table modifications.
333 self.bpm.handle_ptm_events(vmi, ptm_events)?;
334
335 // Disable singlestep and switch back to our view.
336 Ok(VmiEventResponse::default().with_view(self.view))
337 }Auto Trait Implementations§
impl<Driver, Tag> Freeze for PageTableMonitor<Driver, Tag>
impl<Driver, Tag> RefUnwindSafe for PageTableMonitor<Driver, Tag>
impl<Driver, Tag> Send for PageTableMonitor<Driver, Tag>
impl<Driver, Tag> Sync for PageTableMonitor<Driver, Tag>
impl<Driver, Tag> Unpin for PageTableMonitor<Driver, Tag>
impl<Driver, Tag> UnsafeUnpin for PageTableMonitor<Driver, Tag>
impl<Driver, Tag> UnwindSafe for PageTableMonitor<Driver, Tag>
Blanket Implementations§
Source§impl<T> ArchivePointee for T
impl<T> ArchivePointee for T
Source§type ArchivedMetadata = ()
type ArchivedMetadata = ()
Source§fn pointer_metadata(
_: &<T as ArchivePointee>::ArchivedMetadata,
) -> <T as Pointee>::Metadata
fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> LayoutRaw for T
impl<T> LayoutRaw for T
Source§fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
Source§impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
Source§unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
Source§fn resolve_niched(out: Place<NichedOption<T, N1>>)
fn resolve_niched(out: Place<NichedOption<T, N1>>)
out indicating that a T is niched.