diff --git a/src/util/mod.rs b/src/util/mod.rs index 08a23a3..774eb38 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,5 +1,6 @@ pub use address::Address; pub use module::Module; +pub use module::ModuleEntry; pub use process::Process; pub mod address; diff --git a/src/util/module.rs b/src/util/module.rs index c51c36a..35d65a9 100644 --- a/src/util/module.rs +++ b/src/util/module.rs @@ -8,16 +8,34 @@ use goblin::pe::options::ParseOptions; use goblin::pe::section_table::SectionTable; use goblin::pe::PE; +#[cfg(target_os = "linux")] +use goblin::elf::{program_header, sym, Elf, SectionHeader}; +use std::path::PathBuf; + +#[cfg(target_os = "linux")] +#[derive(Debug)] +pub struct ModuleEntry { + pub path: PathBuf, + pub start_addr: Address, + pub data: Vec, + pub module_file_data: Vec, +} /// Represents a module loaded in a Windows process. pub struct Module<'a> { /// The name of the module. pub name: &'a str, /// A reference to a slice of bytes containing the module data. + #[cfg(target_os = "windows")] pub data: &'a [u8], - + // #[cfg(target_os = "linux")] + pub module_info: &'a ModuleEntry, + #[cfg(target_os = "windows")] /// The PE file format representation of the module. pub pe: PE<'a>, + + #[cfg(target_os = "linux")] + pub elf: Elf<'a>, } impl<'a> Module<'a> { @@ -31,6 +49,7 @@ impl<'a> Module<'a> { /// # Returns /// /// * `Result` - A `Result` containing a `Module` instance if successful, or an error if the module could not be parsed. + #[cfg(target_os = "windows")] pub fn parse(name: &'a str, data: &'a [u8]) -> Result { let pe = PE::parse_with_opts( data, @@ -43,6 +62,17 @@ impl<'a> Module<'a> { Ok(Self { name, data, pe }) } + // parse the elf + #[cfg(target_os = "linux")] + pub fn parse(name: &'a str, module_entry: &'a ModuleEntry) -> Result { + let elf = Elf::parse(&module_entry.module_file_data)?; + Ok(Self { + name, + module_info: module_entry, + elf, + }) + } + /// Returns the base address of the module. /// /// # Arguments @@ -53,10 +83,17 @@ impl<'a> Module<'a> { /// /// * `Address` - The base address of the module. #[inline] + #[cfg(target_os = "windows")] pub fn base(&self) -> Address { self.pe.image_base.into() } + #[inline] + #[cfg(target_os = "linux")] + pub fn base(&self) -> Address { + self.module_info.start_addr + } + /// Returns a slice of `Export` structs representing the exports of the module. /// /// # Arguments @@ -67,10 +104,23 @@ impl<'a> Module<'a> { /// /// * `&[Export]` - A slice of `Export` structs representing the exports of the module. #[inline] + #[cfg(target_os = "windows")] pub fn exports(&self) -> &'a [Export] { self.pe.exports.as_slice() } + #[inline] + #[cfg(target_os = "linux")] + pub fn exports(&self) -> Vec { + let exports: Vec = self + .elf + .dynsyms + .iter() + .filter(|sym| sym.st_bind() == sym::STB_GLOBAL || sym.st_bind() == sym::STB_WEAK) + .collect(); + exports + } + /// Returns the address of the export with the given name, if it exists. /// /// # Arguments @@ -81,6 +131,7 @@ impl<'a> Module<'a> { /// /// * `Option
` - The address of the export with the given name, if it exists. #[inline] + #[cfg(target_os = "windows")] pub fn get_export_by_name(&self, name: &str) -> Option
{ self.pe .exports @@ -89,6 +140,20 @@ impl<'a> Module<'a> { .map(|e| (self.pe.image_base + e.rva).into()) } + #[inline] + #[cfg(target_os = "linux")] + pub fn get_export_by_name(&self, name: &str) -> Option
{ + let base_addr: usize = self.base().into(); + self.elf + .dynsyms + .iter() + .find(|sym| { + (sym.st_bind() == sym::STB_GLOBAL || sym.st_bind() == sym::STB_WEAK) + && self.elf.dynstrtab.get_at(sym.st_name) == Some(name) + }) + .map(|sym| ((base_addr as u64 + sym.st_value) as usize).into()) + } + /// Returns the address of the imported function with the given name, if it exists. /// /// # Arguments @@ -99,6 +164,7 @@ impl<'a> Module<'a> { /// /// * `Option
` - The address of the imported function with the given name, if it exists. #[inline] + #[cfg(target_os = "windows")] pub fn get_import_by_name(&self, name: &str) -> Option
{ self.pe .imports @@ -107,6 +173,17 @@ impl<'a> Module<'a> { .map(|i| (self.pe.image_base + i.rva).into()) } + #[inline] + #[cfg(target_os = "linux")] + pub fn get_import_by_name(&self, name: &str) -> Option
{ + let base_addr: usize = self.base().into(); + self.elf + .dynsyms + .iter() + .find(|sym| sym.is_import() && self.elf.dynstrtab.get_at(sym.st_name) == Some(name)) + .map(|sym| ((base_addr as u64 + sym.st_value) as usize).into()) + } + /// Returns a slice of the imported functions of the module. /// /// # Arguments @@ -117,10 +194,23 @@ impl<'a> Module<'a> { /// /// * `&[Import]` - A slice of `Import` structs representing the imported functions of the module. #[inline] + #[cfg(target_os = "windows")] pub fn imports(&self) -> &'a [Import] { self.pe.imports.as_slice() } + #[inline] + #[cfg(target_os = "linux")] + pub fn imports(&self) -> Vec { + let imports: Vec = self + .elf + .dynsyms + .iter() + .filter(|sym| sym.is_import()) + .collect(); + imports + } + /// Returns a slice of the section table entries in the module's PE header. /// /// # Arguments @@ -131,10 +221,17 @@ impl<'a> Module<'a> { /// /// * `&[SectionTable]` - A slice of `SectionTable` structs representing the section table entries in the module's PE header. #[inline] + #[cfg(target_os = "windows")] pub fn sections(&self) -> &[SectionTable] { self.pe.sections.as_slice() } + #[inline] + #[cfg(target_os = "linux")] + pub fn sections(&self) -> &[SectionHeader] { + self.elf.section_headers.as_slice() + } + /// Returns the size of the module. /// /// # Arguments @@ -145,6 +242,7 @@ impl<'a> Module<'a> { /// /// * `u32` - The size of the module. #[inline] + #[cfg(target_os = "windows")] pub fn size(&self) -> u32 { self.pe .header diff --git a/src/util/process.rs b/src/util/process.rs index 5197ba5..bd4fb1e 100644 --- a/src/util/process.rs +++ b/src/util/process.rs @@ -1,4 +1,4 @@ -use super::{Address, Module}; +use super::{Address, Module, ModuleEntry}; use anyhow::{bail, Result}; @@ -32,7 +32,11 @@ pub struct Process { handle: HANDLE, /// A HashMap containing the name of each module and its corresponding raw data. + #[cfg(target_os = "windows")] modules: HashMap>, + + #[cfg(target_os = "linux")] + modules: HashMap, } impl Process { @@ -91,6 +95,7 @@ impl Process { /// # Returns /// /// * `Option
` - The address of the first occurrence of the pattern if found, or `None` if the pattern was not found. + #[cfg(target_os = "windows")] pub fn find_pattern(&self, module_name: &str, pattern: &str) -> Option
{ let module = self.get_module_by_name(module_name)?; @@ -109,6 +114,30 @@ impl Process { None } + #[cfg(target_os = "linux")] + pub fn find_pattern(&self, module_name: &str, pattern: &str) -> Option
{ + let module = self.get_module_by_name(module_name)?; + + let pattern_bytes = Self::pattern_to_bytes(pattern); + + for (i, window) in module + .module_info + .data + .windows(pattern_bytes.len()) + .enumerate() + { + if window + .iter() + .zip(&pattern_bytes) + .all(|(&x, &y)| x == y as u8 || y == -1) + { + return Some(module.base() + i); + } + } + + None + } + /// Returns an optional `Module` instance by its name. /// /// # Arguments @@ -122,7 +151,8 @@ impl Process { pub fn get_module_by_name<'a>(&'a self, name: &'a str) -> Option> { self.modules .get(name) - .map(|data| Module::parse(name, data).unwrap()) + .map(|entry| Module::parse(name, entry).unwrap()) + // Module::parse(name, self.modules.get_mut(name)?).ok() } /// Returns a vector of `Module` instances parsed from the process's loaded modules. @@ -137,8 +167,8 @@ impl Process { pub fn modules(&self) -> Result> { let mut modules = Vec::new(); - for (name, data) in &self.modules { - modules.push(Module::parse(name, data)?); + for (name, entry) in &self.modules { + modules.push(Module::parse(name, entry)?); } Ok(modules) @@ -418,6 +448,15 @@ impl Process { Ok(()) } + #[cfg(target_os = "linux")] + fn read_elf_file(path: &PathBuf) -> Result> { + let mut file = File::open(path)?; + let mut data = Vec::new(); + file.read_to_end(&mut data)?; + + Ok(data) + } + fn get_transformed_module_name(path: PathBuf) -> Option { if let Ok(module_path) = path.into_os_string().into_string() { if let Some(module_name) = module_path.split('/').last() { @@ -436,27 +475,35 @@ impl Process { fn parse_loaded_modules(&mut self) -> Result<()> { let process = process::Process::new(self.id as i32)?; - let mut modules_info: HashMap = HashMap::new(); + let mut modules_info: HashMap = HashMap::new(); for mmap in process.maps()? { let mmap_path = match mmap.pathname { process::MMapPath::Path(path) => path, _ => continue, }; - let module_name = match Process::get_transformed_module_name(mmap_path) { + let module_name = match Process::get_transformed_module_name(mmap_path.clone()) { Some(new_path) => new_path, None => continue, }; + if module_name != "client.dll" + && module_name != "engine2.dll" + && module_name != "inputsystem.dll" + && module_name != "matchmaking.dll" + && module_name != "schemasystem.dll" + { + continue; + } let module_entry = modules_info .entry(module_name) - .or_insert_with(|| (mmap.address)); - *module_entry = ( - std::cmp::min(mmap.address.0, module_entry.0), - std::cmp::max(mmap.address.1, module_entry.1), + .or_insert_with(|| (mmap.address, mmap_path)); + module_entry.0 = ( + std::cmp::min(mmap.address.0, module_entry.0 .0), + std::cmp::max(mmap.address.1, module_entry.0 .1), ); } - for (module_name, address_space) in modules_info.into_iter() { + for (module_name, (address_space, path)) in modules_info.into_iter() { let (start, end) = address_space; let mut data = vec![0; (end - start + 1) as usize]; if let Ok(_) = self.read_memory_raw( @@ -464,7 +511,15 @@ impl Process { data.as_mut_ptr() as *mut _, data.len(), ) { - self.modules.insert(module_name, data); + self.modules.insert( + module_name, + ModuleEntry { + path: path.clone(), + start_addr: (start as usize).into(), + data: data, + module_file_data: Process::read_elf_file(&path)?, + }, + ); } }