1use crate::attack::strategies::*;
6use crate::signatures::SignatureEngine;
7use crate::types::*;
8use crate::xray::patterns::PatternDetector;
9use anyhow::{Context, Result};
10use std::process::{Command, Stdio};
11use std::time::Instant;
12
13pub struct AttackExecutor {
14 config: AttackConfig,
15 patterns: Vec<AttackPattern>,
16}
17
18impl AttackExecutor {
19 pub fn new(config: AttackConfig) -> Self {
20 Self {
21 config,
22 patterns: Vec::new(),
23 }
24 }
25
26 pub fn with_patterns(
27 config: AttackConfig,
28 language: Language,
29 frameworks: &[Framework],
30 ) -> Self {
31 let patterns = PatternDetector::patterns_for(language, frameworks);
32 Self { config, patterns }
33 }
34
35 pub fn execute(&self) -> Result<Vec<AttackResult>> {
36 let mut results = Vec::new();
37
38 for program in &self.config.target_programs {
39 for axis in &self.config.axes {
40 println!("Attacking {:?} on axis {:?}...", program, axis);
41
42 let result = self.execute_single_attack(program, *axis)?;
43 results.push(result);
44 }
45 }
46
47 Ok(results)
48 }
49
50 fn execute_single_attack(
51 &self,
52 program: &std::path::PathBuf,
53 axis: AttackAxis,
54 ) -> Result<AttackResult> {
55 let strategy = self.select_strategy(axis);
56 println!(" Strategy: {}", strategy.description());
57
58 let applicable: Vec<_> = self
60 .patterns
61 .iter()
62 .filter(|p| p.applicable_axes.contains(&axis))
63 .collect();
64 if !applicable.is_empty() {
65 println!(" Applicable patterns:");
66 for pat in &applicable {
67 println!(" - {}: {}", pat.name, pat.description);
68 }
69 }
70
71 let start = Instant::now();
72 let mut crashes = Vec::new();
73 let mut peak_memory = 0u64;
74
75 let (exit_code, success) = match strategy {
77 AttackStrategy::CpuStress => self.attack_cpu(program, &mut crashes)?,
78 AttackStrategy::MemoryExhaustion => {
79 self.attack_memory(program, &mut crashes, &mut peak_memory)?
80 }
81 AttackStrategy::DiskThrashing => self.attack_disk(program, &mut crashes)?,
82 AttackStrategy::NetworkFlood => self.attack_network(program, &mut crashes)?,
83 AttackStrategy::ConcurrencyStorm => self.attack_concurrency(program, &mut crashes)?,
84 AttackStrategy::TimeBomb => self.attack_time(program, &mut crashes)?,
85 };
86
87 let duration = start.elapsed();
88
89 let signatures_detected = if !crashes.is_empty() {
91 let engine = SignatureEngine::new();
92 crashes
93 .iter()
94 .flat_map(|crash| engine.detect_from_crash(crash))
95 .collect()
96 } else {
97 Vec::new()
98 };
99
100 Ok(AttackResult {
101 program: program.clone(),
102 axis,
103 success,
104 exit_code,
105 duration,
106 peak_memory,
107 crashes,
108 signatures_detected,
109 })
110 }
111
112 fn select_strategy(&self, axis: AttackAxis) -> AttackStrategy {
113 match axis {
114 AttackAxis::Cpu => AttackStrategy::CpuStress,
115 AttackAxis::Memory => AttackStrategy::MemoryExhaustion,
116 AttackAxis::Disk => AttackStrategy::DiskThrashing,
117 AttackAxis::Network => AttackStrategy::NetworkFlood,
118 AttackAxis::Concurrency => AttackStrategy::ConcurrencyStorm,
119 AttackAxis::Time => AttackStrategy::TimeBomb,
120 }
121 }
122
123 fn attack_cpu(
124 &self,
125 program: &std::path::PathBuf,
126 crashes: &mut Vec<CrashReport>,
127 ) -> Result<(Option<i32>, bool)> {
128 let iterations = (1000.0 * self.config.intensity.multiplier()) as u64;
130
131 let output = Command::new(program)
132 .arg("--iterations")
133 .arg(iterations.to_string())
134 .stdin(Stdio::null())
135 .stdout(Stdio::piped())
136 .stderr(Stdio::piped())
137 .output()
138 .context("Failed to execute program")?;
139
140 let success = output.status.success();
141 let exit_code = output.status.code();
142
143 if !success {
144 crashes.push(CrashReport {
145 timestamp: chrono::Utc::now().to_rfc3339(),
146 signal: Self::extract_signal(&output.stderr),
147 backtrace: Self::extract_backtrace(&output.stderr),
148 stderr: String::from_utf8_lossy(&output.stderr).to_string(),
149 stdout: String::from_utf8_lossy(&output.stdout).to_string(),
150 });
151 }
152
153 Ok((exit_code, success))
154 }
155
156 fn attack_memory(
157 &self,
158 program: &std::path::PathBuf,
159 crashes: &mut Vec<CrashReport>,
160 peak_memory: &mut u64,
161 ) -> Result<(Option<i32>, bool)> {
162 let memory_mb = (1024.0 * self.config.intensity.multiplier()) as u64;
164
165 let output = Command::new(program)
166 .arg("--allocate-mb")
167 .arg(memory_mb.to_string())
168 .stdin(Stdio::null())
169 .stdout(Stdio::piped())
170 .stderr(Stdio::piped())
171 .output()
172 .context("Failed to execute program")?;
173
174 *peak_memory = memory_mb * 1024 * 1024;
175
176 let success = output.status.success();
177 let exit_code = output.status.code();
178
179 if !success {
180 crashes.push(CrashReport {
181 timestamp: chrono::Utc::now().to_rfc3339(),
182 signal: Self::extract_signal(&output.stderr),
183 backtrace: Self::extract_backtrace(&output.stderr),
184 stderr: String::from_utf8_lossy(&output.stderr).to_string(),
185 stdout: String::from_utf8_lossy(&output.stdout).to_string(),
186 });
187 }
188
189 Ok((exit_code, success))
190 }
191
192 fn attack_disk(
193 &self,
194 program: &std::path::PathBuf,
195 crashes: &mut Vec<CrashReport>,
196 ) -> Result<(Option<i32>, bool)> {
197 let file_count = (100.0 * self.config.intensity.multiplier()) as u64;
199
200 let output = Command::new(program)
201 .arg("--write-files")
202 .arg(file_count.to_string())
203 .stdin(Stdio::null())
204 .stdout(Stdio::piped())
205 .stderr(Stdio::piped())
206 .output()
207 .context("Failed to execute program")?;
208
209 let success = output.status.success();
210 let exit_code = output.status.code();
211
212 if !success {
213 crashes.push(CrashReport {
214 timestamp: chrono::Utc::now().to_rfc3339(),
215 signal: Self::extract_signal(&output.stderr),
216 backtrace: Self::extract_backtrace(&output.stderr),
217 stderr: String::from_utf8_lossy(&output.stderr).to_string(),
218 stdout: String::from_utf8_lossy(&output.stdout).to_string(),
219 });
220 }
221
222 Ok((exit_code, success))
223 }
224
225 fn attack_network(
226 &self,
227 program: &std::path::PathBuf,
228 crashes: &mut Vec<CrashReport>,
229 ) -> Result<(Option<i32>, bool)> {
230 let connections = (100.0 * self.config.intensity.multiplier()) as u64;
232
233 let output = Command::new(program)
234 .arg("--connections")
235 .arg(connections.to_string())
236 .stdin(Stdio::null())
237 .stdout(Stdio::piped())
238 .stderr(Stdio::piped())
239 .output()
240 .context("Failed to execute program")?;
241
242 let success = output.status.success();
243 let exit_code = output.status.code();
244
245 if !success {
246 crashes.push(CrashReport {
247 timestamp: chrono::Utc::now().to_rfc3339(),
248 signal: Self::extract_signal(&output.stderr),
249 backtrace: Self::extract_backtrace(&output.stderr),
250 stderr: String::from_utf8_lossy(&output.stderr).to_string(),
251 stdout: String::from_utf8_lossy(&output.stdout).to_string(),
252 });
253 }
254
255 Ok((exit_code, success))
256 }
257
258 fn attack_concurrency(
259 &self,
260 program: &std::path::PathBuf,
261 crashes: &mut Vec<CrashReport>,
262 ) -> Result<(Option<i32>, bool)> {
263 let threads = (50.0 * self.config.intensity.multiplier()) as u64;
265
266 let output = Command::new(program)
267 .arg("--threads")
268 .arg(threads.to_string())
269 .stdin(Stdio::null())
270 .stdout(Stdio::piped())
271 .stderr(Stdio::piped())
272 .output()
273 .context("Failed to execute program")?;
274
275 let success = output.status.success();
276 let exit_code = output.status.code();
277
278 if !success {
279 crashes.push(CrashReport {
280 timestamp: chrono::Utc::now().to_rfc3339(),
281 signal: Self::extract_signal(&output.stderr),
282 backtrace: Self::extract_backtrace(&output.stderr),
283 stderr: String::from_utf8_lossy(&output.stderr).to_string(),
284 stdout: String::from_utf8_lossy(&output.stdout).to_string(),
285 });
286 }
287
288 Ok((exit_code, success))
289 }
290
291 fn attack_time(
292 &self,
293 program: &std::path::PathBuf,
294 crashes: &mut Vec<CrashReport>,
295 ) -> Result<(Option<i32>, bool)> {
296 let duration_secs = (60.0 * self.config.intensity.multiplier()) as u64;
298
299 let output = Command::new("timeout")
300 .arg(duration_secs.to_string())
301 .arg(program)
302 .stdin(Stdio::null())
303 .stdout(Stdio::piped())
304 .stderr(Stdio::piped())
305 .output()
306 .context("Failed to execute program")?;
307
308 let success = output.status.success();
309 let exit_code = output.status.code();
310
311 if !success {
312 crashes.push(CrashReport {
313 timestamp: chrono::Utc::now().to_rfc3339(),
314 signal: Self::extract_signal(&output.stderr),
315 backtrace: Self::extract_backtrace(&output.stderr),
316 stderr: String::from_utf8_lossy(&output.stderr).to_string(),
317 stdout: String::from_utf8_lossy(&output.stdout).to_string(),
318 });
319 }
320
321 Ok((exit_code, success))
322 }
323
324 fn extract_signal(stderr: &[u8]) -> Option<String> {
325 let stderr_str = String::from_utf8_lossy(stderr);
326 if stderr_str.contains("SIGSEGV") {
327 Some("SIGSEGV".to_string())
328 } else if stderr_str.contains("SIGABRT") {
329 Some("SIGABRT".to_string())
330 } else if stderr_str.contains("SIGILL") {
331 Some("SIGILL".to_string())
332 } else {
333 None
334 }
335 }
336
337 fn extract_backtrace(stderr: &[u8]) -> Option<String> {
338 let stderr_str = String::from_utf8_lossy(stderr);
339 if stderr_str.contains("backtrace") || stderr_str.contains("stack backtrace") {
340 Some(stderr_str.to_string())
341 } else {
342 None
343 }
344 }
345}