1use crate::{
8 BinaryFile, Result,
9 types::{
10 Architecture, BinaryFormat, Import, Section, SecurityFeatures, SecurityIndicators, Symbol,
11 },
12};
13use std::collections::HashSet;
14
15pub struct SecurityAnalyzer {
17 #[allow(dead_code)]
19 architecture: Architecture,
20 config: SecurityConfig,
22}
23
24#[derive(Debug, Clone)]
26pub struct SecurityConfig {
27 pub detect_suspicious_apis: bool,
29 pub detect_anti_debug: bool,
31 pub detect_anti_vm: bool,
33 pub detect_crypto: bool,
35 pub detect_network: bool,
37 pub detect_filesystem: bool,
39 pub detect_registry: bool,
41 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#[derive(Debug, Clone)]
62pub struct SecurityAnalysisResult {
63 pub indicators: SecurityIndicators,
65 pub features: SecurityFeatures,
67 pub risk_score: f64,
69 pub findings: Vec<SecurityFinding>,
71}
72
73#[derive(Debug, Clone)]
75pub struct SecurityFinding {
76 pub category: FindingCategory,
78 pub severity: Severity,
80 pub description: String,
82 pub location: Option<String>,
84 pub data: Option<String>,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
90pub enum FindingCategory {
91 SuspiciousApi,
93 AntiDebug,
95 AntiVm,
97 Cryptographic,
99 Network,
101 Filesystem,
103 Registry,
105 MissingSecurity,
107 Obfuscation,
109 CodeInjection,
111 PrivilegeEscalation,
113}
114
115#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
117pub enum Severity {
118 Info,
120 Low,
122 Medium,
124 High,
126 Critical,
128}
129
130impl SecurityAnalyzer {
131 pub fn new(architecture: Architecture) -> Self {
133 Self {
134 architecture,
135 config: SecurityConfig::default(),
136 }
137 }
138
139 pub fn with_config(architecture: Architecture, config: SecurityConfig) -> Self {
141 Self {
142 architecture,
143 config,
144 }
145 }
146
147 pub fn analyze(&self, binary: &BinaryFile) -> Result<SecurityAnalysisResult> {
149 let mut indicators = SecurityIndicators::default();
150 let mut findings = Vec::new();
151
152 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 self.analyze_sections(binary.sections(), &mut indicators, &mut findings);
166
167 self.analyze_symbols(binary.symbols(), &mut indicators, &mut findings);
169
170 let features = binary.metadata().security_features.clone();
172
173 self.analyze_security_features(binary.format(), &features, &mut findings);
175
176 Self::canonicalize_indicators(&mut indicators);
177
178 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 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 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 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 if section.name.len() >= self.config.min_string_length
348 && self.is_suspicious_section_name(§ion.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 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 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 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 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 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 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 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 (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 fn get_suspicious_apis(&self) -> HashSet<&'static str> {
504 let mut apis = HashSet::new();
505
506 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 apis.insert("OpenProcess");
516 apis.insert("TerminateProcess");
517 apis.insert("SuspendThread");
518 apis.insert("ResumeThread");
519
520 apis.insert("AdjustTokenPrivileges");
522 apis.insert("LookupPrivilegeValue");
523 apis.insert("SeDebugPrivilege");
524
525 apis
526 }
527
528 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"); apis
542 }
543
544 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 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 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 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 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 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 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
662pub 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 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 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 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 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 analyzer.analyze_imports(binary.imports(), &mut indicators, &mut findings);
814
815 analyzer.analyze_sections(binary.sections(), &mut indicators, &mut findings);
817
818 analyzer.analyze_symbols(binary.symbols(), &mut indicators, &mut findings);
820
821 let features = binary.metadata().security_features.clone();
823
824 analyzer.analyze_security_features(binary.metadata().format, &features, &mut findings);
826
827 SecurityAnalyzer::canonicalize_indicators(&mut indicators);
828
829 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]
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]
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]
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(§ions, &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(§ions, &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]
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]
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 assert_eq!(missing_security_findings.len(), 4);
1309
1310 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]
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); }
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); }
1454
1455 #[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")); }
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")); }
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")); }
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")); }
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")); }
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")); }
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")); }
1542
1543 #[test]
1545 fn test_suspicious_section_detection() {
1546 let analyzer = SecurityAnalyzer::new(Architecture::X86_64);
1547
1548 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 assert!(analyzer.is_suspicious_section_name(".UPX0"));
1559 assert!(analyzer.is_suspicious_section_name(".PACKED"));
1560
1561 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 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 assert!(analyzer.is_suspicious_symbol_name("INJECT_CODE"));
1585 assert!(analyzer.is_suspicious_symbol_name("Bypass_Check"));
1586
1587 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]
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 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]
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(§ions, &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 let indicators = SecurityIndicators::default();
1771 let features = SecurityFeatures::default();
1772
1773 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 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]
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 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 assert!(!analyzer2.config.detect_anti_debug);
1862 assert!(!analyzer2.config.detect_anti_vm);
1863 assert!(analyzer2.config.detect_suspicious_apis); }
1865
1866 #[test]
1868 fn test_complete_security_analysis_high_risk() {
1869 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, aslr: false, stack_canary: false, cfi: false, fortify: false,
1982 pie: false,
1983 relro: false,
1984 signed: false,
1985 },
1986 },
1987 };
1988
1989 let result = analyze_mock_binary(&binary);
1990
1991 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 assert!(!result.features.nx_bit);
2016 assert!(!result.features.aslr);
2017 assert!(!result.features.stack_canary);
2018 assert!(!result.features.cfi);
2019
2020 assert!(!result.findings.is_empty());
2022
2023 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 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 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 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 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 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 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, aslr: true, stack_canary: true, cfi: true, fortify: true,
2175 pie: true,
2176 relro: true,
2177 signed: true,
2178 },
2179 },
2180 };
2181
2182 let result = analyze_mock_binary(&binary);
2183
2184 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 assert!(result.features.nx_bit);
2191 assert!(result.features.aslr);
2192 assert!(result.features.stack_canary);
2193 assert!(result.features.cfi);
2194
2195 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 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 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 let binary = create_test_binary_file();
2231
2232 let result = analyze_mock_binary(&binary);
2234
2235 assert!(!result.indicators.suspicious_apis.is_empty());
2237 assert!(result.risk_score > 0.0);
2238 assert!(!result.findings.is_empty());
2239
2240 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 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 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 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 assert!(result.risk_score >= 0.0);
2293
2294 assert!(!result.findings.is_empty());
2296 }
2297
2298 #[test]
2299 fn test_config_detection_flags() {
2300 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 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 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}