windows_dump/
windows-dump.rs

1//! This example demonstrates how to use the VMI library to analyze a Windows
2//! kernel dump file.
3//!
4//! # Possible log output
5//!
6//! ```text
7//! Kernel Modules:
8//! =================================================
9//! Module @ 0xffffd486baa62b80
10//!     Base Address: 0xfffff8016e80f000
11//!     Size: 17068032
12//!     Name: ntoskrnl.exe
13//!     Full Name: \SystemRoot\system32\ntoskrnl.exe
14//! Module @ 0xffffd486baa77050
15//!     Base Address: 0xfffff8016fa00000
16//!     Size: 24576
17//!     Name: hal.dll
18//!     Full Name: \SystemRoot\system32\hal.dll
19//! Module @ 0xffffd486baa77200
20//!     Base Address: 0xfffff8016fa10000
21//!     Size: 45056
22//!     Name: kdcom.dll
23//!     Full Name: \SystemRoot\system32\kd.dll
24//!
25//! ...
26//!
27//! Object Tree (root directory: 0xffffbe8b62c7ae10):
28//! =================================================
29//! Mutant: \PendingRenameMutex (Object: 0xffffd486bb6d6bd0)
30//! Directory: \ObjectTypes (Object: 0xffffbe8b62c900a0)
31//!     Type: \ObjectTypes\TmTm (Object: 0xffffd486bab8a220)
32//!     Type: \ObjectTypes\CpuPartition (Object: 0xffffd486baabdda0)
33//!     Type: \ObjectTypes\Desktop (Object: 0xffffd486baabc220)
34//!
35//! ...
36//!
37//! Process @ 0xffffd486be782080, PID: 1244
38//!     Name: svchost.exe
39//!     Session: 0
40//!     Threads:
41//!         Thread @ 0xffffd486be789080, TID: 1248
42//!         Thread @ 0xffffd486be7f0080, TID: 1464
43//!         Thread @ 0xffffd486be7ef080, TID: 1504
44//!         Thread @ 0xffffd486be81f040, TID: 1548
45//!         Thread @ 0xffffd486c0129080, TID: 4896
46//!         Thread @ 0xffffd486bfc52080, TID: 4876
47//!         Thread @ 0xffffd486bfcf5080, TID: 6832
48//!         Thread @ 0xffffd486bf57b080, TID: 3920
49//!         Thread @ 0xffffd486c0487080, TID: 6468
50//!         Thread @ 0xffffd486c012d080, TID: 4312
51//!     Regions:
52//!         Region @ 0xffffd486be7260d0: 0x000000007ffe0000-0x000000007ffe1000 MemoryAccess(R) Private
53//!         Region @ 0xffffd486be726260: 0x000000fb1a220000-0x000000fb1a2a0000 MemoryAccess(R | W) Private
54//!         ...
55//!         Region @ 0xffffd486be6f92f0: 0x00007ffa62cf0000-0x00007ffa62e9d000 MemoryAccess(R | W | X) Mapped (Exe): \Windows\System32\user32.dll
56//!         Region @ 0xffffd486be7a4a30: 0x00007ffa62ea0000-0x00007ffa62f46000 MemoryAccess(R | W | X) Mapped (Exe): \Windows\System32\sechost.dll
57//!         Region @ 0xffffd486be46e140: 0x00007ffa63190000-0x00007ffa63240000 MemoryAccess(R | W | X) Mapped (Exe): \Windows\System32\clbcatq.dll
58//!         Region @ 0xffffd486be6be530: 0x00007ffa632b0000-0x00007ffa633c7000 MemoryAccess(R | W | X) Mapped (Exe): \Windows\System32\rpcrt4.dll
59//!         Region @ 0xffffd486be6f87b0: 0x00007ffa63400000-0x00007ffa634a7000 MemoryAccess(R | W | X) Mapped (Exe): \Windows\System32\msvcrt.dll
60//!         Region @ 0xffffd486be6f60f0: 0x00007ffa63d50000-0x00007ffa63f67000 MemoryAccess(R | W | X) Mapped (Exe): \Windows\System32\ntdll.dll
61//!     PEB:
62//!         Current Directory:    C:\Windows\system32\
63//!         DLL Path:
64//!         Image Path Name:      C:\Windows\system32\svchost.exe
65//!         Command Line:         C:\Windows\system32\svchost.exe -k LocalServiceNetworkRestricted -p
66//!     Handles:
67//!         0004: Object: ffffd486be74cc60 GrantedAccess: 001f0003 Entry: 0xffffbe8b6e74d010
68//!             Type: Event, Path: <no-path>
69//!         0008: Object: ffffd486be74c9e0 GrantedAccess: 001f0003 Entry: 0xffffbe8b6e74d020
70//!             Type: Event, Path: <no-path>
71//!         000c: Object: ffffbe8b6e620900 GrantedAccess: 00000009 Entry: 0xffffbe8b6e74d030
72//!             Type: Key, Path: \REGISTRY\MACHINE\SOFTWARE\SOFTWARE\MICROSOFT\WINDOWS NT\CURRENTVERSION\IMAGE FILE EXECUTION OPTIONS
73//!         0010: Object: ffffd486be76f960 GrantedAccess: 00000804 Entry: 0xffffbe8b6e74d040
74//!             Type: EtwRegistration, Path: <no-path>
75//!
76//! ...
77//! ```
78
79use isr::cache::{IsrCache, JsonCodec};
80use vmi::{
81    VcpuId, VmiCore, VmiError, VmiSession, VmiState, VmiVa as _,
82    arch::amd64::Amd64,
83    driver::kdmp::VmiKdmpDriver,
84    os::{
85        VmiOsMapped as _, VmiOsModule as _, VmiOsProcess as _, VmiOsRegion as _, VmiOsRegionKind,
86        VmiOsThread as _,
87        windows::{WindowsDirectoryObject, WindowsOs, WindowsOsExt, WindowsProcess},
88    },
89};
90
91type Arch = Amd64;
92type Driver = VmiKdmpDriver<Arch>;
93
94fn handle_error(err: VmiError) -> Result<String, VmiError> {
95    match err {
96        VmiError::Translation(pf) => Ok(format!("PF({pf:?})")),
97        _ => Err(err),
98    }
99}
100
101// Enumerate processes in the system.
102fn enumerate_kernel_modules(vmi: &VmiState<Driver, WindowsOs<Driver>>) -> Result<(), VmiError> {
103    for module in vmi.os().modules()? {
104        let module = module?;
105
106        let module_va = module.va();
107        let base_address = module.base_address()?; // `KLDR_DATA_TABLE_ENTRY.DllBase`
108        let size = module.size()?; // `KLDR_DATA_TABLE_ENTRY.SizeOfImage`
109        let name = module.name()?; // `KLDR_DATA_TABLE_ENTRY.BaseDllName`
110        let full_name = match module.full_name() {
111            // `KLDR_DATA_TABLE_ENTRY.FullDllName`
112            Ok(full_name) => full_name,
113            Err(err) => handle_error(err)?,
114        };
115
116        println!("Module @ {module_va}");
117        println!("    Base Address: {base_address}");
118        println!("    Size: {size}");
119        println!("    Name: {name}");
120        println!("    Full Name: {full_name}");
121    }
122
123    Ok(())
124}
125
126// Enumerate entries in a `_OBJECT_DIRECTORY`.
127fn enumerate_directory_object(
128    directory_object: &WindowsDirectoryObject<Driver>,
129    level: usize,
130) -> Result<(), VmiError> {
131    for object in directory_object.iter()? {
132        // Print the indentation.
133        for _ in 0..level {
134            print!("    ");
135        }
136
137        // Retrieve the `_OBJECT_DIRECTORY_ENTRY.Object`.
138        let object = match object {
139            Ok(object) => object,
140            Err(err) => {
141                println!("{}", handle_error(err)?);
142                continue;
143            }
144        };
145
146        let object_va = object.va();
147
148        // Determine the object type.
149        let type_kind = match object.type_kind() {
150            Ok(Some(typ)) => format!("{typ:?}"),
151            Ok(None) => String::from("<unknown>"),
152            Err(err) => handle_error(err)?,
153        };
154
155        print!("{type_kind}: ");
156
157        // Retrieve the full name of the object.
158        let name = match object.full_path() {
159            Ok(Some(name)) => name,
160            Ok(None) => String::from("<unnamed>"),
161            Err(err) => handle_error(err)?,
162        };
163
164        println!("{name} (Object: {object_va})");
165
166        // If the entry is a directory, recursively enumerate it.
167        if let Ok(Some(next)) = object.as_directory() {
168            enumerate_directory_object(&next, level + 1)?;
169        }
170    }
171
172    Ok(())
173}
174
175// Enumerate entries in a `_HANDLE_TABLE`.
176fn enumerate_handle_table(process: &WindowsProcess<Driver>) -> Result<(), VmiError> {
177    const OBJ_PROTECT_CLOSE: u32 = 0x00000001;
178    const OBJ_INHERIT: u32 = 0x00000002;
179    const OBJ_AUDIT_OBJECT_CLOSE: u32 = 0x00000004;
180
181    static LABEL_PROTECTED: [&str; 2] = ["", " (Protected)"];
182    static LABEL_INHERIT: [&str; 2] = ["", " (Inherit)"];
183    static LABEL_AUDIT: [&str; 2] = ["", " (Audit)"];
184
185    // Get the handle table from `_EPROCESS.ObjectTable`.
186    let handle_table = match process.handle_table() {
187        Ok(Some(handle_table)) => handle_table,
188        Ok(None) => {
189            println!("        (No handle table)");
190            return Ok(());
191        }
192        Err(err) => {
193            tracing::error!(?err, "Failed to get handle table");
194            return Ok(());
195        }
196    };
197
198    // Iterate over `_HANDLE_TABLE_ENTRY` items.
199    for handle_entry in handle_table.iter()? {
200        let (handle, entry) = match handle_entry {
201            Ok(entry) => entry,
202            Err(err) => {
203                println!("Failed to get handle entry: {}", handle_error(err)?);
204                continue;
205            }
206        };
207
208        let attributes = match entry.attributes() {
209            Ok(attributes) => attributes,
210            Err(err) => {
211                println!("Failed to get attributes: {}", handle_error(err)?);
212                continue;
213            }
214        };
215
216        let granted_access = match entry.granted_access() {
217            Ok(granted_access) => granted_access,
218            Err(err) => {
219                println!("Failed to get granted access: {}", handle_error(err)?);
220                continue;
221            }
222        };
223
224        let object = match entry.object() {
225            Ok(Some(object)) => object,
226            Ok(None) => {
227                // [`WindowsHandleTable::iter`] should only return entries with
228                // valid objects, so this should not happen.
229                println!("<NULL>");
230                continue;
231            }
232            Err(err) => {
233                println!("Failed to get object: {}", handle_error(err)?);
234                continue;
235            }
236        };
237
238        let type_name = match object.type_name() {
239            Ok(type_name) => type_name,
240            Err(err) => handle_error(err)?,
241        };
242
243        let full_path = match object.full_path() {
244            Ok(Some(path)) => path,
245            Ok(None) => String::from("<no-path>"),
246            Err(err) => handle_error(err)?,
247        };
248
249        println!(
250            "        {:04x}: Object: {:x} GrantedAccess: {:08x}{}{}{} Entry: {}",
251            handle,
252            object.va().0,
253            granted_access,
254            LABEL_PROTECTED[((attributes & OBJ_PROTECT_CLOSE) != 0) as usize],
255            LABEL_INHERIT[((attributes & OBJ_INHERIT) != 0) as usize],
256            LABEL_AUDIT[((attributes & OBJ_AUDIT_OBJECT_CLOSE) != 0) as usize],
257            entry.va(),
258        );
259
260        println!("            Type: {type_name}, Path: {full_path}");
261    }
262
263    Ok(())
264}
265
266// Enumerate VADs in a process.
267fn enumerate_regions(process: &WindowsProcess<Driver>) -> Result<(), VmiError> {
268    for region in process.regions()? {
269        let region = region?;
270
271        let region_va = region.va();
272        let start = region.start()?;
273        let end = region.end()?;
274        let protection = region.protection()?;
275        let kind = region.kind()?;
276
277        print!("        Region @ {region_va}: {start}-{end} {protection:?}");
278
279        match &kind {
280            VmiOsRegionKind::Private => println!(" Private"),
281            VmiOsRegionKind::MappedImage(mapped) => {
282                let path = match mapped.path() {
283                    Ok(Some(path)) => path,
284                    Ok(None) => String::from("<Pagefile>"),
285                    Err(err) => handle_error(err)?,
286                };
287
288                println!(" Mapped (Exe): {path}");
289            }
290            VmiOsRegionKind::MappedData(mapped) => {
291                let path = match mapped.path() {
292                    Ok(Some(path)) => path,
293                    Ok(None) => String::from("<Pagefile>"),
294                    Err(err) => handle_error(err)?,
295                };
296
297                println!(" Mapped: {path}");
298            }
299        }
300    }
301
302    Ok(())
303}
304
305// Enumerate threads in a process.
306fn enumerate_threads(process: &WindowsProcess<Driver>) -> Result<(), VmiError> {
307    for thread in process.threads()? {
308        let thread = thread?;
309
310        let tid = thread.id()?;
311        let object = thread.object()?;
312
313        println!("        Thread @ {object}, TID: {tid}");
314    }
315
316    Ok(())
317}
318
319// Print process information in a `_PEB.ProcessParameters`.
320fn print_process_parameters(process: &WindowsProcess<Driver>) -> Result<(), VmiError> {
321    let peb = match process.peb() {
322        Ok(Some(peb)) => peb,
323        Ok(None) => {
324            println!("        (No PEB)");
325            return Ok(());
326        }
327        Err(err) => {
328            println!("Failed to get PEB: {}", handle_error(err)?);
329            return Ok(());
330        }
331    };
332
333    let current_directory = match peb.current_directory() {
334        Ok(current_directory) => current_directory,
335        Err(err) => handle_error(err)?,
336    };
337
338    let dll_path = match peb.dll_path() {
339        Ok(dll_path) => dll_path,
340        Err(err) => handle_error(err)?,
341    };
342
343    let image_path_name = match peb.image_path_name() {
344        Ok(image_path_name) => image_path_name,
345        Err(err) => handle_error(err)?,
346    };
347
348    let command_line = match peb.command_line() {
349        Ok(command_line) => command_line,
350        Err(err) => handle_error(err)?,
351    };
352
353    println!("        Current Directory:    {current_directory}");
354    println!("        DLL Path:             {dll_path}");
355    println!("        Image Path Name:      {image_path_name}");
356    println!("        Command Line:         {command_line}");
357
358    Ok(())
359}
360
361// Enumerate processes in the system.
362fn enumerate_processes(vmi: &VmiState<Driver, WindowsOs<Driver>>) -> Result<(), VmiError> {
363    for process in vmi.os().processes()? {
364        let process = process?;
365
366        let pid = process.id()?; // `_EPROCESS.UniqueProcessId`
367        let object = process.object()?; // `_EPROCESS` pointer
368        let name = process.name()?; // `_EPROCESS.ImageFileName`
369        let session = process.session()?; // `_EPROCESS.Session`
370
371        println!("Process @ {object}, PID: {pid}");
372        println!("    Name: {name}");
373        if let Some(session) = session {
374            println!("    Session: {}", session.id()?); // `_MM_SESSION_SPACE.SessionId`
375        }
376
377        println!("    Threads:");
378        enumerate_threads(&process)?;
379
380        println!("    Regions:");
381        enumerate_regions(&process)?;
382
383        println!("    PEB:");
384        print_process_parameters(&process)?;
385
386        println!("    Handles:");
387        enumerate_handle_table(&process)?;
388    }
389
390    Ok(())
391}
392
393fn main() -> Result<(), Box<dyn std::error::Error>> {
394    tracing_subscriber::fmt()
395        .with_max_level(tracing::Level::DEBUG)
396        .with_ansi(false)
397        .init();
398
399    // First argument is the path to the dump file.
400    let args = std::env::args().collect::<Vec<_>>();
401    if args.len() != 2 {
402        eprintln!("Usage: {} <dump-file>", args[0]);
403        std::process::exit(1);
404    }
405
406    let dump_file = &args[1];
407
408    // Setup VMI.
409    let driver = Driver::new(dump_file)?;
410    let core = VmiCore::new(driver)?;
411
412    let registers = core.registers(VcpuId(0))?;
413
414    // Try to find the kernel information.
415    // This is necessary in order to load the profile.
416    let kernel_info = WindowsOs::find_kernel(&core, &registers)?.expect("kernel information");
417    tracing::info!(?kernel_info, "Kernel information");
418
419    // Load the profile.
420    // The profile contains offsets to kernel functions and data structures.
421    let isr = IsrCache::<JsonCodec>::new("cache")?;
422    let entry = isr.entry_from_codeview(kernel_info.codeview)?;
423    let profile = entry.profile()?;
424
425    // Create the VMI session.
426    tracing::info!("Creating VMI session");
427    let os = WindowsOs::<Driver>::with_kernel_base(&profile, kernel_info.base_address)?;
428    let session = VmiSession::new(&core, &os);
429
430    let vmi = session.with_registers(&registers);
431    let root_directory = vmi.os().object_root_directory()?;
432
433    println!("Kernel Modules:");
434    println!("=================================================");
435    enumerate_kernel_modules(&vmi)?;
436
437    println!("Object Tree (root directory: {}):", root_directory.va());
438    println!("=================================================");
439    enumerate_directory_object(&root_directory, 0)?;
440
441    println!("Processes:");
442    println!("=================================================");
443    enumerate_processes(&vmi)?;
444
445    Ok(())
446}