Skip to main content

threatflux_binary_analysis/analysis/
security.rs

1//! Heuristic security-triage signals for parsed binaries.
2//!
3//! Rules inspect selected imports, names, section permissions, and parser-derived
4//! hardening metadata. Findings can be incomplete or incorrect and must not be
5//! interpreted as vulnerability detection, a malware verdict, or proof of safety.
6
7use crate::{
8    BinaryFile, Result,
9    types::{
10        Architecture, BinaryFormat, Import, Section, SecurityFeatures, SecurityIndicators, Symbol,
11    },
12};
13use std::collections::HashSet;
14
15/// Best-effort security-triage analyzer for binary files
16pub struct SecurityAnalyzer {
17    /// Architecture being analyzed
18    #[allow(dead_code)]
19    architecture: Architecture,
20    /// Analysis configuration
21    config: SecurityConfig,
22}
23
24/// Configuration for security analysis
25#[derive(Debug, Clone)]
26pub struct SecurityConfig {
27    /// Enable suspicious API detection
28    pub detect_suspicious_apis: bool,
29    /// Enable anti-debugging detection
30    pub detect_anti_debug: bool,
31    /// Enable anti-VM detection
32    pub detect_anti_vm: bool,
33    /// Enable cryptographic indicators
34    pub detect_crypto: bool,
35    /// Enable network indicators
36    pub detect_network: bool,
37    /// Enable filesystem indicators
38    pub detect_filesystem: bool,
39    /// Enable registry indicators (Windows)
40    pub detect_registry: bool,
41    /// Minimum string length for analysis
42    pub min_string_length: usize,
43}
44
45impl Default for SecurityConfig {
46    fn default() -> Self {
47        Self {
48            detect_suspicious_apis: true,
49            detect_anti_debug: true,
50            detect_anti_vm: true,
51            detect_crypto: true,
52            detect_network: true,
53            detect_filesystem: true,
54            detect_registry: true,
55            min_string_length: 4,
56        }
57    }
58}
59
60/// Security analysis result
61#[derive(Debug, Clone)]
62pub struct SecurityAnalysisResult {
63    /// Security indicators found
64    pub indicators: SecurityIndicators,
65    /// Security features present
66    pub features: SecurityFeatures,
67    /// Crate-local heuristic score (0-100), not CVSS or a probability
68    pub risk_score: f64,
69    /// Detailed findings
70    pub findings: Vec<SecurityFinding>,
71}
72
73/// Individual security finding
74#[derive(Debug, Clone)]
75pub struct SecurityFinding {
76    /// Finding category
77    pub category: FindingCategory,
78    /// Severity level
79    pub severity: Severity,
80    /// Description
81    pub description: String,
82    /// Location (address, section, etc.)
83    pub location: Option<String>,
84    /// Associated data
85    pub data: Option<String>,
86}
87
88/// Security finding categories
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub enum FindingCategory {
91    /// Suspicious API call
92    SuspiciousApi,
93    /// Anti-debugging technique
94    AntiDebug,
95    /// Anti-VM technique
96    AntiVm,
97    /// Cryptographic operation
98    Cryptographic,
99    /// Network operation
100    Network,
101    /// Filesystem operation
102    Filesystem,
103    /// Registry operation
104    Registry,
105    /// Security feature missing
106    MissingSecurity,
107    /// Packing/obfuscation
108    Obfuscation,
109    /// Code injection
110    CodeInjection,
111    /// Privilege escalation
112    PrivilegeEscalation,
113}
114
115/// Finding severity levels
116#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
117pub enum Severity {
118    /// Informational
119    Info,
120    /// Low risk
121    Low,
122    /// Medium risk
123    Medium,
124    /// High risk
125    High,
126    /// Critical risk
127    Critical,
128}
129
130impl SecurityAnalyzer {
131    /// Create a new security analyzer
132    pub fn new(architecture: Architecture) -> Self {
133        Self {
134            architecture,
135            config: SecurityConfig::default(),
136        }
137    }
138
139    /// Create analyzer with custom configuration
140    pub fn with_config(architecture: Architecture, config: SecurityConfig) -> Self {
141        Self {
142            architecture,
143            config,
144        }
145    }
146
147    /// Run the configured heuristic security checks
148    pub fn analyze(&self, binary: &BinaryFile) -> Result<SecurityAnalysisResult> {
149        let mut indicators = SecurityIndicators::default();
150        let mut findings = Vec::new();
151
152        // Analyze imports for suspicious APIs
153        if self.config.detect_suspicious_apis
154            || self.config.detect_anti_debug
155            || self.config.detect_anti_vm
156            || self.config.detect_crypto
157            || self.config.detect_network
158            || self.config.detect_filesystem
159            || self.config.detect_registry
160        {
161            self.analyze_imports(binary.imports(), &mut indicators, &mut findings);
162        }
163
164        // Analyze sections for security indicators
165        self.analyze_sections(binary.sections(), &mut indicators, &mut findings);
166
167        // Analyze symbols
168        self.analyze_symbols(binary.symbols(), &mut indicators, &mut findings);
169
170        // Get security features from metadata
171        let features = binary.metadata().security_features.clone();
172
173        // Analyze security features
174        self.analyze_security_features(binary.format(), &features, &mut findings);
175
176        Self::canonicalize_indicators(&mut indicators);
177
178        // Calculate risk score
179        let risk_score =
180            self.calculate_risk_score(binary.format(), &indicators, &features, &findings);
181
182        Ok(SecurityAnalysisResult {
183            indicators,
184            features,
185            risk_score,
186            findings,
187        })
188    }
189
190    /// Analyze imports for suspicious APIs
191    fn analyze_imports(
192        &self,
193        imports: &[Import],
194        indicators: &mut SecurityIndicators,
195        findings: &mut Vec<SecurityFinding>,
196    ) {
197        let suspicious_apis = self.get_suspicious_apis();
198        let anti_debug_apis = self.get_anti_debug_apis();
199        let anti_vm_apis = self.get_anti_vm_apis();
200        let crypto_apis = self.get_crypto_apis();
201        let network_apis = self.get_network_apis();
202        let filesystem_apis = self.get_filesystem_apis();
203        let registry_apis = self.get_registry_apis();
204        let mut seen_api_names = HashSet::new();
205
206        for import in imports {
207            let api_name = &import.name;
208            if api_name.len() < self.config.min_string_length {
209                continue;
210            }
211
212            let enabled_match = (self.config.detect_suspicious_apis
213                && suspicious_apis.contains(api_name.as_str()))
214                || (self.config.detect_anti_debug && anti_debug_apis.contains(api_name.as_str()))
215                || (self.config.detect_anti_vm && anti_vm_apis.contains(api_name.as_str()))
216                || (self.config.detect_crypto && crypto_apis.contains(api_name.as_str()))
217                || (self.config.detect_network && network_apis.contains(api_name.as_str()))
218                || (self.config.detect_filesystem && filesystem_apis.contains(api_name.as_str()))
219                || (self.config.detect_registry && registry_apis.contains(api_name.as_str()));
220            if !enabled_match || !seen_api_names.insert(api_name.as_str()) {
221                continue;
222            }
223
224            if self.config.detect_suspicious_apis && suspicious_apis.contains(api_name.as_str()) {
225                indicators.suspicious_apis.push(api_name.clone());
226                findings.push(SecurityFinding {
227                    category: FindingCategory::SuspiciousApi,
228                    severity: Severity::High,
229                    description: format!("Suspicious API call: {}", api_name),
230                    location: import.library.clone(),
231                    data: Some(api_name.clone()),
232                });
233            }
234
235            if self.config.detect_anti_debug && anti_debug_apis.contains(api_name.as_str()) {
236                indicators.anti_debug.push(api_name.clone());
237                findings.push(SecurityFinding {
238                    category: FindingCategory::AntiDebug,
239                    severity: Severity::Medium,
240                    description: format!("Anti-debugging API: {}", api_name),
241                    location: import.library.clone(),
242                    data: Some(api_name.clone()),
243                });
244            }
245
246            if self.config.detect_anti_vm && anti_vm_apis.contains(api_name.as_str()) {
247                indicators.anti_vm.push(api_name.clone());
248                findings.push(SecurityFinding {
249                    category: FindingCategory::AntiVm,
250                    severity: Severity::Medium,
251                    description: format!("Anti-VM API: {}", api_name),
252                    location: import.library.clone(),
253                    data: Some(api_name.clone()),
254                });
255            }
256
257            if self.config.detect_crypto && crypto_apis.contains(api_name.as_str()) {
258                indicators.crypto_indicators.push(api_name.clone());
259                findings.push(SecurityFinding {
260                    category: FindingCategory::Cryptographic,
261                    severity: Severity::Info,
262                    description: format!("Cryptographic API: {}", api_name),
263                    location: import.library.clone(),
264                    data: Some(api_name.clone()),
265                });
266            }
267
268            if self.config.detect_network && network_apis.contains(api_name.as_str()) {
269                indicators.network_indicators.push(api_name.clone());
270                findings.push(SecurityFinding {
271                    category: FindingCategory::Network,
272                    severity: Severity::Low,
273                    description: format!("Network API: {}", api_name),
274                    location: import.library.clone(),
275                    data: Some(api_name.clone()),
276                });
277            }
278
279            if self.config.detect_filesystem && filesystem_apis.contains(api_name.as_str()) {
280                indicators.filesystem_indicators.push(api_name.clone());
281                findings.push(SecurityFinding {
282                    category: FindingCategory::Filesystem,
283                    severity: Severity::Low,
284                    description: format!("Filesystem API: {}", api_name),
285                    location: import.library.clone(),
286                    data: Some(api_name.clone()),
287                });
288            }
289
290            if self.config.detect_registry && registry_apis.contains(api_name.as_str()) {
291                indicators.registry_indicators.push(api_name.clone());
292                findings.push(SecurityFinding {
293                    category: FindingCategory::Registry,
294                    severity: Severity::Low,
295                    description: format!("Registry API: {}", api_name),
296                    location: import.library.clone(),
297                    data: Some(api_name.clone()),
298                });
299            }
300        }
301    }
302
303    fn canonicalize_indicators(indicators: &mut SecurityIndicators) {
304        for values in [
305            &mut indicators.suspicious_apis,
306            &mut indicators.anti_debug,
307            &mut indicators.anti_vm,
308            &mut indicators.crypto_indicators,
309            &mut indicators.network_indicators,
310            &mut indicators.filesystem_indicators,
311            &mut indicators.registry_indicators,
312        ] {
313            values.sort_unstable();
314            values.dedup();
315        }
316    }
317
318    /// Analyze sections for security indicators
319    fn analyze_sections(
320        &self,
321        sections: &[Section],
322        _indicators: &mut SecurityIndicators,
323        findings: &mut Vec<SecurityFinding>,
324    ) {
325        let mut seen_rwx_sections = HashSet::new();
326        let mut seen_suspicious_names = HashSet::new();
327
328        for section in sections {
329            // Check for executable and writable sections (potential code injection)
330            if section.permissions.execute
331                && section.permissions.write
332                && seen_rwx_sections.insert((section.name.as_str(), section.address))
333            {
334                findings.push(SecurityFinding {
335                    category: FindingCategory::CodeInjection,
336                    severity: Severity::High,
337                    description: format!(
338                        "Section '{}' is both executable and writable (RWX)",
339                        section.name
340                    ),
341                    location: Some(format!("0x{:x}", section.address)),
342                    data: Some(section.name.clone()),
343                });
344            }
345
346            // Check for suspicious section names
347            if section.name.len() >= self.config.min_string_length
348                && self.is_suspicious_section_name(&section.name)
349                && seen_suspicious_names.insert((section.name.as_str(), section.address))
350            {
351                findings.push(SecurityFinding {
352                    category: FindingCategory::Obfuscation,
353                    severity: Severity::Medium,
354                    description: format!("Suspicious section name: {}", section.name),
355                    location: Some(format!("0x{:x}", section.address)),
356                    data: Some(section.name.clone()),
357                });
358            }
359        }
360    }
361
362    /// Analyze symbols for security indicators
363    fn analyze_symbols(
364        &self,
365        symbols: &[Symbol],
366        _indicators: &mut SecurityIndicators,
367        findings: &mut Vec<SecurityFinding>,
368    ) {
369        let mut seen_symbols = HashSet::new();
370
371        for symbol in symbols {
372            // Check for suspicious symbol names
373            if symbol.name.len() >= self.config.min_string_length
374                && self.is_suspicious_symbol_name(&symbol.name)
375                && seen_symbols.insert((symbol.name.as_str(), symbol.address))
376            {
377                findings.push(SecurityFinding {
378                    category: FindingCategory::SuspiciousApi,
379                    severity: Severity::Medium,
380                    description: format!("Suspicious symbol: {}", symbol.name),
381                    location: Some(format!("0x{:x}", symbol.address)),
382                    data: Some(symbol.name.clone()),
383                });
384            }
385        }
386    }
387
388    /// Analyze security features
389    fn analyze_security_features(
390        &self,
391        format: BinaryFormat,
392        features: &SecurityFeatures,
393        findings: &mut Vec<SecurityFinding>,
394    ) {
395        if !Self::has_native_hardening_metadata(format) {
396            return;
397        }
398
399        if !features.nx_bit {
400            findings.push(SecurityFinding {
401                category: FindingCategory::MissingSecurity,
402                severity: Severity::Medium,
403                description: "NX/DEP bit not enabled".to_string(),
404                location: None,
405                data: None,
406            });
407        }
408
409        if !features.aslr {
410            findings.push(SecurityFinding {
411                category: FindingCategory::MissingSecurity,
412                severity: Severity::Medium,
413                description: "ASLR not enabled".to_string(),
414                location: None,
415                data: None,
416            });
417        }
418
419        if !features.stack_canary {
420            findings.push(SecurityFinding {
421                category: FindingCategory::MissingSecurity,
422                severity: Severity::Low,
423                description: "Stack canaries not detected".to_string(),
424                location: None,
425                data: None,
426            });
427        }
428
429        if !features.cfi {
430            findings.push(SecurityFinding {
431                category: FindingCategory::MissingSecurity,
432                severity: Severity::Low,
433                description: "Control Flow Integrity not enabled".to_string(),
434                location: None,
435                data: None,
436            });
437        }
438    }
439
440    /// Calculate overall risk score
441    fn calculate_risk_score(
442        &self,
443        format: BinaryFormat,
444        indicators: &SecurityIndicators,
445        features: &SecurityFeatures,
446        findings: &[SecurityFinding],
447    ) -> f64 {
448        let mut score = 0.0;
449
450        // Base score from indicators
451        score += indicators.suspicious_apis.len() as f64 * 10.0;
452        score += indicators.anti_debug.len() as f64 * 5.0;
453        score += indicators.anti_vm.len() as f64 * 5.0;
454        score += indicators.crypto_indicators.len() as f64 * 1.0;
455        score += indicators.network_indicators.len() as f64 * 2.0;
456        score += indicators.filesystem_indicators.len() as f64 * 1.0;
457        score += indicators.registry_indicators.len() as f64 * 1.0;
458
459        // Only native executable formats expose the hardening metadata these
460        // booleans approximate. False values on Java, WebAssembly, and raw
461        // inputs mean unknown or inapplicable and must not be penalized.
462        if Self::has_native_hardening_metadata(format) {
463            if !features.nx_bit {
464                score += 10.0;
465            }
466            if !features.aslr {
467                score += 10.0;
468            }
469            if !features.stack_canary {
470                score += 5.0;
471            }
472            if !features.cfi {
473                score += 5.0;
474            }
475            if !features.pie {
476                score += 5.0;
477            }
478        }
479
480        // Add severity-based scoring from findings
481        for finding in findings {
482            match finding.severity {
483                Severity::Critical => score += 20.0,
484                Severity::High => score += 10.0,
485                Severity::Medium => score += 5.0,
486                Severity::Low => score += 2.0,
487                Severity::Info => score += 0.5,
488            }
489        }
490
491        // Normalize to 0-100
492        (score / 2.0).min(100.0)
493    }
494
495    fn has_native_hardening_metadata(format: BinaryFormat) -> bool {
496        matches!(
497            format,
498            BinaryFormat::Elf | BinaryFormat::Pe | BinaryFormat::MachO
499        )
500    }
501
502    /// Get list of suspicious APIs
503    fn get_suspicious_apis(&self) -> HashSet<&'static str> {
504        let mut apis = HashSet::new();
505
506        // Code injection APIs
507        apis.insert("VirtualAllocEx");
508        apis.insert("WriteProcessMemory");
509        apis.insert("CreateRemoteThread");
510        apis.insert("SetWindowsHookEx");
511        apis.insert("NtMapViewOfSection");
512        apis.insert("ZwMapViewOfSection");
513
514        // Process manipulation
515        apis.insert("OpenProcess");
516        apis.insert("TerminateProcess");
517        apis.insert("SuspendThread");
518        apis.insert("ResumeThread");
519
520        // Privilege escalation
521        apis.insert("AdjustTokenPrivileges");
522        apis.insert("LookupPrivilegeValue");
523        apis.insert("SeDebugPrivilege");
524
525        apis
526    }
527
528    /// Get list of anti-debugging APIs
529    fn get_anti_debug_apis(&self) -> HashSet<&'static str> {
530        let mut apis = HashSet::new();
531
532        apis.insert("IsDebuggerPresent");
533        apis.insert("CheckRemoteDebuggerPresent");
534        apis.insert("NtQueryInformationProcess");
535        apis.insert("ZwQueryInformationProcess");
536        apis.insert("OutputDebugString");
537        apis.insert("GetTickCount");
538        apis.insert("QueryPerformanceCounter");
539        apis.insert("ptrace"); // Linux
540
541        apis
542    }
543
544    /// Get list of anti-VM APIs
545    fn get_anti_vm_apis(&self) -> HashSet<&'static str> {
546        let mut apis = HashSet::new();
547
548        apis.insert("GetSystemInfo");
549        apis.insert("GlobalMemoryStatusEx");
550        apis.insert("GetAdaptersInfo");
551        apis.insert("GetVolumeInformation");
552        apis.insert("RegOpenKeyEx");
553        apis.insert("CreateToolhelp32Snapshot");
554
555        apis
556    }
557
558    /// Get list of cryptographic APIs
559    fn get_crypto_apis(&self) -> HashSet<&'static str> {
560        let mut apis = HashSet::new();
561
562        apis.insert("CryptAcquireContext");
563        apis.insert("CryptCreateHash");
564        apis.insert("CryptEncrypt");
565        apis.insert("CryptDecrypt");
566        apis.insert("CryptImportKey");
567        apis.insert("CryptExportKey");
568
569        apis
570    }
571
572    /// Get list of network APIs
573    fn get_network_apis(&self) -> HashSet<&'static str> {
574        let mut apis = HashSet::new();
575
576        apis.insert("WSAStartup");
577        apis.insert("socket");
578        apis.insert("connect");
579        apis.insert("send");
580        apis.insert("recv");
581        apis.insert("InternetOpen");
582        apis.insert("InternetOpenUrl");
583        apis.insert("HttpSendRequest");
584
585        apis
586    }
587
588    /// Get list of filesystem APIs
589    fn get_filesystem_apis(&self) -> HashSet<&'static str> {
590        let mut apis = HashSet::new();
591
592        apis.insert("CreateFile");
593        apis.insert("WriteFile");
594        apis.insert("ReadFile");
595        apis.insert("DeleteFile");
596        apis.insert("MoveFile");
597        apis.insert("CopyFile");
598        apis.insert("FindFirstFile");
599        apis.insert("FindNextFile");
600
601        apis
602    }
603
604    /// Get list of registry APIs
605    fn get_registry_apis(&self) -> HashSet<&'static str> {
606        let mut apis = HashSet::new();
607
608        apis.insert("RegOpenKeyEx");
609        apis.insert("RegCreateKeyEx");
610        apis.insert("RegSetValueEx");
611        apis.insert("RegQueryValueEx");
612        apis.insert("RegDeleteKey");
613        apis.insert("RegDeleteValue");
614
615        apis
616    }
617
618    /// Check if section name is suspicious
619    fn is_suspicious_section_name(&self, name: &str) -> bool {
620        let suspicious_names = [
621            ".packed",
622            ".upx",
623            ".themida",
624            ".aspack",
625            ".pecompact",
626            ".enigma",
627            ".vmprotect",
628            ".obsidium",
629            ".tElock",
630            ".shell",
631            ".stub",
632            ".overlay",
633        ];
634
635        suspicious_names
636            .iter()
637            .any(|&pattern| name.to_lowercase().contains(pattern))
638    }
639
640    /// Check if symbol name is suspicious
641    fn is_suspicious_symbol_name(&self, name: &str) -> bool {
642        let suspicious_patterns = [
643            "bypass",
644            "inject",
645            "hook",
646            "shellcode",
647            "payload",
648            "exploit",
649            "backdoor",
650            "rootkit",
651            "keylog",
652            "stealth",
653        ];
654
655        let lower_name = name.to_lowercase();
656        suspicious_patterns
657            .iter()
658            .any(|&pattern| lower_name.contains(pattern))
659    }
660}
661
662/// Analyze binary security
663pub fn analyze_binary_security(binary: &BinaryFile) -> Result<SecurityAnalysisResult> {
664    let analyzer = SecurityAnalyzer::new(binary.architecture());
665    analyzer.analyze(binary)
666}
667
668#[cfg(test)]
669mod tests {
670    use super::*;
671    use crate::types::*;
672
673    // Helper function to create test binary metadata
674    fn create_test_metadata() -> BinaryMetadata {
675        BinaryMetadata {
676            size: 1024,
677            format: BinaryFormat::Pe,
678            architecture: Architecture::X86_64,
679            entry_point: Some(0x1000),
680            base_address: Some(0x400000),
681            timestamp: None,
682            compiler_info: None,
683            endian: Endianness::Little,
684            security_features: SecurityFeatures {
685                nx_bit: true,
686                aslr: true,
687                stack_canary: false,
688                cfi: false,
689                fortify: false,
690                pie: false,
691                relro: false,
692                signed: false,
693            },
694        }
695    }
696
697    // Helper function to create a mock binary file for testing
698    fn create_test_binary_file() -> MockBinaryFile {
699        MockBinaryFile {
700            imports: vec![
701                Import {
702                    name: "CreateProcess".to_string(),
703                    library: Some("kernel32.dll".to_string()),
704                    address: Some(0x1000),
705                    ordinal: None,
706                },
707                Import {
708                    name: "VirtualAllocEx".to_string(),
709                    library: Some("kernel32.dll".to_string()),
710                    address: Some(0x1004),
711                    ordinal: None,
712                },
713                Import {
714                    name: "IsDebuggerPresent".to_string(),
715                    library: Some("kernel32.dll".to_string()),
716                    address: Some(0x1008),
717                    ordinal: None,
718                },
719            ],
720            sections: vec![
721                Section {
722                    name: ".text".to_string(),
723                    address: 0x1000,
724                    size: 0x500,
725                    offset: 0x400,
726                    file_size: 0x500,
727                    permissions: SectionPermissions {
728                        read: true,
729                        write: false,
730                        execute: true,
731                    },
732                    section_type: SectionType::Code,
733                    data: None,
734                },
735                Section {
736                    name: ".upx0".to_string(),
737                    address: 0x2000,
738                    size: 0x300,
739                    offset: 0x900,
740                    file_size: 0x300,
741                    permissions: SectionPermissions {
742                        read: true,
743                        write: true,
744                        execute: true,
745                    },
746                    section_type: SectionType::Code,
747                    data: None,
748                },
749            ],
750            symbols: vec![
751                Symbol {
752                    name: "main".to_string(),
753                    demangled_name: None,
754                    address: 0x1000,
755                    size: 100,
756                    symbol_type: SymbolType::Function,
757                    binding: SymbolBinding::Global,
758                    visibility: SymbolVisibility::Default,
759                    section_index: Some(0),
760                },
761                Symbol {
762                    name: "inject_payload".to_string(),
763                    demangled_name: None,
764                    address: 0x1100,
765                    size: 50,
766                    symbol_type: SymbolType::Function,
767                    binding: SymbolBinding::Local,
768                    visibility: SymbolVisibility::Hidden,
769                    section_index: Some(0),
770                },
771            ],
772            metadata: create_test_metadata(),
773        }
774    }
775
776    // Mock BinaryFile for testing
777    struct MockBinaryFile {
778        imports: Vec<Import>,
779        sections: Vec<Section>,
780        symbols: Vec<Symbol>,
781        metadata: BinaryMetadata,
782    }
783
784    impl MockBinaryFile {
785        fn imports(&self) -> &[Import] {
786            &self.imports
787        }
788
789        fn sections(&self) -> &[Section] {
790            &self.sections
791        }
792
793        fn symbols(&self) -> &[Symbol] {
794            &self.symbols
795        }
796
797        fn metadata(&self) -> &BinaryMetadata {
798            &self.metadata
799        }
800
801        fn architecture(&self) -> Architecture {
802            self.metadata.architecture
803        }
804    }
805
806    // Test helper to create a complete test analysis result using direct method calls
807    fn analyze_mock_binary(binary: &MockBinaryFile) -> SecurityAnalysisResult {
808        let analyzer = SecurityAnalyzer::new(binary.architecture());
809        let mut indicators = SecurityIndicators::default();
810        let mut findings = Vec::new();
811
812        // Analyze imports
813        analyzer.analyze_imports(binary.imports(), &mut indicators, &mut findings);
814
815        // Analyze sections
816        analyzer.analyze_sections(binary.sections(), &mut indicators, &mut findings);
817
818        // Analyze symbols
819        analyzer.analyze_symbols(binary.symbols(), &mut indicators, &mut findings);
820
821        // Get security features from metadata
822        let features = binary.metadata().security_features.clone();
823
824        // Analyze security features
825        analyzer.analyze_security_features(binary.metadata().format, &features, &mut findings);
826
827        SecurityAnalyzer::canonicalize_indicators(&mut indicators);
828
829        // Calculate risk score
830        let risk_score = analyzer.calculate_risk_score(
831            binary.metadata().format,
832            &indicators,
833            &features,
834            &findings,
835        );
836
837        SecurityAnalysisResult {
838            indicators,
839            features,
840            risk_score,
841            findings,
842        }
843    }
844
845    // Test SecurityAnalyzer creation and configuration
846    #[test]
847    fn test_analyzer_creation() {
848        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
849        assert_eq!(analyzer.architecture, Architecture::X86_64);
850    }
851
852    #[test]
853    fn test_analyzer_with_custom_config() {
854        let config = SecurityConfig {
855            detect_suspicious_apis: false,
856            detect_anti_debug: true,
857            detect_anti_vm: false,
858            detect_crypto: true,
859            detect_network: false,
860            detect_filesystem: true,
861            detect_registry: false,
862            min_string_length: 8,
863        };
864        let analyzer = SecurityAnalyzer::with_config(Architecture::Arm64, config.clone());
865        assert_eq!(analyzer.architecture, Architecture::Arm64);
866        assert!(!analyzer.config.detect_suspicious_apis);
867        assert!(analyzer.config.detect_anti_debug);
868        assert_eq!(analyzer.config.min_string_length, 8);
869    }
870
871    #[test]
872    fn test_security_config_default() {
873        let config = SecurityConfig::default();
874        assert!(config.detect_suspicious_apis);
875        assert!(config.detect_anti_debug);
876        assert!(config.detect_anti_vm);
877        assert!(config.detect_crypto);
878        assert!(config.detect_network);
879        assert!(config.detect_filesystem);
880        assert!(config.detect_registry);
881        assert_eq!(config.min_string_length, 4);
882    }
883
884    // Test analyze_imports method
885    #[test]
886    fn test_analyze_imports_suspicious_apis() {
887        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
888        let mut indicators = SecurityIndicators::default();
889        let mut findings = Vec::new();
890
891        let imports = vec![
892            Import {
893                name: "VirtualAllocEx".to_string(),
894                library: Some("kernel32.dll".to_string()),
895                address: Some(0x1000),
896                ordinal: None,
897            },
898            Import {
899                name: "WriteProcessMemory".to_string(),
900                library: Some("kernel32.dll".to_string()),
901                address: Some(0x1004),
902                ordinal: None,
903            },
904            Import {
905                name: "RegularFunction".to_string(),
906                library: Some("user32.dll".to_string()),
907                address: Some(0x1008),
908                ordinal: None,
909            },
910        ];
911
912        analyzer.analyze_imports(&imports, &mut indicators, &mut findings);
913
914        assert_eq!(indicators.suspicious_apis.len(), 2);
915        assert!(
916            indicators
917                .suspicious_apis
918                .contains(&"VirtualAllocEx".to_string())
919        );
920        assert!(
921            indicators
922                .suspicious_apis
923                .contains(&"WriteProcessMemory".to_string())
924        );
925
926        let suspicious_findings: Vec<_> = findings
927            .iter()
928            .filter(|f| f.category == FindingCategory::SuspiciousApi)
929            .collect();
930        assert_eq!(suspicious_findings.len(), 2);
931        assert_eq!(suspicious_findings[0].severity, Severity::High);
932    }
933
934    #[test]
935    fn test_analyze_imports_anti_debug() {
936        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
937        let mut indicators = SecurityIndicators::default();
938        let mut findings = Vec::new();
939
940        let imports = vec![
941            Import {
942                name: "IsDebuggerPresent".to_string(),
943                library: Some("kernel32.dll".to_string()),
944                address: Some(0x1000),
945                ordinal: None,
946            },
947            Import {
948                name: "CheckRemoteDebuggerPresent".to_string(),
949                library: Some("kernel32.dll".to_string()),
950                address: Some(0x1004),
951                ordinal: None,
952            },
953        ];
954
955        analyzer.analyze_imports(&imports, &mut indicators, &mut findings);
956
957        assert_eq!(indicators.anti_debug.len(), 2);
958        assert!(
959            indicators
960                .anti_debug
961                .contains(&"IsDebuggerPresent".to_string())
962        );
963        assert!(
964            indicators
965                .anti_debug
966                .contains(&"CheckRemoteDebuggerPresent".to_string())
967        );
968
969        let anti_debug_findings: Vec<_> = findings
970            .iter()
971            .filter(|f| f.category == FindingCategory::AntiDebug)
972            .collect();
973        assert_eq!(anti_debug_findings.len(), 2);
974        assert_eq!(anti_debug_findings[0].severity, Severity::Medium);
975    }
976
977    #[test]
978    fn test_analyze_imports_anti_vm() {
979        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
980        let mut indicators = SecurityIndicators::default();
981        let mut findings = Vec::new();
982
983        let imports = vec![
984            Import {
985                name: "GetSystemInfo".to_string(),
986                library: Some("kernel32.dll".to_string()),
987                address: Some(0x1000),
988                ordinal: None,
989            },
990            Import {
991                name: "GlobalMemoryStatusEx".to_string(),
992                library: Some("kernel32.dll".to_string()),
993                address: Some(0x1004),
994                ordinal: None,
995            },
996        ];
997
998        analyzer.analyze_imports(&imports, &mut indicators, &mut findings);
999
1000        assert_eq!(indicators.anti_vm.len(), 2);
1001        let anti_vm_findings: Vec<_> = findings
1002            .iter()
1003            .filter(|f| f.category == FindingCategory::AntiVm)
1004            .collect();
1005        assert_eq!(anti_vm_findings.len(), 2);
1006        assert_eq!(anti_vm_findings[0].severity, Severity::Medium);
1007    }
1008
1009    #[test]
1010    fn test_analyze_imports_crypto() {
1011        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1012        let mut indicators = SecurityIndicators::default();
1013        let mut findings = Vec::new();
1014
1015        let imports = vec![
1016            Import {
1017                name: "CryptAcquireContext".to_string(),
1018                library: Some("advapi32.dll".to_string()),
1019                address: Some(0x1000),
1020                ordinal: None,
1021            },
1022            Import {
1023                name: "CryptEncrypt".to_string(),
1024                library: Some("advapi32.dll".to_string()),
1025                address: Some(0x1004),
1026                ordinal: None,
1027            },
1028        ];
1029
1030        analyzer.analyze_imports(&imports, &mut indicators, &mut findings);
1031
1032        assert_eq!(indicators.crypto_indicators.len(), 2);
1033        let crypto_findings: Vec<_> = findings
1034            .iter()
1035            .filter(|f| f.category == FindingCategory::Cryptographic)
1036            .collect();
1037        assert_eq!(crypto_findings.len(), 2);
1038        assert_eq!(crypto_findings[0].severity, Severity::Info);
1039    }
1040
1041    #[test]
1042    fn test_analyze_imports_network() {
1043        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1044        let mut indicators = SecurityIndicators::default();
1045        let mut findings = Vec::new();
1046
1047        let imports = vec![
1048            Import {
1049                name: "WSAStartup".to_string(),
1050                library: Some("ws2_32.dll".to_string()),
1051                address: Some(0x1000),
1052                ordinal: None,
1053            },
1054            Import {
1055                name: "socket".to_string(),
1056                library: Some("ws2_32.dll".to_string()),
1057                address: Some(0x1004),
1058                ordinal: None,
1059            },
1060        ];
1061
1062        analyzer.analyze_imports(&imports, &mut indicators, &mut findings);
1063
1064        assert_eq!(indicators.network_indicators.len(), 2);
1065        let network_findings: Vec<_> = findings
1066            .iter()
1067            .filter(|f| f.category == FindingCategory::Network)
1068            .collect();
1069        assert_eq!(network_findings.len(), 2);
1070        assert_eq!(network_findings[0].severity, Severity::Low);
1071    }
1072
1073    #[test]
1074    fn test_analyze_imports_filesystem() {
1075        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1076        let mut indicators = SecurityIndicators::default();
1077        let mut findings = Vec::new();
1078
1079        let imports = vec![
1080            Import {
1081                name: "CreateFile".to_string(),
1082                library: Some("kernel32.dll".to_string()),
1083                address: Some(0x1000),
1084                ordinal: None,
1085            },
1086            Import {
1087                name: "DeleteFile".to_string(),
1088                library: Some("kernel32.dll".to_string()),
1089                address: Some(0x1004),
1090                ordinal: None,
1091            },
1092        ];
1093
1094        analyzer.analyze_imports(&imports, &mut indicators, &mut findings);
1095
1096        assert_eq!(indicators.filesystem_indicators.len(), 2);
1097        let fs_findings: Vec<_> = findings
1098            .iter()
1099            .filter(|f| f.category == FindingCategory::Filesystem)
1100            .collect();
1101        assert_eq!(fs_findings.len(), 2);
1102        assert_eq!(fs_findings[0].severity, Severity::Low);
1103    }
1104
1105    #[test]
1106    fn test_analyze_imports_registry() {
1107        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1108        let mut indicators = SecurityIndicators::default();
1109        let mut findings = Vec::new();
1110
1111        let imports = vec![
1112            Import {
1113                name: "RegOpenKeyEx".to_string(),
1114                library: Some("advapi32.dll".to_string()),
1115                address: Some(0x1000),
1116                ordinal: None,
1117            },
1118            Import {
1119                name: "RegSetValueEx".to_string(),
1120                library: Some("advapi32.dll".to_string()),
1121                address: Some(0x1004),
1122                ordinal: None,
1123            },
1124        ];
1125
1126        analyzer.analyze_imports(&imports, &mut indicators, &mut findings);
1127
1128        assert_eq!(indicators.registry_indicators.len(), 2);
1129        let reg_findings: Vec<_> = findings
1130            .iter()
1131            .filter(|f| f.category == FindingCategory::Registry)
1132            .collect();
1133        assert_eq!(reg_findings.len(), 2);
1134        assert_eq!(reg_findings[0].severity, Severity::Low);
1135    }
1136
1137    // Test analyze_sections method
1138    #[test]
1139    fn test_analyze_sections_rwx_section() {
1140        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1141        let mut indicators = SecurityIndicators::default();
1142        let mut findings = Vec::new();
1143
1144        let sections = vec![
1145            Section {
1146                name: ".text".to_string(),
1147                address: 0x1000,
1148                size: 0x500,
1149                offset: 0x400,
1150                file_size: 0x500,
1151                permissions: SectionPermissions {
1152                    read: true,
1153                    write: false,
1154                    execute: true,
1155                },
1156                section_type: SectionType::Code,
1157                data: None,
1158            },
1159            Section {
1160                name: ".rwx_section".to_string(),
1161                address: 0x2000,
1162                size: 0x300,
1163                offset: 0x900,
1164                file_size: 0x300,
1165                permissions: SectionPermissions {
1166                    read: true,
1167                    write: true,
1168                    execute: true,
1169                },
1170                section_type: SectionType::Code,
1171                data: None,
1172            },
1173        ];
1174
1175        analyzer.analyze_sections(&sections, &mut indicators, &mut findings);
1176
1177        let code_injection_findings: Vec<_> = findings
1178            .iter()
1179            .filter(|f| f.category == FindingCategory::CodeInjection)
1180            .collect();
1181        assert_eq!(code_injection_findings.len(), 1);
1182        assert_eq!(code_injection_findings[0].severity, Severity::High);
1183        assert!(code_injection_findings[0].description.contains("RWX"));
1184    }
1185
1186    #[test]
1187    fn test_analyze_sections_suspicious_names() {
1188        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1189        let mut indicators = SecurityIndicators::default();
1190        let mut findings = Vec::new();
1191
1192        let sections = vec![
1193            Section {
1194                name: ".upx0".to_string(),
1195                address: 0x1000,
1196                size: 0x500,
1197                offset: 0x400,
1198                file_size: 0x500,
1199                permissions: SectionPermissions {
1200                    read: true,
1201                    write: false,
1202                    execute: true,
1203                },
1204                section_type: SectionType::Code,
1205                data: None,
1206            },
1207            Section {
1208                name: ".packed".to_string(),
1209                address: 0x2000,
1210                size: 0x300,
1211                offset: 0x900,
1212                file_size: 0x300,
1213                permissions: SectionPermissions {
1214                    read: true,
1215                    write: false,
1216                    execute: false,
1217                },
1218                section_type: SectionType::Data,
1219                data: None,
1220            },
1221        ];
1222
1223        analyzer.analyze_sections(&sections, &mut indicators, &mut findings);
1224
1225        let obfuscation_findings: Vec<_> = findings
1226            .iter()
1227            .filter(|f| f.category == FindingCategory::Obfuscation)
1228            .collect();
1229        assert_eq!(obfuscation_findings.len(), 2);
1230        assert_eq!(obfuscation_findings[0].severity, Severity::Medium);
1231    }
1232
1233    // Test analyze_symbols method
1234    #[test]
1235    fn test_analyze_symbols_suspicious() {
1236        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1237        let mut indicators = SecurityIndicators::default();
1238        let mut findings = Vec::new();
1239
1240        let symbols = vec![
1241            Symbol {
1242                name: "main".to_string(),
1243                demangled_name: None,
1244                address: 0x1000,
1245                size: 100,
1246                symbol_type: SymbolType::Function,
1247                binding: SymbolBinding::Global,
1248                visibility: SymbolVisibility::Default,
1249                section_index: Some(0),
1250            },
1251            Symbol {
1252                name: "inject_payload".to_string(),
1253                demangled_name: None,
1254                address: 0x1100,
1255                size: 50,
1256                symbol_type: SymbolType::Function,
1257                binding: SymbolBinding::Local,
1258                visibility: SymbolVisibility::Hidden,
1259                section_index: Some(0),
1260            },
1261            Symbol {
1262                name: "bypass_security".to_string(),
1263                demangled_name: None,
1264                address: 0x1200,
1265                size: 75,
1266                symbol_type: SymbolType::Function,
1267                binding: SymbolBinding::Global,
1268                visibility: SymbolVisibility::Default,
1269                section_index: Some(0),
1270            },
1271        ];
1272
1273        analyzer.analyze_symbols(&symbols, &mut indicators, &mut findings);
1274
1275        let suspicious_symbol_findings: Vec<_> = findings
1276            .iter()
1277            .filter(|f| f.category == FindingCategory::SuspiciousApi && f.data.is_some())
1278            .collect();
1279        assert_eq!(suspicious_symbol_findings.len(), 2);
1280        assert_eq!(suspicious_symbol_findings[0].severity, Severity::Medium);
1281    }
1282
1283    // Test analyze_security_features method
1284    #[test]
1285    fn test_analyze_security_features_missing() {
1286        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1287        let mut findings = Vec::new();
1288
1289        let features = SecurityFeatures {
1290            nx_bit: false,
1291            aslr: false,
1292            stack_canary: false,
1293            cfi: false,
1294            fortify: true,
1295            pie: false,
1296            relro: true,
1297            signed: false,
1298        };
1299
1300        analyzer.analyze_security_features(BinaryFormat::Pe, &features, &mut findings);
1301
1302        let missing_security_findings: Vec<_> = findings
1303            .iter()
1304            .filter(|f| f.category == FindingCategory::MissingSecurity)
1305            .collect();
1306
1307        // Should find 4 missing features: nx_bit, aslr, stack_canary, cfi
1308        assert_eq!(missing_security_findings.len(), 4);
1309
1310        // Check specific findings
1311        assert!(findings.iter().any(|f| f.description.contains("NX/DEP")));
1312        assert!(findings.iter().any(|f| f.description.contains("ASLR")));
1313        assert!(
1314            findings
1315                .iter()
1316                .any(|f| f.description.contains("Stack canaries"))
1317        );
1318        assert!(
1319            findings
1320                .iter()
1321                .any(|f| f.description.contains("Control Flow Integrity"))
1322        );
1323    }
1324
1325    #[test]
1326    fn test_analyze_security_features_all_enabled() {
1327        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1328        let mut findings = Vec::new();
1329
1330        let features = SecurityFeatures {
1331            nx_bit: true,
1332            aslr: true,
1333            stack_canary: true,
1334            cfi: true,
1335            fortify: true,
1336            pie: true,
1337            relro: true,
1338            signed: true,
1339        };
1340
1341        analyzer.analyze_security_features(BinaryFormat::Pe, &features, &mut findings);
1342
1343        let missing_security_findings: Vec<_> = findings
1344            .iter()
1345            .filter(|f| f.category == FindingCategory::MissingSecurity)
1346            .collect();
1347        assert_eq!(missing_security_findings.len(), 0);
1348    }
1349
1350    // Test calculate_risk_score method
1351    #[test]
1352    fn test_calculate_risk_score_low_risk() {
1353        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1354
1355        let indicators = SecurityIndicators {
1356            suspicious_apis: vec![],
1357            anti_debug: vec![],
1358            anti_vm: vec![],
1359            crypto_indicators: vec!["CryptAcquireContext".to_string()],
1360            network_indicators: vec![],
1361            filesystem_indicators: vec!["CreateFile".to_string()],
1362            registry_indicators: vec![],
1363        };
1364
1365        let features = SecurityFeatures {
1366            nx_bit: true,
1367            aslr: true,
1368            stack_canary: true,
1369            cfi: true,
1370            fortify: true,
1371            pie: true,
1372            relro: true,
1373            signed: true,
1374        };
1375
1376        let findings = vec![
1377            SecurityFinding {
1378                category: FindingCategory::Cryptographic,
1379                severity: Severity::Info,
1380                description: "Crypto API".to_string(),
1381                location: None,
1382                data: None,
1383            },
1384            SecurityFinding {
1385                category: FindingCategory::Filesystem,
1386                severity: Severity::Low,
1387                description: "File API".to_string(),
1388                location: None,
1389                data: None,
1390            },
1391        ];
1392
1393        let risk_score =
1394            analyzer.calculate_risk_score(BinaryFormat::Pe, &indicators, &features, &findings);
1395        assert!(risk_score < 10.0); // Should be low risk
1396    }
1397
1398    #[test]
1399    fn test_calculate_risk_score_high_risk() {
1400        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1401
1402        let indicators = SecurityIndicators {
1403            suspicious_apis: vec![
1404                "VirtualAllocEx".to_string(),
1405                "WriteProcessMemory".to_string(),
1406            ],
1407            anti_debug: vec!["IsDebuggerPresent".to_string()],
1408            anti_vm: vec!["GetSystemInfo".to_string()],
1409            crypto_indicators: vec![],
1410            network_indicators: vec!["socket".to_string()],
1411            filesystem_indicators: vec![],
1412            registry_indicators: vec!["RegSetValueEx".to_string()],
1413        };
1414
1415        let features = SecurityFeatures {
1416            nx_bit: false,
1417            aslr: false,
1418            stack_canary: false,
1419            cfi: false,
1420            fortify: false,
1421            pie: false,
1422            relro: false,
1423            signed: false,
1424        };
1425
1426        let findings = vec![
1427            SecurityFinding {
1428                category: FindingCategory::SuspiciousApi,
1429                severity: Severity::High,
1430                description: "Suspicious API".to_string(),
1431                location: None,
1432                data: None,
1433            },
1434            SecurityFinding {
1435                category: FindingCategory::CodeInjection,
1436                severity: Severity::Critical,
1437                description: "RWX section".to_string(),
1438                location: None,
1439                data: None,
1440            },
1441            SecurityFinding {
1442                category: FindingCategory::MissingSecurity,
1443                severity: Severity::Medium,
1444                description: "Missing ASLR".to_string(),
1445                location: None,
1446                data: None,
1447            },
1448        ];
1449
1450        let risk_score =
1451            analyzer.calculate_risk_score(BinaryFormat::Pe, &indicators, &features, &findings);
1452        assert!(risk_score > 50.0); // Should be high risk
1453    }
1454
1455    // Test API categorization methods
1456    #[test]
1457    fn test_get_suspicious_apis() {
1458        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1459        let apis = analyzer.get_suspicious_apis();
1460
1461        assert!(apis.contains("VirtualAllocEx"));
1462        assert!(apis.contains("WriteProcessMemory"));
1463        assert!(apis.contains("CreateRemoteThread"));
1464        assert!(apis.contains("SetWindowsHookEx"));
1465        assert!(apis.contains("OpenProcess"));
1466        assert!(apis.contains("AdjustTokenPrivileges"));
1467        assert!(!apis.contains("CreateFile")); // Should not be in suspicious APIs
1468    }
1469
1470    #[test]
1471    fn test_get_anti_debug_apis() {
1472        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1473        let apis = analyzer.get_anti_debug_apis();
1474
1475        assert!(apis.contains("IsDebuggerPresent"));
1476        assert!(apis.contains("CheckRemoteDebuggerPresent"));
1477        assert!(apis.contains("NtQueryInformationProcess"));
1478        assert!(apis.contains("OutputDebugString"));
1479        assert!(apis.contains("ptrace"));
1480        assert!(!apis.contains("VirtualAllocEx")); // Should not be in anti-debug APIs
1481    }
1482
1483    #[test]
1484    fn test_get_anti_vm_apis() {
1485        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1486        let apis = analyzer.get_anti_vm_apis();
1487
1488        assert!(apis.contains("GetSystemInfo"));
1489        assert!(apis.contains("GlobalMemoryStatusEx"));
1490        assert!(apis.contains("GetAdaptersInfo"));
1491        assert!(apis.contains("GetVolumeInformation"));
1492        assert!(!apis.contains("IsDebuggerPresent")); // Should not be in anti-VM APIs
1493    }
1494
1495    #[test]
1496    fn test_get_crypto_apis() {
1497        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1498        let apis = analyzer.get_crypto_apis();
1499
1500        assert!(apis.contains("CryptAcquireContext"));
1501        assert!(apis.contains("CryptCreateHash"));
1502        assert!(apis.contains("CryptEncrypt"));
1503        assert!(apis.contains("CryptDecrypt"));
1504        assert!(!apis.contains("socket")); // Should not be in crypto APIs
1505    }
1506
1507    #[test]
1508    fn test_get_network_apis() {
1509        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1510        let apis = analyzer.get_network_apis();
1511
1512        assert!(apis.contains("WSAStartup"));
1513        assert!(apis.contains("socket"));
1514        assert!(apis.contains("connect"));
1515        assert!(apis.contains("InternetOpen"));
1516        assert!(!apis.contains("CreateFile")); // Should not be in network APIs
1517    }
1518
1519    #[test]
1520    fn test_get_filesystem_apis() {
1521        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1522        let apis = analyzer.get_filesystem_apis();
1523
1524        assert!(apis.contains("CreateFile"));
1525        assert!(apis.contains("WriteFile"));
1526        assert!(apis.contains("DeleteFile"));
1527        assert!(apis.contains("FindFirstFile"));
1528        assert!(!apis.contains("RegOpenKeyEx")); // Should not be in filesystem APIs
1529    }
1530
1531    #[test]
1532    fn test_get_registry_apis() {
1533        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1534        let apis = analyzer.get_registry_apis();
1535
1536        assert!(apis.contains("RegOpenKeyEx"));
1537        assert!(apis.contains("RegCreateKeyEx"));
1538        assert!(apis.contains("RegSetValueEx"));
1539        assert!(apis.contains("RegDeleteKey"));
1540        assert!(!apis.contains("CreateFile")); // Should not be in registry APIs
1541    }
1542
1543    // Test section and symbol name detection
1544    #[test]
1545    fn test_suspicious_section_detection() {
1546        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1547
1548        // Test suspicious section names
1549        assert!(analyzer.is_suspicious_section_name(".upx0"));
1550        assert!(analyzer.is_suspicious_section_name(".packed"));
1551        assert!(analyzer.is_suspicious_section_name(".THEMIDA"));
1552        assert!(analyzer.is_suspicious_section_name(".aspack"));
1553        assert!(analyzer.is_suspicious_section_name(".enigma"));
1554        assert!(analyzer.is_suspicious_section_name(".vmprotect"));
1555        assert!(analyzer.is_suspicious_section_name(".shell"));
1556
1557        // Test case insensitivity
1558        assert!(analyzer.is_suspicious_section_name(".UPX0"));
1559        assert!(analyzer.is_suspicious_section_name(".PACKED"));
1560
1561        // Test normal section names
1562        assert!(!analyzer.is_suspicious_section_name(".text"));
1563        assert!(!analyzer.is_suspicious_section_name(".data"));
1564        assert!(!analyzer.is_suspicious_section_name(".bss"));
1565        assert!(!analyzer.is_suspicious_section_name(".rdata"));
1566    }
1567
1568    #[test]
1569    fn test_suspicious_symbol_detection() {
1570        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1571
1572        // Test suspicious symbol patterns
1573        assert!(analyzer.is_suspicious_symbol_name("inject_code"));
1574        assert!(analyzer.is_suspicious_symbol_name("bypass_check"));
1575        assert!(analyzer.is_suspicious_symbol_name("hook_function"));
1576        assert!(analyzer.is_suspicious_symbol_name("shellcode_payload"));
1577        assert!(analyzer.is_suspicious_symbol_name("exploit_buffer"));
1578        assert!(analyzer.is_suspicious_symbol_name("backdoor_entry"));
1579        assert!(analyzer.is_suspicious_symbol_name("rootkit_hide"));
1580        assert!(analyzer.is_suspicious_symbol_name("keylogger_start"));
1581        assert!(analyzer.is_suspicious_symbol_name("stealth_mode"));
1582
1583        // Test case insensitivity
1584        assert!(analyzer.is_suspicious_symbol_name("INJECT_CODE"));
1585        assert!(analyzer.is_suspicious_symbol_name("Bypass_Check"));
1586
1587        // Test normal symbol names
1588        assert!(!analyzer.is_suspicious_symbol_name("main"));
1589        assert!(!analyzer.is_suspicious_symbol_name("printf"));
1590        assert!(!analyzer.is_suspicious_symbol_name("malloc"));
1591        assert!(!analyzer.is_suspicious_symbol_name("strcmp"));
1592        assert!(!analyzer.is_suspicious_symbol_name("init_function"));
1593    }
1594
1595    // Test severity ordering
1596    #[test]
1597    fn test_severity_ordering() {
1598        assert!(Severity::Critical > Severity::High);
1599        assert!(Severity::High > Severity::Medium);
1600        assert!(Severity::Medium > Severity::Low);
1601        assert!(Severity::Low > Severity::Info);
1602    }
1603
1604    #[test]
1605    fn test_security_finding_categories() {
1606        // Test that all expected categories exist
1607        let categories = [
1608            FindingCategory::SuspiciousApi,
1609            FindingCategory::AntiDebug,
1610            FindingCategory::AntiVm,
1611            FindingCategory::Cryptographic,
1612            FindingCategory::Network,
1613            FindingCategory::Filesystem,
1614            FindingCategory::Registry,
1615            FindingCategory::MissingSecurity,
1616            FindingCategory::Obfuscation,
1617            FindingCategory::CodeInjection,
1618            FindingCategory::PrivilegeEscalation,
1619        ];
1620
1621        for category in &categories {
1622            let finding = SecurityFinding {
1623                category: category.clone(),
1624                severity: Severity::Medium,
1625                description: "Test finding".to_string(),
1626                location: None,
1627                data: None,
1628            };
1629            assert_eq!(finding.category, *category);
1630        }
1631    }
1632
1633    // Test edge cases and error handling
1634    #[test]
1635    fn test_analyze_imports_empty() {
1636        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1637        let mut indicators = SecurityIndicators::default();
1638        let mut findings = Vec::new();
1639
1640        analyzer.analyze_imports(&[], &mut indicators, &mut findings);
1641
1642        assert_eq!(indicators.suspicious_apis.len(), 0);
1643        assert_eq!(findings.len(), 0);
1644    }
1645
1646    #[test]
1647    fn repeated_import_names_do_not_duplicate_findings_or_score() {
1648        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1649        let import = Import {
1650            name: "VirtualAllocEx".to_string(),
1651            library: Some("kernel32.dll".to_string()),
1652            address: Some(0x1000),
1653            ordinal: None,
1654        };
1655        let imports = vec![import; 1_000];
1656        let mut indicators = SecurityIndicators::default();
1657        let mut findings = Vec::new();
1658
1659        analyzer.analyze_imports(&imports, &mut indicators, &mut findings);
1660        SecurityAnalyzer::canonicalize_indicators(&mut indicators);
1661
1662        assert_eq!(indicators.suspicious_apis, ["VirtualAllocEx"]);
1663        assert_eq!(findings.len(), 1);
1664
1665        let features = SecurityFeatures {
1666            nx_bit: true,
1667            aslr: true,
1668            stack_canary: true,
1669            cfi: true,
1670            fortify: true,
1671            pie: true,
1672            relro: true,
1673            signed: true,
1674        };
1675        assert_eq!(
1676            analyzer.calculate_risk_score(BinaryFormat::Pe, &indicators, &features, &findings,),
1677            10.0
1678        );
1679    }
1680
1681    #[test]
1682    fn non_native_formats_do_not_report_or_score_missing_native_hardening() {
1683        let analyzer = SecurityAnalyzer::new(Architecture::Unknown);
1684        let features = SecurityFeatures::default();
1685        let indicators = SecurityIndicators::default();
1686
1687        for format in [BinaryFormat::Java, BinaryFormat::Wasm, BinaryFormat::Raw] {
1688            let mut findings = Vec::new();
1689            analyzer.analyze_security_features(format, &features, &mut findings);
1690
1691            assert!(findings.is_empty(), "unexpected findings for {format}");
1692            assert_eq!(
1693                analyzer.calculate_risk_score(format, &indicators, &features, &findings),
1694                0.0,
1695                "unexpected hardening penalty for {format}"
1696            );
1697        }
1698
1699        let raw = BinaryFile::parse(b"plain raw bytes").unwrap();
1700        let result = SecurityAnalyzer::new(raw.architecture())
1701            .analyze(&raw)
1702            .unwrap();
1703        assert!(result.findings.is_empty());
1704        assert_eq!(result.risk_score, 0.0);
1705    }
1706
1707    #[test]
1708    fn test_analyze_sections_empty() {
1709        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1710        let mut indicators = SecurityIndicators::default();
1711        let mut findings = Vec::new();
1712
1713        analyzer.analyze_sections(&[], &mut indicators, &mut findings);
1714        assert_eq!(findings.len(), 0);
1715    }
1716
1717    #[test]
1718    fn repeated_section_and_symbol_records_do_not_duplicate_findings() {
1719        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1720        let fixture = create_test_binary_file();
1721        let sections = vec![fixture.sections[1].clone(); 1_000];
1722        let symbols = vec![fixture.symbols[1].clone(); 1_000];
1723        let mut indicators = SecurityIndicators::default();
1724        let mut findings = Vec::new();
1725
1726        analyzer.analyze_sections(&sections, &mut indicators, &mut findings);
1727        analyzer.analyze_symbols(&symbols, &mut indicators, &mut findings);
1728
1729        assert_eq!(
1730            findings
1731                .iter()
1732                .filter(|finding| finding.category == FindingCategory::CodeInjection)
1733                .count(),
1734            1
1735        );
1736        assert_eq!(
1737            findings
1738                .iter()
1739                .filter(|finding| finding.category == FindingCategory::Obfuscation)
1740                .count(),
1741            1
1742        );
1743        assert_eq!(
1744            findings
1745                .iter()
1746                .filter(|finding| {
1747                    finding.category == FindingCategory::SuspiciousApi
1748                        && finding.description.starts_with("Suspicious symbol:")
1749                })
1750                .count(),
1751            1
1752        );
1753    }
1754
1755    #[test]
1756    fn test_analyze_symbols_empty() {
1757        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1758        let mut indicators = SecurityIndicators::default();
1759        let mut findings = Vec::new();
1760
1761        analyzer.analyze_symbols(&[], &mut indicators, &mut findings);
1762        assert_eq!(findings.len(), 0);
1763    }
1764
1765    #[test]
1766    fn test_risk_score_bounds() {
1767        let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1768
1769        // Test that risk score is always between 0 and 100
1770        let indicators = SecurityIndicators::default();
1771        let features = SecurityFeatures::default();
1772
1773        // Empty findings should give 0 or very low score
1774        let empty_findings = vec![];
1775        let score = analyzer.calculate_risk_score(
1776            BinaryFormat::Pe,
1777            &indicators,
1778            &features,
1779            &empty_findings,
1780        );
1781        assert!((0.0..=100.0).contains(&score));
1782
1783        // Maximum risk scenario
1784        let high_risk_indicators = SecurityIndicators {
1785            suspicious_apis: (0..20).map(|i| format!("api_{}", i)).collect(),
1786            anti_debug: (0..10).map(|i| format!("debug_{}", i)).collect(),
1787            anti_vm: (0..10).map(|i| format!("vm_{}", i)).collect(),
1788            crypto_indicators: (0..5).map(|i| format!("crypto_{}", i)).collect(),
1789            network_indicators: (0..5).map(|i| format!("net_{}", i)).collect(),
1790            filesystem_indicators: (0..5).map(|i| format!("fs_{}", i)).collect(),
1791            registry_indicators: (0..5).map(|i| format!("reg_{}", i)).collect(),
1792        };
1793
1794        let insecure_features = SecurityFeatures {
1795            nx_bit: false,
1796            aslr: false,
1797            stack_canary: false,
1798            cfi: false,
1799            fortify: false,
1800            pie: false,
1801            relro: false,
1802            signed: false,
1803        };
1804
1805        let critical_findings: Vec<SecurityFinding> = (0..10)
1806            .map(|i| SecurityFinding {
1807                category: FindingCategory::SuspiciousApi,
1808                severity: Severity::Critical,
1809                description: format!("Critical finding {}", i),
1810                location: None,
1811                data: None,
1812            })
1813            .collect();
1814
1815        let max_score = analyzer.calculate_risk_score(
1816            BinaryFormat::Pe,
1817            &high_risk_indicators,
1818            &insecure_features,
1819            &critical_findings,
1820        );
1821        assert!(max_score <= 100.0);
1822    }
1823
1824    // Test disabled config scenarios
1825    #[test]
1826    fn test_analyze_with_disabled_suspicious_apis() {
1827        let config = SecurityConfig {
1828            detect_suspicious_apis: false,
1829            ..SecurityConfig::default()
1830        };
1831        let analyzer = SecurityAnalyzer::with_config(Architecture::X86_64, config);
1832        let mut indicators = SecurityIndicators::default();
1833        let mut findings = Vec::new();
1834
1835        let imports = vec![Import {
1836            name: "VirtualAllocEx".to_string(),
1837            library: Some("kernel32.dll".to_string()),
1838            address: Some(0x1000),
1839            ordinal: None,
1840        }];
1841
1842        analyzer.analyze_imports(&imports, &mut indicators, &mut findings);
1843
1844        assert!(indicators.suspicious_apis.is_empty());
1845        assert!(findings.is_empty());
1846
1847        // But we can test that the config affects the main analyze flow
1848        // by testing a scenario with different detection flags
1849        let config2 = SecurityConfig {
1850            detect_anti_debug: false,
1851            detect_anti_vm: false,
1852            detect_crypto: false,
1853            detect_network: false,
1854            detect_filesystem: false,
1855            detect_registry: false,
1856            ..SecurityConfig::default()
1857        };
1858        let analyzer2 = SecurityAnalyzer::with_config(Architecture::X86_64, config2);
1859
1860        // Test that individual detection methods can be configured
1861        assert!(!analyzer2.config.detect_anti_debug);
1862        assert!(!analyzer2.config.detect_anti_vm);
1863        assert!(analyzer2.config.detect_suspicious_apis); // This one is still enabled
1864    }
1865
1866    // Integration tests for the main analyze method and analyze_binary_security function
1867    #[test]
1868    fn test_complete_security_analysis_high_risk() {
1869        // Create a high-risk mock binary
1870        let binary = MockBinaryFile {
1871            imports: vec![
1872                Import {
1873                    name: "VirtualAllocEx".to_string(),
1874                    library: Some("kernel32.dll".to_string()),
1875                    address: Some(0x1000),
1876                    ordinal: None,
1877                },
1878                Import {
1879                    name: "WriteProcessMemory".to_string(),
1880                    library: Some("kernel32.dll".to_string()),
1881                    address: Some(0x1004),
1882                    ordinal: None,
1883                },
1884                Import {
1885                    name: "IsDebuggerPresent".to_string(),
1886                    library: Some("kernel32.dll".to_string()),
1887                    address: Some(0x1008),
1888                    ordinal: None,
1889                },
1890            ],
1891            sections: vec![
1892                Section {
1893                    name: ".text".to_string(),
1894                    address: 0x1000,
1895                    size: 0x500,
1896                    offset: 0x400,
1897                    file_size: 0x500,
1898                    permissions: SectionPermissions {
1899                        read: true,
1900                        write: false,
1901                        execute: true,
1902                    },
1903                    section_type: SectionType::Code,
1904                    data: None,
1905                },
1906                Section {
1907                    name: ".rwx_evil".to_string(),
1908                    address: 0x2000,
1909                    size: 0x300,
1910                    offset: 0x900,
1911                    file_size: 0x300,
1912                    permissions: SectionPermissions {
1913                        read: true,
1914                        write: true,
1915                        execute: true,
1916                    },
1917                    section_type: SectionType::Code,
1918                    data: None,
1919                },
1920                Section {
1921                    name: ".upx0".to_string(),
1922                    address: 0x3000,
1923                    size: 0x200,
1924                    offset: 0xc00,
1925                    file_size: 0x200,
1926                    permissions: SectionPermissions {
1927                        read: true,
1928                        write: false,
1929                        execute: true,
1930                    },
1931                    section_type: SectionType::Code,
1932                    data: None,
1933                },
1934            ],
1935            symbols: vec![
1936                Symbol {
1937                    name: "main".to_string(),
1938                    demangled_name: None,
1939                    address: 0x1000,
1940                    size: 100,
1941                    symbol_type: SymbolType::Function,
1942                    binding: SymbolBinding::Global,
1943                    visibility: SymbolVisibility::Default,
1944                    section_index: Some(0),
1945                },
1946                Symbol {
1947                    name: "inject_shellcode".to_string(),
1948                    demangled_name: None,
1949                    address: 0x1100,
1950                    size: 50,
1951                    symbol_type: SymbolType::Function,
1952                    binding: SymbolBinding::Local,
1953                    visibility: SymbolVisibility::Hidden,
1954                    section_index: Some(0),
1955                },
1956                Symbol {
1957                    name: "bypass_protection".to_string(),
1958                    demangled_name: None,
1959                    address: 0x1200,
1960                    size: 75,
1961                    symbol_type: SymbolType::Function,
1962                    binding: SymbolBinding::Global,
1963                    visibility: SymbolVisibility::Default,
1964                    section_index: Some(0),
1965                },
1966            ],
1967            metadata: BinaryMetadata {
1968                size: 2048,
1969                format: BinaryFormat::Pe,
1970                architecture: Architecture::X86_64,
1971                entry_point: Some(0x1000),
1972                base_address: Some(0x400000),
1973                timestamp: None,
1974                compiler_info: None,
1975                endian: Endianness::Little,
1976                security_features: SecurityFeatures {
1977                    nx_bit: false,       // Missing security feature
1978                    aslr: false,         // Missing security feature
1979                    stack_canary: false, // Missing security feature
1980                    cfi: false,          // Missing security feature
1981                    fortify: false,
1982                    pie: false,
1983                    relro: false,
1984                    signed: false,
1985                },
1986            },
1987        };
1988
1989        let result = analyze_mock_binary(&binary);
1990
1991        // Verify high-risk indicators
1992        assert!(!result.indicators.suspicious_apis.is_empty());
1993        assert!(
1994            result
1995                .indicators
1996                .suspicious_apis
1997                .contains(&"VirtualAllocEx".to_string())
1998        );
1999        assert!(
2000            result
2001                .indicators
2002                .suspicious_apis
2003                .contains(&"WriteProcessMemory".to_string())
2004        );
2005
2006        assert!(!result.indicators.anti_debug.is_empty());
2007        assert!(
2008            result
2009                .indicators
2010                .anti_debug
2011                .contains(&"IsDebuggerPresent".to_string())
2012        );
2013
2014        // Verify security features
2015        assert!(!result.features.nx_bit);
2016        assert!(!result.features.aslr);
2017        assert!(!result.features.stack_canary);
2018        assert!(!result.features.cfi);
2019
2020        // Verify findings
2021        assert!(!result.findings.is_empty());
2022
2023        // Should have suspicious API findings
2024        let suspicious_findings: Vec<_> = result
2025            .findings
2026            .iter()
2027            .filter(|f| f.category == FindingCategory::SuspiciousApi)
2028            .collect();
2029        assert!(!suspicious_findings.is_empty());
2030
2031        // Should have anti-debug findings
2032        let anti_debug_findings: Vec<_> = result
2033            .findings
2034            .iter()
2035            .filter(|f| f.category == FindingCategory::AntiDebug)
2036            .collect();
2037        assert!(!anti_debug_findings.is_empty());
2038
2039        // Should have code injection findings (RWX section)
2040        let code_injection_findings: Vec<_> = result
2041            .findings
2042            .iter()
2043            .filter(|f| f.category == FindingCategory::CodeInjection)
2044            .collect();
2045        assert!(!code_injection_findings.is_empty());
2046
2047        // Should have obfuscation findings (suspicious section name)
2048        let obfuscation_findings: Vec<_> = result
2049            .findings
2050            .iter()
2051            .filter(|f| f.category == FindingCategory::Obfuscation)
2052            .collect();
2053        assert!(!obfuscation_findings.is_empty());
2054
2055        // Should have missing security findings
2056        let missing_security_findings: Vec<_> = result
2057            .findings
2058            .iter()
2059            .filter(|f| f.category == FindingCategory::MissingSecurity)
2060            .collect();
2061        assert!(!missing_security_findings.is_empty());
2062
2063        // Should have high risk score due to multiple indicators
2064        assert!(
2065            result.risk_score > 30.0,
2066            "Risk score should be high for malicious binary"
2067        );
2068    }
2069
2070    #[test]
2071    fn test_complete_security_analysis_low_risk() {
2072        // Create a low-risk mock binary
2073        let binary = MockBinaryFile {
2074            imports: vec![
2075                Import {
2076                    name: "printf".to_string(),
2077                    library: Some("msvcrt.dll".to_string()),
2078                    address: Some(0x1000),
2079                    ordinal: None,
2080                },
2081                Import {
2082                    name: "malloc".to_string(),
2083                    library: Some("msvcrt.dll".to_string()),
2084                    address: Some(0x1004),
2085                    ordinal: None,
2086                },
2087                Import {
2088                    name: "ExitProcess".to_string(),
2089                    library: Some("kernel32.dll".to_string()),
2090                    address: Some(0x1008),
2091                    ordinal: None,
2092                },
2093            ],
2094            sections: vec![
2095                Section {
2096                    name: ".text".to_string(),
2097                    address: 0x1000,
2098                    size: 0x500,
2099                    offset: 0x400,
2100                    file_size: 0x500,
2101                    permissions: SectionPermissions {
2102                        read: true,
2103                        write: false,
2104                        execute: true,
2105                    },
2106                    section_type: SectionType::Code,
2107                    data: None,
2108                },
2109                Section {
2110                    name: ".data".to_string(),
2111                    address: 0x2000,
2112                    size: 0x300,
2113                    offset: 0x900,
2114                    file_size: 0x300,
2115                    permissions: SectionPermissions {
2116                        read: true,
2117                        write: true,
2118                        execute: false,
2119                    },
2120                    section_type: SectionType::Data,
2121                    data: None,
2122                },
2123                Section {
2124                    name: ".rdata".to_string(),
2125                    address: 0x3000,
2126                    size: 0x200,
2127                    offset: 0xc00,
2128                    file_size: 0x200,
2129                    permissions: SectionPermissions {
2130                        read: true,
2131                        write: false,
2132                        execute: false,
2133                    },
2134                    section_type: SectionType::Data,
2135                    data: None,
2136                },
2137            ],
2138            symbols: vec![
2139                Symbol {
2140                    name: "main".to_string(),
2141                    demangled_name: None,
2142                    address: 0x1000,
2143                    size: 100,
2144                    symbol_type: SymbolType::Function,
2145                    binding: SymbolBinding::Global,
2146                    visibility: SymbolVisibility::Default,
2147                    section_index: Some(0),
2148                },
2149                Symbol {
2150                    name: "print_hello".to_string(),
2151                    demangled_name: None,
2152                    address: 0x1100,
2153                    size: 50,
2154                    symbol_type: SymbolType::Function,
2155                    binding: SymbolBinding::Local,
2156                    visibility: SymbolVisibility::Default,
2157                    section_index: Some(0),
2158                },
2159            ],
2160            metadata: BinaryMetadata {
2161                size: 1024,
2162                format: BinaryFormat::Pe,
2163                architecture: Architecture::X86_64,
2164                entry_point: Some(0x1000),
2165                base_address: Some(0x400000),
2166                timestamp: None,
2167                compiler_info: None,
2168                endian: Endianness::Little,
2169                security_features: SecurityFeatures {
2170                    nx_bit: true,       // Security feature enabled
2171                    aslr: true,         // Security feature enabled
2172                    stack_canary: true, // Security feature enabled
2173                    cfi: true,          // Security feature enabled
2174                    fortify: true,
2175                    pie: true,
2176                    relro: true,
2177                    signed: true,
2178                },
2179            },
2180        };
2181
2182        let result = analyze_mock_binary(&binary);
2183
2184        // Verify low-risk indicators
2185        assert!(result.indicators.suspicious_apis.is_empty());
2186        assert!(result.indicators.anti_debug.is_empty());
2187        assert!(result.indicators.anti_vm.is_empty());
2188
2189        // Verify security features are enabled
2190        assert!(result.features.nx_bit);
2191        assert!(result.features.aslr);
2192        assert!(result.features.stack_canary);
2193        assert!(result.features.cfi);
2194
2195        // Should have no or very few security findings
2196        let suspicious_findings: Vec<_> = result
2197            .findings
2198            .iter()
2199            .filter(|f| {
2200                matches!(
2201                    f.category,
2202                    FindingCategory::SuspiciousApi
2203                        | FindingCategory::AntiDebug
2204                        | FindingCategory::AntiVm
2205                        | FindingCategory::CodeInjection
2206                )
2207            })
2208            .collect();
2209        assert!(suspicious_findings.is_empty());
2210
2211        // Should have no missing security findings since all features are enabled
2212        let missing_security_findings: Vec<_> = result
2213            .findings
2214            .iter()
2215            .filter(|f| f.category == FindingCategory::MissingSecurity)
2216            .collect();
2217        assert!(missing_security_findings.is_empty());
2218
2219        // Should have low risk score
2220        assert!(
2221            result.risk_score < 10.0,
2222            "Risk score should be low for benign binary"
2223        );
2224    }
2225
2226    #[test]
2227    fn test_analyze_binary_security_function() {
2228        // This test is simplified since analyze_binary_security expects a BinaryFile
2229        // but we can test the function exists and works with proper parameters
2230        let binary = create_test_binary_file();
2231
2232        // Test using our analyze_mock_binary helper which exercises the same logic
2233        let result = analyze_mock_binary(&binary);
2234
2235        // Verify the result contains expected fields
2236        assert!(!result.indicators.suspicious_apis.is_empty());
2237        assert!(result.risk_score > 0.0);
2238        assert!(!result.findings.is_empty());
2239
2240        // Verify that the function detected the suspicious API from our test data
2241        assert!(
2242            result
2243                .indicators
2244                .suspicious_apis
2245                .contains(&"VirtualAllocEx".to_string())
2246        );
2247    }
2248
2249    #[test]
2250    fn test_analyzer_different_architectures() {
2251        // Test that analyzer works with different architectures
2252        let architectures = [
2253            Architecture::X86,
2254            Architecture::X86_64,
2255            Architecture::Arm,
2256            Architecture::Arm64,
2257            Architecture::Mips,
2258            Architecture::PowerPC,
2259        ];
2260
2261        for arch in &architectures {
2262            let analyzer = SecurityAnalyzer::new(*arch);
2263            assert_eq!(analyzer.architecture, *arch);
2264
2265            // Test basic functionality with each architecture
2266            let config = SecurityConfig::default();
2267            let analyzer_with_config = SecurityAnalyzer::with_config(*arch, config);
2268            assert_eq!(analyzer_with_config.architecture, *arch);
2269        }
2270    }
2271
2272    #[test]
2273    fn test_security_analysis_result_completeness() {
2274        let binary = create_test_binary_file();
2275        let result = analyze_mock_binary(&binary);
2276
2277        // Verify all fields of SecurityAnalysisResult are populated
2278        // indicators should be populated
2279        assert!(
2280            !result.indicators.suspicious_apis.is_empty()
2281                || !result.indicators.anti_debug.is_empty()
2282                || !result.indicators.anti_vm.is_empty()
2283                || !result.indicators.crypto_indicators.is_empty()
2284                || !result.indicators.network_indicators.is_empty()
2285                || !result.indicators.filesystem_indicators.is_empty()
2286                || !result.indicators.registry_indicators.is_empty()
2287        );
2288
2289        // features should be copied from metadata
2290        // (values don't matter, just that they're populated)
2291        // risk_score should be calculated (>= 0.0)
2292        assert!(result.risk_score >= 0.0);
2293
2294        // findings should be populated when there are indicators
2295        assert!(!result.findings.is_empty());
2296    }
2297
2298    #[test]
2299    fn test_config_detection_flags() {
2300        // Test each detection flag individually
2301        let mut config = SecurityConfig {
2302            detect_suspicious_apis: false,
2303            detect_anti_debug: false,
2304            detect_anti_vm: false,
2305            detect_crypto: false,
2306            detect_network: false,
2307            detect_filesystem: false,
2308            detect_registry: false,
2309            min_string_length: 4,
2310        };
2311
2312        // Enable only suspicious API detection
2313        config.detect_suspicious_apis = true;
2314        let analyzer = SecurityAnalyzer::with_config(Architecture::X86_64, config.clone());
2315        let mut indicators = SecurityIndicators::default();
2316        let mut findings = Vec::new();
2317
2318        let imports = vec![Import {
2319            name: "VirtualAllocEx".to_string(),
2320            library: Some("kernel32.dll".to_string()),
2321            address: Some(0x1000),
2322            ordinal: None,
2323        }];
2324
2325        analyzer.analyze_imports(&imports, &mut indicators, &mut findings);
2326        assert!(!indicators.suspicious_apis.is_empty());
2327
2328        // Disable suspicious APIs, enable anti-debug
2329        config.detect_suspicious_apis = false;
2330        config.detect_anti_debug = true;
2331        let analyzer = SecurityAnalyzer::with_config(Architecture::X86_64, config);
2332        let mut indicators = SecurityIndicators::default();
2333        let mut findings = Vec::new();
2334
2335        let imports = vec![Import {
2336            name: "IsDebuggerPresent".to_string(),
2337            library: Some("kernel32.dll".to_string()),
2338            address: Some(0x1000),
2339            ordinal: None,
2340        }];
2341
2342        analyzer.analyze_imports(&imports, &mut indicators, &mut findings);
2343        assert!(!indicators.anti_debug.is_empty());
2344    }
2345}