#![allow(clippy::uninlined_format_args)]
#![cfg(feature = "elf")]
#![allow(unused_variables)]
use pretty_assertions::assert_eq;
use rstest::*;
use threatflux_binary_analysis::types::*;
use threatflux_binary_analysis::BinaryAnalyzer;
mod common;
use common::fixtures::*;
#[test]
fn test_elf_header_parsing() {
let data = create_realistic_elf_64();
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));
}
#[rstest]
#[case(&[0x7f, 0x45, 0x4c, 0x46], false, "Valid ELF magic but incomplete header")]
#[case(&[0x7f, 0x45, 0x4c], true, "Incomplete ELF magic - falls back to Raw")]
#[case(&[0x00, 0x45, 0x4c, 0x46], true, "Invalid first byte - falls back to Raw")]
#[case(&[0x7f, 0x00, 0x4c, 0x46], true, "Invalid second byte - falls back to Raw")]
#[case(&[], false, "Empty data")]
fn test_elf_magic_detection(
#[case] data: &[u8],
#[case] should_pass: bool,
#[case] description: &str,
) {
let result = BinaryAnalyzer::new().analyze(data);
if should_pass {
assert!(result.is_ok(), "Failed: {}", description);
} else {
assert!(result.is_err(), "Should have failed: {}", description);
}
}
#[rstest]
#[case(0x01, Architecture::X86, "32-bit ELF")]
#[case(0x02, Architecture::X86_64, "64-bit ELF")]
#[case(0x00, Architecture::Unknown, "Invalid class")]
#[case(0x03, Architecture::Unknown, "Reserved class")]
fn test_elf_class_detection(
#[case] ei_class: u8,
#[case] expected_arch: Architecture,
#[case] description: &str,
) {
let mut data = create_realistic_elf_64();
data[4] = ei_class;
let result = BinaryAnalyzer::new().analyze(&data);
if ei_class == 0x01 || ei_class == 0x02 {
if let Ok(parsed) = result {
assert_eq!(
parsed.architecture, expected_arch,
"Failed: {}",
description
);
}
} else {
if let Ok(parsed) = result {
assert_eq!(
parsed.architecture,
Architecture::Unknown,
"Failed: {}",
description
);
}
}
}
#[rstest]
#[case(0x01, Endianness::Little, "Little endian")]
#[case(0x02, Endianness::Big, "Big endian")]
#[case(0x00, Endianness::Little, "Invalid encoding (default to little)")]
fn test_elf_endianness(
#[case] ei_data: u8,
#[case] expected_endian: Endianness,
#[case] description: &str,
) {
let mut data = create_realistic_elf_64();
data[5] = ei_data;
let result = BinaryAnalyzer::new().analyze(&data);
if result.is_err() {
return;
}
let result = result.unwrap();
let metadata = result.metadata;
assert_eq!(metadata.endian, expected_endian, "Failed: {}", description);
}
#[rstest]
#[case(0x3e, 0x00, Architecture::X86_64, "x86-64")]
#[case(0x03, 0x00, Architecture::X86, "i386")]
#[case(0xb7, 0x00, Architecture::Arm64, "AArch64")]
#[case(0x28, 0x00, Architecture::Arm, "ARM")]
#[case(0xf3, 0x00, Architecture::RiscV, "RISC-V")]
#[case(0x08, 0x00, Architecture::Mips, "MIPS")]
#[case(0x14, 0x00, Architecture::PowerPC, "PowerPC")]
#[case(0x15, 0x00, Architecture::PowerPC64, "PowerPC 64")]
#[case(0x00, 0x00, Architecture::Unknown, "No machine")]
#[case(0xff, 0xff, Architecture::Unknown, "Unknown machine")]
fn test_elf_machine_types(
#[case] machine_low: u8,
#[case] machine_high: u8,
#[case] expected_arch: Architecture,
#[case] description: &str,
) {
let mut data = create_realistic_elf_64();
data[18] = machine_low; data[19] = machine_high;
let result = BinaryAnalyzer::new().analyze(&data);
if let Ok(parsed) = result {
assert_eq!(
parsed.architecture, expected_arch,
"Failed: {}",
description
);
} else {
assert_eq!(
expected_arch,
Architecture::Unknown,
"Parsing failed but expected known architecture: {}",
description
);
}
}
#[rstest]
#[case(0x00, 0x00, "ET_NONE - No file type")]
#[case(0x01, 0x00, "ET_REL - Relocatable object")]
#[case(0x02, 0x00, "ET_EXEC - Executable file")]
#[case(0x03, 0x00, "ET_DYN - Shared object")]
#[case(0x04, 0x00, "ET_CORE - Core file")]
fn test_elf_file_types(#[case] type_low: u8, #[case] type_high: u8, #[case] description: &str) {
let mut data = create_realistic_elf_64();
data[16] = type_low; data[17] = type_high;
let result = BinaryAnalyzer::new().analyze(&data);
assert!(result.is_ok(), "Failed to parse: {}", description);
}
#[rstest]
#[case(0x01, true, "Current version")]
#[case(0x00, false, "Invalid version")]
#[case(0x02, false, "Future version")]
fn test_elf_version_validation(
#[case] version: u8,
#[case] should_pass: bool,
#[case] description: &str,
) {
let mut data = create_realistic_elf_64();
data[6] = version;
let result = BinaryAnalyzer::new().analyze(&data);
if should_pass {
assert!(result.is_ok(), "Failed: {}", description);
} else {
if let Ok(parsed) = result {
let metadata = parsed.metadata;
}
}
}
#[rstest]
#[case(0x00, "ELFOSABI_NONE - System V")]
#[case(0x01, "ELFOSABI_HPUX - HP-UX")]
#[case(0x02, "ELFOSABI_NETBSD - NetBSD")]
#[case(0x03, "ELFOSABI_LINUX - Linux")]
#[case(0x06, "ELFOSABI_SOLARIS - Solaris")]
#[case(0x07, "ELFOSABI_AIX - AIX")]
#[case(0x08, "ELFOSABI_IRIX - IRIX")]
#[case(0x09, "ELFOSABI_FREEBSD - FreeBSD")]
#[case(0x0c, "ELFOSABI_OPENBSD - OpenBSD")]
fn test_elf_osabi_detection(#[case] osabi: u8, #[case] description: &str) {
let mut data = create_realistic_elf_64();
data[7] = osabi;
let result = BinaryAnalyzer::new().analyze(&data);
assert!(result.is_ok(), "Failed to parse: {}", description);
let parsed = result.unwrap();
let metadata = parsed.metadata;
assert!(metadata.size > 0);
}
#[test]
fn test_elf_section_parsing() {
let data = create_comprehensive_elf_with_sections();
let result = BinaryAnalyzer::new().analyze(&data).unwrap();
let sections = result.sections;
assert!(!sections.is_empty(), "Should have parsed sections");
let section_names: Vec<&str> = sections.iter().map(|s| s.name.as_str()).collect();
let expected_sections = [".text", ".data", ".bss", ".rodata"];
let found_sections: Vec<_> = expected_sections
.iter()
.filter(|&&expected| section_names.contains(&expected))
.collect();
assert!(
!found_sections.is_empty(),
"Should find at least one common section, found: {:?}",
section_names
);
}
#[test]
fn test_elf_symbol_parsing() {
let data = create_comprehensive_elf_with_symbols();
let result = BinaryAnalyzer::new().analyze(&data).unwrap();
let symbols = result.symbols;
if symbols.is_empty() {
return; }
let symbol_names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
let expected_symbols = vec!["main", "_start", "_init"];
for expected in &expected_symbols {
if symbol_names.contains(expected) {
let symbol = symbols.iter().find(|s| s.name == *expected).unwrap();
assert!(
symbol.address > 0,
"Symbol {} should have valid address",
expected
);
}
}
}
#[test]
fn test_elf_program_header_parsing() {
let data = create_realistic_elf_64();
let result = BinaryAnalyzer::new().analyze(&data).unwrap();
let metadata = result.metadata;
assert_eq!(metadata.format, BinaryFormat::Elf);
assert!(metadata.entry_point.is_some());
}
#[test]
fn test_elf_dynamic_section_parsing() {
let data = create_elf_with_dynamic_section();
let result = BinaryAnalyzer::new().analyze(&data).unwrap();
let imports = result.imports;
let exports = result.exports;
if !imports.is_empty() {
for import in imports {
assert!(!import.name.is_empty(), "Import should have a name");
}
}
}
#[rstest]
#[case("truncated_header", &[0x7f, 0x45, 0x4c, 0x46, 0x02], "Truncated ELF header")]
#[case(
"invalid_section_headers",
create_elf_with_invalid_sections(),
"Invalid section headers"
)]
#[case(
"overlapping_sections",
create_elf_with_overlapping_sections(),
"Overlapping sections"
)]
#[case(
"invalid_program_headers",
create_elf_with_invalid_program_headers(),
"Invalid program headers"
)]
fn test_elf_error_handling(
#[case] _test_name: &str,
#[case] data: &[u8],
#[case] description: &str,
) {
let result = BinaryAnalyzer::new().analyze(data);
if let Err(error) = result {
let error_msg = format!("{}", error);
assert!(
!error_msg.is_empty(),
"Error message should not be empty for: {}",
description
);
} else {
let parsed = result.unwrap();
assert_eq!(parsed.format, BinaryFormat::Elf);
}
}
#[test]
fn test_elf_security_features() {
let data = create_elf_with_security_features();
let result = BinaryAnalyzer::new().analyze(&data).unwrap();
let metadata = result.metadata;
let security = &metadata.security_features;
assert!(metadata.size > 0); }
#[test]
fn test_elf_note_section_parsing() {
let data = create_elf_with_note_sections();
let result = BinaryAnalyzer::new().analyze(&data).unwrap();
let sections = result.sections;
let note_sections: Vec<_> = sections
.iter()
.filter(|s| s.name.starts_with(".note"))
.collect();
if !note_sections.is_empty() {
for note_section in note_sections {
assert_eq!(note_section.section_type, SectionType::Note);
assert!(note_section.size > 0);
}
}
}
#[test]
fn test_elf_string_table_parsing() {
let data = create_elf_with_string_tables();
let result = BinaryAnalyzer::new().analyze(&data).unwrap();
let sections = result.sections;
let string_sections: Vec<_> = sections
.iter()
.filter(|s| s.name.contains("str") || s.section_type == SectionType::String)
.collect();
for string_section in string_sections {
assert!(string_section.size > 0);
if let Some(data) = &string_section.data {
assert!(
data.contains(&0),
"String table should contain null terminators"
);
}
}
}
#[test]
fn test_elf_relocation_parsing() {
let data = create_elf_with_relocations();
let result = BinaryAnalyzer::new().analyze(&data).unwrap();
let sections = result.sections;
let reloc_sections: Vec<_> = sections
.iter()
.filter(|s| s.name.starts_with(".rel") || s.section_type == SectionType::Relocation)
.collect();
for reloc_section in reloc_sections {
assert!(reloc_section.size > 0);
assert_eq!(reloc_section.section_type, SectionType::Relocation);
}
}
#[test]
fn test_elf_performance_large_file() {
let data = create_large_elf_binary(10 * 1024 * 1024);
let start = std::time::Instant::now();
let result = BinaryAnalyzer::new().analyze(&data);
let duration = start.elapsed();
assert!(result.is_ok(), "Should parse large ELF file successfully");
assert!(
duration.as_secs() < 5,
"Should parse large file in reasonable time"
);
}
#[test]
fn test_elf_concurrent_parsing() {
use std::sync::Arc;
use std::thread;
let data = Arc::new(create_realistic_elf_64());
let mut handles = vec![];
for i in 0..10 {
let data_clone = Arc::clone(&data);
let handle = thread::spawn(move || {
let result = BinaryAnalyzer::new().analyze(&data_clone);
assert!(result.is_ok(), "Thread {} failed to parse ELF", i);
result.unwrap()
});
handles.push(handle);
}
for handle in handles {
let parsed = handle.join().unwrap();
assert_eq!(parsed.format, BinaryFormat::Elf);
}
}
fn create_comprehensive_elf_with_sections() -> Vec<u8> {
let mut data = create_realistic_elf_64();
data.resize(8192, 0);
let shoff = 3072;
let mut section_headers = Vec::new();
section_headers.extend_from_slice(&[0u8; 64]);
let text_header = [
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 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, ];
section_headers.extend_from_slice(&text_header);
let data_header = [
0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
section_headers.extend_from_slice(&data_header);
let bss_header = [
0x0e, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
section_headers.extend_from_slice(&bss_header);
let strtab_header = [
0x13, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
section_headers.extend_from_slice(&strtab_header);
data[60..62].copy_from_slice(&5u16.to_le_bytes()); data[62..64].copy_from_slice(&4u16.to_le_bytes());
data[shoff..shoff + section_headers.len()].copy_from_slice(§ion_headers);
let string_table = b"\0.text\0.data\0.bss\0.shstrtab\0";
data[0x1800..0x1800 + string_table.len()].copy_from_slice(string_table);
data
}
fn create_comprehensive_elf_with_symbols() -> Vec<u8> {
let mut data = create_comprehensive_elf_with_sections();
data.resize(16384, 0);
let symtab_header = [
0x1d, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let strtab_header = [
0x25, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
data[60..62].copy_from_slice(&7u16.to_le_bytes());
let shoff = 3072 + 5 * 64; data[shoff..shoff + 64].copy_from_slice(&symtab_header);
data[shoff + 64..shoff + 128].copy_from_slice(&strtab_header);
let symbols = [
[0u8; 24],
[
0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x01, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ],
[
0x06, 0x00, 0x00, 0x00, 0x12, 0x00, 0x01, 0x00, 0x00, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ],
[
0x0d, 0x00, 0x00, 0x00, 0x12, 0x00, 0x01, 0x00, 0x00, 0x07, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ],
];
for (i, symbol) in symbols.iter().enumerate() {
let offset = 0x2000 + i * 24;
data[offset..offset + 24].copy_from_slice(symbol);
}
let string_table = b"\0main\0_start\0_init\0";
data[0x2060..0x2060 + string_table.len()].copy_from_slice(string_table);
let updated_string_table = b"\0.text\0.data\0.bss\0.shstrtab\0.symtab\0.strtab\0";
data[0x1800..0x1800 + updated_string_table.len()].copy_from_slice(updated_string_table);
let new_size = updated_string_table.len() as u64;
data[3072 + 4 * 64 + 32..3072 + 4 * 64 + 40].copy_from_slice(&new_size.to_le_bytes());
data
}
fn create_elf_with_dynamic_section() -> Vec<u8> {
let mut data = create_realistic_elf_64();
data[16] = 0x03; data[17] = 0x00;
data.resize(12288, 0);
data
}
fn create_elf_with_invalid_sections() -> &'static [u8] {
static INVALID_ELF: &[u8] = &[
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, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x40, 0x00, 0x05, 0x00, 0x04, 0x00, ];
INVALID_ELF
}
fn create_elf_with_overlapping_sections() -> &'static [u8] {
static OVERLAPPING_ELF: &[u8] = &[
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, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x40, 0x00, 0x02, 0x00, 0x01,
0x00, ];
OVERLAPPING_ELF
}
fn create_elf_with_invalid_program_headers() -> &'static [u8] {
static INVALID_PH_ELF: &[u8] = &[
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, 0xff, 0xff, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, ];
INVALID_PH_ELF
}
fn create_elf_with_security_features() -> Vec<u8> {
let mut data = create_realistic_elf_64();
data[16] = 0x03; data[17] = 0x00;
data.resize(4096, 0);
data
}
fn create_elf_with_note_sections() -> Vec<u8> {
let mut data = create_realistic_elf_64();
data.resize(6144, 0);
data
}
fn create_elf_with_string_tables() -> Vec<u8> {
let mut data = create_realistic_elf_64();
data.resize(8192, 0);
let strings = b"\0.text\0.data\0.bss\0.rodata\0main\0_start\0printf\0";
data.extend_from_slice(strings);
data
}
fn create_elf_with_relocations() -> Vec<u8> {
let mut data = create_realistic_elf_64();
data.resize(10240, 0);
data
}
fn create_large_elf_binary(size: usize) -> Vec<u8> {
let mut data = create_realistic_elf_64();
data.resize(size, 0);
data
}