#![allow(clippy::uninlined_format_args)]
#![allow(unused_comparisons)]
#![allow(clippy::absurd_extreme_comparisons)]
#![allow(clippy::comparison_to_empty)]
use proptest::prelude::*;
use threatflux_binary_analysis::{formats::detect_format, types::*, BinaryAnalyzer};
mod common;
fn arb_binary_data() -> impl Strategy<Value = Vec<u8>> {
prop::collection::vec(any::<u8>(), 0..65536)
}
fn arb_binary_with_magic() -> impl Strategy<Value = Vec<u8>> {
prop_oneof![
prop::collection::vec(any::<u8>(), 100..8192).prop_map(|mut data| {
if data.len() >= 4 {
data[0..4].copy_from_slice(b"\x7fELF");
}
data
}),
prop::collection::vec(any::<u8>(), 100..8192).prop_map(|mut data| {
if data.len() >= 2 {
data[0..2].copy_from_slice(b"MZ");
}
data
}),
prop::collection::vec(any::<u8>(), 100..8192).prop_map(|mut data| {
if data.len() >= 4 {
data[0..4].copy_from_slice(b"\xfe\xed\xfa\xcf");
}
data
}),
prop::collection::vec(any::<u8>(), 100..8192).prop_map(|mut data| {
if data.len() >= 4 {
data[0..4].copy_from_slice(b"\xca\xfe\xba\xbe");
}
data
}),
]
}
#[allow(dead_code)]
fn arb_elf_like_data() -> impl Strategy<Value = Vec<u8>> {
(
any::<u8>(), any::<u8>(), any::<u8>(), any::<u16>(), any::<u32>(), prop::collection::vec(any::<u8>(), 64..4096), )
.prop_map(
|(ei_class, ei_data, ei_version, e_machine, e_version, mut rest)| {
let mut data = vec![0; 64];
data[0..4].copy_from_slice(b"\x7fELF");
data[4] = ei_class;
data[5] = ei_data;
data[6] = ei_version;
data[18] = (e_machine & 0xff) as u8;
data[19] = (e_machine >> 8) as u8;
let version_bytes = e_version.to_le_bytes();
data[20..24].copy_from_slice(&version_bytes);
data.append(&mut rest);
data
},
)
}
#[allow(dead_code)]
fn arb_pe_like_data() -> impl Strategy<Value = Vec<u8>> {
(
any::<u16>(), any::<u16>(), any::<u32>(), any::<u16>(), any::<u16>(), prop::collection::vec(any::<u8>(), 128..4096), )
.prop_map(
|(machine, num_sections, timestamp, opt_hdr_size, characteristics, mut rest)| {
let mut data = vec![0; 160];
data[0..2].copy_from_slice(b"MZ");
data[60..64].copy_from_slice(&0x80u32.to_le_bytes());
data[0x80..0x84].copy_from_slice(b"PE\0\0");
data[0x84..0x86].copy_from_slice(&machine.to_le_bytes());
data[0x86..0x88].copy_from_slice(&num_sections.to_le_bytes());
data[0x88..0x8c].copy_from_slice(×tamp.to_le_bytes());
data[0x94..0x96].copy_from_slice(&opt_hdr_size.to_le_bytes());
data[0x96..0x98].copy_from_slice(&characteristics.to_le_bytes());
data.append(&mut rest);
data
},
)
}
#[allow(dead_code)]
fn arb_macho_like_data() -> impl Strategy<Value = Vec<u8>> {
(
any::<u32>(), any::<u32>(), any::<u32>(), any::<u32>(), any::<u32>(), any::<u32>(), prop::collection::vec(any::<u8>(), 64..4096), )
.prop_map(
|(cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags, mut rest)| {
let mut data = vec![0; 32];
data[0..4].copy_from_slice(b"\xcf\xfa\xed\xfe");
data[4..8].copy_from_slice(&cputype.to_le_bytes());
data[8..12].copy_from_slice(&cpusubtype.to_le_bytes());
data[12..16].copy_from_slice(&filetype.to_le_bytes());
data[16..20].copy_from_slice(&ncmds.to_le_bytes());
data[20..24].copy_from_slice(&sizeofcmds.to_le_bytes());
data[24..28].copy_from_slice(&flags.to_le_bytes());
data.append(&mut rest);
data
},
)
}
fn arb_java_like_data() -> impl Strategy<Value = Vec<u8>> {
(
any::<u16>(), any::<u16>(), any::<u16>(), prop::collection::vec(any::<u8>(), 32..2048), )
.prop_map(|(minor, major, cp_count, mut rest)| {
let mut data = vec![0; 10];
data[0..4].copy_from_slice(b"\xca\xfe\xba\xbe");
data[4..6].copy_from_slice(&minor.to_be_bytes());
data[6..8].copy_from_slice(&major.to_be_bytes());
data[8..10].copy_from_slice(&cp_count.to_be_bytes());
data.append(&mut rest);
data
})
}
proptest! {
#[test]
fn prop_format_detection_no_panic(data in arb_binary_data()) {
let _ = detect_format(&data);
}
#[test]
fn prop_parsers_graceful_failure(data in arb_binary_data()) {
let _ = BinaryAnalyzer::new().analyze(&data);
let _ = BinaryAnalyzer::new().analyze(&data);
let _ = BinaryAnalyzer::new().analyze(&data);
let _ = BinaryAnalyzer::new().analyze(&data);
}
#[test]
fn prop_magic_number_validation(data in arb_binary_with_magic()) {
let format = detect_format(&data);
if let Ok(detected_format) = format {
match detected_format {
BinaryFormat::Elf => {
assert!(data.len() >= 4 && &data[0..4] == b"\x7fELF");
},
BinaryFormat::Pe => {
assert!(data.len() >= 2 && &data[0..2] == b"MZ");
},
BinaryFormat::MachO => {
assert!(data.len() >= 4 && (
&data[0..4] == b"\xfe\xed\xfa\xce" ||
&data[0..4] == b"\xce\xfa\xed\xfe" ||
&data[0..4] == b"\xfe\xed\xfa\xcf" ||
&data[0..4] == b"\xcf\xfa\xed\xfe"
));
},
BinaryFormat::Java => {
assert!(data.len() >= 4 && &data[0..4] == b"\xca\xfe\xba\xbe");
},
_ => {}
}
}
}
#[cfg(feature = "elf")]
#[test]
fn prop_elf_parser_invariants(data in arb_elf_like_data()) {
if let Ok(parsed) = BinaryAnalyzer::new().analyze(&data) {
assert_eq!(parsed.format, BinaryFormat::Elf);
let arch = parsed.architecture;
assert!(matches!(arch,
Architecture::X86 | Architecture::X86_64 | Architecture::Arm |
Architecture::Arm64 | Architecture::Mips | Architecture::PowerPC |
Architecture::PowerPC64 | Architecture::RiscV | Architecture::Unknown
));
for section in parsed.sections {
assert!(!section.name.is_empty() || section.name == "");
assert!(section.size >= 0);
assert!(section.address >= 0);
assert!(section.offset >= 0);
}
for symbol in parsed.symbols {
assert!(symbol.address >= 0);
assert!(symbol.size >= 0);
}
let metadata = parsed.metadata;
assert_eq!(metadata.format, BinaryFormat::Elf);
assert_eq!(metadata.architecture, arch);
assert!(metadata.size > 0);
}
}
#[cfg(feature = "pe")]
#[test]
fn prop_pe_parser_invariants(data in arb_pe_like_data()) {
if let Ok(parsed) = BinaryAnalyzer::new().analyze(&data) {
assert_eq!(parsed.format, BinaryFormat::Pe);
let arch = parsed.architecture;
assert!(matches!(arch,
Architecture::X86 | Architecture::X86_64 | Architecture::Arm |
Architecture::Arm64 | Architecture::Unknown
));
for section in parsed.sections {
assert!(section.size >= 0);
assert!(section.address >= 0);
assert!(section.offset >= 0);
}
for import in parsed.imports {
assert!(!import.name.is_empty());
}
for export in parsed.exports {
assert!(!export.name.is_empty());
assert!(export.address > 0);
}
let metadata = parsed.metadata;
assert_eq!(metadata.format, BinaryFormat::Pe);
assert_eq!(metadata.architecture, arch);
assert!(metadata.size > 0);
}
}
#[cfg(feature = "macho")]
#[test]
fn prop_macho_parser_invariants(data in arb_macho_like_data()) {
if let Ok(parsed) = BinaryAnalyzer::new().analyze(&data) {
assert_eq!(parsed.format, BinaryFormat::MachO);
let arch = parsed.architecture;
assert!(matches!(arch,
Architecture::X86 | Architecture::X86_64 | Architecture::Arm |
Architecture::Arm64 | Architecture::PowerPC | Architecture::PowerPC64 |
Architecture::Unknown
));
for section in parsed.sections {
assert!(section.size >= 0);
assert!(section.address >= 0);
assert!(section.offset >= 0);
}
let metadata = parsed.metadata;
assert_eq!(metadata.format, BinaryFormat::MachO);
assert_eq!(metadata.architecture, arch);
assert!(metadata.size > 0);
}
}
#[test]
fn prop_java_parser_invariants(data in arb_java_like_data()) {
if let Ok(parsed) = BinaryAnalyzer::new().analyze(&data) {
assert_eq!(parsed.format, BinaryFormat::Java);
assert_eq!(parsed.architecture, Architecture::Jvm);
assert!(parsed.entry_point.is_none());
assert!(!parsed.sections.is_empty());
let metadata = parsed.metadata;
assert_eq!(metadata.format, BinaryFormat::Java);
assert_eq!(metadata.architecture, Architecture::Jvm);
assert!(metadata.size > 0);
}
}
#[test]
fn prop_analysis_consistency(data in arb_binary_with_magic()) {
let analyzer = BinaryAnalyzer::new();
if let Ok(result) = analyzer.analyze(&data) {
assert_eq!(result.metadata.format, result.format);
assert_eq!(result.metadata.architecture, result.architecture);
assert_eq!(result.metadata.entry_point, result.entry_point);
assert_eq!(result.sections.len(), result.sections.len());
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
if let Some(ref instructions) = result.disassembly {
for instruction in instructions {
assert!(instruction.address > 0);
assert!(!instruction.bytes.is_empty());
assert!(instruction.size > 0);
assert_eq!(instruction.size, instruction.bytes.len());
}
}
#[cfg(feature = "entropy-analysis")]
if let Some(ref entropy) = result.entropy {
assert!(entropy.overall_entropy >= 0.0);
assert!(entropy.overall_entropy <= 8.0);
}
}
}
#[test]
fn prop_memory_bounds(size in 1usize..1024*1024) { let data = vec![0u8; size];
let _ = detect_format(&data);
let _ = BinaryAnalyzer::new().analyze(&data);
let _ = BinaryAnalyzer::new().analyze(&data);
let _ = BinaryAnalyzer::new().analyze(&data);
let _ = BinaryAnalyzer::new().analyze(&data);
}
#[test]
fn prop_deterministic_parsing(data in arb_binary_with_magic()) {
let result1 = detect_format(&data);
let result2 = detect_format(&data);
assert_eq!(result1.is_ok(), result2.is_ok());
if let (Ok(format1), Ok(format2)) = (result1, result2) {
assert_eq!(format1, format2);
}
let elf_result1 = BinaryAnalyzer::new().analyze(&data);
let elf_result2 = BinaryAnalyzer::new().analyze(&data);
assert_eq!(elf_result1.is_ok(), elf_result2.is_ok());
if let (Ok(parsed1), Ok(parsed2)) = (elf_result1, elf_result2) {
assert_eq!(parsed1.format, parsed2.format);
assert_eq!(parsed1.architecture, parsed2.architecture);
assert_eq!(parsed1.entry_point, parsed2.entry_point);
assert_eq!(parsed1.sections.len(), parsed2.sections.len());
}
}
#[test]
fn prop_informative_errors(data in arb_binary_data()) {
if let Err(error) = BinaryAnalyzer::new().analyze(&data) {
let error_msg = format!("{}", error);
assert!(!error_msg.is_empty());
assert!(error_msg.len() > 5); }
if let Err(error) = BinaryAnalyzer::new().analyze(&data) {
let error_msg = format!("{}", error);
assert!(!error_msg.is_empty());
assert!(error_msg.len() > 5);
}
if let Err(error) = BinaryAnalyzer::new().analyze(&data) {
let error_msg = format!("{}", error);
assert!(!error_msg.is_empty());
assert!(error_msg.len() > 5);
}
if let Err(error) = BinaryAnalyzer::new().analyze(&data) {
let error_msg = format!("{}", error);
assert!(!error_msg.is_empty());
assert!(error_msg.len() > 5);
}
}
#[test]
fn prop_architecture_preservation(
arch in prop_oneof![
Just(Architecture::X86),
Just(Architecture::X86_64),
Just(Architecture::Arm),
Just(Architecture::Arm64),
Just(Architecture::Mips),
Just(Architecture::PowerPC),
Just(Architecture::PowerPC64),
Just(Architecture::RiscV),
Just(Architecture::Jvm),
Just(Architecture::Unknown),
]
) {
let metadata = BinaryMetadata {
size: 1024,
format: BinaryFormat::Elf,
architecture: arch,
entry_point: Some(0x1000),
base_address: Some(0x400000),
timestamp: None,
compiler_info: None,
endian: Endianness::Little,
security_features: SecurityFeatures::default(),
};
assert_eq!(metadata.architecture, arch);
#[cfg(feature = "serde-support")]
{
let json = serde_json::to_string(&metadata).unwrap();
let deserialized: BinaryMetadata = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.architecture, arch);
}
}
#[test]
fn prop_section_permissions(read in any::<bool>(), write in any::<bool>(), execute in any::<bool>()) {
let permissions = SectionPermissions { read, write, execute };
let section = Section {
name: "test".to_string(),
address: 0x1000,
size: 1024,
offset: 0x1000,
permissions,
section_type: SectionType::Data,
data: None,
};
assert_eq!(section.permissions.read, read);
assert_eq!(section.permissions.write, write);
assert_eq!(section.permissions.execute, execute);
}
}
#[test]
fn test_proptest_compilation() {
}
#[test]
fn test_property_test_edge_cases() {
let empty_data = vec![];
assert!(detect_format(&empty_data).is_err());
let single_byte = vec![0x7f];
let _ = detect_format(&single_byte);
let elf_magic = b"\x7fELF".to_vec();
let _ = detect_format(&elf_magic);
let mut large_magic = b"\x7fELF".to_vec();
large_magic.resize(1024 * 1024, 0); let _ = detect_format(&large_magic);
let zero_data = vec![0; 1024];
let _ = detect_format(&zero_data);
let ff_data = vec![0xFF; 1024];
let _ = detect_format(&ff_data); }
#[cfg(all(feature = "elf", feature = "pe", feature = "macho"))]
#[test]
fn test_binary_pattern_properties() {
let mut elf_64 = b"\x7fELF\x02".to_vec();
elf_64.resize(1024, 0);
if let Ok(parsed) = BinaryAnalyzer::new().analyze(&elf_64) {
assert_eq!(parsed.architecture, Architecture::X86_64);
}
let mut elf_32 = b"\x7fELF\x01".to_vec();
elf_32.resize(1024, 0);
if let Ok(parsed) = BinaryAnalyzer::new().analyze(&elf_32) {
assert_eq!(parsed.architecture, Architecture::X86);
}
let mut pe_amd64 = vec![0; 256];
pe_amd64[0..2].copy_from_slice(b"MZ");
pe_amd64[60..64].copy_from_slice(&0x80u32.to_le_bytes());
pe_amd64[0x80..0x84].copy_from_slice(b"PE\0\0");
pe_amd64[0x84..0x86].copy_from_slice(&0x8664u16.to_le_bytes());
if let Ok(parsed) = BinaryAnalyzer::new().analyze(&pe_amd64) {
assert_eq!(parsed.architecture, Architecture::X86_64);
}
}