1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
// Axel '0vercl0k' Souchet - February 25 2024
//! This has all the raw structures that makes up Windows kernel crash-dumps.
use std::collections::BTreeMap;
use std::fmt::Debug;
use std::{io, mem, slice};

use crate::error::Result;
use crate::{Gpa, KdmpParserError, Reader};

/// A page.
pub struct Page;

impl Page {
    /// Get the size of a memory page.
    pub const fn size() -> u64 {
        0x1_000
    }
}

/// Types of kernel crash dump.
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u32)]
pub enum DumpType {
    // Old dump types from dbgeng.dll
    Full = 0x1,
    Bmp = 0x5,
    /// Produced by `.dump /m`.
    // Mini = 0x4,
    /// Produced by `.dump /k`.
    KernelMemory = 0x8,
    /// Produced by `.dump /ka`.
    KernelAndUserMemory = 0x9,
    /// Produced by `.dump /f`.
    CompleteMemory = 0xa,
}

/// The physical memory map maps a physical address to a file offset.
pub type PhysmemMap = BTreeMap<Gpa, u64>;

impl TryFrom<u32> for DumpType {
    type Error = KdmpParserError;

    fn try_from(value: u32) -> Result<Self> {
        match value {
            x if x == DumpType::Full as u32 => Ok(DumpType::Full),
            x if x == DumpType::Bmp as u32 => Ok(DumpType::Bmp),
            x if x == DumpType::KernelMemory as u32 => Ok(DumpType::KernelMemory),
            x if x == DumpType::KernelAndUserMemory as u32 => Ok(DumpType::KernelAndUserMemory),
            x if x == DumpType::CompleteMemory as u32 => Ok(DumpType::CompleteMemory),
            _ => Err(KdmpParserError::UnknownDumpType(value)),
        }
    }
}

#[repr(C)]
#[derive(Debug, Default)]
pub struct ExceptionRecord64 {
    pub exception_code: u32,
    pub exception_flags: u32,
    pub exception_record: u64,
    pub exception_address: u64,
    pub number_parameters: u32,
    __unused_alignment: u32,
    pub exception_information: [u64; 15],
}

pub const HEADER64_EXPECTED_SIGNATURE: u32 = 0x45_47_41_50; // 'EGAP'
pub const HEADER64_EXPECTED_VALID_DUMP: u32 = 0x34_36_55_44; // '46UD'

/// Adjusted C struct for `DUMP_HEADERS64` from MS Rust docs. Padding
/// adjustment added from reversing `nt!IoFillDumpHeader`.
// https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Diagnostics/Debug/struct.DUMP_HEADER64.html#structfield.DumpType
#[repr(C)]
pub struct Header64 {
    pub signature: u32,
    pub valid_dump: u32,
    pub major_version: u32,
    pub minor_version: u32,
    pub directory_table_base: u64,
    pub pfn_database: u64,
    pub ps_loaded_module_list: u64,
    pub ps_active_process_head: u64,
    pub machine_image_type: u32,
    pub number_processors: u32,
    pub bug_check_code: u32,
    __padding0: u32,
    pub bug_check_code_parameters: [u64; 4],
    pub version_user: [u8; 32],
    pub kd_debugger_data_block: u64,
    //   /* 0x0088 */ union DUMP_HEADER64_0 {
    //     PHYSMEM_DESC PhysicalMemoryBlock;
    //     std::array<uint8_t, 700> PhysicalMemoryBlockBuffer;
    //   } u1;
    pub physical_memory_block_buffer: [u8; 700],
    // 0x0344
    __padding1: u32,
    //   /* 0x0348 */ union CONTEXT_RECORD64_0 {
    //     CONTEXT ContextRecord;
    //     std::array<uint8_t, 3000> ContextRecordBuffer;
    //   } u2;
    pub context_record_buffer: [u8; 3_000],
    pub exception: ExceptionRecord64,
    pub dump_type: u32,
    __padding2: u32,
    pub required_dump_space: i64,
    pub system_time: i64,
    pub comment: [u8; 128],
    pub system_up_time: i64,
    pub minidump_fields: u32,
    pub secondary_data_state: u32,
    pub product_type: u32,
    pub suite_mask: u32,
    pub writer_status: u32,
    __unused0: u8,
    pub kd_secondary_version: u8,
    __unused1: [u8; 2],
    pub attributes: u32,
    pub boot_id: u32,
    __reserved0: [u8; 4008],
}

