diff --git a/Cargo.lock b/Cargo.lock index 48d4ed5..abb8d9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -948,8 +948,6 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memflow" version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a164dd29bb697a512c389215acf4899a3e671a72844acb04d1ddac6ba8d7f" dependencies = [ "abi_stable", "bitflags 1.3.2", @@ -982,8 +980,6 @@ dependencies = [ [[package]] name = "memflow-derive" version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d894dc2b0bbce37b81280e1b36da4db3e524e4df5a57d5899b4bd943f489b506" dependencies = [ "darling", "proc-macro-crate", diff --git a/memflow-win32-defs/Cargo.toml b/memflow-win32-defs/Cargo.toml index 2014390..c1b3459 100644 --- a/memflow-win32-defs/Cargo.toml +++ b/memflow-win32-defs/Cargo.toml @@ -13,7 +13,9 @@ keywords = [ "memflow", "introspection", "memory", "dma" ] categories = [ "api-bindings", "memory-management", "os" ] [dependencies] -memflow = { version = "0.2", default-features = false } +#memflow = { version = "0.2", default-features = false } +memflow = { path = "../../memflow/memflow" } + log = { version = "0.4", default-features = false } no-std-compat = { version = "0.4", features = ["alloc"] } serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] } diff --git a/memflow-win32-defs/src/offsets/mod.rs b/memflow-win32-defs/src/offsets/mod.rs index 4238bdd..f43d5a1 100644 --- a/memflow-win32-defs/src/offsets/mod.rs +++ b/memflow-win32-defs/src/offsets/mod.rs @@ -35,15 +35,17 @@ use std::{fs::File, io::Read, path::Path}; #[repr(C)] #[cfg_attr(feature = "serde", derive(::serde::Serialize))] pub struct Win32ArchOffsets { - pub peb_ldr: usize, // _PEB::Ldr - pub peb_process_params: usize, // _PEB::ProcessParameters - pub ldr_list: usize, // _PEB_LDR_DATA::InLoadOrderModuleList - pub ldr_data_base: usize, // _LDR_DATA_TABLE_ENTRY::DllBase - pub ldr_data_size: usize, // _LDR_DATA_TABLE_ENTRY::SizeOfImage - pub ldr_data_full_name: usize, // _LDR_DATA_TABLE_ENTRY::FullDllName - pub ldr_data_base_name: usize, // _LDR_DATA_TABLE_ENTRY::BaseDllName - pub ppm_image_path_name: usize, // _RTL_USER_PROCESS_PARAMETERS::ImagePathName - pub ppm_command_line: usize, // _RTL_USER_PROCESS_PARAMETERS::CommandLine + pub peb_ldr: usize, // _PEB::Ldr + pub peb_process_params: usize, // _PEB::ProcessParameters + pub ldr_list: usize, // _PEB_LDR_DATA::InLoadOrderModuleList + pub ldr_data_base: usize, // _LDR_DATA_TABLE_ENTRY::DllBase + pub ldr_data_size: usize, // _LDR_DATA_TABLE_ENTRY::SizeOfImage + pub ldr_data_full_name: usize, // _LDR_DATA_TABLE_ENTRY::FullDllName + pub ldr_data_base_name: usize, // _LDR_DATA_TABLE_ENTRY::BaseDllName + pub ppm_image_path_name: usize, // _RTL_USER_PROCESS_PARAMETERS::ImagePathName + pub ppm_command_line: usize, // _RTL_USER_PROCESS_PARAMETERS::CommandLine + pub ppm_environment: usize, // _RTL_USER_PROCESS_PARAMETERS::Environment + pub ppm_environment_size: usize, // _RTL_USER_PROCESS_PARAMETERS::EnvironmentSize } pub const X86: Win32ArchOffsets = Win32ArchOffsets { @@ -56,6 +58,8 @@ pub const X86: Win32ArchOffsets = Win32ArchOffsets { ldr_data_base_name: 0x2c, ppm_image_path_name: 0x38, ppm_command_line: 0x40, + ppm_environment: 0x48, + ppm_environment_size: 0x290, }; pub const X64: Win32ArchOffsets = Win32ArchOffsets { @@ -68,6 +72,8 @@ pub const X64: Win32ArchOffsets = Win32ArchOffsets { ldr_data_base_name: 0x58, ppm_image_path_name: 0x60, ppm_command_line: 0x70, + ppm_environment: 0x80, + ppm_environment_size: 0x3f0, }; pub const AARCH64: Win32ArchOffsets = Win32ArchOffsets { @@ -80,6 +86,8 @@ pub const AARCH64: Win32ArchOffsets = Win32ArchOffsets { ldr_data_base_name: 0x58, ppm_image_path_name: 0x60, ppm_command_line: 0x70, + ppm_environment: 0x80, + ppm_environment_size: 0x3f0, }; impl Win32OffsetsArchitecture { diff --git a/memflow-win32/Cargo.toml b/memflow-win32/Cargo.toml index a59a38c..468cbce 100644 --- a/memflow-win32/Cargo.toml +++ b/memflow-win32/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "memflow-win32" version = "0.2.1" -authors = ["ko1N ", "Aurimas Blažulionis <0x60@pm.me>"] +authors = ["ko1N ", "Aurimas Blažulionis <0x60@pm.me>", "k1nd0ne "] edition = "2021" rust-version = "1.65" description = "win32 integration of the memflow physical memory introspection framework" @@ -21,7 +21,8 @@ codecov = { repository = "github", branch = "master", service = "github" } crate-type = ["lib", "cdylib"] [dependencies] -memflow = { version = "0.2", default-features = false } +#memflow = { version = "0.2", default-features = false } +memflow = { path = "../../memflow/memflow" } log = { version = "0.4", default-features = false } pelite = { version = "0.10", default-features = false } widestring = { version = "1.1", default-features = false, features = ["alloc"] } @@ -43,7 +44,8 @@ toml = "0.8" [build-dependencies] toml = "0.8" serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } -memflow = { version = "0.2", default-features = false } +#memflow = { version = "0.2", default-features = false } +memflow = { path = "../../memflow/memflow" } memflow-win32-defs = { version = "0.2", path = "../memflow-win32-defs", features = ["symstore"] } [features] diff --git a/memflow-win32/examples/envars_list.rs b/memflow-win32/examples/envars_list.rs new file mode 100644 index 0000000..7977909 --- /dev/null +++ b/memflow-win32/examples/envars_list.rs @@ -0,0 +1,104 @@ +/*! +This example shows how to use a dynamically loaded connector in conjunction +with memflow-win32. This example uses the `Inventory` feature of memflow +but hard-wires the connector instance into the memflow-win32 OS layer. + +The example is an adaption of the memflow core process list example: +https://github.com/memflow/memflow/blob/next/memflow/examples/process_list.rs + +# Usage: +```bash +cargo run --release --example envar_list -- -vv -c memraw:/path/to/memory.raw +``` +*/ +use clap::*; +use log::Level; + +use memflow::prelude::v1::*; +use memflow_win32::prelude::v1::*; + +pub fn main() -> Result<()> { + let matches = parse_args(); + let chain = extract_args(&matches)?; + + let mut inventory = Inventory::scan(); + let connector = inventory.builder().connector_chain(chain).build()?; + + let os = Win32Kernel::builder(connector) + .build_default_caches() + .build() + .unwrap(); + + let mut process = os + .into_process_by_name("explorer.exe") + .expect("unable to find process"); + println!("found process: {:?}", process.proc_info.base_info.name); + + println!( + "\nPID {:>5} | {:<} | sys={:?} proc={:?}", + process.proc_info.base_info.pid, + process.proc_info.base_info.name, + process.proc_info.base_info.sys_arch, + process.proc_info.base_info.proc_arch + ); + let envar_list = process + .envar_list() + .expect("unable to retrieve environment variables list"); + + println!(" VARIABLE | VALUE"); + + for ev in envar_list { + println!(" {}={}", ev.name.as_ref(), ev.value.as_ref()); + } + + Ok(()) +} + +fn parse_args() -> ArgMatches { + Command::new("envar_list example") + .version(crate_version!()) + .author(crate_authors!()) + .arg(Arg::new("verbose").short('v').action(ArgAction::Count)) + .arg( + Arg::new("connector") + .short('c') + .action(ArgAction::Append) + .required(true), + ) + .arg(Arg::new("os").short('o').action(ArgAction::Append)) + .get_matches() +} + +fn extract_args(matches: &ArgMatches) -> Result> { + let log_level = match matches.get_count("verbose") { + 0 => Level::Error, + 1 => Level::Warn, + 2 => Level::Info, + 3 => Level::Debug, + 4 => Level::Trace, + _ => Level::Trace, + }; + simplelog::TermLogger::init( + log_level.to_level_filter(), + simplelog::Config::default(), + simplelog::TerminalMode::Stdout, + simplelog::ColorChoice::Auto, + ) + .unwrap(); + + let conn_iter = matches + .indices_of("connector") + .zip(matches.get_many::("connector")) + .map(|(a, b)| a.zip(b.map(String::as_str))) + .into_iter() + .flatten(); + + let os_iter = matches + .indices_of("os") + .zip(matches.get_many::("os")) + .map(|(a, b)| a.zip(b.map(String::as_str))) + .into_iter() + .flatten(); + + ConnectorChain::new(conn_iter, os_iter) +} diff --git a/memflow-win32/src/win32.rs b/memflow-win32/src/win32.rs index dda70ca..aaa81cd 100644 --- a/memflow-win32/src/win32.rs +++ b/memflow-win32/src/win32.rs @@ -6,6 +6,7 @@ pub use kernel::Win32Kernel; pub use kernel_builder::Win32KernelBuilder; pub use kernel_info::Win32KernelInfo; +pub mod envars; pub mod keyboard; pub mod module; pub mod process; @@ -13,6 +14,7 @@ pub mod unicode_string; pub mod vat; pub mod vkey; +pub use envars::*; pub use keyboard::*; pub use module::*; pub use process::*; diff --git a/memflow-win32/src/win32/envars.rs b/memflow-win32/src/win32/envars.rs new file mode 100644 index 0000000..d9278cb --- /dev/null +++ b/memflow-win32/src/win32/envars.rs @@ -0,0 +1,109 @@ +use std::prelude::v1::*; + +use crate::offsets::Win32ArchOffsets; + +use log::trace; + +use memflow::architecture::ArchitectureIdent; +use memflow::error::Result; +use memflow::mem::MemoryView; +use memflow::os::util::env_block_list_utf16_callback; +use memflow::os::{EnvVarCallback, EnvVarInfo}; +use memflow::types::{umem, Address}; + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize))] +pub struct Win32EnvListInfo { + env_block: Address, + env_size: umem, + offsets: Win32ArchOffsets, + proc_params: Address, +} + +impl Win32EnvListInfo { + /// Construct from a PEB: reads _PEB::ProcessParameters and + /// _RTL_USER_PROCESS_PARAMETERS::Environment (PWSTR) and + /// _RTL_USER_PROCESS_PARAMETERS::EnvironmentSize. + + pub fn with_peb( + mem: &mut impl MemoryView, + peb: Address, + arch: ArchitectureIdent, + ) -> Result { + let offsets = Win32ArchOffsets::from(arch); + let arch_obj = arch.into(); + + trace!("peb_process_params_offs={:x}", offsets.peb_process_params); + let proc_params = mem.read_addr_arch(arch_obj, peb + offsets.peb_process_params)?; + trace!("ProcessParameters={:x}", proc_params); + + trace!("ppm_environment_offs={:x}", offsets.ppm_environment); + let env_block = mem.read_addr_arch(arch_obj, proc_params + offsets.ppm_environment)?; + trace!("Environment={:x}", env_block); + + trace!( + "ppm_environment_size_offs={:x}", + offsets.ppm_environment_size + ); + let size_addr = proc_params + offsets.ppm_environment_size; + + let env_size: umem = mem.read(size_addr)?; + + trace!("EnvironmentSize={:x}", env_size); + Ok(Self { + env_block, + env_size, + offsets, + proc_params, + }) + } + + /// Construct directly from a known Environment pointer and size. + pub fn with_base(env_block: Address, env_size: umem, arch: ArchitectureIdent) -> Result { + let offsets = Win32ArchOffsets::from(arch); + trace!( + "env_block={:x} offsets={:?} size={:?}", + env_block, + offsets, + env_size + ); + Ok(Self { + env_block, + env_size, + offsets, + proc_params: Address::NULL, + }) + } + + #[inline] + pub fn env_block(&self) -> Address { + self.env_block + } + + /// Collect all environment variables into a Vec. + pub fn envar_list( + &self, + mem: &mut impl AsMut, + arch: ArchitectureIdent, + ) -> Result> { + let mut out = vec![]; + self.envar_list_callback(mem, arch, (&mut out).into())?; + Ok(out) + } + + /// Enumerate environment variables via callback (UTF-16LE multi-string). + /// + /// # Arguments + /// * `mem` - memory view in process context + /// * `arch` - view architecture (native or WOW64) + /// * `callback` - receives each EnvVarInfo; return `true` to continue, `false` to stop. + pub fn envar_list_callback, V: MemoryView>( + &self, + mem: &mut M, + arch: ArchitectureIdent, + callback: EnvVarCallback, + ) -> Result<()> { + env_block_list_utf16_callback(mem.as_mut(), self.env_block, self.env_size, arch, callback) + } +} diff --git a/memflow-win32/src/win32/kernel.rs b/memflow-win32/src/win32/kernel.rs index 2c03df5..9752153 100644 --- a/memflow-win32/src/win32/kernel.rs +++ b/memflow-win32/src/win32/kernel.rs @@ -6,8 +6,8 @@ use crate::{ }; use super::{ - process::IMAGE_FILE_NAME_LENGTH, Win32KernelBuilder, Win32KernelInfo, Win32Keyboard, - Win32ModuleListInfo, Win32Process, Win32ProcessInfo, Win32VirtualTranslate, + process::IMAGE_FILE_NAME_LENGTH, Win32EnvListInfo, Win32KernelBuilder, Win32KernelInfo, + Win32Keyboard, Win32ModuleListInfo, Win32Process, Win32ProcessInfo, Win32VirtualTranslate, }; use memflow::mem::virt_translate::*; @@ -207,6 +207,9 @@ impl, pub module_info_wow64: Option, + //envars + pub env_info_native: Option, + pub env_info_wow64: Option, + // memory pub vad_root: Address, } @@ -311,6 +315,110 @@ impl Process memflow::os::util::module_section_list_callback(&mut self.virt_mem, info, callback) } + /// Walks the process' environment and calls the provided callback for each variable + /// + /// # Arguments + /// * `target_arch` - sets which architecture to retrieve the environment for (if emulated). Choose + /// between `Some(ProcessInfo::sys_arch())`, and `Some(ProcessInfo::proc_arch())`. `None` for all. + /// * `callback` - where to pass each variable to. This is an opaque callback. + fn envar_list_callback( + &mut self, + target_arch: Option<&ArchitectureIdent>, + mut callback: EnvVarCallback, + ) -> Result<()> { + let infos = [ + ( + self.proc_info.env_info_native, + self.proc_info.base_info.sys_arch, + ), + ( + self.proc_info.env_info_wow64, + self.proc_info.base_info.proc_arch, + ), + ]; + + let iter = infos + .into_iter() + .filter(|(_, a)| target_arch.map(|ta| *a == *ta).unwrap_or(true)) + .filter_map(|(info, arch)| info.zip(Some(arch))); + + self.envar_list_with_infos_callback(iter, &mut callback) + } + + /// Retrieves address of the process' environment block for the given architecture + /// + /// # Remarks + /// + /// On Windows the environment is the UTF-16LE multi-string at + /// PEB->ProcessParameters->Environment (or the 32-bit PEB for WOW64). + fn environment_block_address(&mut self, architecture: ArchitectureIdent) -> Result
{ + // Select the correct PEB for the requested view (native vs WOW64) + let peb = if architecture == self.proc_info.base_info.sys_arch { + self.proc_info.peb_native + } else { + self.proc_info.peb_wow64 + } + .ok_or(Error(ErrorOrigin::OsLayer, ErrorKind::EnvarNotFound))?; + + // Arch-specific offsets + let aoff = crate::offsets::Win32ArchOffsets::from(architecture); + let arch_obj = architecture.into(); + + // PEB->ProcessParameters + let proc_params = self + .virt_mem + .read_addr_arch(arch_obj, peb + aoff.peb_process_params)?; + if proc_params.is_null() { + return Err(Error(ErrorOrigin::OsLayer, ErrorKind::EnvarNotFound)); + } + + // ProcessParameters->Environment (PWSTR) + let env_ptr = self + .virt_mem + .read_addr_arch(arch_obj, proc_params + aoff.ppm_environment)?; + if env_ptr.is_null() { + return Err(Error(ErrorOrigin::OsLayer, ErrorKind::EnvarNotFound)); + } + + Ok(env_ptr) + } + + /// Enumerates environment variables starting from a known environment block address + /// + /// # Arguments + /// * `env_block` - base address of the environment block + /// * `architecture` - architecture of the environment + /// * `callback` - where to pass each variable to. This is an opaque callback. + fn envar_list_from_address( + &mut self, + env_block: Address, + architecture: ArchitectureIdent, + callback: EnvVarCallback, + ) -> Result<()> { + // Compute a safe upper bound for the environment block. + let ranges = self.mapped_mem_range_vec(0, Address::null(), Address::invalid()); + let mut max_bytes: umem = 256 * 1024; // default cap + + for r in ranges { + // r.0 = base, r.1 = size + let region_end = r.0 + r.1; + if env_block >= r.0 && env_block < region_end { + let span = region_end.to_umem().saturating_sub(env_block.to_umem()); + max_bytes = core::cmp::min(span, (1usize << 20) as umem); + break; + } + } + + // Windows environment is UTF-16LE multi-string. + memflow::os::util::env_block_list_utf16_callback( + &mut self.virt_mem, + env_block, + max_bytes, + architecture, + callback, + ) + } + /// Retrieves the process info fn info(&self) -> &ProcessInfo { &self.proc_info.base_info @@ -492,6 +600,18 @@ impl Win32Process } Ok(()) } + + fn envar_list_with_infos_callback( + &mut self, + env_infos: impl Iterator, + out: &mut EnvVarCallback, + ) -> Result<()> { + for (info, arch) in env_infos { + let cb = &mut |ev: EnvVarInfo| out.call(ev); + info.envar_list_callback(self, arch, cb.into())?; + } + Ok(()) + } } impl fmt::Debug for Win32Process {