From 5c86ead1d31bea75388599f01f24c267a9add7de Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Tue, 23 Nov 2021 23:03:11 -0800 Subject: [PATCH] Playing with success cases / defenses against arbitrary code execution reading/writing weird addresses :) --- src/cartridge/mmc3.rs | 2 +- src/cartridge/uxrom.rs | 11 ++++++++- src/cpu/addressing_modes.rs | 2 +- src/cpu/mod.rs | 10 ++++---- src/cpu/opcodes.rs | 2 +- src/cpu/serialize.rs | 6 ++++- src/input.rs | 49 +++++++++++++++++++++++++++++++++++-- src/main.rs | 47 ++++++++++++++++++++++++----------- src/ppu/mod.rs | 3 --- src/ppu/rendering.rs | 2 +- src/ppu/serialize.rs | 3 ++- 11 files changed, 106 insertions(+), 31 deletions(-) diff --git a/src/cartridge/mmc3.rs b/src/cartridge/mmc3.rs index 2b1f3d7..2965844 100644 --- a/src/cartridge/mmc3.rs +++ b/src/cartridge/mmc3.rs @@ -131,7 +131,7 @@ impl Mapper for Mmc3 { self.cart.prg_rom[chunk_num][chunk_half + offset_8k] } _ => { - println!("bad address read from MMC3: 0x{:X}", address); + //println!("bad address read from MMC3: 0x{:X}", address); 0 } }; diff --git a/src/cartridge/uxrom.rs b/src/cartridge/uxrom.rs index a797ba3..9a52ecd 100644 --- a/src/cartridge/uxrom.rs +++ b/src/cartridge/uxrom.rs @@ -26,7 +26,16 @@ impl Mapper for Uxrom { self.chr_ram[address] } } - 0x8000..=0xBFFF => self.cart.prg_rom[self.bank_select][address % 0x4000], + 0x8000..=0xBFFF => { + let address = address % 0x4000; + if self.bank_select > self.cart.prg_rom.len() { + return 0; + } + match self.cart.prg_rom[self.bank_select].get(address) { + None => 0, + Some(x) => *x, + } + } 0xC000..=0xFFFF => self.cart.prg_rom[self.cart.prg_rom.len() - 1][address % 0x4000], _ => { println!("bad address read from UxROM mapper: 0x{:X}", address); diff --git a/src/cpu/addressing_modes.rs b/src/cpu/addressing_modes.rs index 62e2a7e..be81698 100644 --- a/src/cpu/addressing_modes.rs +++ b/src/cpu/addressing_modes.rs @@ -16,7 +16,7 @@ impl super::Cpu { | 0xFC | 0xFD => self.address_page_cross(old_address, new_address), 0x1E | 0x1F | 0x3E | 0x3F | 0x5E | 0x5F | 0x7E | 0x7F | 0x9D | 0xC3 | 0xC7 | 0xCF | 0xD3 | 0xD7 | 0xDB | 0xDE | 0xDF | 0xFE | 0xFF => self.clock += 1, - _ => panic!("illegal opcode using abs x: {:02x}", current_opcode), + _ => self.clock += 1, } new_address } diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index 9a5a082..e65e8d9 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -669,7 +669,7 @@ impl Cpu { self.handle_interrupts(); // back up clock so we know how many cycles we complete - + self.addresses_fetched.insert(self.pc); self.before_clock = self.clock; let opcode = ::from(self.read(self.pc)); self.addresses_fetched.insert(self.pc); @@ -689,7 +689,7 @@ impl Cpu { // memory interface pub fn read(&mut self, address: usize) -> u8 { - // self.addresses_fetched.insert(address); + self.addresses_fetched.insert(address); let val = match address { 0x0000..=0x1FFF => self.mem[address % 0x0800], 0x2000..=0x3FFF => self.read_ppu_reg(address % 8), @@ -700,14 +700,14 @@ impl Cpu { 0x4000..=0x4017 => 0, // can't read from these APU registers 0x4018..=0x401F => 0, // APU and I/O functionality that is normally disabled. See CPU Test Mode. 0x4020..=0xFFFF => self.mapper.borrow().read(address), - _ => panic!("invalid read from 0x{:02x}", address), + _ => 0, //panic!("invalid read from 0x{:02x}", address), }; val } // memory interface fn write(&mut self, address: usize, val: u8) { - // self.addresses_fetched.insert(address); + self.addresses_fetched.insert(address); match address { 0x0000..=0x1FFF => self.mem[address % 0x0800] = val, 0x2000..=0x3FFF => self.write_ppu_reg(address % 8, val), @@ -716,7 +716,7 @@ impl Cpu { 0x4000..=0x4017 => self.apu.write_reg(address, val), 0x4018..=0x401F => (), // APU and I/O functionality that is normally disabled. See CPU Test Mode. 0x4020..=0xFFFF => self.mapper.borrow_mut().write(address, val), - _ => panic!("invalid write to {:02x}", address), + _ => {} //panic!("invalid write to {:02x}", address), } } diff --git a/src/cpu/opcodes.rs b/src/cpu/opcodes.rs index a121f68..01527ce 100644 --- a/src/cpu/opcodes.rs +++ b/src/cpu/opcodes.rs @@ -553,7 +553,7 @@ impl super::Cpu { } pub fn bad(&mut self, _address: usize, _mode: Mode) { - panic!("illegal opcode: 0x{:02X}", self.read(self.pc)); // this won't be the illegal opcode because the PC somehow hasn't been updated yet + //panic!("illegal opcode: 0x{:02X}", self.read(self.pc)); // this won't be the illegal opcode because the PC somehow hasn't been updated yet } // Interrupts diff --git a/src/cpu/serialize.rs b/src/cpu/serialize.rs index 7494abd..7f1e307 100644 --- a/src/cpu/serialize.rs +++ b/src/cpu/serialize.rs @@ -1,6 +1,7 @@ use super::Mode; use serde::{Deserialize, Serialize}; +use std::collections::HashSet; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct CpuData { @@ -8,7 +9,7 @@ pub struct CpuData { a: u8, x: u8, y: u8, - pc: usize, + pub(crate) pc: usize, s: u8, p: u8, clock: u64, @@ -19,6 +20,7 @@ pub struct CpuData { button_number1: u8, button_number2: u8, mode_table: Vec, + pub(crate) addresses_fetched: HashSet, } impl super::Cpu { @@ -39,6 +41,7 @@ impl super::Cpu { button_number1: self.button_number1, button_number2: self.button_number2, mode_table: self.mode_table.clone(), + addresses_fetched: self.addresses_fetched.clone(), } } @@ -58,5 +61,6 @@ impl super::Cpu { self.button_number1 = data.button_number1; self.button_number2 = data.button_number2; self.mode_table = data.mode_table; + self.addresses_fetched = data.addresses_fetched; } } diff --git a/src/input.rs b/src/input.rs index fa385a6..088560a 100644 --- a/src/input.rs +++ b/src/input.rs @@ -83,7 +83,51 @@ impl FuzzingInput { } else { tas = format!("{}.", tas); } - tas = format!("{}|||\n", tas); + tas = format!("{}|", tas); + + if input.player_2_input >> 7 == 1 { + tas = format!("{}R", tas); + } else { + tas = format!("{}.", tas); + } + if (input.player_2_input >> 6) & 0x01 == 1 { + tas = format!("{}L", tas); + } else { + tas = format!("{}.", tas); + } + if (input.player_2_input >> 5) & 0x01 == 1 { + tas = format!("{}D", tas); + } else { + tas = format!("{}.", tas); + } + if (input.player_2_input >> 4) & 0x01 == 1 { + tas = format!("{}U", tas); + } else { + tas = format!("{}.", tas); + } + if (input.player_2_input >> 3) & 0x01 == 1 { + tas = format!("{}T", tas); + } else { + tas = format!("{}.", tas); + } + + if (input.player_2_input >> 2) & 0x01 == 1 { + tas = format!("{}S", tas); + } else { + tas = format!("{}.", tas); + } + if (input.player_2_input >> 1) & 0x01 == 1 { + tas = format!("{}B", tas); + } else { + tas = format!("{}.", tas); + } + + if (input.player_2_input >> 0) & 0x01 == 1 { + tas = format!("{}A", tas); + } else { + tas = format!("{}.", tas); + } + tas = format!("{}||\n", tas); } fs::write(filename, tas).expect("Unable to write file"); @@ -96,7 +140,7 @@ impl FuzzingInput { self.frames.push(FrameInput { console_action: ConsoleAction::None, player_1_input: rng.next() as u8, - player_2_input: 0, + player_2_input: rng.next() as u8, }); } @@ -105,6 +149,7 @@ impl FuzzingInput { if (check / frames_to_consider as f64) < MUTATION_RATE { if frame_num < self.frames.len() { self.frames[frame_num].player_1_input = rng.next() as u8; + self.frames[frame_num].player_2_input = rng.next() as u8; } } diff --git a/src/main.rs b/src/main.rs index 5062c01..247a55a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,7 @@ use std::sync::{mpsc, Arc, Mutex}; use std::{fs, thread}; // The number of cpu instances to spawn.. -const NUM_THREADS: usize = 1; +const NUM_THREADS: usize = 28; // The number of frames to fuzz and process // A small number exploits the current point more at the expense of @@ -36,26 +36,26 @@ const FRAMES_TO_CONSIDER: usize = 500; // Same input should generate the same output... // (I make no guarantee of that at the moment) -const RNG_SEED: u32 = 0x35234623; +const RNG_SEED: u32 = 0x1334357; // If set to a low number, this disables start presses after the given frame // Useful for some games where pausing does nothing to advance the game... -const DISABLE_START_PRESSES_AFTER: usize = usize::MAX; +const DISABLE_START_PRESSES_AFTER: usize = 100; // The rate at which seed inputs become corrupted.. -const MUTATION_RATE: f64 = 0.05; +const MUTATION_RATE: f64 = 0.10; // The rate at which seed inputs may become soft resets.. const MUTATION_RATE_SOFT_RESET: f64 = 0.00; // The number of cases to fuzz from a given seed input at each consideration point.. -const SEED_CASES: usize = 500; +const SEED_CASES: usize = 10; // Only add the original seed case top the queue (hammer a single state) -const SINGLE_SHOT: bool = false; +const SINGLE_SHOT: bool = true; // Fast forward the seed state to a given frame -const PLAY_FROM: usize = 0; +const PLAY_FROM: usize = 1900; fn main() -> Result<(), String> { let argv = std::env::args().collect::>(); @@ -81,11 +81,16 @@ fn main() -> Result<(), String> { let mut starting_state = FuzzingState::default(rom_filename.clone()); + let mut previous_states: HashSet = HashSet::new(); + if PLAY_FROM > 0 { println!("Playing {} frames prelude...", PLAY_FROM); let (mut cpu, _) = starting_state.load_state(rom_filename.clone()); play_frames(&mut cpu, PLAY_FROM, &seed_input); starting_state = FuzzingState::save_state(cpu, PLAY_FROM); + for addr in starting_state.cpu_state.addresses_fetched.iter() { + if previous_states.insert(*addr) {} + } } let mut fuzzing_queue: PriorityQueue = PriorityQueue::new(); @@ -121,8 +126,7 @@ fn main() -> Result<(), String> { }); } - let mut previous_states: HashSet> = HashSet::new(); - previous_states.insert(vec![0; 8192]); + let mut loop_count = 0; loop { println!("Prospective Cases: {}", fuzzing_queue.len()); @@ -147,21 +151,35 @@ fn main() -> Result<(), String> { for i in 0..NUM_THREADS { match result_channels[i].1.recv() { Ok((fuzzing_input, fuzzing_state)) => { - let mut lowest_similarity = u64::MAX; + /* let mut lowest_similarity = u64::MAX; for (_j, state) in previous_states.iter().enumerate() { let similiarty = - hamming::distance(&[state[0x86]], &[fuzzing_state.cpu_state.mem[0x86]]); - // println!("{} {} {} ",i,j,similiarty); + hamming::distance(&state, &fuzzing_state.cpu_state.mem); + if similiarty < lowest_similarity { lowest_similarity = similiarty } } - previous_states.insert(fuzzing_state.cpu_state.mem.clone()); + previous_states.insert(fuzzing_state.cpu_state.mem.clone());*/ + let mut lowest_similarity = 0u64; + for addr in fuzzing_state.cpu_state.addresses_fetched.iter() { + if previous_states.insert(*addr) { + lowest_similarity += 1; + } + } + + if i == 3 && loop_count == 3 { + fuzzing_input.export( + "megaman_interestng.fm2".parse().unwrap(), + fuzzing_state.frames, + ); + } if SINGLE_SHOT { if lowest_similarity > 0 { - for i in 0..lowest_similarity.log10() { + println!("{} ", lowest_similarity); + for i in 0..lowest_similarity { let mut mutated_input: FuzzingInput = fuzzing_input.clone(); mutated_input.mutate_from( &mut rng, @@ -239,6 +257,7 @@ fn main() -> Result<(), String> { ); } } + loop_count += 1; } } diff --git a/src/ppu/mod.rs b/src/ppu/mod.rs index d888a48..c8d03d6 100644 --- a/src/ppu/mod.rs +++ b/src/ppu/mod.rs @@ -171,7 +171,6 @@ impl Ppu { } let mut pixel: Option<(usize, usize, (u8, u8, u8))> = None; - // Visible scanlines (0-239) if rendering && (self.scanline < 240 || self.scanline == 261) { // background-related things @@ -236,8 +235,6 @@ impl Ppu { // signal time to draw frame - - self.line_cycle += 1; if self.line_cycle == 341 { diff --git a/src/ppu/rendering.rs b/src/ppu/rendering.rs index 3c5aa5d..1e6a36f 100644 --- a/src/ppu/rendering.rs +++ b/src/ppu/rendering.rs @@ -124,7 +124,7 @@ impl super::Ppu { } // let pixel = self.read(palette_address as usize) as usize; let pixel = self.palette_ram[palette_address as usize] as usize; - let mut color: (u8, u8, u8) = super::PALETTE_TABLE[pixel]; + let mut color: (u8, u8, u8) = super::PALETTE_TABLE[pixel % 64]; if self.emphasize_red { color.0 = emphasize(&color.0); color.1 = deemphasize(&color.1); diff --git a/src/ppu/serialize.rs b/src/ppu/serialize.rs index fd0932b..137a381 100644 --- a/src/ppu/serialize.rs +++ b/src/ppu/serialize.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; +use std::collections::HashSet; -#[derive(Serialize, Deserialize, Debug, Clone, Hash)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct PpuData { line_cycle: usize, scanline: usize,