impl Debug for Header64 {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Header64")
            .field("signature", &self.signature)
            .field("valid_dump", &self.valid_dump)
            .field("major_version", &self.major_version)
            .field("minor_version", &self.minor_version)
            .field("directory_table_base", &self.directory_table_base)
            .field("pfn_database", &self.pfn_database)
            .field("ps_loaded_module_list", &self.ps_loaded_module_list)
            .field("ps_active_process_head", &self.ps_active_process_head)
            .field("machine_image_type", &self.machine_image_type)
            .field("number_processors", &self.number_processors)
            .field("bug_check_code", &self.bug_check_code)
            .field("bug_check_code_parameters", &self.bug_check_code_parameters)
            .field("version_user", &self.version_user)
            .field("kd_debugger_data_block", &self.kd_debugger_data_block)
            .field("exception", &self.exception)
            .field("dump_type", &self.dump_type)
            .field("required_dump_space", &self.required_dump_space)
            .field("system_time", &self.system_time)
            .field("comment", &self.comment)
            .field("system_up_time", &self.system_up_time)
            .field("minidump_fields", &self.minidump_fields)
            .field("secondary_data_state", &self.secondary_data_state)
            .field("product_type", &self.product_type)
            .field("suite_mask", &self.suite_mask)
            .field("writer_status", &self.writer_status)
            .field("kd_secondary_version", &self.kd_secondary_version)
            .field("attributes", &self.attributes)
            .field("boot_id", &self.boot_id)
            .finish()
    }
}

const BMPHEADER64_EXPECTED_SIGNATURE: u32 = 0x50_4D_44_53; // 'PMDS'
const BMPHEADER64_EXPECTED_SIGNATURE2: u32 = 0x50_4D_44_46; // 'PMDF'
const BMPHEADER64_EXPECTED_VALID_DUMP: u32 = 0x50_4D_55_44; // 'PMUD'

#[derive(Debug, Default)]
#[repr(C)]
pub struct BmpHeader64 {
    pub signature: u32,
    pub valid_dump: u32,
    // According to rekall there's a gap there:
    // 'ValidDump': [0x4, ['String', dict(
    //    length=4,
    //    term=None,
    //    )]],
    // # The offset of the first page in the file.
    // 'FirstPage': [0x20, ['unsigned long long']],
    pub __padding0: [u8; 0x20 - (0x4 + mem::size_of::<u32>())],
    /// The offset of the first page in the file.
    pub first_page: u64,
    /// Total number of pages present in the bitmap.
    pub total_present_pages: u64,
    /// Total number of pages in image. This dictates the total size of the
    /// bitmap.This is not the same as the TotalPresentPages which is only
    /// the sum of the bits set to 1.
    pub pages: u64,
    // Bitmap follows
}

impl BmpHeader64 {
    pub fn looks_good(&self) -> bool {
        (self.signature == BMPHEADER64_EXPECTED_SIGNATURE
            || self.signature == BMPHEADER64_EXPECTED_SIGNATURE2)
            && self.valid_dump == BMPHEADER64_EXPECTED_VALID_DUMP
    }
}

#[derive(Debug, Default)]
#[repr(C)]
pub struct PhysmemRun {
    pub base_page: u64,
    pub page_count: u64,
}

impl PhysmemRun {
    /// Calculate a physical address from a run and an index.
    ///
    /// The formulae is: (`base_page` + `page_idx`) * `Page::size()`.
    pub fn phys_addr(&self, page_idx: u64) -> Option<Gpa> {
        debug_assert!(page_idx < self.page_count);

        self.base_page
            .checked_add(page_idx)?
            .checked_mul(Page::size())
            .map(Gpa::new)
    }
}

#[derive(Debug, Default)]
#[repr(C)]
pub struct PhysmemDesc {
    pub number_of_runs: u32,
    __padding0: u32,
    pub number_of_pages: u64,
    // PHYSMEM_RUN Run[1]; follows
}

impl TryFrom<&[u8]> for PhysmemDesc {
    type Error = KdmpParserError;

    fn try_from(slice: &[u8]) -> Result<Self> {
        let expected_len = mem::size_of::<Self>();
        if slice.len() < expected_len {
            return Err(KdmpParserError::InvalidData("physmem desc is too small"));
        }

        let number_of_runs = u32::from_le_bytes((&slice[0..4]).try_into().unwrap());
        let number_of_pages = u64::from_le_bytes((&slice[4..12]).try_into().unwrap());

        Ok(Self {
            number_of_runs,
            number_of_pages,
            ..Default::default()
        })
    }
}

