From efdecefb217256d9091406c3c3720d4736983067 Mon Sep 17 00:00:00 2001 From: Jacob Latonis Date: Tue, 28 May 2024 17:16:00 -0600 Subject: [PATCH] feat: implement export hashing for mach-o --- lib/src/modules/macho/mod.rs | 35 ++++++++++++++++++++++++++++-- lib/src/modules/macho/tests/mod.rs | 33 ++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/lib/src/modules/macho/mod.rs b/lib/src/modules/macho/mod.rs index 93d4e8414..9e732329c 100644 --- a/lib/src/modules/macho/mod.rs +++ b/lib/src/modules/macho/mod.rs @@ -311,13 +311,13 @@ fn entitlement_hash(ctx: &mut ScanContext) -> Option { let mut md5_hash = Md5::new(); let mut entitlements_to_hash = &macho.entitlements; - // if there are not any entitlements in the main Macho, the dylibs of the + // if there are not any entitlements in the main Macho, the entitlements of the // nested file should be hashed if entitlements_to_hash.is_empty() && !macho.file.is_empty() { entitlements_to_hash = &macho.file[0].entitlements; } - // we need to check again as the nested file dylibs could be empty too + // we need to check again as the nested file entitlements could be empty too if entitlements_to_hash.is_empty() { return None; } @@ -335,6 +335,37 @@ fn entitlement_hash(ctx: &mut ScanContext) -> Option { Some(RuntimeString::new(digest)) } +/// Returns an md5 hash of the export symbols in the mach-o binary +#[module_export] +fn export_hash(ctx: &mut ScanContext) -> Option { + let macho = ctx.module_output::()?; + let mut md5_hash = Md5::new(); + let mut exports_to_hash = &macho.exports; + + // if there are not any exports in the main Macho, the exports of the + // nested file should be hashed + if exports_to_hash.is_empty() && !macho.file.is_empty() { + exports_to_hash = &macho.file[0].exports; + } + + // we need to check again as the nested file exports could be empty too + if exports_to_hash.is_empty() { + return None; + } + + let exports_str: String = exports_to_hash + .iter() + .map(|e| e.trim().to_lowercase()) + .unique() + .sorted() + .join(","); + + md5_hash.update(exports_str.as_bytes()); + + let digest = format!("{:x}", md5_hash.finalize()); + Some(RuntimeString::new(digest)) +} + #[module_main] fn main(input: &[u8]) -> Macho { match parser::MachO::parse(input) { diff --git a/lib/src/modules/macho/tests/mod.rs b/lib/src/modules/macho/tests/mod.rs index d37b5d427..4d4449388 100644 --- a/lib/src/modules/macho/tests/mod.rs +++ b/lib/src/modules/macho/tests/mod.rs @@ -364,4 +364,37 @@ fn test_macho_module() { "#, &[] ); + + rule_true!( + r#" + import "macho" + rule macho_test { + condition: + macho.export_hash() == "7f3b75c82e3151fff6c0a55b51cd5b94" + } + "#, + &chess_macho_data + ); + + rule_true!( + r#" + import "macho" + rule macho_test { + condition: + not defined macho.export_hash() + } + "#, + &[] + ); + + rule_true!( + r#" + import "macho" + rule macho_test { + condition: + macho.export_hash() == "6bfc6e935c71039e6e6abf097830dceb" + } + "#, + &tiny_universal_macho_data + ); }