parser/
parser.rs

1// Axel '0vercl0k' Souchet - July 20 2023
2use std::env;
3use std::result::Result;
4
5use udmp_parser::UserDumpParser;
6
7/// Command line argument.
8struct Cli {
9    dump_path: String,
10    show_all: bool,
11    show_mods: bool,
12    show_memmap: bool,
13    show_threads: bool,
14    show_foreground_thread: bool,
15    thread: Option<u32>,
16    address: Option<u64>,
17}
18
19/// Convert an hexadecimal string to a `u64`.
20fn string_to_hex(s: &str) -> Result<u64, String> {
21    u64::from_str_radix(s.trim_start_matches("0x"), 16).map_err(|e| e.to_string())
22}
23
24/// Parse the command line arguments.
25fn parse_args() -> Result<Cli, String> {
26    let mut dump_path = None;
27    let mut show_all = false;
28    let mut show_mods = false;
29    let mut show_memmap = false;
30    let mut show_threads = false;
31    let mut show_foreground_thread = false;
32    let mut thread = None;
33    let mut address = None;
34
35    let args = env::args().collect::<Vec<_>>();
36    let mut idx = 1;
37    while idx < args.len() {
38        let cur = &args[idx];
39        let is_final = (idx + 1) >= args.len();
40        if is_final {
41            dump_path = Some(cur.clone());
42            break;
43        }
44
45        let next = if is_final { None } else { Some(&args[idx + 1]) };
46        match cur.as_str() {
47            "-a" => {
48                show_all = true;
49            }
50            "-mods" => {
51                show_mods = true;
52            }
53            "-mem" => {
54                show_memmap = true;
55            }
56            "-t" => {
57                show_threads = true;
58                let Some(next) = next else {
59                    break;
60                };
61
62                if next == "main" {
63                    show_foreground_thread = true;
64                } else {
65                    thread = next.parse().map(Some).unwrap_or(None);
66                }
67
68                if show_foreground_thread || thread.is_some() {
69                    idx += 1;
70                }
71            }
72            "-dump" => {
73                let Some(next) = next else {
74                    return Err("-dump needs to be followed by an address".into());
75                };
76
77                address = Some(string_to_hex(next)?);
78                idx += 1;
79            }
80            rest => {
81                return Err(format!("{} is not a valid option", rest));
82            }
83        };
84
85        idx += 1;
86    }
87
88    let Some(dump_path) = dump_path else {
89        return Err("You didn't specify a dump path".into());
90    };
91
92    Ok(Cli {
93        dump_path,
94        show_all,
95        show_mods,
96        show_memmap,
97        show_threads,
98        show_foreground_thread,
99        thread,
100        address,
101    })
102}
103
104/// Print a hexdump of data that started at `address`.
105fn hexdump(address: u64, mut data_iter: impl ExactSizeIterator<Item = u8>) {
106    let len = data_iter.len();
107    for i in (0..len).step_by(16) {
108        print!("{:016x}: ", address + (i as u64 * 16));
109        let mut row = [None; 16];
110        for item in row.iter_mut() {
111            if let Some(c) = data_iter.next() {
112                *item = Some(c);
113                print!("{:02x}", c);
114            } else {
115                print!(" ");
116            }
117        }
118        print!(" |");
119        for item in &row {
120            if let Some(c) = item {
121                let c = char::from(*c);
122                print!("{}", if c.is_ascii_graphic() { c } else { '.' });
123            } else {
124                print!(" ");
125            }
126        }
127        println!("|");
128    }
129}
130
131/// Display help.
132fn help() {
133    println!("parser.exe [-a] [-mods] [-mem] [-t [<TID|main>]] [-dump <addr>] <dump path>");
134    println!();
135    println!("Examples:");
136    println!("  Show all:");
137    println!("    parser.exe -a user.dmp");
138    println!("  Show loaded modules:");
139    println!("    parser.exe -mods user.dmp");
140    println!("  Show memory map:");
141    println!("    parser.exe -mem user.dmp");
142    println!("  Show all threads:");
143    println!("    parser.exe -t user.dmp");
144    println!("  Show thread w/ specific TID:");
145    println!("    parser.exe -t 1337 user.dmp");
146    println!("  Show foreground thread:");
147    println!("    parser.exe -t main user.dmp");
148    println!("  Dump a memory page at a specific address:");
149    println!("    parser.exe -dump 0x7ff00 user.dmp");
150}
151
152fn main() -> Result<(), String> {
153    // If we don't have any arguments, display the help.
154    if env::args().len() == 1 {
155        help();
156        return Ok(());
157    }
158
159    // Parse the command line arguments.
160    let cli = parse_args()?;
161
162    // Let's try to parse the dump file specified by the user.
163    let dump = UserDumpParser::new(cli.dump_path).map_err(|e| e.to_string())?;
164
165    // Do we want to display modules?
166    if cli.show_mods || cli.show_all {
167        println!("Loaded modules:");
168
169        // Iterate through the module and display their base address and path.
170        for (base, module) in dump.modules() {
171            println!("{:016x}: {}", base, module.path.display());
172        }
173    }
174
175    // Do we want the memory map?
176    if cli.show_memmap || cli.show_all {
177        println!("Memory map:");
178
179        // Iterate over the memory blocks.
180        for block in dump.mem_blocks().values() {
181            // Grab the string representation about its state, type, protection.
182            let state = block.state_as_str();
183            let type_ = block.type_as_str();
184            let protect = block.protect_as_str();
185
186            // Print it all out.
187            print!(
188                "{:016x} {:016x} {:016x} {:11} {:11} {:22}",
189                block.range.start,
190                block.range.end,
191                block.len(),
192                type_,
193                state,
194                protect
195            );
196
197            // Do we have a module that exists at this address?
198            let module = dump.get_module(block.range.start);
199
200            // If we do, then display its name / path.
201            if let Some(module) = module {
202                print!(
203                    " [{}; \"{}\"]",
204                    module.file_name().unwrap(),
205                    module.path.display()
206                );
207            }
208
209            // Do we have data with this block? If so display the first few
210            // bytes.
211            if block.data.len() >= 4 {
212                print!(
213                    " {:02x} {:02x} {:02x} {:02x}...",
214                    block.data[0], block.data[1], block.data[2], block.data[3]
215                );
216            }
217
218            println!();
219        }
220    }
221
222    // Do we want threads?
223    if cli.show_threads || cli.show_all {
224        println!("Threads:");
225
226        // Grab the foreground tid.
227        let foreground_tid = dump.foreground_tid;
228
229        // Iterate through all the threads.
230        for (tid, thread) in dump.threads() {
231            // If the user specified a pid..
232            if let Some(wanted_tid) = cli.thread {
233                // .. skip an threads that don't match what the user wants..
234                if *tid != wanted_tid {
235                    continue;
236                }
237
238                // Otherwise we keep going.
239            }
240
241            // If the user only wants the main thread, and we haven't found it,
242            // skip this thread until we find it.
243            if cli.show_foreground_thread
244                && *tid != foreground_tid.expect("no foreground thread id in dump")
245            {
246                continue;
247            }
248
249            // Print out the thread info.
250            println!("TID {}, TEB {:016x}", tid, thread.teb);
251            println!("Context:");
252            println!("{}", thread.context());
253        }
254    }
255
256    // Do we want to dump memory?
257    if let Some(address) = cli.address {
258        println!("Memory:");
259
260        // Try to find a block that contains `address`.
261        let block = dump.get_mem_block(address);
262
263        // If we have one..
264        if let Some(block) = block {
265            // .. and it has data, dump it..
266            if let Some(data) = block.data_from(address) {
267                println!("{:016x} -> {:016x}", address, block.end_addr());
268                hexdump(address, data.iter().take(0x1_00).copied());
269            }
270            // .. otherwise, inform the user..
271            else {
272                println!(
273                    "The memory at {:016x} (from block {:016x} -> {:016x}) has no backing data",
274                    address, block.range.start, block.range.end
275                );
276            }
277        }
278        // .. otherwise, inform he user.
279        else {
280            println!("No memory block were found for {:016x}", address);
281        }
282    }
283
284    // All right, enough for today.
285    Ok(())
286}