From 38efd7dee4beb6f322257107690ec45b88456e8e Mon Sep 17 00:00:00 2001 From: Esgr0bar <163046224+Esgr0bar@users.noreply.github.com> Date: Wed, 19 Jun 2024 17:21:11 +0200 Subject: [PATCH] merge feat/confloader to dev/0.0.1 (#9) Co-authored-by: std3 <67806187+standard3@users.noreply.github.com> --- Cargo.toml | 5 +- README.md | 19 ++-- src/architecture.rs | 2 + src/architecture/generic.rs | 170 +++++++++++++++++++++++++++++++ src/architecture/riscv.rs | 111 ++++++++++++++++++++ src/configuration.rs | 19 ++++ src/lib.rs | 1 + src/utils.rs | 28 +++++ tests/configuration.rs | 89 ++++++++++++++++ tests/fixtures/riscv-sample.toml | 15 +++ tests/integration.rs | 1 + tests/test.rs | 0 12 files changed, 452 insertions(+), 8 deletions(-) create mode 100644 src/architecture/generic.rs create mode 100644 src/architecture/riscv.rs create mode 100644 src/configuration.rs create mode 100644 tests/configuration.rs create mode 100644 tests/fixtures/riscv-sample.toml create mode 100644 tests/integration.rs delete mode 100644 tests/test.rs diff --git a/Cargo.toml b/Cargo.toml index 8182d8f..b6deba5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,9 @@ keywords = ["forensics", "memory forensics"] exclude = [".github", "CONTRIBUTING.md", "CODE_OF_CONDUCT.md", "assets"] license = "GPL-3.0" -authors = ["Théo Abel "] # todo: add other contributors +authors = ["Théo Abel ", "Nathan Dandrimont "] # todo: add other contributors [dependencies] +serde = { version = "1.0", features = ["derive"] } +toml = "0.8.12" +anyhow = "1.0" diff --git a/README.md b/README.md index ceb722a..de9ed10 100644 --- a/README.md +++ b/README.md @@ -46,21 +46,26 @@ todo - Add support for Binary Code Analysis with `miasm` ```rust +use anyhow::Result; use libmmu::architectures::{ RiscV, RiscVMMUMode }; use libmmu::utils::{ MemorySpace, SpaceType, MachineConfig }; -fn main() { +fn main() -> Result<()> { + let dumpfile = ...; + let outfolder = ...; + let memspaces = MemorySpace::new() .add(SpaceType::RAM, 0x0000000080000000, 0x000000017fffffff) .add(SpaceType::ROM, 0x0000000000000000, 0x0000000000011fff); - let conf = MachineConfig::::new() - .dumpfile("dump.raw") - .mmu(RiscVMMUMode::SV39) - .memspaces(memspaces) - .outfile("output"); + let machine = Machine::new( + MachineType::RiscV(MMUMode:SV32), + memspaces, + dumpfile, + outfolder + )?; - conf.resolve_spaces() + machine.resolve_spaces()?; } ``` diff --git a/src/architecture.rs b/src/architecture.rs index e69de29..50acd74 100644 --- a/src/architecture.rs +++ b/src/architecture.rs @@ -0,0 +1,2 @@ +pub mod generic; +pub mod riscv; diff --git a/src/architecture/generic.rs b/src/architecture/generic.rs new file mode 100644 index 0000000..0f68247 --- /dev/null +++ b/src/architecture/generic.rs @@ -0,0 +1,170 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use std::{hash, path::PathBuf}; + +use super::riscv::MMUMode as RiscVMMUMode; + +/// Enumerates types of memory regions. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum MemoryRegionType { + #[default] + RAM, + ROM, +} + +/// Represents a memory region with a start and end address. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct MemoryRegion { + pub region_type: MemoryRegionType, + pub start_address: u64, + pub end_address: u64, +} + +impl MemoryRegion { + pub fn new( + region_type: MemoryRegionType, + start_address: u64, + end_address: u64, + ) -> Result { + // Check that addresses are valid memory addresses + if start_address >= end_address { + return Err(anyhow::anyhow!( + "Invalid memory region, start address is greater than or equal to end address" + )); + } + + Ok(Self { + region_type, + start_address, + end_address, + }) + } + + /// Returns the size of the memory region. + pub fn size(&self) -> u64 { + self.end_address - self.start_address + } + + /// Returns true if the memory region contains the address. + /// A memory region contains an address if the address is greater than or equal to the start address and less than the end address. + pub fn contains(&self, address: u64) -> bool { + self.start_address <= address && address < self.end_address + } + + /// Returns true if the two memory regions are overlapping. + /// Two memory regions are overlapping if the start address of one region is less than the end address of the other region. + pub fn is_overlapping(&self, other: &MemoryRegion) -> bool { + self.contains(other.start_address) || other.contains(self.start_address) + } + + /// Returns true if the two memory regions are adjacent. + /// Two memory regions are adjacent if the end address of one region is equal to the start address of the other region. + pub fn is_adjacent(&self, other: &MemoryRegion) -> bool { + self.start_address == other.end_address || other.start_address == self.end_address + } +} + +/// Represents a memory space with regions. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Default)] +pub struct MemorySpace { + pub regions: Vec, +} + +impl MemorySpace { + pub fn new() -> Self { + Self { + regions: Vec::new(), + } + } + + pub fn add(&mut self, region: MemoryRegion) -> Result<&mut Self> { + // Check if the memory region is overlapping with another region + if self.regions.iter().any(|r| r.is_overlapping(®ion)) { + return Err(anyhow::anyhow!( + "Memory region is overlapping with another region" + )); + } + + self.regions.push(region); + Ok(self) + } +} + +/// Represents a CPU register with a value. +/// Depending on the architecture, the *validity* changes. +pub trait CPURegister { + type Value: hash::Hash + Eq + Copy + Default; + + fn is_valid(&self) -> Result; +} + +/// Represents a page table entry with an address and flags. +/// It holds the mapping between a virtual address of a page and the address of a physical frame. +/// There is also auxiliary information about the page such as a present bit, a dirty or modified bit, +/// address space or process ID information, amongst others. +pub trait PageTableEntry { + type Address: hash::Hash + Eq + Copy + Default; + type Flags: hash::Hash + Eq + Copy + Default; + + fn is_dirty(&self) -> bool; + fn is_accessed(&self) -> bool; + fn is_global(&self) -> bool; + fn is_readable(&self) -> bool; + fn is_writable(&self) -> bool; + fn is_executable(&self) -> bool; +} + +/// Represents a page table with entries. +/// It is a data structure used in a virtual memory system to manage the mapping between virtual addresses and physical addresses. +/// It is used to translate virtual addresses to physical addresses and to manage the memory permissions of the pages. +/// It is also used to store additional information about the pages, such as the status of the page, the address space or process ID, amongst others. +pub trait PageTable { + type Entries: hash::Hash + Eq + Copy + Default + PageTableEntry; + + // fn apply_on_entries(function: FnMut(PageTableEntry) -> Vec ) -> ? // FIXME: to be defined, but is it necessary? +} + +/// Enumerates types of supported machines. +/// This enum is used to specify the type of machine that is being parsed. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum MachineType { + RiscV(RiscVMMUMode), +} + +impl Default for MachineType { + fn default() -> Self { + Self::RiscV(RiscVMMUMode::SV32) + } +} + +/// Represents a machine with a type, MMU, CPU, memory regions, and an associated dump file. +/// It is used to store the machine's configuration, memory regions, and the dump file that is being used. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Default)] +pub struct Machine { + /// Type of the machine and its associated MMU mode. + pub machine_type: MachineType, + /// Memory regions of the machine. + pub memory_regions: MemorySpace, + /// Path to the dump file. + pub dumpfile: PathBuf, + /// Path to the output folder. + pub outfolder: PathBuf, +} + +impl Machine { + pub fn new( + machine_type: MachineType, + memory_regions: MemorySpace, + dumpfile: PathBuf, + outfolder: PathBuf, + ) -> Self { + // TODO: Validate each field + + Self { + machine_type, + memory_regions, + dumpfile, + outfolder, + } + } +} diff --git a/src/architecture/riscv.rs b/src/architecture/riscv.rs new file mode 100644 index 0000000..43d0543 --- /dev/null +++ b/src/architecture/riscv.rs @@ -0,0 +1,111 @@ +use super::generic::{CPURegister as CPURegisterTrait, PageTableEntry as PageTableEntryTrait}; + +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +/// Represents a RISC-V CPU register associated with a value. +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, Hash, Eq, PartialEq)] +pub struct CPURegister { + pub value: u64, +} + +impl CPURegisterTrait for CPURegister { + type Value = u64; + + fn is_valid(&self) -> Result { + todo!() + } +} + +impl CPURegister { + pub fn new(value: u64) -> Self { + Self { value } + } +} + +/// Represents a RISC-V page table entry. +/// It holds the mapping between a virtual address of a page and the address of a physical frame. +/// There is also auxiliary information about the page such as a present bit, a dirty or modified bit, +/// address space or process ID information, amongst others. +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Hash, Eq, PartialEq)] +pub struct PageTableEntry { + pub address: u64, + pub flags: u64, +} + +impl PageTableEntry { + pub fn new(address: u64, flags: u64) -> Self { + Self { address, flags } + } + + pub fn is_supervisor(&self) -> bool { + todo!() + } +} + +impl PageTableEntryTrait for PageTableEntry { + type Address = u64; + type Flags = u64; + + // FIXME: Implement the following methods + fn is_dirty(&self) -> bool { + todo!() + } + + fn is_accessed(&self) -> bool { + todo!() + } + + fn is_global(&self) -> bool { + todo!() + } + + fn is_readable(&self) -> bool { + todo!() + } + + fn is_writable(&self) -> bool { + todo!() + } + + fn is_executable(&self) -> bool { + todo!() + } +} + +/// Enumerates RISC-V MMU modes. +/// The MMU modes are used to determine the number of bits used for virtual and physical addresses. +/// The modes are named after the number of bits used for the virtual address space. +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Hash, Eq, PartialEq, Default)] +pub enum MMUMode { + #[default] + SV32, + SV39, + SV48, +} + +/// Represents a RISC-V CPU. +#[derive(Debug, Clone, Serialize, Deserialize, Default, Hash, Eq, PartialEq)] +pub struct CPU { + pub registers: Vec, +} + +impl CPU { + pub fn new() -> Self { + Self { + registers: Vec::new(), + } + } +} + +/// Represents a RISC-V MMU. +#[derive(Debug, Clone, Serialize, Deserialize, Default, Hash, Eq, PartialEq)] +pub struct MMU { + pub mode: MMUMode, +} + +impl MMU { + pub fn new(mode: MMUMode) -> Self { + Self { mode } + } +} diff --git a/src/configuration.rs b/src/configuration.rs new file mode 100644 index 0000000..5d585da --- /dev/null +++ b/src/configuration.rs @@ -0,0 +1,19 @@ +use anyhow::Result; +use std::fs; +use std::path::Path; + +use crate::utils::TryFromPath; + +use super::architecture::generic::Machine; + +/// Represents a configuration file for a machine. +/// It holds information about the machine's architecture, memory regions, and other relevant information. +/// The configuration file must be written in the TOML format. +impl TryFromPath for Machine { + fn from_path(path: &Path) -> Result { + let configuration = fs::read_to_string(path)?; + let machine: Machine = toml::from_str(&configuration)?; + + Ok(machine) + } +} diff --git a/src/lib.rs b/src/lib.rs index 6e975b1..f295f55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod architecture; +pub mod configuration; pub mod utils; diff --git a/src/utils.rs b/src/utils.rs index e69de29..27f8151 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -0,0 +1,28 @@ +use anyhow::Result; +use std::path::{Path, PathBuf}; + +/// Try to convert from a path. +pub trait TryFromPath { + /// Converts from a path reference. + fn from_path(path: &Path) -> Result + where + Self: Sized; +} + +/// Defines a trait for converting from a path. +pub trait FromPath { + /// Converts from a path reference. + fn from_path(path: &Path) -> Self; +} + +impl FromPath for String { + fn from_path(path: &Path) -> String { + path.to_string_lossy().into_owned() + } +} + +impl FromPath for PathBuf { + fn from_path(path: &Path) -> PathBuf { + path.to_path_buf() + } +} diff --git a/tests/configuration.rs b/tests/configuration.rs new file mode 100644 index 0000000..af583b1 --- /dev/null +++ b/tests/configuration.rs @@ -0,0 +1,89 @@ +#[cfg(test)] +mod configuration_tests { + use std::path::PathBuf; + + use anyhow::Result; + + use libmmu::architecture::generic::{ + Machine, MachineType, MemoryRegion, MemoryRegionType, MemorySpace, + }; + use libmmu::architecture::riscv::MMUMode; + use libmmu::utils::TryFromPath; + + #[test] + fn create_riscv_sv32_configuration_from_code() -> Result<()> { + let machine_type = MachineType::RiscV(MMUMode::SV32); + let dumpfile = PathBuf::from("tests/fixtures/riscv-sv32.dump"); + let outfolder = PathBuf::from("tests/output"); + + let memspaces = MemorySpace::new() + .add(MemoryRegion { + region_type: MemoryRegionType::RAM, + start_address: 0, + end_address: 1, + })? + .add(MemoryRegion { + region_type: MemoryRegionType::ROM, + start_address: 2, + end_address: 3, + })? + .to_owned(); + + let machine = Machine { + machine_type: machine_type, + memory_regions: memspaces, + dumpfile, + outfolder, + }; + + assert_eq!(machine.machine_type, MachineType::RiscV(MMUMode::SV32)); + assert_eq!(machine.memory_regions.regions.len(), 2); + assert_eq!( + machine.memory_regions.regions[0].region_type, + MemoryRegionType::RAM + ); + assert_eq!(machine.memory_regions.regions[0].start_address, 0); + assert_eq!(machine.memory_regions.regions[0].end_address, 1); + assert_eq!( + machine.memory_regions.regions[1].region_type, + MemoryRegionType::ROM + ); + assert_eq!(machine.memory_regions.regions[1].start_address, 2); + assert_eq!(machine.memory_regions.regions[1].end_address, 3); + assert_eq!( + machine.dumpfile, + PathBuf::from("tests/fixtures/riscv-sv32.dump") + ); + assert_eq!(machine.outfolder, PathBuf::from("tests/output")); + + Ok(()) + } + + #[test] + fn create_riscv_sv32_configuration_from_toml() -> Result<()> { + let path = PathBuf::from("tests/fixtures/riscv-sample.toml"); + let machine = Machine::from_path(path.as_path())?; + + assert_eq!(machine.machine_type, MachineType::RiscV(MMUMode::SV32)); + assert_eq!(machine.memory_regions.regions.len(), 2); + assert_eq!( + machine.memory_regions.regions[0].region_type, + MemoryRegionType::RAM + ); + assert_eq!(machine.memory_regions.regions[0].start_address, 0x12345678); + assert_eq!(machine.memory_regions.regions[0].end_address, 0x87654321); + assert_eq!( + machine.memory_regions.regions[1].region_type, + MemoryRegionType::ROM + ); + assert_eq!(machine.memory_regions.regions[1].start_address, 0); + assert_eq!(machine.memory_regions.regions[1].end_address, 73727); + assert_eq!( + machine.dumpfile, + PathBuf::from("tests/fixtures/riscv-sv32.dump") + ); + assert_eq!(machine.outfolder, PathBuf::from("tests/output")); + + Ok(()) + } +} diff --git a/tests/fixtures/riscv-sample.toml b/tests/fixtures/riscv-sample.toml new file mode 100644 index 0000000..cce4a43 --- /dev/null +++ b/tests/fixtures/riscv-sample.toml @@ -0,0 +1,15 @@ +dumpfile = "tests/fixtures/riscv-sv32.dump" +outfolder = "tests/output" + +[machine_type] +RiscV = "SV32" + +[[memory_regions.regions]] +region_type = "RAM" # test RAM +start_address = 0x12345678 # test hex numbers +end_address = 0x87654321 + +[[memory_regions.regions]] +region_type = "ROM" +start_address = 0 +end_address = 73727 diff --git a/tests/integration.rs b/tests/integration.rs new file mode 100644 index 0000000..987f47d --- /dev/null +++ b/tests/integration.rs @@ -0,0 +1 @@ +mod configuration; diff --git a/tests/test.rs b/tests/test.rs deleted file mode 100644 index e69de29..0000000