#[derive(Debug)]
#[repr(C)]
pub struct Context {
    pub p1_home: u64,
    pub p2_home: u64,
    pub p3_home: u64,
    pub p4_home: u64,
    pub p5_home: u64,
    pub p6_home: u64,
    pub context_flags: u32,
    pub mxcsr: u32,
    pub seg_cs: u16,
    pub seg_ds: u16,
    pub seg_es: u16,
    pub seg_fs: u16,
    pub seg_gs: u16,
    pub seg_ss: u16,
    pub eflags: u32,
    pub dr0: u64,
    pub dr1: u64,
    pub dr2: u64,
    pub dr3: u64,
    pub dr6: u64,
    pub dr7: u64,
    pub rax: u64,
    pub rcx: u64,
    pub rdx: u64,
    pub rbx: u64,
    pub rsp: u64,
    pub rbp: u64,
    pub rsi: u64,
    pub rdi: u64,
    pub r8: u64,
    pub r9: u64,
    pub r10: u64,
    pub r11: u64,
    pub r12: u64,
    pub r13: u64,
    pub r14: u64,
    pub r15: u64,
    pub rip: u64,
    pub control_word: u16,
    pub status_word: u16,
    pub tag_word: u8,
    reserved1: u8,
    pub error_opcode: u16,
    pub error_offset: u32,
    pub error_selector: u16,
    reserved2: u16,
    pub data_offset: u32,
    pub data_selector: u16,
    reserved3: u16,
    pub mxcsr2: u32,
    pub mxcsr_mask: u32,
    pub float_registers: [u128; 8],
    pub xmm_registers: [u128; 16],
    reserved4: [u8; 96],
    pub vector_register: [u128; 26],
    pub vector_control: u64,
    pub debug_control: u64,
    pub last_branch_to_rip: u64,
    pub last_branch_from_rip: u64,
    pub last_exception_to_rip: u64,
    pub last_exception_from_rip: u64,
}

/// Peek for a `T` from the cursor.
pub fn peek_struct<T>(reader: &mut impl Reader) -> Result<T> {
    let mut s = mem::MaybeUninit::uninit();
    let size_of_s = mem::size_of_val(&s);
    let slice_over_s = unsafe { slice::from_raw_parts_mut(s.as_mut_ptr() as *mut u8, size_of_s) };

    let pos = reader.stream_position()?;
    reader.read_exact(slice_over_s)?;
    reader.seek(io::SeekFrom::Start(pos))?;

    Ok(unsafe { s.assume_init() })
}

/// Read a `T` from the cursor.
pub fn read_struct<T>(reader: &mut impl Reader) -> Result<T> {
    let s = peek_struct(reader)?;
    let size_of_s = mem::size_of_val(&s);

    reader.seek(io::SeekFrom::Current(size_of_s.try_into().unwrap()))?;

    Ok(s)
}

const RDMP_HEADER64_EXPECTED_MARKER: u32 = 0x40;
const RDMP_HEADER64_EXPECTED_SIGNATURE: u32 = 0x50_4D_44_52; // 'PMDR'
const RDMP_HEADER64_EXPECTED_VALID_DUMP: u32 = 0x50_4D_55_44; // 'PMUD'

#[repr(C)]
#[derive(Debug, Default)]
pub struct RdmpHeader64 {
    pub marker: u32,
    pub signature: u32,
    pub valid_dump: u32,
    reserved1: u32,
    pub metadata_size: u64,
    pub first_page_offset: u64,
    // Bitmap follows
}

impl RdmpHeader64 {
    pub fn looks_good(&self) -> bool {
        if self.marker != RDMP_HEADER64_EXPECTED_MARKER {
            return false;
        }

        if self.signature != RDMP_HEADER64_EXPECTED_SIGNATURE {
            return false;
        }

        if self.valid_dump != RDMP_HEADER64_EXPECTED_VALID_DUMP {
            return false;
        }

        if self.metadata_size - 0x20 != self.first_page_offset - 0x20_40 {
            return false;
        }

        true
    }
}

#[repr(C)]
#[derive(Debug, Default)]
pub struct KernelRdmpHeader64 {
    pub hdr: RdmpHeader64,
    unknown1: u64,
    unknown2: u64,
    // Bitmap follows
}

#[repr(C)]
#[derive(Debug, Default)]
pub struct FullRdmpHeader64 {
    pub hdr: RdmpHeader64,
    pub number_of_ranges: u32,
    reserved1: u16,
    reserved2: u16,
    pub total_number_of_pages: u64,
    // Bitmap follows
}

#[repr(C)]
#[derive(Debug, Default)]
pub struct PfnRange {
    pub page_file_number: u64,
    pub number_of_pages: u64,
}

#[cfg(test)]
mod tests {
    use std::mem;

    use crate::structs::{Context, Header64, PhysmemDesc, PhysmemRun};

    /// Ensure that the sizes of key structures are right.
    #[test]
    fn layout() {
        assert_eq!(mem::size_of::<PhysmemDesc>(), 0x10);
        assert_eq!(mem::size_of::<PhysmemRun>(), 0x10);
        assert_eq!(mem::size_of::<Header64>(), 0x2_000);
        assert_eq!(mem::size_of::<Context>(), 0x4d0);
    }
}