Parse the modules as elf files

This commit is contained in:
Albert24GG 2024-02-27 23:29:30 +02:00
parent 75492a30d6
commit f2f607c7ac
3 changed files with 167 additions and 13 deletions

View File

@ -1,5 +1,6 @@
pub use address::Address; pub use address::Address;
pub use module::Module; pub use module::Module;
pub use module::ModuleEntry;
pub use process::Process; pub use process::Process;
pub mod address; pub mod address;

View File

@ -8,16 +8,34 @@ use goblin::pe::options::ParseOptions;
use goblin::pe::section_table::SectionTable; use goblin::pe::section_table::SectionTable;
use goblin::pe::PE; 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<u8>,
pub module_file_data: Vec<u8>,
}
/// Represents a module loaded in a Windows process. /// Represents a module loaded in a Windows process.
pub struct Module<'a> { pub struct Module<'a> {
/// The name of the module. /// The name of the module.
pub name: &'a str, pub name: &'a str,
/// A reference to a slice of bytes containing the module data. /// A reference to a slice of bytes containing the module data.
#[cfg(target_os = "windows")]
pub data: &'a [u8], 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. /// The PE file format representation of the module.
pub pe: PE<'a>, pub pe: PE<'a>,
#[cfg(target_os = "linux")]
pub elf: Elf<'a>,
} }
impl<'a> Module<'a> { impl<'a> Module<'a> {
@ -31,6 +49,7 @@ impl<'a> Module<'a> {
/// # Returns /// # Returns
/// ///
/// * `Result<Self>` - A `Result` containing a `Module` instance if successful, or an error if the module could not be parsed. /// * `Result<Self>` - 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<Self> { pub fn parse(name: &'a str, data: &'a [u8]) -> Result<Self> {
let pe = PE::parse_with_opts( let pe = PE::parse_with_opts(
data, data,
@ -43,6 +62,17 @@ impl<'a> Module<'a> {
Ok(Self { name, data, pe }) Ok(Self { name, data, pe })
} }
// parse the elf
#[cfg(target_os = "linux")]
pub fn parse(name: &'a str, module_entry: &'a ModuleEntry) -> Result<Self> {
let elf = Elf::parse(&module_entry.module_file_data)?;
Ok(Self {
name,
module_info: module_entry,
elf,
})
}
/// Returns the base address of the module. /// Returns the base address of the module.
/// ///
/// # Arguments /// # Arguments
@ -53,10 +83,17 @@ impl<'a> Module<'a> {
/// ///
/// * `Address` - The base address of the module. /// * `Address` - The base address of the module.
#[inline] #[inline]
#[cfg(target_os = "windows")]
pub fn base(&self) -> Address { pub fn base(&self) -> Address {
self.pe.image_base.into() 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. /// Returns a slice of `Export` structs representing the exports of the module.
/// ///
/// # Arguments /// # Arguments
@ -67,10 +104,23 @@ impl<'a> Module<'a> {
/// ///
/// * `&[Export]` - A slice of `Export` structs representing the exports of the module. /// * `&[Export]` - A slice of `Export` structs representing the exports of the module.
#[inline] #[inline]
#[cfg(target_os = "windows")]
pub fn exports(&self) -> &'a [Export] { pub fn exports(&self) -> &'a [Export] {
self.pe.exports.as_slice() self.pe.exports.as_slice()
} }
#[inline]
#[cfg(target_os = "linux")]
pub fn exports(&self) -> Vec<sym::Sym> {
let exports: Vec<sym::Sym> = 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. /// Returns the address of the export with the given name, if it exists.
/// ///
/// # Arguments /// # Arguments
@ -81,6 +131,7 @@ impl<'a> Module<'a> {
/// ///
/// * `Option<Address>` - The address of the export with the given name, if it exists. /// * `Option<Address>` - The address of the export with the given name, if it exists.
#[inline] #[inline]
#[cfg(target_os = "windows")]
pub fn get_export_by_name(&self, name: &str) -> Option<Address> { pub fn get_export_by_name(&self, name: &str) -> Option<Address> {
self.pe self.pe
.exports .exports
@ -89,6 +140,20 @@ impl<'a> Module<'a> {
.map(|e| (self.pe.image_base + e.rva).into()) .map(|e| (self.pe.image_base + e.rva).into())
} }
#[inline]
#[cfg(target_os = "linux")]
pub fn get_export_by_name(&self, name: &str) -> Option<Address> {
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. /// Returns the address of the imported function with the given name, if it exists.
/// ///
/// # Arguments /// # Arguments
@ -99,6 +164,7 @@ impl<'a> Module<'a> {
/// ///
/// * `Option<Address>` - The address of the imported function with the given name, if it exists. /// * `Option<Address>` - The address of the imported function with the given name, if it exists.
#[inline] #[inline]
#[cfg(target_os = "windows")]
pub fn get_import_by_name(&self, name: &str) -> Option<Address> { pub fn get_import_by_name(&self, name: &str) -> Option<Address> {
self.pe self.pe
.imports .imports
@ -107,6 +173,17 @@ impl<'a> Module<'a> {
.map(|i| (self.pe.image_base + i.rva).into()) .map(|i| (self.pe.image_base + i.rva).into())
} }
#[inline]
#[cfg(target_os = "linux")]
pub fn get_import_by_name(&self, name: &str) -> Option<Address> {
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. /// Returns a slice of the imported functions of the module.
/// ///
/// # Arguments /// # Arguments
@ -117,10 +194,23 @@ impl<'a> Module<'a> {
/// ///
/// * `&[Import]` - A slice of `Import` structs representing the imported functions of the module. /// * `&[Import]` - A slice of `Import` structs representing the imported functions of the module.
#[inline] #[inline]
#[cfg(target_os = "windows")]
pub fn imports(&self) -> &'a [Import] { pub fn imports(&self) -> &'a [Import] {
self.pe.imports.as_slice() self.pe.imports.as_slice()
} }
#[inline]
#[cfg(target_os = "linux")]
pub fn imports(&self) -> Vec<sym::Sym> {
let imports: Vec<sym::Sym> = 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. /// Returns a slice of the section table entries in the module's PE header.
/// ///
/// # Arguments /// # 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. /// * `&[SectionTable]` - A slice of `SectionTable` structs representing the section table entries in the module's PE header.
#[inline] #[inline]
#[cfg(target_os = "windows")]
pub fn sections(&self) -> &[SectionTable] { pub fn sections(&self) -> &[SectionTable] {
self.pe.sections.as_slice() 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. /// Returns the size of the module.
/// ///
/// # Arguments /// # Arguments
@ -145,6 +242,7 @@ impl<'a> Module<'a> {
/// ///
/// * `u32` - The size of the module. /// * `u32` - The size of the module.
#[inline] #[inline]
#[cfg(target_os = "windows")]
pub fn size(&self) -> u32 { pub fn size(&self) -> u32 {
self.pe self.pe
.header .header

View File

@ -1,4 +1,4 @@
use super::{Address, Module}; use super::{Address, Module, ModuleEntry};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
@ -32,7 +32,11 @@ pub struct Process {
handle: HANDLE, handle: HANDLE,
/// A HashMap containing the name of each module and its corresponding raw data. /// A HashMap containing the name of each module and its corresponding raw data.
#[cfg(target_os = "windows")]
modules: HashMap<String, Vec<u8>>, modules: HashMap<String, Vec<u8>>,
#[cfg(target_os = "linux")]
modules: HashMap<String, ModuleEntry>,
} }
impl Process { impl Process {
@ -91,6 +95,7 @@ impl Process {
/// # Returns /// # Returns
/// ///
/// * `Option<Address>` - The address of the first occurrence of the pattern if found, or `None` if the pattern was not found. /// * `Option<Address>` - 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<Address> { pub fn find_pattern(&self, module_name: &str, pattern: &str) -> Option<Address> {
let module = self.get_module_by_name(module_name)?; let module = self.get_module_by_name(module_name)?;
@ -109,6 +114,30 @@ impl Process {
None None
} }
#[cfg(target_os = "linux")]
pub fn find_pattern(&self, module_name: &str, pattern: &str) -> Option<Address> {
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. /// Returns an optional `Module` instance by its name.
/// ///
/// # Arguments /// # Arguments
@ -122,7 +151,8 @@ impl Process {
pub fn get_module_by_name<'a>(&'a self, name: &'a str) -> Option<Module<'a>> { pub fn get_module_by_name<'a>(&'a self, name: &'a str) -> Option<Module<'a>> {
self.modules self.modules
.get(name) .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. /// Returns a vector of `Module` instances parsed from the process's loaded modules.
@ -137,8 +167,8 @@ impl Process {
pub fn modules(&self) -> Result<Vec<Module>> { pub fn modules(&self) -> Result<Vec<Module>> {
let mut modules = Vec::new(); let mut modules = Vec::new();
for (name, data) in &self.modules { for (name, entry) in &self.modules {
modules.push(Module::parse(name, data)?); modules.push(Module::parse(name, entry)?);
} }
Ok(modules) Ok(modules)
@ -418,6 +448,15 @@ impl Process {
Ok(()) Ok(())
} }
#[cfg(target_os = "linux")]
fn read_elf_file(path: &PathBuf) -> Result<Vec<u8>> {
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<String> { fn get_transformed_module_name(path: PathBuf) -> Option<String> {
if let Ok(module_path) = path.into_os_string().into_string() { if let Ok(module_path) = path.into_os_string().into_string() {
if let Some(module_name) = module_path.split('/').last() { if let Some(module_name) = module_path.split('/').last() {
@ -436,27 +475,35 @@ impl Process {
fn parse_loaded_modules(&mut self) -> Result<()> { fn parse_loaded_modules(&mut self) -> Result<()> {
let process = process::Process::new(self.id as i32)?; let process = process::Process::new(self.id as i32)?;
let mut modules_info: HashMap<String, (u64, u64)> = HashMap::new(); let mut modules_info: HashMap<String, ((u64, u64), PathBuf)> = HashMap::new();
for mmap in process.maps()? { for mmap in process.maps()? {
let mmap_path = match mmap.pathname { let mmap_path = match mmap.pathname {
process::MMapPath::Path(path) => path, process::MMapPath::Path(path) => path,
_ => continue, _ => 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, Some(new_path) => new_path,
None => continue, 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 let module_entry = modules_info
.entry(module_name) .entry(module_name)
.or_insert_with(|| (mmap.address)); .or_insert_with(|| (mmap.address, mmap_path));
*module_entry = ( module_entry.0 = (
std::cmp::min(mmap.address.0, module_entry.0), std::cmp::min(mmap.address.0, module_entry.0 .0),
std::cmp::max(mmap.address.1, module_entry.1), 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 (start, end) = address_space;
let mut data = vec![0; (end - start + 1) as usize]; let mut data = vec![0; (end - start + 1) as usize];
if let Ok(_) = self.read_memory_raw( if let Ok(_) = self.read_memory_raw(
@ -464,7 +511,15 @@ impl Process {
data.as_mut_ptr() as *mut _, data.as_mut_ptr() as *mut _,
data.len(), 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)?,
},
);
} }
} }