#![allow(clippy::uninlined_format_args)]
#![allow(clippy::uninlined_format_args)]
#[cfg(any(feature = "elf", feature = "java"))]
use std::io::Write;
#[cfg(feature = "elf")]
use tempfile::NamedTempFile;
#[allow(unused_imports)]
use threatflux_binary_analysis::error::BinaryError;
use threatflux_binary_analysis::types::*;
use threatflux_binary_analysis::*;
mod test_data {
#[cfg(feature = "elf")]
pub fn create_minimal_elf() -> Vec<u8> {
vec![
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]
}
#[cfg(not(feature = "elf"))]
#[allow(dead_code)]
pub fn create_minimal_elf() -> Vec<u8> {
vec![]
}
#[cfg(feature = "pe")]
pub fn create_minimal_pe() -> Vec<u8> {
let mut data = vec![0; 1024];
data[0] = 0x4d; data[1] = 0x5a; data[60] = 0x80;
data[0x80] = 0x50; data[0x81] = 0x45; data[0x82] = 0x00;
data[0x83] = 0x00;
data[0x84] = 0x64; data[0x85] = 0x86;
data[0x86] = 0x01; data[0x87] = 0x00;
data[0x88] = 0x00;
data[0x89] = 0x00;
data[0x8a] = 0x00;
data[0x8b] = 0x00;
data[0x8c] = 0x00;
data[0x8d] = 0x00;
data[0x8e] = 0x00;
data[0x8f] = 0x00;
data[0x90] = 0x00;
data[0x91] = 0x00;
data[0x92] = 0x00;
data[0x93] = 0x00;
data[0x94] = 0xf0; data[0x95] = 0x00;
data[0x96] = 0x22; data[0x97] = 0x00;
data[0x98] = 0x0b; data[0x99] = 0x02;
data[0xa8] = 0x00;
data[0xa9] = 0x10;
data[0xaa] = 0x00;
data[0xab] = 0x00;
data
}
#[cfg(not(feature = "pe"))]
#[allow(dead_code)]
pub fn create_minimal_pe() -> Vec<u8> {
vec![]
}
#[cfg(feature = "macho")]
pub fn create_minimal_macho() -> Vec<u8> {
vec![
0xfe, 0xed, 0xfa, 0xcf, 0x01, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x48, 0x5f, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x07,
0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
}
#[cfg(not(feature = "macho"))]
#[allow(dead_code)]
pub fn create_minimal_macho() -> Vec<u8> {
vec![]
}
#[cfg(feature = "java")]
pub fn create_java_class() -> Vec<u8> {
vec![
0xca, 0xfe, 0xba, 0xbe, 0x00, 0x00, 0x00, 0x34, 0x00, 0x0d, 0x0a, 0x00, 0x03, 0x00, 0x0a, 0x07, 0x00, 0x0b, 0x0c, 0x00, 0x06, 0x00, 0x07, 0x01, 0x00, 0x06, 0x3c, 0x69, 0x6e, 0x69, 0x74, 0x3e, 0x01, 0x00, 0x03, 0x28, 0x29, 0x56, 0x01, 0x00, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x01, 0x00, 0x0f, 0x4c, 0x69, 0x6e, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x54,
0x61, 0x62, 0x6c, 0x65, 0x01, 0x00, 0x04, 0x6d, 0x61, 0x69, 0x6e, 0x01, 0x00, 0x16, 0x28, 0x5b, 0x4c, 0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e,
0x67, 0x2f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3b, 0x29,
0x56, 0x01, 0x00, 0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c,
0x65, 0x07, 0x00, 0x0c, 0x01, 0x00, 0x10, 0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x4f,
0x62, 0x6a, 0x65, 0x63, 0x74, 0x00, 0x21, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x04, 0x00, 0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x11, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x2a, 0xb7, 0x00, 0x01, 0xb1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
}
#[cfg(not(feature = "java"))]
#[allow(dead_code)]
pub fn create_java_class() -> Vec<u8> {
vec![]
}
#[cfg(feature = "wasm")]
pub fn create_wasm_module() -> Vec<u8> {
vec![
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x01, 0x60, 0x01, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x41, 0x01, 0x6a, 0x0b, ]
}
#[cfg(not(feature = "wasm"))]
#[allow(dead_code)]
pub fn create_wasm_module() -> Vec<u8> {
vec![]
}
#[cfg(feature = "java")]
pub fn create_java_jar() -> Vec<u8> {
use std::io::Write;
use zip::{write::FileOptions, ZipWriter};
let class_data = create_java_class();
let cursor = std::io::Cursor::new(Vec::new());
let mut zip = ZipWriter::new(cursor);
zip.start_file("Test.class", FileOptions::default())
.unwrap();
zip.write_all(&class_data).unwrap();
let cursor = zip.finish().unwrap();
cursor.into_inner()
}
#[cfg(not(feature = "java"))]
#[allow(dead_code)]
pub fn create_java_jar() -> Vec<u8> {
vec![]
}
}
#[test]
#[cfg(feature = "elf")]
fn test_complete_elf_analysis() {
let data = test_data::create_minimal_elf();
let analyzer = BinaryAnalyzer::new();
let result = analyzer.analyze(&data).unwrap();
assert_eq!(result.format, BinaryFormat::Elf);
assert_eq!(result.architecture, Architecture::X86_64);
assert_eq!(result.entry_point, Some(0x401000));
assert_eq!(result.metadata.format, BinaryFormat::Elf);
assert_eq!(result.metadata.architecture, Architecture::X86_64);
assert!(result.metadata.size > 0);
assert_eq!(result.metadata.endian, Endianness::Little);
}
#[test]
#[cfg(feature = "pe")]
fn test_complete_pe_analysis() {
let data = test_data::create_minimal_pe();
let analyzer = BinaryAnalyzer::new();
let result = analyzer.analyze(&data).unwrap();
assert_eq!(result.format, BinaryFormat::Pe);
assert_eq!(result.architecture, Architecture::X86_64);
assert!(result.entry_point.is_some());
assert_eq!(result.metadata.format, BinaryFormat::Pe);
assert_eq!(result.metadata.architecture, Architecture::X86_64);
}
#[test]
#[cfg(feature = "macho")]
fn test_complete_macho_analysis() {
let data = test_data::create_minimal_macho();
let analyzer = BinaryAnalyzer::new();
let result = analyzer.analyze(&data);
match result {
Ok(analysis) => {
assert_eq!(analysis.format, BinaryFormat::MachO);
assert_eq!(analysis.architecture, Architecture::X86_64);
assert_eq!(analysis.metadata.format, BinaryFormat::MachO);
assert_eq!(analysis.metadata.architecture, Architecture::X86_64);
}
Err(BinaryError::ParseError(_)) => {
}
Err(e) => panic!("Unexpected error: {:?}", e),
}
}
#[test]
#[cfg(feature = "java")]
fn test_complete_java_analysis() {
let data = test_data::create_java_class();
let analyzer = BinaryAnalyzer::new();
let analysis = analyzer.analyze(&data).expect("Java class analysis failed");
assert_eq!(analysis.format, BinaryFormat::Java);
assert_eq!(analysis.architecture, Architecture::Jvm);
assert_eq!(analysis.metadata.format, BinaryFormat::Java);
assert_eq!(analysis.metadata.architecture, Architecture::Jvm);
}
#[test]
#[cfg(feature = "java")]
fn test_complete_java_jar_analysis() {
let data = test_data::create_java_jar();
let analyzer = BinaryAnalyzer::new();
let analysis = analyzer.analyze(&data).expect("Java JAR analysis failed");
assert_eq!(analysis.format, BinaryFormat::Java);
assert_eq!(analysis.architecture, Architecture::Jvm);
assert_eq!(analysis.metadata.format, BinaryFormat::Java);
assert_eq!(analysis.metadata.architecture, Architecture::Jvm);
}
#[test]
#[cfg(feature = "wasm")]
fn test_complete_wasm_analysis() {
let data = test_data::create_wasm_module();
let analyzer = BinaryAnalyzer::new();
let result = analyzer.analyze(&data);
match result {
Ok(analysis) => {
assert_eq!(analysis.format, BinaryFormat::Wasm);
assert_eq!(analysis.architecture, Architecture::Wasm);
assert_eq!(analysis.metadata.format, BinaryFormat::Wasm);
assert_eq!(analysis.metadata.architecture, Architecture::Wasm);
}
Err(BinaryError::UnsupportedFormat(_)) => {
}
Err(e) => panic!("Unexpected error: {:?}", e),
}
}
#[test]
#[cfg(feature = "elf")]
fn test_analysis_with_all_features_enabled() {
let data = test_data::create_minimal_elf();
let config = AnalysisConfig {
enable_disassembly: true,
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
disassembly_engine: threatflux_binary_analysis::DisassemblyEngine::Auto,
enable_control_flow: true,
enable_entropy: true,
enable_symbols: true,
max_analysis_size: 100 * 1024 * 1024,
architecture_hint: None,
..Default::default()
};
let analyzer = BinaryAnalyzer::with_config(config);
let result = analyzer.analyze(&data).unwrap();
assert_eq!(result.format, BinaryFormat::Elf);
assert_eq!(result.architecture, Architecture::X86_64);
}
#[test]
#[cfg(feature = "elf")]
fn test_analysis_with_features_disabled() {
let data = test_data::create_minimal_elf();
let config = AnalysisConfig {
enable_disassembly: false,
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
disassembly_engine: threatflux_binary_analysis::DisassemblyEngine::Auto,
enable_control_flow: false,
enable_entropy: false,
enable_symbols: false,
max_analysis_size: 1024,
architecture_hint: Some(Architecture::X86_64),
..Default::default()
};
let analyzer = BinaryAnalyzer::with_config(config);
let result = analyzer.analyze(&data).unwrap();
assert_eq!(result.format, BinaryFormat::Elf);
assert_eq!(result.architecture, Architecture::X86_64);
assert!(result.disassembly.is_none());
assert!(result.control_flow.is_none());
assert!(result.entropy.is_none());
}
#[test]
#[cfg(all(
feature = "elf",
feature = "pe",
feature = "macho",
feature = "java",
feature = "wasm"
))]
fn test_multiple_format_analysis() {
let test_cases = vec![
(
test_data::create_minimal_elf(),
BinaryFormat::Elf,
Architecture::X86_64,
true, ),
(
test_data::create_minimal_pe(),
BinaryFormat::Pe,
Architecture::X86_64,
true, ),
(
test_data::create_minimal_macho(),
BinaryFormat::MachO,
Architecture::X86_64,
false, ),
(
test_data::create_java_class(),
BinaryFormat::Java,
Architecture::Jvm,
true, ),
(
test_data::create_wasm_module(),
BinaryFormat::Wasm,
Architecture::Wasm,
false, ),
];
let analyzer = BinaryAnalyzer::new();
for (data, expected_format, expected_arch, should_succeed) in test_cases {
let result = analyzer.analyze(&data);
if should_succeed {
let analysis = result.unwrap();
assert_eq!(analysis.format, expected_format);
assert_eq!(analysis.architecture, expected_arch);
assert_eq!(analysis.metadata.format, expected_format);
assert_eq!(analysis.metadata.architecture, expected_arch);
} else {
match result {
Ok(analysis) => {
assert_eq!(analysis.format, expected_format);
assert_eq!(analysis.architecture, expected_arch);
assert_eq!(analysis.metadata.format, expected_format);
assert_eq!(analysis.metadata.architecture, expected_arch);
}
Err(BinaryError::UnsupportedFormat(_)) => {
}
Err(BinaryError::ParseError(_)) => {
}
Err(e) => panic!("Unexpected error for format {:?}: {:?}", expected_format, e),
}
}
}
}
#[test]
#[cfg(feature = "elf")]
fn test_file_based_analysis() {
let data = test_data::create_minimal_elf();
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(&data).unwrap();
temp_file.flush().unwrap();
let file_data = std::fs::read(temp_file.path()).unwrap();
let analyzer = BinaryAnalyzer::new();
let result = analyzer.analyze(&file_data).unwrap();
assert_eq!(result.format, BinaryFormat::Elf);
assert_eq!(result.architecture, Architecture::X86_64);
}
#[test]
#[cfg(all(
feature = "elf",
feature = "pe",
feature = "macho",
feature = "java",
feature = "wasm"
))]
fn test_concurrent_analysis_different_formats() {
use std::sync::Arc;
use std::thread;
let test_data = vec![
(
Arc::new(test_data::create_minimal_elf()),
BinaryFormat::Elf,
true,
),
(
Arc::new(test_data::create_minimal_pe()),
BinaryFormat::Pe,
true,
),
(
Arc::new(test_data::create_minimal_macho()),
BinaryFormat::MachO,
false,
),
(
Arc::new(test_data::create_java_class()),
BinaryFormat::Java,
true,
),
(
Arc::new(test_data::create_wasm_module()),
BinaryFormat::Wasm,
false,
),
];
let mut handles = vec![];
for (data, expected_format, should_succeed) in test_data {
let handle = thread::spawn(move || {
let analyzer = BinaryAnalyzer::new();
let result = analyzer.analyze(&data);
(result, expected_format, should_succeed)
});
handles.push(handle);
}
for handle in handles {
let (result, expected_format, should_succeed) = handle.join().unwrap();
if should_succeed {
let analysis = result.unwrap();
assert_eq!(analysis.format, expected_format);
} else {
match result {
Ok(analysis) => {
assert_eq!(analysis.format, expected_format);
}
Err(BinaryError::UnsupportedFormat(_)) => {
}
Err(BinaryError::ParseError(_)) => {
}
Err(e) => panic!("Unexpected error for format {:?}: {:?}", expected_format, e),
}
}
}
}
#[test]
#[cfg(feature = "elf")]
fn test_large_file_handling() {
let mut data = test_data::create_minimal_elf();
data.resize(10 * 1024 * 1024, 0);
let config = AnalysisConfig {
max_analysis_size: 1024 * 1024, ..Default::default()
};
let analyzer = BinaryAnalyzer::with_config(config);
let result = analyzer.analyze(&data);
assert!(result.is_ok());
let analysis = result.unwrap();
assert_eq!(analysis.format, BinaryFormat::Elf);
}
#[test]
fn test_malformed_binaries() {
let test_cases = vec![
vec![0x7f, 0x45, 0x4c], vec![0x4d, 0x5a], vec![0xca, 0xfe, 0xba], vec![0x00, 0x61, 0x73], vec![0xff; 1024], vec![], ];
let analyzer = BinaryAnalyzer::new();
for data in test_cases {
let result = analyzer.analyze(&data);
if let Ok(analysis) = result {
if !data.is_empty() && !data.starts_with(&[0x7f, 0x45, 0x4c, 0x46]) {
match data.get(0..2) {
Some([0x4d, 0x5a]) => {} Some([0xca, 0xfe]) => {} Some([0x00, 0x61]) => {} _ => {
assert!(matches!(
analysis.format,
BinaryFormat::Unknown | BinaryFormat::Raw
));
}
}
}
}
}
}
#[test]
#[cfg(feature = "elf")]
fn test_security_features_detection() {
let data = test_data::create_minimal_elf();
let analyzer = BinaryAnalyzer::new();
let result = analyzer.analyze(&data).unwrap();
let sec_features = &result.metadata.security_features;
let _nx = sec_features.nx_bit;
let _aslr = sec_features.aslr;
let _canary = sec_features.stack_canary;
}
#[test]
fn test_error_propagation() {
let analyzer = BinaryAnalyzer::new();
let empty_data = vec![];
let result = analyzer.analyze(&empty_data);
assert!(result.is_err());
if let Err(e) = result {
let error_string = format!("{}", e);
assert!(!error_string.is_empty());
}
}
#[test]
#[cfg(feature = "elf")]
fn test_analysis_determinism() {
let data = test_data::create_minimal_elf();
let analyzer = BinaryAnalyzer::new();
let mut results = vec![];
for _ in 0..5 {
let result = analyzer.analyze(&data).unwrap();
results.push(result);
}
let first = &results[0];
for result in &results[1..] {
assert_eq!(result.format, first.format);
assert_eq!(result.architecture, first.architecture);
assert_eq!(result.entry_point, first.entry_point);
assert_eq!(result.metadata.format, first.metadata.format);
assert_eq!(result.metadata.architecture, first.metadata.architecture);
}
}
#[test]
#[cfg(feature = "elf")]
fn test_memory_usage() {
let data = test_data::create_minimal_elf();
let analyzer = BinaryAnalyzer::new();
for _ in 0..100 {
let result = analyzer.analyze(&data);
assert!(result.is_ok());
}
}