Skip to main content

tsffs/os/windows/
kernel.rs

1use std::{
2    collections::{HashMap, HashSet},
3    path::{Path, PathBuf},
4};
5
6use anyhow::{anyhow, bail, Result};
7use pdb::{FallibleIterator, SymbolData};
8use simics::{debug, get_attribute, get_object, info, ConfObject};
9use vergilius::bindings::*;
10use windows_sys::Win32::System::{
11    Diagnostics::Debug::{IMAGE_DIRECTORY_ENTRY_EXPORT, IMAGE_NT_HEADERS64},
12    Kernel::LIST_ENTRY,
13    SystemServices::{
14        IMAGE_DOS_HEADER, IMAGE_DOS_SIGNATURE, IMAGE_EXPORT_DIRECTORY, IMAGE_NT_SIGNATURE,
15    },
16};
17
18use crate::os::{
19    windows::{
20        debug_info::DebugInfo,
21        idt::IdtEntry64,
22        util::{read_nul_terminated_string, read_unicode_string, read_virtual},
23    },
24    DebugInfoConfig,
25};
26
27use super::{
28    debug_info::{Module, Process},
29    structs::{WindowsEProcess, WindowsKThread, WindowsKpcr, WindowsKprcb},
30};
31
32const KUSER_SHARED_DATA_ADDRESS_X86_64: u64 = 0xFFFFF78000000000;
33
34/// Returns whether the given page is the ntos kernel
35pub fn page_is_kernel(processor: *mut ConfObject, address: u64) -> Result<bool> {
36    const OPTIONAL_HEADER_SIGNATURE_PE32: u16 = 0x10b;
37    const OPTIONAL_HEADER_SIGNATURE_PE32_PLUS: u16 = 0x20b;
38
39    let dos_header = read_virtual::<IMAGE_DOS_HEADER>(processor, address)?;
40
41    if dos_header.e_magic != IMAGE_DOS_SIGNATURE {
42        return Ok(false);
43    }
44
45    let nt_header =
46        read_virtual::<IMAGE_NT_HEADERS64>(processor, address + dos_header.e_lfanew as u64)?;
47
48    if nt_header.Signature != IMAGE_NT_SIGNATURE {
49        debug!(
50            "NT Signature {:#x} != {:#x}",
51            nt_header.Signature, IMAGE_NT_SIGNATURE
52        );
53        return Ok(false);
54    }
55
56    debug!(
57        "Found NT signature at {:#x}",
58        address + dos_header.e_lfanew as u64
59    );
60
61    if nt_header.FileHeader.SizeOfOptionalHeader == 0 {
62        debug!(get_object("tsffs")?, "Optional header size was 0");
63        return Ok(false);
64    }
65
66    if ![
67        OPTIONAL_HEADER_SIGNATURE_PE32,
68        OPTIONAL_HEADER_SIGNATURE_PE32_PLUS,
69    ]
70    .contains(&nt_header.OptionalHeader.Magic)
71    {
72        debug!(
73            "Optional header magic {:#x} unrecognized",
74            nt_header.OptionalHeader.Magic
75        );
76        return Ok(false);
77    }
78
79    let image_size = nt_header.OptionalHeader.SizeOfImage as u64;
80
81    debug!(get_object("tsffs")?, "Image size is {:#x}", image_size);
82
83    let export_header_offset = nt_header.OptionalHeader.DataDirectory
84        [IMAGE_DIRECTORY_ENTRY_EXPORT as usize]
85        .VirtualAddress as u64;
86    let export_header_size =
87        nt_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT as usize].Size as u64;
88
89    if export_header_offset == 0 || export_header_offset >= image_size {
90        debug!(
91            "Export header offset {:#x} invalid for image sized {:#x}",
92            export_header_offset, image_size
93        );
94        return Ok(false);
95    }
96
97    if export_header_size == 0 || export_header_offset + export_header_size > image_size {
98        debug!(
99            "Export header size {:#x} and offset {:#x} invalid for image sized {:#x}",
100            export_header_size, export_header_offset, image_size
101        );
102        return Ok(false);
103    }
104
105    debug!(
106        "Export header offset {:#x} size {:#x}",
107        export_header_offset, export_header_size
108    );
109
110    let export_directory =
111        read_virtual::<IMAGE_EXPORT_DIRECTORY>(processor, address + export_header_offset)?;
112
113    let name = read_nul_terminated_string(processor, address + export_directory.Name as u64)?;
114
115    debug!(get_object("tsffs")?, "Read image name {}", name);
116
117    if name == "ntoskrnl.exe" {
118        return Ok(true);
119    }
120
121    Ok(false)
122}
123
124/// Search for the ntos kernel within a given address range
125pub fn find_kernel(processor: *mut ConfObject, start: u64, step: u64) -> Result<u64> {
126    let mut scan_address = start & !(step - 1);
127    let stop_address = start & !(0x1000000000000 - 1);
128
129    debug!(
130        "Scanning for kernel from {:#x}->{:#x}",
131        stop_address, scan_address
132    );
133
134    while scan_address >= stop_address {
135        if page_is_kernel(processor, scan_address)? {
136            return Ok(scan_address);
137        }
138
139        scan_address -= step;
140    }
141
142    bail!("Kernel not found");
143}
144
145/// Search for the ntos kernel by using the IDT to determine scan ranges, given a build number
146pub fn find_kernel_with_idt(processor: *mut ConfObject, build: u32) -> Result<u64> {
147    let sim_idtr_base: u64 = get_attribute(processor, "idtr_base")?.try_into()?;
148
149    for i in 0..256 {
150        // try each idtr entry
151        let idtr_entry0 = read_virtual::<IdtEntry64>(
152            processor,
153            sim_idtr_base + (i * std::mem::size_of::<IdtEntry64>() as u64),
154        )?;
155        if !idtr_entry0.present() {
156            debug!(get_object("tsffs")?, "Entry {} not present, skipping", i);
157            continue;
158        }
159        let idtr_entry0_offset = idtr_entry0.offset();
160        debug!(
161            get_object("tsffs")?,
162            "Got valid IDT entry with offset {:#x}", idtr_entry0_offset
163        );
164        return find_kernel(
165            processor,
166            idtr_entry0_offset,
167            if build >= 19000 { 0x200000 } else { 0x1000 },
168        );
169    }
170
171    bail!("Kernel not found");
172}
173
174#[derive(Debug)]
175/// Information about the currently running windows kernel
176pub struct KernelInfo {
177    /// The base address of the kernel
178    pub base: u64,
179    /// The major version of windows
180    pub major: u32,
181    /// The minor version of windows
182    pub minor: u32,
183    /// the build number of windows
184    pub build: u32,
185    /// The loaded debug information for the kernel
186    pub debug_info: DebugInfo<'static>,
187}
188
189impl KernelInfo {
190    /// Create a new kernel info struct
191    pub fn new<P>(
192        processor: *mut ConfObject,
193        name: &str,
194        base: u64,
195        download_directory: P,
196        not_found_full_name_cache: &mut HashSet<String>,
197        user_debug_info: &mut DebugInfoConfig,
198    ) -> Result<Self>
199    where
200        P: AsRef<Path>,
201    {
202        let debug_info = DebugInfo::new(
203            processor,
204            name,
205            base,
206            download_directory,
207            not_found_full_name_cache,
208            // NOTE: We override that system must be true for the kernel because we must
209            // download it
210            &DebugInfoConfig {
211                system: true,
212                user_debug_info: user_debug_info.user_debug_info,
213                coverage: user_debug_info.coverage,
214            },
215        )?
216        .ok_or_else(|| anyhow!("Failed to get debug info for kernel"))?;
217
218        let kuser_shared_data = read_virtual::<windows_10_0_22631_2428_x64::_KUSER_SHARED_DATA>(
219            processor,
220            KUSER_SHARED_DATA_ADDRESS_X86_64,
221        )?;
222
223        Ok(Self {
224            base,
225            major: kuser_shared_data.NtMajorVersion,
226            minor: kuser_shared_data.NtMinorVersion,
227            build: kuser_shared_data.NtBuildNumber,
228            debug_info,
229        })
230    }
231
232    fn find_ps_loaded_module_list_address(&mut self) -> Result<u64> {
233        // PsLoadedModuleList is either a public(publicsymbol) in the PDB file, or if it is not
234        // in the PDB file, we can find it via the export table in the PE file.
235        let pdb_symbols = self.debug_info.pdb.global_symbols()?;
236        let pdb_address_map = self.debug_info.pdb.address_map()?;
237        if let Ok(Some(ps_loaded_module_list_symbol)) =
238            pdb_symbols.iter().find_map(|symbol| match symbol.parse() {
239                Ok(symbol) => {
240                    if let SymbolData::Public(public_symbol) = symbol {
241                        if public_symbol.name.to_string() == "PsLoadedModuleList" {
242                            Ok(Some(
243                                public_symbol
244                                    .offset
245                                    .to_rva(&pdb_address_map)
246                                    .ok_or(pdb::Error::AddressMapNotFound)?
247                                    .0 as u64
248                                    + self.base,
249                            ))
250                        } else {
251                            Ok(None)
252                        }
253                    } else {
254                        Ok(None)
255                    }
256                }
257                Err(e) => Err(e),
258            })
259        {
260            Ok(ps_loaded_module_list_symbol)
261        } else {
262            self.debug_info
263                .exports()?
264                .iter()
265                .find(|export| {
266                    export
267                        .name
268                        .as_ref()
269                        .is_some_and(|name| name == "PsLoadedModuleList")
270                })
271                .map(|export| export.rva as u64 + self.base)
272                .ok_or_else(|| anyhow!("PsLoadedModuleList not found"))
273        }
274    }
275
276    /// Return the list of currently loaded modules
277    pub fn loaded_module_list<P>(
278        &mut self,
279        processor: *mut ConfObject,
280        download_directory: P,
281        not_found_full_name_cache: &mut HashSet<String>,
282        user_debug_info: &DebugInfoConfig,
283    ) -> Result<Vec<Module>>
284    where
285        P: AsRef<Path>,
286    {
287        let list_address = self.find_ps_loaded_module_list_address()?;
288        debug!(
289            get_object("tsffs")?,
290            "PsLoadedModuleList: {:#x}", list_address
291        );
292        let list = read_virtual::<LIST_ENTRY>(processor, list_address)?;
293        let mut modules = Vec::new();
294
295        let mut current = list.Flink;
296
297        while !std::ptr::eq(current, list_address as *mut LIST_ENTRY) {
298            // NOTE: _KLDR_DATA_TABLE_ENTRY struct is stable for all versions of 10, *except* for the following fields:
299            // union
300            // {
301            //     VOID* Spare;                                                        //0x90
302            //     struct _KLDR_DATA_TABLE_ENTRY* NtDataTableEntry;                    //0x90
303            // };
304            // ULONG SizeOfImageNotRounded;                                            //0x98
305            // ULONG TimeDateStamp;                                                    //0x9c
306            //
307            // We don't use these, so it's ok to just use the latest version of this struct instead of generalizing
308            let ldr_data_table_entry = read_virtual::<
309                windows_10_0_22631_2428_x64::_KLDR_DATA_TABLE_ENTRY,
310            >(processor, current as u64)?;
311
312            let base = ldr_data_table_entry.DllBase as u64;
313            let entry = ldr_data_table_entry.EntryPoint as u64;
314            let size = ldr_data_table_entry.SizeOfImage as u64;
315            let full_name = read_unicode_string(
316                processor,
317                ldr_data_table_entry.FullDllName.Length as usize,
318                ldr_data_table_entry.FullDllName.Buffer,
319            )?;
320            let base_name = read_unicode_string(
321                processor,
322                ldr_data_table_entry.BaseDllName.Length as usize,
323                ldr_data_table_entry.BaseDllName.Buffer,
324            )?;
325            let debug_info = full_name
326                .split('\\')
327                .next_back()
328                .ok_or_else(|| anyhow!("Failed to get file name"))
329                .and_then(|fname| {
330                    // No need for DTB version because kernel is always mapped
331                    DebugInfo::new(
332                        processor,
333                        fname,
334                        base,
335                        download_directory.as_ref(),
336                        not_found_full_name_cache,
337                        user_debug_info,
338                    )
339                })
340                .ok()
341                .flatten();
342
343            modules.push(Module {
344                base,
345                entry,
346                size,
347                full_name,
348                base_name,
349                debug_info,
350            });
351
352            current = ldr_data_table_entry.InLoadOrderLinks.Flink as *mut _;
353        }
354
355        Ok(modules)
356    }
357
358    fn find_ps_initial_system_process_address(&mut self) -> Result<u64> {
359        // PsInitialSystemProcess is either a public(publicsymbol) in the PDB file, or if it is not
360        // in the PDB file, we can find it via the export table in the PE file.
361        let pdb_symbols = self.debug_info.pdb.global_symbols()?;
362        let pdb_address_map = self.debug_info.pdb.address_map()?;
363        if let Ok(Some(ps_initial_system_process_symbol)) =
364            pdb_symbols.iter().find_map(|symbol| match symbol.parse() {
365                Ok(symbol) => {
366                    if let SymbolData::Public(public_symbol) = symbol {
367                        if public_symbol.name.to_string() == "PsInitialSystemProcess" {
368                            Ok(Some(
369                                public_symbol
370                                    .offset
371                                    .to_rva(&pdb_address_map)
372                                    .ok_or(pdb::Error::AddressMapNotFound)?
373                                    .0 as u64
374                                    + self.base,
375                            ))
376                        } else {
377                            Ok(None)
378                        }
379                    } else {
380                        Ok(None)
381                    }
382                }
383                Err(e) => Err(e),
384            })
385        {
386            Ok(ps_initial_system_process_symbol)
387        } else {
388            self.debug_info
389                .exports()?
390                .iter()
391                .find(|export| {
392                    export
393                        .name
394                        .as_ref()
395                        .is_some_and(|name| name == "PsInitialSystemProcess")
396                })
397                .map(|export| export.rva as u64 + self.base)
398                .ok_or_else(|| anyhow!("PsInitialSystemProcess not found"))
399        }
400    }
401
402    /// Return the currently running process
403    pub fn current_process<P>(
404        &mut self,
405        processor: *mut ConfObject,
406        download_directory: P,
407        not_found_full_name_cache: &mut HashSet<String>,
408        user_debug_info: &DebugInfoConfig,
409    ) -> Result<Process>
410    where
411        P: AsRef<Path>,
412    {
413        let kpcr = WindowsKpcr::new(processor, self.major, self.minor, self.build)?;
414        let kprcb = WindowsKprcb::new(
415            processor,
416            self.major,
417            self.minor,
418            self.build,
419            kpcr.kpcrb_address(),
420        )?;
421        let kthread = WindowsKThread::new(
422            processor,
423            self.major,
424            self.minor,
425            self.build,
426            kprcb.current_thread(),
427        )?;
428        let eprocess = kthread.process(processor, self.major, self.minor, self.build)?;
429
430        Ok(Process {
431            pid: eprocess.pid(),
432            file_name: eprocess.file_name(processor)?,
433            base_address: eprocess.base_address(processor, self.major, self.minor, self.build)?,
434            modules: eprocess
435                .modules(
436                    processor,
437                    self.major,
438                    self.minor,
439                    self.build,
440                    download_directory.as_ref(),
441                    not_found_full_name_cache,
442                    user_debug_info,
443                )
444                .unwrap_or_default(),
445        })
446    }
447
448    /// Return the list of loaded processes
449    pub fn process_list<P>(
450        &mut self,
451        processor: *mut ConfObject,
452        download_directory: P,
453        not_found_full_name_cache: &mut HashSet<String>,
454        user_debug_info: &DebugInfoConfig,
455    ) -> Result<Vec<Process>>
456    where
457        P: AsRef<Path>,
458    {
459        let kpcr = WindowsKpcr::new(processor, self.major, self.minor, self.build)?;
460        let kprcb = WindowsKprcb::new(
461            processor,
462            self.major,
463            self.minor,
464            self.build,
465            kpcr.kpcrb_address(),
466        )?;
467        let kthread = WindowsKThread::new(
468            processor,
469            self.major,
470            self.minor,
471            self.build,
472            kprcb.current_thread(),
473        )?;
474        let eprocess = kthread.process(processor, self.major, self.minor, self.build)?;
475        // Print initial process info
476
477        let mut processes = Vec::new();
478
479        let mut list_entry = eprocess.active_process_links();
480        let last_entry = list_entry.Blink;
481
482        while !list_entry.Flink.is_null() {
483            let eprocess = WindowsEProcess::new_from_active_process_links_address(
484                processor,
485                self.major,
486                self.minor,
487                self.build,
488                list_entry.Flink as u64,
489            )?;
490            let pid = eprocess.pid();
491            let file_name = eprocess.file_name(processor)?;
492            let base_address =
493                eprocess.base_address(processor, self.major, self.minor, self.build)?;
494            debug!(
495                get_object("tsffs")?,
496                "Found process {} at {:#x}", file_name, base_address
497            );
498            let modules = eprocess
499                .modules(
500                    processor,
501                    self.major,
502                    self.minor,
503                    self.build,
504                    download_directory.as_ref(),
505                    not_found_full_name_cache,
506                    user_debug_info,
507                )
508                .unwrap_or_default();
509            debug!(get_object("tsffs")?, "Found {} modules", modules.len());
510
511            processes.push(Process {
512                pid,
513                file_name,
514                base_address,
515                modules,
516            });
517
518            list_entry = eprocess.active_process_links();
519
520            if std::ptr::eq(list_entry.Flink, last_entry) {
521                break;
522            }
523        }
524
525        Ok(processes)
526    }
527}