356 lines
12 KiB
Rust
356 lines
12 KiB
Rust
//! Integration tests for Ghost detection engine.
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use ghost_core::{
|
|
config::DetectionConfig, DetectionEngine, MemoryProtection, MemoryRegion, ProcessInfo,
|
|
ThreatLevel,
|
|
};
|
|
|
|
fn create_test_process() -> ProcessInfo {
|
|
ProcessInfo {
|
|
pid: 1234,
|
|
ppid: 4,
|
|
name: "test.exe".to_string(),
|
|
path: Some("C:\\Windows\\System32\\test.exe".to_string()),
|
|
thread_count: 1,
|
|
}
|
|
}
|
|
|
|
fn create_rwx_region() -> MemoryRegion {
|
|
MemoryRegion {
|
|
base_address: 0x10000000,
|
|
size: 0x1000,
|
|
protection: MemoryProtection::ReadWriteExecute,
|
|
region_type: "PRIVATE".to_string(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_clean_process_detection() {
|
|
let mut engine = DetectionEngine::new().expect("Failed to create engine");
|
|
let process = create_test_process();
|
|
let regions = vec![MemoryRegion {
|
|
base_address: 0x400000,
|
|
size: 0x10000,
|
|
protection: MemoryProtection::ReadExecute,
|
|
region_type: "IMAGE".to_string(),
|
|
}];
|
|
|
|
let result = engine.analyze_process(&process, ®ions, None);
|
|
assert_eq!(result.threat_level, ThreatLevel::Clean);
|
|
assert!(result.indicators.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_rwx_region_detection() {
|
|
let mut engine = DetectionEngine::new().expect("Failed to create engine");
|
|
let process = create_test_process();
|
|
let regions = vec![create_rwx_region()];
|
|
|
|
let result = engine.analyze_process(&process, ®ions, None);
|
|
assert_ne!(result.threat_level, ThreatLevel::Clean);
|
|
assert!(!result.indicators.is_empty());
|
|
assert!(result.indicators[0].contains("RWX"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_small_executable_regions() {
|
|
let mut engine = DetectionEngine::new().expect("Failed to create engine");
|
|
let process = create_test_process();
|
|
let regions = vec![
|
|
MemoryRegion {
|
|
base_address: 0x10000000,
|
|
size: 0x800, // Small size
|
|
protection: MemoryProtection::ReadExecute,
|
|
region_type: "PRIVATE".to_string(),
|
|
},
|
|
MemoryRegion {
|
|
base_address: 0x20000000,
|
|
size: 0x600, // Small size
|
|
protection: MemoryProtection::ReadExecute,
|
|
region_type: "PRIVATE".to_string(),
|
|
},
|
|
MemoryRegion {
|
|
base_address: 0x30000000,
|
|
size: 0x400, // Small size
|
|
protection: MemoryProtection::ReadExecute,
|
|
region_type: "PRIVATE".to_string(),
|
|
},
|
|
];
|
|
|
|
let result = engine.analyze_process(&process, ®ions, None);
|
|
assert!(result.confidence > 0.0);
|
|
assert!(result
|
|
.indicators
|
|
.iter()
|
|
.any(|i| i.contains("small executable")));
|
|
}
|
|
|
|
#[test]
|
|
fn test_baseline_tracking() {
|
|
let mut engine = DetectionEngine::new().expect("Failed to create engine");
|
|
let mut process = create_test_process();
|
|
let regions = vec![];
|
|
|
|
// First scan establishes baseline
|
|
let result1 = engine.analyze_process(&process, ®ions, None);
|
|
assert_eq!(result1.threat_level, ThreatLevel::Clean);
|
|
|
|
// Second scan with increased thread count
|
|
process.thread_count = 5;
|
|
let result2 = engine.analyze_process(&process, ®ions, None);
|
|
assert!(result2
|
|
.indicators
|
|
.iter()
|
|
.any(|i| i.contains("new threads")));
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_rwx_regions_high_severity() {
|
|
let mut engine = DetectionEngine::new().expect("Failed to create engine");
|
|
let process = create_test_process();
|
|
let regions = vec![
|
|
MemoryRegion {
|
|
base_address: 0x10000000,
|
|
size: 0x1000,
|
|
protection: MemoryProtection::ReadWriteExecute,
|
|
region_type: "PRIVATE".to_string(),
|
|
},
|
|
MemoryRegion {
|
|
base_address: 0x20000000,
|
|
size: 0x2000,
|
|
protection: MemoryProtection::ReadWriteExecute,
|
|
region_type: "PRIVATE".to_string(),
|
|
},
|
|
MemoryRegion {
|
|
base_address: 0x30000000,
|
|
size: 0x3000,
|
|
protection: MemoryProtection::ReadWriteExecute,
|
|
region_type: "PRIVATE".to_string(),
|
|
},
|
|
];
|
|
|
|
let result = engine.analyze_process(&process, ®ions, None);
|
|
// Multiple RWX regions should be highly suspicious
|
|
assert_eq!(result.threat_level, ThreatLevel::Malicious);
|
|
assert!(result.confidence >= 0.5);
|
|
}
|
|
|
|
#[test]
|
|
fn test_memory_protection_display() {
|
|
assert_eq!(format!("{}", MemoryProtection::NoAccess), "---");
|
|
assert_eq!(format!("{}", MemoryProtection::ReadOnly), "R--");
|
|
assert_eq!(format!("{}", MemoryProtection::ReadWrite), "RW-");
|
|
assert_eq!(format!("{}", MemoryProtection::ReadExecute), "R-X");
|
|
assert_eq!(format!("{}", MemoryProtection::ReadWriteExecute), "RWX");
|
|
assert_eq!(format!("{}", MemoryProtection::Execute), "--X");
|
|
}
|
|
|
|
#[test]
|
|
fn test_process_info_display() {
|
|
let process = create_test_process();
|
|
let display = format!("{}", process);
|
|
assert!(display.contains("1234"));
|
|
assert!(display.contains("test.exe"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_memory_region_display() {
|
|
let region = create_rwx_region();
|
|
let display = format!("{}", region);
|
|
assert!(display.contains("RWX"));
|
|
assert!(display.contains("PRIVATE"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_threat_level_ordering() {
|
|
assert!(ThreatLevel::Clean < ThreatLevel::Suspicious);
|
|
assert!(ThreatLevel::Suspicious < ThreatLevel::Malicious);
|
|
}
|
|
|
|
#[test]
|
|
fn test_detection_config_validation() {
|
|
let config = DetectionConfig::default();
|
|
assert!(config.validate().is_ok());
|
|
|
|
let mut invalid_config = DetectionConfig::default();
|
|
invalid_config.confidence_threshold = 1.5; // Invalid
|
|
assert!(invalid_config.validate().is_err());
|
|
|
|
invalid_config.confidence_threshold = -0.1; // Invalid
|
|
assert!(invalid_config.validate().is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_detection_config_presets() {
|
|
let perf_config = DetectionConfig::performance_mode();
|
|
let thorough_config = DetectionConfig::thorough_mode();
|
|
|
|
// Performance mode should have lower thresholds for faster scanning
|
|
assert!(perf_config.confidence_threshold <= thorough_config.confidence_threshold);
|
|
}
|
|
|
|
#[test]
|
|
fn test_process_is_system_process() {
|
|
let mut process = create_test_process();
|
|
assert!(!process.is_system_process());
|
|
|
|
process.pid = 0;
|
|
assert!(process.is_system_process());
|
|
|
|
process.pid = 4;
|
|
assert!(process.is_system_process());
|
|
|
|
process.pid = 100;
|
|
process.name = "System".to_string();
|
|
assert!(process.is_system_process());
|
|
}
|
|
|
|
#[test]
|
|
fn test_engine_with_custom_config() {
|
|
let mut config = DetectionConfig::default();
|
|
config.rwx_detection = false;
|
|
|
|
let mut engine = DetectionEngine::with_config(config).expect("Failed to create engine");
|
|
let process = create_test_process();
|
|
let regions = vec![create_rwx_region()];
|
|
|
|
// With RWX detection disabled, should not flag the region
|
|
let result = engine.analyze_process(&process, ®ions, None);
|
|
// Might still detect based on other heuristics, but confidence should be lower
|
|
assert!(result.confidence < 0.5);
|
|
}
|
|
|
|
#[test]
|
|
fn test_large_memory_region() {
|
|
let mut engine = DetectionEngine::new().expect("Failed to create engine");
|
|
let process = create_test_process();
|
|
let regions = vec![MemoryRegion {
|
|
base_address: 0x10000000,
|
|
size: 100 * 1024 * 1024, // 100MB region
|
|
protection: MemoryProtection::ReadWriteExecute,
|
|
region_type: "PRIVATE".to_string(),
|
|
}];
|
|
|
|
let result = engine.analyze_process(&process, ®ions, None);
|
|
assert_ne!(result.threat_level, ThreatLevel::Clean);
|
|
}
|
|
|
|
#[test]
|
|
fn test_image_vs_private_region() {
|
|
let mut engine = DetectionEngine::new().expect("Failed to create engine");
|
|
let process = create_test_process();
|
|
|
|
// IMAGE region with RX is normal
|
|
let image_regions = vec![MemoryRegion {
|
|
base_address: 0x400000,
|
|
size: 0x100000,
|
|
protection: MemoryProtection::ReadExecute,
|
|
region_type: "IMAGE".to_string(),
|
|
}];
|
|
|
|
let result = engine.analyze_process(&process, &image_regions, None);
|
|
assert_eq!(result.threat_level, ThreatLevel::Clean);
|
|
|
|
// PRIVATE region with RX is suspicious
|
|
let private_regions = vec![MemoryRegion {
|
|
base_address: 0x10000000,
|
|
size: 0x1000,
|
|
protection: MemoryProtection::ReadExecute,
|
|
region_type: "PRIVATE".to_string(),
|
|
}];
|
|
|
|
let result2 = engine.analyze_process(&process, &private_regions, None);
|
|
// Private executable regions are suspicious but not as severe as RWX
|
|
assert!(result2.confidence > 0.0 || result2.indicators.len() > 0);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod mitre_tests {
|
|
use ghost_core::mitre::{MitreMapping, TechniqueId};
|
|
|
|
#[test]
|
|
fn test_technique_id_display() {
|
|
let id = TechniqueId::new("T1055", Some("001"));
|
|
assert_eq!(format!("{}", id), "T1055.001");
|
|
|
|
let id_no_sub = TechniqueId::new("T1055", None);
|
|
assert_eq!(format!("{}", id_no_sub), "T1055");
|
|
}
|
|
|
|
#[test]
|
|
fn test_mitre_mapping_creation() {
|
|
let mapping = MitreMapping::default();
|
|
assert!(mapping.techniques.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_technique_lookup() {
|
|
let mapping = MitreMapping::default();
|
|
// Default mapping should have no techniques initially
|
|
assert!(mapping.get_technique("T1055").is_none());
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod threat_intel_tests {
|
|
use ghost_core::ThreatLevel;
|
|
|
|
#[test]
|
|
fn test_threat_level_description() {
|
|
assert_eq!(ThreatLevel::Clean.description(), "No threats detected");
|
|
assert_eq!(
|
|
ThreatLevel::Suspicious.description(),
|
|
"Potential security concern"
|
|
);
|
|
assert_eq!(
|
|
ThreatLevel::Malicious.description(),
|
|
"High confidence malicious activity"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_threat_level_serialization() {
|
|
let level = ThreatLevel::Suspicious;
|
|
let serialized = serde_json::to_string(&level).expect("Failed to serialize");
|
|
assert!(serialized.contains("Suspicious"));
|
|
|
|
let deserialized: ThreatLevel =
|
|
serde_json::from_str(&serialized).expect("Failed to deserialize");
|
|
assert_eq!(deserialized, level);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod config_tests {
|
|
use ghost_core::config::DetectionConfig;
|
|
|
|
#[test]
|
|
fn test_default_config() {
|
|
let config = DetectionConfig::default();
|
|
assert!(config.rwx_detection);
|
|
assert!(config.shellcode_detection);
|
|
assert!(config.hollowing_detection);
|
|
assert!(config.thread_detection);
|
|
assert!(config.hook_detection);
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_serialization() {
|
|
let config = DetectionConfig::default();
|
|
let json = serde_json::to_string(&config).expect("Failed to serialize");
|
|
let deserialized: DetectionConfig =
|
|
serde_json::from_str(&json).expect("Failed to deserialize");
|
|
assert_eq!(config.rwx_detection, deserialized.rwx_detection);
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_toml_format() {
|
|
let config = DetectionConfig::default();
|
|
let toml_str = toml::to_string(&config).expect("Failed to serialize to TOML");
|
|
assert!(toml_str.contains("rwx_detection"));
|
|
assert!(toml_str.contains("confidence_threshold"));
|
|
}
|
|
} |