├── .gitignore ├── INFO.md ├── LICENSE ├── fuzzer ├── .cargo │ └── config ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── guifuzz ├── .cargo │ └── config ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src │ ├── lib.rs │ ├── rng.rs │ └── winbindings.rs └── mesos ├── .cargo └── config ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── assets ├── code_coverage.png ├── crash_saving.png ├── ida_coloring.png └── meso_bag.png ├── coverage_scripts └── ida.py ├── generate_mesos.py ├── libs └── debugger │ ├── Cargo.toml │ └── src │ ├── debugger.rs │ ├── ffi_helpers.rs │ ├── handles.rs │ ├── lib.rs │ ├── minidump.rs │ └── sedebug.rs ├── mesogen_scripts ├── ghidra.py ├── ida.py └── ida_detect.py ├── plot.plt └── src ├── main.rs └── mesofile.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /crashes/ 2 | /server2008r2/ 3 | /*.iso 4 | /*.png 5 | 6 | -------------------------------------------------------------------------------- /INFO.md: -------------------------------------------------------------------------------- 1 | # Image 2 | 3 | Windows 7 Professional (x64) - DVD (English) 4 | If you are experiencing installation issues related to performing clean installs of the integrated SP1 version of this product, please review KB Article 2534111 for more information. 5 | Released: 8/6/2009 6 | SHA1: 50127304441A793EE51B3F501289F6599A559E9F 7 | File name: en_windows_7_professional_x64_dvd_x15-65805.iso 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Gamozo Labs, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /fuzzer/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.x86_64-pc-windows-msvc] 2 | rustflags = ["-C", "target-feature=+crt-static"] 3 | 4 | -------------------------------------------------------------------------------- /fuzzer/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /fuzzer/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "fuzzer" 5 | version = "0.1.0" 6 | 7 | -------------------------------------------------------------------------------- /fuzzer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzzer" 3 | version = "0.1.0" 4 | authors = ["Brandon Falk "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /fuzzer/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::io::Error; 3 | use std::cell::Cell; 4 | use std::collections::HashSet; 5 | use std::convert::TryInto; 6 | 7 | #[link(name="User32")] 8 | extern "system" { 9 | fn FindWindowW(lpClassName: *mut u16, lpWindowName: *mut u16) -> usize; 10 | fn PostMessageW(hWnd: usize, msg: u32, wParam: usize, lParam: usize) 11 | -> usize; 12 | fn GetForegroundWindow() -> usize; 13 | fn SendInput(cInputs: u32, pInputs: *mut Input, cbSize: i32) -> u32; 14 | fn SetForegroundWindow(hwnd: usize) -> bool; 15 | fn GetClientRect(hwnd: usize, rect: *mut Rect) -> bool; 16 | fn GetWindowRect(hwnd: usize, rect: *mut Rect) -> bool; 17 | } 18 | 19 | #[repr(C)] 20 | #[derive(Clone, Copy, Default, Debug)] 21 | struct Rect { 22 | left: i32, 23 | top: i32, 24 | right: i32, 25 | bottom: i32, 26 | } 27 | 28 | /// Different types of inpust for the `typ` field on `Input` 29 | #[repr(C)] 30 | #[derive(Clone, Copy)] 31 | enum InputType { 32 | Mouse = 0, 33 | Keyboard = 1, 34 | Hardware = 2, 35 | } 36 | 37 | #[repr(C)] 38 | #[derive(Clone, Copy)] 39 | struct Input { 40 | typ: InputType, 41 | union: InputUnion, 42 | } 43 | 44 | #[repr(C)] 45 | #[derive(Clone, Copy)] 46 | union InputUnion { 47 | mouse: MouseInput, 48 | keyboard: KeyboardInput, 49 | hardware: HardwareInput, 50 | } 51 | 52 | #[repr(C)] 53 | #[derive(Clone, Copy)] 54 | struct KeyboardInput { 55 | vk: u16, 56 | scan_code: u16, 57 | flags: u32, 58 | time: u32, 59 | extra_info: usize, 60 | } 61 | 62 | #[repr(C)] 63 | #[derive(Clone, Copy)] 64 | struct MouseInput { 65 | dx: i32, 66 | dy: i32, 67 | mouse_data: u32, 68 | flags: u32, 69 | time: u32, 70 | extra_info: usize, 71 | } 72 | 73 | #[repr(C)] 74 | #[derive(Clone, Copy)] 75 | struct HardwareInput { 76 | msg: u32, 77 | lparam: u16, 78 | hparam: u16, 79 | } 80 | 81 | /// Convert a Rust UTF-8 `string` into a NUL-terminated UTF-16 vector 82 | fn str_to_utf16(string: &str) -> Vec { 83 | let mut ret: Vec = string.encode_utf16().collect(); 84 | ret.push(0); 85 | ret 86 | } 87 | 88 | /// Different types of messages for `SendMessage()` 89 | #[repr(u32)] 90 | enum MsgType { 91 | LeftButtonDown = 0x0201, 92 | LeftButtonUp = 0x0202, 93 | KeyDown = 0x0100, 94 | KeyUp = 0x0101, 95 | } 96 | 97 | /// Different types of states for the `WPARAM` field on 98 | #[repr(usize)] 99 | enum WparamMousePress { 100 | Left = 0x0001, 101 | Right = 0x0002, 102 | Shift = 0x0004, 103 | Control = 0x0008, 104 | Middle = 0x0010, 105 | Xbutton1 = 0x0020, 106 | Xbutton2 = 0x0040, 107 | } 108 | 109 | #[repr(u8)] 110 | enum KeyCode { 111 | Back = 0x08, 112 | Tab = 0x09, 113 | Return = 0x0d, 114 | Shift = 0x10, 115 | Control = 0x11, 116 | Alt = 0x12, 117 | Left = 0x25, 118 | Up = 0x26, 119 | Right = 0x27, 120 | Down = 0x28, 121 | } 122 | 123 | /// An active handle to a window 124 | struct Window { 125 | /// Handle to the window which we have opened 126 | hwnd: usize, 127 | 128 | /// Seed for an RNG 129 | seed: Cell, 130 | 131 | /// Keys which seem interesting 132 | interesting_keys: Vec, 133 | } 134 | 135 | impl Window { 136 | /// Find a window with `title`, and return a new `Window` object 137 | fn attach(title: &str) -> io::Result { 138 | // Convert the title to UTF-16 139 | let mut title = str_to_utf16(title); 140 | 141 | // Finds the window with `title` 142 | let ret = unsafe { 143 | FindWindowW(std::ptr::null_mut(), title.as_mut_ptr()) 144 | }; 145 | 146 | // Generate some interesting keys 147 | let mut interesting_keys = Vec::new(); 148 | interesting_keys.push(KeyCode::Left as u8); 149 | interesting_keys.push(KeyCode::Up as u8); 150 | interesting_keys.push(KeyCode::Down as u8); 151 | interesting_keys.push(KeyCode::Right as u8); 152 | interesting_keys.push(KeyCode::Tab as u8); 153 | interesting_keys.extend_from_slice( 154 | b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789()-+=/*!@#"); 155 | 156 | if ret != 0 { 157 | // Successfully got a handle to the window 158 | return Ok(Window { 159 | hwnd: ret, 160 | seed: Cell::new(unsafe { core::arch::x86_64::_rdtsc() }), 161 | interesting_keys, 162 | }); 163 | } else { 164 | // FindWindow() failed, return out the corresponding error 165 | Err(Error::last_os_error()) 166 | } 167 | } 168 | 169 | /// Get a random 64-bit number using xorshift 170 | fn rand(&self) -> usize { 171 | let mut seed = self.seed.get(); 172 | seed ^= seed << 13; 173 | seed ^= seed >> 17; 174 | seed ^= seed << 43; 175 | self.seed.set(seed); 176 | seed as usize 177 | } 178 | 179 | fn keystream(&self, inputs: &[KeyboardInput]) -> io::Result<()> { 180 | // Generate an array to pass directly to `SendInput()` 181 | let mut win_inputs = Vec::new(); 182 | 183 | // Create inputs based on each keyboard input 184 | for &input in inputs.iter() { 185 | win_inputs.push(Input { 186 | typ: InputType::Keyboard, 187 | union: InputUnion { 188 | keyboard: input 189 | } 190 | }); 191 | } 192 | 193 | let res = unsafe { 194 | SendInput( 195 | win_inputs.len().try_into().unwrap(), 196 | win_inputs.as_mut_ptr(), 197 | std::mem::size_of::().try_into().unwrap()) 198 | }; 199 | 200 | if (res as usize) != inputs.len() { 201 | Err(Error::last_os_error()) 202 | } else { 203 | Ok(()) 204 | } 205 | } 206 | 207 | fn mousestream(&self, inputs: &[MouseInput]) -> io::Result<()> { 208 | // Generate an array to pass directly to `SendInput()` 209 | let mut win_inputs = Vec::new(); 210 | 211 | // Create inputs based on each mouse input 212 | for &input in inputs.iter() { 213 | win_inputs.push(Input { 214 | typ: InputType::Mouse, 215 | union: InputUnion { 216 | mouse: input 217 | } 218 | }); 219 | } 220 | 221 | let res = unsafe { 222 | SendInput( 223 | win_inputs.len().try_into().unwrap(), 224 | win_inputs.as_mut_ptr(), 225 | std::mem::size_of::().try_into().unwrap()) 226 | }; 227 | 228 | if (res as usize) != inputs.len() { 229 | Err(Error::last_os_error()) 230 | } else { 231 | Ok(()) 232 | } 233 | } 234 | 235 | fn press(&self, key: u16) -> io::Result<()> { 236 | self.keystream(&[ 237 | KeyboardInput { 238 | vk: key, 239 | scan_code: 0, 240 | flags: 0, 241 | time: 0, 242 | extra_info: 0, 243 | }, 244 | 245 | KeyboardInput { 246 | vk: key, 247 | scan_code: 0, 248 | flags: KEYEVENTF_KEYUP, 249 | time: 0, 250 | extra_info: 0, 251 | }, 252 | ]) 253 | } 254 | 255 | fn alt_press(&self, key: u16) -> io::Result<()> { 256 | if key == KeyCode::Tab as u16 || key == b' ' as u16 { 257 | return Ok(()); 258 | } 259 | 260 | self.keystream(&[ 261 | KeyboardInput { 262 | vk: KeyCode::Alt as u16, 263 | scan_code: 0, 264 | flags: 0, 265 | time: 0, 266 | extra_info: 0, 267 | }, 268 | 269 | KeyboardInput { 270 | vk: key, 271 | scan_code: 0, 272 | flags: 0, 273 | time: 0, 274 | extra_info: 0, 275 | }, 276 | 277 | KeyboardInput { 278 | vk: key, 279 | scan_code: 0, 280 | flags: KEYEVENTF_KEYUP, 281 | time: 0, 282 | extra_info: 0, 283 | }, 284 | 285 | KeyboardInput { 286 | vk: KeyCode::Alt as u16, 287 | scan_code: 0, 288 | flags: KEYEVENTF_KEYUP, 289 | time: 0, 290 | extra_info: 0, 291 | }, 292 | ]) 293 | } 294 | 295 | fn ctrl_press(&self, key: u16) -> io::Result<()> { 296 | if key == 0x1B { 297 | return Ok(()); 298 | } 299 | 300 | self.keystream(&[ 301 | KeyboardInput { 302 | vk: KeyCode::Control as u16, 303 | scan_code: 0, 304 | flags: 0, 305 | time: 0, 306 | extra_info: 0, 307 | }, 308 | 309 | KeyboardInput { 310 | vk: key, 311 | scan_code: 0, 312 | flags: 0, 313 | time: 0, 314 | extra_info: 0, 315 | }, 316 | 317 | KeyboardInput { 318 | vk: key, 319 | scan_code: 0, 320 | flags: KEYEVENTF_KEYUP, 321 | time: 0, 322 | extra_info: 0, 323 | }, 324 | 325 | KeyboardInput { 326 | vk: KeyCode::Control as u16, 327 | scan_code: 0, 328 | flags: KEYEVENTF_KEYUP, 329 | time: 0, 330 | extra_info: 0, 331 | }, 332 | ]) 333 | } 334 | } 335 | 336 | const KEYEVENTF_KEYUP: u32 = 0x0002; 337 | 338 | fn main() -> io::Result<()> { 339 | let window = Window::attach("Calculator")?; 340 | 341 | assert!(unsafe { SetForegroundWindow(window.hwnd) }); 342 | 343 | let mut rect = Rect::default(); 344 | unsafe { 345 | assert!(GetWindowRect(window.hwnd, &mut rect)); 346 | } 347 | 348 | print!("{:?}\n", rect); 349 | 350 | unsafe { 351 | loop { 352 | std::thread::sleep_ms(50); 353 | PostMessageW(window.hwnd, 0x0200, 0, ((window.rand() % 1000) << 16) | (window.rand() % 1000)); 354 | } 355 | } 356 | 357 | /* 358 | window.mousestream(&[ 359 | MouseInput { 360 | dx: 5000, 361 | dy: 5000, 362 | mouse_data: 0, 363 | flags: 1 | 0x8000, 364 | time: 0, 365 | extra_info: 0, 366 | }, 367 | 368 | /* 369 | MouseInput { 370 | dx: 0, 371 | dy: 0, 372 | mouse_data: 0, 373 | flags: 2, 374 | time: 0, 375 | extra_info: 0, 376 | }, 377 | 378 | MouseInput { 379 | dx: 0, 380 | dy: 0, 381 | mouse_data: 0, 382 | flags: 4, 383 | time: 0, 384 | extra_info: 0, 385 | },*/ 386 | ]);*/ 387 | 388 | Ok(()) 389 | } 390 | 391 | fn scoop() -> io::Result<()> { 392 | let args: Vec = std::env::args().collect(); 393 | 394 | if args.len() != 2 { 395 | print!("usage: cargo run \n"); 396 | return Ok(()); 397 | } 398 | 399 | 'reconnect: loop { 400 | //std::thread::sleep_ms(5); 401 | 402 | let window = Window::attach(&args[1]); 403 | if window.is_err() { 404 | print!("could not attach to window\n"); 405 | continue; 406 | } 407 | 408 | let window = window.unwrap(); 409 | print!("Opened a handle to calc!\n"); 410 | 411 | let mut blacklist = HashSet::new(); 412 | blacklist.insert(0x5b); // Left windows key 413 | blacklist.insert(0x5c); // Right windows key 414 | blacklist.insert(0x5d); // Application key 415 | blacklist.insert(0x5f); // Sleep key 416 | blacklist.insert(0x70); // F1 key 417 | blacklist.insert(0x73); // F4 key 418 | blacklist.insert(0x2f); // Help key 419 | blacklist.insert(0x2c); // Print screen 420 | blacklist.insert(0x2a); // Print 421 | blacklist.insert(0x2b); // Execute 422 | blacklist.insert(0x12); // Alt 423 | blacklist.insert(0x11); // Control 424 | blacklist.insert(0x1b); // Escape 425 | 426 | for key in 0x80..=0xffff { 427 | blacklist.insert(key); 428 | } 429 | 430 | loop { 431 | if unsafe { SetForegroundWindow(window.hwnd) } == false { 432 | print!("Couldn't set foreground\n"); 433 | continue 'reconnect; 434 | } 435 | 436 | /*let key = window.interesting_keys[ 437 | window.rand() % window.interesting_keys.len()] as u16;*/ 438 | let key = window.rand() as u8 as u16; 439 | 440 | if blacklist.contains(&key) { continue; } 441 | 442 | std::thread::sleep_ms(5); 443 | 444 | //print!("{:#x}\n", key); 445 | 446 | let sel = window.rand() % 3; 447 | match sel { 448 | 0 => window.alt_press(key)?, 449 | 1 => window.ctrl_press(key)?, 450 | _ => window.press(key)?, 451 | } 452 | } 453 | } 454 | 455 | Ok(()) 456 | } 457 | 458 | -------------------------------------------------------------------------------- /guifuzz/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.x86_64-pc-windows-msvc] 2 | rustflags = ["-C", "target-feature=+crt-static"] 3 | 4 | -------------------------------------------------------------------------------- /guifuzz/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /guifuzz/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "guifuzz" 5 | version = "0.1.0" 6 | 7 | -------------------------------------------------------------------------------- /guifuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "guifuzz" 3 | version = "0.1.0" 4 | authors = ["Brandon Falk "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /guifuzz/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod winbindings; 2 | pub mod rng; 3 | 4 | use std::error::Error; 5 | use std::collections::{HashSet, HashMap}; 6 | use std::sync::{Mutex, Arc}; 7 | pub use rng::Rng; 8 | pub use winbindings::Window; 9 | 10 | /// Sharable fuzz input 11 | pub type FuzzInput = Arc>; 12 | 13 | /// Fuzz case statistics 14 | #[derive(Default)] 15 | pub struct Statistics { 16 | /// Number of fuzz cases 17 | pub fuzz_cases: u64, 18 | 19 | /// Coverage database. Maps (module, offset) to `FuzzInput`s 20 | pub coverage_db: HashMap<(Arc, usize), FuzzInput>, 21 | 22 | /// Set of all unique inputs 23 | pub input_db: HashSet, 24 | 25 | /// List of all unique inputs 26 | pub input_list: Vec, 27 | 28 | /// Unique set of fuzzer actions 29 | pub unique_action_set: HashSet, 30 | 31 | /// List of all unique fuzzer actions 32 | pub unique_actions: Vec, 33 | 34 | /// Number of crashes 35 | pub crashes: u64, 36 | 37 | /// Database of crash file names to `FuzzInput`s 38 | pub crash_db: HashMap, 39 | } 40 | 41 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 42 | pub enum FuzzerAction { 43 | LeftClick { idx: usize }, 44 | Close, 45 | MenuAction { menu_id: u32 }, 46 | KeyPress { key: usize }, 47 | } 48 | 49 | pub fn perform_actions(pid: u32, 50 | actions: &[FuzzerAction]) -> Result<(), Box>{ 51 | // Attach to the Calculator window 52 | let primary_window = Window::attach_pid(pid, "Calculator")?; 53 | 54 | for &action in actions { 55 | match action { 56 | FuzzerAction::LeftClick { idx } => { 57 | // Click on the GUI element 58 | let sub_windows = primary_window.enumerate_subwindows(); 59 | if sub_windows.is_err() { 60 | return Ok(()); 61 | } 62 | let sub_windows = sub_windows.unwrap(); 63 | 64 | if let Some(window) = sub_windows.get(idx) { 65 | let _ = window.left_click(None); 66 | } 67 | } 68 | FuzzerAction::Close => { 69 | let _ = primary_window.close(); 70 | } 71 | FuzzerAction::MenuAction { menu_id } => { 72 | // Select a random menu item and click it 73 | let _ = primary_window.use_menu_id(menu_id); 74 | std::thread::sleep(std::time::Duration::from_millis(250)); 75 | } 76 | FuzzerAction::KeyPress { key } => { 77 | // Press a key on the keyboard 78 | let _ = primary_window.press_key(key); 79 | } 80 | } 81 | } 82 | 83 | Ok(()) 84 | } 85 | 86 | pub fn mutate(stats: Arc>) 87 | -> Result, Box> { 88 | // Create a new RNG 89 | let rng = Rng::new(); 90 | 91 | // Get access to the global database 92 | let stats = stats.lock().unwrap(); 93 | 94 | // Pick an input to use as the basis of this fuzz case 95 | let input_sel = rng.rand() % stats.input_list.len(); 96 | let mut input: Vec = (*stats.input_list[input_sel]).clone(); 97 | 98 | // Make up to n modifications, minimum of one 99 | for _ in 0..((rng.rand() & 0x1f) + 1) { 100 | let sel = rng.rand() % 5; 101 | 102 | match sel { 103 | 0 => { 104 | // Splice in a random portion from an existing input 105 | 106 | // Select a random slice from our current input 107 | if input.len() == 0 { continue; } 108 | let inp_start = rng.rand() % input.len(); 109 | let inp_length = rng.rand() % (rng.rand() % 64 + 1); 110 | let inp_end = std::cmp::min(inp_start + inp_length, 111 | input.len()); 112 | 113 | // Select a random slice from a random input 114 | let donor_idx = rng.rand() % stats.input_list.len(); 115 | let donor_input = &stats.input_list[donor_idx]; 116 | if donor_input.len() == 0 { continue; } 117 | 118 | let donor_start = rng.rand() % donor_input.len(); 119 | let donor_length = rng.rand() % (rng.rand() % 64 + 1); 120 | let donor_end = std::cmp::min(donor_start + donor_length, 121 | donor_input.len()); 122 | 123 | // Spice in the donor input contents into the input 124 | input.splice(inp_start..inp_end, 125 | donor_input[donor_start..donor_end] 126 | .iter().cloned()); 127 | } 128 | 1 => { 129 | // Delete a random portion from the input 130 | 131 | // Select a random slice from our current input 132 | if input.len() == 0 { continue; } 133 | let inp_start = rng.rand() % input.len(); 134 | let inp_length = rng.rand() % (rng.rand() % 64 + 1); 135 | let inp_end = std::cmp::min(inp_start + inp_length, 136 | input.len()); 137 | 138 | // Delete this slice from the input 139 | input.splice(inp_start..inp_end, [].iter().cloned()); 140 | } 141 | 2 => { 142 | // Repeat a certain part of the slice many times 143 | if input.len() == 0 { continue; } 144 | let sel = rng.rand() % input.len(); 145 | for _ in 0..rng.rand() % (rng.rand() % 64 + 1) { 146 | input.insert(sel, input[sel]); 147 | } 148 | } 149 | 3 => { 150 | // Insert a random slice into the vector 151 | 152 | // Select a random index from our current input 153 | if input.len() == 0 { continue; } 154 | let inp_index = rng.rand() % input.len(); 155 | 156 | // Select a random slice from a random input 157 | let donor_idx = rng.rand() % stats.input_list.len(); 158 | let donor_input = &stats.input_list[donor_idx]; 159 | if donor_input.len() == 0 { continue; } 160 | let donor_start = rng.rand() % donor_input.len(); 161 | let donor_length = rng.rand() % (rng.rand() % 64 + 1); 162 | let donor_end = std::cmp::min(donor_start + donor_length, 163 | donor_input.len()); 164 | 165 | // Splice in donor slice into `inp_index` in the input 166 | let new_inp: Vec = input[0..inp_index].iter() 167 | .chain(donor_input[donor_start..donor_end].iter()) 168 | .chain(input[inp_index..].iter()).cloned().collect(); 169 | 170 | // Replace the input with this newly created input 171 | input = new_inp; 172 | } 173 | 4 => { 174 | if stats.unique_actions.len() == 0 || 175 | input.len() == 0 { continue; } 176 | 177 | // Get a random action 178 | let rand_action = stats.unique_actions[ 179 | rng.rand() % stats.unique_actions.len()]; 180 | 181 | // Add the action to the input 182 | input.insert(rng.rand() % input.len(), rand_action); 183 | } 184 | _ => panic!("Unreachable"), 185 | } 186 | } 187 | 188 | Ok(input) 189 | } 190 | 191 | pub fn generator(pid: u32) -> Result, Box> { 192 | // Log of all actions performed 193 | let mut actions = Vec::new(); 194 | 195 | // Create an RNG 196 | let rng = Rng::new(); 197 | 198 | // Attach to the Calculator window 199 | let primary_window = Window::attach_pid(pid, "Calculator")?; 200 | 201 | loop { 202 | { 203 | // Pick a random GUI element to click on 204 | let sub_windows = primary_window.enumerate_subwindows(); 205 | if sub_windows.is_err() { 206 | return Ok(actions); 207 | } 208 | let sub_windows = sub_windows.unwrap(); 209 | 210 | let sel = rng.rand() % sub_windows.len(); 211 | let window = sub_windows[sel]; 212 | 213 | // Click on the GUI element 214 | actions.push(FuzzerAction::LeftClick { idx: sel }); 215 | let _ = window.left_click(None); 216 | } 217 | 218 | { 219 | // Press a random key on the keyboard 220 | let key = ((rng.rand() % 10) as u8 + b'0') as usize; 221 | actions.push(FuzzerAction::KeyPress { key }); 222 | let _ = primary_window.press_key(key); 223 | } 224 | 225 | if rng.rand() & 0x1f == 0 { 226 | // Press a random key on the keyboard 227 | let key = rng.rand() as u8 as usize; 228 | actions.push(FuzzerAction::KeyPress { key }); 229 | let _ = primary_window.press_key(key); 230 | } 231 | 232 | // Chance of randomly closing the application 233 | if (rng.rand() & 0xff) == 0 { 234 | actions.push(FuzzerAction::Close); 235 | let _ = primary_window.close(); 236 | } 237 | 238 | // Chance of randomly clicking a menu item 239 | if (rng.rand() & 0x1f) == 0 { 240 | if let Ok(menus) = primary_window.enum_menus() { 241 | // Get a list of all of the menu items in calc 242 | let menus: Vec = menus.iter().cloned().collect(); 243 | 244 | // Select a random menu item and click it 245 | let sel = menus[rng.rand() % menus.len()]; 246 | actions.push(FuzzerAction::MenuAction { menu_id: sel }); 247 | let _ = primary_window.use_menu_id(sel); 248 | 249 | std::thread::sleep(std::time::Duration::from_millis(250)); 250 | } 251 | } 252 | } 253 | } 254 | 255 | -------------------------------------------------------------------------------- /guifuzz/src/rng.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | 3 | /// Random number generator implementation using xorshift64 4 | pub struct Rng { 5 | /// Interal xorshift seed 6 | seed: Cell, 7 | } 8 | 9 | impl Rng { 10 | /// Create a new, TSC-seeded random number generator 11 | pub fn new() -> Self { 12 | let ret = Rng { 13 | seed: Cell::new(unsafe { core::arch::x86_64::_rdtsc() }), 14 | }; 15 | 16 | for _ in 0..1000 { 17 | let _ = ret.rand(); 18 | } 19 | 20 | ret 21 | } 22 | 23 | /// Created a RNG with a fixed `seed` value 24 | pub fn seeded(seed: u64) -> Self { 25 | Rng { 26 | seed: Cell::new(seed), 27 | } 28 | } 29 | 30 | /// Get a random 64-bit number using xorshift 31 | pub fn rand(&self) -> usize { 32 | let mut seed = self.seed.get(); 33 | seed ^= seed << 13; 34 | seed ^= seed >> 17; 35 | seed ^= seed << 43; 36 | self.seed.set(seed); 37 | seed as usize 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /guifuzz/src/winbindings.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::fmt; 3 | use std::error::Error; 4 | use std::convert::TryInto; 5 | use std::ops::Deref; 6 | use std::collections::BTreeSet; 7 | 8 | /// Callback function for `EnumChildWindows()` 9 | type EnumChildProc = extern "C" fn(hwnd: usize, lparam: usize) -> bool; 10 | 11 | /// Callback function for `EnumWindows()` 12 | type EnumWindowsProc = extern "C" fn (hwnd: usize, lparam: usize) -> bool; 13 | 14 | #[link(name="User32")] 15 | extern "system" { 16 | fn FindWindowW(lpClassName: *mut u16, lpWindowName: *mut u16) -> usize; 17 | fn EnumChildWindows(hwnd: usize, func: EnumChildProc, 18 | lparam: usize) -> bool; 19 | fn GetWindowTextW(hwnd: usize, string: *mut u16, chars: i32) -> i32; 20 | fn GetWindowTextLengthW(hwnd: usize) -> i32; 21 | fn PostMessageW(hwnd: usize, msg: u32, wparam: usize, lparam: usize) 22 | -> bool; 23 | fn GetMenu(hwnd: usize) -> usize; 24 | fn GetSubMenu(hwnd: usize, pos: i32) -> usize; 25 | fn GetMenuItemID(menu: usize, pos: i32) -> u32; 26 | fn GetMenuItemCount(menu: usize) -> i32; 27 | fn EnumWindows(func: EnumWindowsProc, lparam: usize) -> bool; 28 | fn GetWindowThreadProcessId(hwnd: usize, pid: *mut u32) -> u32; 29 | } 30 | 31 | #[repr(C)] 32 | #[derive(Clone, Copy, Debug, Default)] 33 | struct Rect { 34 | left: i32, 35 | top: i32, 36 | right: i32, 37 | bottom: i32, 38 | } 39 | 40 | /// Convert a Rust UTF-8 `string` into a NUL-terminated UTF-16 vector 41 | fn str_to_utf16(string: &str) -> Vec { 42 | let mut ret: Vec = string.encode_utf16().collect(); 43 | ret.push(0); 44 | ret 45 | } 46 | 47 | /// An active handle to a window 48 | #[derive(Clone, Copy)] 49 | pub struct Window { 50 | /// Handle to the window which we have opened 51 | hwnd: usize, 52 | } 53 | 54 | impl fmt::Debug for Window { 55 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 56 | write!(f, "Window {{ hwnd: {:#x}, title: \"{}\" }}", 57 | self.hwnd, self.window_text().unwrap()) 58 | } 59 | } 60 | 61 | /// Structure which contains a listing of all child windows 62 | #[derive(Default, Debug)] 63 | pub struct WindowListing { 64 | /// List of all window HWNDs 65 | windows: Vec, 66 | } 67 | 68 | impl Deref for WindowListing { 69 | type Target = [Window]; 70 | 71 | fn deref(&self) -> &Self::Target { 72 | &self.windows 73 | } 74 | } 75 | 76 | /// Different message types to be sent to `PostMessage()` and `SendMessage()` 77 | #[repr(u32)] 78 | enum MessageType { 79 | /// Left mouse button down event 80 | LButtonDown = 0x0201, 81 | 82 | /// Left mouse button up event 83 | LButtonUp = 0x0202, 84 | 85 | /// Sends a key down event to the window 86 | KeyDown = 0x0100, 87 | 88 | /// Sends a key up event to the window 89 | KeyUp = 0x0101, 90 | 91 | /// Sends a command to a window, typically sent when a button is pressed 92 | /// or a menu item is used 93 | Command = 0x0111, 94 | 95 | /// Sends a graceful exit to the window 96 | Close = 0x0010, 97 | } 98 | 99 | /// Different types of virtual key codes 100 | #[repr(usize)] 101 | pub enum VirtualKeyCode { 102 | Left = 0x25, 103 | Up = 0x26, 104 | Right = 0x27, 105 | Down = 0x28, 106 | F10 = 0x79, 107 | } 108 | 109 | /// Rust implementation of `MENUITEMINFOW` 110 | #[repr(C)] 111 | #[derive(Debug, Default)] 112 | struct MenuItemInfo { 113 | size: u32, 114 | mask: u32, 115 | typ: u32, 116 | state: u32, 117 | id: u32, 118 | sub_menu: usize, 119 | bmp_checked: usize, 120 | bmp_unchecked: usize, 121 | item_data: usize, 122 | type_data: usize, 123 | cch: u32, 124 | bmp_item: usize, 125 | } 126 | 127 | impl Window { 128 | /// Find a window with `title`, and return a new `Window` object 129 | pub fn attach(title: &str) -> io::Result { 130 | // Convert the title to UTF-16 131 | let mut title = str_to_utf16(title); 132 | 133 | // Finds the window with `title` 134 | let ret = unsafe { 135 | FindWindowW(std::ptr::null_mut(), title.as_mut_ptr()) 136 | }; 137 | 138 | if ret != 0 { 139 | // Successfully got a handle to the window 140 | return Ok(Window { 141 | hwnd: ret, 142 | }); 143 | } else { 144 | // FindWindow() failed, return out the corresponding error 145 | Err(io::Error::last_os_error()) 146 | } 147 | } 148 | 149 | extern "C" fn enum_windows_handler(hwnd: usize, lparam: usize) -> bool { 150 | let param = unsafe { 151 | &mut *(lparam as *mut (u32, Option, String)) 152 | }; 153 | 154 | let mut pid = 0; 155 | let tid = unsafe{ 156 | GetWindowThreadProcessId(hwnd, &mut pid) 157 | }; 158 | if pid == 0 || tid == 0 { 159 | return true; 160 | } 161 | 162 | if param.0 == pid { 163 | // Create a window for this window we are enumerating 164 | let tmpwin = Window { hwnd }; 165 | 166 | // Get the title for the window 167 | if let Ok(title) = tmpwin.window_text() { 168 | // Check if the title matches what we are searching for 169 | if &title == ¶m.2 { 170 | // Match! 171 | param.1 = Some(hwnd); 172 | } 173 | } 174 | 175 | // Keep enumerating 176 | true 177 | } else { 178 | // Keep enumerating 179 | true 180 | } 181 | } 182 | 183 | /// Return a `Window` object for the `pid`s main window 184 | pub fn attach_pid(pid: u32, window_title: &str) -> io::Result { 185 | let mut context: (u32, Option, String) = 186 | (pid, None, window_title.into()); 187 | 188 | unsafe { 189 | if !EnumWindows(Self::enum_windows_handler, 190 | &mut context as *mut _ as usize) { 191 | // EnumWindows() failed, return out the corresponding error 192 | return Err(io::Error::last_os_error()); 193 | } 194 | } 195 | 196 | if let Some(hwnd) = context.1 { 197 | // Create the window object 198 | Ok(Window { hwnd }) 199 | } else { 200 | // Could not find a HWND 201 | Err(io::Error::new(io::ErrorKind::Other, 202 | "Could not find HWND for pid")) 203 | } 204 | } 205 | 206 | /// Internal callback for `EnumChildWindows()` used from the 207 | /// `enumerate_subwindows()` member function 208 | extern "C" fn enum_child_window_callback(hwnd: usize, lparam: usize) 209 | -> bool { 210 | // Get the parameter we passed in 211 | let listing: &mut WindowListing = unsafe { 212 | &mut *(lparam as *mut WindowListing) 213 | }; 214 | 215 | // Add this window handle to the listing 216 | listing.windows.push(Window { hwnd }); 217 | 218 | // Continue the search 219 | true 220 | } 221 | 222 | /// Enumerate all of the sub-windows belonging to `Self` recursively 223 | pub fn enumerate_subwindows(&self) -> io::Result { 224 | // Create a new, empty window listing 225 | let mut listing = WindowListing::default(); 226 | 227 | unsafe { 228 | // Enumerate all the child windows 229 | if EnumChildWindows(self.hwnd, 230 | Self::enum_child_window_callback, 231 | &mut listing as *mut WindowListing as usize) { 232 | // Child windows successfully enumerated 233 | Ok(listing) 234 | } else { 235 | // Failure during call to `EnumChildWindows()` 236 | Err(io::Error::last_os_error()) 237 | } 238 | } 239 | } 240 | 241 | /// Gets the title for the window, or in the case of a control field, gets 242 | /// the text on the object 243 | pub fn window_text(&self) -> Result> { 244 | let text_len = unsafe { GetWindowTextLengthW(self.hwnd) }; 245 | 246 | // Return an empty string if the window text length was reported as 247 | // zero 248 | if text_len == 0 { 249 | return Ok(String::new()); 250 | } 251 | 252 | // Allocate a buffer to hold `text_len` wide characters 253 | let text_len: usize = text_len.try_into().unwrap(); 254 | let alc_len = text_len.checked_add(1).unwrap(); 255 | let mut wchar_buffer: Vec = Vec::with_capacity(alc_len); 256 | 257 | unsafe { 258 | // Get the window text 259 | let ret = GetWindowTextW(self.hwnd, wchar_buffer.as_mut_ptr(), 260 | alc_len.try_into().unwrap()); 261 | 262 | // Set the length of the vector 263 | wchar_buffer.set_len(ret.try_into().unwrap()); 264 | } 265 | 266 | // Convert the UTF-16 string into a Rust UTF-8 `String` 267 | String::from_utf16(wchar_buffer.as_slice()).map_err(|x| { 268 | x.into() 269 | }) 270 | } 271 | 272 | /// Does a left click of the current window 273 | pub fn left_click(&self, state: Option) -> io::Result<()> { 274 | // Get the state, or create a new, empty state 275 | let mut state = state.unwrap_or_default(); 276 | 277 | unsafe { 278 | state.left_mouse = true; 279 | if !PostMessageW(self.hwnd, MessageType::LButtonDown as u32, 280 | state.into(), 0) { 281 | // PostMessageW() failed 282 | return Err(io::Error::last_os_error()); 283 | } 284 | 285 | state.left_mouse = false; 286 | if !PostMessageW(self.hwnd, MessageType::LButtonUp as u32, 287 | state.into(), 0) { 288 | // PostMessageW() failed 289 | return Err(io::Error::last_os_error()); 290 | } 291 | } 292 | 293 | Ok(()) 294 | } 295 | 296 | /// Presses a key down and releases it 297 | pub fn press_key(&self, key: usize) -> io::Result<()> { 298 | unsafe { 299 | if !PostMessageW(self.hwnd, MessageType::KeyDown as u32, key, 0) { 300 | // PostMessageW() failed 301 | return Err(io::Error::last_os_error()); 302 | } 303 | 304 | if !PostMessageW(self.hwnd, MessageType::KeyUp as u32, key, 305 | 3 << 30) { 306 | // PostMessageW() failed 307 | return Err(io::Error::last_os_error()); 308 | } 309 | } 310 | 311 | Ok(()) 312 | } 313 | 314 | /// Recurse into a menu listing, looking for sub menus 315 | fn recurse_menu(&self, menu_ids: &mut BTreeSet, menu_handle: usize) 316 | -> io::Result<()> { 317 | unsafe { 318 | // Get the number of menu items 319 | let menu_count = GetMenuItemCount(menu_handle); 320 | if menu_count == -1 { 321 | // GetMenuItemCount() failed 322 | return Err(io::Error::last_os_error()); 323 | } 324 | 325 | // Go through each item in the menu 326 | for menu_index in 0..menu_count { 327 | // Get the menu ID 328 | let menu_id = GetMenuItemID(menu_handle, menu_index); 329 | 330 | if menu_id == !0 { 331 | // Menu is a sub menu, get the sub menu handle 332 | let sub_menu = GetSubMenu(menu_handle, menu_index); 333 | if sub_menu == 0 { 334 | // GetSubMenu() failed 335 | return Err(io::Error::last_os_error()); 336 | } 337 | 338 | // Recurse into the sub-menu 339 | self.recurse_menu(menu_ids, sub_menu)?; 340 | } else { 341 | // This is a menu identifier, add it to the set 342 | menu_ids.insert(menu_id); 343 | } 344 | } 345 | 346 | Ok(()) 347 | } 348 | } 349 | 350 | /// Enumerate all window menus, return a set of the menu IDs which can 351 | /// be used with a `WM_COMMAND` message 352 | pub fn enum_menus(&self) -> io::Result> { 353 | // Get the window's main menu 354 | let menu = unsafe { GetMenu(self.hwnd) }; 355 | if menu == 0 { 356 | // GetMenu() error 357 | return Err(io::Error::last_os_error()); 358 | } 359 | 360 | // Create the empty hash set 361 | let mut menu_ids = BTreeSet::new(); 362 | 363 | // Recursively search through the menu 364 | self.recurse_menu(&mut menu_ids, menu)?; 365 | 366 | Ok(menu_ids) 367 | } 368 | 369 | /// Send a message to the window, indicating that `menu_id` was clicked. 370 | /// To get a valid `menu_id`, use the `enum_menus` member function. 371 | pub fn use_menu_id(&self, menu_id: u32) -> io::Result<()> { 372 | unsafe { 373 | if PostMessageW(self.hwnd, MessageType::Command as u32, 374 | menu_id.try_into().unwrap(), 0) { 375 | // Success! 376 | Ok(()) 377 | } else { 378 | // PostMessageW() error 379 | Err(io::Error::last_os_error()) 380 | } 381 | } 382 | } 383 | 384 | /// Attempts to gracefully close the applications 385 | pub fn close(&self) -> io::Result<()> { 386 | unsafe { 387 | if PostMessageW(self.hwnd, MessageType::Close as u32, 0, 0) { 388 | // Success! 389 | Ok(()) 390 | } else { 391 | // PostMessageW() error 392 | Err(io::Error::last_os_error()) 393 | } 394 | } 395 | } 396 | } 397 | 398 | /// Holds the state of some of the special keyboard and mouse buttons during 399 | /// certain mouse events 400 | #[derive(Default, Debug, Clone, Copy)] 401 | pub struct KeyMouseState { 402 | /// Left mouse button is down 403 | pub left_mouse: bool, 404 | 405 | /// Middle mouse button is down 406 | pub middle_mouse: bool, 407 | 408 | /// Right mouse button is down 409 | pub right_mouse: bool, 410 | 411 | /// Shift key is down 412 | pub shift: bool, 413 | 414 | /// First x button is down 415 | pub xbutton1: bool, 416 | 417 | /// Second x button is down 418 | pub xbutton2: bool, 419 | 420 | /// Control key is down 421 | pub control: bool, 422 | } 423 | 424 | 425 | impl Into for KeyMouseState { 426 | fn into(self) -> usize { 427 | (if self.left_mouse { 0x0001 } else { 0 }) | 428 | (if self.middle_mouse { 0x0010 } else { 0 }) | 429 | (if self.right_mouse { 0x0002 } else { 0 }) | 430 | (if self.shift { 0x0004 } else { 0 }) | 431 | (if self.xbutton1 { 0x0020 } else { 0 }) | 432 | (if self.xbutton2 { 0x0040 } else { 0 }) | 433 | (if self.control { 0x0008 } else { 0 }) 434 | } 435 | } 436 | 437 | -------------------------------------------------------------------------------- /mesos/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.x86_64-pc-windows-msvc] 2 | rustflags = ["-C", "target-feature=+crt-static"] 3 | 4 | [target.i586-pc-windows-msvc] 5 | rustflags = ["-C", "target-feature=+crt-static"] 6 | 7 | [target.i686-pc-windows-msvc] 8 | rustflags = ["-C", "target-feature=+crt-static"] 9 | 10 | -------------------------------------------------------------------------------- /mesos/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | *.i64 4 | *.idb 5 | *.meso 6 | cache 7 | coverage.txt 8 | *.zip 9 | *.dmp 10 | *.dmp 11 | /archivey_run 12 | /crashes 13 | /examples 14 | /inputs 15 | *.txt 16 | *.meso 17 | 18 | -------------------------------------------------------------------------------- /mesos/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "debugger" 5 | version = "0.1.0" 6 | dependencies = [ 7 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 8 | ] 9 | 10 | [[package]] 11 | name = "guifuzz" 12 | version = "0.1.0" 13 | 14 | [[package]] 15 | name = "mesos" 16 | version = "0.1.0" 17 | dependencies = [ 18 | "debugger 0.1.0", 19 | "guifuzz 0.1.0", 20 | ] 21 | 22 | [[package]] 23 | name = "winapi" 24 | version = "0.3.6" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | dependencies = [ 27 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 28 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 29 | ] 30 | 31 | [[package]] 32 | name = "winapi-i686-pc-windows-gnu" 33 | version = "0.4.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | 36 | [[package]] 37 | name = "winapi-x86_64-pc-windows-gnu" 38 | version = "0.4.0" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | 41 | [metadata] 42 | "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" 43 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 44 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 45 | -------------------------------------------------------------------------------- /mesos/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mesos" 3 | version = "0.1.0" 4 | authors = ["bfalk"] 5 | 6 | [dependencies] 7 | debugger = { path = "libs/debugger" } 8 | guifuzz = { path = "../guifuzz" } 9 | 10 | [profile.release] 11 | debug = true 12 | -------------------------------------------------------------------------------- /mesos/README.md: -------------------------------------------------------------------------------- 1 | ![Bag of Mesos][meso]![Bag of Mesos][meso]![Bag of Mesos][meso]![Bag of Mesos][meso]![Bag of Mesos][meso]![Bag of Mesos][meso]![Bag of Mesos][meso]![Bag of Mesos][meso]![Bag of Mesos][meso]![Bag of Mesos][meso]![Bag of Mesos][meso]![Bag of Mesos][meso]![Bag of Mesos][meso] 2 | 3 | # Summary 4 | 5 | Mesos is a tool to gather binary code coverage on all user-land Windows targets without need for source or recompilation. It also provides an automatic mechanism to save a full minidump of a process if it crashes under mesos. 6 | 7 | Mesos is technically just a really fast debugger, capable of handling tens of millions of breakpoints. Using this debugger, we apply breakpoints to every single basic block in a program. These breakpoints are removed as they are hit. Thus, mesos converges to 0-cost coverage as gathering coverage only has a cost the first time the basic block is hit. 8 | 9 | # Why? 10 | 11 | This is effectively the successor of my 5+ year old Chrome IPC fuzzer. It doesn't have any fuzz components in it, but it is a high-performance debugger. This debugger can apply millions of breakpoints to gather coverage, and handle thousands of breakpoints per second to modify memory to inject inputs. 12 | 13 | This strategy has worked out well for me historically and still is my go-to tooling for fuzzing targets on live systems. 14 | 15 | Out of the box it can be used to gather simple code coverage but it's designed to be easily modified to add fast breakpoint handlers to inject inputs. For example, put a breakpoint after `NtReadFile()` returns and modify the buffer in flight. I used this in Chrome to modify inbound IPC traffic in the browser. 16 | 17 | # Features 18 | 19 | ## Code coverage 20 | 21 | ![code coverage][code coverage] 22 | 23 | ## Automatic full minidump saving 24 | 25 | ![Crash being saved][crash saving] 26 | 27 | ## IDA Coloring 28 | 29 | ![IDA gettin colored up][ida coloring] 30 | 31 | # Quick Usage Guide 32 | 33 | Set `%PATH%` such that `idat64.exe` is in it: 34 | 35 | ``` 36 | path %PATH%;"C:\Program Files\IDA 7.2" 37 | ``` 38 | 39 | Generate mesos (the first time will be slow): 40 | 41 | ``` 42 | powershell .\offline_meso.ps1 43 | python generate_mesos.py process_ida 44 | ``` 45 | 46 | Gather coverage on target! 47 | 48 | ``` 49 | cargo build --release 50 | target\release\mesos.exe 51 | ``` 52 | 53 | Applying 1.6 million breakpoints? No big deal. 54 | 55 | ``` 56 | C:\dev\mesos>target\release\mesos.exe 13828 57 | mesos is 64-bit: true 58 | target is 64-bit: true 59 | [ 0.003783] Applied 5629 breakpoints ( 5629 total breakpoints) notepad.exe 60 | [ 0.028071] Applied 61334 breakpoints ( 66963 total breakpoints) ntdll.dll 61 | [ 0.035298] Applied 25289 breakpoints ( 92252 total breakpoints) kernel32.dll 62 | [ 0.058815] Applied 55611 breakpoints ( 147863 total breakpoints) kernelbase.dll 63 | ... 64 | [ 0.667417] Applied 11504 breakpoints ( 1466344 total breakpoints) oleacc.dll 65 | [ 0.676151] Applied 19557 breakpoints ( 1485901 total breakpoints) textinputframework.dll 66 | [ 0.705431] Applied 66650 breakpoints ( 1552551 total breakpoints) coreuicomponents.dll 67 | [ 0.717276] Applied 25202 breakpoints ( 1577753 total breakpoints) coremessaging.dll 68 | [ 0.720487] Applied 7557 breakpoints ( 1585310 total breakpoints) ntmarta.dll 69 | [ 0.732045] Applied 28569 breakpoints ( 1613879 total breakpoints) iertutil.dll 70 | ``` 71 | 72 | # API 73 | 74 | Currently this tool has a `debugger` lib you can easily bring in and start using to make custom debuggers. However this API is not finalized yet. I suggest not building anything using it yet as it may change very quickly. Once this message is removed it's probably stable :) 75 | 76 | # Performance 77 | 78 | - We can register (request breakpoints to be at module load) about ~6 million/second 79 | - We can apply them (actually install the breakpoints into the target at about ~3 million/second 80 | - We can clear breakpoints at about 15 million/second 81 | - We can hit and handle about 10k breakpoints/second 82 | 83 | Given breakpoints are cleared as they're hit for coverage, that means you can observe 10k _new_ blocks per second. Once you've hit a breakpoint they no longer have a performance cost! 84 | 85 | ``` 86 | C:\dev\mesos\examples\benchmark>cargo run --release 87 | Finished release [optimized + debuginfo] target(s) in 0.03s 88 | Running `target\release\benchmark.exe` 89 | mesos is 64-bit: true 90 | target is 64-bit: true 91 | Registered 1000000 breakpoints in 0.162230 seconds | 6164072.8 / second 92 | Applied 1000000 breakpoints in 0.321347 seconds | 3111897.0 / second 93 | Cleared 1000000 breakpoints in 0.067024 seconds | 14920028.6 / second 94 | Hit 100000 breakpoints in 10.066440 seconds | 9934.0 / second 95 | ``` 96 | 97 | # Usage 98 | 99 | To use mesos there are 3 major steps. First, the modules of a running process are saved. Second, these modules are loaded in IDA which then outputs a list of all basic blocks into the `meso` format. And finally, `mesos` is run against a target process to gather coverage! 100 | 101 | ## Creating meso_deps.zip 102 | 103 | This step is the first thing we have to do. We create a ZIP file containing all of the modules loaded into a given PID. 104 | 105 | This script requires no internet and is designed to be easily dropped onto new VMs so mesos can be generated for your target application. It depends on PowerShell v5.0 or later which is installed by default on Windows 10 and Windows Server 2016. 106 | 107 | Run, with `` replaced with the process ID you want to gather coverage on: 108 | 109 | ``` 110 | C:\dev\mesos>powershell .\offline_meso.ps1 8484 111 | Powershell is 64-bit: True 112 | Target is 64-bit: True 113 | 114 | C:\dev\mesos> 115 | ``` 116 | 117 | _Optionally you can supply `-OutputZip ` to change the output zip file name_ 118 | 119 | This will create a `meso_deps.zip` that if you look at contains all of the modules used in the process you ran the script targeting. 120 | 121 | ### Example output: 122 | 123 | ``` 124 | C:\dev\mesos>powershell .\offline_meso.ps1 8484 -OutputZip testing.zip 125 | Powershell is 64-bit: True Target is 64-bit: True C:\dev\mesos>powershell Expand-Archive testing.zip -DestinationPath example 126 | C:\dev\mesos>powershell Get-ChildItem example -rec -File -Name 127 | cache\c_\program files\common files\microsoft shared\ink\tiptsf.dll 128 | cache\c_\program files\intel\optaneshellextensions\iastorafsserviceapi.dll 129 | cache\c_\program files\widcomm\bluetooth software\btmmhook.dll 130 | cache\c_\program files (x86)\common files\adobe\coresyncextension\coresync_x64.dll 131 | ... 132 | ``` 133 | 134 | ## Generating meso files 135 | 136 | To generate meso files we operate on the `meso_deps.zip` we created in the last step. It doesn't matter where this zip came from. This allows the zip to have come from a VM that the PowerShell script was run on. 137 | 138 | Basic usage is: 139 | 140 | ``` 141 | python generate_mesos.py process_ida 142 | ``` 143 | 144 | This will use the `meso_deps.zip` file as an input, and use IDA to process all executables in the zip file and figure out where their basic blocks are. 145 | 146 | This will create a cache folder with a bunch of files in it. These files are named based on the module name, the modules TimeDateStamp in the PE header, and the ImageSize field in the PE header. This is what DLLs are uniqued by in the PDB symbol store, so it should be good enough for us here too. 147 | 148 | You'll see there are files with no extension (these are the original binaries), there are files with `.meso` extensions (the breakpoint lists), and `.i64` files (the cached IDA database for the original binary). 149 | 150 | ### Symbol resolution 151 | 152 | There is no limitation on what can make these meso files. The quality of the symbol resolution depends on the tool you used to generate and it's ability to resolve symbols. For example with IDA if you have public/private symbols your `_NT_SYMBOL_PATH` should be configured correctly. 153 | 154 | ### More advanced usage 155 | 156 | Check the programs usage for the most recent usage. But there are `_whitelist` and `_blacklist` options that allow you to use a list of strings to filter the amount of mesos generated. 157 | 158 | This is helpful as coverage outside of your target module is probably not relevant and just introduces overheads and unnecessary processing. 159 | 160 | ``` 161 | C:\dev\mesos>python generate_mesos.py 162 | Usage: 163 | generate_mesos.py process_ida 164 | Processes all files in the meso_deps.zip file 165 | 166 | generate_mesos.py process_ida_whitelist 167 | Processes files only containing one of the strings provided 168 | 169 | generate_mesos.py process_ida_blacklist 170 | Processes files all files except for those containing one of the provided strings 171 | 172 | Examples: 173 | 174 | python generate_mesos.py process_ida_whitelist system32 175 | Only processes files in `system32` 176 | 177 | python generate_mesos.py process_ida_blacklist ntdll.dll 178 | Process all files except for `ntdll.dll` 179 | 180 | Path requirements for process_ida_*: must have `idat64.exe` in your PATH 181 | ``` 182 | 183 | ### Example usage 184 | 185 | ``` 186 | C:\dev\mesos>python generate_mesos.py process_ida_whitelist system32 187 | Processing cache/c_/windows/system32/advapi32.dll 188 | Processing cache/c_/windows/system32/bcryptprimitives.dll 189 | Processing cache/c_/windows/system32/cfgmgr32.dll 190 | ... 191 | Processing cache/c_/windows/system32/user32.dll 192 | Processing cache/c_/windows/system32/uxtheme.dll 193 | Processing cache/c_/windows/system32/win32u.dll 194 | Processing cache/c_/windows/system32/windows.storage.dll 195 | Processing cache/c_/windows/system32/wintypes.dll 196 | ``` 197 | 198 | ## Meso usage 199 | 200 | Now we're onto the actual debugger. We've created meso files to tell it where to put breakpoints in each module. 201 | 202 | First we need to build it with Rust! 203 | 204 | ``` 205 | cargo build --release 206 | ``` 207 | 208 | And then we can simply run it with a PID! 209 | 210 | ``` 211 | target\release\mesos.exe 212 | ``` 213 | 214 | ### Command-line options 215 | 216 | Currently there are few options to mesos, run mesos without arguments to get the most recent list. 217 | 218 | ``` 219 | C:\dev\mesos>target\release\mesos.exe 220 | Usage: mesos.exe [--freq | --verbose | --print] 221 | --freq - Treats all breakpoints as frequency breakpoints 222 | --verbose - Enables verbose prints for debugging 223 | --print - Prints breakpoint info on every single breakpoint 224 | [explicit meso file] - Load a specific meso file regardless of loaded modules 225 | 226 | Standard usage: mesos.exe 227 | ``` 228 | 229 | ### Example usage 230 | 231 | ``` 232 | C:\dev\mesos>target\release\mesos.exe 13828 233 | mesos is 64-bit: true 234 | target is 64-bit: true 235 | [ 0.004033] Applied 5629 breakpoints ( 5629 total breakpoints) notepad.exe 236 | [ 0.029248] Applied 61334 breakpoints ( 66963 total breakpoints) ntdll.dll 237 | [ 0.037032] Applied 25289 breakpoints ( 92252 total breakpoints) kernel32.dll 238 | [ 0.062844] Applied 55611 breakpoints ( 147863 total breakpoints) kernelbase.dll 239 | ... 240 | [ 0.739059] Applied 66650 breakpoints ( 1552551 total breakpoints) coreuicomponents.dll 241 | [ 0.750266] Applied 25202 breakpoints ( 1577753 total breakpoints) coremessaging.dll 242 | [ 0.754485] Applied 7557 breakpoints ( 1585310 total breakpoints) ntmarta.dll 243 | [ 0.766119] Applied 28569 breakpoints ( 1613879 total breakpoints) iertutil.dll 244 | ... 245 | [ 23.544097] Removed 5968 breakpoints in imm32.dll 246 | [ 23.551529] Syncing code coverage database... 247 | [ 23.675103] Sync complete (169694 total unique coverage entries) 248 | Detached from process 13828 249 | ``` 250 | 251 | #### Why not use `cargo run`? 252 | 253 | When running in `cargo run` the Ctrl+C handler does not work correctly, and does not allow us to detach from the target program cleanly. 254 | 255 | # Limitations 256 | 257 | Since this relies on a tool (IDA) to identify blocks, if the tool incorrectly identifies a block it could result in us inserting a breakpoint over data. Further it's possible to miss coverage if a block is not correctly found. 258 | 259 | # Why doesn't it do more? 260 | 261 | Well. It really just allows fast breakpoints. Feel free to rip it apart and add your own hooks to functions. It could easily be used to fuzz things :) 262 | 263 | # Why IDA? 264 | 265 | I tried a bunch of tools and IDA was the only one that seemed to work well. Binja probably would also work well but I don't have it installed and I'm not familiar with the API. I have a coworker who wrote a plugin for it and that'll probably get pull requested in soon. 266 | 267 | _The meso files are just simple files, anyone can generate them from any tool_ 268 | 269 | # Technical Details 270 | 271 | ## Minidump autogenned filenames 272 | 273 | The generated minidump filenames are designed to give a high-level of glance value at crashes. It includes things like the exception type, faulting address, and rough classification of the bug. 274 | 275 | Currently if it's an access violation we apply the following classification: 276 | 277 | - Determine the access type (read, write, execute) 278 | - For reads the filename contains: "read" 279 | - For writes the filename contains: "WRITE" 280 | - For execute the filename contains: "DEP" 281 | - Determine if it's a non-canonical 64-bit address 282 | - For non-canonical addresses the filename contains: NONCANON 283 | - Otherwise determine if it's a NULL dereference (within 32 KiB +- of NULL) 284 | - Will put "null" in the filename 285 | - Otherwise it's considered a non-null deref and "HIGH" appears in the filename 286 | 287 | It's intended that more severe things are in all caps to give higher glance value of prioritizing which crash dumps to look into more. 288 | 289 | Example minidump filename for chrome: 290 | 291 | ``` 292 | crash_c0000005_chrome_child.dll+0x2c915c0_WRITE_null.dmp 293 | ``` 294 | 295 | ## Meso file format 296 | 297 | Coming soon (once it's stable) 298 | 299 | [meso]: assets/meso_bag.png 300 | [crash saving]: assets/crash_saving.png 301 | [code coverage]: assets/code_coverage.png 302 | [ida coloring]: assets/ida_coloring.png 303 | -------------------------------------------------------------------------------- /mesos/assets/code_coverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamozolabs/guifuzz/471d744e0e46d21cad39e4287ddc6f13c9811b17/mesos/assets/code_coverage.png -------------------------------------------------------------------------------- /mesos/assets/crash_saving.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamozolabs/guifuzz/471d744e0e46d21cad39e4287ddc6f13c9811b17/mesos/assets/crash_saving.png -------------------------------------------------------------------------------- /mesos/assets/ida_coloring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamozolabs/guifuzz/471d744e0e46d21cad39e4287ddc6f13c9811b17/mesos/assets/ida_coloring.png -------------------------------------------------------------------------------- /mesos/assets/meso_bag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamozolabs/guifuzz/471d744e0e46d21cad39e4287ddc6f13c9811b17/mesos/assets/meso_bag.png -------------------------------------------------------------------------------- /mesos/coverage_scripts/ida.py: -------------------------------------------------------------------------------- 1 | import collections, re, math 2 | 3 | def addr2block(addr): 4 | f = idaapi.get_func(addr) 5 | if not f: 6 | print "No function at 0x%x" % (addr) 7 | return None 8 | 9 | fc = idaapi.FlowChart(f) 10 | 11 | for block in fc: 12 | if (block.startEA <= addr) and (block.endEA > addr): 13 | return (block.startEA, block.endEA) 14 | return None 15 | 16 | fft = re.compile("[0-9a-f]{16} \| Freq: +([0-9]+) \| +(.*?)\+0x([0-9a-f]+) \| (.*?)\n") 17 | 18 | image_base = idaapi.get_imagebase() 19 | ida_modname = GetInputFile().lower() 20 | 21 | SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 22 | 23 | inp = open(os.path.join(SCRIPT_DIR, "..", "coverage.txt"), "r").read() 24 | 25 | # Reset all coloring in all functions 26 | for funcea in Functions(): 27 | f = idaapi.get_func(funcea) 28 | fc = idaapi.FlowChart(f) 29 | 30 | for block in fc: 31 | ea = block.startEA 32 | while ea <= block.endEA: 33 | set_color(ea, CIC_ITEM, DEFCOLOR) 34 | ea = idc.NextHead(ea) 35 | 36 | freqs = collections.Counter() 37 | 38 | # Parse input coverage file 39 | for thing in fft.finditer(inp): 40 | freq = int(thing.group(1), 10) 41 | module = thing.group(2) 42 | offset = int(thing.group(3), 16) 43 | 44 | # Skip non-matching modules 45 | if module.lower() not in ida_modname: 46 | continue 47 | 48 | freqs[image_base + offset] = freq 49 | 50 | # Apply coloring 51 | for addr, freq in freqs.most_common()[::-1]: 52 | function_addr = get_func_attr(addr, FUNCATTR_START) 53 | func_entry_freq = freqs[function_addr] 54 | 55 | if func_entry_freq == 0: 56 | func_entry_freq = 1 57 | 58 | # Log value between [0.0, 1.0) 59 | dist = math.log(float(freq) / float(func_entry_freq) + 1.0) 60 | dist = min(dist, float(1.0)) 61 | 62 | color = 0x808080 + (int((1 - dist) * 100.0) << 8) + (int(dist * 100.0) << 0) 63 | print("%10d | 0x%.16x | %s" % (freq, addr, get_func_off_str(addr))) 64 | 65 | blockbounds = addr2block(addr) 66 | if blockbounds == None: 67 | # Color just the single PC, we don't know what block it belongs to 68 | set_color(addr, CIC_ITEM, color) 69 | else: 70 | # Color in the entire block 71 | (ea, block_end) = blockbounds 72 | while ea < block_end: 73 | set_color(ea, CIC_ITEM, color) 74 | ea = idc.NextHead(ea) 75 | 76 | set_cmt(addr, "Freq: %d | Func entry: %.2f" % \ 77 | (freq, float(freq) / float(func_entry_freq)), False) 78 | -------------------------------------------------------------------------------- /mesos/generate_mesos.py: -------------------------------------------------------------------------------- 1 | import sys, subprocess, os, zipfile, threading, time, struct 2 | 3 | # Maximum number of processing threads to use 4 | # idat64.exe fails silently on OOMs and stuff so we just keep this reasonable 5 | MAX_JOBS = 4 6 | 7 | # Name of the input zip file created by `offline_meso.ps1` 8 | PREP_ZIP = "meso_deps.zip" 9 | 10 | # Name of IDA executable 11 | IDA_NAME = "idat64.exe" 12 | 13 | # Name of the folder to write the mesos to 14 | CACHE_FOLDER_NAME = "cache" 15 | 16 | def usage(): 17 | print("Usage:") 18 | print(" generate_mesos.py process_ida") 19 | print(" Processes all files in the meso_deps.zip file\n") 20 | 21 | print(" generate_mesos.py process_ida_whitelist ") 22 | print(" Processes files only containing one of the strings provided\n") 23 | 24 | print(" generate_mesos.py process_ida_blacklist ") 25 | print(" Processes files all files except for those containing one of the provided strings\n") 26 | 27 | print("Examples:\n") 28 | print(" python generate_mesos.py process_ida_whitelist system32") 29 | print(" Only processes files in `system32`\n") 30 | print(" python generate_mesos.py process_ida_blacklist ntdll.dll") 31 | print(" Process all files except for `ntdll.dll`\n") 32 | 33 | print("Path requirements for process_ida_*: must have `idat64.exe` in your PATH") 34 | quit() 35 | 36 | # Make sure ida is installed and working 37 | def ida_probe(): 38 | PROBENAME = os.path.join("mesogen_scripts", ".idaprobe") 39 | 40 | def ida_error(): 41 | # Validate it worked! 42 | assert os.path.exists(PROBENAME), \ 43 | "idat64.exe is not in your PATH or it's not functioning. \ 44 | Perhaps you need to accept a license agreement" 45 | 46 | # Remove probe file 47 | try: 48 | os.unlink(PROBENAME) 49 | except FileNotFoundError: 50 | pass 51 | 52 | # Invoke IDA to generate the idaprobe file 53 | try: 54 | subprocess.check_call([IDA_NAME, "-t", "-A", 55 | "-Smesogen_scripts/ida_detect.py"], shell=True) 56 | except: 57 | pass 58 | 59 | # Display error if file did not get created 60 | ida_error() 61 | 62 | # Remove file 63 | os.unlink(PROBENAME) 64 | 65 | def process_ida(orig_name, cache_fn, cache_fn_bin, contents): 66 | if not os.path.exists(cache_fn): 67 | # Make the hirearchy for the cache file 68 | try: 69 | os.makedirs(os.path.dirname(cache_fn)) 70 | except FileExistsError: 71 | pass 72 | 73 | # Save file to disk 74 | with open(cache_fn_bin, "wb") as fd: 75 | fd.write(contents) 76 | 77 | # Invoke IDA to generate the meso file 78 | subprocess.check_call([ 79 | IDA_NAME, "-o%s.idb" % cache_fn, "-A", 80 | "-Smesogen_scripts/ida.py cmdline \"%s\" \"%s\"" % \ 81 | (cache_fn, os.path.basename(orig_name)), 82 | "-c", cache_fn_bin], shell=True) 83 | 84 | def process(whitelist, blacklist): 85 | # Open the zip file generated by an offline meso script 86 | tf = zipfile.ZipFile(PREP_ZIP, "r") 87 | 88 | for member in tf.infolist(): 89 | # If there's no file size (it's a directory) skip it 90 | if member.file_size == 0: 91 | continue 92 | 93 | # Check if the blacklist excludes this file 94 | if blacklist is not None: 95 | in_blacklist = filter(lambda x: x in member.filename, blacklist) 96 | if len(list(in_blacklist)) > 0: 97 | continue 98 | 99 | # Check if the whitelist includes a file 100 | if whitelist is not None: 101 | in_whitelist = filter(lambda x: x in member.filename, whitelist) 102 | if len(list(in_whitelist)) == 0: 103 | continue 104 | 105 | print("Processing %s" % member.filename) 106 | 107 | # Read the file from the archive 108 | contents = None 109 | with tf.open(member, "r") as fd: 110 | contents = fd.read() 111 | 112 | # Parse out the TimeDateStamp and SizeOfImage from the PE header 113 | assert contents[:2] == b"MZ" 114 | pe_ptr = struct.unpack(" MAX_JOBS: 131 | time.sleep(0.1) 132 | 133 | # Create thread 134 | threading.Timer(0.0, process_ida, \ 135 | args=[image_name, cache_fn, cache_fn_bin, contents]) \ 136 | .start() 137 | 138 | # Wait for all jobs to finish 139 | while threading.active_count() > 1: 140 | time.sleep(0.1) 141 | 142 | # Check that IDA is installed and working 143 | ida_probe() 144 | 145 | args = sys.argv[1:] 146 | 147 | # Super secret options 148 | while len(args) >= 2: 149 | if args[0] == "--zipfile": 150 | # Use a custom zipfile as input 151 | PREP_ZIP = args[1] 152 | args = args[2:] 153 | continue 154 | elif args[0] == "--threads": 155 | # Change the number of worker jobs for meso generation 156 | MAX_JOBS = int(args[1]) 157 | args = args[2:] 158 | continue 159 | break 160 | 161 | if len(args) == 1 and args[0] == "process_ida": 162 | process(None, None) 163 | elif len(args) >= 2 and args[0] == "process_ida_whitelist": 164 | process(args[1:], None) 165 | elif len(args) >= 2 and args[0] == "process_ida_blacklist": 166 | process(None, args[1:]) 167 | else: 168 | usage() 169 | -------------------------------------------------------------------------------- /mesos/libs/debugger/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "debugger" 3 | version = "0.1.0" 4 | authors = ["Brandon Falk "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | winapi = { version = "0.3.5", features = ["debugapi", "winbase", "memoryapi", "processthreadsapi", "errhandlingapi", "handleapi", "securitybaseapi", "consoleapi", "winerror", "wow64apiset", "psapi"] } 9 | -------------------------------------------------------------------------------- /mesos/libs/debugger/src/debugger.rs: -------------------------------------------------------------------------------- 1 | /// High performance debugger for fuzzing and gathering code coverage on 2 | /// Windows 3 | 4 | use std::io; 5 | use std::time::{Duration, Instant}; 6 | use std::collections::{HashSet, HashMap}; 7 | use std::path::Path; 8 | use std::sync::Arc; 9 | use std::fs::File; 10 | use std::ffi::CString; 11 | use std::io::Write; 12 | use std::io::BufWriter; 13 | use std::sync::atomic::{AtomicBool, Ordering}; 14 | 15 | use winapi::um::memoryapi::ReadProcessMemory; 16 | use winapi::um::memoryapi::WriteProcessMemory; 17 | use winapi::um::winnt::CONTEXT; 18 | use winapi::um::winnt::CONTEXT_ALL; 19 | use winapi::um::winnt::DBG_CONTINUE; 20 | use winapi::um::winnt::DBG_EXCEPTION_NOT_HANDLED; 21 | use winapi::um::winnt::EXCEPTION_RECORD; 22 | use winapi::um::errhandlingapi::GetLastError; 23 | use winapi::um::minwinbase::CREATE_PROCESS_DEBUG_EVENT; 24 | use winapi::um::minwinbase::CREATE_THREAD_DEBUG_EVENT; 25 | use winapi::um::minwinbase::EXCEPTION_DEBUG_EVENT; 26 | use winapi::um::minwinbase::LOAD_DLL_DEBUG_EVENT; 27 | use winapi::um::minwinbase::EXIT_THREAD_DEBUG_EVENT; 28 | use winapi::um::minwinbase::EXIT_PROCESS_DEBUG_EVENT; 29 | use winapi::um::minwinbase::UNLOAD_DLL_DEBUG_EVENT; 30 | use winapi::um::minwinbase::OUTPUT_DEBUG_STRING_EVENT; 31 | use winapi::um::minwinbase::RIP_EVENT; 32 | use winapi::shared::winerror::ERROR_SEM_TIMEOUT; 33 | use winapi::um::debugapi::WaitForDebugEvent; 34 | use winapi::um::debugapi::DebugActiveProcessStop; 35 | use winapi::um::debugapi::DebugActiveProcess; 36 | use winapi::um::debugapi::ContinueDebugEvent; 37 | use winapi::um::processthreadsapi::GetProcessId; 38 | use winapi::um::processthreadsapi::GetCurrentProcess; 39 | use winapi::um::processthreadsapi::SetThreadContext; 40 | use winapi::um::processthreadsapi::GetThreadContext; 41 | use winapi::um::processthreadsapi::FlushInstructionCache; 42 | use winapi::um::processthreadsapi::TerminateProcess; 43 | use winapi::um::processthreadsapi::OpenProcess; 44 | use winapi::um::processthreadsapi::CreateProcessA; 45 | use winapi::um::wow64apiset::IsWow64Process; 46 | use winapi::um::winnt::PROCESS_QUERY_LIMITED_INFORMATION; 47 | use winapi::um::psapi::GetMappedFileNameW; 48 | use winapi::um::winnt::HANDLE; 49 | use winapi::um::minwinbase::DEBUG_EVENT; 50 | use winapi::um::winbase::DEBUG_PROCESS; 51 | use winapi::um::winbase::DEBUG_ONLY_THIS_PROCESS; 52 | use winapi::um::consoleapi::SetConsoleCtrlHandler; 53 | 54 | use crate::minidump::dump; 55 | use crate::handles::Handle; 56 | 57 | /// Tracks if an exit has been requested via the Ctrl+C/Ctrl+Break handler 58 | static EXIT_REQUESTED: AtomicBool = AtomicBool::new(false); 59 | 60 | /// Function invoked on module loads 61 | /// (debugger, module filename, module base) 62 | type ModloadFunc = Box; 63 | 64 | /// Function invoked on debug events 65 | type DebugEventFunc = Box; 66 | 67 | /// Function invoked on breakpoints 68 | /// (debugger, tid, address of breakpoint, 69 | /// number of times breakpoint has been hit) 70 | /// If this returns false the debuggee is terminated 71 | type BreakpointCallback = fn(&mut Debugger, u32, usize, u64) -> bool; 72 | 73 | /// Ctrl+C handler so we can remove breakpoints and detach from the debugger 74 | unsafe extern "system" fn ctrl_c_handler(_ctrl_type: u32) -> i32 { 75 | // Store that an exit was requested 76 | EXIT_REQUESTED.store(true, Ordering::SeqCst); 77 | 78 | // Sleep forever 79 | loop { 80 | std::thread::sleep(Duration::from_secs(100)); 81 | } 82 | } 83 | 84 | /// Different types of breakpoints 85 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 86 | pub enum BreakpointType { 87 | /// Keep the breakpoint in place and keep track of how many times it was 88 | /// hit 89 | Freq, 90 | 91 | /// Delete the breakpoint after it has been hit once 92 | Single, 93 | } 94 | 95 | /// Different types of ways the `Debugger::run()` routine can return 96 | #[derive(Clone, Debug, PartialEq, Eq)] 97 | pub enum ExitType { 98 | /// Program exited gracefully with a given exit code 99 | ExitCode(i32), 100 | 101 | /// Program crashed 102 | Crash(String), 103 | } 104 | 105 | /// Structure to represent breakpoints 106 | #[derive(Clone)] 107 | pub struct Breakpoint { 108 | /// Offset from module base 109 | offset: usize, 110 | 111 | /// Tracks if this breakpoint is currently active 112 | enabled: bool, 113 | 114 | /// Original byte that was at this location, only set if breakpoint was 115 | /// ever applied 116 | orig_byte: Option, 117 | 118 | /// Tracks if this breakpoint should stick around after it's hit once 119 | typ: BreakpointType, 120 | 121 | /// Name of the function this breakpoint is in 122 | funcname: Arc, 123 | 124 | /// Offset into the function that this breakpoint addresses 125 | funcoff: usize, 126 | 127 | /// Module name 128 | modname: Arc, 129 | 130 | /// Callback to invoke if this breakpoint is hit 131 | callback: Option, 132 | 133 | /// Number of times this breakpoint has been hit 134 | freq: u64, 135 | } 136 | 137 | /// Debugger for a single process 138 | pub struct Debugger<'a> { 139 | /// List of breakpoints we want to apply, keyed by module 140 | /// This is not the _active_ list of breakpoints, it only refers to things 141 | /// we would like to apply if we see this module show up 142 | target_breakpoints: HashMap>, 143 | 144 | /// List of potentially active breakpoints, keyed by linear address 145 | /// They may be optionally disabled via `Breakpoint.enabled` 146 | breakpoints: HashMap, 147 | 148 | /// Tracks the minimum and maximum addresses for breakpoints per module 149 | minmax_breakpoint: HashMap, 150 | 151 | /// Handle to the process, given by the first create process event so it 152 | /// is not present until `run()` is used 153 | process_handle: Option, 154 | 155 | /// List of callbacks to invoke when a module is loaded 156 | module_load_callbacks: Option>, 157 | 158 | /// List of callbacks to invoke when a debug event is fired 159 | debug_event_callbacks: Option>, 160 | 161 | /// Thread ID to handle map 162 | thread_handles: HashMap, 163 | 164 | /// List of all PCs we hit during execution 165 | /// Keyed by PC 166 | /// Tuple is (module, offset, symbol+offset, frequency) 167 | pub coverage: HashMap, usize, String, u64)>, 168 | 169 | /// Set of DLL names and the corresponding DLL base 170 | modules: HashSet<(String, usize)>, 171 | 172 | /// TIDs actively single stepping mapped to the PC they stepped from 173 | single_step: HashMap, 174 | 175 | /// Always do frequency tracking. Disables printing to screen and updates 176 | /// the coverage database on an interval to decrease I/O 177 | always_freq: bool, 178 | 179 | /// Last time we saved the coverage database 180 | last_db_save: Instant, 181 | 182 | /// Prints some more status information during runtime 183 | verbose: bool, 184 | 185 | /// Process ID of the process we're debugging 186 | pub pid: u32, 187 | 188 | /// Time we attached to the target at 189 | start_time: Instant, 190 | 191 | /// Prints breakpoints as we hit them if set 192 | bp_print: bool, 193 | 194 | /// Tracks if we want to kill the debuggee 195 | kill_requested: bool, 196 | 197 | /// Pointer to aligned context structure 198 | context: &'a mut CONTEXT, 199 | _context_backing: Vec, 200 | } 201 | 202 | /// Get elapsed time in seconds 203 | fn elapsed_from(start: &Instant) -> f64 { 204 | let dur = start.elapsed(); 205 | dur.as_secs() as f64 + dur.subsec_nanos() as f64 / 1_000_000_000.0 206 | } 207 | 208 | // Mesos print with uptime prefix 209 | macro_rules! mprint { 210 | ($x:ident, $($arg:tt)*) => { 211 | format!("[{:14.6}] ", elapsed_from(&$x.start_time)); 212 | format!($($arg)*); 213 | } 214 | } 215 | 216 | impl<'a> Debugger<'a> { 217 | /// Create a new debugger and attach to `pid` 218 | pub fn attach(pid: u32) -> Debugger<'a> { 219 | Debugger::attach_internal(pid, false) 220 | } 221 | 222 | /// Create a new process argv[0], with arguments argv[1..] and attach to it 223 | pub fn spawn_proc(argv: &[String], follow_fork: bool) -> Debugger<'a> { 224 | let mut startup_info = unsafe { std::mem::zeroed() }; 225 | let mut proc_info = unsafe { std::mem::zeroed() }; 226 | 227 | let cmdline = CString::new(argv.join(" ")).unwrap(); 228 | 229 | let cmdline_ptr = cmdline.into_raw(); 230 | 231 | let flags = if follow_fork { 232 | DEBUG_PROCESS 233 | } 234 | else { 235 | DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS 236 | }; 237 | 238 | unsafe { 239 | assert!(CreateProcessA( 240 | std::ptr::null_mut(), // lpApplicationName 241 | cmdline_ptr, // lpCommandLine 242 | std::ptr::null_mut(), // lpProcessAttributes 243 | std::ptr::null_mut(), // lpThreadAttributes 244 | 0, // bInheritHandles 245 | flags, // dwCreationFlags 246 | std::ptr::null_mut(), // lpEnvironment 247 | std::ptr::null_mut(), // lpCurrentDirectory 248 | &mut startup_info, // lpStartupInfo 249 | &mut proc_info) != 0, // lpProcessInformation 250 | "Failed to create process."); 251 | } 252 | 253 | let pid = unsafe { GetProcessId(proc_info.hProcess) }; 254 | 255 | Debugger::attach_internal(pid, true) 256 | } 257 | 258 | /// Create a new debugger 259 | pub fn attach_internal(pid: u32, attached: bool) -> Debugger<'a> { 260 | // Save the start time 261 | let start_time = Instant::now(); 262 | 263 | // Enable ability to debug system services 264 | crate::sedebug::sedebug(); 265 | 266 | // Register ctrl-c handler 267 | unsafe { 268 | assert!(SetConsoleCtrlHandler(Some(ctrl_c_handler), 1) != 0, 269 | "SetConsoleCtrlHandler() failed"); 270 | } 271 | 272 | unsafe { 273 | let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid); 274 | assert!(handle != std::ptr::null_mut(), "OpenProcess() failed"); 275 | 276 | // Wrap up the handle for drop tracking 277 | let handle = Handle::new(handle).expect("Failed to get handle"); 278 | 279 | let mut cur_is_wow64 = 0i32; 280 | let mut target_is_wow64 = 0i32; 281 | 282 | // Query if the target process is 32-bit 283 | assert!(IsWow64Process(handle.raw(), &mut target_is_wow64) != 0, 284 | "IsWow64Process() failed"); 285 | 286 | // Query if our process is 32-bit 287 | assert!(IsWow64Process(GetCurrentProcess(), 288 | &mut cur_is_wow64) != 0, "IsWow64Process() failed"); 289 | 290 | //print!("mesos is 64-bit: {}\n", cur_is_wow64 == 0); 291 | //print!("target is 64-bit: {}\n", target_is_wow64 == 0); 292 | 293 | // Validate target process is the same bitness as we are 294 | assert!(cur_is_wow64 == target_is_wow64, 295 | "Target process does not match mesos bitness"); 296 | 297 | if !attached { 298 | // Attach to the target! 299 | assert!(DebugActiveProcess(pid) != 0, 300 | "Failed to attach to process, is your PID valid \ 301 | and do you have correct permissions?"); 302 | } 303 | } 304 | 305 | // Correctly initialize a context so it's aligned. We overcommit 306 | // with `buf` to give room for the CONTEXT to slide for alignment 307 | let mut context_backing = 308 | vec![0u8; std::mem::size_of::() + 4096]; 309 | 310 | // 4-KiB align the context (Win7 compat) 311 | let tmp = context_backing.as_mut_ptr(); 312 | let tmp = ((tmp as usize) + 0xfff) & !0xfff; 313 | let cptr = tmp as *mut CONTEXT; 314 | let context = unsafe { &mut *cptr }; 315 | context.ContextFlags = CONTEXT_ALL; 316 | 317 | // Construct the new Debugger object :) 318 | Debugger { 319 | target_breakpoints: HashMap::new(), 320 | breakpoints: HashMap::new(), 321 | process_handle: None, 322 | thread_handles: HashMap::new(), 323 | coverage: HashMap::new(), 324 | minmax_breakpoint: HashMap::new(), 325 | modules: HashSet::new(), 326 | single_step: HashMap::new(), 327 | module_load_callbacks: Some(Vec::new()), 328 | debug_event_callbacks: Some(Vec::new()), 329 | always_freq: false, 330 | kill_requested: false, 331 | last_db_save: Instant::now(), 332 | verbose: false, 333 | bp_print: false, 334 | pid, start_time, 335 | 336 | context, 337 | _context_backing: context_backing, 338 | } 339 | } 340 | 341 | /// Get a list of all thread IDs currently active on this process 342 | pub fn get_thread_list(&self) -> Vec { 343 | self.thread_handles.keys().cloned().collect() 344 | } 345 | 346 | /// Register a function to be invoked on module loads 347 | pub fn register_modload_callback(&mut self, func: ModloadFunc) { 348 | self.module_load_callbacks.as_mut() 349 | .expect("Cannot add callback during callback").push(func); 350 | } 351 | 352 | /// Register a function to be invoked on debug events 353 | pub fn register_debug_event_callback(&mut self, func: DebugEventFunc) { 354 | self.debug_event_callbacks.as_mut() 355 | .expect("Cannot add callback during callback").push(func); 356 | } 357 | 358 | /// Registers a breakpoint for a specific file 359 | /// `module` is the name of the module we want to apply the breakpoint to, 360 | /// for example "notepad.exe", `offset` is the byte offset in this module 361 | /// to apply the breakpoint to 362 | /// 363 | /// `name` and `nameoff` are completely user controlled and are used to 364 | /// give this breakpoint a unique name. Often if used from mesos `name` 365 | /// will correspond to the function name and `nameoff` will be the offset 366 | /// into the function. However these can be whatever you like. It's only 367 | /// for readability of the coverage data 368 | pub fn register_breakpoint(&mut self, module: Arc, offset: usize, 369 | name: Arc, nameoff: usize, typ: BreakpointType, 370 | callback: Option) { 371 | // Create a new entry if none exists 372 | if !self.target_breakpoints.contains_key(&**module) { 373 | self.target_breakpoints.insert(module.to_string(), Vec::new()); 374 | } 375 | 376 | if !self.minmax_breakpoint.contains_key(&**module) { 377 | self.minmax_breakpoint.insert(module.to_string(), (!0, 0)); 378 | } 379 | 380 | let mmbp = self.minmax_breakpoint.get_mut(&**module).unwrap(); 381 | mmbp.0 = std::cmp::min(mmbp.0, offset as usize); 382 | mmbp.1 = std::cmp::max(mmbp.1, offset as usize); 383 | 384 | // Append this breakpoint 385 | self.target_breakpoints.get_mut(&**module).unwrap().push( 386 | Breakpoint { 387 | offset: offset as usize, 388 | enabled: false, 389 | typ: typ, 390 | orig_byte: None, 391 | funcname: name.clone(), 392 | funcoff: nameoff, 393 | modname: module.clone(), 394 | freq: 0, 395 | callback, 396 | } 397 | ); 398 | } 399 | 400 | /// Gets a raw `HANDLE` to the process we are attached to 401 | fn process_handle(&self) -> HANDLE { 402 | self.process_handle.expect("No process handle present") 403 | } 404 | 405 | pub fn set_always_freq(&mut self, val: bool) { self.always_freq = val; } 406 | pub fn set_verbose(&mut self, val: bool) { self.verbose = val; } 407 | pub fn set_bp_print(&mut self, val: bool) { self.bp_print = val; } 408 | 409 | /// Resolves the file name of a given memory mapped file in the target 410 | /// process 411 | fn filename_from_module_base(&self, base: usize) -> String { 412 | // Use GetMappedFileNameW() to get the mapped file name 413 | let mut buf = [0u16; 4096]; 414 | let fnlen = unsafe { 415 | GetMappedFileNameW(self.process_handle(), 416 | base as *mut _, buf.as_mut_ptr(), buf.len() as u32) 417 | }; 418 | assert!(fnlen != 0 && (fnlen as usize) < buf.len(), 419 | "GetMappedFileNameW() failed"); 420 | 421 | // Convert the name to utf-8 and lowercase it 422 | let path = String::from_utf16(&buf[..fnlen as usize]).unwrap() 423 | .to_lowercase(); 424 | 425 | // Get the filename from the path 426 | Path::new(&path).file_name().unwrap().to_str().unwrap().into() 427 | } 428 | 429 | /// Reads from `addr` in the process we're debugging into `buf` 430 | /// Returns number of bytes read 431 | pub fn read_mem(&self, addr: usize, buf: &mut [u8]) -> usize { 432 | let mut offset = 0; 433 | 434 | // Read until complete 435 | while offset < buf.len() { 436 | let mut bread = 0; 437 | 438 | unsafe { 439 | // Issue a read 440 | if ReadProcessMemory( 441 | self.process_handle(), (addr + offset) as *mut _, 442 | buf.as_mut_ptr().offset(offset as isize) as *mut _, 443 | buf.len() - offset, &mut bread) == 0 { 444 | // Return out on error 445 | return offset; 446 | } 447 | assert!(bread > 0); 448 | } 449 | 450 | offset += bread; 451 | } 452 | 453 | offset 454 | } 455 | 456 | /// Writes `buf` to `addr` in the process we're debugging 457 | /// Returns number of bytes written 458 | pub fn write_mem(&self, addr: usize, buf: &[u8]) -> usize { 459 | let mut offset = 0; 460 | 461 | // Write until complete 462 | while offset < buf.len() { 463 | let mut bread = 0; 464 | 465 | unsafe { 466 | // Issue a write 467 | if WriteProcessMemory( 468 | self.process_handle(), (addr + offset) as *mut _, 469 | buf.as_ptr().offset(offset as isize) as *const _, 470 | buf.len() - offset, &mut bread) == 0 { 471 | // Return out on error 472 | return offset; 473 | } 474 | assert!(bread > 0); 475 | } 476 | 477 | offset += bread; 478 | } 479 | 480 | offset 481 | } 482 | 483 | /// Flush all instruction caches in the target process 484 | fn flush_instruction_caches(&self) { 485 | unsafe { 486 | // Flush all instruction caches for the process 487 | assert!(FlushInstructionCache( 488 | self.process_handle(), std::ptr::null(), 0) != 0); 489 | } 490 | } 491 | 492 | /// Add the module loaded at `base` in the target process to our module 493 | /// list 494 | fn register_module(&mut self, base: usize) { 495 | let filename = self.filename_from_module_base(base); 496 | 497 | // Insert into the module list 498 | self.modules.insert((filename.into(), base)); 499 | } 500 | 501 | /// Remove the module loaded at `base` in the target process from our 502 | /// module list 503 | fn unregister_module(&mut self, base: usize) { 504 | let mut to_remove = None; 505 | 506 | // Find the corresponding module to this base 507 | for module in self.modules.iter() { 508 | if module.1 == base { 509 | to_remove = Some(module.clone()); 510 | } 511 | } 512 | 513 | if let Some(to_remove) = to_remove { 514 | if self.minmax_breakpoint.contains_key(&to_remove.0) { 515 | // If there are breakpoints in this module, unregister those too 516 | 517 | // Get minimum and maximum offsets into the module where 518 | // breakpoints are applied 519 | let minmax = self.minmax_breakpoint[&to_remove.0]; 520 | 521 | let start_addr = base + minmax.0; 522 | let end_addr = base + minmax.1; 523 | 524 | // Remove any breakpoints which are present in this range 525 | self.breakpoints.retain(|&k, _| { 526 | k < start_addr || k > end_addr 527 | }); 528 | } 529 | 530 | // Remove the module and breakpoint info for the module 531 | self.modules.remove(&to_remove); 532 | } else { 533 | // Got unregister module for unknown DLL 534 | // Our database is out of sync with reality 535 | panic!("Unexpected DLL unload of base 0x{:x}\n", base); 536 | } 537 | } 538 | 539 | /// Given a `base` of a module in the target process, identify the module 540 | /// and attempt to apply breakpoints to it if we have any scheduled for 541 | /// this module 542 | fn apply_breakpoints(&mut self, base: usize) { 543 | let filename = self.filename_from_module_base(base); 544 | 545 | // Bail if we don't have requested breakpoints for this module 546 | if !self.minmax_breakpoint.contains_key(&filename) { 547 | return; 548 | } 549 | 550 | // Save number of breakpoints at this function start 551 | let startbps = self.breakpoints.len(); 552 | 553 | let mut minmax = self.minmax_breakpoint[&filename]; 554 | 555 | // Convert this to a non-inclusive upper bound 556 | minmax.1 = minmax.1.checked_add(1).unwrap(); 557 | 558 | // Compute the size of all memory between the minimum and maximum 559 | // breakpoint offsets 560 | let region_size = minmax.1.checked_sub(minmax.0).unwrap() as usize; 561 | 562 | let mut contents = vec![0u8; region_size]; 563 | 564 | // Read all memory of this DLL that includes breakpoints 565 | // On partial reads that's fine, we'll only do a partial write later. 566 | let bread = self.read_mem(base + minmax.0, &mut contents); 567 | 568 | // Attempt to apply all requested breakpoints 569 | for breakpoint in self.target_breakpoints.get(&filename).unwrap() { 570 | let mut bp = breakpoint.clone(); 571 | bp.enabled = true; 572 | 573 | let bufoff = 574 | (breakpoint.offset as usize) 575 | .checked_sub(minmax.0 as usize).unwrap(); 576 | 577 | // Save the original byte 578 | bp.orig_byte = Some(contents[bufoff]); 579 | 580 | // Add in a breakpoint 581 | contents[bufoff] = 0xcc; 582 | 583 | if !self.breakpoints.contains_key(&(base + breakpoint.offset)) { 584 | self.breakpoints.insert(base + breakpoint.offset, bp); 585 | } else { 586 | // Silently ignore duplicate breakpoints 587 | } 588 | } 589 | 590 | // Write in all the breakpoints 591 | // If it partially writes then that's fine, we just apply the 592 | // breakpoints we can 593 | let _ = self.write_mem(base + minmax.0, &contents[..bread]); 594 | self.flush_instruction_caches(); 595 | 596 | mprint!(self, "Applied {:10} breakpoints ({:10} total breakpoints) {}\n", 597 | self.breakpoints.len() - startbps, 598 | self.breakpoints.len(), filename); 599 | 600 | return; 601 | } 602 | 603 | /// Remove all breakpoints, restoring the process to a clean state 604 | fn remove_breakpoints(&mut self) { 605 | for (module, base) in self.modules.iter() { 606 | if !self.minmax_breakpoint.contains_key(module) { 607 | // Ignore modules we have no applied breakpoints for 608 | continue; 609 | } 610 | 611 | // Get minimum and maximum offsets into the module where 612 | // breakpoints are applied 613 | let minmax = self.minmax_breakpoint[module]; 614 | 615 | // Compute the size of all memory between the minimum and maximum 616 | // breakpoint offsets 617 | let region_size = minmax.1.checked_add(1).unwrap() 618 | .checked_sub(minmax.0).unwrap() as usize; 619 | 620 | let mut contents = vec![0u8; region_size]; 621 | 622 | // Read all memory of this DLL that includes breakpoints 623 | // On partial reads that's fine, we'll only do a partial write 624 | // later. 625 | let bread = self.read_mem(base + minmax.0, &mut contents); 626 | 627 | let mut removed_bps = 0u64; 628 | 629 | // Restore all bytes that we applied breakpoints to 630 | for (_, bp) in self.breakpoints.iter_mut() { 631 | // Skip breakpoints not in this module 632 | if &*bp.modname != module { 633 | continue; 634 | } 635 | 636 | let bufoff = 637 | (bp.offset as usize) 638 | .checked_sub(minmax.0 as usize).unwrap(); 639 | 640 | // Restore original byte 641 | if let Some(byte) = bp.orig_byte { 642 | contents[bufoff] = byte; 643 | bp.enabled = false; 644 | removed_bps += 1; 645 | } 646 | } 647 | 648 | // Remove all the breakpoints 649 | // On partial removes it's fine. If we can't write to the memory 650 | // it's not mapped in, so we can just ignore partial writes here. 651 | let _ = self.write_mem(base + minmax.0, &contents[..bread]); 652 | self.flush_instruction_caches(); 653 | 654 | mprint!(self, "Removed {} breakpoints in {}\n", removed_bps, module); 655 | } 656 | 657 | // Sanity check that all breakpoints have been removed 658 | for (_, bp) in self.breakpoints.iter() { 659 | assert!(!bp.enabled, 660 | "Unexpected breakpoint left enabled \ 661 | after remove_breakpoints()") 662 | } 663 | } 664 | 665 | /// Handle a breakpoint 666 | fn handle_breakpoint(&mut self, tid: u32, addr: usize) -> bool { 667 | if let Some(ref mut bp) = self.breakpoints.get_mut(&addr).cloned() { 668 | // If we apply a breakpoint over an actual breakpoint just exit 669 | // out now because we don't want to handle it 670 | if bp.orig_byte == Some(0xcc) { 671 | return true; 672 | } 673 | 674 | // Update breakpoint hit frequency 675 | self.breakpoints.get_mut(&addr).unwrap().freq += 1; 676 | 677 | let mut orig_byte = [0u8; 1]; 678 | orig_byte[0] = bp.orig_byte.unwrap(); 679 | 680 | // Restore original byte 681 | assert!(self.write_mem(addr, &orig_byte) == 1); 682 | self.flush_instruction_caches(); 683 | 684 | // Create a new coverage record if one does not exist 685 | if !self.coverage.contains_key(&addr) { 686 | let funcoff = format!("{}+0x{:x}", bp.funcname, bp.funcoff); 687 | 688 | self.coverage.insert(addr, 689 | (bp.modname.clone(), bp.offset, funcoff.clone(), 0)); 690 | } 691 | 692 | // Update coverage frequencies 693 | let freq = { 694 | let bin = self.coverage.get_mut(&addr).unwrap(); 695 | bin.3 += 1; 696 | bin.3 697 | }; 698 | 699 | // Print coverage as we get it 700 | if self.bp_print { 701 | let funcoff = format!("{}+0x{:x}", bp.funcname, bp.funcoff); 702 | mprint!(self, "{:8} of {:8} hit | {:10} freq | 0x{:x} | \ 703 | {:>20}+0x{:08x} | {}\n", 704 | self.coverage.len(), self.breakpoints.len(), 705 | freq, 706 | addr, bp.modname, bp.offset, funcoff); 707 | } 708 | 709 | self.get_context(tid); 710 | 711 | // Back up so we re-execute where the breakpoint was 712 | 713 | #[cfg(target_pointer_width = "64")] 714 | { self.context.Rip = addr as u64; } 715 | 716 | #[cfg(target_pointer_width = "32")] 717 | { self.context.Eip = addr as u32; } 718 | 719 | // Single step if this is a frequency instruction 720 | if self.always_freq || bp.typ == BreakpointType::Freq { 721 | // Set the trap flag 722 | self.context.EFlags |= 1 << 8; 723 | self.single_step.insert(tid, addr); 724 | } else { 725 | // Breakpoint no longer enabled 726 | self.breakpoints.get_mut(&addr).unwrap().enabled = false; 727 | } 728 | 729 | self.set_context(tid); 730 | 731 | // Call the breakpoint callback if needed 732 | if let Some(callback) = bp.callback { 733 | if callback(self, tid, addr, bp.freq) == false { 734 | self.kill_requested = true; 735 | } 736 | } 737 | } else { 738 | // Hit unexpected breakpoint 739 | return false; 740 | } 741 | 742 | true 743 | } 744 | 745 | /// Gets a mutable reference to the internal context structure 746 | pub fn context(&mut self) -> &mut CONTEXT { 747 | &mut self.context 748 | } 749 | 750 | /// Loads `tid`'s context into the internal context structure 751 | pub fn get_context(&mut self, tid: u32) { 752 | unsafe { 753 | assert!(GetThreadContext( 754 | self.thread_handles[&tid], self.context) != 0); 755 | } 756 | } 757 | 758 | /// Sets `tid`'s context from the internal context structure 759 | pub fn set_context(&mut self, tid: u32) { 760 | unsafe { 761 | assert!(SetThreadContext( 762 | self.thread_handles[&tid], self.context) != 0); 763 | } 764 | } 765 | 766 | /// Get a filename to describe a given crash 767 | fn get_crash_filename(&self, context: &CONTEXT, 768 | exception: &EXCEPTION_RECORD) -> String { 769 | let pc = { 770 | #[cfg(target_pointer_width = "64")] 771 | { context.Rip as usize } 772 | 773 | #[cfg(target_pointer_width = "32")] 774 | { context.Eip as usize } 775 | }; 776 | 777 | // Search for the nearest module 778 | let mut nearest_module: Option<(&str, usize)> = None; 779 | for (module, base) in self.modules.iter() { 780 | if let Some(offset) = pc.checked_sub(*base) { 781 | if nearest_module.is_none() || 782 | nearest_module.unwrap().1 > offset { 783 | nearest_module = Some((module, offset)); 784 | } 785 | } 786 | } 787 | 788 | let code = exception.ExceptionCode; 789 | 790 | // Filename starts with exception code 791 | let mut filename = format!("crash_{:08x}_", code); 792 | 793 | // Filename then contains the module+offset, or if no suitable module 794 | // is detected then it just contains the absolute PC address 795 | if let Some((module, offset)) = nearest_module { 796 | filename += &format!("{}+0x{:x}", module, offset); 797 | } else { 798 | filename += &format!("0x{:x}", pc); 799 | } 800 | 801 | // If the crash is an access violation we also have the type of fault 802 | // (read or write) and whether it's a null deref, non-canon, or 803 | // other 804 | if code == 0xc0000005 { 805 | // This should never happen 806 | assert!(exception.NumberParameters == 2, 807 | "Invalid c0000005 parameters"); 808 | 809 | // Classify the type of exception 810 | if exception.ExceptionInformation[0] == 0 { 811 | filename += "_read"; 812 | } else if exception.ExceptionInformation[0] == 1 { 813 | filename += "_WRITE"; 814 | } else if exception.ExceptionInformation[0] == 8 { 815 | // DEP violation 816 | filename += "_DEP"; 817 | } 818 | 819 | let fault_addr = exception.ExceptionInformation[1] as u64; 820 | 821 | let noncanon_bits = fault_addr & 0xffff_0000_0000_0000; 822 | 823 | if noncanon_bits != 0 && noncanon_bits != 0xffff_0000_0000_0000 { 824 | // Address is non-canon, can only happen on 64-bits and is 825 | // typically a _really_ bad sign (fully controlled address) 826 | filename += "_NONCANON"; 827 | } else if (fault_addr as i64).abs() < 32 * 1024 { 828 | // Near-null, thus we consider it to be a null deref 829 | filename += "_null"; 830 | } else { 831 | // Address is canon, but also not null, seems bad 832 | filename += "_HIGH"; 833 | } 834 | } 835 | 836 | // All files are .dmp 837 | filename += ".dmp"; 838 | 839 | filename 840 | } 841 | 842 | /// Sync the coverage database to disk 843 | fn flush_coverage_database(&mut self) { 844 | mprint!(self, "Syncing code coverage database...\n"); 845 | 846 | let mut fd = BufWriter::with_capacity( 847 | 2 * 1024 * 1024, 848 | File::create("coverage.txt") 849 | .expect("Failed to open freq coverage file")); 850 | 851 | for (pc, (module, offset, symoff, freq)) in self.coverage.iter() { 852 | write!(fd, 853 | "{:016x} | Freq: {:10} | \ 854 | {:>20}+0x{:08x} | {}\n", 855 | pc, freq, module, offset, symoff) 856 | .expect("Failed to write coverage info"); 857 | } 858 | 859 | mprint!(self, "Sync complete ({} total unique coverage entries)\n", 860 | self.coverage.len()); 861 | } 862 | 863 | /// Kill the process via `TerminateProcess()` 864 | pub fn kill(&mut self) -> io::Result<()> { 865 | unsafe { 866 | if TerminateProcess(self.process_handle.unwrap(), 0) == 0 { 867 | Err(io::Error::last_os_error()) 868 | } else { 869 | Ok(()) 870 | } 871 | } 872 | } 873 | 874 | /// Run the process forever 875 | pub fn run(&mut self) -> ExitType { 876 | let mut event = unsafe { std::mem::zeroed() }; 877 | 878 | let mut hit_initial_break = false; 879 | 880 | unsafe { loop { 881 | // Flush the coverage database on an intervals 882 | if Instant::now().duration_since(self.last_db_save) >= 883 | Duration::from_secs(15) { 884 | self.flush_coverage_database(); 885 | self.last_db_save = Instant::now(); 886 | self.kill_requested = true; 887 | } 888 | 889 | if self.kill_requested { 890 | assert!( 891 | TerminateProcess(self.process_handle.unwrap(), 123456) != 0, 892 | "Failed to kill target process"); 893 | self.kill_requested = false; 894 | } 895 | 896 | // Check if it's requested that we exit 897 | if EXIT_REQUESTED.load(Ordering::SeqCst) { 898 | // Exit out of the run loop 899 | return ExitType::ExitCode(0); 900 | } 901 | 902 | // Wait for a debug event :) 903 | let der = WaitForDebugEvent(&mut event, 10); 904 | if der == 0 { 905 | if GetLastError() == ERROR_SEM_TIMEOUT { 906 | // Just drop timeouts 907 | continue; 908 | } 909 | 910 | panic!("WaitForDebugEvent() returned error : {}", 911 | GetLastError()); 912 | } 913 | 914 | let decs = self.debug_event_callbacks.take() 915 | .expect("Event without callbacks present"); 916 | // Invoke callbacks 917 | for de in decs.iter() { 918 | de(self, &event); 919 | } 920 | self.debug_event_callbacks = Some(decs); 921 | 922 | // Get the PID and TID for the event 923 | let pid = event.dwProcessId; 924 | let tid = event.dwThreadId; 925 | 926 | match event.dwDebugEventCode { 927 | CREATE_PROCESS_DEBUG_EVENT => { 928 | // A new process was created under our debugger 929 | let create_process = event.u.CreateProcessInfo(); 930 | 931 | // Wrap up the hFile handle. We don't use it but this will 932 | // cause it to get dropped automatically for us 933 | let _ = Handle::new(create_process.hFile); 934 | 935 | // Make sure the hProcess and hThread are valid 936 | assert!( 937 | create_process.hProcess != std::ptr::null_mut() && 938 | create_process.hThread != std::ptr::null_mut(), 939 | "Passed null hProcess or hThread on create process \ 940 | event"); 941 | 942 | // Register this process and thread handles. Note we don't 943 | // wrap these in a `Handle`, that's because they are not 944 | // supposed to be closed by us. 945 | self.process_handle = Some(create_process.hProcess); 946 | self.thread_handles.insert(tid, create_process.hThread); 947 | 948 | let base = create_process.lpBaseOfImage as usize; 949 | 950 | // Register this module in our module list 951 | self.register_module(base); 952 | 953 | let mlcs = self.module_load_callbacks.take() 954 | .expect("Event without callbacks present"); 955 | 956 | // Invoke callbacks 957 | for mlc in mlcs.iter() { 958 | mlc(self, &self.filename_from_module_base(base), base); 959 | } 960 | 961 | self.module_load_callbacks = Some(mlcs); 962 | 963 | // Apply any pending breakpoints! 964 | self.apply_breakpoints(base); 965 | } 966 | CREATE_THREAD_DEBUG_EVENT => { 967 | // A thread was created in the target 968 | let create_thread = event.u.CreateThread(); 969 | 970 | // Insert this thread handle into our list of threads 971 | // We don't wrap this HANDLE in a `Handle` as we're not 972 | // supposed to call CloseHandle() on it according to the 973 | // API 974 | self.thread_handles.insert(tid, create_thread.hThread); 975 | } 976 | EXCEPTION_DEBUG_EVENT => { 977 | // An exception occurred in the target 978 | let exception = event.u.Exception_mut(); 979 | 980 | if exception.ExceptionRecord.ExceptionCode == 0x80000003 { 981 | // Exception was a breakpoint 982 | 983 | if !hit_initial_break { 984 | // If we're expecting an initial breakpoint, just 985 | // handle this exception 986 | hit_initial_break = true; 987 | assert!(ContinueDebugEvent(pid, tid, 988 | DBG_CONTINUE) != 0); 989 | continue; 990 | } 991 | 992 | // Attempt to handle the breakpoint based on our own 993 | // breakpoints we have applied to the target 994 | if !self.handle_breakpoint(tid, 995 | exception.ExceptionRecord 996 | .ExceptionAddress as usize) { 997 | mprint!(self, 998 | "Warning: Continuing unexpected 0x80000003\n"); 999 | } 1000 | } else { 1001 | if exception.ExceptionRecord.ExceptionCode == 1002 | 0xc0000005 { 1003 | // Target had an access violation 1004 | 1005 | self.get_context(tid); 1006 | 1007 | // Compute a filename for this crash 1008 | let filename = self.get_crash_filename( 1009 | &self.context, &mut exception.ExceptionRecord); 1010 | 1011 | mprint!(self, "Got crash: {}\n", filename); 1012 | 1013 | if !Path::new(&filename).is_file() { 1014 | // Remove all breakpoints in the program 1015 | // before minidumping 1016 | self.remove_breakpoints(); 1017 | 1018 | // Take a full minidump of the process 1019 | dump(&filename, pid, tid, 1020 | self.process_handle.unwrap(), 1021 | &mut exception.ExceptionRecord, 1022 | &mut self.context); 1023 | } 1024 | 1025 | // Exit out 1026 | return ExitType::Crash(filename); 1027 | } else if exception.ExceptionRecord 1028 | .ExceptionCode == 0x80000004 { 1029 | // Single step exception 1030 | 1031 | // Check if we're expecting a single step on this 1032 | // thread. 1033 | if let Some(&pc) = self.single_step.get(&tid) { 1034 | // Disable trap flag 1035 | self.get_context(tid); 1036 | self.context.EFlags &= !(1 << 8); 1037 | self.set_context(tid); 1038 | 1039 | // Write breakpoint back in 1040 | assert!(self.write_mem(pc, b"\xcc") == 1); 1041 | self.flush_instruction_caches(); 1042 | 1043 | // Remove that we're single stepping this TID 1044 | self.single_step.remove(&tid); 1045 | } else { 1046 | // Uh oh, we didn't expect that 1047 | mprint!(self, "Unexpected single step, \ 1048 | continuing\n"); 1049 | } 1050 | 1051 | assert!(ContinueDebugEvent( 1052 | pid, tid, DBG_CONTINUE) != 0); 1053 | continue; 1054 | } else { 1055 | // Unhandled exception, pass it through as unhandled 1056 | mprint!(self, "Unhandled exception {:x}\n", 1057 | exception.ExceptionRecord.ExceptionCode); 1058 | 1059 | assert!(ContinueDebugEvent( 1060 | pid, tid, DBG_EXCEPTION_NOT_HANDLED) != 0); 1061 | continue; 1062 | } 1063 | } 1064 | } 1065 | LOAD_DLL_DEBUG_EVENT => { 1066 | // A module was loaded in the target 1067 | let load_dll = event.u.LoadDll(); 1068 | 1069 | // Wrap up the handle so it gets dropped 1070 | let _ = Handle::new(load_dll.hFile); 1071 | 1072 | let base = load_dll.lpBaseOfDll as usize; 1073 | 1074 | // Register the module, attempt to load mesos, and apply 1075 | // all pending breakpoints 1076 | self.register_module(base); 1077 | 1078 | let mlcs = self.module_load_callbacks.take() 1079 | .expect("Event without callbacks present"); 1080 | 1081 | // Invoke callbacks 1082 | for mlc in mlcs.iter() { 1083 | mlc(self, &self.filename_from_module_base(base), base); 1084 | } 1085 | 1086 | self.module_load_callbacks = Some(mlcs); 1087 | 1088 | self.apply_breakpoints(base); 1089 | } 1090 | EXIT_THREAD_DEBUG_EVENT => { 1091 | // Remove the thread handle for this thread 1092 | assert!(self.thread_handles.remove(&tid).is_some(), 1093 | "Got exit thread event for nonexistant thread"); 1094 | } 1095 | EXIT_PROCESS_DEBUG_EVENT => { 1096 | // Target exited 1097 | mprint!(self, "Process exited, qutting!\n"); 1098 | return ExitType::ExitCode(0); 1099 | } 1100 | UNLOAD_DLL_DEBUG_EVENT => { 1101 | // Dll was unloaded in the target, unload it 1102 | let unload_dll = event.u.UnloadDll(); 1103 | self.unregister_module(unload_dll.lpBaseOfDll as usize); 1104 | } 1105 | OUTPUT_DEBUG_STRING_EVENT => { 1106 | // Target attempted to print a debug string, just ignore 1107 | // it 1108 | } 1109 | RIP_EVENT => { 1110 | } 1111 | _ => panic!("Unsupported event"), 1112 | } 1113 | 1114 | assert!(ContinueDebugEvent( 1115 | pid, tid, DBG_CONTINUE) != 0); 1116 | }} 1117 | } 1118 | } 1119 | 1120 | impl<'a> Drop for Debugger<'a> { 1121 | fn drop(&mut self) { 1122 | // Remove all breakpoints 1123 | self.remove_breakpoints(); 1124 | 1125 | // Flush coverage database one last time 1126 | self.flush_coverage_database(); 1127 | 1128 | // Detach from the process 1129 | unsafe { 1130 | assert!(DebugActiveProcessStop(self.pid) != 0, 1131 | "DebugActiveProcessStop() failed"); 1132 | } 1133 | 1134 | // All done, process is safely restored 1135 | mprint!(self, "Detached from process {}\n", self.pid); 1136 | } 1137 | } 1138 | -------------------------------------------------------------------------------- /mesos/libs/debugger/src/ffi_helpers.rs: -------------------------------------------------------------------------------- 1 | /// Misc functions that help during various FFI activities 2 | 3 | use std::ffi::OsStr; 4 | use std::os::windows::ffi::OsStrExt; 5 | use std::iter::once; 6 | 7 | /// Convert rust string to null-terminated UTF-16 Windows API string 8 | pub fn win32_string(value: &str) -> Vec { 9 | OsStr::new(value).encode_wide().chain(once(0)).collect() 10 | } 11 | -------------------------------------------------------------------------------- /mesos/libs/debugger/src/handles.rs: -------------------------------------------------------------------------------- 1 | /// Crate to provide a Drop wrapper for HANDLEs 2 | 3 | use winapi::um::winnt::HANDLE; 4 | use winapi::um::handleapi::CloseHandle; 5 | 6 | /// Wrapper on a HANDLE to provide Drop support to clean up handles 7 | pub struct Handle(HANDLE); 8 | 9 | impl Handle { 10 | /// Wrap up a HANDLE 11 | pub fn new(handle: HANDLE) -> Option { 12 | // Return None if the handle is null 13 | if handle == std::ptr::null_mut() { return None; } 14 | 15 | Some(Handle(handle)) 16 | } 17 | 18 | /// Gets the raw HANDLE value this `Handle` represents 19 | pub fn raw(&self) -> HANDLE { 20 | self.0 21 | } 22 | } 23 | 24 | impl Drop for Handle { 25 | fn drop(&mut self) { 26 | unsafe { 27 | // Close that handle! 28 | assert!(CloseHandle(self.0) != 0, "Failed to drop HANDLE"); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mesos/libs/debugger/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod debugger; 2 | mod minidump; 3 | mod sedebug; 4 | mod ffi_helpers; 5 | mod handles; 6 | 7 | // Make some things public 8 | pub use debugger::{Debugger, ExitType, BreakpointType}; 9 | -------------------------------------------------------------------------------- /mesos/libs/debugger/src/minidump.rs: -------------------------------------------------------------------------------- 1 | /// Module containing utilities to create full minidumps of processes 2 | 3 | use winapi::um::winnt::EXCEPTION_POINTERS; 4 | use winapi::um::winnt::GENERIC_READ; 5 | use winapi::um::winnt::GENERIC_WRITE; 6 | use winapi::um::winnt::EXCEPTION_RECORD; 7 | use winapi::um::fileapi::CREATE_NEW; 8 | use winapi::um::winnt::HANDLE; 9 | use winapi::um::fileapi::CreateFileW; 10 | use winapi::um::handleapi::INVALID_HANDLE_VALUE; 11 | use winapi::um::errhandlingapi::GetLastError; 12 | use winapi::um::winnt::CONTEXT; 13 | 14 | use std::path::Path; 15 | use crate::handles::Handle; 16 | use crate::ffi_helpers::win32_string; 17 | 18 | #[repr(C)] 19 | #[allow(dead_code)] 20 | pub enum MinidumpType { 21 | MiniDumpNormal = 0x00000000, 22 | MiniDumpWithDataSegs = 0x00000001, 23 | MiniDumpWithFullMemory = 0x00000002, 24 | MiniDumpWithHandleData = 0x00000004, 25 | MiniDumpFilterMemory = 0x00000008, 26 | MiniDumpScanMemory = 0x00000010, 27 | MiniDumpWithUnloadedModules = 0x00000020, 28 | MiniDumpWithIndirectlyReferencedMemory = 0x00000040, 29 | MiniDumpFilterModulePaths = 0x00000080, 30 | MiniDumpWithProcessThreadData = 0x00000100, 31 | MiniDumpWithPrivateReadWriteMemory = 0x00000200, 32 | MiniDumpWithoutOptionalData = 0x00000400, 33 | MiniDumpWithFullMemoryInfo = 0x00000800, 34 | MiniDumpWithThreadInfo = 0x00001000, 35 | MiniDumpWithCodeSegs = 0x00002000, 36 | MiniDumpWithoutAuxiliaryState = 0x00004000, 37 | MiniDumpWithFullAuxiliaryState = 0x00008000, 38 | MiniDumpWithPrivateWriteCopyMemory = 0x00010000, 39 | MiniDumpIgnoreInaccessibleMemory = 0x00020000, 40 | MiniDumpWithTokenInformation = 0x00040000, 41 | MiniDumpWithModuleHeaders = 0x00080000, 42 | MiniDumpFilterTriage = 0x00100000, 43 | MiniDumpWithAvxXStateContext = 0x00200000, 44 | MiniDumpWithIptTrace = 0x00400000, 45 | MiniDumpValidTypeFlags = 0x007fffff, 46 | } 47 | 48 | #[link(name = "dbghelp")] 49 | extern "system" { 50 | pub fn MiniDumpWriteDump(hProcess: HANDLE, processId: u32, 51 | hFile: HANDLE, DumpType: u32, 52 | exception: *const MinidumpExceptionInformation, 53 | userstreamparam: usize, 54 | callbackParam: usize) -> i32; 55 | } 56 | 57 | #[repr(C, packed)] 58 | #[derive(Clone, Copy, Debug)] 59 | pub struct MinidumpExceptionInformation { 60 | thread_id: u32, 61 | exception: *const EXCEPTION_POINTERS, 62 | client_pointers: u32, 63 | } 64 | 65 | /// Create a full minidump of a given process 66 | pub fn dump(filename: &str, pid: u32, tid: u32, process: HANDLE, 67 | exception: &mut EXCEPTION_RECORD, context: &mut CONTEXT) { 68 | // Don't overwrite existing dumps 69 | if Path::new(filename).is_file() { 70 | print!("Ignoring duplicate crash {}\n", filename); 71 | return; 72 | } 73 | 74 | unsafe { 75 | let filename = win32_string(filename); 76 | 77 | let ep = EXCEPTION_POINTERS { 78 | ExceptionRecord: exception, 79 | ContextRecord: context, 80 | }; 81 | 82 | // Create the minidump file 83 | let fd = CreateFileW(filename.as_ptr(), 84 | GENERIC_READ | GENERIC_WRITE, 0, 85 | std::ptr::null_mut(), CREATE_NEW, 0, std::ptr::null_mut()); 86 | assert!(fd != INVALID_HANDLE_VALUE, "Failed to create dump file"); 87 | 88 | // Wrap up the HANDLE for drop tracking 89 | let fd = Handle::new(fd).expect("Failed to get handle to minidump"); 90 | 91 | let mei = MinidumpExceptionInformation { 92 | thread_id: tid, 93 | exception: &ep, 94 | client_pointers: 0, 95 | }; 96 | 97 | // Take a minidump! 98 | let res = MiniDumpWriteDump(process, pid, fd.raw(), 99 | MinidumpType::MiniDumpWithFullMemory as u32 | 100 | MinidumpType::MiniDumpWithHandleData as u32, 101 | &mei, 0, 0); 102 | assert!(res != 0, "MiniDumpWriteDump error: {}\n", GetLastError()); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /mesos/libs/debugger/src/sedebug.rs: -------------------------------------------------------------------------------- 1 | use winapi::um::winnt::HANDLE; 2 | use winapi::um::winnt::TOKEN_PRIVILEGES; 3 | use winapi::um::winnt::TOKEN_ADJUST_PRIVILEGES; 4 | use winapi::um::winnt::TOKEN_QUERY; 5 | use winapi::um::winnt::SE_PRIVILEGE_ENABLED; 6 | use winapi::um::processthreadsapi::OpenProcessToken; 7 | use winapi::um::processthreadsapi::GetCurrentProcess; 8 | use winapi::um::securitybaseapi::AdjustTokenPrivileges; 9 | use winapi::um::winbase::LookupPrivilegeValueW; 10 | use crate::ffi_helpers::win32_string; 11 | use crate::handles::Handle; 12 | 13 | /// Enable SeDebugPrivilege so we can debug system services 14 | pub fn sedebug() { 15 | unsafe { 16 | let mut token: HANDLE = std::ptr::null_mut(); 17 | let mut tkp: TOKEN_PRIVILEGES = std::mem::zeroed(); 18 | 19 | // Get the token for the current process 20 | assert!(OpenProcessToken(GetCurrentProcess(), 21 | TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &mut token) != 0); 22 | 23 | // Wrap up the handle so it'll get Dropped correctly 24 | let token = Handle::new(token) 25 | .expect("Failed to get valid handle for token"); 26 | 27 | // Lookup SeDebugPrivilege 28 | let privname = win32_string("SeDebugPrivilege"); 29 | assert!(LookupPrivilegeValueW(std::ptr::null(), 30 | privname.as_ptr(), &mut tkp.Privileges[0].Luid) != 0); 31 | 32 | tkp.PrivilegeCount = 1; 33 | tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 34 | 35 | // Set the privilege 36 | assert!(AdjustTokenPrivileges(token.raw(), 0, &mut tkp, 0, 37 | std::ptr::null_mut(), std::ptr::null_mut()) != 0); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mesos/mesogen_scripts/ghidra.py: -------------------------------------------------------------------------------- 1 | #Generates a Mesos file from the current program. 2 | #@author marpie (Markus Piéton - marpie@a12d404.net) 3 | #@category Mesos 4 | #@keybinding 5 | #@menupath 6 | #@toolbar 7 | 8 | import struct 9 | import ghidra.program.model.block.SimpleBlockModel as SimpleBlockModel 10 | 11 | def get_simple_blocks_by_function(image_base, listing): 12 | model = SimpleBlockModel(currentProgram) 13 | 14 | entries = {} 15 | block_iter = model.getCodeBlocks(monitor) 16 | while block_iter.hasNext() and (not monitor.isCancelled()): 17 | block = block_iter.next() 18 | for block_addr in block.getStartAddresses(): 19 | if monitor.isCancelled(): 20 | break 21 | block_offset = block_addr.getOffset() - image_base 22 | 23 | func_name = block.getName() 24 | func_offset = 0 25 | func_offset_rel = 0 26 | func_of_block = listing.getFunctionContaining(block_addr) 27 | if func_of_block: 28 | func_name = func_of_block.getName() 29 | func_offset = func_of_block.getEntryPoint().getOffset() 30 | func_offset_rel = func_offset - image_base 31 | block_offset = block_addr.getOffset() - func_offset 32 | 33 | try: 34 | entries["{}_{}".format(func_offset_rel,func_name)][2].append(block_offset) 35 | except KeyError: 36 | entries["{}_{}".format(func_offset_rel,func_name)] = [func_offset_rel, func_name, [block_offset]] 37 | 38 | return entries 39 | 40 | ghidra_file = askFile("Please select the Mesos Output-File", "Save To File") 41 | 42 | with open(ghidra_file.getAbsolutePath(), "wb") as fd: 43 | input_name = currentProgram.getName() 44 | image_base = currentProgram.getImageBase().getOffset() 45 | 46 | listing = currentProgram.getListing() 47 | 48 | # Write record type 0 (module) 49 | # unsigned 16-bit module name 50 | # And module name 51 | fd.write(struct.pack("= 4 and ARGV[1] == "cmdline": 15 | input_name = ARGV[3] 16 | 17 | filename = "%s/%s.meso" % (os.path.dirname(os.path.abspath(__file__)), input_name) 18 | if len(ARGV) >= 4 and ARGV[1] == "cmdline": 19 | filename = ARGV[2] 20 | filename += ".tmp" 21 | 22 | with open(filename, "wb") as fd: 23 | # Write record type 0 (module) 24 | # unsigned 16-bit module name 25 | # And module name 26 | fd.write(struct.pack("= 4 and ARGV[1] == "cmdline": 55 | idc.Exit(0) 56 | -------------------------------------------------------------------------------- /mesos/mesogen_scripts/ida_detect.py: -------------------------------------------------------------------------------- 1 | filename = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".idaprobe") 2 | 3 | with open(filename, "wb") as fd: 4 | fd.write(b"WOO") 5 | 6 | idc.Exit(-5) 7 | 8 | -------------------------------------------------------------------------------- /mesos/plot.plt: -------------------------------------------------------------------------------- 1 | set terminal wxt size 1000,800 2 | set xlabel "Fuzz cases" 3 | set ylabel "Coverage" 4 | set logscale x 5 | set samples 1000000 6 | set key bottom 7 | 8 | plot "fuzz_stats.txt" u 2:3 w l,\ 9 | "fuzz_stats_nomutate.txt" u 2:3 w l,\ 10 | "fuzz_stats_first_mutate.txt" u 2:3 w l,\ 11 | "fuzz_stats_nomutate2.txt" u 2:3 w l 12 | 13 | -------------------------------------------------------------------------------- /mesos/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate debugger; 2 | extern crate guifuzz; 3 | 4 | pub mod mesofile; 5 | 6 | use std::path::Path; 7 | use std::process::Command; 8 | use std::collections::{HashMap}; 9 | use std::sync::{Arc, Mutex}; 10 | use std::fs::File; 11 | use std::io::Write; 12 | use std::time::{Instant, Duration}; 13 | use std::collections::hash_map::DefaultHasher; 14 | use std::hash::{Hash, Hasher}; 15 | use debugger::{ExitType, Debugger}; 16 | use guifuzz::*; 17 | 18 | fn record_input(fuzz_input: FuzzInput) { 19 | let mut hasher = DefaultHasher::new(); 20 | fuzz_input.hash(&mut hasher); 21 | 22 | let _ = std::fs::create_dir("inputs"); 23 | std::fs::write(format!("inputs/{:016x}.input", hasher.finish()), 24 | format!("{:#?}", fuzz_input)).expect("Failed to save input to disk"); 25 | } 26 | 27 | fn worker(stats: Arc>) { 28 | // Local stats database 29 | let mut local_stats = Statistics::default(); 30 | 31 | // Create an RNG for this thread 32 | let rng = Rng::new(); 33 | 34 | loop { 35 | // Delete all state invoked with the calc.exe process 36 | Command::new("reg.exe").args(&[ 37 | "delete", 38 | r"HKEY_CURRENT_USER\Software\Microsoft\Calc", 39 | "/f", 40 | ]).output().unwrap(); 41 | 42 | std::thread::sleep(Duration::from_millis(rng.rand() as u64 % 500)); 43 | 44 | // Create a new calc instance 45 | let mut dbg = Debugger::spawn_proc(&["calc.exe".into()], false); 46 | 47 | // Load the meso 48 | mesofile::load_meso(&mut dbg, Path::new("calc.exe.meso")); 49 | 50 | // Spin up the fuzzer thread 51 | let pid = dbg.pid; 52 | let thr = { 53 | let generate = (rng.rand() & 0x7) == 0; 54 | let stats = stats.clone(); 55 | 56 | std::thread::spawn(move || { 57 | while Window::attach_pid(pid, "Calculator").is_err() { 58 | std::thread::sleep(Duration::from_millis(200)); 59 | } 60 | 61 | if generate || stats.lock().unwrap().input_db.len() == 0 { 62 | generator(pid).unwrap_or(Vec::new()) 63 | } else { 64 | let mutated = mutate(stats).unwrap_or(Vec::new()); 65 | let _ = perform_actions(pid, &mutated); 66 | mutated 67 | } 68 | }) 69 | }; 70 | 71 | // Debug forever 72 | let exit_state = dbg.run(); 73 | 74 | // Extra-kill the debuggee 75 | let _ = dbg.kill(); 76 | 77 | // Swap coverage with the debugger and drop it so that the debugger 78 | // disconnects its resources from the debuggee so it can exit 79 | let mut coverage = HashMap::new(); 80 | std::mem::swap(&mut dbg.coverage, &mut coverage); 81 | std::mem::drop(dbg); 82 | 83 | // Connect to the fuzzer thread and get the result 84 | let genres = thr.join(); 85 | if genres.is_err() { 86 | continue; 87 | } 88 | let genres = genres.unwrap(); 89 | 90 | // Wrap up the fuzz input in an `Arc` 91 | let fuzz_input = Arc::new(genres); 92 | 93 | // Go through all coverage entries in the coverage database 94 | for (_, (module, offset, _, _)) in coverage.iter() { 95 | let key = (module.clone(), *offset); 96 | 97 | // Check if this coverage entry is something we've never seen 98 | // before 99 | if !local_stats.coverage_db.contains_key(&key) { 100 | // Coverage entry is new, save the fuzz input in the input 101 | // database 102 | local_stats.input_db.insert(fuzz_input.clone()); 103 | 104 | // Update the module+offset in the coverage database to 105 | // reflect that this input caused this coverage to occur 106 | local_stats.coverage_db.insert(key.clone(), 107 | fuzz_input.clone()); 108 | 109 | // Get access to global stats 110 | let mut stats = stats.lock().unwrap(); 111 | if !stats.coverage_db.contains_key(&key) { 112 | // Save input to global input database 113 | if stats.input_db.insert(fuzz_input.clone()) { 114 | stats.input_list.push(fuzz_input.clone()); 115 | 116 | record_input(fuzz_input.clone()); 117 | 118 | // Update the action database with known-feasible 119 | // actions 120 | for &action in fuzz_input.iter() { 121 | if stats.unique_action_set.insert(action) { 122 | stats.unique_actions.push(action); 123 | } 124 | } 125 | } 126 | 127 | // Save coverage to global coverage database 128 | stats.coverage_db.insert(key.clone(), fuzz_input.clone()); 129 | } 130 | } 131 | } 132 | 133 | // Get access to global stats 134 | let mut stats = stats.lock().unwrap(); 135 | 136 | // Update fuzz case count 137 | local_stats.fuzz_cases += 1; 138 | stats.fuzz_cases += 1; 139 | 140 | // Check if this case ended due to a crash 141 | if let ExitType::Crash(crashname) = exit_state { 142 | // Update crash information 143 | local_stats.crashes += 1; 144 | stats.crashes += 1; 145 | 146 | // Add the crashing input to the input databases 147 | local_stats.input_db.insert(fuzz_input.clone()); 148 | if stats.input_db.insert(fuzz_input.clone()) { 149 | stats.input_list.push(fuzz_input.clone()); 150 | 151 | record_input(fuzz_input.clone()); 152 | 153 | // Update the action database with known-feasible 154 | // actions 155 | for &action in fuzz_input.iter() { 156 | if stats.unique_action_set.insert(action) { 157 | stats.unique_actions.push(action); 158 | } 159 | } 160 | } 161 | 162 | // Add the crash name and corresponding fuzz input to the crash 163 | // database 164 | local_stats.crash_db.insert(crashname.clone(), fuzz_input.clone()); 165 | stats.crash_db.insert(crashname, fuzz_input.clone()); 166 | } 167 | } 168 | } 169 | 170 | fn main() { 171 | // Global statistics 172 | let stats = Arc::new(Mutex::new(Statistics::default())); 173 | 174 | // Open a log file 175 | let mut log = File::create("fuzz_stats.txt").unwrap(); 176 | 177 | // Save the current time 178 | let start_time = Instant::now(); 179 | 180 | for _ in 0..10 { 181 | // Spawn threads 182 | let stats = stats.clone(); 183 | let _ = std::thread::spawn(move || { 184 | worker(stats); 185 | }); 186 | } 187 | 188 | loop { 189 | std::thread::sleep(Duration::from_millis(1000)); 190 | 191 | // Get access to the global stats 192 | let stats = stats.lock().unwrap(); 193 | 194 | let uptime = (Instant::now() - start_time).as_secs_f64(); 195 | let fuzz_case = stats.fuzz_cases; 196 | print!("{:12.2} uptime | {:7} fuzz cases | {:5} uniq actions | \ 197 | {:8} coverage | {:5} inputs | {:6} crashes [{:6} unique]\n", 198 | uptime, fuzz_case, 199 | stats.unique_actions.len(), 200 | stats.coverage_db.len(), stats.input_db.len(), 201 | stats.crashes, stats.crash_db.len()); 202 | 203 | write!(log, "{:12.0} {:7} {:8} {:5} {:6} {:6}\n", 204 | uptime, fuzz_case, stats.coverage_db.len(), stats.input_db.len(), 205 | stats.crashes, stats.crash_db.len()).unwrap(); 206 | log.flush().unwrap(); 207 | } 208 | } 209 | 210 | -------------------------------------------------------------------------------- /mesos/src/mesofile.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | use std::sync::Arc; 3 | use debugger::{Debugger, BreakpointType}; 4 | 5 | /// Grab a native-endianness u32 from a slice of u8s 6 | fn u32_from_slice(val: &[u8]) -> u32 { 7 | let mut tmp = [0u8; 4]; 8 | tmp.copy_from_slice(&val[..4]); 9 | u32::from_ne_bytes(tmp) 10 | } 11 | 12 | /// Computes the path to the cached meso filename for a given module loaded 13 | /// at `base` 14 | pub fn compute_cached_meso_name(dbg: &mut Debugger, filename: &str, 15 | base: usize) -> PathBuf { 16 | let mut image_header = [0u8; 4096]; 17 | 18 | // Read the image header at `base` 19 | assert!(dbg.read_mem(base, &mut image_header) == 20 | std::mem::size_of_val(&image_header), 21 | "Failed to read PE image header from target"); 22 | 23 | // Validate this is a PE 24 | assert!(&image_header[0..2] == b"MZ", "File was not MZ"); 25 | let pe_ptr = u32_from_slice(&image_header[0x3c..0x40]) as usize; 26 | assert!(&image_header[pe_ptr..pe_ptr+4] == b"PE\0\0"); 27 | 28 | // Get TimeDateStamp and ImageSize from the PE header 29 | let timestamp = u32_from_slice(&image_header[pe_ptr+8..pe_ptr+0xc]); 30 | let imagesz = 31 | u32_from_slice(&image_header[pe_ptr+0x50..pe_ptr+0x54]); 32 | 33 | // Compute the meso name 34 | format!("cache\\{}_{:x}_{:x}.meso", filename, timestamp, imagesz).into() 35 | } 36 | 37 | /// Load a meso file based on `meso_path` and apply breakpoints as requested to 38 | /// the `Debugger` specified by `dbg` 39 | pub fn load_meso(dbg: &mut Debugger, meso_path: &Path) { 40 | // Do nothing if the file doesn't exist 41 | if !meso_path.is_file() { 42 | return; 43 | } 44 | 45 | // Read the file 46 | let meso: Vec = std::fs::read(meso_path).expect("Failed to read meso"); 47 | 48 | // Current module name we are processing 49 | let mut cur_modname: Option> = None; 50 | 51 | // Pointer to the remainder of the file 52 | let mut ptr = &meso[..]; 53 | 54 | // Read a `$ty` from the mesofile 55 | macro_rules! read { 56 | ($ty:ty) => {{ 57 | let mut array = [0; std::mem::size_of::<$ty>()]; 58 | array.copy_from_slice(&ptr[..std::mem::size_of::<$ty>()]); 59 | ptr = &ptr[std::mem::size_of::<$ty>()..]; 60 | <$ty>::from_le_bytes(array) 61 | }}; 62 | } 63 | 64 | while ptr.len() > 0 { 65 | // Get record type 66 | let record = read!(u8); 67 | 68 | if record == 0 { 69 | // Module record 70 | let modname_len = read!(u16); 71 | 72 | // Convert name to Rust str 73 | let modname = std::str::from_utf8(&ptr[..modname_len as usize]) 74 | .expect("Module name was not valid UTF-8"); 75 | ptr = &ptr[modname_len as usize..]; 76 | 77 | cur_modname = Some(Arc::new(modname.into())); 78 | } else if record == 1 { 79 | // Current module name state 80 | let module: &Arc = cur_modname.as_ref().unwrap(); 81 | 82 | // Function record 83 | let funcname_len = read!(u16); 84 | 85 | // Convert name to Rust str 86 | let funcname: Arc = 87 | Arc::new(std::str::from_utf8(&ptr[..funcname_len as usize]) 88 | .expect("Function name was not valid UTF-8") 89 | .to_string()); 90 | ptr = &ptr[funcname_len as usize..]; 91 | 92 | // Get function offset from module base 93 | let funcoff = read!(u64) as usize; 94 | 95 | // Get number of basic blocks 96 | let num_blocks = read!(u32) as usize; 97 | 98 | // Iterate over all block offsets 99 | for _ in 0..num_blocks { 100 | let blockoff = read!(i32) as isize as usize; 101 | 102 | // Add function offset from module base to offset 103 | let offset = funcoff.wrapping_add(blockoff); 104 | 105 | // Register this breakpoint 106 | dbg.register_breakpoint(module.clone(), offset, 107 | funcname.clone(), blockoff, BreakpointType::Single, None); 108 | } 109 | } else { 110 | panic!("Unhandled record"); 111 | } 112 | } 113 | } 114 | --------------------------------------------------------------------------------