use std::{collections::HashMap, ops::Range, path::PathBuf};
use crate::{emulation::tracer::TraceFilter, metadata::typesystem::PointerSize};
#[derive(Clone, Debug)]
pub struct EmulationConfig {
pub limits: EmulationLimits,
pub unknown_method: UnknownMethodBehavior,
pub symbolic_tracking: bool,
pub threading_enabled: bool,
pub thread_quantum: usize,
pub max_threads: usize,
pub exception_handling: bool,
pub allow_unwind: bool,
pub pointer_size: PointerSize,
pub memory: MemoryConfig,
pub stubs: StubConfig,
pub tracing: TracingConfig,
pub environment: EnvironmentConfig,
}
#[derive(Clone, Debug)]
pub struct EmulationLimits {
pub max_instructions: u64,
pub max_call_depth: usize,
pub max_heap_objects: usize,
pub max_heap_bytes: usize,
pub max_unmanaged_bytes: usize,
pub timeout_ms: u64,
}
#[derive(Clone, Debug)]
pub struct MemoryConfig {
pub initial_heap_size: usize,
pub max_heap_size: usize,
pub stack_size: usize,
pub address_space_bits: u8,
pub track_access: bool,
pub zero_init: bool,
}
#[derive(Clone, Debug)]
pub struct StubConfig {
pub bcl_stubs: bool,
pub pinvoke_stubs: bool,
pub reflection_stubs: bool,
pub crypto_stubs: bool,
pub io_stubs: bool,
pub threading_stubs: bool,
pub strict_mode: bool,
}
#[derive(Clone, Debug)]
pub struct TracingConfig {
pub trace_instructions: bool,
pub trace_calls: bool,
pub trace_heap: bool,
pub trace_exceptions: bool,
pub trace_stubs: bool,
pub trace_stack_values: bool,
pub trace_array_ops: bool,
pub max_trace_entries: usize,
pub output_path: Option<PathBuf>,
pub context_prefix: Option<String>,
pub filter: TraceFilter,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum UnknownMethodBehavior {
#[default]
Emulate,
Symbolic,
Fail,
Default,
Skip,
}
#[derive(Clone, Debug, Default)]
pub struct CaptureConfig {
pub assemblies: bool,
pub memory_regions: Vec<Range<u64>>,
pub strings: bool,
pub file_operations: bool,
pub network_operations: bool,
}
#[derive(Clone, Debug)]
pub struct EnvironmentConfig {
pub os_version: String,
pub processor_count: i32,
pub is_64bit_os: bool,
pub is_64bit_process: bool,
pub user_name: String,
pub machine_name: String,
pub current_directory: String,
pub tick_count_base: i64,
pub tick_count_divisor: u64,
pub environment_variables: HashMap<String, String>,
pub folder_paths: HashMap<i32, String>,
pub module_base_path: String,
pub assembly_location_base: String,
}
impl Default for EnvironmentConfig {
fn default() -> Self {
let mut folder_paths = HashMap::new();
folder_paths.insert(0, "C:\\Users\\user\\Desktop".to_string());
folder_paths.insert(26, "C:\\Users\\user\\AppData\\Roaming".to_string());
folder_paths.insert(28, "C:\\Users\\user\\AppData\\Local".to_string());
folder_paths.insert(35, "C:\\ProgramData".to_string());
folder_paths.insert(37, "C:\\Windows".to_string());
folder_paths.insert(41, "C:\\Program Files".to_string());
Self {
os_version: "10.0.19041.0".to_string(),
processor_count: 4,
is_64bit_os: true,
is_64bit_process: true,
user_name: "user".to_string(),
machine_name: "DESKTOP-PC".to_string(),
current_directory: "C:\\Users\\user\\Desktop".to_string(),
tick_count_base: 300_000,
tick_count_divisor: 100,
environment_variables: HashMap::new(),
folder_paths,
module_base_path: "C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319".to_string(),
assembly_location_base: "C:\\Users\\user\\AppData\\Local\\Temp".to_string(),
}
}
}
impl Default for EmulationConfig {
fn default() -> Self {
Self {
limits: EmulationLimits::default(),
unknown_method: UnknownMethodBehavior::Symbolic,
symbolic_tracking: false,
threading_enabled: true,
thread_quantum: 1000,
max_threads: 16,
exception_handling: true,
allow_unwind: true,
pointer_size: PointerSize::Bit64,
memory: MemoryConfig::default(),
stubs: StubConfig::default(),
tracing: TracingConfig::default(),
environment: EnvironmentConfig::default(),
}
}
}
impl Default for EmulationLimits {
fn default() -> Self {
Self {
max_instructions: 10_000_000,
max_call_depth: 1000,
max_heap_objects: 100_000,
max_heap_bytes: 256 * 1024 * 1024, max_unmanaged_bytes: 64 * 1024 * 1024, timeout_ms: 60_000, }
}
}
impl Default for MemoryConfig {
fn default() -> Self {
Self {
initial_heap_size: 16 * 1024 * 1024, max_heap_size: 256 * 1024 * 1024, stack_size: 1024 * 1024, address_space_bits: 32,
track_access: false,
zero_init: true,
}
}
}
impl Default for StubConfig {
fn default() -> Self {
Self {
bcl_stubs: true,
pinvoke_stubs: true,
reflection_stubs: true,
crypto_stubs: true,
io_stubs: true,
threading_stubs: true,
strict_mode: false,
}
}
}
impl TracingConfig {
#[must_use]
pub fn full_trace<P: Into<PathBuf>>(path: P) -> Self {
Self {
trace_instructions: true,
trace_calls: true,
trace_heap: true,
trace_exceptions: true,
trace_stubs: true,
trace_stack_values: true,
trace_array_ops: true,
max_trace_entries: 0, output_path: Some(path.into()),
context_prefix: None,
filter: TraceFilter::default(),
}
}
#[must_use]
pub fn call_trace<P: Into<PathBuf>>(path: P) -> Self {
Self {
trace_instructions: false,
trace_calls: true,
trace_heap: false,
trace_exceptions: true,
trace_stubs: true,
trace_stack_values: false,
trace_array_ops: false,
max_trace_entries: 0,
output_path: Some(path.into()),
context_prefix: None,
filter: TraceFilter::default(),
}
}
#[must_use]
pub fn with_context(mut self, prefix: impl Into<String>) -> Self {
self.context_prefix = Some(prefix.into());
self
}
#[must_use]
pub fn is_enabled(&self) -> bool {
self.trace_instructions
|| self.trace_calls
|| self.trace_heap
|| self.trace_exceptions
|| self.trace_stubs
|| self.trace_array_ops
}
#[must_use]
pub fn has_output_file(&self) -> bool {
self.output_path.is_some()
}
}
impl Default for TracingConfig {
fn default() -> Self {
Self {
trace_instructions: false,
trace_calls: false,
trace_heap: false,
trace_exceptions: true,
trace_stubs: false,
trace_stack_values: false,
trace_array_ops: false,
max_trace_entries: 10_000,
output_path: None,
context_prefix: None,
filter: TraceFilter::default(),
}
}
}
impl EmulationConfig {
#[must_use]
pub fn extraction() -> Self {
Self {
limits: EmulationLimits {
max_instructions: 50_000_000,
..Default::default()
},
unknown_method: UnknownMethodBehavior::Default,
symbolic_tracking: false,
threading_enabled: true,
exception_handling: true,
stubs: StubConfig {
strict_mode: false,
..Default::default()
},
..Default::default()
}
}
#[must_use]
pub fn analysis() -> Self {
Self {
limits: EmulationLimits {
max_instructions: 1_000_000,
..Default::default()
},
unknown_method: UnknownMethodBehavior::Symbolic,
symbolic_tracking: true,
threading_enabled: false,
exception_handling: false,
stubs: StubConfig {
strict_mode: false,
..Default::default()
},
..Default::default()
}
}
#[must_use]
pub fn full() -> Self {
Self {
limits: EmulationLimits {
max_instructions: 100_000_000,
timeout_ms: 300_000, ..Default::default()
},
unknown_method: UnknownMethodBehavior::Fail,
symbolic_tracking: false,
threading_enabled: true,
exception_handling: true,
stubs: StubConfig {
strict_mode: true,
..Default::default()
},
tracing: TracingConfig {
trace_exceptions: true,
..Default::default()
},
..Default::default()
}
}
#[must_use]
pub fn minimal() -> Self {
Self {
limits: EmulationLimits {
max_instructions: 10_000,
max_call_depth: 10,
..Default::default()
},
unknown_method: UnknownMethodBehavior::Skip,
symbolic_tracking: false,
threading_enabled: false,
exception_handling: false,
stubs: StubConfig {
bcl_stubs: true,
pinvoke_stubs: false,
reflection_stubs: false,
crypto_stubs: false,
io_stubs: false,
threading_stubs: false,
strict_mode: false,
},
..Default::default()
}
}
}
impl EmulationLimits {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_max_instructions(mut self, max: u64) -> Self {
self.max_instructions = max;
self
}
#[must_use]
pub fn with_max_call_depth(mut self, max: usize) -> Self {
self.max_call_depth = max;
self
}
#[must_use]
pub fn with_max_heap_objects(mut self, max: usize) -> Self {
self.max_heap_objects = max;
self
}
#[must_use]
pub fn with_max_heap_bytes(mut self, max: usize) -> Self {
self.max_heap_bytes = max;
self
}
#[must_use]
pub fn with_timeout_ms(mut self, ms: u64) -> Self {
self.timeout_ms = ms;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = EmulationConfig::default();
assert_eq!(config.limits.max_instructions, 10_000_000);
assert!(config.threading_enabled);
assert!(config.exception_handling);
}
#[test]
fn test_extraction_preset() {
let config = EmulationConfig::extraction();
assert_eq!(config.limits.max_instructions, 50_000_000);
assert_eq!(config.unknown_method, UnknownMethodBehavior::Default);
}
#[test]
fn test_analysis_preset() {
let config = EmulationConfig::analysis();
assert!(config.symbolic_tracking);
assert!(!config.threading_enabled);
}
#[test]
fn test_full_preset() {
let config = EmulationConfig::full();
assert_eq!(config.unknown_method, UnknownMethodBehavior::Fail);
assert!(config.stubs.strict_mode);
}
#[test]
fn test_minimal_preset() {
let config = EmulationConfig::minimal();
assert_eq!(config.limits.max_instructions, 10_000);
assert!(!config.stubs.pinvoke_stubs);
}
#[test]
fn test_limits_builder() {
let limits = EmulationLimits::new()
.with_max_instructions(5000)
.with_max_call_depth(50)
.with_timeout_ms(30_000);
assert_eq!(limits.max_instructions, 5000);
assert_eq!(limits.max_call_depth, 50);
assert_eq!(limits.timeout_ms, 30_000);
}
}