memflow_win32/win32/
kernel.rs

1mod mem_map;
2
3use crate::{
4    offsets::{Win32ArchOffsets, Win32Offsets},
5    prelude::{VirtualReadUnicodeString, Win32ExitStatus, EXIT_STATUS_STILL_ACTIVE},
6};
7
8use super::{
9    process::IMAGE_FILE_NAME_LENGTH, Win32KernelBuilder, Win32KernelInfo, Win32Keyboard,
10    Win32ModuleListInfo, Win32Process, Win32ProcessInfo, Win32VirtualTranslate,
11};
12
13use memflow::mem::virt_translate::*;
14use memflow::prelude::v1::{Result, *};
15
16#[cfg(feature = "plugins")]
17use memflow::cglue;
18#[cfg(feature = "plugins")]
19use memflow::mem::{memory_view::*, phys_mem::*};
20#[cfg(feature = "plugins")]
21use memflow::os::keyboard::*;
22
23use log::{info, trace};
24use std::convert::TryInto;
25use std::fmt;
26use std::prelude::v1::*;
27
28use pelite::{self, pe64::exports::Export, PeView};
29
30const MAX_ITER_COUNT: usize = 65536;
31
32#[cfg(feature = "plugins")]
33cglue_impl_group!(Win32Kernel<T, V>, OsInstance<'a>, { PhysicalMemory, MemoryView, VirtualTranslate, OsKeyboard });
34
35#[derive(Clone)]
36pub struct Win32Kernel<T, V> {
37    pub virt_mem: VirtualDma<T, V, Win32VirtualTranslate>,
38    pub offsets: Win32Offsets,
39
40    pub kernel_info: Win32KernelInfo,
41    pub sysproc_dtb: Address,
42
43    pub kernel_modules: Option<Win32ModuleListInfo>,
44}
45
46impl<T: 'static + PhysicalMemory + Clone, V: 'static + VirtualTranslate2 + Clone>
47    Win32Kernel<T, V>
48{
49    pub fn new(phys_mem: T, vat: V, offsets: Win32Offsets, kernel_info: Win32KernelInfo) -> Self {
50        let mut virt_mem = VirtualDma::with_vat(
51            phys_mem,
52            kernel_info.os_info.arch,
53            Win32VirtualTranslate::new(kernel_info.os_info.arch, kernel_info.dtb),
54            vat,
55        );
56
57        if offsets.phys_mem_block() != 0 {
58            match kernel_info.os_info.arch.into_obj().bits() {
59                32 => {
60                    if let Some(mem_map) = mem_map::parse::<_, u32>(
61                        &mut virt_mem,
62                        kernel_info.os_info.base + offsets.phys_mem_block(),
63                    ) {
64                        // update mem mapping in connector
65                        info!("updating connector mem_map={:?}", mem_map);
66                        let (mut phys_mem, vat) = virt_mem.into_inner();
67                        phys_mem.set_mem_map(mem_map.into_vec().as_slice());
68                        virt_mem = VirtualDma::with_vat(
69                            phys_mem,
70                            kernel_info.os_info.arch,
71                            Win32VirtualTranslate::new(kernel_info.os_info.arch, kernel_info.dtb),
72                            vat,
73                        );
74                    }
75                }
76                64 => {
77                    if let Some(mem_map) = mem_map::parse::<_, u64>(
78                        &mut virt_mem,
79                        kernel_info.os_info.base + offsets.phys_mem_block(),
80                    ) {
81                        // update mem mapping in connector
82                        info!("updating connector mem_map={:?}", mem_map);
83                        let (mut phys_mem, vat) = virt_mem.into_inner();
84                        phys_mem.set_mem_map(mem_map.into_vec().as_slice());
85                        virt_mem = VirtualDma::with_vat(
86                            phys_mem,
87                            kernel_info.os_info.arch,
88                            Win32VirtualTranslate::new(kernel_info.os_info.arch, kernel_info.dtb),
89                            vat,
90                        );
91                    }
92                }
93                _ => {}
94            }
95        }
96
97        // start_block only contains the winload's dtb which might
98        // be different to the one used in the actual kernel.
99        // In case of a failure this will fall back to the winload dtb.
100        // Read dtb of first process in eprocess list:
101        let sysproc_dtb = if let Some(Some(dtb)) = virt_mem
102            .read_addr_arch(
103                kernel_info.os_info.arch.into(),
104                kernel_info.eprocess_base + offsets.kproc_dtb(),
105            )
106            .ok()
107            .map(|a| a.as_page_aligned(4096).non_null())
108        {
109            info!("updating sysproc_dtb={:x}", dtb);
110            let (phys_mem, vat) = virt_mem.into_inner();
111            virt_mem = VirtualDma::with_vat(
112                phys_mem,
113                kernel_info.os_info.arch,
114                Win32VirtualTranslate::new(kernel_info.os_info.arch, dtb),
115                vat,
116            );
117            dtb
118        } else {
119            kernel_info.dtb
120        };
121
122        Self {
123            virt_mem,
124            offsets,
125
126            kernel_info,
127            sysproc_dtb,
128            kernel_modules: None,
129        }
130    }
131
132    pub fn kernel_modules(&mut self) -> Result<Win32ModuleListInfo> {
133        if let Some(info) = self.kernel_modules {
134            Ok(info)
135        } else {
136            let image = self.virt_mem.read_raw(
137                self.kernel_info.os_info.base,
138                self.kernel_info.os_info.size.try_into().unwrap(),
139            )?;
140            let pe = PeView::from_bytes(&image).map_err(|err| {
141                Error(ErrorOrigin::OsLayer, ErrorKind::InvalidExeFile).log_info(err)
142            })?;
143            let addr = match pe.get_export_by_name("PsLoadedModuleList").map_err(|err| {
144                Error(ErrorOrigin::OsLayer, ErrorKind::ExportNotFound).log_info(err)
145            })? {
146                Export::Symbol(s) => self.kernel_info.os_info.base + *s as umem,
147                Export::Forward(_) => {
148                    return Err(Error(ErrorOrigin::OsLayer, ErrorKind::ExportNotFound)
149                        .log_info("PsLoadedModuleList found but it was a forwarded export"))
150                }
151            };
152
153            let addr = self
154                .virt_mem
155                .read_addr_arch(self.kernel_info.os_info.arch.into(), addr)?;
156
157            let info = Win32ModuleListInfo::with_base(addr, self.kernel_info.os_info.arch)?;
158
159            self.kernel_modules = Some(info);
160            Ok(info)
161        }
162    }
163
164    /// Consumes this kernel and return the underlying owned memory and vat objects
165    pub fn into_inner(self) -> (T, V) {
166        self.virt_mem.into_inner()
167    }
168
169    pub fn kernel_process_info(&mut self) -> Result<Win32ProcessInfo> {
170        let kernel_modules = self.kernel_modules()?;
171
172        let vad_root = self.read_addr_arch(
173            self.kernel_info.os_info.arch.into(),
174            self.kernel_info.os_info.base + self.offsets.eproc_vad_root(),
175        )?;
176
177        Ok(Win32ProcessInfo {
178            base_info: ProcessInfo {
179                address: self.kernel_info.os_info.base,
180                pid: 0,
181                state: ProcessState::Alive,
182                name: "ntoskrnl.exe".into(),
183                path: "".into(),
184                command_line: "".into(),
185                sys_arch: self.kernel_info.os_info.arch,
186                proc_arch: self.kernel_info.os_info.arch,
187                dtb1: self.sysproc_dtb,
188                dtb2: Address::invalid(),
189            },
190            section_base: Address::NULL, // TODO: see below
191            ethread: Address::NULL,      // TODO: see below
192            wow64: Address::NULL,
193
194            teb: None,
195            teb_wow64: None,
196
197            peb_native: None,
198            peb_wow64: None,
199
200            module_info_native: Some(kernel_modules),
201            module_info_wow64: None,
202
203            vad_root,
204        })
205    }
206
207    pub fn process_info_from_base_info(
208        &mut self,
209        base_info: ProcessInfo,
210    ) -> Result<Win32ProcessInfo> {
211        let section_base = self.virt_mem.read_addr_arch(
212            self.kernel_info.os_info.arch.into(),
213            base_info.address + self.offsets.eproc_section_base(),
214        )?;
215        trace!("section_base={:x}", section_base);
216
217        // find first ethread
218        let ethread = self.virt_mem.read_addr_arch(
219            self.kernel_info.os_info.arch.into(),
220            base_info.address + self.offsets.eproc_thread_list(),
221        )? - self.offsets.ethread_list_entry();
222        trace!("ethread={:x}", ethread);
223
224        let peb_native = self
225            .virt_mem
226            .read_addr_arch(
227                self.kernel_info.os_info.arch.into(),
228                base_info.address + self.offsets.eproc_peb(),
229            )?
230            .non_null();
231
232        // TODO: Avoid doing this twice
233        let wow64 = if self.offsets.eproc_wow64() == 0 {
234            trace!("eproc_wow64=null; skipping wow64 detection");
235            Address::null()
236        } else {
237            trace!(
238                "eproc_wow64={:x}; trying to read wow64 pointer",
239                self.offsets.eproc_wow64()
240            );
241            self.virt_mem.read_addr_arch(
242                self.kernel_info.os_info.arch.into(),
243                base_info.address + self.offsets.eproc_wow64(),
244            )?
245        };
246        trace!("wow64={:x}", wow64);
247
248        let mut peb_wow64 = None;
249
250        // TODO: does this need to be read with the process ctx?
251        let (teb, teb_wow64) = if self.kernel_info.kernel_winver >= (6, 2).into() {
252            let teb = self.virt_mem.read_addr_arch(
253                self.kernel_info.os_info.arch.into(),
254                ethread + self.offsets.kthread_teb(),
255            )?;
256
257            trace!("teb={:x}", teb);
258
259            if !teb.is_null() {
260                (
261                    Some(teb),
262                    if base_info.proc_arch == base_info.sys_arch {
263                        None
264                    } else {
265                        Some(teb + 0x2000)
266                    },
267                )
268            } else {
269                (None, None)
270            }
271        } else {
272            (None, None)
273        };
274
275        let vad_root = self.virt_mem.read_addr_arch(
276            self.kernel_info.os_info.arch.into(),
277            base_info.address + self.offsets.eproc_vad_root(),
278        )?;
279
280        // construct reader with process dtb - win32 only uses/requires one dtb so we always store it in `dtb1`
281        // TODO: can tlb be used here already?
282        let (phys_mem, vat) = self.virt_mem.mem_vat_pair();
283        let mut proc_reader = VirtualDma::with_vat(
284            phys_mem.forward_mut(),
285            base_info.proc_arch,
286            Win32VirtualTranslate::new(self.kernel_info.os_info.arch, base_info.dtb1),
287            vat,
288        );
289
290        if let Some(teb) = teb_wow64 {
291            // from here on out we are in the process context
292            // we will be using the process type architecture now
293            peb_wow64 = proc_reader
294                .read_addr_arch(
295                    self.kernel_info.os_info.arch.into(),
296                    teb + self.offsets.teb_peb_x86(),
297                )?
298                .non_null();
299
300            trace!("peb_wow64={:?}", peb_wow64);
301        }
302
303        trace!("peb_native={:?}", peb_native);
304
305        let module_info_native = peb_native
306            .map(|peb| Win32ModuleListInfo::with_peb(&mut proc_reader, peb, base_info.sys_arch))
307            .transpose()?;
308
309        let module_info_wow64 = peb_wow64
310            .map(|peb| Win32ModuleListInfo::with_peb(&mut proc_reader, peb, base_info.proc_arch))
311            .transpose()?;
312
313        Ok(Win32ProcessInfo {
314            base_info,
315
316            section_base,
317            ethread,
318            wow64,
319
320            teb,
321            teb_wow64,
322
323            peb_native,
324            peb_wow64,
325
326            module_info_native,
327            module_info_wow64,
328
329            vad_root,
330        })
331    }
332
333    fn process_info_fill(&mut self, info: Win32ProcessInfo) -> Result<Win32ProcessInfo> {
334        // get full process name from module list
335        let cloned_base = info.base_info.clone();
336        let mut name = info.base_info.name.clone();
337        let callback = &mut |m: ModuleInfo| {
338            if m.name.as_ref().starts_with(name.as_ref()) {
339                name = m.name;
340                false
341            } else {
342                true
343            }
344        };
345        let sys_arch = info.base_info.sys_arch;
346        let mut process = self.process_by_info(cloned_base)?;
347        process.module_list_callback(Some(&sys_arch), callback.into())?;
348
349        // get process_parameters
350        let offsets = Win32ArchOffsets::from(info.base_info.proc_arch);
351        let (path, command_line) = if let Some(Ok(peb_process_params)) = info.peb().map(|peb| {
352            process.read_addr_arch(
353                info.base_info.proc_arch.into(),
354                peb + offsets.peb_process_params,
355            )
356        }) {
357            trace!("peb_process_params={:x}", peb_process_params);
358            let image_path_name = process
359                .read_unicode_string(
360                    info.base_info.proc_arch.into(),
361                    peb_process_params + offsets.ppm_image_path_name,
362                )
363                .unwrap_or_default();
364
365            let command_line = process
366                .read_unicode_string(
367                    info.base_info.proc_arch.into(),
368                    peb_process_params + offsets.ppm_command_line,
369                )
370                .unwrap_or_default();
371
372            (image_path_name.into(), command_line.into())
373        } else {
374            ("".into(), "".into())
375        };
376
377        Ok(Win32ProcessInfo {
378            base_info: ProcessInfo {
379                name,
380                path,
381                command_line,
382                ..info.base_info
383            },
384            ..info
385        })
386    }
387
388    fn process_info_base_by_address(&mut self, address: Address) -> Result<ProcessInfo> {
389        let dtb = self.virt_mem.read_addr_arch(
390            self.kernel_info.os_info.arch.into(),
391            address + self.offsets.kproc_dtb(),
392        )?;
393        trace!("dtb={:x}", dtb);
394
395        let pid: Pid = self.virt_mem.read(address + self.offsets.eproc_pid())?;
396        trace!("pid={}", pid);
397
398        let state = if let Ok(exit_status) = self
399            .virt_mem
400            .read::<Win32ExitStatus>(address + self.offsets.eproc_exit_status())
401        {
402            if exit_status == EXIT_STATUS_STILL_ACTIVE {
403                ProcessState::Alive
404            } else {
405                ProcessState::Dead(exit_status)
406            }
407        } else {
408            ProcessState::Unknown
409        };
410
411        let name: ReprCString = self
412            .virt_mem
413            .read_char_array(address + self.offsets.eproc_name(), IMAGE_FILE_NAME_LENGTH)?
414            .into();
415        trace!("name={}", name);
416
417        let wow64 = if self.offsets.eproc_wow64() == 0 {
418            trace!("eproc_wow64=null; skipping wow64 detection");
419            Address::null()
420        } else {
421            trace!(
422                "eproc_wow64={:x}; trying to read wow64 pointer",
423                self.offsets.eproc_wow64()
424            );
425            self.virt_mem.read_addr_arch(
426                self.kernel_info.os_info.arch.into(),
427                address + self.offsets.eproc_wow64(),
428            )?
429        };
430        trace!("wow64={:x}", wow64);
431
432        // determine process architecture
433        let sys_arch = self.kernel_info.os_info.arch;
434        trace!("sys_arch={:?}", sys_arch);
435        let proc_arch = match ArchitectureObj::from(sys_arch).bits() {
436            64 => {
437                if wow64.is_null() {
438                    sys_arch
439                } else {
440                    ArchitectureIdent::X86(32, true)
441                }
442            }
443            32 => sys_arch,
444            _ => return Err(Error(ErrorOrigin::OsLayer, ErrorKind::InvalidArchitecture)),
445        };
446        trace!("proc_arch={:?}", proc_arch);
447
448        Ok(ProcessInfo {
449            address,
450            pid,
451            state,
452            name,
453            path: "".into(),
454            command_line: "".into(),
455            sys_arch,
456            proc_arch,
457            dtb1: dtb,
458            dtb2: Address::invalid(),
459        })
460    }
461}
462
463impl<T: PhysicalMemory> Win32Kernel<T, DirectTranslate> {
464    pub fn builder(connector: T) -> Win32KernelBuilder<T, T, DirectTranslate> {
465        Win32KernelBuilder::<T, T, DirectTranslate>::new(connector)
466    }
467}
468
469impl<T: PhysicalMemory, V: VirtualTranslate2> AsMut<T> for Win32Kernel<T, V> {
470    fn as_mut(&mut self) -> &mut T {
471        self.virt_mem.phys_mem()
472    }
473}
474
475impl<T: PhysicalMemory, V: VirtualTranslate2> AsMut<VirtualDma<T, V, Win32VirtualTranslate>>
476    for Win32Kernel<T, V>
477{
478    fn as_mut(&mut self) -> &mut VirtualDma<T, V, Win32VirtualTranslate> {
479        &mut self.virt_mem
480    }
481}
482
483impl<T: PhysicalMemory, V: VirtualTranslate2> PhysicalMemory for Win32Kernel<T, V> {
484    fn phys_read_raw_iter(&mut self, data: PhysicalReadMemOps) -> Result<()> {
485        self.virt_mem.phys_mem().phys_read_raw_iter(data)
486    }
487
488    fn phys_write_raw_iter(&mut self, data: PhysicalWriteMemOps) -> Result<()> {
489        self.virt_mem.phys_mem().phys_write_raw_iter(data)
490    }
491
492    fn metadata(&self) -> PhysicalMemoryMetadata {
493        self.virt_mem.phys_mem_ref().metadata()
494    }
495
496    fn set_mem_map(&mut self, mem_map: &[PhysicalMemoryMapping]) {
497        self.virt_mem.phys_mem().set_mem_map(mem_map)
498    }
499}
500
501impl<T: PhysicalMemory, V: VirtualTranslate2> MemoryView for Win32Kernel<T, V> {
502    fn read_raw_iter(&mut self, data: ReadRawMemOps) -> Result<()> {
503        self.virt_mem.read_raw_iter(data)
504    }
505
506    fn write_raw_iter(&mut self, data: WriteRawMemOps) -> Result<()> {
507        self.virt_mem.write_raw_iter(data)
508    }
509
510    fn metadata(&self) -> MemoryViewMetadata {
511        self.virt_mem.metadata()
512    }
513}
514
515impl<T: PhysicalMemory, V: VirtualTranslate2> VirtualTranslate for Win32Kernel<T, V> {
516    fn virt_to_phys_list(
517        &mut self,
518        addrs: &[VtopRange],
519        out: VirtualTranslationCallback,
520        out_fail: VirtualTranslationFailCallback,
521    ) {
522        self.virt_mem.virt_to_phys_list(addrs, out, out_fail)
523    }
524}
525
526impl<T: 'static + PhysicalMemory + Clone, V: 'static + VirtualTranslate2 + Clone> Os
527    for Win32Kernel<T, V>
528{
529    type ProcessType<'a> = Win32Process<Fwd<&'a mut T>, Fwd<&'a mut V>, Win32VirtualTranslate>;
530    type IntoProcessType = Win32Process<T, V, Win32VirtualTranslate>;
531
532    /// Walks a process list and calls a callback for each process structure address
533    ///
534    /// The callback is fully opaque. We need this style so that C FFI can work seamlessly.
535    fn process_address_list_callback(
536        &mut self,
537        mut callback: AddressCallback,
538    ) -> memflow::error::Result<()> {
539        let list_start = self.kernel_info.eprocess_base + self.offsets.eproc_link();
540        let mut list_entry = list_start;
541
542        for _ in 0..MAX_ITER_COUNT {
543            let eprocess = list_entry - self.offsets.eproc_link();
544            trace!("eprocess={}", eprocess);
545
546            // test flink + blink before adding the process
547            let flink_entry = self
548                .virt_mem
549                .read_addr_arch(self.kernel_info.os_info.arch.into(), list_entry)?;
550            trace!("flink_entry={}", flink_entry);
551            let blink_entry = self.virt_mem.read_addr_arch(
552                self.kernel_info.os_info.arch.into(),
553                list_entry + self.offsets.list_blink(),
554            )?;
555            trace!("blink_entry={}", blink_entry);
556
557            if flink_entry.is_null()
558                || blink_entry.is_null()
559                || flink_entry == list_start
560                || flink_entry == list_entry
561            {
562                break;
563            }
564
565            trace!("found eprocess {:x}", eprocess);
566            if !callback.call(eprocess) {
567                break;
568            }
569            trace!("Continuing {:x} -> {:x}", list_entry, flink_entry);
570
571            // continue
572            list_entry = flink_entry;
573        }
574
575        Ok(())
576    }
577
578    /// Find process information by its internal address
579    fn process_info_by_address(&mut self, address: Address) -> memflow::error::Result<ProcessInfo> {
580        let base_info = self.process_info_base_by_address(address)?;
581        if let Ok(info) = self.process_info_from_base_info(base_info.clone()) {
582            Ok(self.process_info_fill(info)?.base_info)
583        } else {
584            Ok(base_info)
585        }
586    }
587
588    /// Creates a process by its internal address
589    ///
590    /// It will share the underlying memory resources
591    fn process_by_info(
592        &mut self,
593        info: ProcessInfo,
594    ) -> memflow::error::Result<Self::ProcessType<'_>> {
595        let proc_info = self.process_info_from_base_info(info)?;
596        Ok(Win32Process::with_kernel_ref(self, proc_info))
597    }
598
599    /// Creates a process by its internal address
600    ///
601    /// It will consume the kernel and not affect memory usage
602    ///
603    /// If no process with the specified address can be found this function will return an Error.
604    ///
605    /// This function can be useful for quickly accessing a process.
606    fn into_process_by_info(
607        mut self,
608        info: ProcessInfo,
609    ) -> memflow::error::Result<Self::IntoProcessType> {
610        let proc_info = self.process_info_from_base_info(info)?;
611        Ok(Win32Process::with_kernel(self, proc_info))
612    }
613
614    /// Walks the kernel module list and calls the provided callback for each module structure
615    /// address
616    ///
617    /// # Arguments
618    /// * `callback` - where to pass each matching module to. This is an opaque callback.
619    fn module_address_list_callback(
620        &mut self,
621        callback: AddressCallback,
622    ) -> memflow::error::Result<()> {
623        self.kernel_modules()?
624            .module_entry_list_callback::<Self, VirtualDma<T, V, Win32VirtualTranslate>>(
625                self,
626                self.kernel_info.os_info.arch,
627                callback,
628            )
629            .map_err(From::from)
630    }
631
632    /// Retrieves a module by its structure address
633    ///
634    /// # Arguments
635    /// * `address` - address where module's information resides in
636    fn module_by_address(&mut self, address: Address) -> memflow::error::Result<ModuleInfo> {
637        self.kernel_modules()?
638            .module_info_from_entry(
639                address,
640                self.kernel_info.eprocess_base,
641                &mut self.virt_mem,
642                self.kernel_info.os_info.arch,
643            )
644            .map_err(From::from)
645    }
646
647    /// Retrieves address of the primary module structure of the process
648    ///
649    /// This will generally be for the initial executable that was run
650    fn primary_module_address(&mut self) -> Result<Address> {
651        Ok(self.module_by_name("ntoskrnl.exe")?.address)
652    }
653
654    /// Retrieves information for the primary module of the process
655    ///
656    /// This will generally be the initial executable that was run
657    fn primary_module(&mut self) -> Result<ModuleInfo> {
658        self.module_by_name("ntoskrnl.exe")
659    }
660
661    /// Retrieves a list of all imports of a given module
662    fn module_import_list_callback(
663        &mut self,
664        info: &ModuleInfo,
665        callback: ImportCallback,
666    ) -> Result<()> {
667        memflow::os::util::module_import_list_callback(&mut self.virt_mem, info, callback)
668    }
669
670    /// Retrieves a list of all exports of a given module
671    fn module_export_list_callback(
672        &mut self,
673        info: &ModuleInfo,
674        callback: ExportCallback,
675    ) -> Result<()> {
676        memflow::os::util::module_export_list_callback(&mut self.virt_mem, info, callback)
677    }
678
679    /// Retrieves a list of all sections of a given module
680    fn module_section_list_callback(
681        &mut self,
682        info: &ModuleInfo,
683        callback: SectionCallback,
684    ) -> Result<()> {
685        memflow::os::util::module_section_list_callback(&mut self.virt_mem, info, callback)
686    }
687
688    /// Retrieves the kernel info
689    fn info(&self) -> &OsInfo {
690        &self.kernel_info.os_info
691    }
692}
693
694impl<T: 'static + PhysicalMemory + Clone, V: 'static + VirtualTranslate2 + Clone> OsKeyboard
695    for Win32Kernel<T, V>
696{
697    type KeyboardType<'a> =
698        Win32Keyboard<VirtualDma<Fwd<&'a mut T>, Fwd<&'a mut V>, Win32VirtualTranslate>>;
699    type IntoKeyboardType = Win32Keyboard<VirtualDma<T, V, Win32VirtualTranslate>>;
700
701    fn keyboard(&mut self) -> memflow::error::Result<Self::KeyboardType<'_>> {
702        Win32Keyboard::with_kernel_ref(self)
703    }
704
705    fn into_keyboard(self) -> memflow::error::Result<Self::IntoKeyboardType> {
706        Win32Keyboard::with_kernel(self)
707    }
708}
709
710impl<T: PhysicalMemory, V: VirtualTranslate2> fmt::Debug for Win32Kernel<T, V> {
711    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
712        write!(f, "{:?}", self.kernel_info)
713    }
714}