#![allow(clippy::uninlined_format_args)]
#![cfg(feature = "macho")]
use threatflux_binary_analysis::types::*;
use threatflux_binary_analysis::BinaryAnalyzer;
fn is_macho(data: &[u8]) -> bool {
matches!(
threatflux_binary_analysis::formats::detect_format(data),
Ok(BinaryFormat::MachO)
)
}
mod macho_test_data {
pub fn create_macho_64_x86_64_le() -> Vec<u8> {
let mut data = vec![0u8; 1024];
data[0..4].copy_from_slice(&[0xcf, 0xfa, 0xed, 0xfe]); data[4..8].copy_from_slice(&[0x07, 0x00, 0x00, 0x01]); data[8..12].copy_from_slice(&[0x03, 0x00, 0x00, 0x00]); data[12..16].copy_from_slice(&[0x02, 0x00, 0x00, 0x00]); data[16..20].copy_from_slice(&[0x01, 0x00, 0x00, 0x00]); data[20..24].copy_from_slice(&[0x48, 0x00, 0x00, 0x00]); data[24..28].copy_from_slice(&[0x00, 0x00, 0x20, 0x00]); data[28..32].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]);
data[32..36].copy_from_slice(&[0x19, 0x00, 0x00, 0x00]); data[36..40].copy_from_slice(&[0x48, 0x00, 0x00, 0x00]);
data[40..56].copy_from_slice(b"__TEXT\0\0\0\0\0\0\0\0\0\0");
data[56..64].copy_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); data[64..72].copy_from_slice(&[0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); data[72..80].copy_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); data[80..88].copy_from_slice(&[0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); data[88..92].copy_from_slice(&[0x05, 0x00, 0x00, 0x00]); data[92..96].copy_from_slice(&[0x05, 0x00, 0x00, 0x00]); data[96..100].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]); data[100..104].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]);
data
}
pub fn create_macho_32_x86_le() -> Vec<u8> {
let mut data = vec![0u8; 512];
data[0..4].copy_from_slice(&[0xce, 0xfa, 0xed, 0xfe]); data[4..8].copy_from_slice(&[0x07, 0x00, 0x00, 0x00]); data[8..12].copy_from_slice(&[0x03, 0x00, 0x00, 0x00]); data[12..16].copy_from_slice(&[0x02, 0x00, 0x00, 0x00]); data[16..20].copy_from_slice(&[0x01, 0x00, 0x00, 0x00]); data[20..24].copy_from_slice(&[0x38, 0x00, 0x00, 0x00]); data[24..28].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]);
data[28..32].copy_from_slice(&[0x01, 0x00, 0x00, 0x00]); data[32..36].copy_from_slice(&[0x38, 0x00, 0x00, 0x00]);
data[36..52].copy_from_slice(b"__TEXT\0\0\0\0\0\0\0\0\0\0");
data[52..56].copy_from_slice(&[0x00, 0x00, 0x00, 0x01]); data[56..60].copy_from_slice(&[0x00, 0x10, 0x00, 0x00]); data[60..64].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]); data[64..68].copy_from_slice(&[0x00, 0x02, 0x00, 0x00]); data[68..72].copy_from_slice(&[0x05, 0x00, 0x00, 0x00]); data[72..76].copy_from_slice(&[0x05, 0x00, 0x00, 0x00]); data[76..80].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]); data[80..84].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]);
data
}
pub fn create_macho_64_arm64_le() -> Vec<u8> {
let mut data = vec![0u8; 1024];
data[0..4].copy_from_slice(&[0xcf, 0xfa, 0xed, 0xfe]); data[4..8].copy_from_slice(&[0x0c, 0x00, 0x00, 0x01]); data[8..12].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]); data[12..16].copy_from_slice(&[0x02, 0x00, 0x00, 0x00]); data[16..20].copy_from_slice(&[0x01, 0x00, 0x00, 0x00]); data[20..24].copy_from_slice(&[0x48, 0x00, 0x00, 0x00]); data[24..28].copy_from_slice(&[0x00, 0x00, 0x20, 0x00]); data[28..32].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]);
data[32..36].copy_from_slice(&[0x19, 0x00, 0x00, 0x00]); data[36..40].copy_from_slice(&[0x48, 0x00, 0x00, 0x00]);
data[40..56].copy_from_slice(b"__TEXT\0\0\0\0\0\0\0\0\0\0");
data[56..64].copy_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); data[64..72].copy_from_slice(&[0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); data[72..80].copy_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); data[80..88].copy_from_slice(&[0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); data[88..92].copy_from_slice(&[0x05, 0x00, 0x00, 0x00]); data[92..96].copy_from_slice(&[0x05, 0x00, 0x00, 0x00]); data[96..100].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]); data[100..104].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]);
data
}
pub fn create_macho_32_powerpc_be() -> Vec<u8> {
let mut data = vec![0u8; 512];
data[0..4].copy_from_slice(&[0xce, 0xfa, 0xed, 0xfe]); data[4..8].copy_from_slice(&[0x00, 0x00, 0x00, 0x12]); data[8..12].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]); data[12..16].copy_from_slice(&[0x00, 0x00, 0x00, 0x02]); data[16..20].copy_from_slice(&[0x00, 0x00, 0x00, 0x01]); data[20..24].copy_from_slice(&[0x00, 0x00, 0x00, 0x38]); data[24..28].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]);
data[28..32].copy_from_slice(&[0x00, 0x00, 0x00, 0x01]); data[32..36].copy_from_slice(&[0x00, 0x00, 0x00, 0x38]);
data[36..52].copy_from_slice(b"__TEXT\0\0\0\0\0\0\0\0\0\0");
data[52..56].copy_from_slice(&[0x01, 0x00, 0x00, 0x00]); data[56..60].copy_from_slice(&[0x00, 0x00, 0x10, 0x00]); data[60..64].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]); data[64..68].copy_from_slice(&[0x00, 0x00, 0x02, 0x00]); data[68..72].copy_from_slice(&[0x00, 0x00, 0x00, 0x05]); data[72..76].copy_from_slice(&[0x00, 0x00, 0x00, 0x05]); data[76..80].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]); data[80..84].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]);
data
}
pub fn create_fat_binary() -> Vec<u8> {
let mut data = vec![0u8; 512];
data[0..4].copy_from_slice(&[0xca, 0xfe, 0xba, 0xbe]); data[4..8].copy_from_slice(&[0x00, 0x00, 0x00, 0x02]);
data[8..12].copy_from_slice(&[0x00, 0x00, 0x00, 0x07]); data[12..16].copy_from_slice(&[0x00, 0x00, 0x00, 0x03]); data[16..20].copy_from_slice(&[0x00, 0x00, 0x01, 0x00]); data[20..24].copy_from_slice(&[0x00, 0x00, 0x01, 0x00]); data[24..28].copy_from_slice(&[0x00, 0x00, 0x00, 0x0c]);
data[28..32].copy_from_slice(&[0x01, 0x00, 0x00, 0x07]); data[32..36].copy_from_slice(&[0x00, 0x00, 0x00, 0x03]); data[36..40].copy_from_slice(&[0x00, 0x00, 0x02, 0x00]); data[40..44].copy_from_slice(&[0x00, 0x00, 0x01, 0x00]); data[44..48].copy_from_slice(&[0x00, 0x00, 0x00, 0x0c]);
data
}
pub fn create_truncated_header() -> Vec<u8> {
vec![0xcf, 0xfa, 0xed, 0xfe, 0x07, 0x00] }
pub fn create_invalid_magic() -> Vec<u8> {
let mut data = vec![0u8; 1024];
data[0..4].copy_from_slice(&[0x12, 0x34, 0x56, 0x78]); data
}
pub fn create_macho_with_sections() -> Vec<u8> {
let mut data = vec![0u8; 2048];
data[0..4].copy_from_slice(&[0xcf, 0xfa, 0xed, 0xfe]); data[4..8].copy_from_slice(&[0x07, 0x00, 0x00, 0x01]); data[8..12].copy_from_slice(&[0x03, 0x00, 0x00, 0x00]); data[12..16].copy_from_slice(&[0x02, 0x00, 0x00, 0x00]); data[16..20].copy_from_slice(&[0x01, 0x00, 0x00, 0x00]); data[20..24].copy_from_slice(&[0x98, 0x00, 0x00, 0x00]); data[24..28].copy_from_slice(&[0x00, 0x00, 0x20, 0x00]); data[28..32].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]);
data[32..36].copy_from_slice(&[0x19, 0x00, 0x00, 0x00]); data[36..40].copy_from_slice(&[0x98, 0x00, 0x00, 0x00]);
data[40..56].copy_from_slice(b"__TEXT\0\0\0\0\0\0\0\0\0\0");
data[56..64].copy_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); data[64..72].copy_from_slice(&[0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); data[72..80].copy_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); data[80..88].copy_from_slice(&[0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); data[88..92].copy_from_slice(&[0x05, 0x00, 0x00, 0x00]); data[92..96].copy_from_slice(&[0x05, 0x00, 0x00, 0x00]); data[96..100].copy_from_slice(&[0x01, 0x00, 0x00, 0x00]); data[100..104].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]);
data[104..120].copy_from_slice(b"__text\0\0\0\0\0\0\0\0\0\0"); data[120..136].copy_from_slice(b"__TEXT\0\0\0\0\0\0\0\0\0\0"); data[136..144].copy_from_slice(&[0x00, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]); data[144..152].copy_from_slice(&[0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); data[152..156].copy_from_slice(&[0x00, 0x04, 0x00, 0x00]); data[156..160].copy_from_slice(&[0x02, 0x00, 0x00, 0x00]); data[160..164].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]); data[164..168].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]); data[168..172].copy_from_slice(&[0x00, 0x04, 0x00, 0x80]); data[172..176].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]); data[176..184].copy_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
data
}
}
#[test]
fn test_macho_parser_can_parse_valid_magic_numbers() {
let magic_32_le = vec![0xce, 0xfa, 0xed, 0xfe];
assert!(is_macho(&magic_32_le));
let magic_32_be = vec![0xfe, 0xed, 0xfa, 0xce];
assert!(is_macho(&magic_32_be));
let magic_64_le = vec![0xcf, 0xfa, 0xed, 0xfe];
assert!(is_macho(&magic_64_le));
let magic_64_be = vec![0xfe, 0xed, 0xfa, 0xcf];
assert!(is_macho(&magic_64_be));
let fat_magic = vec![0xca, 0xfe, 0xba, 0xbe];
assert!(!is_macho(&fat_magic));
let fat_cigam = vec![0xbe, 0xba, 0xfe, 0xca];
assert!(!is_macho(&fat_cigam));
}
#[test]
fn test_macho_parser_can_parse_invalid_data() {
assert!(!is_macho(&[]));
assert!(!is_macho(&[0x01, 0x02]));
assert!(!is_macho(&[0x12, 0x34, 0x56, 0x78]));
assert!(!is_macho(&[0x7f, 0x45, 0x4c, 0x46]));
assert!(!is_macho(&[0x4d, 0x5a, 0x90, 0x00]));
}
#[test]
fn test_macho_parser_parse_64_bit_x86_64_le() {
let data = macho_test_data::create_macho_64_x86_64_le();
let result = BinaryAnalyzer::new().analyze(&data);
assert!(result.is_ok());
let binary = result.unwrap();
assert_eq!(binary.format, BinaryFormat::MachO);
assert_eq!(binary.architecture, Architecture::X86_64);
let metadata = &binary.metadata;
assert_eq!(metadata.format, BinaryFormat::MachO);
assert_eq!(metadata.architecture, Architecture::X86_64);
assert_eq!(metadata.endian, Endianness::Little);
assert!(metadata.security_features.pie); assert_eq!(metadata.size, data.len());
}
#[test]
fn test_macho_parser_parse_64_bit_x86_64_be() {
let big_endian_magic = vec![0xcf, 0xfa, 0xed, 0xfe]; assert!(is_macho(&big_endian_magic));
let data = macho_test_data::create_macho_64_x86_64_le();
let result = BinaryAnalyzer::new().analyze(&data);
assert!(result.is_ok());
let binary = result.unwrap();
assert_eq!(binary.metadata.endian, Endianness::Little);
}
#[test]
fn test_macho_parser_parse_32_bit_x86_le() {
let data = macho_test_data::create_macho_32_x86_le();
let result = BinaryAnalyzer::new().analyze(&data);
assert!(result.is_ok());
let binary = result.unwrap();
assert_eq!(binary.format, BinaryFormat::MachO);
assert_eq!(binary.architecture, Architecture::X86);
let metadata = &binary.metadata;
assert_eq!(metadata.endian, Endianness::Little);
assert!(!metadata.security_features.pie); }
#[test]
fn test_macho_parser_parse_arm64() {
let data = macho_test_data::create_macho_64_arm64_le();
let result = BinaryAnalyzer::new().analyze(&data);
assert!(result.is_ok());
let binary = result.unwrap();
assert_eq!(binary.format, BinaryFormat::MachO);
assert_eq!(binary.architecture, Architecture::Arm64);
let metadata = &binary.metadata;
assert_eq!(metadata.endian, Endianness::Little);
assert!(metadata.security_features.pie);
}
#[test]
fn test_macho_parser_parse_powerpc() {
let data = macho_test_data::create_macho_32_powerpc_be();
assert!(is_macho(&data));
let be_32_magic = vec![0xce, 0xfa, 0xed, 0xfe]; assert!(is_macho(&be_32_magic));
}
#[test]
fn test_macho_parser_parse_with_sections() {
let data = macho_test_data::create_macho_with_sections();
let result = BinaryAnalyzer::new().analyze(&data);
assert!(result.is_ok());
let binary = result.unwrap();
let sections = &binary.sections;
assert!(!sections.is_empty());
let text_section = sections.iter().find(|s| s.name == "__text");
assert!(text_section.is_some());
let text_section = text_section.unwrap();
assert_eq!(text_section.section_type, SectionType::Code);
assert!(text_section.permissions.read);
assert!(!text_section.permissions.write);
assert!(text_section.permissions.execute);
}
#[test]
fn test_macho_parser_fat_binary_rejection() {
let data = macho_test_data::create_fat_binary();
let result = BinaryAnalyzer::new().analyze(&data);
assert!(result.is_ok());
let analysis = result.unwrap();
assert_eq!(analysis.format, BinaryFormat::Java);
}
#[test]
fn test_macho_parser_error_handling() {
let truncated = macho_test_data::create_truncated_header();
let result = BinaryAnalyzer::new().analyze(&truncated);
assert!(result.is_err());
let invalid_magic = macho_test_data::create_invalid_magic();
let result = BinaryAnalyzer::new().analyze(&invalid_magic);
assert!(result.is_ok());
let analysis = result.unwrap();
assert_eq!(analysis.format, BinaryFormat::Raw);
let result = BinaryAnalyzer::new().analyze(&[]);
assert!(result.is_err());
}
#[test]
fn test_macho_binary_format_trait_methods() {
let data = macho_test_data::create_macho_64_x86_64_le();
let binary = BinaryAnalyzer::new().analyze(&data).unwrap();
assert_eq!(binary.format, BinaryFormat::MachO);
assert_eq!(binary.architecture, Architecture::X86_64);
assert!(binary.entry_point.is_none());
let sections = &binary.sections;
assert!(sections.is_empty() || !sections.is_empty());
let symbols = &binary.symbols;
assert!(symbols.is_empty());
let imports = &binary.imports;
assert!(imports.is_empty());
let exports = &binary.exports;
assert!(exports.is_empty());
let metadata = &binary.metadata;
assert_eq!(metadata.format, BinaryFormat::MachO);
assert_eq!(metadata.architecture, Architecture::X86_64);
}
#[test]
fn test_macho_security_features_analysis() {
let data = macho_test_data::create_macho_64_x86_64_le();
let binary = BinaryAnalyzer::new().analyze(&data).unwrap();
let metadata = &binary.metadata;
assert!(metadata.security_features.pie); assert!(metadata.security_features.aslr); assert!(metadata.security_features.nx_bit); assert!(!metadata.security_features.stack_canary);
let non_pie_data = macho_test_data::create_macho_32_x86_le();
let non_pie_binary = BinaryAnalyzer::new().analyze(&non_pie_data).unwrap();
let non_pie_metadata = &non_pie_binary.metadata;
assert!(!non_pie_metadata.security_features.pie); assert!(!non_pie_metadata.security_features.aslr); assert!(non_pie_metadata.security_features.nx_bit); }
#[test]
fn test_macho_endianness_detection() {
let le_data = macho_test_data::create_macho_64_x86_64_le();
let le_binary = BinaryAnalyzer::new().analyze(&le_data).unwrap();
assert_eq!(le_binary.metadata.endian, Endianness::Little);
let le_magic = vec![0xcf, 0xfa, 0xed, 0xfe]; let be_magic = vec![0xcf, 0xfa, 0xed, 0xfe];
assert!(is_macho(&le_magic));
assert!(is_macho(&be_magic));
}
#[test]
fn test_macho_compiler_info_extraction() {
let data = macho_test_data::create_macho_64_x86_64_le();
let binary = BinaryAnalyzer::new().analyze(&data).unwrap();
let metadata = &binary.metadata;
assert!(metadata.compiler_info.is_some());
let compiler_info = metadata.compiler_info.as_ref().unwrap();
assert!(compiler_info.contains("Apple toolchain"));
}
#[test]
fn test_macho_architecture_mapping() {
let test_cases = vec![
(macho_test_data::create_macho_32_x86_le(), Architecture::X86),
(
macho_test_data::create_macho_64_x86_64_le(),
Architecture::X86_64,
),
(
macho_test_data::create_macho_64_arm64_le(),
Architecture::Arm64,
),
];
for (data, expected_arch) in test_cases {
let binary = BinaryAnalyzer::new().analyze(&data).unwrap();
assert_eq!(binary.architecture, expected_arch);
assert_eq!(binary.architecture, expected_arch);
}
}
#[test]
fn test_macho_section_type_classification() {
let data = macho_test_data::create_macho_with_sections();
let binary = BinaryAnalyzer::new().analyze(&data).unwrap();
let sections = &binary.sections;
if let Some(text_section) = sections.iter().find(|s| s.name == "__text") {
assert_eq!(text_section.section_type, SectionType::Code);
assert!(text_section.permissions.execute);
assert!(!text_section.permissions.write);
}
}
#[test]
fn test_macho_section_permissions() {
let data = macho_test_data::create_macho_with_sections();
let binary = BinaryAnalyzer::new().analyze(&data).unwrap();
let sections = &binary.sections;
for section in sections {
assert!(section.permissions.read);
if section.section_type == SectionType::Code {
assert!(section.permissions.execute);
assert!(!section.permissions.write);
}
}
}
#[test]
fn test_macho_section_data_extraction() {
let data = macho_test_data::create_macho_with_sections();
let binary = BinaryAnalyzer::new().analyze(&data).unwrap();
let sections = &binary.sections;
for section in sections {
if section.size <= 1024 && section.offset > 0 {
assert!(section.data.is_some() || section.data.is_none());
}
}
}
#[test]
fn test_macho_binary_size_metadata() {
let test_cases = vec![
macho_test_data::create_macho_32_x86_le(),
macho_test_data::create_macho_64_x86_64_le(),
macho_test_data::create_macho_64_arm64_le(),
];
for data in test_cases {
let binary = BinaryAnalyzer::new().analyze(&data).unwrap();
let metadata = &binary.metadata;
assert_eq!(metadata.size, data.len());
}
}
#[test]
fn test_macho_edge_cases() {
let min_data = vec![0xcf, 0xfa, 0xed, 0xfe]; let result = BinaryAnalyzer::new().analyze(&min_data);
assert!(result.is_err());
assert!(is_macho(&[0xcf, 0xfa, 0xed, 0xfe]));
assert!(!is_macho(&[0x12, 0x34, 0x56, 0x78]));
}
#[test]
fn test_macho_unknown_architecture_handling() {
let mut data = macho_test_data::create_macho_64_x86_64_le();
data[4..8].copy_from_slice(&[0xff, 0xff, 0xff, 0xff]);
let result = BinaryAnalyzer::new().analyze(&data);
if let Ok(binary) = result {
assert_eq!(binary.architecture, Architecture::Unknown);
assert_eq!(binary.architecture, Architecture::Unknown);
}
}