Fix CLI formatting and imports
This commit is contained in:
@@ -13,13 +13,15 @@ fn main() -> Result<()> {
|
|||||||
let matches = Command::new("ghost")
|
let matches = Command::new("ghost")
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
.about("Cross-Platform Process Injection Detection Framework")
|
.about("Cross-Platform Process Injection Detection Framework")
|
||||||
.long_about("Ghost scans running processes for signs of code injection, \
|
.long_about(
|
||||||
|
"Ghost scans running processes for signs of code injection, \
|
||||||
process hollowing, and other malicious techniques. \
|
process hollowing, and other malicious techniques. \
|
||||||
Supports Windows and Linux platforms with kernel-level monitoring.\n\n\
|
Supports Windows and Linux platforms with kernel-level monitoring.\n\n\
|
||||||
Exit Codes:\n\
|
Exit Codes:\n\
|
||||||
0 - No suspicious activity detected\n\
|
0 - No suspicious activity detected\n\
|
||||||
1 - Suspicious processes found\n\
|
1 - Suspicious processes found\n\
|
||||||
2 - Error occurred during scanning")
|
2 - Error occurred during scanning",
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("format")
|
Arg::new("format")
|
||||||
.short('f')
|
.short('f')
|
||||||
@@ -27,34 +29,34 @@ fn main() -> Result<()> {
|
|||||||
.value_name("FORMAT")
|
.value_name("FORMAT")
|
||||||
.help("Output format: table, json")
|
.help("Output format: table, json")
|
||||||
.default_value("table")
|
.default_value("table")
|
||||||
.value_parser(["table", "json", "csv"])
|
.value_parser(["table", "json", "csv"]),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("verbose")
|
Arg::new("verbose")
|
||||||
.short('v')
|
.short('v')
|
||||||
.long("verbose")
|
.long("verbose")
|
||||||
.help("Enable verbose output")
|
.help("Enable verbose output")
|
||||||
.action(clap::ArgAction::SetTrue)
|
.action(clap::ArgAction::SetTrue),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("pid")
|
Arg::new("pid")
|
||||||
.short('p')
|
.short('p')
|
||||||
.long("pid")
|
.long("pid")
|
||||||
.value_name("PID")
|
.value_name("PID")
|
||||||
.help("Target specific process ID")
|
.help("Target specific process ID"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("process")
|
Arg::new("process")
|
||||||
.long("process")
|
.long("process")
|
||||||
.value_name("NAME")
|
.value_name("NAME")
|
||||||
.help("Target specific process name")
|
.help("Target specific process name"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("output")
|
Arg::new("output")
|
||||||
.short('o')
|
.short('o')
|
||||||
.long("output")
|
.long("output")
|
||||||
.value_name("FILE")
|
.value_name("FILE")
|
||||||
.help("Write output to file instead of stdout"),
|
.help("Write output to file instead of stdout"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("debug")
|
Arg::new("debug")
|
||||||
@@ -147,22 +149,25 @@ fn main() -> Result<()> {
|
|||||||
format, verbose, quiet, target_pid, target_process, config_file);
|
format, verbose, quiet, target_pid, target_process, config_file);
|
||||||
|
|
||||||
if !quiet {
|
if !quiet {
|
||||||
println!("Ghost v{} - Process Injection Detection\n", env!("CARGO_PKG_VERSION"));
|
println!(
|
||||||
|
"Ghost v{} - Process Injection Detection\n",
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let scan_start = Instant::now();
|
let _scan_start = Instant::now();
|
||||||
let mut engine = DetectionEngine::with_config(config).map_err(|e| {
|
let mut engine = DetectionEngine::with_config(config).map_err(|e| {
|
||||||
error!("Failed to initialize detection engine: {}", e);
|
error!("Failed to initialize detection engine: {}", e);
|
||||||
anyhow::anyhow!("Detection engine initialization failed: {}", e)
|
anyhow::anyhow!("Detection engine initialization failed: {}", e)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Display MITRE ATT&CK statistics if requested
|
// Display MITRE ATT&CK statistics if requested
|
||||||
if mitre_stats {
|
if mitre_stats {
|
||||||
if !quiet {
|
if !quiet {
|
||||||
println!("MITRE ATT&CK Framework Statistics:");
|
println!("MITRE ATT&CK Framework Statistics:");
|
||||||
println!("==================================");
|
println!("==================================");
|
||||||
}
|
}
|
||||||
|
|
||||||
let (techniques, tactics, actors) = engine.get_mitre_stats();
|
let (techniques, tactics, actors) = engine.get_mitre_stats();
|
||||||
if !quiet {
|
if !quiet {
|
||||||
println!("Techniques: {}", techniques);
|
println!("Techniques: {}", techniques);
|
||||||
@@ -177,26 +182,23 @@ fn main() -> Result<()> {
|
|||||||
println!(" - APT29 (Cozy Bear)");
|
println!(" - APT29 (Cozy Bear)");
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If only showing stats, exit here
|
// If only showing stats, exit here
|
||||||
if mitre_stats && target_pid.is_none() && target_process.is_none() {
|
if mitre_stats && target_pid.is_none() && target_process.is_none() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let processes = if let Some(pid_str) = target_pid {
|
let processes = if let Some(pid_str) = target_pid {
|
||||||
let pid: u32 = pid_str.parse().map_err(|e| {
|
let pid: u32 = pid_str.parse().map_err(|e| {
|
||||||
error!("Invalid PID format '{}': {}", pid_str, e);
|
error!("Invalid PID format '{}': {}", pid_str, e);
|
||||||
anyhow::anyhow!("Invalid PID format: {}", pid_str)
|
anyhow::anyhow!("Invalid PID format: {}", pid_str)
|
||||||
})?;
|
})?;
|
||||||
info!("Targeting specific process ID: {}", pid);
|
info!("Targeting specific process ID: {}", pid);
|
||||||
|
|
||||||
let all_processes = process::enumerate_processes()?;
|
let all_processes = process::enumerate_processes()?;
|
||||||
let filtered: Vec<_> = all_processes
|
let filtered: Vec<_> = all_processes.into_iter().filter(|p| p.pid == pid).collect();
|
||||||
.into_iter()
|
|
||||||
.filter(|p| p.pid == pid)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if filtered.is_empty() {
|
if filtered.is_empty() {
|
||||||
warn!("No process found with PID {}", pid);
|
warn!("No process found with PID {}", pid);
|
||||||
if !quiet {
|
if !quiet {
|
||||||
@@ -213,20 +215,36 @@ fn main() -> Result<()> {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|p| p.name.to_lowercase().contains(&process_name.to_lowercase()))
|
.filter(|p| p.name.to_lowercase().contains(&process_name.to_lowercase()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if filtered.is_empty() {
|
if filtered.is_empty() {
|
||||||
warn!("No processes found matching name: {}", process_name);
|
warn!("No processes found matching name: {}", process_name);
|
||||||
if !quiet {
|
if !quiet {
|
||||||
println!("Warning: No processes found matching name: {}", process_name);
|
println!(
|
||||||
|
"Warning: No processes found matching name: {}",
|
||||||
|
process_name
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
info!("Found {} processes matching name: {}", filtered.len(), process_name);
|
info!(
|
||||||
debug!("Matching processes: {:?}", filtered.iter().map(|p| format!("{} ({})", p.name, p.pid)).collect::<Vec<_>>());
|
"Found {} processes matching name: {}",
|
||||||
|
filtered.len(),
|
||||||
|
process_name
|
||||||
|
);
|
||||||
|
debug!(
|
||||||
|
"Matching processes: {:?}",
|
||||||
|
filtered
|
||||||
|
.iter()
|
||||||
|
.map(|p| format!("{} ({})", p.name, p.pid))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
filtered
|
filtered
|
||||||
} else {
|
} else {
|
||||||
let all_processes = process::enumerate_processes()?;
|
let all_processes = process::enumerate_processes()?;
|
||||||
info!("Enumerating all processes, found {} total", all_processes.len());
|
info!(
|
||||||
|
"Enumerating all processes, found {} total",
|
||||||
|
all_processes.len()
|
||||||
|
);
|
||||||
all_processes
|
all_processes
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -244,19 +262,26 @@ fn main() -> Result<()> {
|
|||||||
debug!("Skipping safe system process: {}", proc.name);
|
debug!("Skipping safe system process: {}", proc.name);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
scanned_count += 1;
|
scanned_count += 1;
|
||||||
debug!("Scanning process: {} (PID: {})", proc.name, proc.pid);
|
debug!("Scanning process: {} (PID: {})", proc.name, proc.pid);
|
||||||
|
|
||||||
match memory::enumerate_memory_regions(proc.pid) {
|
match memory::enumerate_memory_regions(proc.pid) {
|
||||||
Ok(regions) => {
|
Ok(regions) => {
|
||||||
debug!("Found {} memory regions for process {}", regions.len(), proc.name);
|
debug!(
|
||||||
|
"Found {} memory regions for process {}",
|
||||||
|
regions.len(),
|
||||||
|
proc.name
|
||||||
|
);
|
||||||
// Get thread information if available
|
// Get thread information if available
|
||||||
let threads = thread::enumerate_threads(proc.pid).ok();
|
let threads = thread::enumerate_threads(proc.pid).ok();
|
||||||
let result = engine.analyze_process(proc, ®ions, threads.as_deref());
|
let result = engine.analyze_process(proc, ®ions, threads.as_deref());
|
||||||
|
|
||||||
if result.threat_level != ThreatLevel::Clean {
|
if result.threat_level != ThreatLevel::Clean {
|
||||||
warn!("Suspicious activity detected in process {} (PID: {})", proc.name, proc.pid);
|
warn!(
|
||||||
|
"Suspicious activity detected in process {} (PID: {})",
|
||||||
|
proc.name, proc.pid
|
||||||
|
);
|
||||||
detections.push(result);
|
detections.push(result);
|
||||||
} else {
|
} else {
|
||||||
debug!("Process {} (PID: {}) is clean", proc.name, proc.pid);
|
debug!("Process {} (PID: {}) is clean", proc.name, proc.pid);
|
||||||
@@ -264,9 +289,15 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error_count += 1;
|
error_count += 1;
|
||||||
error!("Failed to scan process {} (PID: {}): {}", proc.name, proc.pid, e);
|
error!(
|
||||||
|
"Failed to scan process {} (PID: {}): {}",
|
||||||
|
proc.name, proc.pid, e
|
||||||
|
);
|
||||||
if verbose && !quiet {
|
if verbose && !quiet {
|
||||||
println!("Warning: Could not scan process {} (PID: {})", proc.name, proc.pid);
|
println!(
|
||||||
|
"Warning: Could not scan process {} (PID: {})",
|
||||||
|
proc.name, proc.pid
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,7 +308,11 @@ fn main() -> Result<()> {
|
|||||||
println!("Scan completed with {} access errors", error_count);
|
println!("Scan completed with {} access errors", error_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Scan completed: {} processes scanned, {} suspicious processes found", scanned_count, detections.len());
|
info!(
|
||||||
|
"Scan completed: {} processes scanned, {} suspicious processes found",
|
||||||
|
scanned_count,
|
||||||
|
detections.len()
|
||||||
|
);
|
||||||
|
|
||||||
// Handle output
|
// Handle output
|
||||||
let output_content = match format.as_str() {
|
let output_content = match format.as_str() {
|
||||||
@@ -287,13 +322,15 @@ fn main() -> Result<()> {
|
|||||||
"status": "clean",
|
"status": "clean",
|
||||||
"message": "No suspicious activity detected",
|
"message": "No suspicious activity detected",
|
||||||
"detections": []
|
"detections": []
|
||||||
}).to_string()
|
})
|
||||||
|
.to_string()
|
||||||
} else {
|
} else {
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"status": "suspicious",
|
"status": "suspicious",
|
||||||
"message": format!("Found {} suspicious processes", detections.len()),
|
"message": format!("Found {} suspicious processes", detections.len()),
|
||||||
"detections": &detections
|
"detections": &detections
|
||||||
}).to_string()
|
})
|
||||||
|
.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@@ -331,7 +368,7 @@ fn main() -> Result<()> {
|
|||||||
if let Some(output_path) = output_file {
|
if let Some(output_path) = output_file {
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
info!("Writing results to file: {}", output_path);
|
info!("Writing results to file: {}", output_path);
|
||||||
let mut file = File::create(output_path)?;
|
let mut file = File::create(output_path)?;
|
||||||
file.write_all(output_content.as_bytes())?;
|
file.write_all(output_content.as_bytes())?;
|
||||||
@@ -347,11 +384,11 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
// Exit with appropriate code for automation
|
// Exit with appropriate code for automation
|
||||||
let exit_code = if error_count > 0 {
|
let exit_code = if error_count > 0 {
|
||||||
2 // Error occurred during scanning
|
2 // Error occurred during scanning
|
||||||
} else if !detections.is_empty() {
|
} else if !detections.is_empty() {
|
||||||
1 // Suspicious processes found
|
1 // Suspicious processes found
|
||||||
} else {
|
} else {
|
||||||
0 // Clean scan
|
0 // Clean scan
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("Exiting with code: {}", exit_code);
|
debug!("Exiting with code: {}", exit_code);
|
||||||
|
|||||||
Reference in New Issue
Block a user